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 rustc_hash::{FxHashMap, FxHashSet};
21
22use crate::MAX_ASYNC_RUNTIME_WORKER_THREADS;
23#[cfg(test)]
24use async_task::Task;
25use async_task::{FallibleTask, Runnable};
26use futures::{
27  FutureExt,
28  future::{AbortHandle, AbortRegistration, Abortable},
29};
30
31#[cfg(not(target_family = "wasm"))]
32use futures::channel::oneshot;
33#[cfg(all(test, not(target_family = "wasm")))]
34use rayon::max_num_threads;
35#[cfg(not(target_family = "wasm"))]
36use rayon::{ThreadPool, ThreadPoolBuilder};
37
38/// Stable scheduler identities are equality capabilities. Exhaustion must fail
39/// before reuse can authorize a stale handle or collide in an indexed queue.
40fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
41  counter
42    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
43    .expect(exhausted)
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum RuntimeFlavor {
48  CurrentThread,
49  MultiThread,
50}
51
52#[derive(Debug, Clone)]
53pub struct RuntimeOptions {
54  pub flavor: RuntimeFlavor,
55  pub worker_threads: usize,
56  pub max_blocking_tasks: usize,
57  pub thread_name_prefix: String,
58  /// Deadline-based `block_on` deadlock detection: bounds how long a
59  /// runtime-owned park (the CurrentThread `block_on` parker and
60  /// MultiThread COOPERATIVE driver parks -- never the foreign/napi
61  /// whole-build park) may sleep with ZERO runtime progress before
62  /// panicking with the typed [`BlockOnDeadlock`] diagnostic. `None` (the
63  /// default) disables deadline detection: a legitimately long park (e.g.
64  /// awaiting a slow JS plugin) must never panic a production build. The
65  /// runtime itself never reads the environment; the embedder resolves its
66  /// own configuration (conventionally a [`PARK_DEADLINE_ENV`]-style
67  /// variable) into this field at addon load. The
68  /// threadless-wasm CERTAIN deadlock check is independent of this knob and
69  /// always on.
70  pub park_deadline: Option<Duration>,
71}
72
73/// A partial update to the runtime options exposed by the binding.
74///
75/// [`configure_partial`] merges these fields into the latest configured
76/// options, validates the merged candidate, and commits it while holding the
77/// runtime controller lock. Fields that are not exposed for partial updates,
78/// such as `thread_name_prefix` and `park_deadline`, remain unchanged.
79#[derive(Debug, Clone, Copy, Default)]
80pub struct RuntimeOptionsPatch {
81  pub flavor: Option<RuntimeFlavor>,
82  pub worker_threads: Option<usize>,
83  pub max_blocking_tasks: Option<usize>,
84}
85
86impl RuntimeOptionsPatch {
87  fn apply_to(self, options: &mut RuntimeOptions) {
88    if let Some(flavor) = self.flavor {
89      options.flavor = flavor;
90    }
91    if let Some(worker_threads) = self.worker_threads {
92      options.worker_threads = worker_threads;
93    }
94    if let Some(max_blocking_tasks) = self.max_blocking_tasks {
95      options.max_blocking_tasks = max_blocking_tasks;
96    }
97  }
98}
99
100impl Default for RuntimeOptions {
101  fn default() -> Self {
102    let worker_threads = std::thread::available_parallelism()
103      .map_or(1, usize::from)
104      .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
105    Self {
106      flavor: if cfg!(target_family = "wasm") {
107        RuntimeFlavor::CurrentThread
108      } else {
109        RuntimeFlavor::MultiThread
110      },
111      worker_threads,
112      max_blocking_tasks: worker_threads,
113      thread_name_prefix: "napi-async-runtime".to_string(),
114      park_deadline: None,
115    }
116  }
117}
118
119impl RuntimeOptions {
120  fn validate(self) -> Result<Self, RuntimeConfigError> {
121    #[cfg(target_family = "wasm")]
122    let rayon_max_threads = None;
123    #[cfg(not(target_family = "wasm"))]
124    let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
125    self.validate_with_rayon_max_threads(rayon_max_threads)
126  }
127
128  fn validate_with_rayon_max_threads(
129    mut self,
130    rayon_max_threads: Option<usize>,
131  ) -> Result<Self, RuntimeConfigError> {
132    if self.worker_threads == 0 {
133      return Err(RuntimeConfigError(
134        "worker_threads must be greater than zero".to_string(),
135      ));
136    }
137    if self.max_blocking_tasks == 0 {
138      return Err(RuntimeConfigError(
139        "max_blocking_tasks must be greater than zero".to_string(),
140      ));
141    }
142    if self.flavor == RuntimeFlavor::CurrentThread {
143      self.worker_threads = 1;
144      self.max_blocking_tasks = 1;
145    } else {
146      let Some(rayon_max_threads) = rayon_max_threads else {
147        return Err(RuntimeConfigError(
148          "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
149        ));
150      };
151      if rayon_max_threads < 2 {
152        return Err(RuntimeConfigError(
153          "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
154        ));
155      }
156      // Blocking closures execute on the shared Rayon pool. Keep one execution
157      // lane available for runnable futures and timer service even when every
158      // admitted blocking job is stalled. MultiThread therefore has a truthful
159      // minimum of two physical/configured workers rather than a hidden reserve.
160      // Apply both this crate's production ceiling and Rayon's platform cap
161      // before deriving the blocking allowance, so a silently reduced physical
162      // pool cannot consume that reserved lane.
163      self.worker_threads = self
164        .worker_threads
165        .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
166        .min(rayon_max_threads);
167      let blocking_capacity = self.worker_threads - 1;
168      self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
169    }
170    Ok(self)
171  }
172}
173
174/// Environment variable that arms deadline-based `block_on` deadlock
175/// detection: milliseconds a runtime-owned park may sleep with zero runtime
176/// progress before panicking (see [`RuntimeOptions::park_deadline`]).
177/// Missing, non-numeric or `0` all mean "disabled". The runtime does NOT
178/// read this variable itself: the embedding host's env-resolution parses it
179/// once at addon load and hands the result to [`configure`] through
180/// [`RuntimeOptions::park_deadline`]. The name is the conventional default;
181/// hosts may resolve their own variable into the same field.
182pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
183
184/// True on builds where no second thread can EVER deliver a wake to a parked
185/// `block_on`: every wasm build except the threaded WASI target (i.e. the
186/// single-thread `wasm32-wasip1` build and `wasm32-unknown-unknown`). On such
187/// a build a CurrentThread park decision with an empty queue and no pending
188/// wake token is a PROVABLE deadlock. The threaded wasi build
189/// (`wasm32-wasip1-threads`) has real OS threads, so its parks are not
190/// provably dead and fall under the optional deadline detection instead, like
191/// native builds.
192///
193/// `napi_runtime_wasi_threads` is emitted by this crate's build.rs for the exact
194/// `wasm32-wasip1-threads` cargo TARGET. It is NOT derivable from built-in
195/// cfgs: on current rustc the two WASI targets expose identical cfg sets --
196/// `cfg!(target_feature = "atomics")` is false even on the threads target
197/// (verified empirically; embedders needing the same discrimination use the
198/// same build.rs mechanism).
199const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
200
201#[derive(Debug, Clone)]
202pub struct RuntimeConfigError(String);
203
204impl fmt::Display for RuntimeConfigError {
205  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206    f.write_str(&self.0)
207  }
208}
209
210impl std::error::Error for RuntimeConfigError {}
211
212/// Which self-detected `block_on` deadlock class fired.
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum BlockOnDeadlockKind {
215  /// CurrentThread on a threadless build: the park decision found an empty
216  /// queue and no pending wake token, and no other thread exists that could
217  /// ever deliver one -- a PROVABLE deadlock, detected unconditionally (not
218  /// timing-based).
219  CurrentThreadCertain,
220  /// [`Self::CurrentThreadCertain`] detected while a HOST timer was pending.
221  /// The timer does not make the park any less dead on a threadless build:
222  /// its host callback (the JS `setTimeout` relay) can only run on the very
223  /// thread that is about to park, so it can never fire while `block_on`
224  /// holds that thread. A distinct kind only so the diagnostic can name the
225  /// shape the user actually hit.
226  CurrentThreadCertainTimerBacked,
227  /// Threaded CurrentThread: a `block_on` park outlived the configured
228  /// deadline with zero runtime progress.
229  CurrentThreadDeadline,
230  /// MultiThread: a cooperative pool driver's re-entrant `block_on` park
231  /// outlived the configured deadline with zero executor progress. The
232  /// foreign/napi whole-build park is exempt by design.
233  MultiThreadCooperativeDeadline,
234}
235
236/// Typed panic payload for a self-detected `block_on` deadlock. Thrown with
237/// `std::panic::panic_any` at the park DECISION (never merely on a `Pending`
238/// return, because a self-waking future is not a deadlock), so the
239/// runtime fails loudly instead of freezing the thread (and, in the
240/// CurrentThread-on-JS-thread case, the whole JS event loop, where not even
241/// test-harness timeouts can fire). When the panic unwinds out of a spawned
242/// task's poll, `JoinError::from_panic` preserves this diagnostic's message.
243#[derive(Debug, Clone)]
244pub struct BlockOnDeadlock {
245  pub kind: BlockOnDeadlockKind,
246  /// The armed deadline for the deadline-based kinds; `None` for the provable
247  /// threadless case.
248  pub park_deadline: Option<Duration>,
249}
250
251impl BlockOnDeadlock {
252  fn current_thread_certain() -> Self {
253    Self {
254      kind: BlockOnDeadlockKind::CurrentThreadCertain,
255      park_deadline: None,
256    }
257  }
258
259  fn current_thread_certain_timer_backed() -> Self {
260    Self {
261      kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
262      park_deadline: None,
263    }
264  }
265
266  fn current_thread_deadline(deadline: Duration) -> Self {
267    Self {
268      kind: BlockOnDeadlockKind::CurrentThreadDeadline,
269      park_deadline: Some(deadline),
270    }
271  }
272
273  // The MultiThread executor (and thus this constructor's caller) does not
274  // exist on wasm builds.
275  #[cfg(not(target_family = "wasm"))]
276  fn multi_thread_cooperative(deadline: Duration) -> Self {
277    Self {
278      kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
279      park_deadline: Some(deadline),
280    }
281  }
282}
283
284impl fmt::Display for BlockOnDeadlock {
285  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286    let deadline_ms = self
287      .park_deadline
288      .map_or(0, |deadline| deadline.as_millis());
289    match self.kind {
290      BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
291        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
292         park with an empty task queue and no pending wake on a threadless build -- no other \
293         thread exists that could ever deliver the wake, so this park would never end. This is \
294         the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
295         (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
296         dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
297         continuation is the one now parking.",
298      ),
299      BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
300        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
301         park with an empty task queue on a threadless build while a host timer is pending. \
302         The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
303         only run on the very thread that is now parking, so it can never fire, and no other \
304         thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
305         hazard: native code called `block_on` on a future that (transitively) awaits a JS \
306         continuation, but the only thread that could run that continuation is the one now \
307         parking.",
308      ),
309      BlockOnDeadlockKind::CurrentThreadDeadline => write!(
310        f,
311        "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
312         {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
313         task queue, no pending wake and ZERO runtime progress across the whole window. This is \
314         the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
315         awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
316         once this thread returns to the event loop. If this park is legitimate, raise or unset \
317         the deadline.",
318      ),
319      BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
320        f,
321        "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
322         parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
323         no runnable or blocking work available and ZERO executor progress across the whole \
324         window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
325         The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
326         If this park is legitimate, raise or unset the deadline.",
327      ),
328    }
329  }
330}
331
332impl std::error::Error for BlockOnDeadlock {}
333
334#[derive(Debug)]
335pub struct JoinError {
336  message: String,
337}
338
339impl JoinError {
340  fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
341    Self {
342      // The typed deadlock diagnostic first: a deadline firing inside a
343      // spawned task's poll unwinds into the spawn wrapper's catch_unwind and
344      // surfaces here -- the diagnostic text must survive the trip.
345      message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
346        deadlock.to_string()
347      } else if let Some(message) = panic.downcast_ref::<&str>() {
348        (*message).to_string()
349      } else if let Some(message) = panic.downcast_ref::<String>() {
350        message.clone()
351      } else {
352        "async runtime task panicked".to_string()
353      },
354    }
355  }
356}
357
358impl fmt::Display for JoinError {
359  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360    f.write_str(&self.message)
361  }
362}
363
364impl std::error::Error for JoinError {}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367enum BlockOnOutcome {
368  Completed,
369  Stopped,
370}
371
372fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
373  // The original unwind is already caught, so ordinary payloads should be
374  // destroyed normally. A hostile payload destructor can panic again; catch
375  // that nested unwind and leak only its new payload, whose destructor is
376  // likewise untrusted.
377  if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
378    std::mem::forget(nested_payload);
379  }
380}
381
382#[derive(Default)]
383struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
384
385impl RetainedPanicPayload {
386  fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
387    if self.0.is_some() {
388      discard_panic_payload(payload);
389    } else {
390      self.0 = Some(payload);
391    }
392  }
393
394  fn is_some(&self) -> bool {
395    self.0.is_some()
396  }
397
398  fn resume(mut self) -> ! {
399    resume_unwind(
400      self
401        .0
402        .take()
403        .expect("a retained panic payload must remain owned"),
404    )
405  }
406}
407
408impl Drop for RetainedPanicPayload {
409  fn drop(&mut self) {
410    if let Some(payload) = self.0.take() {
411      discard_panic_payload(payload);
412    }
413  }
414}
415
416fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
417  match catch_unwind(AssertUnwindSafe(function)) {
418    Ok(value) => Some(value),
419    Err(payload) => {
420      discard_panic_payload(payload);
421      None
422    }
423  }
424}
425
426struct ContainedArc<T: ?Sized> {
427  value: Option<Arc<T>>,
428}
429
430impl<T: ?Sized> ContainedArc<T> {
431  fn new(value: Arc<T>) -> Self {
432    Self { value: Some(value) }
433  }
434
435  fn get(&self) -> &T {
436    self
437      .value
438      .as_deref()
439      .expect("the contained Arc must remain owned")
440  }
441
442  #[cfg(test)]
443  fn into_inner(mut self) -> Arc<T> {
444    self
445      .value
446      .take()
447      .expect("the contained Arc must remain owned")
448  }
449}
450
451impl<T: ?Sized> Drop for ContainedArc<T> {
452  fn drop(&mut self) {
453    if let Some(value) = self.value.take() {
454      let _ = catch_unwind_contained(|| drop(value));
455    }
456  }
457}
458
459struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
460  values: Vec<(I, Arc<T>)>,
461}
462
463impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
464  fn new(values: Vec<(I, Arc<T>)>) -> Self {
465    Self { values }
466  }
467
468  fn as_slice(&self) -> &[(I, Arc<T>)] {
469    &self.values
470  }
471
472  fn len(&self) -> usize {
473    self.values.len()
474  }
475
476  fn is_empty(&self) -> bool {
477    self.values.is_empty()
478  }
479
480  fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
481    self
482      .values
483      .pop()
484      .map(|(id, value)| (id, ContainedArc::new(value)))
485  }
486}
487
488impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
489  fn drop(&mut self) {
490    while let Some((_, value)) = self.values.pop() {
491      let _ = catch_unwind_contained(|| drop(value));
492    }
493  }
494}
495
496struct PanicContainedWaker {
497  waker: ManuallyDrop<Waker>,
498  generation: Option<u64>,
499}
500
501impl std::task::Wake for PanicContainedWaker {
502  fn wake(self: Arc<Self>) {
503    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
504    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
505  }
506
507  fn wake_by_ref(self: &Arc<Self>) {
508    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
509    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
510  }
511}
512
513impl Drop for PanicContainedWaker {
514  fn drop(&mut self) {
515    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
516    let _ = catch_unwind_contained(|| {
517      // SAFETY: this is the sole destruction path for the underlying waker.
518      unsafe { ManuallyDrop::drop(&mut self.waker) };
519    });
520  }
521}
522
523fn panic_contained_waker(waker: Waker) -> Waker {
524  Waker::from(Arc::new(PanicContainedWaker {
525    waker: ManuallyDrop::new(waker),
526    generation: active_runtime_generation(),
527  }))
528}
529
530struct PanicContainedWakerSource {
531  waker: ManuallyDrop<Waker>,
532  generation: Option<u64>,
533}
534
535impl PanicContainedWakerSource {
536  fn new(waker: Waker) -> Self {
537    Self {
538      waker: ManuallyDrop::new(waker),
539      generation: active_runtime_generation(),
540    }
541  }
542
543  fn as_waker(&self) -> &Waker {
544    &self.waker
545  }
546}
547
548impl Drop for PanicContainedWakerSource {
549  fn drop(&mut self) {
550    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
551    let _ = catch_unwind_contained(|| {
552      // SAFETY: this is the sole destruction path for the source waker.
553      unsafe { ManuallyDrop::drop(&mut self.waker) };
554    });
555  }
556}
557
558struct PanicContainedWakerCache {
559  source: PanicContainedWakerSource,
560  contained: Waker,
561}
562
563impl PanicContainedWakerCache {
564  fn new(waker: &Waker) -> Self {
565    let generation = active_runtime_generation();
566    let _generation = RuntimeGenerationGuard::enter_optional(generation);
567    let source = PanicContainedWakerSource::new(waker.clone());
568    let contained = panic_contained_waker(source.as_waker().clone());
569    Self { source, contained }
570  }
571
572  fn update(&mut self, waker: &Waker) {
573    let will_wake = {
574      let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
575      self.source.as_waker().will_wake(waker)
576    };
577    if !will_wake {
578      *self = Self::new(waker);
579    }
580  }
581
582  fn contained(&self) -> &Waker {
583    &self.contained
584  }
585}
586
587fn wake_waker_contained(waker: Waker) {
588  // `Waker::wake` consumes the waker, so a panicking wake followed by a
589  // panicking RawWaker destructor would double-panic before the outer catch
590  // could regain control. Borrow for the wake and contain destruction
591  // separately.
592  let _ = catch_unwind_contained(|| waker.wake_by_ref());
593  let _ = catch_unwind_contained(|| drop(waker));
594}
595
596fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
597  match catch_unwind(AssertUnwindSafe(function)) {
598    Ok(value) => Ok(value),
599    Err(payload) => {
600      let error = JoinError::from_panic(&*payload);
601      discard_panic_payload(payload);
602      Err(error)
603    }
604  }
605}
606
607fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
608  let error = JoinError::from_panic(&*payload);
609  discard_panic_payload(payload);
610  error
611}
612
613struct ContainedTaskOutput<T> {
614  output: ManuallyDrop<T>,
615  generation: u64,
616}
617
618impl<T> ContainedTaskOutput<T> {
619  fn new(output: T, generation: u64) -> Self {
620    Self {
621      output: ManuallyDrop::new(output),
622      generation,
623    }
624  }
625
626  fn into_inner(mut self) -> T {
627    // SAFETY: ownership moves to the JoinHandle caller and `self` is then
628    // forgotten so its Drop implementation cannot run a second time.
629    let output = unsafe { ManuallyDrop::take(&mut self.output) };
630    std::mem::forget(self);
631    output
632  }
633}
634
635impl<T> Drop for ContainedTaskOutput<T> {
636  fn drop(&mut self) {
637    let _generation = RuntimeGenerationGuard::enter(self.generation);
638    let _ = catch_unwind_contained(|| {
639      // SAFETY: this is the sole destruction path while the output remains
640      // retained by a JoinHandle.
641      unsafe { ManuallyDrop::drop(&mut self.output) };
642    });
643  }
644}
645
646#[cfg(not(target_family = "wasm"))]
647#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
648struct BlockingJobId(u64);
649
650#[cfg(not(target_family = "wasm"))]
651static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
652
653#[cfg(all(test, not(target_family = "wasm")))]
654thread_local! {
655  static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
656    const { std::cell::RefCell::new(None) };
657  static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
658    const { std::cell::RefCell::new(None) };
659  static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
660    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
661      const { std::cell::RefCell::new(None) };
662  static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
663    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
664      const { std::cell::RefCell::new(None) };
665  static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
666    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
667      const { std::cell::RefCell::new(None) };
668  static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
669    const { std::cell::RefCell::new(None) };
670  static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
671    const { std::cell::RefCell::new(None) };
672  static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
673    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
674      const { std::cell::RefCell::new(None) };
675  static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
676    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
677      const { std::cell::RefCell::new(None) };
678  static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
679    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
680      const { std::cell::RefCell::new(None) };
681  static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
682    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
683      const { std::cell::RefCell::new(None) };
684  static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
685    const { std::cell::RefCell::new(None) };
686  static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
687    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
688      const { std::cell::RefCell::new(None) };
689  static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
690    const { std::cell::RefCell::new(None) };
691}
692
693#[cfg(all(test, not(target_family = "wasm")))]
694struct WorkerTlsDropProbe {
695  action: Option<WorkerTlsDropAction>,
696}
697
698// LOADER-LOCK RULE for every probe action below: TLS destructors run at
699// thread exit -- on Windows inside `LdrShutdownThread`, which HOLDS the
700// process loader lock while walking TLS callbacks. Any cross-thread wait
701// here (condvar, channel recv, barrier, joining a thread) therefore freezes
702// every other `thread::spawn` and thread-exit in the process -- and, with
703// RUST_BACKTRACE=1, even panic reporting (dbghelp loads DLLs under the same
704// lock). A previous `Wait { entered, release }` action parked the FIRST
705// exiting worker's destructor on a condvar; the SECOND worker then could not
706// even enter its destructor (loader lock), the release condition (both
707// entered) became unsatisfiable, and the whole test suite went silent until
708// the CI step timeout (Windows x64 jobs, runs 29195160953 / 86662331487).
709// Destructor actions may only RECORD (atomic stores, non-blocking sends);
710// ordering invariants are asserted from OUTSIDE, after `shutdown()` returns.
711#[cfg(all(test, not(target_family = "wasm")))]
712enum WorkerTlsDropAction {
713  /// Record-only: bump the shared counter so the harness can assert -- the
714  /// instant `shutdown()` returns -- that every worker TLS destructor had
715  /// already completed (the join barrier includes destructor completion).
716  Record { completed: Arc<AtomicUsize> },
717  ReenterLifecycle {
718    controller: Arc<RuntimeController>,
719    completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
720  },
721}
722
723#[cfg(all(test, not(target_family = "wasm")))]
724impl Drop for WorkerTlsDropProbe {
725  fn drop(&mut self) {
726    match self
727      .action
728      .take()
729      .expect("worker TLS drop probe action already consumed")
730    {
731      WorkerTlsDropAction::Record { completed } => {
732        completed.fetch_add(1, Ordering::SeqCst);
733      }
734      // Both lifecycle re-entries FAIL FAST from a stopping-generation worker
735      // (short state-mutex acquisitions, no cross-thread wait), and the send
736      // is non-blocking -- loader-lock safe. The owning test additionally
737      // isolates itself in a watchdogged subprocess.
738      WorkerTlsDropAction::ReenterLifecycle {
739        controller,
740        completed,
741      } => {
742        let start = controller.start().map_err(|error| error.to_string());
743        let shutdown = controller.shutdown().map_err(|error| error.to_string());
744        let _ = completed.send((start, shutdown));
745      }
746    }
747  }
748}
749
750#[cfg(all(test, not(target_family = "wasm")))]
751fn run_dependency_claim_test_hook() {
752  if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
753    hook();
754  }
755}
756
757#[cfg(all(test, not(target_family = "wasm")))]
758fn run_current_thread_dispatch_claim_test_hook() {
759  if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
760  {
761    hook();
762  }
763}
764
765#[cfg(all(test, not(target_family = "wasm")))]
766fn run_current_thread_dispatch_publication_blocked_test_hook() {
767  if let Some(hook) =
768    CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
769  {
770    hook();
771  }
772}
773
774#[cfg(all(test, not(target_family = "wasm")))]
775fn run_current_thread_dispatch_recovery_test_hook() {
776  if let Some(hook) =
777    CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
778  {
779    hook();
780  }
781}
782
783#[cfg(all(test, not(target_family = "wasm")))]
784fn run_current_thread_failed_publication_retired_test_hook() {
785  if let Some(hook) =
786    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
787  {
788    hook();
789  }
790}
791
792#[cfg(all(test, not(target_family = "wasm")))]
793fn run_current_thread_dispatch_call_test_hook() {
794  if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
795    hook();
796  }
797}
798
799#[cfg(all(test, not(target_family = "wasm")))]
800fn run_current_thread_dispatch_returned_test_hook() {
801  if let Some(hook) =
802    CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
803  {
804    hook();
805  }
806}
807
808#[cfg(all(test, not(target_family = "wasm")))]
809fn run_current_thread_blocking_claim_test_hook() {
810  if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
811  {
812    hook();
813  }
814}
815
816#[cfg(all(test, not(target_family = "wasm")))]
817fn run_current_thread_runnable_claim_test_hook() {
818  if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
819  {
820    hook();
821  }
822}
823
824#[cfg(all(test, not(target_family = "wasm")))]
825fn run_multi_thread_work_claimed_test_hook() {
826  if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
827    hook();
828  }
829}
830
831#[cfg(all(test, not(target_family = "wasm")))]
832fn run_multi_thread_work_started_test_hook() {
833  if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
834    hook();
835  }
836}
837
838#[cfg(all(test, not(target_family = "wasm")))]
839fn run_dependency_predicate_test_hook() {
840  if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
841    hook();
842  }
843}
844
845#[cfg(all(test, not(target_family = "wasm")))]
846fn run_before_owned_blocking_queue_claim_test_hook() {
847  if let Some(hook) =
848    BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
849  {
850    hook();
851  }
852}
853
854#[cfg(not(target_family = "wasm"))]
855#[derive(Clone, Copy, Debug, PartialEq, Eq)]
856struct BlockingDependency {
857  job: BlockingJobId,
858  owner: Option<BlockingOwnerToken>,
859}
860
861#[cfg(not(target_family = "wasm"))]
862#[derive(Debug)]
863struct DependencyClaim {
864  claimed: AtomicBool,
865  transition: Mutex<()>,
866}
867
868#[cfg(not(target_family = "wasm"))]
869impl DependencyClaim {
870  fn new() -> Self {
871    Self {
872      claimed: AtomicBool::new(false),
873      transition: Mutex::new(()),
874    }
875  }
876
877  fn is_live(&self) -> bool {
878    !self.claimed.load(Ordering::Acquire)
879  }
880
881  fn cancel_link(&self, link: &DependencyLink) {
882    let _transition = self
883      .transition
884      .lock()
885      .unwrap_or_else(std::sync::PoisonError::into_inner);
886    link.cancel();
887  }
888
889  fn try_claim(&self, link: &DependencyLink) -> bool {
890    let _transition = self
891      .transition
892      .lock()
893      .unwrap_or_else(std::sync::PoisonError::into_inner);
894    #[cfg(test)]
895    run_dependency_claim_test_hook();
896    if self.claimed.load(Ordering::Acquire) || !link.is_live() {
897      return false;
898    }
899    self.claimed.store(true, Ordering::Release);
900    true
901  }
902}
903
904#[cfg(not(target_family = "wasm"))]
905#[derive(Debug)]
906struct DependencyLink {
907  live: AtomicBool,
908  parent: Option<Weak<DependencyLink>>,
909}
910
911#[cfg(not(target_family = "wasm"))]
912impl DependencyLink {
913  fn root() -> Arc<Self> {
914    Arc::new(Self {
915      live: AtomicBool::new(true),
916      parent: None,
917    })
918  }
919
920  fn derived(parent: &Arc<Self>) -> Arc<Self> {
921    Arc::new(Self {
922      live: AtomicBool::new(true),
923      parent: Some(Arc::downgrade(parent)),
924    })
925  }
926
927  fn is_live(&self) -> bool {
928    if !self.live.load(Ordering::Acquire) {
929      return false;
930    }
931    let Some(parent) = &self.parent else {
932      return true;
933    };
934    let Some(mut current) = parent.upgrade() else {
935      return false;
936    };
937    loop {
938      if !current.live.load(Ordering::Acquire) {
939        return false;
940      }
941      let Some(parent) = &current.parent else {
942        return true;
943      };
944      let Some(next) = parent.upgrade() else {
945        return false;
946      };
947      current = next;
948    }
949  }
950
951  fn cancel(&self) {
952    self.live.store(false, Ordering::Release);
953  }
954}
955
956#[cfg(not(target_family = "wasm"))]
957#[derive(Clone, Copy, PartialEq, Eq, Hash)]
958struct DependencyPublicationIdentity {
959  job: BlockingJobId,
960  claim: *const DependencyClaim,
961  link: *const DependencyLink,
962}
963
964#[cfg(not(target_family = "wasm"))]
965#[derive(Clone, Debug)]
966struct DependencyPublication {
967  dependency: BlockingDependency,
968  claim: Arc<DependencyClaim>,
969  link: Arc<DependencyLink>,
970}
971
972#[cfg(not(target_family = "wasm"))]
973impl DependencyPublication {
974  fn owned(dependency: BlockingDependency) -> Self {
975    Self {
976      dependency,
977      claim: Arc::new(DependencyClaim::new()),
978      link: DependencyLink::root(),
979    }
980  }
981
982  fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
983    Self {
984      dependency: BlockingDependency {
985        owner: self.dependency.owner.or(owner),
986        ..self.dependency
987      },
988      claim: Arc::clone(&self.claim),
989      link: DependencyLink::derived(&self.link),
990    }
991  }
992
993  fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
994    self.dependency.owner = self.dependency.owner.or(owner);
995    self
996  }
997
998  fn same_as(&self, other: &Self) -> bool {
999    self.dependency == other.dependency
1000      && Arc::ptr_eq(&self.claim, &other.claim)
1001      && Arc::ptr_eq(&self.link, &other.link)
1002  }
1003
1004  fn identity(&self) -> DependencyPublicationIdentity {
1005    DependencyPublicationIdentity {
1006      job: self.dependency.job,
1007      claim: Arc::as_ptr(&self.claim),
1008      link: Arc::as_ptr(&self.link),
1009    }
1010  }
1011
1012  fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1013    Some(DependencyPublicationIdentity {
1014      job: self.dependency.job,
1015      claim: Arc::as_ptr(&self.claim),
1016      link: self.link.parent.as_ref()?.as_ptr(),
1017    })
1018  }
1019
1020  fn is_derived_from(&self, source: &Self) -> bool {
1021    self.immediate_source_identity() == Some(source.identity())
1022  }
1023
1024  fn is_live(&self) -> bool {
1025    self.claim.is_live() && self.link.is_live()
1026  }
1027
1028  fn cancel(&self) {
1029    self.claim.cancel_link(&self.link);
1030  }
1031
1032  fn try_claim(&self) -> bool {
1033    self.claim.try_claim(&self.link)
1034  }
1035}
1036
1037#[cfg(not(target_family = "wasm"))]
1038struct TaskDependencyEntry {
1039  sequence: u64,
1040  publication: DependencyPublication,
1041}
1042
1043#[cfg(not(target_family = "wasm"))]
1044struct SelectedDependencyPublication {
1045  sequence: u64,
1046  publication: DependencyPublication,
1047}
1048
1049#[cfg(not(target_family = "wasm"))]
1050#[derive(Clone, Copy)]
1051enum OwnerDependencyMode {
1052  Available,
1053  Exact,
1054}
1055
1056#[cfg(not(target_family = "wasm"))]
1057#[derive(Default)]
1058struct TaskDependencyState {
1059  current: VecDeque<TaskDependencyEntry>,
1060  next_sequence: u64,
1061  unowned_sequences: VecDeque<u64>,
1062  owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1063  job_sequences: FxHashMap<BlockingJobId, FxHashSet<u64>>,
1064  waiter: Option<Waker>,
1065}
1066
1067#[cfg(not(target_family = "wasm"))]
1068impl TaskDependencyState {
1069  fn append(&mut self, publication: DependencyPublication) {
1070    let sequence = self.next_sequence;
1071    self.next_sequence = self
1072      .next_sequence
1073      .checked_add(1)
1074      .expect("task dependency sequence space exhausted");
1075    match publication.dependency.owner {
1076      Some(owner) => self
1077        .owner_sequences
1078        .entry(owner)
1079        .or_default()
1080        .push_back(sequence),
1081      None => self.unowned_sequences.push_back(sequence),
1082    }
1083    self
1084      .job_sequences
1085      .entry(publication.dependency.job)
1086      .or_default()
1087      .insert(sequence);
1088    self.current.push_back(TaskDependencyEntry {
1089      sequence,
1090      publication,
1091    });
1092  }
1093
1094  fn entry_in(
1095    current: &VecDeque<TaskDependencyEntry>,
1096    sequence: u64,
1097  ) -> Option<&TaskDependencyEntry> {
1098    let front = current.front()?.sequence;
1099    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1100    let entry = current.get(index)?;
1101    (entry.sequence == sequence).then_some(entry)
1102  }
1103
1104  fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1105    Self::entry_in(&self.current, sequence)
1106  }
1107
1108  fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1109    let front = self.current.front()?.sequence;
1110    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1111    let entry = self.current.get_mut(index)?;
1112    (entry.sequence == sequence).then_some(entry)
1113  }
1114
1115  fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1116    match dependency.owner {
1117      Some(owner) => {
1118        let remove_owner = self
1119          .owner_sequences
1120          .get_mut(&owner)
1121          .is_some_and(|sequences| {
1122            if sequences.front() == Some(&sequence) {
1123              sequences.pop_front();
1124            }
1125            sequences.is_empty()
1126          });
1127        if remove_owner {
1128          self.owner_sequences.remove(&owner);
1129        }
1130      }
1131      None => {
1132        if self.unowned_sequences.front() == Some(&sequence) {
1133          self.unowned_sequences.pop_front();
1134        }
1135      }
1136    }
1137    if let Some(sequences) = self.job_sequences.get_mut(&dependency.job) {
1138      sequences.remove(&sequence);
1139      if sequences.is_empty() {
1140        self.job_sequences.remove(&dependency.job);
1141      }
1142    }
1143  }
1144
1145  fn clear_indexes(&mut self) {
1146    self.unowned_sequences.clear();
1147    self.owner_sequences.clear();
1148    self.job_sequences.clear();
1149  }
1150}
1151
1152#[cfg(not(target_family = "wasm"))]
1153struct TaskDependency {
1154  state: Mutex<TaskDependencyState>,
1155  owner_hint: Option<BlockingOwnerToken>,
1156  has_current: AtomicBool,
1157  generation: u64,
1158  #[cfg(test)]
1159  entry_visits: AtomicUsize,
1160}
1161
1162#[cfg(not(target_family = "wasm"))]
1163impl Default for TaskDependency {
1164  fn default() -> Self {
1165    Self::new(u64::MAX, None)
1166  }
1167}
1168
1169#[cfg(not(target_family = "wasm"))]
1170impl TaskDependency {
1171  fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1172    Self {
1173      state: Mutex::new(TaskDependencyState::default()),
1174      owner_hint,
1175      has_current: AtomicBool::new(false),
1176      generation,
1177      #[cfg(test)]
1178      entry_visits: AtomicUsize::new(0),
1179    }
1180  }
1181
1182  fn new_task(generation: u64) -> Self {
1183    Self::new(generation, None)
1184  }
1185
1186  fn notify_waiter(&self, waiter: Option<Waker>) {
1187    if let Some(waiter) = waiter {
1188      let _generation = RuntimeGenerationGuard::enter(self.generation);
1189      wake_waker_contained(waiter);
1190    }
1191  }
1192
1193  fn clear_waiter(&self) {
1194    let waiter = self
1195      .state
1196      .lock()
1197      .unwrap_or_else(std::sync::PoisonError::into_inner)
1198      .waiter
1199      .take();
1200    if let Some(waiter) = waiter {
1201      let _generation = RuntimeGenerationGuard::enter(self.generation);
1202      let _ = catch_unwind_contained(|| drop(waiter));
1203    }
1204  }
1205
1206  fn clear(&self) {
1207    let waiter = {
1208      let mut state = self
1209        .state
1210        .lock()
1211        .unwrap_or_else(std::sync::PoisonError::into_inner);
1212      if state.current.is_empty() {
1213        None
1214      } else {
1215        for current in state.current.drain(..) {
1216          current.publication.cancel();
1217        }
1218        state.clear_indexes();
1219        self.has_current.store(false, Ordering::Release);
1220        state.waiter.take()
1221      }
1222    };
1223    self.notify_waiter(waiter);
1224  }
1225
1226  fn clear_if_dependency(&self, dependency: BlockingDependency) {
1227    let waiter = {
1228      let mut state = self
1229        .state
1230        .lock()
1231        .unwrap_or_else(std::sync::PoisonError::into_inner);
1232      let mut removed = false;
1233      if let Some(sequences) = state.job_sequences.remove(&dependency.job) {
1234        for sequence in sequences {
1235          if let Some(current) = state.entry_mut(sequence) {
1236            current.publication.cancel();
1237            removed = true;
1238          }
1239        }
1240      }
1241      if removed {
1242        self.prune_stale_prefix(&mut state);
1243        state.waiter.take()
1244      } else {
1245        None
1246      }
1247    };
1248    self.notify_waiter(waiter);
1249  }
1250
1251  fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1252    let Some(first_publication) = publications.first() else {
1253      return;
1254    };
1255    let publication_identities = (publications.len() > 1).then(|| {
1256      publications
1257        .iter()
1258        .map(DependencyPublication::identity)
1259        .collect::<FxHashSet<_>>()
1260    });
1261    let waiter = {
1262      let mut state = self
1263        .state
1264        .lock()
1265        .unwrap_or_else(std::sync::PoisonError::into_inner);
1266      let removed = state
1267        .current
1268        .iter_mut()
1269        .filter_map(|current| {
1270          self.note_entry_visit();
1271          let is_derived = publication_identities.as_ref().map_or_else(
1272            || current.publication.is_derived_from(first_publication),
1273            |identities| {
1274              current
1275                .publication
1276                .immediate_source_identity()
1277                .is_some_and(|identity| identities.contains(&identity))
1278            },
1279          );
1280          if !is_derived {
1281            return None;
1282          }
1283          current.publication.cancel();
1284          Some((current.sequence, current.publication.dependency.job))
1285        })
1286        .collect::<Vec<_>>();
1287      for (sequence, job) in &removed {
1288        if let Some(sequences) = state.job_sequences.get_mut(job) {
1289          sequences.remove(sequence);
1290          if sequences.is_empty() {
1291            state.job_sequences.remove(job);
1292          }
1293        }
1294      }
1295      if removed.is_empty() {
1296        None
1297      } else {
1298        self.prune_stale_prefix(&mut state);
1299        state.waiter.take()
1300      }
1301    };
1302    self.notify_waiter(waiter);
1303  }
1304
1305  fn set_owned(&self, dependency: BlockingDependency) {
1306    let publication = DependencyPublication::owned(BlockingDependency {
1307      owner: dependency.owner.or(self.owner_hint),
1308      ..dependency
1309    });
1310    self.set_entry(publication);
1311  }
1312
1313  fn set_borrowed(&self, publication: &DependencyPublication) {
1314    self.set_entry(publication.derived(self.owner_hint));
1315  }
1316
1317  fn set_entry(&self, publication: DependencyPublication) {
1318    let waiter = {
1319      let mut state = self
1320        .state
1321        .lock()
1322        .unwrap_or_else(std::sync::PoisonError::into_inner);
1323      // Owned publications and propagated publications both create a fresh
1324      // local liveness link, so identity cannot already exist in this state.
1325      state.append(publication);
1326      self.has_current.store(true, Ordering::Release);
1327      state.waiter.take()
1328    };
1329    self.notify_waiter(waiter);
1330  }
1331
1332  fn note_entry_visit(&self) {
1333    #[cfg(test)]
1334    self.entry_visits.fetch_add(1, Ordering::Relaxed);
1335  }
1336
1337  fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1338    while state.current.front().is_some_and(|current| {
1339      self.note_entry_visit();
1340      !current.publication.is_live()
1341    }) {
1342      let current = state
1343        .current
1344        .pop_front()
1345        .expect("the stale prefix entry must exist");
1346      state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1347    }
1348    self
1349      .has_current
1350      .store(!state.current.is_empty(), Ordering::Release);
1351  }
1352
1353  fn first_live_sequence(
1354    &self,
1355    current: &VecDeque<TaskDependencyEntry>,
1356    sequences: &mut VecDeque<u64>,
1357  ) -> Option<u64> {
1358    loop {
1359      let sequence = *sequences.front()?;
1360      let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1361        sequences.pop_front();
1362        continue;
1363      };
1364      self.note_entry_visit();
1365      if entry.publication.is_live() {
1366        return Some(sequence);
1367      }
1368      sequences.pop_front();
1369    }
1370  }
1371
1372  fn first_live_owner_sequence(
1373    &self,
1374    state: &mut TaskDependencyState,
1375    owner: BlockingOwnerToken,
1376  ) -> Option<u64> {
1377    let sequence = {
1378      let current = &state.current;
1379      state
1380        .owner_sequences
1381        .get_mut(&owner)
1382        .and_then(|sequences| self.first_live_sequence(current, sequences))
1383    };
1384    if sequence.is_none() {
1385      state.owner_sequences.remove(&owner);
1386    }
1387    sequence
1388  }
1389
1390  fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1391    self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1392  }
1393
1394  #[cfg(test)]
1395  fn get(&self) -> Option<DependencyPublication> {
1396    self
1397      .state
1398      .lock()
1399      .unwrap_or_else(std::sync::PoisonError::into_inner)
1400      .current
1401      .iter()
1402      .find(|current| current.publication.is_live())
1403      .map(|current| current.publication.clone())
1404  }
1405
1406  fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1407    self
1408      .state
1409      .lock()
1410      .unwrap_or_else(std::sync::PoisonError::into_inner)
1411      .current
1412      .iter()
1413      .filter(|current| {
1414        self.note_entry_visit();
1415        current.publication.is_live()
1416      })
1417      .map(|current| current.publication.clone())
1418      .collect()
1419  }
1420
1421  fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1422    let mut state = self
1423      .state
1424      .lock()
1425      .unwrap_or_else(std::sync::PoisonError::into_inner);
1426    self.prune_stale_prefix(&mut state);
1427    let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1428    let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1429    let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1430      (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1431      (Some(_) | None, Some(unowned)) => (unowned, true),
1432      (Some(exact), None) => (exact, false),
1433      (None, None) => return None,
1434    };
1435    if bind_owner {
1436      let removed = state.unowned_sequences.pop_front();
1437      debug_assert_eq!(removed, Some(sequence));
1438      let current = state
1439        .entry_mut(sequence)
1440        .expect("the selected dependency must remain indexed");
1441      debug_assert_eq!(current.publication.dependency.owner, None);
1442      current.publication.dependency.owner = Some(owner);
1443      // A previous exact-owner query may have observed no entry. Publishing
1444      // this sequence at the front makes the newly bound dependency visible
1445      // immediately without restarting a global scan.
1446      state
1447        .owner_sequences
1448        .entry(owner)
1449        .or_default()
1450        .push_front(sequence);
1451    }
1452    let publication = state
1453      .entry(sequence)
1454      .expect("the selected dependency must remain indexed")
1455      .publication
1456      .clone();
1457    Some(SelectedDependencyPublication {
1458      sequence,
1459      publication,
1460    })
1461  }
1462
1463  fn has_live_owner_dependency(
1464    &self,
1465    owner: BlockingOwnerToken,
1466    mode: OwnerDependencyMode,
1467  ) -> bool {
1468    #[cfg(all(test, not(target_family = "wasm")))]
1469    run_dependency_predicate_test_hook();
1470    let mut state = self
1471      .state
1472      .lock()
1473      .unwrap_or_else(std::sync::PoisonError::into_inner);
1474    self.prune_stale_prefix(&mut state);
1475    if self.first_live_owner_sequence(&mut state, owner).is_some() {
1476      return true;
1477    }
1478    matches!(mode, OwnerDependencyMode::Available)
1479      && self.first_live_unowned_sequence(&mut state).is_some()
1480  }
1481
1482  #[cfg(test)]
1483  fn get_dependency(&self) -> Option<BlockingDependency> {
1484    self.get().map(|publication| publication.dependency)
1485  }
1486
1487  #[cfg(test)]
1488  fn get_dependencies(&self) -> Vec<BlockingDependency> {
1489    self
1490      .snapshot_all_live()
1491      .into_iter()
1492      .map(|publication| publication.dependency)
1493      .collect()
1494  }
1495
1496  #[cfg(test)]
1497  fn reset_entry_visits(&self) {
1498    self.entry_visits.store(0, Ordering::Relaxed);
1499  }
1500
1501  #[cfg(test)]
1502  fn entry_visits(&self) -> usize {
1503    self.entry_visits.load(Ordering::Relaxed)
1504  }
1505
1506  /// Atomically reserve `job` for exact-dependency execution. A dependency
1507  /// transition that wins this race makes the claim fail, so lending can never
1508  /// execute a stale job copied from an earlier poll.
1509  fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1510    let mut state = self
1511      .state
1512      .lock()
1513      .unwrap_or_else(std::sync::PoisonError::into_inner);
1514    let Some(current) = state.entry(selected.sequence) else {
1515      return false;
1516    };
1517    self.note_entry_visit();
1518    if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1519      return false;
1520    }
1521    let dependency = current.publication.dependency;
1522    state
1523      .entry_mut(selected.sequence)
1524      .expect("the claimed dependency must remain indexed")
1525      .publication
1526      .cancel();
1527    state.remove_sequence_indexes(selected.sequence, dependency);
1528    self.prune_stale_prefix(&mut state);
1529    true
1530  }
1531
1532  fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1533    let _generation = RuntimeGenerationGuard::enter(self.generation);
1534    let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1535      return Vec::new();
1536    };
1537    let (previous, unused, current) = {
1538      let mut state = self
1539        .state
1540        .lock()
1541        .unwrap_or_else(std::sync::PoisonError::into_inner);
1542      let current = state
1543        .current
1544        .iter()
1545        .filter(|current| current.publication.is_live())
1546        .map(|current| current.publication.clone())
1547        .collect();
1548      if state
1549        .waiter
1550        .as_ref()
1551        .is_some_and(|previous| previous.will_wake(&waker))
1552      {
1553        (None, Some(waker), current)
1554      } else {
1555        (state.waiter.replace(waker), None, current)
1556      }
1557    };
1558    if let Some(previous) = previous {
1559      let _ = catch_unwind_contained(|| drop(previous));
1560    }
1561    if let Some(unused) = unused {
1562      let _ = catch_unwind_contained(|| drop(unused));
1563    }
1564    current
1565  }
1566
1567  fn begin_poll(&self) {
1568    if !self.has_current.load(Ordering::Acquire) {
1569      return;
1570    }
1571    let waiter = {
1572      let mut state = self
1573        .state
1574        .lock()
1575        .unwrap_or_else(std::sync::PoisonError::into_inner);
1576      if state.current.is_empty() {
1577        self.has_current.store(false, Ordering::Release);
1578        None
1579      } else {
1580        for previous in state.current.drain(..) {
1581          previous.publication.cancel();
1582        }
1583        state.clear_indexes();
1584        self.has_current.store(false, Ordering::Release);
1585        state.waiter.take()
1586      }
1587    };
1588    self.notify_waiter(waiter);
1589  }
1590
1591  #[cfg(test)]
1592  fn register_waiter(&self, waker: &Waker) {
1593    let _ = self.register_waiter_and_get(waker);
1594  }
1595}
1596
1597#[cfg(not(target_family = "wasm"))]
1598impl Drop for TaskDependency {
1599  fn drop(&mut self) {
1600    let state = self
1601      .state
1602      .get_mut()
1603      .unwrap_or_else(std::sync::PoisonError::into_inner);
1604    for current in state.current.drain(..) {
1605      current.publication.cancel();
1606    }
1607    if let Some(waiter) = state.waiter.take() {
1608      let _generation = RuntimeGenerationGuard::enter(self.generation);
1609      let _ = catch_unwind_contained(|| drop(waiter));
1610    }
1611  }
1612}
1613
1614#[cfg(not(target_family = "wasm"))]
1615thread_local! {
1616  static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1617    const { std::cell::RefCell::new(Vec::new()) };
1618}
1619
1620#[cfg(not(target_family = "wasm"))]
1621struct TaskDependencyGuard(Arc<TaskDependency>);
1622
1623#[cfg(not(target_family = "wasm"))]
1624impl TaskDependencyGuard {
1625  fn enter(dependency: &Arc<TaskDependency>) -> Self {
1626    let dependency = Arc::clone(dependency);
1627    CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1628    Self(dependency)
1629  }
1630}
1631
1632#[cfg(not(target_family = "wasm"))]
1633impl Drop for TaskDependencyGuard {
1634  fn drop(&mut self) {
1635    CURRENT_DEPENDENCY_STACK.with(|stack| {
1636      let current = stack
1637        .borrow_mut()
1638        .pop()
1639        .expect("blocking dependency context underflow");
1640      debug_assert!(Arc::ptr_eq(&current, &self.0));
1641    });
1642  }
1643}
1644
1645#[cfg(not(target_family = "wasm"))]
1646struct BlockOnDependencyGuard {
1647  dependency: Arc<TaskDependency>,
1648  _context: TaskDependencyGuard,
1649}
1650
1651#[cfg(not(target_family = "wasm"))]
1652impl BlockOnDependencyGuard {
1653  fn enter(generation: u64) -> Self {
1654    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1655    let dependency = Arc::new(TaskDependency::new(generation, owner));
1656    let context = TaskDependencyGuard::enter(&dependency);
1657    Self {
1658      dependency,
1659      _context: context,
1660    }
1661  }
1662
1663  fn clear(&self) {
1664    self.dependency.clear();
1665  }
1666}
1667
1668#[cfg(not(target_family = "wasm"))]
1669fn record_blocking_dependency(dependency: BlockingDependency) {
1670  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1671  if let Some(current) = current {
1672    let dependency = BlockingDependency {
1673      owner: dependency
1674        .owner
1675        .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
1676      ..dependency
1677    };
1678    current.set_owned(dependency);
1679  }
1680}
1681
1682#[cfg(not(target_family = "wasm"))]
1683fn propagate_blocking_dependency(publication: DependencyPublication) {
1684  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1685  if let Some(current) = current {
1686    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1687    let publication = publication.with_owner_if_none(owner);
1688    current.set_borrowed(&publication);
1689  }
1690}
1691
1692#[cfg(not(target_family = "wasm"))]
1693fn clear_blocking_dependency(dependency: BlockingDependency) {
1694  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1695  if let Some(current) = current {
1696    current.clear_if_dependency(dependency);
1697  }
1698}
1699
1700#[cfg(not(target_family = "wasm"))]
1701fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
1702  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1703  if let Some(current) = current {
1704    current.clear_if_publications(publications);
1705  }
1706}
1707
1708enum JoinHandleInner<T> {
1709  Task {
1710    task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
1711    awaiter: Option<PanicContainedWakerCache>,
1712    #[cfg(not(target_family = "wasm"))]
1713    dependency: Arc<TaskDependency>,
1714  },
1715  #[cfg(not(target_family = "wasm"))]
1716  Blocking {
1717    receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
1718    dependency: BlockingDependency,
1719  },
1720  Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
1721}
1722
1723pub struct JoinHandle<T>(JoinHandleInner<T>);
1724
1725impl<T> Unpin for JoinHandle<T> {}
1726
1727impl<T> JoinHandle<T> {
1728  fn detach_task(&mut self) {
1729    let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
1730    match inner {
1731      JoinHandleInner::Task {
1732        task,
1733        awaiter: _,
1734        #[cfg(not(target_family = "wasm"))]
1735        dependency,
1736      } => {
1737        #[cfg(not(target_family = "wasm"))]
1738        let current_dependencies = dependency.snapshot_all_live();
1739        #[cfg(not(target_family = "wasm"))]
1740        dependency.clear_waiter();
1741        task.detach();
1742        #[cfg(not(target_family = "wasm"))]
1743        // Dependency cleanup may wake an arbitrary caller-provided waker.
1744        // Retire the task first, then contain the notification boundary so
1745        // reentrant wake code cannot observe a half-detached handle.
1746        let _ = catch_unwind_contained(|| {
1747          clear_propagated_blocking_dependencies(&current_dependencies);
1748        });
1749      }
1750      #[cfg(not(target_family = "wasm"))]
1751      JoinHandleInner::Blocking {
1752        receiver,
1753        dependency,
1754      } => {
1755        // A completed result may already be buffered in the receiver. Its
1756        // destructor is user code and must receive the same containment as an
1757        // immediate CurrentThread result.
1758        let _ = catch_unwind_contained(|| drop(receiver));
1759        // Notify only after receiver retirement, and keep that arbitrary waker
1760        // behind an independent unwind boundary.
1761        let _ = catch_unwind_contained(|| clear_blocking_dependency(dependency));
1762      }
1763      JoinHandleInner::Ready(result) => {
1764        let _ = catch_unwind_contained(|| drop(result));
1765      }
1766    }
1767  }
1768
1769  pub fn detach(mut self) {
1770    self.detach_task();
1771  }
1772}
1773
1774impl<T> Drop for JoinHandle<T> {
1775  fn drop(&mut self) {
1776    // Tokio's JoinHandle detaches on drop. Preserve that compatibility:
1777    // fire-and-forget callers must not silently cancel work merely because
1778    // they ignore the returned handle.
1779    self.detach_task();
1780  }
1781}
1782
1783impl<T> Future for JoinHandle<T> {
1784  type Output = Result<T, JoinError>;
1785
1786  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1787    match &mut self.get_mut().0 {
1788      JoinHandleInner::Task {
1789        task,
1790        awaiter: awaiter_slot,
1791        #[cfg(not(target_family = "wasm"))]
1792        dependency,
1793      } => {
1794        let awaiter = match awaiter_slot {
1795          Some(awaiter) => {
1796            awaiter.update(cx.waker());
1797            awaiter
1798          }
1799          None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
1800        };
1801        let contained_waker = awaiter.contained();
1802        let mut contained_cx = Context::from_waker(contained_waker);
1803        let poll = Pin::new(task).poll(&mut contained_cx);
1804        #[cfg(not(target_family = "wasm"))]
1805        if poll.is_pending() {
1806          for current_dependency in dependency.register_waiter_and_get(contained_waker) {
1807            propagate_blocking_dependency(current_dependency);
1808          }
1809        } else {
1810          dependency.clear_waiter();
1811        }
1812        if poll.is_ready() {
1813          *awaiter_slot = None;
1814        }
1815        match poll {
1816          Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
1817          Poll::Ready(None) => Poll::Ready(Err(JoinError {
1818            message: "async runtime stopped before the task completed".to_string(),
1819          })),
1820          Poll::Pending => Poll::Pending,
1821        }
1822      }
1823      #[cfg(not(target_family = "wasm"))]
1824      JoinHandleInner::Blocking {
1825        receiver,
1826        dependency,
1827      } => {
1828        let poll = Pin::new(receiver).poll(cx);
1829        if poll.is_pending() {
1830          record_blocking_dependency(*dependency);
1831        }
1832        match poll {
1833          Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
1834          Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
1835            message: "async runtime stopped before the blocking task completed".to_string(),
1836          })),
1837          Poll::Pending => Poll::Pending,
1838        }
1839      }
1840      JoinHandleInner::Ready(result) => Poll::Ready(
1841        result
1842          .take()
1843          .expect("JoinHandle polled after completion")
1844          .map(ContainedTaskOutput::into_inner),
1845      ),
1846    }
1847  }
1848}
1849
1850#[derive(Debug, Clone, Copy)]
1851pub struct RuntimeMetricsSnapshot {
1852  pub flavor: RuntimeFlavor,
1853  pub worker_threads: usize,
1854  pub max_blocking_tasks: usize,
1855  pub tasks_spawned: u64,
1856  pub tasks_completed: u64,
1857  pub tasks_panicked: u64,
1858  pub runnable_schedules: u64,
1859  pub runnable_polls: u64,
1860  pub queued_runnables: u64,
1861  pub max_queued_runnables: u64,
1862  pub active_runnables: u64,
1863  pub max_active_runnables: u64,
1864  pub blocking_tasks_started: u64,
1865  pub blocking_tasks_completed: u64,
1866  pub active_blocking_tasks: u64,
1867  pub max_active_blocking_tasks: u64,
1868}
1869
1870#[derive(Default)]
1871struct RuntimeMetrics {
1872  tasks_spawned: AtomicU64,
1873  tasks_completed: AtomicU64,
1874  tasks_panicked: AtomicU64,
1875  runnable_schedules: AtomicU64,
1876  runnable_polls: AtomicU64,
1877  queued_runnables: AtomicU64,
1878  max_queued_runnables: AtomicU64,
1879  active_runnables: AtomicU64,
1880  max_active_runnables: AtomicU64,
1881  // Bumped at ENQUEUE time (before the queue push), the blocking-side twin of
1882  // `runnable_schedules`. Internal-only (not exported in the public snapshot):
1883  // it exists so the deadlock detector's progress fingerprint can observe a
1884  // blocking submission the moment it is made -- `blocking_tasks_started`
1885  // only advances once a worker RUNS the job, which may be arbitrarily later.
1886  blocking_tasks_scheduled: AtomicU64,
1887  blocking_tasks_started: AtomicU64,
1888  blocking_tasks_completed: AtomicU64,
1889  active_blocking_tasks: AtomicU64,
1890  max_active_blocking_tasks: AtomicU64,
1891  // Seqlock generation around resets. The deadlock detector snapshots
1892  // resettable counters, so it must distinguish identical counter values from
1893  // different reset generations instead of mistaking that ABA for no progress.
1894  reset_generation: AtomicU64,
1895  reset_lock: Mutex<()>,
1896}
1897
1898#[cfg(test)]
1899thread_local! {
1900  static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
1901    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
1902      const { std::cell::RefCell::new(None) };
1903}
1904
1905#[cfg(test)]
1906fn run_runtime_metrics_high_water_test_hook() {
1907  if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1908    hook();
1909  }
1910}
1911
1912impl RuntimeMetrics {
1913  fn runnable_scheduled(&self) {
1914    self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
1915    let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
1916    #[cfg(test)]
1917    run_runtime_metrics_high_water_test_hook();
1918    self
1919      .max_queued_runnables
1920      .fetch_max(queued, Ordering::Relaxed);
1921  }
1922
1923  fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
1924    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
1925    self.runnable_polls.fetch_add(1, Ordering::Relaxed);
1926    let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
1927    #[cfg(test)]
1928    run_runtime_metrics_high_water_test_hook();
1929    self
1930      .max_active_runnables
1931      .fetch_max(active, Ordering::Relaxed);
1932    ActiveRunnableGuard { metrics: self }
1933  }
1934
1935  #[cfg(any(not(target_family = "wasm"), test))]
1936  fn runnable_cancelled(&self) {
1937    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
1938  }
1939
1940  /// Enqueue-time twin of [`Self::runnable_scheduled`] for blocking work:
1941  /// called BEFORE the blocking-queue push, so the deadlock detector's
1942  /// fingerprint sees a submission even when no worker has started the job.
1943  fn blocking_scheduled(&self) {
1944    self
1945      .blocking_tasks_scheduled
1946      .fetch_add(1, Ordering::Relaxed);
1947  }
1948
1949  fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
1950    self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
1951    if count_active_lane {
1952      let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
1953      #[cfg(test)]
1954      run_runtime_metrics_high_water_test_hook();
1955      self
1956        .max_active_blocking_tasks
1957        .fetch_max(active, Ordering::Relaxed);
1958    }
1959    BlockingTaskGuard {
1960      metrics: Arc::clone(self),
1961      count_active_lane,
1962    }
1963  }
1964
1965  fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
1966    let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
1967    let active_runnables = self.active_runnables.load(Ordering::Relaxed);
1968    let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
1969    RuntimeMetricsSnapshot {
1970      flavor: options.flavor,
1971      worker_threads: options.worker_threads,
1972      max_blocking_tasks: options.max_blocking_tasks,
1973      tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
1974      tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
1975      tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
1976      runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
1977      runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
1978      queued_runnables,
1979      max_queued_runnables: self
1980        .max_queued_runnables
1981        .load(Ordering::Relaxed)
1982        .max(queued_runnables),
1983      active_runnables,
1984      max_active_runnables: self
1985        .max_active_runnables
1986        .load(Ordering::Relaxed)
1987        .max(active_runnables),
1988      blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
1989      blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
1990      active_blocking_tasks,
1991      max_active_blocking_tasks: self
1992        .max_active_blocking_tasks
1993        .load(Ordering::Relaxed)
1994        .max(active_blocking_tasks),
1995    }
1996  }
1997
1998  /// Coarse liveness fingerprint for deadline-based deadlock detection (wake
1999  /// -path §8.5): any of these counters advancing between two reads means the
2000  /// runtime did SOMETHING (scheduled or polled a runnable, completed a task,
2001  /// scheduled, started or finished a blocking job), so a parked driver
2002  /// observing an advance re-arms its deadline instead of firing. Both ENQUEUE
2003  /// counters (`runnable_schedules`, `blocking_tasks_scheduled`) are included
2004  /// so a submission is progress the moment it is made -- not only once a
2005  /// worker gets to run it.
2006  fn progress_fingerprint(&self) -> ProgressFingerprint {
2007    loop {
2008      let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2009      if !reset_generation.is_multiple_of(2) {
2010        std::hint::spin_loop();
2011        continue;
2012      }
2013      let fingerprint = ProgressFingerprint {
2014        reset_generation,
2015        runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2016        runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2017        tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2018        blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2019        blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2020        blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2021      };
2022      if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2023        return fingerprint;
2024      }
2025    }
2026  }
2027
2028  fn reset(&self) {
2029    let reset_guard = self
2030      .reset_lock
2031      .lock()
2032      .unwrap_or_else(std::sync::PoisonError::into_inner);
2033    // Event counters are safe to reset independently. Live gauges are not:
2034    // queued/active guards may still hold a future decrement, and zeroing a
2035    // gauge underneath them wraps the unsigned counter on completion.
2036    //
2037    // High-water gauges remain lifetime values. Resetting them concurrently
2038    // with fetch_max would either lose a real peak or require synchronization
2039    // on every hot-path metric update.
2040    let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2041    let Some(resetting_generation) = reset_generation.checked_add(1) else {
2042      drop(reset_guard);
2043      panic!("runtime metrics reset generation space exhausted");
2044    };
2045    let Some(settled_generation) = resetting_generation.checked_add(1) else {
2046      drop(reset_guard);
2047      panic!("runtime metrics reset generation space exhausted");
2048    };
2049    self
2050      .reset_generation
2051      .store(resetting_generation, Ordering::SeqCst);
2052    self.tasks_spawned.store(0, Ordering::Relaxed);
2053    self.tasks_completed.store(0, Ordering::Relaxed);
2054    self.tasks_panicked.store(0, Ordering::Relaxed);
2055    self.runnable_schedules.store(0, Ordering::Relaxed);
2056    self.runnable_polls.store(0, Ordering::Relaxed);
2057    self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2058    self.blocking_tasks_started.store(0, Ordering::Relaxed);
2059    self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2060    self
2061      .reset_generation
2062      .store(settled_generation, Ordering::SeqCst);
2063  }
2064}
2065
2066/// See [`RuntimeMetrics::progress_fingerprint`].
2067#[derive(Clone, Copy, PartialEq, Eq)]
2068struct ProgressFingerprint {
2069  reset_generation: u64,
2070  runnable_schedules: u64,
2071  runnable_polls: u64,
2072  tasks_completed: u64,
2073  blocking_tasks_scheduled: u64,
2074  blocking_tasks_started: u64,
2075  blocking_tasks_completed: u64,
2076}
2077
2078struct ActiveRunnableGuard<'a> {
2079  metrics: &'a RuntimeMetrics,
2080}
2081
2082impl Drop for ActiveRunnableGuard<'_> {
2083  fn drop(&mut self) {
2084    self
2085      .metrics
2086      .active_runnables
2087      .fetch_sub(1, Ordering::Relaxed);
2088  }
2089}
2090
2091struct BlockingTaskGuard {
2092  metrics: Arc<RuntimeMetrics>,
2093  count_active_lane: bool,
2094}
2095
2096impl Drop for BlockingTaskGuard {
2097  fn drop(&mut self) {
2098    if self.count_active_lane {
2099      self
2100        .metrics
2101        .active_blocking_tasks
2102        .fetch_sub(1, Ordering::Relaxed);
2103    }
2104    self
2105      .metrics
2106      .blocking_tasks_completed
2107      .fetch_add(1, Ordering::Relaxed);
2108  }
2109}
2110
2111static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2112
2113fn next_runtime_generation() -> u64 {
2114  next_unique_id(
2115    &NEXT_RUNTIME_GENERATION,
2116    "async runtime generation id space exhausted",
2117  )
2118}
2119
2120thread_local! {
2121  static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2122    const { std::cell::Cell::new(None) };
2123}
2124
2125fn active_runtime_generation() -> Option<u64> {
2126  ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2127}
2128
2129struct RuntimeGenerationGuard {
2130  previous: Option<u64>,
2131}
2132
2133impl RuntimeGenerationGuard {
2134  fn enter(generation: u64) -> Self {
2135    Self::enter_optional(Some(generation))
2136  }
2137
2138  fn enter_optional(generation: Option<u64>) -> Self {
2139    Self {
2140      previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2141    }
2142  }
2143}
2144
2145impl Drop for RuntimeGenerationGuard {
2146  fn drop(&mut self) {
2147    ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2148  }
2149}
2150
2151struct ContainedFuture<F> {
2152  future: ManuallyDrop<F>,
2153  generation: u64,
2154}
2155
2156impl<F> ContainedFuture<F> {
2157  fn new(future: F, generation: u64) -> Self {
2158    Self {
2159      future: ManuallyDrop::new(future),
2160      generation,
2161    }
2162  }
2163}
2164
2165impl<F: Future> Future for ContainedFuture<F> {
2166  type Output = F::Output;
2167
2168  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2169    // SAFETY: `future` is never moved after `ContainedFuture` is pinned.
2170    let this = unsafe { self.get_unchecked_mut() };
2171    let _generation = RuntimeGenerationGuard::enter(this.generation);
2172    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2173    // structurally pinned for the lifetime of `self`.
2174    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2175  }
2176}
2177
2178impl<F> Drop for ContainedFuture<F> {
2179  fn drop(&mut self) {
2180    let _generation = RuntimeGenerationGuard::enter(self.generation);
2181    let _ = catch_unwind_contained(|| {
2182      // SAFETY: this is the sole destruction path for `future`.
2183      unsafe { ManuallyDrop::drop(&mut self.future) };
2184    });
2185  }
2186}
2187
2188#[derive(Default)]
2189struct GenerationStop {
2190  stopping: AtomicBool,
2191  publication: Mutex<()>,
2192  parkers: Mutex<Vec<Weak<DriverParker>>>,
2193}
2194
2195impl GenerationStop {
2196  fn is_stopping(&self) -> bool {
2197    self.stopping.load(Ordering::Acquire)
2198  }
2199
2200  fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2201    #[cfg(all(test, not(target_family = "wasm")))]
2202    run_before_generation_stop_publication_lock_test_hook();
2203    self.runnable_claim_guard()
2204  }
2205
2206  fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2207    GenerationStopPublicationGuard {
2208      stop: self,
2209      _publication: self
2210        .publication
2211        .lock()
2212        .unwrap_or_else(std::sync::PoisonError::into_inner),
2213    }
2214  }
2215
2216  fn publish_stopping(&self) {
2217    self.publication_guard().publish_stopping();
2218  }
2219
2220  fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2221    {
2222      let mut parkers = self
2223        .parkers
2224        .lock()
2225        .unwrap_or_else(std::sync::PoisonError::into_inner);
2226      parkers.retain(|candidate| candidate.strong_count() != 0);
2227      parkers.push(Arc::downgrade(parker));
2228    }
2229    if self.is_stopping() {
2230      parker.unpark();
2231    }
2232    GenerationStopGuard {
2233      stop: Arc::clone(self),
2234      parker: Arc::downgrade(parker),
2235    }
2236  }
2237
2238  /// Wake every active `block_on` frame without changing the generation state.
2239  /// The number of parkers is bounded by concurrent explicit drivers. Waking
2240  /// all avoids directing a queue wake only to a newer driver that is blocked
2241  /// inside its poll while an older driver is already asleep.
2242  fn wake_all(&self) -> usize {
2243    let mut count = 0;
2244    let mut registered = self
2245      .parkers
2246      .lock()
2247      .unwrap_or_else(std::sync::PoisonError::into_inner);
2248    registered.retain(|candidate| {
2249      let Some(parker) = candidate.upgrade() else {
2250        return false;
2251      };
2252      count += 1;
2253      // `DriverParker::unpark` invokes no user code and never enters the
2254      // generation registry, so waking under this lock is non-reentrant.
2255      parker.unpark();
2256      true
2257    });
2258    count
2259  }
2260
2261  fn begin_shutdown(&self) {
2262    self.publish_stopping();
2263    let parkers = {
2264      let mut registered = self
2265        .parkers
2266        .lock()
2267        .unwrap_or_else(std::sync::PoisonError::into_inner);
2268      let parkers = registered
2269        .iter()
2270        .filter_map(Weak::upgrade)
2271        .collect::<Vec<_>>();
2272      registered.clear();
2273      parkers
2274    };
2275    for parker in parkers {
2276      parker.unpark();
2277    }
2278  }
2279}
2280
2281struct GenerationStopPublicationGuard<'a> {
2282  stop: &'a GenerationStop,
2283  _publication: std::sync::MutexGuard<'a, ()>,
2284}
2285
2286impl GenerationStopPublicationGuard<'_> {
2287  fn is_stopping(&self) -> bool {
2288    self.stop.is_stopping()
2289  }
2290
2291  fn publish_stopping(&self) {
2292    self.stop.stopping.store(true, Ordering::Release);
2293  }
2294}
2295
2296struct GenerationStopGuard {
2297  stop: Arc<GenerationStop>,
2298  parker: Weak<DriverParker>,
2299}
2300
2301impl Drop for GenerationStopGuard {
2302  fn drop(&mut self) {
2303    let Some(parker) = self.parker.upgrade() else {
2304      return;
2305    };
2306    let mut registered = self
2307      .stop
2308      .parkers
2309      .lock()
2310      .unwrap_or_else(std::sync::PoisonError::into_inner);
2311    if let Some(index) = registered.iter().position(|candidate| {
2312      candidate
2313        .upgrade()
2314        .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2315    }) {
2316      registered.swap_remove(index);
2317    }
2318  }
2319}
2320
2321struct GenerationWorkState {
2322  closed: bool,
2323  next_task_id: u64,
2324  active: usize,
2325  abort_handles: FxHashMap<u64, AbortHandle>,
2326}
2327
2328struct GenerationWork {
2329  id: u64,
2330  state: Mutex<GenerationWorkState>,
2331  idle: Condvar,
2332}
2333
2334impl GenerationWork {
2335  fn new() -> Arc<Self> {
2336    Arc::new(Self {
2337      id: next_runtime_generation(),
2338      state: Mutex::new(GenerationWorkState {
2339        closed: false,
2340        next_task_id: 0,
2341        active: 0,
2342        abort_handles: FxHashMap::default(),
2343      }),
2344      idle: Condvar::new(),
2345    })
2346  }
2347
2348  fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2349    let (abort_handle, abort_registration) = AbortHandle::new_pair();
2350    let mut state = self
2351      .state
2352      .lock()
2353      .unwrap_or_else(std::sync::PoisonError::into_inner);
2354    if state.closed {
2355      return None;
2356    }
2357    let task_id = state.next_task_id;
2358    let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2359      drop(state);
2360      panic!("async runtime generation task id space exhausted");
2361    };
2362    state.next_task_id = next_task_id;
2363    state.active += 1;
2364    state.abort_handles.insert(task_id, abort_handle);
2365    Some((
2366      abort_registration,
2367      GenerationWorkGuard {
2368        work: Arc::clone(self),
2369        task_id: Some(task_id),
2370      },
2371    ))
2372  }
2373
2374  fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2375    let mut state = self
2376      .state
2377      .lock()
2378      .unwrap_or_else(std::sync::PoisonError::into_inner);
2379    if state.closed {
2380      return None;
2381    }
2382    state.active += 1;
2383    Some(GenerationWorkGuard {
2384      work: Arc::clone(self),
2385      task_id: None,
2386    })
2387  }
2388
2389  fn close_and_abort(&self) {
2390    let abort_handles = {
2391      let mut state = self
2392        .state
2393        .lock()
2394        .unwrap_or_else(std::sync::PoisonError::into_inner);
2395      state.closed = true;
2396      state.abort_handles.values().cloned().collect::<Vec<_>>()
2397    };
2398    for abort_handle in abort_handles {
2399      abort_handle.abort();
2400    }
2401  }
2402
2403  fn wait_until_idle(&self) {
2404    let mut state = self
2405      .state
2406      .lock()
2407      .unwrap_or_else(std::sync::PoisonError::into_inner);
2408    while state.active != 0 {
2409      state = self
2410        .idle
2411        .wait(state)
2412        .unwrap_or_else(std::sync::PoisonError::into_inner);
2413    }
2414  }
2415}
2416
2417struct GenerationWorkGuard {
2418  work: Arc<GenerationWork>,
2419  task_id: Option<u64>,
2420}
2421
2422impl Drop for GenerationWorkGuard {
2423  fn drop(&mut self) {
2424    let mut state = self
2425      .work
2426      .state
2427      .lock()
2428      .unwrap_or_else(std::sync::PoisonError::into_inner);
2429    if let Some(task_id) = self.task_id {
2430      state.abort_handles.remove(&task_id);
2431    }
2432    state.active = state
2433      .active
2434      .checked_sub(1)
2435      .expect("generation work count underflow");
2436    if state.active == 0 {
2437      self.work.idle.notify_all();
2438    }
2439  }
2440}
2441
2442/// Keeps accepted async work registered until the complete task generator has
2443/// been destroyed. In particular, an executor queue may close after
2444/// registration but before the initial runnable is published. `async-task`
2445/// then drops the generator unpolled, whose compiler-generated capture order
2446/// must not be allowed to retire the generation guard before user captures.
2447struct RegisteredTaskFuture<F> {
2448  future: ManuallyDrop<F>,
2449  registration: Option<GenerationWorkGuard>,
2450  generation: u64,
2451}
2452
2453impl<F> RegisteredTaskFuture<F> {
2454  fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2455    Self {
2456      future: ManuallyDrop::new(future),
2457      registration: Some(registration),
2458      generation,
2459    }
2460  }
2461}
2462
2463impl<F: Future> Future for RegisteredTaskFuture<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 `RegisteredTaskFuture` 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 RegisteredTaskFuture<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    // Shutdown/restart may observe this generation idle only after every user
2484    // capture and caught panic payload above has retired.
2485    drop(self.registration.take());
2486  }
2487}
2488
2489#[cfg(not(target_family = "wasm"))]
2490struct RegisteredBlockingFunction<F> {
2491  function: Option<F>,
2492  registration: Option<GenerationWorkGuard>,
2493  generation: u64,
2494}
2495
2496#[cfg(not(target_family = "wasm"))]
2497impl<F> RegisteredBlockingFunction<F> {
2498  fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2499    Self {
2500      function: Some(function),
2501      registration: Some(registration),
2502      generation,
2503    }
2504  }
2505
2506  fn run<T>(mut self) -> Result<T, JoinError>
2507  where
2508    F: FnOnce() -> T,
2509  {
2510    let _generation = RuntimeGenerationGuard::enter(self.generation);
2511    let result = catch_unwind_join_error(
2512      self
2513        .function
2514        .take()
2515        .expect("blocking function must be available"),
2516    );
2517    drop(self.registration.take());
2518    result
2519  }
2520}
2521
2522#[cfg(not(target_family = "wasm"))]
2523impl<F> Drop for RegisteredBlockingFunction<F> {
2524  fn drop(&mut self) {
2525    let _generation = RuntimeGenerationGuard::enter(self.generation);
2526    if let Some(function) = self.function.take() {
2527      let _ = catch_unwind_contained(|| drop(function));
2528    }
2529    // A submission accepted by the controller may reach a queue that shutdown
2530    // already closed. Keep it registered until all captured user state above
2531    // has been destroyed on the submitting thread.
2532    drop(self.registration.take());
2533  }
2534}
2535
2536fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2537  let _active = metrics.runnable_started();
2538  let _ = catch_unwind_contained(|| runnable.run());
2539}
2540
2541#[cfg(not(target_family = "wasm"))]
2542enum RunnableClaim {
2543  Empty,
2544  Run(Runnable),
2545  Cancel(Runnable),
2546}
2547
2548static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2549static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2550
2551fn next_current_thread_dispatch_id() -> u64 {
2552  next_unique_id(
2553    &NEXT_CURRENT_THREAD_DISPATCH_ID,
2554    "CurrentThread dispatch id space exhausted",
2555  )
2556  .checked_add(1)
2557  .expect("CurrentThread dispatch id space exhausted")
2558}
2559
2560struct CurrentThreadBlockingAdmission {
2561  state: Mutex<CurrentThreadBlockingAdmissionState>,
2562}
2563
2564struct CurrentThreadBlockingAdmissionState {
2565  owner: Option<BlockingOwnerToken>,
2566  closed: bool,
2567  #[cfg(not(target_family = "wasm"))]
2568  queue: BlockingQueue,
2569}
2570
2571struct CurrentThreadExecutor {
2572  id: u64,
2573  generation: u64,
2574  stop: Arc<GenerationStop>,
2575  queue: Mutex<CurrentThreadQueue>,
2576  blocking_admission: CurrentThreadBlockingAdmission,
2577  draining: AtomicBool,
2578  scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2579  scheduler_idle: Condvar,
2580  /// Exact nonzero internal capability for the currently published host turn.
2581  /// The host registry maps this to distinct registration-scoped delivery
2582  /// capabilities, so delayed drive/cancel calls cannot cross host attempts.
2583  dispatch_pending: AtomicU64,
2584  /// Exact capability of the one replacement dispatch allowed after a host
2585  /// scheduler rejects an accepted callback. A second matching cancellation
2586  /// fails the queued work instead of retrying forever.
2587  recovery_dispatch: AtomicU64,
2588  /// Terminal dispatch failure temporarily rejects scheduler publications
2589  /// while the affected runnable and blocking queues are cancelled.
2590  cancelling_failed_dispatch: AtomicBool,
2591  task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2592  task_drivers: &'static CurrentThreadTaskDriverRegistry,
2593  metrics: Arc<RuntimeMetrics>,
2594  // Deadlock detection. `threadless`: no second thread can EVER deliver a
2595  // wake to a parked `block_on`, so a park decision with an
2596  // empty queue and no pending wake token is a PROVABLE deadlock (panic
2597  // immediately, no timing involved). `park_deadline`: on threaded builds,
2598  // the optional bound for a park with zero runtime progress -- off unless
2599  // configured, because a legitimately long park must never panic a
2600  // production build.
2601  threadless: bool,
2602  park_deadline: Option<Duration>,
2603  // HOST-driver timers currently armed on this runtime.
2604  // On the THREADED CT flavor a pending entry is a future wake token -- the
2605  // host event loop's timer callback -- so the deadline verdict treats a LIVE
2606  // wait (deadline still in the future) as a legitimate park. On a THREADLESS
2607  // build it is NOT a wake token (the host relay shares the parked thread and
2608  // can never run); there the CERTAIN check only consults it to pick the
2609  // timer-backed variant of the typed diagnostic. Shared with the `Sleep`
2610  // futures minted for this executor (they insert at first poll and remove on
2611  // completion/drop).
2612  host_timers: Arc<HostTimerRegistry>,
2613}
2614
2615struct CurrentThreadQueue {
2616  closed: bool,
2617  runnables: VecDeque<Runnable>,
2618}
2619
2620#[derive(Default)]
2621struct CurrentThreadSchedulerState {
2622  active_host_turns: usize,
2623  active_dispatch_calls: usize,
2624  dispatch_publications: Vec<CurrentThreadDispatchPublication>,
2625}
2626
2627struct CurrentThreadDispatchPublication {
2628  dispatch: u64,
2629  republish_requested: bool,
2630}
2631
2632#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2633enum CurrentThreadDispatchRequest {
2634  Accepted,
2635  Coalesced,
2636  Unavailable,
2637}
2638
2639#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2640struct CurrentThreadTaskDispatchFailure {
2641  dispatch: u64,
2642  epoch: u64,
2643}
2644
2645#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2646enum CurrentThreadHostDispatchResult {
2647  Accepted,
2648  Unavailable,
2649  Failed(CurrentThreadTaskDispatchFailure),
2650}
2651
2652enum CurrentThreadDispatchPublicationStep {
2653  Republish,
2654  Complete(Option<CurrentThreadHostDispatchResult>),
2655}
2656
2657struct CurrentThreadHostTurn {
2658  executor: Arc<CurrentThreadExecutor>,
2659  draining: bool,
2660  _generation: RuntimeGenerationGuard,
2661}
2662
2663impl CurrentThreadHostTurn {
2664  fn drive(&mut self) {
2665    let executor = Arc::clone(&self.executor);
2666    executor.drive_admitted_host_turn(self);
2667  }
2668
2669  fn release_draining(&mut self) {
2670    if !self.draining {
2671      return;
2672    }
2673    let _scheduler = self
2674      .executor
2675      .scheduler_idle_lock
2676      .lock()
2677      .unwrap_or_else(std::sync::PoisonError::into_inner);
2678    self.executor.draining.store(false, Ordering::Release);
2679    self.draining = false;
2680  }
2681}
2682
2683impl Drop for CurrentThreadHostTurn {
2684  fn drop(&mut self) {
2685    let mut scheduler = self
2686      .executor
2687      .scheduler_idle_lock
2688      .lock()
2689      .unwrap_or_else(std::sync::PoisonError::into_inner);
2690    if self.draining {
2691      self.executor.draining.store(false, Ordering::Release);
2692      self.draining = false;
2693    }
2694    scheduler.active_host_turns = scheduler
2695      .active_host_turns
2696      .checked_sub(1)
2697      .expect("CurrentThread active host-turn count underflow");
2698    self.executor.scheduler_idle.notify_all();
2699  }
2700}
2701
2702/// Retains one admitted CurrentThread host callback in its runtime generation.
2703///
2704/// The host must keep this opaque lease alive until its native callback has
2705/// acknowledged or failed the delivery and destroyed every callback-owned
2706/// payload. The `Rc` marker prevents moving a TLS generation guard to another
2707/// thread before it restores the callback thread's prior runtime identity.
2708#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
2709pub struct CurrentThreadTaskCallbackLease {
2710  _host_turn: CurrentThreadHostTurn,
2711  _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
2712}
2713
2714impl CurrentThreadTaskCallbackLease {
2715  fn new(host_turn: CurrentThreadHostTurn) -> Self {
2716    Self {
2717      _host_turn: host_turn,
2718      _not_send: std::marker::PhantomData,
2719    }
2720  }
2721}
2722
2723struct CurrentThreadDispatchCall<'a> {
2724  executor: &'a CurrentThreadExecutor,
2725  publication: Option<u64>,
2726  _generation: RuntimeGenerationGuard,
2727}
2728
2729impl CurrentThreadDispatchCall<'_> {
2730  fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
2731    let dispatch = self
2732      .publication
2733      .expect("the CurrentThread dispatch call must own a publication");
2734    let index = scheduler
2735      .dispatch_publications
2736      .iter()
2737      .position(|publication| publication.dispatch == dispatch)
2738      .expect("the CurrentThread dispatch publication must remain registered");
2739    scheduler.dispatch_publications.swap_remove(index);
2740    self.publication = None;
2741  }
2742}
2743
2744impl Drop for CurrentThreadDispatchCall<'_> {
2745  fn drop(&mut self) {
2746    let mut scheduler = self
2747      .executor
2748      .scheduler_idle_lock
2749      .lock()
2750      .unwrap_or_else(std::sync::PoisonError::into_inner);
2751    if let Some(dispatch) = self.publication.take()
2752      && let Some(index) = scheduler
2753        .dispatch_publications
2754        .iter()
2755        .position(|publication| publication.dispatch == dispatch)
2756    {
2757      scheduler.dispatch_publications.swap_remove(index);
2758    }
2759    scheduler.active_dispatch_calls = scheduler
2760      .active_dispatch_calls
2761      .checked_sub(1)
2762      .expect("CurrentThread active dispatch-call count underflow");
2763    self.executor.scheduler_idle.notify_all();
2764  }
2765}
2766
2767struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
2768
2769impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
2770  fn drop(&mut self) {
2771    self
2772      .0
2773      .cancelling_failed_dispatch
2774      .store(false, Ordering::Release);
2775  }
2776}
2777
2778enum CurrentThreadDispatchFailureAction<'a> {
2779  None,
2780  Cancel(CurrentThreadDispatchCancellationGuard<'a>),
2781  Publish {
2782    dispatch: u64,
2783    call: CurrentThreadDispatchCall<'a>,
2784  },
2785}
2786
2787struct CurrentThreadDispatchFailureRecovery<'a> {
2788  action: CurrentThreadDispatchFailureAction<'a>,
2789  _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
2790}
2791
2792struct CurrentThreadRegistryCancellationGuard<'a> {
2793  registry: &'a CurrentThreadTaskDriverRegistry,
2794  failure: Option<CurrentThreadTaskDispatchFailure>,
2795}
2796
2797impl CurrentThreadRegistryCancellationGuard<'_> {
2798  fn new(
2799    registry: &CurrentThreadTaskDriverRegistry,
2800    failure: CurrentThreadTaskDispatchFailure,
2801  ) -> CurrentThreadRegistryCancellationGuard<'_> {
2802    CurrentThreadRegistryCancellationGuard {
2803      registry,
2804      failure: Some(failure),
2805    }
2806  }
2807}
2808
2809impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
2810  fn drop(&mut self) {
2811    if let Some(failure) = self.failure.take() {
2812      let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
2813    }
2814  }
2815}
2816
2817struct CurrentThreadBlockingGuard<'a> {
2818  executor: &'a CurrentThreadExecutor,
2819  owner: BlockingOwnerToken,
2820  previous_owner: Option<BlockingOwnerToken>,
2821  owns_lane: bool,
2822}
2823
2824impl CurrentThreadBlockingGuard<'_> {
2825  fn counts_active_lane(&self) -> bool {
2826    self.owns_lane
2827  }
2828}
2829
2830impl Drop for CurrentThreadBlockingGuard<'_> {
2831  fn drop(&mut self) {
2832    BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
2833    if !self.owns_lane {
2834      return;
2835    }
2836    let mut state = self
2837      .executor
2838      .blocking_admission
2839      .state
2840      .lock()
2841      .unwrap_or_else(std::sync::PoisonError::into_inner);
2842    debug_assert_eq!(state.owner, Some(self.owner));
2843    state.owner = None;
2844    drop(state);
2845    self.executor.stop.wake_all();
2846  }
2847}
2848
2849impl CurrentThreadExecutor {
2850  /// Bound one host callback so a self-waking task cannot monopolize the
2851  /// JavaScript event loop. Remaining work is continued in a fresh host turn.
2852  const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
2853  /// Force one blocking turn after this many consecutive runnable polls.
2854  #[cfg(not(target_family = "wasm"))]
2855  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
2856
2857  /// Build-default construction, kept for the pre-existing unit tests
2858  /// (production goes through [`Self::with_detection`] in
2859  /// `RuntimeBackend::new`, which also resolves the env deadline).
2860  #[cfg(test)]
2861  fn new(metrics: Arc<RuntimeMetrics>) -> Self {
2862    Self::with_detection(metrics, THREADLESS_BUILD, None)
2863  }
2864
2865  /// Full constructor for the deadlock-detection knobs: `new` fixes
2866  /// `threadless` to the build's [`THREADLESS_BUILD`] with the deadline off;
2867  /// `RuntimeBackend::new` resolves the deadline from
2868  /// `RuntimeOptions::park_deadline` / [`PARK_DEADLINE_ENV`]; tests inject a
2869  /// native `threadless` stand-in and short deadlines through it.
2870  #[cfg(test)]
2871  fn with_detection(
2872    metrics: Arc<RuntimeMetrics>,
2873    threadless: bool,
2874    park_deadline: Option<Duration>,
2875  ) -> Self {
2876    Self::with_generation(
2877      metrics,
2878      threadless,
2879      park_deadline,
2880      u64::MAX,
2881      Arc::new(GenerationStop::default()),
2882    )
2883  }
2884
2885  fn with_generation(
2886    metrics: Arc<RuntimeMetrics>,
2887    threadless: bool,
2888    park_deadline: Option<Duration>,
2889    generation: u64,
2890    stop: Arc<GenerationStop>,
2891  ) -> Self {
2892    Self {
2893      id: next_unique_id(
2894        &NEXT_RUNTIME_EXECUTOR_ID,
2895        "async runtime executor id space exhausted",
2896      ),
2897      generation,
2898      stop,
2899      queue: Mutex::new(CurrentThreadQueue {
2900        closed: false,
2901        runnables: VecDeque::new(),
2902      }),
2903      blocking_admission: CurrentThreadBlockingAdmission {
2904        state: Mutex::new(CurrentThreadBlockingAdmissionState {
2905          owner: None,
2906          closed: false,
2907          #[cfg(not(target_family = "wasm"))]
2908          queue: BlockingQueue {
2909            closed: false,
2910            head: None,
2911            tail: None,
2912            jobs: FxHashMap::default(),
2913          },
2914        }),
2915      },
2916      draining: AtomicBool::new(false),
2917      scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
2918      scheduler_idle: Condvar::new(),
2919      dispatch_pending: AtomicU64::new(0),
2920      recovery_dispatch: AtomicU64::new(0),
2921      cancelling_failed_dispatch: AtomicBool::new(false),
2922      task_dispatch: dispatch_current_thread_tasks,
2923      task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
2924      metrics,
2925      threadless,
2926      park_deadline,
2927      host_timers: Arc::new(HostTimerRegistry::default()),
2928    }
2929  }
2930
2931  fn fresh_owner_token(&self) -> BlockingOwnerToken {
2932    BlockingOwnerToken {
2933      executor_id: self.id,
2934      frame: next_unique_id(
2935        &NEXT_BLOCKING_FRAME,
2936        "blocking owner frame id space exhausted",
2937      ),
2938    }
2939  }
2940
2941  fn enter_blocking_owner(
2942    &self,
2943    owner: BlockingOwnerToken,
2944    owns_lane: bool,
2945  ) -> CurrentThreadBlockingGuard<'_> {
2946    let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
2947    CurrentThreadBlockingGuard {
2948      executor: self,
2949      owner,
2950      previous_owner,
2951      owns_lane,
2952    }
2953  }
2954
2955  fn schedule_blocking<F, T>(
2956    self: &Arc<Self>,
2957    function: F,
2958    registration: GenerationWorkGuard,
2959  ) -> JoinHandle<T>
2960  where
2961    F: FnOnce() -> T + Send + 'static,
2962    T: Send + 'static,
2963  {
2964    // See the implementation notes in this crate's README.
2965    self.metrics.blocking_scheduled();
2966    let mut function = Some(function);
2967    let mut registration = Some(registration);
2968    let ambient_owner = BLOCKING_OWNER
2969      .with(std::cell::Cell::get)
2970      .filter(|owner| owner.executor_id == self.id);
2971    let mut run_owner = None;
2972    let mut owns_lane = false;
2973    let mut rejected = false;
2974
2975    #[cfg(not(target_family = "wasm"))]
2976    let (sender, receiver) = oneshot::channel();
2977    #[cfg(not(target_family = "wasm"))]
2978    let dependency = BlockingDependency {
2979      job: BlockingJobId(next_unique_id(
2980        &NEXT_BLOCKING_JOB_ID,
2981        "blocking job id space exhausted",
2982      )),
2983      owner: ambient_owner,
2984    };
2985    #[cfg(not(target_family = "wasm"))]
2986    let mut queued = false;
2987
2988    {
2989      let mut state = self
2990        .blocking_admission
2991        .state
2992        .lock()
2993        .unwrap_or_else(std::sync::PoisonError::into_inner);
2994      #[cfg(all(test, not(target_family = "wasm")))]
2995      run_current_thread_blocking_claim_test_hook();
2996      // Shutdown publishes generation stop before taking this mutex. Recheck
2997      // it while admission is serialized so work cannot claim or queue for the
2998      // CurrentThread lane after stop publication.
2999      if self.stop.is_stopping()
3000        || state.closed
3001        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3002      {
3003        rejected = true;
3004      } else if ambient_owner.is_some() && state.owner == ambient_owner {
3005        run_owner = ambient_owner;
3006      } else {
3007        #[cfg(not(target_family = "wasm"))]
3008        let queue_empty = state.queue.is_empty();
3009        #[cfg(target_family = "wasm")]
3010        let queue_empty = true;
3011
3012        if state.owner.is_none() && queue_empty {
3013          let owner = self.fresh_owner_token();
3014          state.owner = Some(owner);
3015          run_owner = Some(owner);
3016          owns_lane = true;
3017        } else {
3018          #[cfg(not(target_family = "wasm"))]
3019          {
3020            let function = function
3021              .take()
3022              .expect("blocking function must be available");
3023            let registration = registration
3024              .take()
3025              .expect("blocking registration must be available");
3026            let generation = self.generation;
3027            let function = RegisteredBlockingFunction::new(function, registration, generation);
3028            state.queue.push(QueuedBlocking {
3029              id: dependency.job,
3030              run: Box::new(move || {
3031                let _generation = RuntimeGenerationGuard::enter(generation);
3032                let result = function
3033                  .run()
3034                  .map(|output| ContainedTaskOutput::new(output, generation));
3035                let _ = sender.send(result);
3036              }),
3037            });
3038            queued = true;
3039          }
3040          #[cfg(target_family = "wasm")]
3041          {
3042            // A threadless CurrentThread executor cannot reach unrelated
3043            // concurrent admission. Same-stack nesting carries `ambient_owner`
3044            // and takes the borrowing branch above.
3045            rejected = true;
3046          }
3047        }
3048      }
3049    }
3050
3051    #[cfg(not(target_family = "wasm"))]
3052    if queued {
3053      self.stop.wake_all();
3054      if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3055        self.request_drain();
3056      }
3057      return JoinHandle(JoinHandleInner::Blocking {
3058        receiver,
3059        dependency,
3060      });
3061    }
3062
3063    if rejected {
3064      let _generation = RuntimeGenerationGuard::enter(self.generation);
3065      let _ = catch_unwind_contained(|| drop(function.take()));
3066      drop(registration.take());
3067      return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3068        message: "the async runtime stopped before the blocking task could start".to_string(),
3069      }))));
3070    }
3071
3072    let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3073    let blocking = self.enter_blocking_owner(owner, owns_lane);
3074    let _registration = registration
3075      .take()
3076      .expect("blocking registration must be available");
3077    let _generation = RuntimeGenerationGuard::enter(self.generation);
3078    let result = {
3079      let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3080      catch_unwind_join_error(
3081        function
3082          .take()
3083          .expect("blocking function must be available"),
3084      )
3085    }
3086    .map(|output| ContainedTaskOutput::new(output, self.generation));
3087    drop(blocking);
3088    #[cfg(not(target_family = "wasm"))]
3089    if owns_lane && !self.draining.load(Ordering::Acquire) {
3090      // An admitted host turn already owns a bounded queue driver. Let its
3091      // outer loop service the released FIFO so blocking work cannot bypass
3092      // the host-turn budget and monopolize the JavaScript event loop.
3093      self.service_queued_blocking_work();
3094    }
3095    JoinHandle(JoinHandleInner::Ready(Some(result)))
3096  }
3097
3098  #[cfg(not(target_family = "wasm"))]
3099  fn take_queued_blocking(
3100    &self,
3101  ) -> Option<(
3102    Box<dyn FnOnce() + Send + 'static>,
3103    CurrentThreadBlockingGuard<'_>,
3104  )> {
3105    let (job, owner) = {
3106      let mut state = self
3107        .blocking_admission
3108        .state
3109        .lock()
3110        .unwrap_or_else(std::sync::PoisonError::into_inner);
3111      #[cfg(all(test, not(target_family = "wasm")))]
3112      run_current_thread_blocking_claim_test_hook();
3113      if self.stop.is_stopping()
3114        || state.closed
3115        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3116        || state.owner.is_some()
3117      {
3118        return None;
3119      }
3120      let job = state.queue.pop_front()?;
3121      let owner = self.fresh_owner_token();
3122      state.owner = Some(owner);
3123      (job, owner)
3124    };
3125    Some((job, self.enter_blocking_owner(owner, true)))
3126  }
3127
3128  #[cfg(not(target_family = "wasm"))]
3129  fn run_one_blocking(&self) -> bool {
3130    let Some((job, blocking)) = self.take_queued_blocking() else {
3131      return false;
3132    };
3133    let _generation = RuntimeGenerationGuard::enter(self.generation);
3134    {
3135      let _task = self.metrics.blocking_started(true);
3136      let _ = catch_unwind_contained(job);
3137    }
3138    drop(blocking);
3139    true
3140  }
3141
3142  #[cfg(not(target_family = "wasm"))]
3143  fn service_queued_blocking_work(&self) {
3144    while self.run_one_blocking() {}
3145  }
3146
3147  #[cfg(not(target_family = "wasm"))]
3148  fn has_serviceable_blocking_work(&self) -> bool {
3149    let state = self
3150      .blocking_admission
3151      .state
3152      .lock()
3153      .unwrap_or_else(std::sync::PoisonError::into_inner);
3154    !state.closed && state.owner.is_none() && !state.queue.is_empty()
3155  }
3156
3157  #[cfg(not(target_family = "wasm"))]
3158  fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3159    let Some(ambient_owner) = BLOCKING_OWNER
3160      .with(std::cell::Cell::get)
3161      .filter(|owner| owner.executor_id == self.id)
3162    else {
3163      return false;
3164    };
3165    let Some(current) = dependency.select_for_owner(ambient_owner) else {
3166      return false;
3167    };
3168    let job = {
3169      let mut state = self
3170        .blocking_admission
3171        .state
3172        .lock()
3173        .unwrap_or_else(std::sync::PoisonError::into_inner);
3174      #[cfg(all(test, not(target_family = "wasm")))]
3175      run_current_thread_blocking_claim_test_hook();
3176      if self.stop.is_stopping()
3177        || state.closed
3178        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3179        || state.owner != Some(ambient_owner)
3180      {
3181        return false;
3182      }
3183      if dependency.try_claim(&current) {
3184        state.queue.remove(current.publication.dependency.job)
3185      } else {
3186        None
3187      }
3188    };
3189    let Some(job) = job else {
3190      return false;
3191    };
3192    let _generation = RuntimeGenerationGuard::enter(self.generation);
3193    let _task = self.metrics.blocking_started(false);
3194    let _ = catch_unwind_contained(job);
3195    true
3196  }
3197
3198  fn close_blocking_admission(&self) {
3199    #[cfg(not(target_family = "wasm"))]
3200    let queued = {
3201      let mut state = self
3202        .blocking_admission
3203        .state
3204        .lock()
3205        .unwrap_or_else(std::sync::PoisonError::into_inner);
3206      state.closed = true;
3207      state.queue.closed = true;
3208      state.queue.take_all()
3209    };
3210    #[cfg(target_family = "wasm")]
3211    {
3212      let mut state = self
3213        .blocking_admission
3214        .state
3215        .lock()
3216        .unwrap_or_else(std::sync::PoisonError::into_inner);
3217      state.closed = true;
3218    }
3219    #[cfg(not(target_family = "wasm"))]
3220    for job in queued {
3221      let _ = catch_unwind_contained(|| drop(job));
3222    }
3223  }
3224
3225  #[cfg(test)]
3226  fn with_task_dispatch(
3227    metrics: Arc<RuntimeMetrics>,
3228    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3229  ) -> Self {
3230    Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
3231  }
3232
3233  #[cfg(test)]
3234  fn with_task_dispatch_registry(
3235    metrics: Arc<RuntimeMetrics>,
3236    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3237    task_drivers: &'static CurrentThreadTaskDriverRegistry,
3238  ) -> Self {
3239    let mut executor = Self::new(metrics);
3240    executor.task_dispatch = task_dispatch;
3241    executor.task_drivers = task_drivers;
3242    executor
3243  }
3244
3245  fn schedule(self: &Arc<Self>, runnable: Runnable) {
3246    self.metrics.runnable_scheduled();
3247    let rejected = {
3248      let mut queue = self
3249        .queue
3250        .lock()
3251        .unwrap_or_else(std::sync::PoisonError::into_inner);
3252      if queue.closed || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3253        Some(runnable)
3254      } else {
3255        queue.runnables.push_back(runnable);
3256        None
3257      }
3258    };
3259    if let Some(runnable) = rejected {
3260      self
3261        .metrics
3262        .queued_runnables
3263        .fetch_sub(1, Ordering::Relaxed);
3264      let _generation = RuntimeGenerationGuard::enter(self.generation);
3265      let _ = catch_unwind_contained(|| drop(runnable));
3266      return;
3267    }
3268    // Queue work and only then wake an explicit block_on driver. This is still
3269    // enqueue-only with respect to the wake caller: polling happens on the
3270    // driver's thread, never inline from an arbitrary waker invocation.
3271    self.stop.wake_all();
3272    if !self.draining.load(Ordering::Acquire) {
3273      self.request_drain();
3274    }
3275  }
3276
3277  /// Ask the host to enter a fresh JavaScript turn before polling queued work.
3278  ///
3279  /// A `Waker` may be invoked while its producer holds an internal mutex
3280  /// (`futures::Shared` does this). Polling inline from the scheduler callback
3281  /// can then re-enter that future and self-deadlock on the same mutex. Once a
3282  /// host dispatcher has accepted work, this executor stays host-driven for
3283  /// the rest of its generation; a temporarily missing env leaves work queued
3284  /// for the next registration or for shutdown cancellation.
3285  fn request_drain(self: &Arc<Self>) {
3286    let _ = self.request_drain_with(next_current_thread_dispatch_id);
3287  }
3288
3289  fn lock_scheduler_for_publication(
3290    &self,
3291  ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
3292    #[cfg(all(test, not(target_family = "wasm")))]
3293    match self.scheduler_idle_lock.try_lock() {
3294      Ok(scheduler) => scheduler,
3295      Err(std::sync::TryLockError::WouldBlock) => {
3296        run_current_thread_dispatch_publication_blocked_test_hook();
3297        self
3298          .scheduler_idle_lock
3299          .lock()
3300          .unwrap_or_else(std::sync::PoisonError::into_inner)
3301      }
3302      Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
3303    }
3304    #[cfg(any(not(test), target_family = "wasm"))]
3305    self
3306      .scheduler_idle_lock
3307      .lock()
3308      .unwrap_or_else(std::sync::PoisonError::into_inner)
3309  }
3310
3311  fn request_drain_with(
3312    self: &Arc<Self>,
3313    next_dispatch: impl FnOnce() -> u64,
3314  ) -> CurrentThreadDispatchRequest {
3315    let mut scheduler = self.lock_scheduler_for_publication();
3316    if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3317      return CurrentThreadDispatchRequest::Unavailable;
3318    }
3319    if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
3320      return CurrentThreadDispatchRequest::Coalesced;
3321    }
3322    let dispatch = next_dispatch();
3323    if self
3324      .dispatch_pending
3325      .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
3326      .is_err()
3327    {
3328      return CurrentThreadDispatchRequest::Coalesced;
3329    }
3330    let dispatch_call = self
3331      .begin_host_dispatch_publication(&mut scheduler, dispatch)
3332      .expect("a fresh CurrentThread dispatch must own its publication");
3333    drop(scheduler);
3334    match self.publish_host_dispatch(dispatch, dispatch_call) {
3335      CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
3336        CurrentThreadDispatchRequest::Accepted
3337      }
3338      CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
3339    }
3340  }
3341
3342  fn claim_dispatch_call<'a>(
3343    &'a self,
3344    scheduler: &mut CurrentThreadSchedulerState,
3345  ) -> CurrentThreadDispatchCall<'a> {
3346    let generation = RuntimeGenerationGuard::enter(self.generation);
3347    let active_dispatch_calls = scheduler
3348      .active_dispatch_calls
3349      .checked_add(1)
3350      .expect("CurrentThread active dispatch-call count exhausted");
3351    scheduler.active_dispatch_calls = active_dispatch_calls;
3352    CurrentThreadDispatchCall {
3353      executor: self,
3354      publication: None,
3355      _generation: generation,
3356    }
3357  }
3358
3359  fn begin_host_dispatch_publication<'a>(
3360    &'a self,
3361    scheduler: &mut CurrentThreadSchedulerState,
3362    dispatch: u64,
3363  ) -> Option<CurrentThreadDispatchCall<'a>> {
3364    // See the implementation notes in this crate's README.
3365    if let Some(publication) = scheduler
3366      .dispatch_publications
3367      .iter_mut()
3368      .find(|publication| publication.dispatch == dispatch)
3369    {
3370      publication.republish_requested = true;
3371      return None;
3372    }
3373    let mut dispatch_call = self.claim_dispatch_call(scheduler);
3374    scheduler
3375      .dispatch_publications
3376      .push(CurrentThreadDispatchPublication {
3377        dispatch,
3378        republish_requested: false,
3379      });
3380    dispatch_call.publication = Some(dispatch);
3381    Some(dispatch_call)
3382  }
3383
3384  fn publish_host_dispatch(
3385    self: &Arc<Self>,
3386    dispatch: u64,
3387    mut dispatch_call: CurrentThreadDispatchCall<'_>,
3388  ) -> CurrentThreadHostDispatchResult {
3389    let mut panic_payload = RetainedPanicPayload::default();
3390    loop {
3391      let result = match catch_unwind(AssertUnwindSafe(|| {
3392        #[cfg(all(test, not(target_family = "wasm")))]
3393        run_current_thread_dispatch_call_test_hook();
3394        let result = (self.task_dispatch)(dispatch);
3395        #[cfg(all(test, not(target_family = "wasm")))]
3396        run_current_thread_dispatch_returned_test_hook();
3397        result
3398      })) {
3399        Ok(result) => Some(result),
3400        Err(payload) => {
3401          panic_payload.capture(payload);
3402          None
3403        }
3404      };
3405
3406      let step = catch_unwind(AssertUnwindSafe(|| {
3407        let (republish, cancelling, failure, result) = {
3408          let mut scheduler = self
3409            .scheduler_idle_lock
3410            .lock()
3411            .unwrap_or_else(std::sync::PoisonError::into_inner);
3412          let index = scheduler
3413            .dispatch_publications
3414            .iter()
3415            .position(|publication| publication.dispatch == dispatch)
3416            .expect("the CurrentThread dispatch publication must remain registered");
3417          let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
3418          let republish = dispatch_remains_pending
3419            && !self.stop.is_stopping()
3420            && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
3421            && scheduler.dispatch_publications[index].republish_requested;
3422          if republish {
3423            if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3424              let _ = self.task_drivers.rearm_failed_publication(failure);
3425            }
3426            scheduler.dispatch_publications[index].republish_requested = false;
3427            (true, None, None, result)
3428          } else {
3429            dispatch_call.finish_publication(&mut scheduler);
3430            let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3431              #[cfg(all(test, not(target_family = "wasm")))]
3432              run_current_thread_failed_publication_retired_test_hook();
3433              self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3434            } else {
3435              None
3436            };
3437            let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
3438              && dispatch_remains_pending)
3439              .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
3440              .flatten();
3441            let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
3442              && !dispatch_remains_pending
3443            {
3444              // A synchronous callback may already have consumed, cancelled, or
3445              // superseded this exact capability.
3446              Some(CurrentThreadHostDispatchResult::Accepted)
3447            } else {
3448              result
3449            };
3450            (false, cancelling, failure, result)
3451          }
3452        };
3453
3454        if republish {
3455          return CurrentThreadDispatchPublicationStep::Republish;
3456        }
3457        if let Some(cancelling) = cancelling {
3458          self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
3459        }
3460        if let Some(failure) = failure {
3461          self.run_host_dispatch_failure(failure.action);
3462        }
3463        CurrentThreadDispatchPublicationStep::Complete(result)
3464      }));
3465
3466      match step {
3467        Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
3468        Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
3469          if panic_payload.is_some() {
3470            panic_payload.resume();
3471          }
3472          return result
3473            .expect("a CurrentThread host dispatch without a result must resume its panic");
3474        }
3475        Err(payload) => {
3476          panic_payload.capture(payload);
3477          panic_payload.resume();
3478        }
3479      }
3480    }
3481  }
3482
3483  fn claim_host_turn(
3484    self: &Arc<Self>,
3485    scheduler: &mut CurrentThreadSchedulerState,
3486  ) -> CurrentThreadHostTurn {
3487    scheduler.active_host_turns = scheduler
3488      .active_host_turns
3489      .checked_add(1)
3490      .expect("CurrentThread active host-turn count exhausted");
3491    self.draining.store(true, Ordering::Release);
3492    CurrentThreadHostTurn {
3493      executor: Arc::clone(self),
3494      draining: true,
3495      _generation: RuntimeGenerationGuard::enter(self.generation),
3496    }
3497  }
3498
3499  fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
3500    let mut scheduler = self
3501      .scheduler_idle_lock
3502      .lock()
3503      .unwrap_or_else(std::sync::PoisonError::into_inner);
3504    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3505      return None;
3506    }
3507    if dispatch == 0
3508      || self
3509        .dispatch_pending
3510        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3511        .is_err()
3512    {
3513      return None;
3514    }
3515    #[cfg(all(test, not(target_family = "wasm")))]
3516    run_current_thread_dispatch_claim_test_hook();
3517    let _ =
3518      self
3519        .recovery_dispatch
3520        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
3521    Some(self.claim_host_turn(&mut scheduler))
3522  }
3523
3524  #[cfg(test)]
3525  fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
3526    let mut scheduler = self
3527      .scheduler_idle_lock
3528      .lock()
3529      .unwrap_or_else(std::sync::PoisonError::into_inner);
3530    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3531      return None;
3532    }
3533    Some(self.claim_host_turn(&mut scheduler))
3534  }
3535
3536  #[cfg(test)]
3537  fn drive_host_turn(self: &Arc<Self>) {
3538    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3539    let host_turn = if dispatch == 0 {
3540      self.try_admit_host_turn_without_dispatch()
3541    } else {
3542      self.try_admit_host_turn(dispatch)
3543    };
3544    if let Some(mut host_turn) = host_turn {
3545      host_turn.drive();
3546    }
3547  }
3548
3549  #[cfg(test)]
3550  fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
3551    if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
3552      host_turn.drive();
3553    }
3554  }
3555
3556  /// Drive a host turn whose scheduler role was claimed while the controller
3557  /// still held the lifecycle mutex. See
3558  /// the implementation notes in this crate's README.
3559  fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
3560    #[cfg(not(target_family = "wasm"))]
3561    let mut runnable_streak = 0usize;
3562    for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
3563      #[cfg(not(target_family = "wasm"))]
3564      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
3565        runnable_streak = 0;
3566        if self.run_one_blocking() {
3567          continue;
3568        }
3569      }
3570      if self.drain_one() {
3571        #[cfg(not(target_family = "wasm"))]
3572        {
3573          runnable_streak += 1;
3574        }
3575        continue;
3576      }
3577      #[cfg(not(target_family = "wasm"))]
3578      if self.run_one_blocking() {
3579        runnable_streak = 0;
3580        continue;
3581      }
3582      break;
3583    }
3584    // A bounded turn releases queue-drain exclusivity before publishing its
3585    // continuation, but remains an active host turn until that publication
3586    // returns. Shutdown waits for the latter lifecycle role, not merely the
3587    // former exclusivity bit.
3588    host_turn.release_draining();
3589    self.request_drain_if_work_remains();
3590  }
3591
3592  fn request_drain_if_queued(self: &Arc<Self>) {
3593    if !self.has_serviceable_work() {
3594      return;
3595    }
3596    let mut scheduler = self.lock_scheduler_for_publication();
3597    if self.stop.is_stopping()
3598      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3599      || self.draining.load(Ordering::Acquire)
3600    {
3601      return;
3602    }
3603    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3604    if dispatch == 0 {
3605      drop(scheduler);
3606      self.request_drain_if_work_remains();
3607      return;
3608    }
3609
3610    // A newly registered host joins the existing all-host race through its own
3611    // delivery token. Reusing the executor capability preserves every other
3612    // host's accepted attempt and lets old registration tokens fail closed.
3613    let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
3614      return;
3615    };
3616    drop(scheduler);
3617    let _ = self.publish_host_dispatch(dispatch, dispatch_call);
3618  }
3619
3620  fn retire_unavailable_host_dispatch_locked(
3621    &self,
3622    dispatch: u64,
3623  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
3624    if self
3625      .dispatch_pending
3626      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3627      .is_err()
3628    {
3629      return None;
3630    }
3631    if self
3632      .recovery_dispatch
3633      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3634      .is_ok()
3635    {
3636      self.begin_failed_dispatch_cancellation()
3637    } else {
3638      None
3639    }
3640  }
3641
3642  fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
3643    // See the implementation notes in this crate's README.
3644    let failure = {
3645      let mut scheduler = self
3646        .scheduler_idle_lock
3647        .lock()
3648        .unwrap_or_else(std::sync::PoisonError::into_inner);
3649      self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3650    };
3651    if let Some(failure) = failure {
3652      self.run_host_dispatch_failure(failure.action);
3653    }
3654  }
3655
3656  fn begin_host_dispatch_failure_locked<'a>(
3657    &'a self,
3658    scheduler: &mut CurrentThreadSchedulerState,
3659    failure: CurrentThreadTaskDispatchFailure,
3660  ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
3661    if !self.task_drivers.is_current_failure(failure) {
3662      return None;
3663    }
3664    let registry_cancellation =
3665      CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
3666    let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
3667    Some(CurrentThreadDispatchFailureRecovery {
3668      action,
3669      _registry_cancellation: registry_cancellation,
3670    })
3671  }
3672
3673  fn begin_host_dispatch_failure_transition_locked<'a>(
3674    &'a self,
3675    scheduler: &mut CurrentThreadSchedulerState,
3676    dispatch: u64,
3677  ) -> CurrentThreadDispatchFailureAction<'a> {
3678    if dispatch == 0
3679      || self
3680        .dispatch_pending
3681        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3682        .is_err()
3683    {
3684      return CurrentThreadDispatchFailureAction::None;
3685    }
3686    #[cfg(all(test, not(target_family = "wasm")))]
3687    run_current_thread_dispatch_recovery_test_hook();
3688
3689    if self
3690      .recovery_dispatch
3691      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3692      .is_ok()
3693    {
3694      // The one fresh-turn replacement failed too. Polling inline here would
3695      // re-enter the host callback, while another retry could spin forever.
3696      // Claim terminal cancellation before allowing another publication.
3697      return self.begin_failed_dispatch_cancellation().map_or(
3698        CurrentThreadDispatchFailureAction::None,
3699        CurrentThreadDispatchFailureAction::Cancel,
3700      );
3701    }
3702
3703    if self.stop.is_stopping()
3704      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3705      || self.draining.load(Ordering::Acquire)
3706      || !self.has_serviceable_work()
3707    {
3708      return CurrentThreadDispatchFailureAction::None;
3709    }
3710
3711    // Consume the failed capability and reserve its sole replacement while
3712    // publication is excluded. A newly registered host can only join this
3713    // tagged replacement, never publish an untagged dispatch in between.
3714    let replacement = next_current_thread_dispatch_id();
3715    self.recovery_dispatch.store(replacement, Ordering::Release);
3716    self.dispatch_pending.store(replacement, Ordering::Release);
3717    let dispatch_call = self
3718      .begin_host_dispatch_publication(scheduler, replacement)
3719      .expect("a reserved CurrentThread replacement must own its publication");
3720    CurrentThreadDispatchFailureAction::Publish {
3721      dispatch: replacement,
3722      call: dispatch_call,
3723    }
3724  }
3725
3726  #[cfg(test)]
3727  fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
3728    let failure = {
3729      let mut scheduler = self
3730        .scheduler_idle_lock
3731        .lock()
3732        .unwrap_or_else(std::sync::PoisonError::into_inner);
3733      self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
3734    };
3735    self.run_host_dispatch_failure(failure);
3736  }
3737
3738  fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
3739    match failure {
3740      CurrentThreadDispatchFailureAction::None => {}
3741      CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
3742        self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
3743      }
3744      CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
3745        let _ = self.publish_host_dispatch(dispatch, call);
3746      }
3747    }
3748  }
3749
3750  fn has_serviceable_work(&self) -> bool {
3751    let has_queued_runnable = {
3752      let queue = self
3753        .queue
3754        .lock()
3755        .unwrap_or_else(std::sync::PoisonError::into_inner);
3756      !queue.closed && !queue.runnables.is_empty()
3757    };
3758    #[cfg(not(target_family = "wasm"))]
3759    let has_queued_blocking = self.has_serviceable_blocking_work();
3760    #[cfg(target_family = "wasm")]
3761    let has_queued_blocking = false;
3762    has_queued_runnable || has_queued_blocking
3763  }
3764
3765  fn request_drain_if_work_remains(self: &Arc<Self>) {
3766    if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
3767      self.request_drain();
3768    }
3769  }
3770
3771  fn begin_failed_dispatch_cancellation(
3772    &self,
3773  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
3774    if self
3775      .cancelling_failed_dispatch
3776      .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
3777      .is_err()
3778    {
3779      return None;
3780    }
3781    self.dispatch_pending.store(0, Ordering::Release);
3782    self.recovery_dispatch.store(0, Ordering::Release);
3783    Some(CurrentThreadDispatchCancellationGuard(self))
3784  }
3785
3786  fn cancel_work_after_host_dispatch_failure_claimed(
3787    &self,
3788    _cancelling: CurrentThreadDispatchCancellationGuard<'_>,
3789  ) {
3790    let queued = {
3791      let mut queue = self
3792        .queue
3793        .lock()
3794        .unwrap_or_else(std::sync::PoisonError::into_inner);
3795      std::mem::take(&mut queue.runnables)
3796    };
3797    #[cfg(not(target_family = "wasm"))]
3798    let queued_blocking = {
3799      let mut state = self
3800        .blocking_admission
3801        .state
3802        .lock()
3803        .unwrap_or_else(std::sync::PoisonError::into_inner);
3804      state.queue.take_all()
3805    };
3806
3807    let _generation = RuntimeGenerationGuard::enter(self.generation);
3808    for runnable in queued {
3809      self
3810        .metrics
3811        .queued_runnables
3812        .fetch_sub(1, Ordering::Relaxed);
3813      let _ = catch_unwind_contained(|| drop(runnable));
3814    }
3815    #[cfg(not(target_family = "wasm"))]
3816    for job in queued_blocking {
3817      let _ = catch_unwind_contained(|| drop(job));
3818    }
3819  }
3820
3821  fn take_runnable(&self) -> Option<Runnable> {
3822    // Terminal dispatch cancellation publishes while holding `scheduler_idle_lock`;
3823    // generation shutdown publishes while holding `stop.publication`. Claiming
3824    // under both locks gives each publication one exact ordering edge with the
3825    // runnable queue. The guards are released before polling user code.
3826    let scheduler = self
3827      .scheduler_idle_lock
3828      .lock()
3829      .unwrap_or_else(std::sync::PoisonError::into_inner);
3830    #[cfg(all(test, not(target_family = "wasm")))]
3831    run_current_thread_runnable_claim_test_hook();
3832    let stop_publication = self.stop.publication_guard();
3833    if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3834      return None;
3835    }
3836    let runnable = {
3837      let mut queue = self
3838        .queue
3839        .lock()
3840        .unwrap_or_else(std::sync::PoisonError::into_inner);
3841      if queue.closed {
3842        None
3843      } else {
3844        queue.runnables.pop_front()
3845      }
3846    };
3847    drop(stop_publication);
3848    drop(scheduler);
3849    runnable
3850  }
3851
3852  fn drain_one(&self) -> bool {
3853    let runnable = self.take_runnable();
3854    if let Some(runnable) = runnable {
3855      #[cfg(not(target_family = "wasm"))]
3856      let _non_owner = BlockingOwnerGuard::enter(None);
3857      run_runnable(&self.metrics, runnable);
3858      true
3859    } else {
3860      false
3861    }
3862  }
3863
3864  /// Drive `future` to completion on this thread, draining queued runnables
3865  /// between polls. This loop OWNS its park site: the future is polled with
3866  /// this frame's own [`DriverParker`] as its waker, so the park DECISION can
3867  /// inspect the wake token directly instead of handing it to
3868  /// `futures::executor::block_on`'s opaque parker:
3869  ///
3870  ///   * `Pending` + queue empty + wake token pending: a self-waking future
3871  ///     (the `PendThenReady` shape) -- consume the token and re-poll. NOT a
3872  ///     park, NOT a deadlock; panic only at the park decision, never at
3873  ///     Pending-return.
3874  ///   * `Pending` + queue empty + NO token, threadless build: PROVABLE
3875  ///     deadlock -- no other thread exists that could ever deliver the wake
3876  ///     (the block_on-awaiting-JS class). Panic immediately with the typed
3877  ///     [`BlockOnDeadlock`] diagnostic. A pending HOST timer is no
3878  ///     rescue here: its `setTimeout` relay shares this very thread, so it
3879  ///     can never fire while the park holds it -- it only selects the
3880  ///     timer-backed variant of the same certain diagnostic.
3881  ///   * `Pending` + queue empty + NO token, threaded build: park. When a
3882  ///     `park_deadline` is armed, the park is bounded with progress-based
3883  ///     reset (see [`park_with_deadline`]) and panics typed if a full
3884  ///     window passes with zero runtime progress.
3885  fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
3886    let parker = Arc::new(DriverParker::default());
3887    let _stop_registration = self.stop.register(&parker);
3888    #[cfg(not(target_family = "wasm"))]
3889    let dependency = BlockOnDependencyGuard::enter(self.generation);
3890    let waker = std::task::Waker::from(Arc::clone(&parker));
3891    let mut cx = Context::from_waker(&waker);
3892    #[cfg(not(target_family = "wasm"))]
3893    let mut runnable_streak = 0usize;
3894    loop {
3895      if self.stop.is_stopping() {
3896        return BlockOnOutcome::Stopped;
3897      }
3898      #[cfg(not(target_family = "wasm"))]
3899      dependency.clear();
3900      if future.as_mut().poll(&mut cx).is_ready() {
3901        return BlockOnOutcome::Completed;
3902      }
3903      // Shutdown must outrank queue work and self-wake permits. Otherwise an
3904      // always-self-waking future can continue before reaching the park-site
3905      // check forever while its generation work guard blocks quiescence.
3906      if self.stop.is_stopping() {
3907        return BlockOnOutcome::Stopped;
3908      }
3909      #[cfg(not(target_family = "wasm"))]
3910      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
3911        runnable_streak = 0;
3912        if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
3913          continue;
3914        }
3915      }
3916      if self.drain_one() {
3917        #[cfg(not(target_family = "wasm"))]
3918        {
3919          runnable_streak += 1;
3920        }
3921        continue;
3922      }
3923      #[cfg(not(target_family = "wasm"))]
3924      if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
3925        runnable_streak = 0;
3926        continue;
3927      }
3928      #[cfg(not(target_family = "wasm"))]
3929      {
3930        runnable_streak = 0;
3931      }
3932      // PARK DECISION: the future is Pending and the queue is empty. A token
3933      // stored during the poll (or by a racing thread) means a wake is
3934      // already pending: consume it and re-poll instead of parking.
3935      if parker.consume_permit() {
3936        continue;
3937      }
3938      // Threadless timer invariant: on a THREADLESS build even a pending
3939      // HOST timer is NOT a wake-token source -- the host event loop's
3940      // `setTimeout` relay can only run on the very thread that is about to
3941      // park, so it can never fire while `block_on` holds it. EVERY threadless
3942      // park at this decision is therefore provably dead; a pending timer only
3943      // selects the more precise diagnostic. Falling through instead would hit
3944      // `parker.park()`, which on the real target (wasm32-wasip1 no_threads
3945      // std) is an untyped "condvar wait not supported" abort -- a diagnostics
3946      // downgrade, never a rescue. (The deadline verdict's LIVE-wait veto
3947      // below stays: it serves the THREADED CT flavor, where the host loop
3948      // genuinely runs concurrently.)
3949      if self.threadless {
3950        if self.host_timers.has_pending() {
3951          std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
3952        }
3953        std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
3954      }
3955      match self.park_deadline {
3956        // A wake landing between `consume_permit` above and the park is
3957        // stored as the parker's permit, so `park` returns immediately --
3958        // never lost.
3959        None => parker.park(),
3960        Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
3961          DeadlineParkOutcome::Woken => {}
3962          DeadlineParkOutcome::Expired(armed) => {
3963            // EXPIRY DECISION (CurrentThread has no parked-driver registry):
3964            // a wake can land in the instructions after the window
3965            // closed, and the fingerprint read inside `park_with_deadline`
3966            // is stale by now. Re-check both before declaring death; a hit
3967            // means the park was healthy -- continue the loop (re-poll,
3968            // drain, fresh full window on the next park). A LIVE host-timer
3969            // wait also vetoes, re-checked LAST -- arming a host timer bumps
3970            // no progress
3971            // counter, so it is invisible to the fingerprint read: waiting
3972            // out a host timer with zero progress is legitimate, its wake is
3973            // scheduled on the host loop. `deadline` doubles as the grace
3974            // for host firing lag; a timer past due by more than one full
3975            // window without firing is the frozen-JS-loop signature this
3976            // detection exists for, so it stops vetoing.
3977            if parker.consume_permit()
3978              || self.metrics.progress_fingerprint() != armed
3979              || self.host_timers.has_live_wait(deadline)
3980            {
3981              continue;
3982            }
3983            std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
3984          }
3985        },
3986      }
3987    }
3988  }
3989
3990  fn begin_shutdown(&self) {
3991    let _generation = RuntimeGenerationGuard::enter(self.generation);
3992    self.stop.begin_shutdown();
3993    self.close_blocking_admission();
3994    let queued = {
3995      let mut queue = self
3996        .queue
3997        .lock()
3998        .unwrap_or_else(std::sync::PoisonError::into_inner);
3999      queue.closed = true;
4000      std::mem::take(&mut queue.runnables)
4001    };
4002    self.dispatch_pending.store(0, Ordering::Release);
4003    self.recovery_dispatch.store(0, Ordering::Release);
4004    self.task_drivers.retire_for_shutdown();
4005    for runnable in queued {
4006      self
4007        .metrics
4008        .queued_runnables
4009        .fetch_sub(1, Ordering::Relaxed);
4010      // Dropping the last runnable cancels its detached async-task and retires
4011      // the generation guard. Isolate user future destructors from shutdown.
4012      let _ = catch_unwind_contained(|| drop(runnable));
4013    }
4014    self.host_timers.shutdown();
4015  }
4016
4017  fn wait_until_scheduler_idle(&self) {
4018    let mut idle = self
4019      .scheduler_idle_lock
4020      .lock()
4021      .unwrap_or_else(std::sync::PoisonError::into_inner);
4022    while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
4023      idle = self
4024        .scheduler_idle
4025        .wait(idle)
4026        .unwrap_or_else(std::sync::PoisonError::into_inner);
4027    }
4028    debug_assert!(
4029      idle.dispatch_publications.is_empty(),
4030      "an idle CurrentThread scheduler must not retain dispatch publications"
4031    );
4032  }
4033}
4034
4035// Identifies the executor whose Rayon pool owns THIS worker thread (or `None`
4036// when not on a pool worker). Used by `MultiThreadExecutor::block_on` to detect
4037// a re-entrant (on-pool) call OF THE SAME EXECUTOR and drive the queue
4038// cooperatively instead of parking the worker. Carrying the executor id (not a
4039// bare bool) keeps this marker scoped exactly like
4040// `BlockingOwnerToken.executor_id`: a worker of executor A that re-enters
4041// `block_on` on executor B must NOT be treated as B's on-pool driver -- it owns
4042// none of B's accounting, so it parks like any foreign-thread caller rather
4043// than driving B's queues as an unaccounted within-cap driver. Under the single
4044// global executor used in production these ids always match, so this is a
4045// defensive scoping gate (consistent with the blocking-owner machinery), not a
4046// reachable production change.
4047#[cfg(not(target_family = "wasm"))]
4048thread_local! {
4049  static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4050  /// Identifies the dedicated timer thread for lifecycle self-wait
4051  /// prevention. It is deliberately separate from `ON_POOL_WORKER`: the
4052  /// timer thread must never become a cooperative Rayon scheduler driver.
4053  static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4054}
4055
4056// Identifies scheduler-driver frames on a pool worker. Unlike
4057// `ON_POOL_WORKER`, this is not installed by Rayon's worker start hook:
4058// arbitrary nested Rayon jobs need cooperative `block_on` classification, but
4059// must not place work in the per-thread LIFO slot because no drain frame may
4060// remain to consume it after the Rayon job returns.
4061#[cfg(not(target_family = "wasm"))]
4062#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4063struct SchedulerDriverRole {
4064  executor_id: u64,
4065  can_run_blocking: bool,
4066}
4067
4068#[cfg(not(target_family = "wasm"))]
4069thread_local! {
4070  static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
4071    const { std::cell::Cell::new(None) };
4072}
4073
4074// Per-worker LIFO slot (tokio-style): holds at most ONE
4075// runnable that was scheduled FROM this pool worker (typically a task the
4076// currently running runnable just woke). It is popped by this same thread's
4077// next drain/run_one iteration BEFORE the shared FIFO, so the hot
4078// wake-then-await ping-pong pattern stays on one core with no queue mutex, no
4079// wake and no drainer spawn. Tagged with the executor id -- the same scoping
4080// pattern as `ON_POOL_WORKER` and `BlockingOwnerToken.executor_id` -- so only
4081// drain/run_one/flush of the SAME executor may pop it.
4082//
4083// STRANDING INVARIANT: slot work is invisible to every other
4084// thread (`finish_draining`'s re-check and `wake_one` cannot see it), so the
4085// owning thread MUST either run its slot entry or flush it to the shared FIFO
4086// before it stops draining -- see the flush calls at `drain`'s returns, the
4087// pre-park flush in `cooperative_block_on`, and `LifoSlotFlushGuard` for
4088// unwinds. A runnable left here while the thread returns to rayon would be a
4089// silently lost task and a `queued_runnables` leak.
4090#[cfg(not(target_family = "wasm"))]
4091thread_local! {
4092  static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
4093    const { std::cell::Cell::new(None) };
4094}
4095
4096#[cfg(not(target_family = "wasm"))]
4097struct OnPoolWorkerGuard {
4098  previous_worker: Option<u64>,
4099  previous_driver: Option<SchedulerDriverRole>,
4100}
4101
4102#[cfg(not(target_family = "wasm"))]
4103impl OnPoolWorkerGuard {
4104  // Save the previous marker and install `id`, so nested/re-entrant drains
4105  // (possibly from different executors on the same thread) restore the exact
4106  // prior executor id on drop instead of unconditionally clearing it.
4107  fn enter(id: u64) -> Self {
4108    Self::enter_with_role(id, true)
4109  }
4110
4111  fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
4112    Self {
4113      previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
4114      previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
4115        flag.replace(Some(SchedulerDriverRole {
4116          executor_id: id,
4117          can_run_blocking,
4118        }))
4119      }),
4120    }
4121  }
4122}
4123
4124#[cfg(not(target_family = "wasm"))]
4125impl Drop for OnPoolWorkerGuard {
4126  fn drop(&mut self) {
4127    IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
4128    ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
4129  }
4130}
4131
4132// Identifies one specific counted-blocking owner frame on one specific executor.
4133//
4134// `executor_id` is assigned once per executor (so a stale token left on a
4135// thread by a shut-down executor can never authorize work on a replacement);
4136// `frame` is unique per blocking-lane entry. An owner frame is the eligibility
4137// half of dependency lending: the exact job half comes from the pending
4138// `JoinHandle` dependency recorded by the owning `block_on` frame. A plain
4139// runnable driver owns no frame, so it must respect normal blocking admission.
4140//
4141// CurrentThread uses the same identity to distinguish same-stack lane borrowing
4142// from cross-driver contention. MultiThread additionally reserves active owner
4143// frames while an otherwise-idle cooperative driver reuses their admitted lane.
4144#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
4145struct BlockingOwnerToken {
4146  executor_id: u64,
4147  frame: u64,
4148}
4149
4150#[cfg(not(target_family = "wasm"))]
4151enum OwnedBlockingOutcome {
4152  Ineligible,
4153  OwnerBusy(BlockingOwnerToken),
4154  ReservationReleased {
4155    owner: BlockingOwnerToken,
4156    ran: bool,
4157  },
4158}
4159
4160// Process-global owner-frame source for per-frame dependency-lending
4161// isolation. Executor ids use `NEXT_RUNTIME_EXECUTOR_ID` above on every target.
4162static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
4163#[cfg(not(target_family = "wasm"))]
4164static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
4165
4166// The owner frame (if any) currently running a counted blocking closure on THIS
4167// thread. The exact frame identity scopes any over-cap dependency lending.
4168thread_local! {
4169  static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
4170    const { std::cell::Cell::new(None) };
4171}
4172
4173// Save/replace/restore guard for `BLOCKING_OWNER`. Enter with `Some(token)` while
4174// running a counted blocking closure, or with `None` while a cooperative driver
4175// runs a plain queued runnable (ownership is LEXICAL to the blocking closure: a
4176// runnable an owner happens to drive via `run_one` is a logical non-owner and
4177// must not inherit the owner's over-cap privilege). Restored on drop, so the
4178// pattern is panic-safe even though `run_runnable` already catches panics.
4179#[cfg(not(target_family = "wasm"))]
4180struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
4181
4182#[cfg(not(target_family = "wasm"))]
4183impl BlockingOwnerGuard {
4184  fn enter(token: Option<BlockingOwnerToken>) -> Self {
4185    Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
4186  }
4187}
4188
4189#[cfg(not(target_family = "wasm"))]
4190impl Drop for BlockingOwnerGuard {
4191  fn drop(&mut self) {
4192    BLOCKING_OWNER.with(|cell| cell.set(self.0));
4193  }
4194}
4195
4196#[cfg(not(target_family = "wasm"))]
4197#[derive(Default)]
4198struct ActiveBlockingOwners {
4199  // `None` is available; `Some(id)` is reserved by exactly that lending
4200  // transfer. The identity prevents a delayed stale drop from releasing a
4201  // newer reservation of the same owner frame.
4202  owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
4203}
4204
4205#[cfg(not(target_family = "wasm"))]
4206impl ActiveBlockingOwners {
4207  fn register(&self, owner: BlockingOwnerToken) {
4208    let previous = self
4209      .owners
4210      .lock()
4211      .unwrap_or_else(std::sync::PoisonError::into_inner)
4212      .insert(owner, None);
4213    debug_assert!(previous.is_none());
4214  }
4215
4216  fn unregister(&self, owner: BlockingOwnerToken) {
4217    let removed = self
4218      .owners
4219      .lock()
4220      .unwrap_or_else(std::sync::PoisonError::into_inner)
4221      .remove(&owner);
4222    debug_assert!(removed.is_some());
4223  }
4224
4225  fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
4226    let mut owners = self
4227      .owners
4228      .lock()
4229      .unwrap_or_else(std::sync::PoisonError::into_inner);
4230    let reservation = owners.get_mut(&owner)?;
4231    if reservation.is_some() {
4232      return None;
4233    }
4234    let id = next_unique_id(
4235      &NEXT_OWNER_RESERVATION,
4236      "blocking owner reservation id space exhausted",
4237    );
4238    *reservation = Some(id);
4239    Some(id)
4240  }
4241
4242  fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
4243    let mut owners = self
4244      .owners
4245      .lock()
4246      .unwrap_or_else(std::sync::PoisonError::into_inner);
4247    if let Some(current) = owners.get_mut(&owner)
4248      && *current == Some(reservation)
4249    {
4250      *current = None;
4251    }
4252  }
4253
4254  fn is_available(&self, owner: BlockingOwnerToken) -> bool {
4255    self
4256      .owners
4257      .lock()
4258      .unwrap_or_else(std::sync::PoisonError::into_inner)
4259      .get(&owner)
4260      .is_some_and(Option::is_none)
4261  }
4262}
4263
4264#[cfg(not(target_family = "wasm"))]
4265struct ActiveBlockingOwnerGuard<'a> {
4266  owners: &'a ActiveBlockingOwners,
4267  owner: BlockingOwnerToken,
4268}
4269
4270#[cfg(not(target_family = "wasm"))]
4271impl<'a> ActiveBlockingOwnerGuard<'a> {
4272  fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
4273    owners.register(owner);
4274    Self { owners, owner }
4275  }
4276}
4277
4278#[cfg(not(target_family = "wasm"))]
4279impl Drop for ActiveBlockingOwnerGuard<'_> {
4280  fn drop(&mut self) {
4281    self.owners.unregister(self.owner);
4282  }
4283}
4284
4285#[cfg(not(target_family = "wasm"))]
4286struct OwnerLaneReservation<'a> {
4287  owners: &'a ActiveBlockingOwners,
4288  owner: BlockingOwnerToken,
4289  reservation: u64,
4290}
4291
4292#[cfg(not(target_family = "wasm"))]
4293impl<'a> OwnerLaneReservation<'a> {
4294  fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
4295    let reservation = owners.reserve(owner)?;
4296    Some(Self {
4297      owners,
4298      owner,
4299      reservation,
4300    })
4301  }
4302}
4303
4304#[cfg(not(target_family = "wasm"))]
4305impl Drop for OwnerLaneReservation<'_> {
4306  fn drop(&mut self) {
4307    self.owners.release(self.owner, self.reservation);
4308  }
4309}
4310
4311// A blocking job queued for the pool. Its stable id is copied into the returned
4312// JoinHandle and propagated through async JoinHandle dependencies, allowing a
4313// saturated owner to run exactly the job its nested block_on awaits.
4314#[cfg(not(target_family = "wasm"))]
4315type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
4316
4317#[cfg(not(target_family = "wasm"))]
4318struct QueuedBlocking {
4319  id: BlockingJobId,
4320  run: BlockingClosure,
4321}
4322
4323#[cfg(not(target_family = "wasm"))]
4324struct BlockingQueueNode {
4325  previous: Option<BlockingJobId>,
4326  next: Option<BlockingJobId>,
4327  run: Box<dyn FnOnce() + Send + 'static>,
4328}
4329
4330#[cfg(not(target_family = "wasm"))]
4331struct BlockingQueue {
4332  closed: bool,
4333  head: Option<BlockingJobId>,
4334  tail: Option<BlockingJobId>,
4335  jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
4336}
4337
4338#[cfg(not(target_family = "wasm"))]
4339impl BlockingQueue {
4340  fn push(&mut self, job: QueuedBlocking) {
4341    let previous = self.tail;
4342    let replaced = self.jobs.insert(
4343      job.id,
4344      BlockingQueueNode {
4345        previous,
4346        next: None,
4347        run: job.run,
4348      },
4349    );
4350    debug_assert!(replaced.is_none());
4351    if let Some(previous) = previous {
4352      self
4353        .jobs
4354        .get_mut(&previous)
4355        .expect("blocking queue tail must be indexed")
4356        .next = Some(job.id);
4357    } else {
4358      self.head = Some(job.id);
4359    }
4360    self.tail = Some(job.id);
4361  }
4362
4363  fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4364    self.head.and_then(|id| self.remove(id))
4365  }
4366
4367  fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4368    let node = self.jobs.remove(&id)?;
4369    if let Some(previous) = node.previous {
4370      self
4371        .jobs
4372        .get_mut(&previous)
4373        .expect("blocking queue predecessor must be indexed")
4374        .next = node.next;
4375    } else {
4376      self.head = node.next;
4377    }
4378    if let Some(next) = node.next {
4379      self
4380        .jobs
4381        .get_mut(&next)
4382        .expect("blocking queue successor must be indexed")
4383        .previous = node.previous;
4384    } else {
4385      self.tail = node.previous;
4386    }
4387    Some(node.run)
4388  }
4389
4390  fn is_empty(&self) -> bool {
4391    debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
4392    debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
4393    self.jobs.is_empty()
4394  }
4395
4396  fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
4397    self.head = None;
4398    self.tail = None;
4399    std::mem::take(&mut self.jobs)
4400      .into_values()
4401      .map(|node| node.run)
4402      .collect()
4403  }
4404}
4405
4406#[cfg(not(target_family = "wasm"))]
4407struct WorkerLifecycle {
4408  remaining: Mutex<usize>,
4409  handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
4410}
4411
4412#[cfg(not(target_family = "wasm"))]
4413impl WorkerLifecycle {
4414  fn new(worker_threads: usize) -> Arc<Self> {
4415    Arc::new(Self {
4416      remaining: Mutex::new(worker_threads),
4417      handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
4418    })
4419  }
4420
4421  fn retain(&self, handle: std::thread::JoinHandle<()>) {
4422    self
4423      .handles
4424      .lock()
4425      .unwrap_or_else(std::sync::PoisonError::into_inner)
4426      .as_mut()
4427      .expect("runtime worker handles already joined")
4428      .push(handle);
4429  }
4430
4431  fn worker_exited(&self) {
4432    let mut remaining = self
4433      .remaining
4434      .lock()
4435      .unwrap_or_else(std::sync::PoisonError::into_inner);
4436    *remaining = remaining
4437      .checked_sub(1)
4438      .expect("runtime worker exit count underflow");
4439  }
4440
4441  fn wait_for_all_workers(&self) {
4442    let handles = self
4443      .handles
4444      .lock()
4445      .unwrap_or_else(std::sync::PoisonError::into_inner)
4446      .take()
4447      .expect("runtime worker handles already joined");
4448    for handle in handles {
4449      if let Err(payload) = handle.join() {
4450        discard_panic_payload(payload);
4451      }
4452    }
4453  }
4454
4455  #[cfg(test)]
4456  fn remaining(&self) -> usize {
4457    *self
4458      .remaining
4459      .lock()
4460      .unwrap_or_else(std::sync::PoisonError::into_inner)
4461  }
4462}
4463
4464#[cfg(not(target_family = "wasm"))]
4465struct MultiThreadExecutor {
4466  generation: u64,
4467  stop: Arc<GenerationStop>,
4468  // Stable per-executor id; tags owner tokens so a stale token from a shut-down
4469  // executor can never authorize an over-cap escape on a replacement.
4470  id: u64,
4471  pool: ThreadPool,
4472  queue: Mutex<VecDeque<Runnable>>,
4473  blocking_queue: Mutex<BlockingQueue>,
4474  active_drainers: AtomicUsize,
4475  active_blocking: AtomicUsize,
4476  scheduler_idle_lock: Mutex<()>,
4477  scheduler_idle: Condvar,
4478  worker_lifecycle: Arc<WorkerLifecycle>,
4479  // Registry of pool workers currently parked inside a re-entrant cooperative
4480  // `block_on`, woken ONE at a time when new queue work is scheduled. The pool
4481  // has a fixed number of OS threads, so a parked worker cannot rely on
4482  // `ensure_drainer` spawning a replacement (there is no free thread to run
4483  // it) -- it must wake and run the work itself. Future-wakes do NOT go
4484  // through this registry: each driver's own `DriverParker` is its awaited
4485  // future's waker (see the wake-domain note at `DriverParker`).
4486  parked_drivers: ParkedDrivers,
4487  active_blocking_owners: ActiveBlockingOwners,
4488  #[cfg(test)]
4489  owner_lending_attempts: AtomicUsize,
4490  max_drainers: usize,
4491  max_blocking: usize,
4492  // Deadline-based deadlock detection for COOPERATIVE driver parks ONLY. The
4493  // foreign/napi whole-build park in `block_on`'s else-branch is exempt by
4494  // design because it parks for entire builds and a deadline there would
4495  // panic healthy production runs. Off unless
4496  // configured via `RuntimeOptions::park_deadline` (the embedder resolves
4497  // `PARK_DEADLINE_ENV` into it).
4498  park_deadline: Option<Duration>,
4499  // Shared with the timer heap because firing removes the timer before invoking
4500  // its waker. The admission remains active until that wake has published any
4501  // resulting runnable, so a final verdict cannot observe both sources empty.
4502  deadlock_state: Arc<MultiThreadDeadlockState>,
4503  // Runtime-owned timer heap plus its dedicated non-Rayon service thread.
4504  // Cooperative parks also fire due timers after timer-bounded waits.
4505  timers: TimerHeap,
4506  metrics: Arc<RuntimeMetrics>,
4507}
4508
4509#[cfg(not(target_family = "wasm"))]
4510impl MultiThreadExecutor {
4511  /// Max work units before a drainer yields. Cooperative drivers use the same
4512  /// value as the LIFO streak cap.
4513  const RUNNABLE_BUDGET: usize = 64;
4514  /// Force one blocking admission after this many consecutive runnable polls
4515  /// when blocking capacity and queued work are both available.
4516  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
4517
4518  #[cfg(test)]
4519  fn new(
4520    options: &RuntimeOptions,
4521    metrics: Arc<RuntimeMetrics>,
4522  ) -> Result<Self, RuntimeConfigError> {
4523    Self::new_for_generation(
4524      options,
4525      metrics,
4526      u64::MAX,
4527      Arc::new(GenerationStop::default()),
4528    )
4529  }
4530
4531  fn new_for_generation(
4532    options: &RuntimeOptions,
4533    metrics: Arc<RuntimeMetrics>,
4534    generation: u64,
4535    stop: Arc<GenerationStop>,
4536  ) -> Result<Self, RuntimeConfigError> {
4537    let id = next_unique_id(
4538      &NEXT_RUNTIME_EXECUTOR_ID,
4539      "async runtime executor id space exhausted",
4540    );
4541    let thread_name_prefix = options.thread_name_prefix.clone();
4542    let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
4543      options.park_deadline.is_some(),
4544    ));
4545    let timers = TimerHeap::new(
4546      generation,
4547      id,
4548      format!("{thread_name_prefix}-timer"),
4549      Arc::clone(&deadlock_state),
4550    );
4551    // Production options are validated before construction, so this exact
4552    // physical count is also the configured/reported count. Direct executor
4553    // unit tests may still construct a one-thread topology intentionally.
4554    let pool_threads = options.worker_threads;
4555    let worker_lifecycle = WorkerLifecycle::new(pool_threads);
4556    let exit_lifecycle = Arc::clone(&worker_lifecycle);
4557    let spawn_lifecycle = Arc::clone(&worker_lifecycle);
4558    let pool = ThreadPoolBuilder::new()
4559      .num_threads(pool_threads)
4560      .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
4561      .start_handler(move |_| {
4562        ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
4563      })
4564      .exit_handler(move |_| {
4565        IN_SCHEDULER_DRIVER.with(|driver| {
4566          if driver.get().is_some_and(|role| role.executor_id == id) {
4567            driver.set(None);
4568          }
4569        });
4570        // Keep ON_POOL_WORKER set until the OS thread exits. TLS destructors
4571        // run after this Rayon hook and may re-enter lifecycle APIs while the
4572        // external shutdown caller is joining this worker; retaining the
4573        // executor identity makes that re-entry fail fast instead of waiting
4574        // in Stopping and deadlocking the join.
4575        exit_lifecycle.worker_exited();
4576      })
4577      .spawn_handler(move |thread| {
4578        let mut builder = std::thread::Builder::new();
4579        if let Some(name) = thread.name() {
4580          builder = builder.name(name.to_string());
4581        }
4582        if let Some(stack_size) = thread.stack_size() {
4583          builder = builder.stack_size(stack_size);
4584        }
4585        let handle = builder.spawn(move || thread.run())?;
4586        spawn_lifecycle.retain(handle);
4587        Ok(())
4588      })
4589      .build()
4590      .map_err(|error| {
4591        worker_lifecycle.wait_for_all_workers();
4592        RuntimeConfigError(format!("failed to create runtime workers: {error}"))
4593      })?;
4594    Ok(Self {
4595      generation,
4596      stop,
4597      id,
4598      pool,
4599      queue: Mutex::new(VecDeque::new()),
4600      blocking_queue: Mutex::new(BlockingQueue {
4601        closed: false,
4602        head: None,
4603        tail: None,
4604        jobs: FxHashMap::default(),
4605      }),
4606      active_drainers: AtomicUsize::new(0),
4607      active_blocking: AtomicUsize::new(0),
4608      scheduler_idle_lock: Mutex::new(()),
4609      scheduler_idle: Condvar::new(),
4610      worker_lifecycle,
4611      parked_drivers: ParkedDrivers::default(),
4612      active_blocking_owners: ActiveBlockingOwners::default(),
4613      #[cfg(test)]
4614      owner_lending_attempts: AtomicUsize::new(0),
4615      max_drainers: pool_threads,
4616      max_blocking: options.max_blocking_tasks,
4617      park_deadline: options.park_deadline,
4618      deadlock_state,
4619      timers,
4620      metrics,
4621    })
4622  }
4623
4624  fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
4625    self.deadlock_state.begin_admission()
4626  }
4627
4628  fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
4629    self.deadlock_state.try_begin_verdict()
4630  }
4631
4632  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
4633    // See the implementation notes in this crate's README.
4634    let _publication = self.deadlock_state.owner_handoff_publication_guard();
4635    self.parked_drivers.wake_one_owner_dependency(owner)
4636  }
4637
4638  fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
4639    let owner = reservation.owner;
4640    // Keep release and its targeted handoff in one detector-visible critical
4641    // section. Unwind drops the mutex after the lane is already available, so
4642    // a later verdict rechecks that availability even if selection panicked.
4643    let _publication = self.deadlock_state.owner_handoff_publication_guard();
4644    drop(reservation);
4645    self.parked_drivers.wake_one_owner_dependency(owner)
4646  }
4647
4648  fn schedule(self: &Arc<Self>, runnable: Runnable) {
4649    let mut admission = self.begin_deadlock_admission();
4650    // Fires for BOTH the slot and the FIFO path: a slot-resident runnable is
4651    // still "queued" until popped (`runnable_started` balances it either way).
4652    self.metrics.runnable_scheduled();
4653    // LIFO fast path: a runnable scheduled from a pool
4654    // worker OF THIS EXECUTOR goes into that worker's slot instead of the
4655    // shared FIFO -- this thread pops it before its next FIFO look, so no
4656    // queue mutex, no wake, no drainer spawn.
4657    //
4658    // "Pops it before its next FIFO look" is only true while RUNNABLE code is
4659    // on the stack (drain/run_one loop iterations, cooperative polls). It is
4660    // FALSE inside a blocking closure: `ON_POOL_WORKER` spans the whole drain
4661    // frame including `blocking()` calls, but the slot is only popped after
4662    // the closure returns -- so a closure that spawns and then synchronously
4663    // waits on the child would deadlock with the child slotted (no wake, no
4664    // drainer, even with idle workers). `BLOCKING_OWNER` is `Some` exactly
4665    // while straight-line blocking-closure code runs (drain/run_one blocking
4666    // branches, the over-cap escape) and `None` while runnables run (the
4667    // slot/FIFO owner-clear guards), so it is the discriminator: bypass the
4668    // slot and take the FIFO+wake path whenever an owner frame is ambient.
4669    // (A cooperative poll entered FROM a blocking closure also carries the
4670    // closure's token and loses the fast path -- a rare, conservative,
4671    // correctness-first trade.)
4672    if IN_SCHEDULER_DRIVER
4673      .with(std::cell::Cell::get)
4674      .is_some_and(|role| role.executor_id == self.id)
4675      && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
4676    {
4677      match LIFO_SLOT.with(std::cell::Cell::take) {
4678        None => {
4679          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
4680          if let Some(admission) = admission.as_mut() {
4681            admission.mark_published();
4682          }
4683          return;
4684        }
4685        // Same-executor occupant: the NEWEST runnable takes the slot (it is
4686        // the hottest); the displaced occupant falls back to the shared FIFO
4687        // with a normal wake so other workers can pick it up.
4688        Some((id, displaced)) if id == self.id => {
4689          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
4690          self.push_to_queue_and_wake(displaced);
4691          if let Some(admission) = admission.as_mut() {
4692            admission.mark_published();
4693          }
4694          return;
4695        }
4696        // Foreign-executor occupant (defensive: requires a thread acting as a
4697        // worker of two executors, e.g. under a test-installed marker; the
4698        // production process has one global executor). We must not flush
4699        // another executor's runnable onto OUR queue, so leave it in place --
4700        // its own executor's drain on this thread pops or flushes it -- and
4701        // route the new runnable to the FIFO.
4702        Some(foreign) => {
4703          LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
4704        }
4705      }
4706    }
4707    self.push_to_queue_and_wake(runnable);
4708    if let Some(admission) = admission.as_mut() {
4709      admission.mark_published();
4710    }
4711  }
4712
4713  /// Shared-FIFO tail of [`Self::schedule`]: push, then wake (also used for
4714  /// displaced/flushed slot runnables, whose `runnable_scheduled` accounting
4715  /// already happened at their original schedule).
4716  fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
4717    self
4718      .queue
4719      .lock()
4720      .unwrap_or_else(std::sync::PoisonError::into_inner)
4721      .push_back(runnable);
4722    self.wake_for_new_work();
4723  }
4724
4725  /// Queue-wake tail shared by `schedule` and `schedule_blocking`: wake ONE
4726  /// parked cooperative driver; when none is parked, fall back to spawning a
4727  /// drainer. A single wake suffices because the woken driver re-checks
4728  /// every work source in its loop before re-parking, and a wake it absorbs
4729  /// without consuming is handed on at `cooperative_block_on` exit (miss
4730  /// compensation). MUST be called AFTER the work was pushed
4731  /// under its queue mutex -- see the lost-wakeup argument at
4732  /// [`ParkedDrivers::wake_one`].
4733  fn wake_for_new_work(self: &Arc<Self>) {
4734    if !self.parked_drivers.wake_one() {
4735      self.ensure_drainer();
4736    }
4737  }
4738
4739  fn wake_for_blocking_work(self: &Arc<Self>) {
4740    // Prefer a cooperative driver that can consume the blocking FIFO;
4741    // otherwise arm a normal Rayon drainer.
4742    if !self.parked_drivers.wake_one_blocking() {
4743      self.ensure_drainer();
4744    }
4745  }
4746
4747  #[cfg(test)]
4748  fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
4749  where
4750    F: FnOnce() -> T + Send + 'static,
4751    T: Send + 'static,
4752  {
4753    self.schedule_blocking_result(move || catch_unwind_join_error(function))
4754  }
4755
4756  fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
4757  where
4758    F: FnOnce() -> Result<T, JoinError> + Send + 'static,
4759    T: Send + 'static,
4760  {
4761    let mut admission = self.begin_deadlock_admission();
4762    let (sender, receiver) = oneshot::channel();
4763    let generation = self.generation;
4764    let dependency = BlockingDependency {
4765      job: BlockingJobId(next_unique_id(
4766        &NEXT_BLOCKING_JOB_ID,
4767        "blocking job id space exhausted",
4768      )),
4769      owner: BLOCKING_OWNER
4770        .with(std::cell::Cell::get)
4771        .filter(|owner| owner.executor_id == self.id),
4772    };
4773    // Enqueue-time progress signal, BEFORE the queue push/wake: the deadlock
4774    // detector's fingerprint must be able to observe
4775    // this submission even while the job merely sits queued -- symmetric with
4776    // `runnable_scheduled` on the runnable path.
4777    self.metrics.blocking_scheduled();
4778    let queued = QueuedBlocking {
4779      id: dependency.job,
4780      run: Box::new(move || {
4781        let result = function().map(|output| ContainedTaskOutput::new(output, generation));
4782        let _ = sender.send(result);
4783      }),
4784    };
4785    let rejected = {
4786      let mut queue = self
4787        .blocking_queue
4788        .lock()
4789        .unwrap_or_else(std::sync::PoisonError::into_inner);
4790      if queue.closed {
4791        Some(queued)
4792      } else {
4793        queue.push(queued);
4794        None
4795      }
4796    };
4797    if let Some(rejected) = rejected {
4798      // Shutdown may close the queue after the controller registered this
4799      // generation work but before it reaches the executor. Drop captured
4800      // user values outside the queue lock and contain hostile destructors,
4801      // matching the queued-job shutdown path.
4802      let _generation = RuntimeGenerationGuard::enter(self.generation);
4803      let _ = catch_unwind_contained(|| drop(rejected));
4804    } else {
4805      // Wake a blocking-capable cooperative driver, or arm a normal drainer.
4806      self.wake_for_blocking_work();
4807      if let Some(admission) = admission.as_mut() {
4808        admission.mark_published();
4809      }
4810    }
4811    JoinHandle(JoinHandleInner::Blocking {
4812      receiver,
4813      dependency,
4814    })
4815  }
4816
4817  fn ensure_drainer(self: &Arc<Self>) {
4818    loop {
4819      let active = self.active_drainers.load(Ordering::Acquire);
4820      if active >= self.max_drainers {
4821        return;
4822      }
4823      if self
4824        .active_drainers
4825        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
4826        .is_ok()
4827      {
4828        let executor = Arc::clone(self);
4829        self.pool.spawn_fifo(move || executor.drain());
4830        return;
4831      }
4832    }
4833  }
4834
4835  fn drain(self: Arc<Self>) {
4836    // Mark this worker so a re-entrant `block_on` (reached from a polled task)
4837    // drives the queue cooperatively instead of parking the worker.
4838    let _on_pool = OnPoolWorkerGuard::enter(self.id);
4839    // Unwind backstop: never leave a runnable stranded in
4840    // the LIFO slot. Normal exits flush explicitly BEFORE `finish_draining`
4841    // below (so its re-check can see the flushed runnable); this guard only
4842    // covers unwinds, which should be impossible (runnables and blocking
4843    // wrappers are catch_unwind'd) but must not lose a task if they happen.
4844    let _slot_backstop = LifoSlotFlushGuard(&self);
4845
4846    let mut runnable_streak = 0usize;
4847    for _ in 0..Self::RUNNABLE_BUDGET {
4848      if self.run_one_fair(&mut runnable_streak) {
4849        continue;
4850      }
4851      // MANDATORY flush before finish_draining. On this path
4852      // the slot was observed empty at the top of the iteration and no user
4853      // code ran since, so the flush is defensive -- kept unconditional so
4854      // every drain exit upholds the stranding invariant by construction.
4855      self.flush_lifo_slot();
4856      self.finish_draining();
4857      return;
4858    }
4859
4860    // Budget exhausted: the last runnable may have scheduled into the slot.
4861    // MANDATORY flush before `finish_draining` so its
4862    // re-check sees the runnable on the shared FIFO and re-arms a drainer.
4863    self.flush_lifo_slot();
4864    self.finish_draining();
4865  }
4866
4867  /// Pop this worker's LIFO slot if it holds a runnable of THIS executor.
4868  /// A foreign executor's entry is left untouched (see the scoping note at
4869  /// `LIFO_SLOT`).
4870  fn pop_lifo_slot(&self) -> Option<Runnable> {
4871    LIFO_SLOT.with(|slot| match slot.take() {
4872      Some((id, runnable)) if id == self.id => Some(runnable),
4873      other => {
4874        slot.set(other);
4875        None
4876      }
4877    })
4878  }
4879
4880  /// Move this worker's slot entry (same-executor only) to the shared FIFO,
4881  /// with a wake so any worker can pick it up. MANDATORY on every path where
4882  /// this thread stops draining this executor: `drain`'s
4883  /// returns (BEFORE `finish_draining`), before a cooperative park, and on
4884  /// unwind via `LifoSlotFlushGuard` -- a runnable stranded in a thread-local
4885  /// slot while its thread leaves executor code is a silently lost task.
4886  fn flush_lifo_slot(self: &Arc<Self>) {
4887    let mut admission = self.begin_deadlock_admission();
4888    if let Some(runnable) = self.pop_lifo_slot() {
4889      self.push_to_queue_and_wake(runnable);
4890      if let Some(admission) = admission.as_mut() {
4891        admission.mark_published();
4892      }
4893    }
4894  }
4895
4896  /// Mint a fresh owner token for a counted blocking closure entering on this
4897  /// executor. Each entry gets a unique `frame` so nested owners on the same
4898  /// thread can never run each other's queued jobs over the cap.
4899  fn fresh_owner_token(&self) -> BlockingOwnerToken {
4900    BlockingOwnerToken {
4901      executor_id: self.id,
4902      frame: next_unique_id(
4903        &NEXT_BLOCKING_FRAME,
4904        "blocking owner frame id space exhausted",
4905      ),
4906    }
4907  }
4908
4909  fn finish_draining(self: &Arc<Self>) {
4910    // LIFO-slot invariant: this re-check reads only the
4911    // SHARED queues -- per-worker slots are invisible to it, and that is
4912    // sound because slot work never needs a foreign observer. A slot entry is
4913    // always either popped and run by its owning thread (drain / run_one) or
4914    // flushed to the shared FIFO by that same thread BEFORE it stops draining
4915    // (both `drain` returns flush ahead of this call; `LifoSlotFlushGuard`
4916    // covers unwinds; cooperative parks flush defensively). No thread leaves
4917    // executor code with its slot occupied, so the exit-then-respawn window
4918    // below cannot hide slot work from `ensure_drainer`.
4919    self.active_drainers.fetch_sub(1, Ordering::AcqRel);
4920    let has_runnable = !self
4921      .queue
4922      .lock()
4923      .unwrap_or_else(std::sync::PoisonError::into_inner)
4924      .is_empty();
4925    let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
4926      && !self
4927        .blocking_queue
4928        .lock()
4929        .unwrap_or_else(std::sync::PoisonError::into_inner)
4930        .is_empty();
4931    if has_runnable || has_blocking {
4932      self.ensure_drainer();
4933    }
4934    let _idle = self
4935      .scheduler_idle_lock
4936      .lock()
4937      .unwrap_or_else(std::sync::PoisonError::into_inner);
4938    self.scheduler_idle.notify_all();
4939  }
4940
4941  fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4942    if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
4943      // Re-entrant call from a pool worker OF THIS EXECUTOR: parking here would
4944      // freeze a worker that the awaited future may itself need to make
4945      // progress. Drive the queue cooperatively instead (mirrors the
4946      // CurrentThread drive loop). The id check mirrors the
4947      // `BlockingOwnerToken.executor_id` gate: a worker of a DIFFERENT executor
4948      // owns none of this executor's accounting, so it must take the parking
4949      // path below rather than drive these queues as an unaccounted driver.
4950      self.cooperative_block_on(future)
4951    } else {
4952      // Non-pool (e.g. napi caller) thread, or a pool worker of another
4953      // executor: park without driving queues. The runtime owns this park so
4954      // generation shutdown can wake and cancel it.
4955      self.parking_block_on(future)
4956    }
4957  }
4958
4959  fn parking_block_on(
4960    self: &Arc<Self>,
4961    mut future: Pin<&mut dyn Future<Output = ()>>,
4962  ) -> BlockOnOutcome {
4963    let parker = Arc::new(DriverParker::default());
4964    let _stop_registration = self.stop.register(&parker);
4965    let waker = Waker::from(Arc::clone(&parker));
4966    let mut cx = Context::from_waker(&waker);
4967    loop {
4968      if self.stop.is_stopping() {
4969        return BlockOnOutcome::Stopped;
4970      }
4971      if future.as_mut().poll(&mut cx).is_ready() {
4972        return BlockOnOutcome::Completed;
4973      }
4974      if self.stop.is_stopping() {
4975        return BlockOnOutcome::Stopped;
4976      }
4977      parker.park();
4978    }
4979  }
4980
4981  fn run_runnable(
4982    self: &Arc<Self>,
4983    runnable: Runnable,
4984    admission: Option<MultiThreadDeadlockAdmissionGuard>,
4985  ) {
4986    // Ownership is lexical to a blocking closure. A runnable driven from that
4987    // closure must not borrow its over-cap privilege.
4988    let _non_owner = BlockingOwnerGuard::enter(None);
4989    let _active = self.metrics.runnable_started();
4990    #[cfg(test)]
4991    run_multi_thread_work_started_test_hook();
4992    // The queue/slot removal is invisible to the deadline verdict until start
4993    // accounting advances. Keep the detector gate open across exactly that gap,
4994    // but never across a user poll.
4995    drop(admission);
4996    let _ = catch_unwind_contained(|| runnable.run());
4997  }
4998
4999  fn cancel_runnable(
5000    &self,
5001    runnable: Runnable,
5002    admission: Option<MultiThreadDeadlockAdmissionGuard>,
5003  ) {
5004    self.metrics.runnable_cancelled();
5005    // Cancellation accounting closes the dequeue gap just as start accounting
5006    // does for a runnable that will be polled.
5007    drop(admission);
5008    let _generation = RuntimeGenerationGuard::enter(self.generation);
5009    let _ = catch_unwind_contained(|| drop(runnable));
5010  }
5011
5012  fn finish_runnable_claim(
5013    self: &Arc<Self>,
5014    claim: RunnableClaim,
5015    admission: Option<MultiThreadDeadlockAdmissionGuard>,
5016  ) -> bool {
5017    match claim {
5018      RunnableClaim::Empty => {
5019        drop(admission);
5020        false
5021      }
5022      RunnableClaim::Run(runnable) => {
5023        #[cfg(test)]
5024        run_multi_thread_work_claimed_test_hook();
5025        self.run_runnable(runnable, admission);
5026        true
5027      }
5028      RunnableClaim::Cancel(runnable) => {
5029        #[cfg(test)]
5030        run_multi_thread_work_claimed_test_hook();
5031        self.cancel_runnable(runnable, admission);
5032        true
5033      }
5034    }
5035  }
5036
5037  fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
5038    match runnable {
5039      Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
5040      Some(runnable) => RunnableClaim::Run(runnable),
5041      None => RunnableClaim::Empty,
5042    }
5043  }
5044
5045  fn claim_fifo_runnable(&self) -> RunnableClaim {
5046    let stop_publication = self.stop.runnable_claim_guard();
5047    let runnable = self
5048      .queue
5049      .lock()
5050      .unwrap_or_else(std::sync::PoisonError::into_inner)
5051      .pop_front();
5052    let stopping = stop_publication.is_stopping();
5053    drop(stop_publication);
5054    Self::runnable_claim(stopping, runnable)
5055  }
5056
5057  fn claim_runnable(&self) -> RunnableClaim {
5058    let stop_publication = self.stop.runnable_claim_guard();
5059    let runnable = self.pop_lifo_slot().or_else(|| {
5060      self
5061        .queue
5062        .lock()
5063        .unwrap_or_else(std::sync::PoisonError::into_inner)
5064        .pop_front()
5065    });
5066    let stopping = stop_publication.is_stopping();
5067    drop(stop_publication);
5068    Self::runnable_claim(stopping, runnable)
5069  }
5070
5071  fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
5072    let admission = self.begin_deadlock_admission();
5073    let claim = self.claim_fifo_runnable();
5074    self.finish_runnable_claim(claim, admission)
5075  }
5076
5077  /// Run one runnable, preferring this worker's LIFO slot over the shared FIFO.
5078  fn run_one_runnable(self: &Arc<Self>) -> bool {
5079    let admission = self.begin_deadlock_admission();
5080    // Same owner-clear as the FIFO-only path: a runnable from the slot must
5081    // not smuggle an owner frame's over-cap privilege.
5082    let claim = self.claim_runnable();
5083    self.finish_runnable_claim(claim, admission)
5084  }
5085
5086  fn run_one_blocking(self: &Arc<Self>) -> bool {
5087    if let Some((blocking, admission)) = self.take_blocking() {
5088      let _generation = RuntimeGenerationGuard::enter(self.generation);
5089      {
5090        let _task = self.metrics.blocking_started(true);
5091        #[cfg(test)]
5092        run_multi_thread_work_started_test_hook();
5093        // Publish start before the dequeue admission retires. The detector can
5094        // then observe either the queued job, the active admission, or progress.
5095        drop(admission);
5096        let owner = self.fresh_owner_token();
5097        let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
5098        let _owner = BlockingOwnerGuard::enter(Some(owner));
5099        // The queued wrapper contains panics from the user function, but
5100        // delivering its result can still drop user-owned state when the
5101        // receiver is gone. Contain that destructor boundary as well so an
5102        // unwind cannot bypass blocking-slot and drainer retirement.
5103        let _ = catch_unwind_contained(blocking);
5104      }
5105      self.active_blocking.fetch_sub(1, Ordering::Release);
5106      return true;
5107    }
5108    false
5109  }
5110
5111  /// Run one fair scheduler unit. Runnable locality remains the normal
5112  /// priority, but a continuously hot runnable stream must yield one quantum
5113  /// to the blocking FIFO.
5114  fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
5115    self.run_one_fair_with_exact(runnable_streak, None)
5116  }
5117
5118  /// Cooperative variant of [`Self::run_one_fair`]. Once runnable priority has
5119  /// been satisfied, a live exact dependency must claim its owner's lane before
5120  /// unrelated FIFO blocking work can consume the last ordinary slot.
5121  fn run_one_fair_with_exact(
5122    self: &Arc<Self>,
5123    runnable_streak: &mut usize,
5124    exact_dependency: Option<&TaskDependency>,
5125  ) -> bool {
5126    let mut exact_attempted = false;
5127    if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
5128      *runnable_streak = 0;
5129      if let Some(dependency) = exact_dependency {
5130        exact_attempted = true;
5131        if self.try_owned_blocking_over_cap(dependency) {
5132          return true;
5133        }
5134      }
5135      if self.run_one_blocking() {
5136        return true;
5137      }
5138    }
5139    if self.run_one_runnable() {
5140      *runnable_streak += 1;
5141      return true;
5142    }
5143    if !exact_attempted
5144      && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
5145    {
5146      *runnable_streak = 0;
5147      return true;
5148    }
5149    if self.run_one_blocking() {
5150      *runnable_streak = 0;
5151      return true;
5152    }
5153    false
5154  }
5155
5156  #[cfg(test)]
5157  fn run_one(self: &Arc<Self>) -> bool {
5158    let mut runnable_streak = 0;
5159    self.run_one_fair(&mut runnable_streak)
5160  }
5161
5162  /// Exact-dependency fallback for a re-entrant `block_on` that would otherwise
5163  /// park while the blocking cap is saturated. The dependency carries its
5164  /// exact owner frame through JoinHandle lineage. Reserving that frame
5165  /// transfers its counted lane to this otherwise-idle cooperative driver.
5166  ///
5167  /// Owner lending is lexical: the cooperative driver must carry the exact
5168  /// blocking frame whose counted lane it reuses. Rayon does not propagate
5169  /// thread-local ancestry to stolen jobs, and registry cardinality cannot
5170  /// prove that an untagged worker belongs to the sole active owner.
5171  fn run_owned_blocking_over_cap(
5172    self: &Arc<Self>,
5173    dependency: &TaskDependency,
5174  ) -> OwnedBlockingOutcome {
5175    #[cfg(test)]
5176    self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
5177
5178    let Some(ambient_owner) = BLOCKING_OWNER
5179      .with(std::cell::Cell::get)
5180      .filter(|owner| owner.executor_id == self.id)
5181    else {
5182      return OwnedBlockingOutcome::Ineligible;
5183    };
5184    let Some(current) = dependency.select_for_owner(ambient_owner) else {
5185      return OwnedBlockingOutcome::Ineligible;
5186    };
5187    let admission = self.begin_deadlock_admission();
5188    let owner = current
5189      .publication
5190      .dependency
5191      .owner
5192      .expect("selected dependency must carry its owner");
5193    let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
5194    else {
5195      return OwnedBlockingOutcome::OwnerBusy(owner);
5196    };
5197    #[cfg(test)]
5198    run_before_owned_blocking_queue_claim_test_hook();
5199    let job = {
5200      let mut queue = self
5201        .blocking_queue
5202        .lock()
5203        .unwrap_or_else(std::sync::PoisonError::into_inner);
5204      // Shutdown publishes stop before waiting on this queue mutex. Recheck both
5205      // admission states before consuming the dependency so a shutdown that
5206      // reached this claim first leaves the job queued for cancellation.
5207      if queue.closed || self.stop.is_stopping() {
5208        None
5209      // Claim while holding the queue lock. A concurrent clear/switch that
5210      // wins first makes this fail; once the claim succeeds, no stale snapshot
5211      // can authorize removal of the old job.
5212      } else if dependency.try_claim(&current) {
5213        queue.remove(current.publication.dependency.job)
5214      } else {
5215        None
5216      }
5217    };
5218    let ran = if let Some(job) = job {
5219      #[cfg(test)]
5220      run_multi_thread_work_claimed_test_hook();
5221      let _generation = RuntimeGenerationGuard::enter(self.generation);
5222      {
5223        let _task = self.metrics.blocking_started(false);
5224        #[cfg(test)]
5225        run_multi_thread_work_started_test_hook();
5226        // The exact job is no longer visible in the indexed FIFO. Publish its
5227        // start before allowing a deadline verdict to close the admission gate.
5228        drop(admission);
5229        let nested_owner = self.fresh_owner_token();
5230        let _active_owner =
5231          ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
5232        let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
5233        let _ = catch_unwind_contained(job);
5234      }
5235      true
5236    } else {
5237      drop(admission);
5238      false
5239    };
5240    // Every reservation release can make another exact dependency serviceable,
5241    // including failed-claim and already-removed-job exits.
5242    self.release_owner_lane_and_wake(reservation);
5243    OwnedBlockingOutcome::ReservationReleased { owner, ran }
5244  }
5245
5246  fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
5247    matches!(
5248      self.run_owned_blocking_over_cap(dependency),
5249      OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
5250    )
5251  }
5252
5253  fn service_owner_handoff(
5254    self: &Arc<Self>,
5255    parker: &DriverParker,
5256    dependency: &TaskDependency,
5257    can_run_blocking: bool,
5258  ) -> Option<bool> {
5259    let handoff_owner = parker.take_owner_handoff()?;
5260    if !can_run_blocking {
5261      self.wake_one_owner_dependency(handoff_owner);
5262      return Some(false);
5263    }
5264    match self.run_owned_blocking_over_cap(dependency) {
5265      OwnedBlockingOutcome::Ineligible => {
5266        self.wake_one_owner_dependency(handoff_owner);
5267        Some(false)
5268      }
5269      OwnedBlockingOutcome::OwnerBusy(owner) => {
5270        if owner != handoff_owner {
5271          self.wake_one_owner_dependency(handoff_owner);
5272        }
5273        Some(false)
5274      }
5275      OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
5276        if owner != handoff_owner {
5277          self.wake_one_owner_dependency(handoff_owner);
5278        }
5279        Some(ran)
5280      }
5281    }
5282  }
5283
5284  fn forward_owner_handoff(&self, parker: &DriverParker) {
5285    if let Some(owner) = parker.take_owner_handoff() {
5286      self.wake_one_owner_dependency(owner);
5287    }
5288  }
5289
5290  fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
5291    let Some(ambient_owner) = BLOCKING_OWNER
5292      .with(std::cell::Cell::get)
5293      .filter(|owner| owner.executor_id == self.id)
5294    else {
5295      return false;
5296    };
5297    self.active_blocking_owners.is_available(ambient_owner)
5298      && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
5299  }
5300
5301  fn has_exact_owner_work_for_role(
5302    &self,
5303    parker: &DriverParker,
5304    dependency: &TaskDependency,
5305    can_run_blocking: bool,
5306  ) -> bool {
5307    parker.has_owner_handoff()
5308      || (can_run_blocking && self.owner_dependency_lane_available(dependency))
5309  }
5310
5311  /// Cooperative `block_on` for a re-entrant call from a pool worker. Instead
5312  /// of parking the worker (which would freeze one of the pool's fixed OS
5313  /// threads), it drives the awaited future while servicing queued work. When
5314  /// nothing is immediately runnable it parks on its own [`DriverParker`],
5315  /// which is woken directly by the awaited future's waker and through the
5316  /// `parked_drivers` registry by `schedule`/`schedule_blocking`, so a later
5317  /// wake reaches this worker even when every worker is parked and
5318  /// `ensure_drainer` has no free thread to spawn a replacement.
5319  fn cooperative_block_on(
5320    self: &Arc<Self>,
5321    mut future: Pin<&mut dyn Future<Output = ()>>,
5322  ) -> BlockOnOutcome {
5323    // One parker per `block_on` frame. Nested re-entrant frames each own one:
5324    // only the innermost frame runs or parks at any moment, and an outer
5325    // frame's future-wake is stored as its parker's permit, consumed when
5326    // control returns to that frame's loop.
5327    let parker = Arc::new(DriverParker::default());
5328    let _stop_registration = self.stop.register(&parker);
5329    let dependency = BlockOnDependencyGuard::enter(self.generation);
5330    let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
5331    let can_run_blocking =
5332      driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
5333    let _exit = CooperativeDriverExitGuard {
5334      executor: self,
5335      parker: &parker,
5336      can_run_blocking,
5337    };
5338    // WAKE-DOMAIN SPLIT: the awaited future is polled with
5339    // THIS driver's own parker as its waker, so a future-wake targets exactly
5340    // the driver that awaits it -- delivered as a stored permit (skip next
5341    // park) when this driver is currently running, so it is never lost.
5342    let waker = std::task::Waker::from(Arc::clone(&parker));
5343    let mut cx = Context::from_waker(&waker);
5344    // Consecutive `run_one` successes since the last yield point: the
5345    // cooperative loop mirrors `drain`'s RUNNABLE_BUDGET streak cap. Without
5346    // it, a hot slot chain (each runnable schedules a successor into this
5347    // worker's slot, which `run_one` pops BEFORE the FIFO) would keep this
5348    // loop inside `run_one` forever and the shared FIFO would never be
5349    // reached from this thread -- a deadlock on a 1-worker pool when the
5350    // awaited future depends on a FIFO task.
5351    let mut runnable_streak = 0usize;
5352    let mut work_streak = 0usize;
5353    let mut force_fifo = false;
5354    loop {
5355      if self.stop.is_stopping() {
5356        return BlockOnOutcome::Stopped;
5357      }
5358      dependency.clear();
5359      if future.as_mut().poll(&mut cx).is_ready() {
5360        return BlockOnOutcome::Completed;
5361      }
5362      if self.stop.is_stopping() {
5363        return BlockOnOutcome::Stopped;
5364      }
5365      if self
5366        .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
5367        .is_some_and(|ran| ran)
5368      {
5369        continue;
5370      }
5371      if force_fifo {
5372        force_fifo = false;
5373        if self.run_one_fifo_runnable() {
5374          runnable_streak += 1;
5375          work_streak += 1;
5376          continue;
5377        }
5378      }
5379      let ran_work = if can_run_blocking {
5380        self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
5381      } else if self.run_one_runnable() {
5382        runnable_streak += 1;
5383        true
5384      } else {
5385        false
5386      };
5387      if ran_work {
5388        work_streak += 1;
5389        if work_streak >= Self::RUNNABLE_BUDGET {
5390          // Budget yield: move a hot slot occupant to the shared FIFO (with
5391          // a wake), so the next `run_one` pops the FIFO front (the oldest
5392          // work) and other workers can steal the chain. The awaited future
5393          // is polled every iteration regardless, so it is never starved.
5394          self.flush_lifo_slot();
5395          work_streak = 0;
5396          force_fifo = true;
5397        }
5398        continue;
5399      }
5400      runnable_streak = 0;
5401      work_streak = 0;
5402      // MANDATORY pre-park flush. Defensive on every
5403      // currently reachable path: `run_one` above popped the slot and no user
5404      // code ran since -- but parking with a stranded slot runnable would
5405      // silently lose that task, so the invariant is upheld unconditionally.
5406      self.flush_lifo_slot();
5407      // Park protocol: register FIRST, then re-check, then park -- the
5408      // waiter-side mirror of the push-then-wake schedule side. See the
5409      // lost-wakeup argument at [`ParkedDrivers::wake_one`].
5410      self.parked_drivers.register_with_role(
5411        &parker,
5412        can_run_blocking,
5413        Some(Arc::clone(&dependency.dependency)),
5414      );
5415      if self.has_queued_work_for_role(can_run_blocking)
5416        || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
5417      {
5418        self.parked_drivers.deregister(&parker);
5419        continue;
5420      }
5421      if self.stop.is_stopping() {
5422        return BlockOnOutcome::Stopped;
5423      }
5424      // TIMER-BOUNDED PARK: while any heap
5425      // timer is pending, this park is a TIMER WAIT, not a deadlock
5426      // candidate -- the wall clock is a guaranteed wake source -- so it uses
5427      // its own bounded primitive instead of `park`/`park_with_deadline` and
5428      // fires whatever came due on the way out. An armed park deadline is
5429      // deliberately NOT evaluated during a timer wait: the fire schedules
5430      // the woken task (fingerprint progress), and the next timerless park
5431      // arms a fresh full window, so genuine-deadlock detection is delayed
5432      // past the timer, never lost. This is also load-bearing for liveness:
5433      // on a 1-worker pool whose only thread is parked HERE, the timekeeper
5434      // job has no free thread to run on, so this driver must fire due
5435      // timers itself. (A timer registered between the deadline read and the
5436      // park re-arms this parker through `note_earlier_timer`'s
5437      // `wake_one` -- we are registered -- or wakes the timekeeper.)
5438      if let Some(next) = self.next_timer_deadline() {
5439        let now = Instant::now();
5440        if next > now {
5441          parker.park_timeout(next - now);
5442        }
5443        self.parked_drivers.deregister(&parker);
5444        self.fire_due_timers();
5445        continue;
5446      }
5447      match self.park_deadline {
5448        None => parker.park(),
5449        Some(deadline) => {
5450          // Deadline-bounded cooperative park with PROGRESS-BASED reset: a
5451          // candidate deadlock only when a
5452          // FULL deadline window passes with zero executor progress. The
5453          // foreign-thread whole-build park in `block_on`'s else-branch is
5454          // exempt by design.
5455          let deadlock_publications = self.deadlock_state.publications();
5456          match park_with_deadline(&parker, deadline, &self.metrics) {
5457            DeadlineParkOutcome::Woken => {}
5458            DeadlineParkOutcome::Expired(armed) => {
5459              #[cfg(test)]
5460              run_deadline_expiry_test_hook();
5461              // EXPIRY DECISION. While this parker was
5462              // registered, a racing `wake_one` may have popped it, stored
5463              // its permit and reported the wake as DELIVERED (so the
5464              // scheduler spawned no drainer) -- panicking now would swallow
5465              // a healthy wake and kill a live build. Deregister FIRST so no
5466              // FURTHER wake can be counted against this parker, then
5467              // re-check every way a wake could have landed around the
5468              // expiry:
5469              //   * permit: a `wake_one` (or direct future-wake) that landed
5470              //     while still registered MUST be acted on;
5471              //   * queued work: the work behind such a wake was pushed
5472              //     under the queue mutex BEFORE `wake_one` popped us, so
5473              //     even if its `unpark` is still in flight the queue
5474              //     re-check observes it;
5475              //   * pending timers: arming a timer bumps NO progress counter
5476              //     by design, so a registration racing this already-
5477              //     committed park (it read an empty heap before parking) is
5478              //     invisible to every other re-check -- yet its wake (the
5479              //     fire) is wall-clock-guaranteed, so this park is healthy.
5480              //     The loop then re-parks TIMER-BOUNDED (see above) and
5481              //     fires the timer itself if needed;
5482              //   * fingerprint: the verdict requires ZERO progress, and the
5483              //     read inside `park_with_deadline` is stale by the time we
5484              //     get here -- re-read as the LAST step. Both ENQUEUE
5485              //     counters are in the fingerprint (`runnable_schedules`
5486              //     and `blocking_tasks_scheduled`, both bumped before the
5487              //     push), so a submission of EITHER kind
5488              //     landing after the queue re-check above is still caught
5489              //     here.
5490              // Any hit => not a deadlock: continue the cooperative loop
5491              // (fresh future poll, `run_one` picks up the work, and the
5492              // next park arms a fresh FULL deadline window). The residue is
5493              // exactly a BARE direct future-wake -- no queue push, no
5494              // counter movement -- landing in the few instructions after
5495              // these re-checks: the inherent, unclosable residue of any
5496              // timing-based detector, one reason the deadline is opt-in.
5497              self.parked_drivers.deregister(&parker);
5498              if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
5499                continue;
5500              }
5501              // Test-only seam between the permit/queued-work re-checks and
5502              // the fingerprint verdict.
5503              #[cfg(test)]
5504              run_deadline_verdict_test_hook();
5505              if self.metrics.progress_fingerprint() != armed {
5506                continue;
5507              }
5508              // Pending-timer re-check LAST, after even the fingerprint:
5509              // arming a timer bumps no counter, so it is the one healthy
5510              // wake source that can land after every earlier re-check and
5511              // still be invisible to the fingerprint read above. (A
5512              // registration racing the instructions after this line is the
5513              // same inherent residue class as the bare future-wake -- and
5514              // its timer is still served: `note_earlier_timer` claims the
5515              // timekeeper independently of this driver.)
5516              if self.next_timer_deadline().is_some() {
5517                continue;
5518              }
5519              // Test-only seam for a cross-thread scheduler admission after
5520              // every legacy final check and before the synchronized verdict.
5521              #[cfg(test)]
5522              run_deadline_final_verdict_test_hook();
5523              let Some(verdict) = self.try_begin_deadlock_verdict() else {
5524                continue;
5525              };
5526              #[cfg(test)]
5527              run_deadline_gated_verdict_test_hook();
5528              // Lock order is verdict gate -> stop publication -> exact-owner
5529              // publication. The controller takes lifecycle -> stop
5530              // publication and never acquires the verdict gate or exact-owner
5531              // publication while retaining either lock.
5532              let stop_publication = self.stop.publication_guard();
5533              #[cfg(test)]
5534              run_deadline_synchronized_stop_test_hook();
5535              if stop_publication.is_stopping() {
5536                return BlockOnOutcome::Stopped;
5537              }
5538              let owner_handoff_publication = self
5539                .deadlock_state
5540                .owner_handoff_publication_guard()
5541                .expect("a deadline verdict requires exact-owner publication");
5542              #[cfg(test)]
5543              run_deadline_exact_owner_verdict_test_hook(&parker);
5544              // The closed gate excludes later publications. Any admission
5545              // that began earlier either remains counted (so gate acquisition
5546              // failed above) or completed before this recheck and advanced
5547              // the publication epoch. The stop-publication guard also keeps
5548              // the lifecycle in Running if this verdict linearized first. The
5549              // exact-owner publication guard similarly orders reservation
5550              // release and targeted handoff with the final predicates below.
5551              if self.has_exact_owner_work_for_role(
5552                &parker,
5553                &dependency.dependency,
5554                can_run_blocking,
5555              ) || parker.consume_permit()
5556                || self.has_queued_work_for_role(can_run_blocking)
5557                || self.deadlock_state.publications() != deadlock_publications
5558                || self.metrics.progress_fingerprint() != armed
5559                || self.next_timer_deadline().is_some()
5560              {
5561                continue;
5562              }
5563              let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
5564              // Panic hooks run before unwind. Release every synchronization
5565              // guard first so a hook that submits work, releases an owner
5566              // lane, or starts shutdown cannot wait for the panic that invokes
5567              // it to unwind.
5568              drop(owner_handoff_publication);
5569              drop(stop_publication);
5570              drop(verdict);
5571              std::panic::panic_any(diagnostic);
5572            }
5573          }
5574        }
5575      }
5576      // `wake_one` removes the parkers it pops; a direct future-wake does
5577      // not. Deregister-if-present covers both.
5578      self.parked_drivers.deregister(&parker);
5579    }
5580  }
5581
5582  /// Is there work a cooperative driver could pick up right now? Used by the
5583  /// pre-park re-check and the exit compensation in `cooperative_block_on`;
5584  /// mirrors `finish_draining`'s re-check (a queued runnable, or a queued
5585  /// blocking job within the cap). The exact over-cap dependency is handled
5586  /// separately before registration.
5587  fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
5588    if self.has_queued_runnable() {
5589      return true;
5590    }
5591    can_run_blocking
5592      && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5593      && !self
5594        .blocking_queue
5595        .lock()
5596        .unwrap_or_else(std::sync::PoisonError::into_inner)
5597        .is_empty()
5598  }
5599
5600  fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
5601    if self.has_queued_runnable() {
5602      self.wake_for_new_work();
5603    }
5604    // Shutdown-aborted runnables still need a driver so their generators can
5605    // retire, but queued blocking closures are cancelled by begin_shutdown and
5606    // must never be admitted by exit compensation after stop is observable.
5607    if self.stop.is_stopping() {
5608      return;
5609    }
5610    if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5611      && !self
5612        .blocking_queue
5613        .lock()
5614        .unwrap_or_else(std::sync::PoisonError::into_inner)
5615        .is_empty()
5616    {
5617      // This driver may have absorbed the queue wake that made its awaited
5618      // future ready. Runnable and blocking residue are independent. If no
5619      // other blocking-capable driver is available, merely spawning a Rayon
5620      // job is insufficient: this worker may return into arbitrary
5621      // synchronous user code while every other Rayon lane is busy.
5622      // Consume one admitted job before exit, then hand any residue to another
5623      // blocking-capable driver.
5624      let consumed = can_run_blocking && self.run_one_blocking();
5625      let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5626        && !self
5627          .blocking_queue
5628          .lock()
5629          .unwrap_or_else(std::sync::PoisonError::into_inner)
5630          .is_empty();
5631      if !self.stop.is_stopping() && (!consumed || has_residue) {
5632        self.wake_for_blocking_work();
5633      }
5634    }
5635  }
5636
5637  fn has_queued_runnable(&self) -> bool {
5638    !self
5639      .queue
5640      .lock()
5641      .unwrap_or_else(std::sync::PoisonError::into_inner)
5642      .is_empty()
5643  }
5644
5645  /// Pop the next queued blocking job FIFO within the cap. Any blocking-capable
5646  /// driver may run any job within `max_blocking`; only the over-cap escape is
5647  /// restricted to an exact dependency id.
5648  fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
5649    let admission = self.begin_deadlock_admission();
5650    loop {
5651      let active = self.active_blocking.load(Ordering::Acquire);
5652      if active >= self.max_blocking {
5653        return None;
5654      }
5655      let mut queue = self
5656        .blocking_queue
5657        .lock()
5658        .unwrap_or_else(std::sync::PoisonError::into_inner);
5659      if queue.closed || self.stop.is_stopping() || queue.is_empty() {
5660        return None;
5661      }
5662      if self
5663        .active_blocking
5664        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5665        .is_ok()
5666      {
5667        let blocking = queue
5668          .pop_front()
5669          .expect("a non-empty blocking queue must have a FIFO head");
5670        drop(queue);
5671        #[cfg(test)]
5672        run_multi_thread_work_claimed_test_hook();
5673        return Some((blocking, admission));
5674      }
5675    }
5676  }
5677}
5678
5679#[cfg(not(target_family = "wasm"))]
5680struct MultiThreadDeadlockState {
5681  enabled: bool,
5682  verdict_closed: AtomicBool,
5683  admissions: AtomicUsize,
5684  publications: AtomicU64,
5685  owner_handoff_publication: Mutex<()>,
5686}
5687
5688#[cfg(not(target_family = "wasm"))]
5689impl MultiThreadDeadlockState {
5690  fn new(enabled: bool) -> Self {
5691    Self {
5692      enabled,
5693      verdict_closed: AtomicBool::new(false),
5694      admissions: AtomicUsize::new(0),
5695      publications: AtomicU64::new(0),
5696      owner_handoff_publication: Mutex::new(()),
5697    }
5698  }
5699
5700  fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
5701    if !self.enabled {
5702      return None;
5703    }
5704    loop {
5705      while self.verdict_closed.load(Ordering::SeqCst) {
5706        #[cfg(test)]
5707        run_deadlock_closed_gate_admission_test_hook();
5708        std::hint::spin_loop();
5709      }
5710      self.admissions.fetch_add(1, Ordering::SeqCst);
5711      if !self.verdict_closed.load(Ordering::SeqCst) {
5712        return Some(MultiThreadDeadlockAdmissionGuard {
5713          state: Arc::clone(self),
5714          published: false,
5715        });
5716      }
5717      self.admissions.fetch_sub(1, Ordering::SeqCst);
5718    }
5719  }
5720
5721  fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
5722    debug_assert!(
5723      self.enabled,
5724      "a deadline verdict requires an enabled admission state"
5725    );
5726    if self
5727      .verdict_closed
5728      .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
5729      .is_err()
5730    {
5731      return None;
5732    }
5733    if self.admissions.load(Ordering::SeqCst) != 0 {
5734      self.verdict_closed.store(false, Ordering::SeqCst);
5735      return None;
5736    }
5737    Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
5738  }
5739
5740  fn publications(&self) -> u64 {
5741    self.publications.load(Ordering::SeqCst)
5742  }
5743
5744  fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
5745    self.enabled.then(|| {
5746      self
5747        .owner_handoff_publication
5748        .lock()
5749        .unwrap_or_else(std::sync::PoisonError::into_inner)
5750    })
5751  }
5752}
5753
5754#[cfg(not(target_family = "wasm"))]
5755struct MultiThreadDeadlockAdmissionGuard {
5756  state: Arc<MultiThreadDeadlockState>,
5757  published: bool,
5758}
5759
5760#[cfg(not(target_family = "wasm"))]
5761impl MultiThreadDeadlockAdmissionGuard {
5762  fn mark_published(&mut self) {
5763    self.published = true;
5764  }
5765}
5766
5767#[cfg(not(target_family = "wasm"))]
5768impl Drop for MultiThreadDeadlockAdmissionGuard {
5769  fn drop(&mut self) {
5770    if self.published {
5771      self.state.publications.fetch_add(1, Ordering::SeqCst);
5772    }
5773    // Publication and every scheduler-state write above must precede the final
5774    // admission decrement observed by a closed verdict gate.
5775    self.state.admissions.fetch_sub(1, Ordering::SeqCst);
5776  }
5777}
5778
5779#[cfg(not(target_family = "wasm"))]
5780struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
5781
5782#[cfg(not(target_family = "wasm"))]
5783impl Drop for MultiThreadDeadlockVerdictGuard {
5784  fn drop(&mut self) {
5785    self.0.verdict_closed.store(false, Ordering::SeqCst);
5786  }
5787}
5788
5789/// Cooperative-driver exit backstop. A user-future panic can unwind from any
5790/// poll, including after this driver absorbed a queue wake, received an exact
5791/// owner handoff, or filled its thread-local LIFO slot. Retire each obligation
5792/// independently so one cleanup failure cannot suppress the others or
5793/// double-panic the process.
5794#[cfg(not(target_family = "wasm"))]
5795struct CooperativeDriverExitGuard<'a> {
5796  executor: &'a Arc<MultiThreadExecutor>,
5797  parker: &'a Arc<DriverParker>,
5798  can_run_blocking: bool,
5799}
5800
5801#[cfg(not(target_family = "wasm"))]
5802impl Drop for CooperativeDriverExitGuard<'_> {
5803  fn drop(&mut self) {
5804    let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
5805    let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
5806    let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
5807    let _ = catch_unwind_contained(|| {
5808      self
5809        .executor
5810        .compensate_queued_work_wake(self.can_run_blocking);
5811    });
5812  }
5813}
5814
5815/// Unwind backstop for `drain`: flushes the worker's LIFO slot on drop so an
5816/// unwinding drain can never strand a runnable in the
5817/// thread-local slot of a thread that is about to return to rayon. Normal
5818/// drain exits flush explicitly BEFORE `finish_draining`; this drop is then a
5819/// no-op (the slot is already empty).
5820#[cfg(not(target_family = "wasm"))]
5821struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
5822
5823#[cfg(not(target_family = "wasm"))]
5824impl Drop for LifoSlotFlushGuard<'_> {
5825  fn drop(&mut self) {
5826    self.0.flush_lifo_slot();
5827  }
5828}
5829
5830// ---------------------------------------------------------------------------
5831// Targeted wake machinery for cooperative re-entrant `block_on` drivers.
5832//
5833// WAKE-DOMAIN SPLIT: future wakes and queue wakes have different targets:
5834//   * FUTURE-WAKES: `cooperative_block_on` polls its future with the driver's
5835//     OWN `DriverParker` as the waker, so a future-wake reaches exactly the
5836//     driver that awaits it. The permit bit makes a wake delivered while that
5837//     driver is RUNNING (mid-poll / mid-run_one) stick: the driver skips its
5838//     next park instead of losing the wake.
5839//   * QUEUE-WAKES (`schedule` / `schedule_blocking`): any parked driver can
5840//     serve queue work, so these go through the `ParkedDrivers` registry and
5841//     wake ONE parked driver; `ensure_drainer` remains the fallback when none
5842//     is parked, and an absorbed wake is handed on at `cooperative_block_on`
5843//     exit (miss compensation).
5844// Foreign-thread (napi caller) parks in `block_on` use
5845// `futures::executor::block_on`'s own parker and are OUTSIDE this mechanism.
5846// The CurrentThread flavor's `block_on` reuses `DriverParker` (one per
5847// frame, no registry) so it too OWNS its park site for deadlock detection
5848// while `ParkedDrivers` remains MultiThread-only.
5849
5850/// No permit; the driver is running (or consumed its last permit).
5851const PARKER_EMPTY: usize = 0;
5852/// A wake permit is stored; the next `park` consumes it without sleeping.
5853const PARKER_NOTIFIED: usize = 1;
5854/// The driver is sleeping on the condvar (or committing to sleep under `lock`).
5855const PARKER_SLEEPING: usize = 2;
5856
5857/// One driver's private parker: a saturating one-permit token plus a condvar
5858/// to sleep on when no permit is stored. Also the `Waker` for the future that
5859/// driver's `cooperative_block_on` / CurrentThread `block_on` frame awaits.
5860#[derive(Default)]
5861struct DriverParker {
5862  state: AtomicUsize,
5863  lock: Mutex<()>,
5864  condvar: std::sync::Condvar,
5865  #[cfg(not(target_family = "wasm"))]
5866  owner_handoff_pending: AtomicBool,
5867  #[cfg(not(target_family = "wasm"))]
5868  owner_handoff: Mutex<Option<BlockingOwnerToken>>,
5869}
5870
5871impl DriverParker {
5872  #[cfg(not(target_family = "wasm"))]
5873  fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
5874    let mut pending = self
5875      .owner_handoff
5876      .lock()
5877      .unwrap_or_else(std::sync::PoisonError::into_inner);
5878    debug_assert!(pending.is_none());
5879    *pending = Some(owner);
5880    self.owner_handoff_pending.store(true, Ordering::Release);
5881  }
5882
5883  #[cfg(not(target_family = "wasm"))]
5884  fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
5885    if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
5886      return None;
5887    }
5888    self
5889      .owner_handoff
5890      .lock()
5891      .unwrap_or_else(std::sync::PoisonError::into_inner)
5892      .take()
5893  }
5894
5895  #[cfg(not(target_family = "wasm"))]
5896  fn has_owner_handoff(&self) -> bool {
5897    self.owner_handoff_pending.load(Ordering::Acquire)
5898  }
5899
5900  /// Grant the wake permit. Wakes the driver if it is sleeping; otherwise the
5901  /// permit is stored and the driver's next [`Self::park`] returns
5902  /// immediately -- a wake delivered while the driver is running is never
5903  /// lost. Multiple grants coalesce into one permit, which is safe because a
5904  /// woken driver re-checks every work source in its loop before it can park
5905  /// again.
5906  fn unpark(&self) {
5907    if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
5908      // The sleeper publishes PARKER_SLEEPING while holding `lock` and
5909      // releases the lock only inside `condvar.wait`; the empty critical
5910      // section here fences the notify so it cannot fire in the window
5911      // between the sleeper's state store and its wait.
5912      drop(
5913        self
5914          .lock
5915          .lock()
5916          .unwrap_or_else(std::sync::PoisonError::into_inner),
5917      );
5918      self.condvar.notify_one();
5919    }
5920  }
5921
5922  /// Consume a stored wake permit if one is present, without ever sleeping.
5923  /// This is the park DECISION's token check: a `true` return means a wake is
5924  /// already pending, so the caller must re-poll instead of parking (or
5925  /// panicking).
5926  fn consume_permit(&self) -> bool {
5927    self
5928      .state
5929      .compare_exchange(
5930        PARKER_NOTIFIED,
5931        PARKER_EMPTY,
5932        Ordering::SeqCst,
5933        Ordering::SeqCst,
5934      )
5935      .is_ok()
5936  }
5937
5938  /// Block until the permit is granted, consuming it. Returns immediately if
5939  /// a permit is already stored. Only the owning driver calls `park`, so at
5940  /// most one thread ever sleeps here.
5941  fn park(&self) {
5942    // Fast path: consume a stored permit without touching the lock.
5943    if self.consume_permit() {
5944      return;
5945    }
5946    let mut guard = self
5947      .lock
5948      .lock()
5949      .unwrap_or_else(std::sync::PoisonError::into_inner);
5950    match self.state.compare_exchange(
5951      PARKER_EMPTY,
5952      PARKER_SLEEPING,
5953      Ordering::SeqCst,
5954      Ordering::SeqCst,
5955    ) {
5956      Ok(_) => {}
5957      // The permit arrived between the fast path and here: consume it. (Only
5958      // this thread stores PARKER_SLEEPING, so the observed value can only be
5959      // PARKER_NOTIFIED.)
5960      Err(_) => {
5961        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
5962        return;
5963      }
5964    }
5965    loop {
5966      guard = self
5967        .condvar
5968        .wait(guard)
5969        .unwrap_or_else(std::sync::PoisonError::into_inner);
5970      if self
5971        .state
5972        .compare_exchange(
5973          PARKER_NOTIFIED,
5974          PARKER_EMPTY,
5975          Ordering::SeqCst,
5976          Ordering::SeqCst,
5977        )
5978        .is_ok()
5979      {
5980        return;
5981      }
5982      // Spurious wakeup (state still PARKER_SLEEPING): keep waiting.
5983    }
5984  }
5985
5986  /// Like [`Self::park`], but bounded: returns `true` when woken by a permit
5987  /// (consumed) and `false` when `timeout` elapsed with no permit granted.
5988  /// A `false` return has retracted the SLEEPING claim and left the parker
5989  /// EMPTY, so the caller may park again. Used by the deadline-based deadlock
5990  /// detection.
5991  fn park_timeout(&self, timeout: Duration) -> bool {
5992    if self.consume_permit() {
5993      return true;
5994    }
5995    let mut guard = self
5996      .lock
5997      .lock()
5998      .unwrap_or_else(std::sync::PoisonError::into_inner);
5999    match self.state.compare_exchange(
6000      PARKER_EMPTY,
6001      PARKER_SLEEPING,
6002      Ordering::SeqCst,
6003      Ordering::SeqCst,
6004    ) {
6005      Ok(_) => {}
6006      // The permit arrived between the fast path and here: consume it. (Only
6007      // this thread stores PARKER_SLEEPING, so the observed value can only be
6008      // PARKER_NOTIFIED.)
6009      Err(_) => {
6010        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6011        return true;
6012      }
6013    }
6014    let Some(deadline) = Instant::now().checked_add(timeout) else {
6015      // An unrepresentable deadline is farther away than this platform can
6016      // observe. Treat it as an unbounded wait instead of panicking on a
6017      // user-provided deadlock-detection duration.
6018      loop {
6019        guard = self
6020          .condvar
6021          .wait(guard)
6022          .unwrap_or_else(std::sync::PoisonError::into_inner);
6023        if self
6024          .state
6025          .compare_exchange(
6026            PARKER_NOTIFIED,
6027            PARKER_EMPTY,
6028            Ordering::SeqCst,
6029            Ordering::SeqCst,
6030          )
6031          .is_ok()
6032        {
6033          return true;
6034        }
6035      }
6036    };
6037    loop {
6038      if self
6039        .state
6040        .compare_exchange(
6041          PARKER_NOTIFIED,
6042          PARKER_EMPTY,
6043          Ordering::SeqCst,
6044          Ordering::SeqCst,
6045        )
6046        .is_ok()
6047      {
6048        return true;
6049      }
6050      let now = Instant::now();
6051      if now >= deadline {
6052        // Timed out: retract the SLEEPING claim -- unless a permit landed in
6053        // the meantime (an `unpark` that already saw SLEEPING may be waiting
6054        // on `lock` to notify; consuming its permit here IS the wake it
6055        // intended to deliver, so report "woken", not "timed out").
6056        return match self.state.compare_exchange(
6057          PARKER_SLEEPING,
6058          PARKER_EMPTY,
6059          Ordering::SeqCst,
6060          Ordering::SeqCst,
6061        ) {
6062          Ok(_) => false,
6063          Err(_) => {
6064            self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6065            true
6066          }
6067        };
6068      }
6069      let (returned_guard, _timed_out) = self
6070        .condvar
6071        .wait_timeout(guard, deadline - now)
6072        .unwrap_or_else(std::sync::PoisonError::into_inner);
6073      guard = returned_guard;
6074      // Loop re-checks the permit first, then the deadline (spurious wakeups
6075      // simply wait out the remaining time).
6076    }
6077  }
6078}
6079
6080impl std::task::Wake for DriverParker {
6081  fn wake(self: Arc<Self>) {
6082    self.unpark();
6083  }
6084
6085  fn wake_by_ref(self: &Arc<Self>) {
6086    self.unpark();
6087  }
6088}
6089
6090/// Outcome of [`park_with_deadline`].
6091enum DeadlineParkOutcome {
6092  /// Woken by a permit (consumed); the park was healthy.
6093  Woken,
6094  /// A full deadline window elapsed with zero observed progress. Carries the
6095  /// fingerprint captured when that window was ARMED so the caller can make
6096  /// the final expiry DECISION against a fresh read -- expiry alone is NOT a
6097  /// verdict: a wake can land in the instructions after the window closes,
6098  /// and for MultiThread a `wake_one` can still pop the
6099  /// still-registered parker and count its wake as DELIVERED. Callers must
6100  /// deregister (MT), then re-check permit / queued work / fingerprint before
6101  /// panicking.
6102  Expired(ProgressFingerprint),
6103}
6104
6105/// Deadline-bounded park with PROGRESS-BASED reset.
6106/// Every time `deadline` elapses without a wake, the runtime's progress
6107/// fingerprint is compared against the value captured when the deadline was
6108/// ARMED: any advance re-arms the deadline instead of firing, because a
6109/// legitimately long park -- e.g. awaiting a slow JS plugin while other work
6110/// proceeds -- must never be declared dead. `Expired` is a *candidate*
6111/// deadlock only; the panic decision lives with the caller (see
6112/// [`DeadlineParkOutcome`]).
6113fn park_with_deadline(
6114  parker: &DriverParker,
6115  deadline: Duration,
6116  metrics: &RuntimeMetrics,
6117) -> DeadlineParkOutcome {
6118  let mut armed = metrics.progress_fingerprint();
6119  loop {
6120    if parker.park_timeout(deadline) {
6121      return DeadlineParkOutcome::Woken;
6122    }
6123    let current = metrics.progress_fingerprint();
6124    if current != armed {
6125      armed = current;
6126      continue;
6127    }
6128    return DeadlineParkOutcome::Expired(armed);
6129  }
6130}
6131
6132// Test-only injection seam for the deadline-expiry race: fired on the driver
6133// thread immediately after
6134// `park_with_deadline` reports `Expired`, while the parker is STILL
6135// REGISTERED in `parked_drivers` and before the expiry decision runs -- the
6136// exact window in which a racing `wake_one` can pop the parker, store its
6137// permit and report the wake as delivered. Lets a test interleave that race
6138// deterministically instead of wall-clock lottery.
6139#[cfg(all(test, not(target_family = "wasm")))]
6140thread_local! {
6141  static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6142    const { std::cell::RefCell::new(None) };
6143}
6144
6145#[cfg(all(test, not(target_family = "wasm")))]
6146fn run_deadline_expiry_test_hook() {
6147  if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6148    hook();
6149  }
6150}
6151
6152// Second test-only injection seam for the enqueue/verdict race: fired AFTER
6153// the expiry decision's permit and queued-work re-checks both came up
6154// empty (parker already deregistered) and BEFORE the final fingerprint
6155// verdict -- the window in which an enqueue is invisible to every earlier
6156// re-check and only its ENQUEUE counter in the fingerprint can prevent a
6157// false BlockOnDeadlock.
6158#[cfg(all(test, not(target_family = "wasm")))]
6159thread_local! {
6160  static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6161    const { std::cell::RefCell::new(None) };
6162}
6163
6164#[cfg(all(test, not(target_family = "wasm")))]
6165fn run_deadline_verdict_test_hook() {
6166  if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6167    hook();
6168  }
6169}
6170
6171// Final test-only injection seam: fired after every permit/queue/fingerprint/
6172// timer check from the legacy verdict and immediately before the synchronized
6173// admission gate closes. A separate OS thread can complete a publication here
6174// to prove the final verdict recheck has a real cross-thread ordering edge.
6175#[cfg(all(test, not(target_family = "wasm")))]
6176thread_local! {
6177  static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6178    const { std::cell::RefCell::new(None) };
6179}
6180
6181#[cfg(all(test, not(target_family = "wasm")))]
6182fn run_deadline_final_verdict_test_hook() {
6183  if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6184    hook();
6185  }
6186}
6187
6188// Fired after the synchronized verdict gate has closed and before the verdict
6189// takes the stop-publication mutex for its final rechecks. A test can pause
6190// here while shutdown publishes generation stop and its abort wake reaches the
6191// closed admission gate.
6192#[cfg(all(test, not(target_family = "wasm")))]
6193thread_local! {
6194  static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6195    const { std::cell::RefCell::new(None) };
6196}
6197
6198#[cfg(all(test, not(target_family = "wasm")))]
6199fn run_deadline_gated_verdict_test_hook() {
6200  if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6201    hook();
6202  }
6203}
6204
6205// Fired after the final verdict has acquired the stop-publication mutex. A
6206// shutdown test can retain that guard while another thread reaches the
6207// lifecycle -> stop-publication lock edge.
6208#[cfg(all(test, not(target_family = "wasm")))]
6209thread_local! {
6210  static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6211    const { std::cell::RefCell::new(None) };
6212}
6213
6214#[cfg(all(test, not(target_family = "wasm")))]
6215fn run_deadline_synchronized_stop_test_hook() {
6216  if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6217    hook();
6218  }
6219}
6220
6221// Fired after the final verdict owns exact-owner publication and before it
6222// checks handoff/lane predicates. Tests can remove unrelated saving predicates
6223// while retaining the synchronized owner state under examination.
6224#[cfg(all(test, not(target_family = "wasm")))]
6225type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
6226
6227#[cfg(all(test, not(target_family = "wasm")))]
6228thread_local! {
6229  static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
6230    std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
6231      const { std::cell::RefCell::new(None) };
6232}
6233
6234#[cfg(all(test, not(target_family = "wasm")))]
6235fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
6236  if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6237    hook(parker);
6238  }
6239}
6240
6241// Fired immediately before a test thread takes the stop-publication mutex.
6242// Installed only on the shutdown thread, this proves the controller still
6243// holds the lifecycle in Running while waiting for a verdict-owned guard.
6244#[cfg(all(test, not(target_family = "wasm")))]
6245thread_local! {
6246  static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
6247    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6248      const { std::cell::RefCell::new(None) };
6249}
6250
6251#[cfg(all(test, not(target_family = "wasm")))]
6252fn run_before_generation_stop_publication_lock_test_hook() {
6253  if let Some(hook) =
6254    BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6255  {
6256    hook();
6257  }
6258}
6259
6260// Fired on an admission thread after it observes a closed verdict gate. The
6261// hook is thread-local so a shutdown test can prove that an abort wake reached
6262// this exact wait without affecting unrelated scheduler threads.
6263#[cfg(all(test, not(target_family = "wasm")))]
6264thread_local! {
6265  static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
6266    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6267      const { std::cell::RefCell::new(None) };
6268}
6269
6270#[cfg(all(test, not(target_family = "wasm")))]
6271fn run_deadlock_closed_gate_admission_test_hook() {
6272  if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6273  {
6274    hook();
6275  }
6276}
6277
6278// Timer registration enters the shared verdict admission before this seam.
6279// Tests can pause there to prove the timer remains an announced publication
6280// until its heap entry becomes visible.
6281#[cfg(all(test, not(target_family = "wasm")))]
6282thread_local! {
6283  static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6284    const { std::cell::RefCell::new(None) };
6285}
6286
6287#[cfg(all(test, not(target_family = "wasm")))]
6288fn run_timer_registration_admission_test_hook() {
6289  if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6290    hook();
6291  }
6292}
6293
6294#[cfg(all(test, not(target_family = "wasm")))]
6295thread_local! {
6296  static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6297    const { std::cell::RefCell::new(None) };
6298}
6299
6300#[cfg(all(test, not(target_family = "wasm")))]
6301fn run_owner_handoff_selected_test_hook() {
6302  if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6303    hook();
6304  }
6305}
6306
6307/// Registry of parked (or committed-to-parking) cooperative drivers, used by
6308/// queue-wakes to wake exactly one of them.
6309#[cfg(not(target_family = "wasm"))]
6310#[derive(Default)]
6311struct ParkedDrivers {
6312  parked: Mutex<Vec<ParkedDriver>>,
6313  // Mirror of `parked.len()`, maintained under the mutex and read lock-free
6314  // by `wake_one`'s no-waiter fast path.
6315  count: AtomicUsize,
6316}
6317
6318#[cfg(not(target_family = "wasm"))]
6319struct ParkedDriver {
6320  parker: Arc<DriverParker>,
6321  can_run_blocking: bool,
6322  dependency: Option<Arc<TaskDependency>>,
6323}
6324
6325#[cfg(not(target_family = "wasm"))]
6326impl ParkedDrivers {
6327  /// Register `parker` as parked-or-parking. MUST precede the caller's final
6328  /// work re-check -- see the lost-wakeup argument at [`Self::wake_one`].
6329  #[cfg(test)]
6330  fn register(&self, parker: &Arc<DriverParker>) {
6331    self.register_with_role(parker, true, None);
6332  }
6333
6334  fn register_with_role(
6335    &self,
6336    parker: &Arc<DriverParker>,
6337    can_run_blocking: bool,
6338    dependency: Option<Arc<TaskDependency>>,
6339  ) {
6340    let mut parked = self
6341      .parked
6342      .lock()
6343      .unwrap_or_else(std::sync::PoisonError::into_inner);
6344    parked.push(ParkedDriver {
6345      parker: Arc::clone(parker),
6346      can_run_blocking,
6347      dependency,
6348    });
6349    self.count.store(parked.len(), Ordering::SeqCst);
6350  }
6351
6352  /// Remove `parker` if still registered. `wake_one` removes the parkers it
6353  /// pops, while a direct future-wake does not, so drivers call this after
6354  /// every park (and after a re-check aborts one).
6355  fn deregister(&self, parker: &Arc<DriverParker>) {
6356    let removed = {
6357      let mut parked = self
6358        .parked
6359        .lock()
6360        .unwrap_or_else(std::sync::PoisonError::into_inner);
6361      let removed = parked
6362        .iter()
6363        .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
6364        .map(|index| parked.swap_remove(index));
6365      self.count.store(parked.len(), Ordering::SeqCst);
6366      removed
6367    };
6368    drop(removed);
6369  }
6370
6371  /// Wake one parked driver -- most recently parked first, its stack and
6372  /// caches are the warmest -- returning whether anyone was woken.
6373  ///
6374  /// LOST-WAKEUP ARGUMENT. A scheduler pushes
6375  /// work under a work-queue mutex Q, THEN calls `wake_one`; a parking driver
6376  /// registers itself (the `count` store above, under the registry mutex),
6377  /// THEN re-checks the work queues under Q, and only then parks. Order the
6378  /// two Q critical sections:
6379  ///   * If the driver's re-check locks Q after the scheduler's push unlocked
6380  ///     it, the re-check observes the work and the driver does not park --
6381  ///     skipping the wake (fast path below) is safe.
6382  ///   * Otherwise the driver's re-check unlocked Q before the scheduler's
6383  ///     push locked it. The driver's `count` store is sequenced before its
6384  ///     re-check, the re-check's unlock of Q synchronizes-with the push's
6385  ///     lock of Q, and the push is sequenced before the fast-path load
6386  ///     below; the load therefore happens-after the `count` store and must
6387  ///     observe `count >= 1`: the slow path pops a registered parker and
6388  ///     wakes it.
6389  ///
6390  /// A wake delivered to a driver that raced out of its park (or aborted it)
6391  /// is stored as that driver's permit; the driver re-checks every work
6392  /// source before it can sleep on the next park, so the wake still lands.
6393  fn wake_one(&self) -> bool {
6394    if self.count.load(Ordering::SeqCst) == 0 {
6395      // No-waiter fast path: nobody is parked or committed to parking (any
6396      // in-flight parker that missed this check re-checks the queues after
6397      // registering, per the argument above).
6398      return false;
6399    }
6400    let driver = {
6401      let mut parked = self
6402        .parked
6403        .lock()
6404        .unwrap_or_else(std::sync::PoisonError::into_inner);
6405      let driver = parked.pop();
6406      self.count.store(parked.len(), Ordering::SeqCst);
6407      driver
6408    };
6409    match driver {
6410      Some(driver) => {
6411        driver.parker.unpark();
6412        true
6413      }
6414      None => false,
6415    }
6416  }
6417
6418  /// Wake the most recently parked cooperative driver that is eligible to
6419  /// consume blocking work.
6420  fn wake_one_blocking(&self) -> bool {
6421    if self.count.load(Ordering::SeqCst) == 0 {
6422      return false;
6423    }
6424    let driver = {
6425      let mut parked = self
6426        .parked
6427        .lock()
6428        .unwrap_or_else(std::sync::PoisonError::into_inner);
6429      let driver = parked
6430        .iter()
6431        .rposition(|driver| driver.can_run_blocking)
6432        .map(|index| parked.swap_remove(index));
6433      self.count.store(parked.len(), Ordering::SeqCst);
6434      driver
6435    };
6436    match driver {
6437      Some(driver) => {
6438        driver.parker.unpark();
6439        true
6440      }
6441      None => false,
6442    }
6443  }
6444
6445  /// Wake one parked driver whose live dependency belongs to `owner`.
6446  ///
6447  /// The dependency is published with the parker, so an unrelated newer
6448  /// driver cannot consume the bounded owner-lane handoff.
6449  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
6450    loop {
6451      if self.count.load(Ordering::SeqCst) == 0 {
6452        return false;
6453      }
6454      let candidates = {
6455        let parked = self
6456          .parked
6457          .lock()
6458          .unwrap_or_else(std::sync::PoisonError::into_inner);
6459        parked
6460          .iter()
6461          .rev()
6462          .filter_map(|driver| {
6463            if !driver.can_run_blocking {
6464              return None;
6465            }
6466            driver
6467              .dependency
6468              .as_ref()
6469              .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
6470          })
6471          .collect::<Vec<_>>()
6472      };
6473      let selected = candidates.into_iter().find(|(_, dependency)| {
6474        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
6475      });
6476      let Some((selected_parker, selected_dependency)) = selected else {
6477        return false;
6478      };
6479      let driver = {
6480        let mut parked = self
6481          .parked
6482          .lock()
6483          .unwrap_or_else(std::sync::PoisonError::into_inner);
6484        let driver = parked
6485          .iter()
6486          .rposition(|driver| {
6487            Arc::ptr_eq(&driver.parker, &selected_parker)
6488              && driver
6489                .dependency
6490                .as_ref()
6491                .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
6492          })
6493          .map(|index| parked.swap_remove(index));
6494        // Publish the exact-owner token before releasing the registry lock. A
6495        // direct future wake can race this selection, but the driver must
6496        // deregister under this same lock before it can poll Ready and forward
6497        // an unconsumed handoff on exit.
6498        if let Some(driver) = &driver {
6499          driver.parker.grant_owner_handoff(owner);
6500        }
6501        self.count.store(parked.len(), Ordering::SeqCst);
6502        driver
6503      };
6504      if let Some(driver) = driver {
6505        #[cfg(test)]
6506        run_owner_handoff_selected_test_hook();
6507        driver.parker.unpark();
6508        return true;
6509      }
6510    }
6511  }
6512
6513  /// Wake every timer-bounded driver so cancellation of the earliest timer
6514  /// cannot leave pool workers parked until a stale deadline.
6515  fn wake_all(&self) {
6516    if self.count.load(Ordering::SeqCst) == 0 {
6517      return;
6518    }
6519    let drivers = {
6520      let mut parked = self
6521        .parked
6522        .lock()
6523        .unwrap_or_else(std::sync::PoisonError::into_inner);
6524      let drivers = parked.drain(..).collect::<Vec<_>>();
6525      self.count.store(0, Ordering::SeqCst);
6526      drivers
6527    };
6528    for driver in drivers {
6529      driver.parker.unpark();
6530    }
6531  }
6532}
6533
6534// ---------------------------------------------------------------------------
6535// Runtime timer facility: `sleep_until` without a tokio
6536// reactor. Two drivers, selected by runtime flavor:
6537//
6538//   * MultiThread: a runtime-owned binary heap plus one dedicated non-Rayon
6539//     timekeeper thread. Timer waits never consume the configured CPU workers.
6540//     Cooperative re-entrant `block_on` parks are ALSO bounded by the earliest
6541//     deadline, so the parked driver can fire due timers without depending on
6542//     any particular inter-thread wake ordering.
6543//   * CurrentThread: timers are DELEGATED TO THE HOST event loop through an
6544//     injected [`TimerDriver`] (the Node binding registers a setTimeout-based
6545//     driver at import; wasi-single uses the same path). A CT runtime without
6546//     a registered driver fails LOUD at `sleep_until` -- never a silent hang.
6547//
6548// DEADLOCK-DETECTION INTERACTIONS:
6549//   * MT: a wait on a pending timer is never a deadlock candidate -- the wall
6550//     clock is a guaranteed wake source. Timer waits therefore use their own
6551//     primitive (a plain `park_timeout` bounded by the earliest deadline),
6552//     NEVER `park_with_deadline`, so an armed park deadline
6553//     shorter than the next timer cannot fire on a legitimate timer wait; the
6554//     fire itself schedules the woken task, which IS fingerprint progress for
6555//     every other parked driver. The expiry VERDICT additionally re-checks
6556//     the heap (see `cooperative_block_on`): arming a timer bumps no progress
6557//     counter by design, so a registration racing an already-committed
6558//     deadline park must veto the panic there.
6559//   * CT certain check (threadless): a pending HOST timer is NOT a wake
6560//     token -- the JS setTimeout relay can only run on the very thread that
6561//     is parking, so it can never fire while `block_on` holds it. The check
6562//     panics unconditionally on a threadless build; a pending timer merely
6563//     selects the timer-backed variant of the typed diagnostic. The CT
6564//     deadline verdict (threaded flavor, where the host loop genuinely runs
6565//     concurrently) counts only LIVE waits (deadline still in the future): a
6566//     pending timer whose deadline passed without firing is the
6567//     frozen-JS-event-loop signature that the deadline-based detection
6568//     exists to catch, so it must NOT suppress the panic.
6569
6570/// Host-turn dispatcher for the CurrentThread runnable queue.
6571///
6572/// The NAPI binding registers one weak native threadsafe-function driver per
6573/// importing environment. Its custom Node-API callback owns the fresh host turn
6574/// and keeps delivery capabilities out of JavaScript. Scheduling through a
6575/// fresh host turn is required for soundness: arbitrary futures may invoke
6576/// their waker while holding internal locks, so polling the runnable inline
6577/// from that wake can self-deadlock.
6578pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
6579  /// Queue one host turn that will call [`drive_current_thread_tasks`] with
6580  /// [`CurrentThreadTaskDelivery::capability`].
6581  ///
6582  /// The delivery capability is scoped to this exact host registration and
6583  /// attempt. The registry maps it back to the executor's one internal
6584  /// dispatch capability only while this attempt remains current.
6585  /// `false` means this driver can no longer dispatch and must be swept. After
6586  /// returning `true`, the driver must eventually call either
6587  /// [`acknowledge_current_thread_task_delivery`] or
6588  /// [`fail_current_thread_task_delivery`] with this exact value. A successful
6589  /// acknowledgement is accepted only after
6590  /// [`drive_current_thread_tasks`] claimed this exact delivery.
6591  fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
6592
6593  fn is_live(&self) -> bool {
6594    true
6595  }
6596
6597  /// Called after this driver has been removed from the registry, with no
6598  /// registry lock held. Must be idempotent with explicit host cleanup.
6599  fn on_swept(&self) {}
6600}
6601
6602#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6603pub struct CurrentThreadTaskDriverId(u64);
6604
6605type CurrentThreadTaskDriverSnapshot =
6606  ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
6607
6608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6609pub struct CurrentThreadTaskDelivery {
6610  driver: CurrentThreadTaskDriverId,
6611  capability: u64,
6612  dispatch: u64,
6613}
6614
6615impl CurrentThreadTaskDelivery {
6616  /// Opaque driver capability for this exact registration attempt.
6617  pub fn capability(self) -> u64 {
6618    self.capability
6619  }
6620}
6621
6622struct CurrentThreadTaskDeliveryState {
6623  delivery: CurrentThreadTaskDelivery,
6624  claimed: bool,
6625  accepted: bool,
6626}
6627
6628struct CurrentThreadTaskDriverEntry {
6629  driver: Arc<dyn CurrentThreadTaskDriver>,
6630  delivery: Option<CurrentThreadTaskDeliveryState>,
6631  pending: Option<u64>,
6632  previous: Option<CurrentThreadTaskDriverId>,
6633  next: Option<CurrentThreadTaskDriverId>,
6634}
6635
6636#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
6637enum CurrentThreadDispatchPhase {
6638  #[default]
6639  Pending,
6640  Serviced,
6641  Cancelling,
6642}
6643
6644#[derive(Default)]
6645struct CurrentThreadDispatchState {
6646  phase: CurrentThreadDispatchPhase,
6647  epoch: u64,
6648  publications: usize,
6649  references: usize,
6650  accepted: bool,
6651  accepted_failure: bool,
6652}
6653
6654impl CurrentThreadDispatchState {
6655  fn is_serviced(&self) -> bool {
6656    self.phase == CurrentThreadDispatchPhase::Serviced
6657  }
6658
6659  fn is_cancelling(&self) -> bool {
6660    self.phase == CurrentThreadDispatchPhase::Cancelling
6661  }
6662
6663  fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
6664    CurrentThreadTaskDispatchFailure {
6665      dispatch,
6666      epoch: self.epoch,
6667    }
6668  }
6669}
6670
6671#[derive(Default)]
6672struct CurrentThreadTaskDriverRegistryState {
6673  entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
6674  oldest: Option<CurrentThreadTaskDriverId>,
6675  newest: Option<CurrentThreadTaskDriverId>,
6676  delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
6677  dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
6678}
6679
6680impl CurrentThreadTaskDriverRegistryState {
6681  fn insert_driver(
6682    &mut self,
6683    id: CurrentThreadTaskDriverId,
6684    driver: Arc<dyn CurrentThreadTaskDriver>,
6685  ) {
6686    let previous = self.newest;
6687    let replaced = self.entries.insert(
6688      id,
6689      CurrentThreadTaskDriverEntry {
6690        driver,
6691        delivery: None,
6692        pending: None,
6693        previous,
6694        next: None,
6695      },
6696    );
6697    assert!(
6698      replaced.is_none(),
6699      "CurrentThread task driver id must be unique"
6700    );
6701    if let Some(previous) = previous {
6702      self
6703        .entries
6704        .get_mut(&previous)
6705        .expect("the newest CurrentThread task driver must remain registered")
6706        .next = Some(id);
6707    } else {
6708      self.oldest = Some(id);
6709    }
6710    self.newest = Some(id);
6711  }
6712
6713  fn remove_driver(
6714    &mut self,
6715    id: CurrentThreadTaskDriverId,
6716  ) -> Option<CurrentThreadTaskDriverEntry> {
6717    let entry = self.entries.remove(&id)?;
6718    if let Some(previous) = entry.previous {
6719      self
6720        .entries
6721        .get_mut(&previous)
6722        .expect("the previous CurrentThread task driver must remain registered")
6723        .next = entry.next;
6724    } else {
6725      self.oldest = entry.next;
6726    }
6727    if let Some(next) = entry.next {
6728      self
6729        .entries
6730        .get_mut(&next)
6731        .expect("the next CurrentThread task driver must remain registered")
6732        .previous = entry.previous;
6733    } else {
6734      self.newest = entry.previous;
6735    }
6736    if let Some(delivery) = &entry.delivery {
6737      let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
6738      debug_assert_eq!(
6739        indexed,
6740        Some(id),
6741        "delivery capability index must match its driver"
6742      );
6743    }
6744    Some(entry)
6745  }
6746
6747  fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
6748    let mut snapshot = Vec::with_capacity(self.entries.len());
6749    let mut current = self.oldest;
6750    while let Some(id) = current {
6751      let entry = self
6752        .entries
6753        .get(&id)
6754        .expect("CurrentThread task driver order must reference a live entry");
6755      snapshot.push((id, Arc::clone(&entry.driver)));
6756      current = entry.next;
6757    }
6758    debug_assert_eq!(
6759      snapshot.len(),
6760      self.entries.len(),
6761      "CurrentThread task driver order must contain every entry exactly once"
6762    );
6763    snapshot
6764  }
6765
6766  fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
6767    if self.entries.len() != expected.len() {
6768      return false;
6769    }
6770    let mut current = self.oldest;
6771    for expected in expected {
6772      if current != Some(*expected) {
6773        return false;
6774      }
6775      current = self
6776        .entries
6777        .get(expected)
6778        .expect("CurrentThread task driver order must reference a live entry")
6779        .next;
6780    }
6781    current.is_none()
6782  }
6783}
6784
6785struct CurrentThreadTaskDriverSelection {
6786  live: CurrentThreadTaskDriverSnapshot,
6787  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
6788}
6789
6790#[derive(Default)]
6791struct CurrentThreadTaskDeliveryCompletion {
6792  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
6793  redispatch: bool,
6794}
6795
6796struct CurrentThreadTaskDispatchCompletion {
6797  result: CurrentThreadHostDispatchResult,
6798  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
6799}
6800
6801struct CurrentThreadTaskDispatchPublicationGuard<'a> {
6802  registry: &'a CurrentThreadTaskDriverRegistry,
6803  dispatch: Option<u64>,
6804}
6805
6806impl CurrentThreadTaskDispatchPublicationGuard<'_> {
6807  fn finish(mut self) -> CurrentThreadHostDispatchResult {
6808    let dispatch = self
6809      .dispatch
6810      .expect("the CurrentThread task dispatch publication must remain armed");
6811    let result = self.registry.finish_publication(dispatch);
6812    self.dispatch = None;
6813    result
6814  }
6815}
6816
6817impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
6818  fn drop(&mut self) {
6819    if let Some(dispatch) = self.dispatch.take() {
6820      self.registry.abort_publication(dispatch);
6821    }
6822  }
6823}
6824
6825#[derive(Default)]
6826struct CurrentThreadTaskDriverRegistry {
6827  state: Mutex<CurrentThreadTaskDriverRegistryState>,
6828  next_id: AtomicU64,
6829  next_delivery: AtomicU64,
6830}
6831
6832impl CurrentThreadTaskDriverRegistry {
6833  fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
6834    let id = next_unique_id(
6835      &self.next_id,
6836      "CurrentThread task driver id space exhausted",
6837    );
6838    let id = CurrentThreadTaskDriverId(id);
6839    self
6840      .state
6841      .lock()
6842      .unwrap_or_else(std::sync::PoisonError::into_inner)
6843      .insert_driver(id, driver);
6844    id
6845  }
6846
6847  fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
6848    let (removed, failed_dispatches) = {
6849      let mut state = self
6850        .state
6851        .lock()
6852        .unwrap_or_else(std::sync::PoisonError::into_inner);
6853      let removed = state.remove_driver(id);
6854      let mut failed_dispatches = Vec::new();
6855      if let Some(entry) = &removed {
6856        Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
6857      }
6858      (removed, failed_dispatches)
6859    };
6860    let _ = catch_unwind_contained(|| drop(removed));
6861    CurrentThreadTaskDeliveryCompletion {
6862      failed_dispatches,
6863      redispatch: false,
6864    }
6865  }
6866
6867  fn live(&self) -> CurrentThreadTaskDriverSelection {
6868    let mut failed_dispatches = Vec::new();
6869    loop {
6870      let snapshot = CurrentThreadTaskDriverSnapshot::new({
6871        let state = self
6872          .state
6873          .lock()
6874          .unwrap_or_else(std::sync::PoisonError::into_inner);
6875        state.ordered_snapshot()
6876      });
6877      let mut dead_ids = FxHashSet::default();
6878      let mut live_ids = Vec::new();
6879      for (id, driver) in snapshot.as_slice() {
6880        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
6881          live_ids.push(*id);
6882        } else {
6883          dead_ids.insert(*id);
6884        }
6885      }
6886
6887      let (live, swept, retry) = {
6888        let mut state = self
6889          .state
6890          .lock()
6891          .unwrap_or_else(std::sync::PoisonError::into_inner);
6892        let mut swept = Vec::new();
6893        let mut current = state.oldest;
6894        while let Some(id) = current {
6895          let next = state
6896            .entries
6897            .get(&id)
6898            .expect("CurrentThread task driver order must reference a live entry")
6899            .next;
6900          if dead_ids.contains(&id)
6901            && let Some(entry) = state.remove_driver(id)
6902          {
6903            swept.push(entry);
6904          }
6905          current = next;
6906        }
6907        for entry in &swept {
6908          Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
6909        }
6910        let retry = !state.ordered_ids_match(&live_ids);
6911        let live = if retry {
6912          Vec::new()
6913        } else {
6914          state.ordered_snapshot()
6915        };
6916        (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
6917      };
6918
6919      // A reentrant probe can unregister multiple snapshot-only drivers.
6920      // See the implementation notes in this crate's README.
6921      drop(snapshot);
6922      for entry in swept {
6923        let _ = catch_unwind_contained(|| entry.driver.on_swept());
6924        let _ = catch_unwind_contained(|| drop(entry));
6925      }
6926      if !retry {
6927        return CurrentThreadTaskDriverSelection {
6928          live,
6929          failed_dispatches,
6930        };
6931      }
6932    }
6933  }
6934
6935  #[cfg(test)]
6936  fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
6937    let mut live = self.live().live;
6938    live.pop().map(|(id, driver)| (id, driver.into_inner()))
6939  }
6940
6941  fn next_delivery(
6942    &self,
6943    driver: CurrentThreadTaskDriverId,
6944    dispatch: u64,
6945  ) -> CurrentThreadTaskDelivery {
6946    let capability = next_unique_id(
6947      &self.next_delivery,
6948      "CurrentThread task delivery id space exhausted",
6949    )
6950    .checked_add(1)
6951    .expect("CurrentThread task delivery id space exhausted");
6952    CurrentThreadTaskDelivery {
6953      driver,
6954      capability,
6955      dispatch,
6956    }
6957  }
6958
6959  fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
6960    let dispatch_state = state
6961      .dispatches
6962      .get_mut(&dispatch)
6963      .expect("a referenced CurrentThread dispatch must remain registered");
6964    dispatch_state.references = dispatch_state
6965      .references
6966      .checked_add(1)
6967      .expect("CurrentThread dispatch reference count exhausted");
6968  }
6969
6970  fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
6971    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
6972      return;
6973    };
6974    dispatch_state.references = dispatch_state
6975      .references
6976      .checked_sub(1)
6977      .expect("CurrentThread dispatch reference count underflow");
6978  }
6979
6980  fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
6981    let publication = {
6982      let mut state = self
6983        .state
6984        .lock()
6985        .unwrap_or_else(std::sync::PoisonError::into_inner);
6986      let dispatch_state = state.dispatches.entry(dispatch).or_default();
6987      if dispatch_state.is_serviced() {
6988        return CurrentThreadTaskDispatchCompletion {
6989          result: CurrentThreadHostDispatchResult::Accepted,
6990          failed_dispatches: Vec::new(),
6991        };
6992      }
6993      if dispatch_state.is_cancelling() {
6994        return CurrentThreadTaskDispatchCompletion {
6995          result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
6996          failed_dispatches: Vec::new(),
6997        };
6998      }
6999      dispatch_state.publications = dispatch_state
7000        .publications
7001        .checked_add(1)
7002        .expect("CurrentThread dispatch publication count exhausted");
7003      CurrentThreadTaskDispatchPublicationGuard {
7004        registry: self,
7005        dispatch: Some(dispatch),
7006      }
7007    };
7008
7009    let CurrentThreadTaskDriverSelection {
7010      mut live,
7011      mut failed_dispatches,
7012    } = self.live();
7013    let planned_deliveries = {
7014      let mut state = self
7015        .state
7016        .lock()
7017        .unwrap_or_else(std::sync::PoisonError::into_inner);
7018      let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
7019        return CurrentThreadTaskDispatchCompletion {
7020          result: CurrentThreadHostDispatchResult::Unavailable,
7021          failed_dispatches,
7022        };
7023      };
7024      if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
7025        Vec::new()
7026      } else {
7027        // Reserve every capability and all output storage before mutating an
7028        // entry. If ID allocation unwinds, the publication guard can retire
7029        // the barrier without leaving an undispatched physical slot occupied.
7030        let mut reserved_deliveries = Vec::with_capacity(live.len());
7031        for (id, _) in live.as_slice().iter().rev() {
7032          let Some(entry) = state.entries.get(id) else {
7033            continue;
7034          };
7035          if entry.delivery.is_none() {
7036            reserved_deliveries.push(self.next_delivery(*id, dispatch));
7037          }
7038        }
7039        state.delivery_drivers.reserve(reserved_deliveries.len());
7040        let mut reserved_deliveries = reserved_deliveries.into_iter();
7041        let mut planned_deliveries = Vec::with_capacity(live.len());
7042        let mut retired = Vec::with_capacity(live.len());
7043        let mut accepted_reservation = false;
7044        for (id, _) in live.as_slice().iter().rev() {
7045          let Some(entry) = state.entries.get(id) else {
7046            continue;
7047          };
7048          if let Some(delivery) = &entry.delivery
7049            && delivery.delivery.dispatch == dispatch
7050          {
7051            accepted_reservation |= delivery.accepted;
7052            continue;
7053          }
7054          if entry.delivery.is_some() {
7055            let previous = state
7056              .entries
7057              .get_mut(id)
7058              .expect("the CurrentThread task driver must remain registered")
7059              .pending
7060              .replace(dispatch);
7061            if previous != Some(dispatch) {
7062              if let Some(previous) = previous {
7063                Self::remove_dispatch_reference(&mut state, previous);
7064              }
7065              Self::add_dispatch_reference(&mut state, dispatch);
7066            }
7067            retired.push(previous);
7068            accepted_reservation = true;
7069            continue;
7070          }
7071          let delivery = reserved_deliveries
7072            .next()
7073            .expect("every available CurrentThread host must have a reserved delivery");
7074          let pending = state
7075            .entries
7076            .get_mut(id)
7077            .expect("the CurrentThread task driver must remain registered")
7078            .pending
7079            .take();
7080          let accepted = pending == Some(dispatch);
7081          if !accepted && pending.is_some() {
7082            retired.push(pending);
7083          }
7084          match pending {
7085            Some(pending) if pending == dispatch => {}
7086            Some(pending) => {
7087              Self::remove_dispatch_reference(&mut state, pending);
7088              Self::add_dispatch_reference(&mut state, dispatch);
7089            }
7090            None => Self::add_dispatch_reference(&mut state, dispatch),
7091          }
7092          let indexed = state.delivery_drivers.insert(delivery.capability, *id);
7093          assert!(
7094            indexed.is_none(),
7095            "CurrentThread delivery capability must be unique"
7096          );
7097          state
7098            .entries
7099            .get_mut(id)
7100            .expect("the CurrentThread task driver must remain registered")
7101            .delivery = Some(CurrentThreadTaskDeliveryState {
7102            delivery,
7103            claimed: false,
7104            accepted,
7105          });
7106          accepted_reservation |= accepted;
7107          planned_deliveries.push((*id, delivery));
7108        }
7109        let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
7110        debug_assert!(
7111          all_reserved_deliveries_installed,
7112          "every reserved CurrentThread delivery must be installed"
7113        );
7114        if accepted_reservation {
7115          state
7116            .dispatches
7117            .get_mut(&dispatch)
7118            .expect("the publishing dispatch state must remain registered")
7119            .accepted = true;
7120        }
7121        for dispatch in retired.into_iter().flatten() {
7122          Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7123        }
7124        planned_deliveries
7125      }
7126    };
7127
7128    // Pair newest-first delivery plans with contained driver owners after
7129    // releasing the registry lock. Normal, rejection, and unwind cleanup can
7130    // then destroy every selected driver independently without re-entering the
7131    // registry under its mutex.
7132    let mut deliveries = Vec::with_capacity(planned_deliveries.len());
7133    let mut planned_deliveries = planned_deliveries.into_iter();
7134    let mut next_delivery = planned_deliveries.next();
7135    while let Some((id, driver)) = live.pop() {
7136      let Some((planned_id, delivery)) = next_delivery else {
7137        break;
7138      };
7139      if planned_id == id {
7140        deliveries.push((id, driver, delivery));
7141        next_delivery = planned_deliveries.next();
7142      } else {
7143        next_delivery = Some((planned_id, delivery));
7144      }
7145    }
7146    debug_assert!(
7147      next_delivery.is_none() && planned_deliveries.len() == 0,
7148      "every planned CurrentThread delivery must retain its selected driver"
7149    );
7150
7151    for (id, driver, delivery) in deliveries {
7152      if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
7153        self.accept_delivery(id, delivery);
7154      } else {
7155        let (entry, failures) = self.reject_delivery(id, delivery);
7156        failed_dispatches.extend(failures);
7157        if let Some(entry) = entry {
7158          let _ = catch_unwind_contained(|| entry.driver.on_swept());
7159          let _ = catch_unwind_contained(|| drop(entry));
7160        }
7161      }
7162    }
7163
7164    let result = publication.finish();
7165    debug_assert!(
7166      !failed_dispatches
7167        .iter()
7168        .any(|failure| failure.dispatch == dispatch),
7169      "the active publication must report its own terminal outcome directly"
7170    );
7171    CurrentThreadTaskDispatchCompletion {
7172      result,
7173      failed_dispatches,
7174    }
7175  }
7176
7177  #[cfg(test)]
7178  fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7179    self.dispatch_with_completion(dispatch).result
7180  }
7181
7182  fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
7183    let mut state = self
7184      .state
7185      .lock()
7186      .unwrap_or_else(std::sync::PoisonError::into_inner);
7187    let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
7188      entry
7189        .delivery
7190        .as_ref()
7191        .is_some_and(|current| current.delivery == delivery)
7192    }) {
7193      state
7194        .entries
7195        .get_mut(&id)
7196        .expect("the matching CurrentThread task driver must remain registered")
7197        .delivery
7198        .as_mut()
7199        .expect("the matching delivery must remain installed")
7200        .accepted = true;
7201      true
7202    } else {
7203      // A callback may complete or unregister synchronously before
7204      // `CurrentThreadTaskDriver::dispatch` returns. The true return still
7205      // proves acceptance; if no host serviced it, losing the reference is an
7206      // accepted delivery failure retained behind this publication barrier.
7207      false
7208    };
7209    if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
7210      dispatch_state.accepted = true;
7211      dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
7212    }
7213  }
7214
7215  fn reject_delivery(
7216    &self,
7217    id: CurrentThreadTaskDriverId,
7218    delivery: CurrentThreadTaskDelivery,
7219  ) -> (
7220    Option<CurrentThreadTaskDriverEntry>,
7221    Vec<CurrentThreadTaskDispatchFailure>,
7222  ) {
7223    let mut state = self
7224      .state
7225      .lock()
7226      .unwrap_or_else(std::sync::PoisonError::into_inner);
7227    if !state.entries.get(&id).is_some_and(|entry| {
7228      entry
7229        .delivery
7230        .as_ref()
7231        .is_some_and(|current| current.delivery == delivery)
7232    }) {
7233      return (None, Vec::new());
7234    }
7235    let entry = state
7236      .remove_driver(id)
7237      .expect("the matching CurrentThread task driver must remain registered");
7238    let mut failures = Vec::new();
7239    Self::finish_entry_references(&mut state, &entry, &mut failures);
7240    (Some(entry), failures)
7241  }
7242
7243  fn finish_delivery(
7244    &self,
7245    delivery: CurrentThreadTaskDelivery,
7246    failed: bool,
7247  ) -> CurrentThreadTaskDeliveryCompletion {
7248    let mut state = self
7249      .state
7250      .lock()
7251      .unwrap_or_else(std::sync::PoisonError::into_inner);
7252    if !state.entries.get(&delivery.driver).is_some_and(|entry| {
7253      entry
7254        .delivery
7255        .as_ref()
7256        .is_some_and(|current| current.delivery == delivery)
7257    }) {
7258      return CurrentThreadTaskDeliveryCompletion::default();
7259    }
7260    let current = state
7261      .entries
7262      .get_mut(&delivery.driver)
7263      .expect("the matching CurrentThread task driver must remain registered")
7264      .delivery
7265      .take()
7266      .expect("the matching delivery must remain installed");
7267    let indexed = state.delivery_drivers.remove(&delivery.capability);
7268    debug_assert_eq!(
7269      indexed,
7270      Some(delivery.driver),
7271      "delivery capability index must match its driver"
7272    );
7273    let redispatch = state
7274      .entries
7275      .get(&delivery.driver)
7276      .expect("the matching CurrentThread task driver must remain registered")
7277      .pending
7278      .is_some_and(|dispatch| {
7279        state
7280          .dispatches
7281          .get(&dispatch)
7282          .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
7283      });
7284    let mut failed_dispatches = Vec::new();
7285    // Callback completion proves only that JavaScript ran. A success return is
7286    // authoritative only after `drive_current_thread_tasks` claimed this exact
7287    // host attempt; otherwise a no-op or malicious callback could retire the
7288    // registry reference while leaving the executor dispatch pending forever.
7289    Self::finish_dispatch_reference(
7290      &mut state,
7291      delivery.dispatch,
7292      true,
7293      failed || !current.claimed,
7294      &mut failed_dispatches,
7295    );
7296    CurrentThreadTaskDeliveryCompletion {
7297      failed_dispatches,
7298      redispatch,
7299    }
7300  }
7301
7302  fn claim_delivery(&self, capability: u64) -> Option<u64> {
7303    let mut state = self
7304      .state
7305      .lock()
7306      .unwrap_or_else(std::sync::PoisonError::into_inner);
7307    let driver = *state.delivery_drivers.get(&capability)?;
7308    let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
7309    if delivery.capability != capability {
7310      return None;
7311    }
7312    let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
7313    if dispatch_state.is_serviced()
7314      || dispatch_state.is_cancelling()
7315      || state.entries.get(&driver)?.delivery.as_ref()?.claimed
7316    {
7317      return None;
7318    }
7319    let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
7320    current.claimed = true;
7321    current.accepted = true;
7322    state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
7323    Some(delivery.dispatch)
7324  }
7325
7326  fn mark_serviced(&self, dispatch: u64) {
7327    let mut state = self
7328      .state
7329      .lock()
7330      .unwrap_or_else(std::sync::PoisonError::into_inner);
7331    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7332      return;
7333    };
7334    dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
7335    let mut removed_references = 0usize;
7336    for entry in state.entries.values_mut() {
7337      if entry.pending == Some(dispatch) {
7338        entry.pending = None;
7339        removed_references = removed_references
7340          .checked_add(1)
7341          .expect("CurrentThread dispatch reference count exhausted");
7342      }
7343    }
7344    let dispatch_state = state
7345      .dispatches
7346      .get_mut(&dispatch)
7347      .expect("the serviced CurrentThread dispatch must remain registered");
7348    dispatch_state.references = dispatch_state
7349      .references
7350      .checked_sub(removed_references)
7351      .expect("CurrentThread dispatch reference count underflow");
7352    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7353  }
7354
7355  fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
7356    let delivery = {
7357      let state = self
7358        .state
7359        .lock()
7360        .unwrap_or_else(std::sync::PoisonError::into_inner);
7361      state
7362        .delivery_drivers
7363        .get(&capability)
7364        .and_then(|driver| state.entries.get(driver))
7365        .and_then(|entry| entry.delivery.as_ref())
7366        .map(|delivery| delivery.delivery)
7367        .filter(|delivery| delivery.capability == capability)
7368    };
7369    delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
7370      self.finish_delivery(delivery, true)
7371    })
7372  }
7373
7374  fn finish_dispatch_reference(
7375    state: &mut CurrentThreadTaskDriverRegistryState,
7376    dispatch: u64,
7377    accepted: bool,
7378    failed: bool,
7379    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7380  ) {
7381    let (publications, references) = {
7382      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7383        return;
7384      };
7385      dispatch_state.references = dispatch_state
7386        .references
7387        .checked_sub(1)
7388        .expect("CurrentThread dispatch reference count underflow");
7389      dispatch_state.accepted |= accepted;
7390      dispatch_state.accepted_failure |= accepted && failed;
7391      (dispatch_state.publications, dispatch_state.references)
7392    };
7393    if references != 0 || publications != 0 {
7394      return;
7395    }
7396    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7397      return;
7398    };
7399    if dispatch_state.accepted_failure
7400      && !dispatch_state.is_serviced()
7401      && !dispatch_state.is_cancelling()
7402    {
7403      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7404      failures.push(dispatch_state.failure(dispatch));
7405    } else if !dispatch_state.is_cancelling() {
7406      state.dispatches.remove(&dispatch);
7407    }
7408  }
7409
7410  fn finish_entry_references(
7411    state: &mut CurrentThreadTaskDriverRegistryState,
7412    entry: &CurrentThreadTaskDriverEntry,
7413    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7414  ) {
7415    if let Some(delivery) = &entry.delivery {
7416      Self::finish_dispatch_reference(
7417        state,
7418        delivery.delivery.dispatch,
7419        delivery.accepted,
7420        delivery.accepted,
7421        failures,
7422      );
7423    }
7424    if let Some(pending) = entry.pending {
7425      Self::finish_dispatch_reference(state, pending, true, true, failures);
7426    }
7427  }
7428
7429  fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7430    let mut state = self
7431      .state
7432      .lock()
7433      .unwrap_or_else(std::sync::PoisonError::into_inner);
7434    let publications = {
7435      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7436        return CurrentThreadHostDispatchResult::Unavailable;
7437      };
7438      dispatch_state.publications = dispatch_state
7439        .publications
7440        .checked_sub(1)
7441        .expect("CurrentThread dispatch publication count underflow");
7442      dispatch_state.publications
7443    };
7444    if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
7445      return CurrentThreadHostDispatchResult::Accepted;
7446    }
7447    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7448      return CurrentThreadHostDispatchResult::Unavailable;
7449    };
7450    if dispatch_state.is_cancelling() {
7451      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7452    }
7453    if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
7454      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7455      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7456    }
7457    let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
7458    state.dispatches.remove(&dispatch);
7459    if accepted {
7460      CurrentThreadHostDispatchResult::Accepted
7461    } else {
7462      CurrentThreadHostDispatchResult::Unavailable
7463    }
7464  }
7465
7466  fn abort_publication(&self, dispatch: u64) {
7467    let mut state = self
7468      .state
7469      .lock()
7470      .unwrap_or_else(std::sync::PoisonError::into_inner);
7471    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7472      return;
7473    };
7474    let Some(publications) = dispatch_state.publications.checked_sub(1) else {
7475      return;
7476    };
7477    dispatch_state.publications = publications;
7478    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7479  }
7480
7481  fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
7482    let mut state = self
7483      .state
7484      .lock()
7485      .unwrap_or_else(std::sync::PoisonError::into_inner);
7486    let can_rearm = state
7487      .dispatches
7488      .get(&failure.dispatch)
7489      .is_some_and(|dispatch_state| {
7490        dispatch_state.is_cancelling()
7491          && dispatch_state.epoch == failure.epoch
7492          && dispatch_state.publications == 0
7493      })
7494      && !Self::has_dispatch_reference(&state, failure.dispatch);
7495    if !can_rearm {
7496      return false;
7497    }
7498    let dispatch_state = state
7499      .dispatches
7500      .get_mut(&failure.dispatch)
7501      .expect("the exact failed CurrentThread dispatch must remain registered");
7502    let epoch = dispatch_state
7503      .epoch
7504      .checked_add(1)
7505      .expect("CurrentThread dispatch failure epoch space exhausted");
7506    *dispatch_state = CurrentThreadDispatchState {
7507      epoch,
7508      ..CurrentThreadDispatchState::default()
7509    };
7510    true
7511  }
7512
7513  fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
7514    self
7515      .state
7516      .lock()
7517      .unwrap_or_else(std::sync::PoisonError::into_inner)
7518      .dispatches
7519      .get(&failure.dispatch)
7520      .is_some_and(|dispatch_state| {
7521        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
7522      })
7523  }
7524
7525  fn retire_unreferenced_dispatch(
7526    state: &mut CurrentThreadTaskDriverRegistryState,
7527    dispatch: Option<u64>,
7528  ) {
7529    let Some(dispatch) = dispatch else {
7530      return;
7531    };
7532    if Self::has_dispatch_reference(state, dispatch) {
7533      return;
7534    }
7535    let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
7536      return;
7537    };
7538    if dispatch_state.publications == 0
7539      && !dispatch_state.is_cancelling()
7540      && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
7541    {
7542      state.dispatches.remove(&dispatch);
7543    }
7544  }
7545
7546  fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
7547    state
7548      .dispatches
7549      .get(&dispatch)
7550      .is_some_and(|dispatch_state| dispatch_state.references != 0)
7551  }
7552
7553  fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
7554    let mut state = self
7555      .state
7556      .lock()
7557      .unwrap_or_else(std::sync::PoisonError::into_inner);
7558    if state
7559      .dispatches
7560      .get(&failure.dispatch)
7561      .is_some_and(|dispatch_state| {
7562        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
7563      })
7564    {
7565      state.dispatches.remove(&failure.dispatch);
7566    }
7567  }
7568
7569  fn retire_for_shutdown(&self) {
7570    let mut state = self
7571      .state
7572      .lock()
7573      .unwrap_or_else(std::sync::PoisonError::into_inner);
7574    state.dispatches.clear();
7575    for entry in state.entries.values_mut() {
7576      entry.pending = None;
7577    }
7578  }
7579}
7580
7581static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
7582  LazyLock::new(CurrentThreadTaskDriverRegistry::default);
7583
7584pub fn register_current_thread_task_driver(
7585  driver: Arc<dyn CurrentThreadTaskDriver>,
7586) -> CurrentThreadTaskDriverId {
7587  CURRENT_THREAD_TASK_DRIVERS.register(driver)
7588}
7589
7590/// Request service for work that may have accumulated while no live host
7591/// dispatcher was registered. A new host receives the existing internal
7592/// dispatch capability through a fresh registration-scoped delivery token;
7593/// already accepting hosts keep their attempts.
7594pub fn request_current_thread_task_drain() {
7595  RUNTIME.request_current_thread_drain();
7596}
7597
7598pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
7599  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
7600}
7601
7602fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
7603  let CurrentThreadTaskDispatchCompletion {
7604    result,
7605    failed_dispatches,
7606  } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
7607  complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
7608    failed_dispatches,
7609    redispatch: false,
7610  });
7611  result
7612}
7613
7614pub type TimerId = u64;
7615
7616/// Process-global id source for [`Sleep`] futures (both drivers).
7617static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
7618
7619/// Seam for host-delegated timers: the CurrentThread flavor cannot park a
7620/// helper thread on a threadless build, so it delegates
7621/// `sleep_until` to the host event loop through this trait. The Node binding
7622/// installs a `setTimeout`-based implementation via [`register_timer_driver`]
7623/// at import -- one per importing napi env (main thread AND workers).
7624pub trait TimerDriver: Send + Sync + 'static {
7625  /// Arm (or re-arm on re-poll) `waker` to fire at/after `deadline`. Called
7626  /// once per [`Sleep`] poll: implementations must treat a repeated `id` as a
7627  /// waker refresh, not a second timer.
7628  fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
7629  /// Best-effort cancel (Drop of the [`Sleep`] future). A fire that already
7630  /// raced ahead is acceptable (the woken task observes the sleep completed).
7631  fn cancel(&self, id: TimerId);
7632  /// Whether this driver can still deliver wakes. `false` once the driver's
7633  /// host is gone (the Node binding's driver dies with its owning napi env:
7634  /// worker exit aborts the weak threadsafe function). Selection skips and
7635  /// evicts dead drivers -- a timer armed on one would only ever busy-fail.
7636  fn is_live(&self) -> bool {
7637    true
7638  }
7639  /// Called by [`TimerDriverRegistry`] AFTER it swept this driver out of the
7640  /// entries (its `is_live` turned false), with NO registry lock held.
7641  /// Implementations with pending-waker bookkeeping must wake everything
7642  /// armed on them here, so those sleeps re-poll onto the next live
7643  /// registrant: the `is_live` probe can be the FIRST layer to notice a dying
7644  /// host (before its env-cleanup hook or any call failure runs), and a
7645  /// silent `retain` would strand sleeps whose only wake source was the swept
7646  /// driver. Idempotent with the owner's other eviction paths. Default no-op
7647  /// for drivers without waker state.
7648  fn on_swept(&self) {}
7649}
7650
7651/// Handle to one registration in a [`TimerDriverRegistry`], returned by
7652/// [`TimerDriverRegistry::register`] and consumed by
7653/// [`TimerDriverRegistry::unregister`].
7654#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7655pub struct TimerDriverId(u64);
7656
7657type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
7658
7659struct TimerDriverEntry {
7660  driver: Arc<dyn TimerDriver>,
7661  previous: Option<TimerDriverId>,
7662  next: Option<TimerDriverId>,
7663}
7664
7665#[derive(Default)]
7666struct TimerDriverRegistryState {
7667  entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
7668  oldest: Option<TimerDriverId>,
7669  newest: Option<TimerDriverId>,
7670}
7671
7672impl TimerDriverRegistryState {
7673  fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
7674    let previous = self.newest;
7675    let replaced = self.entries.insert(
7676      id,
7677      TimerDriverEntry {
7678        driver,
7679        previous,
7680        next: None,
7681      },
7682    );
7683    assert!(replaced.is_none(), "timer driver id must be unique");
7684    if let Some(previous) = previous {
7685      self
7686        .entries
7687        .get_mut(&previous)
7688        .expect("the newest timer driver must remain registered")
7689        .next = Some(id);
7690    } else {
7691      self.oldest = Some(id);
7692    }
7693    self.newest = Some(id);
7694  }
7695
7696  fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
7697    let entry = self.entries.remove(&id)?;
7698    if let Some(previous) = entry.previous {
7699      self
7700        .entries
7701        .get_mut(&previous)
7702        .expect("the previous timer driver must remain registered")
7703        .next = entry.next;
7704    } else {
7705      self.oldest = entry.next;
7706    }
7707    if let Some(next) = entry.next {
7708      self
7709        .entries
7710        .get_mut(&next)
7711        .expect("the next timer driver must remain registered")
7712        .previous = entry.previous;
7713    } else {
7714      self.newest = entry.previous;
7715    }
7716    Some(entry)
7717  }
7718
7719  fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
7720    let mut snapshot = Vec::with_capacity(self.entries.len());
7721    let mut current = self.oldest;
7722    while let Some(id) = current {
7723      let entry = self
7724        .entries
7725        .get(&id)
7726        .expect("timer driver order must reference a live entry");
7727      snapshot.push((id, Arc::clone(&entry.driver)));
7728      current = entry.next;
7729    }
7730    debug_assert_eq!(
7731      snapshot.len(),
7732      self.entries.len(),
7733      "timer driver order must contain every entry exactly once"
7734    );
7735    snapshot
7736  }
7737
7738  fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
7739    if self.entries.len() != expected.len() {
7740      return false;
7741    }
7742    let mut current = self.oldest;
7743    for expected in expected {
7744      if current != Some(*expected) {
7745        return false;
7746      }
7747      current = self
7748        .entries
7749        .get(expected)
7750        .expect("timer driver order must reference a live entry")
7751        .next;
7752    }
7753    current.is_none()
7754  }
7755}
7756
7757/// Host timer drivers keyed by registration order. A driver is owned by a host
7758/// context that can die independently
7759/// of the process -- the Node binding registers one driver per importing napi
7760/// env, and a worker's env teardown kills its driver's threadsafe function. A
7761/// single first-wins slot is therefore unsound: a worker that imports the
7762/// binding first and then exits would permanently shadow the slot with a dead
7763/// driver, and every CT timer armed afterwards would busy-fail against it.
7764/// Selecting only the newest live driver is also
7765/// insufficient: a live worker can be CPU-blocked indefinitely while another
7766/// environment remains responsive. The registry therefore keeps all
7767/// registrants and returns a stable snapshot of every live driver at each
7768/// [`Sleep`] poll, sweeping dead entries out as it goes.
7769///
7770/// Instance-based (not a hidden global) so tests exercise
7771/// registration/eviction/selection against local registries; production uses
7772/// the process-global one behind
7773/// [`register_timer_driver`]/[`unregister_timer_driver`]/[`has_live_timer_driver`].
7774#[derive(Default)]
7775pub struct TimerDriverRegistry {
7776  /// Keyed lookup with a linked registration order. Exact unregister and a
7777  /// swept binding host's idempotent unregister are expected O(1), while
7778  /// snapshots keep deterministic oldest-to-newest fallback order.
7779  state: Mutex<TimerDriverRegistryState>,
7780  /// Stable repoll signals for every currently polled host sleep. A newly
7781  /// registered driver snapshots these after publishing itself, then wakes
7782  /// them outside both registry locks so each sleep re-arms the same timer id
7783  /// on the expanded all-live driver set.
7784  pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
7785  next_id: AtomicU64,
7786}
7787
7788impl Drop for TimerDriverRegistry {
7789  fn drop(&mut self) {
7790    let state = self
7791      .state
7792      .get_mut()
7793      .unwrap_or_else(std::sync::PoisonError::into_inner);
7794    state.oldest = None;
7795    state.newest = None;
7796    let entries = std::mem::take(&mut state.entries);
7797    for (_, entry) in entries {
7798      let _ = catch_unwind_contained(|| drop(entry));
7799    }
7800  }
7801}
7802
7803impl TimerDriverRegistry {
7804  /// Add `driver` as the newest registrant and return its handle.
7805  pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
7806    let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
7807    let id = TimerDriverId(id);
7808    self
7809      .state
7810      .lock()
7811      .unwrap_or_else(std::sync::PoisonError::into_inner)
7812      .insert_driver(id, driver);
7813    // Publish the driver before snapshotting pending sleeps. A sleep publishes
7814    // its repoll signal before selecting live drivers. Therefore either this
7815    // snapshot sees the sleep, or that sleep's subsequent selection sees this
7816    // driver; no registration/insertion interleaving can miss both sides.
7817    let pending = {
7818      let mut entries = self
7819        .pending
7820        .lock()
7821        .unwrap_or_else(std::sync::PoisonError::into_inner);
7822      let mut pending = Vec::with_capacity(entries.len());
7823      entries.retain(|_, repoll| {
7824        if let Some(repoll) = repoll.upgrade() {
7825          pending.push(repoll);
7826          true
7827        } else {
7828          false
7829        }
7830      });
7831      pending
7832    };
7833    for repoll in pending {
7834      repoll.wake();
7835    }
7836    id
7837  }
7838
7839  /// Drop the registration behind `id` (no-op when already swept/removed).
7840  pub fn unregister(&self, id: TimerDriverId) {
7841    let removed = self
7842      .state
7843      .lock()
7844      .unwrap_or_else(std::sync::PoisonError::into_inner)
7845      .remove_driver(id);
7846    // A driver's destructor may re-enter this registry. Keep it outside the
7847    // entries lock just like is_live/on_swept callbacks.
7848    let _ = catch_unwind_contained(|| drop(removed));
7849  }
7850
7851  /// Every LIVE driver in registration order, sweeping dead entries out as a
7852  /// side effect.
7853  ///
7854  /// LOCK DISCIPLINE: no driver callback or destructor runs with the entries
7855  /// lock held. `is_live` is externally implemented and may re-enter this
7856  /// registry; `on_swept` does so in the binding by calling `unregister` and
7857  /// waking pending sleeps whose re-polls call `current()` again. Selection
7858  /// therefore probes a snapshot, removes dead entries under the lock, then
7859  /// runs hooks after release. A concurrent registration/unregistration can
7860  /// stale the snapshot, in which case selection retries.
7861  fn live(&self) -> HostTimerDriverSnapshot {
7862    loop {
7863      let snapshot = HostTimerDriverSnapshot::new({
7864        let state = self
7865          .state
7866          .lock()
7867          .unwrap_or_else(std::sync::PoisonError::into_inner);
7868        state.ordered_snapshot()
7869      });
7870      let mut dead_ids = FxHashSet::default();
7871      let mut live_ids = Vec::new();
7872      for (id, driver) in snapshot.as_slice() {
7873        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
7874          live_ids.push(*id);
7875        } else {
7876          dead_ids.insert(*id);
7877        }
7878      }
7879
7880      let (live, swept, retry) = {
7881        let mut state = self
7882          .state
7883          .lock()
7884          .unwrap_or_else(std::sync::PoisonError::into_inner);
7885        let mut swept = Vec::new();
7886        let mut current = state.oldest;
7887        while let Some(id) = current {
7888          let next = state
7889            .entries
7890            .get(&id)
7891            .expect("timer driver order must reference a live entry")
7892            .next;
7893          if dead_ids.contains(&id)
7894            && let Some(entry) = state.remove_driver(id)
7895          {
7896            swept.push(entry);
7897          }
7898          current = next;
7899        }
7900        let retry = !state.ordered_ids_match(&live_ids);
7901        let live = if retry {
7902          Vec::new()
7903        } else {
7904          state.ordered_snapshot()
7905        };
7906        (HostTimerDriverSnapshot::new(live), swept, retry)
7907      };
7908
7909      // A reentrant probe can unregister multiple snapshot-only drivers.
7910      // See the implementation notes in this crate's README.
7911      drop(snapshot);
7912      for entry in swept {
7913        let _ = catch_unwind_contained(|| entry.driver.on_swept());
7914        let _ = catch_unwind_contained(|| drop(entry));
7915      }
7916      if !retry {
7917        return live;
7918      }
7919    }
7920  }
7921
7922  #[cfg(test)]
7923  fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
7924    let mut live = self.live();
7925    live.pop().map(|(id, driver)| (id, driver.into_inner()))
7926  }
7927
7928  /// Whether a LIVE driver is currently registered (dead-only counts as no).
7929  pub fn has_live_driver(&self) -> bool {
7930    !self.live().is_empty()
7931  }
7932
7933  fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
7934    let replaced = self
7935      .pending
7936      .lock()
7937      .unwrap_or_else(std::sync::PoisonError::into_inner)
7938      .insert(id, Arc::downgrade(repoll));
7939    assert!(
7940      replaced.is_none(),
7941      "host timer {id} registered its repoll signal twice"
7942    );
7943    // Shutdown can close and try to remove the signal immediately before this
7944    // insertion. The post-insert check is the other side of that race.
7945    if repoll.is_closed() {
7946      self.remove_pending(id);
7947    }
7948  }
7949
7950  fn remove_pending(&self, id: TimerId) {
7951    let removed = self
7952      .pending
7953      .lock()
7954      .unwrap_or_else(std::sync::PoisonError::into_inner)
7955      .remove(&id);
7956    let _ = catch_unwind_contained(|| drop(removed));
7957  }
7958
7959  #[cfg(test)]
7960  fn pending_len(&self) -> usize {
7961    self
7962      .pending
7963      .lock()
7964      .unwrap_or_else(std::sync::PoisonError::into_inner)
7965      .len()
7966  }
7967}
7968
7969/// The process-global registry behind [`sleep_until`] (see
7970/// [`TimerDriverRegistry`] for the lifetime story).
7971static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
7972  LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
7973
7974/// Register a host timer driver for the CurrentThread flavor's `sleep_until`.
7975/// Every host context that can serve timers registers its own driver (the
7976/// Node binding: one per importing napi env); every LIVE registrant receives
7977/// each timer so a starved environment cannot own its only wake source.
7978/// Returns the handle for [`unregister_timer_driver`].
7979pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
7980  TIMER_DRIVERS.register(driver)
7981}
7982
7983/// Remove a driver registered via [`register_timer_driver`] -- called when
7984/// its host context dies (the Node binding evicts on env teardown and on
7985/// callback failure). Idempotent.
7986pub fn unregister_timer_driver(id: TimerDriverId) {
7987  TIMER_DRIVERS.unregister(id);
7988}
7989
7990/// Whether a LIVE host timer driver is currently registered. Lets the
7991/// embedder report CurrentThread timer availability honestly (e.g. through a
7992/// capabilities export) instead of guessing: with no
7993/// live driver a CurrentThread `sleep_until` would panic, so the capability
7994/// must read `false` -- including when the only registrants are DEAD (their
7995/// owning envs torn down), not merely when none ever registered.
7996pub fn has_live_timer_driver() -> bool {
7997  TIMER_DRIVERS.has_live_driver()
7998}
7999
8000/// Coalescing wake signal used only to make an already-pending [`Sleep`]
8001/// re-select host drivers after a new registration. The task's arbitrary
8002/// waker is never cloned, invoked, or dropped while this mutex is held.
8003#[derive(Default)]
8004struct HostTimerRepoll {
8005  state: Mutex<HostTimerRepollState>,
8006}
8007
8008#[derive(Default)]
8009struct HostTimerRepollState {
8010  closed: bool,
8011  notified: bool,
8012  waker: Option<Waker>,
8013}
8014
8015impl HostTimerRepoll {
8016  fn register(&self, waker: Waker) {
8017    let (replaced, wake) = {
8018      let mut state = self
8019        .state
8020        .lock()
8021        .unwrap_or_else(std::sync::PoisonError::into_inner);
8022      if state.closed {
8023        (Some(waker), None)
8024      } else if state.notified {
8025        state.notified = false;
8026        (state.waker.take(), Some(waker))
8027      } else {
8028        (state.waker.replace(waker), None)
8029      }
8030    };
8031    let _ = catch_unwind_contained(|| drop(replaced));
8032    if let Some(waker) = wake {
8033      wake_waker_contained(waker);
8034    }
8035  }
8036
8037  fn wake(&self) {
8038    let waker = {
8039      let mut state = self
8040        .state
8041        .lock()
8042        .unwrap_or_else(std::sync::PoisonError::into_inner);
8043      if state.closed {
8044        return;
8045      }
8046      if let Some(waker) = state.waker.take() {
8047        Some(waker)
8048      } else {
8049        state.notified = true;
8050        None
8051      }
8052    };
8053    if let Some(waker) = waker {
8054      wake_waker_contained(waker);
8055    }
8056  }
8057
8058  fn close(&self) {
8059    let waker = {
8060      let mut state = self
8061        .state
8062        .lock()
8063        .unwrap_or_else(std::sync::PoisonError::into_inner);
8064      state.closed = true;
8065      state.notified = false;
8066      state.waker.take()
8067    };
8068    let _ = catch_unwind_contained(|| drop(waker));
8069  }
8070
8071  fn is_closed(&self) -> bool {
8072    self
8073      .state
8074      .lock()
8075      .unwrap_or_else(std::sync::PoisonError::into_inner)
8076      .closed
8077  }
8078}
8079
8080/// Pending HOST-driver timers armed on one CurrentThread executor, keyed by
8081/// timer id with the armed deadline as the value. Maintained by the [`Sleep`]
8082/// futures themselves (insert at FIRST POLL, exactly when `driver.register`
8083/// arms the host timer -- an unpolled Sleep holds no wake-token; remove on
8084/// completion/drop via [`HostTimerPendingGuard`]) and read by the CT deadlock
8085/// detection -- see the interaction notes at the top of this section.
8086#[derive(Default)]
8087struct HostTimerRegistry {
8088  state: Mutex<HostTimerRegistryState>,
8089}
8090
8091#[derive(Default)]
8092struct HostTimerRegistryState {
8093  closed: bool,
8094  timers: FxHashMap<TimerId, HostTimerEntry>,
8095}
8096
8097struct HostTimerEntry {
8098  deadline: Instant,
8099  waker: Waker,
8100  fired: Arc<AtomicBool>,
8101  drivers: Weak<TimerDriverRegistry>,
8102  repoll: Arc<HostTimerRepoll>,
8103}
8104
8105impl HostTimerRegistry {
8106  fn register(
8107    &self,
8108    id: TimerId,
8109    deadline: Instant,
8110    waker: Waker,
8111    fired: &Arc<AtomicBool>,
8112    drivers: &Arc<TimerDriverRegistry>,
8113    repoll: &Arc<HostTimerRepoll>,
8114  ) -> bool {
8115    let replaced = {
8116      let mut state = self
8117        .state
8118        .lock()
8119        .unwrap_or_else(std::sync::PoisonError::into_inner);
8120      if state.closed || fired.load(Ordering::SeqCst) {
8121        fired.store(true, Ordering::SeqCst);
8122        Err(waker)
8123      } else {
8124        Ok(state.timers.insert(
8125          id,
8126          HostTimerEntry {
8127            deadline,
8128            waker,
8129            fired: Arc::clone(fired),
8130            drivers: Arc::downgrade(drivers),
8131            repoll: Arc::clone(repoll),
8132          },
8133        ))
8134      }
8135    };
8136    match replaced {
8137      Ok(Some(entry)) => {
8138        let _ = catch_unwind_contained(|| drop(entry));
8139        true
8140      }
8141      Ok(None) => true,
8142      Err(waker) => {
8143        let _ = catch_unwind_contained(|| drop(waker));
8144        false
8145      }
8146    }
8147  }
8148
8149  fn remove(&self, id: TimerId) {
8150    let removed = self
8151      .state
8152      .lock()
8153      .unwrap_or_else(std::sync::PoisonError::into_inner)
8154      .timers
8155      .remove(&id);
8156    if let Some(entry) = removed {
8157      let _ = catch_unwind_contained(|| drop(entry));
8158    }
8159  }
8160
8161  fn shutdown(&self) {
8162    let timers = {
8163      let mut state = self
8164        .state
8165        .lock()
8166        .unwrap_or_else(std::sync::PoisonError::into_inner);
8167      state.closed = true;
8168      state
8169        .timers
8170        .drain()
8171        .map(|(id, entry)| {
8172          entry.fired.store(true, Ordering::SeqCst);
8173          (id, entry)
8174        })
8175        .collect::<Vec<_>>()
8176    };
8177    for (id, entry) in timers {
8178      entry.repoll.close();
8179      if let Some(drivers) = entry.drivers.upgrade() {
8180        drivers.remove_pending(id);
8181      }
8182      wake_waker_contained(entry.waker);
8183    }
8184  }
8185
8186  /// Any host timer armed at all? Consulted by the threadless CERTAIN check
8187  /// only to pick the diagnostic variant: on a threadless build the park is
8188  /// provably dead either way (the host relay shares the parked thread), but
8189  /// a pending timer means the timer-backed message applies.
8190  fn has_pending(&self) -> bool {
8191    !self
8192      .state
8193      .lock()
8194      .unwrap_or_else(std::sync::PoisonError::into_inner)
8195      .timers
8196      .is_empty()
8197  }
8198
8199  /// Any host timer whose deadline (plus `grace` for host firing lag) is
8200  /// still in the future? Used by the CT deadline VERDICT: waiting out such a
8201  /// timer with zero progress is legitimate. A timer past due by more than
8202  /// `grace` that never fired is not -- that is the frozen-JS-event-loop
8203  /// signature the deadline detection exists to catch, so it must not keep
8204  /// suppressing the panic. Callers pass the armed park deadline as `grace`
8205  /// (the detector's own granularity: firing lag within one window is
8206  /// indistinguishable from an expiry racing the fire).
8207  fn has_live_wait(&self, grace: Duration) -> bool {
8208    let now = Instant::now();
8209    self
8210      .state
8211      .lock()
8212      .unwrap_or_else(std::sync::PoisonError::into_inner)
8213      .timers
8214      .values()
8215      .any(|entry| {
8216        entry
8217          .deadline
8218          .checked_add(grace)
8219          .is_none_or(|deadline| deadline > now)
8220      })
8221  }
8222}
8223
8224/// Removes this sleep's entry from its executor's [`HostTimerRegistry`] on
8225/// drop, so completion AND cancellation (Sleep dropped by a `select!` losing
8226/// arm) both retire the wake-token exactly once.
8227struct HostTimerPendingGuard {
8228  registry: Arc<HostTimerRegistry>,
8229  drivers: Arc<TimerDriverRegistry>,
8230  repoll: Arc<HostTimerRepoll>,
8231  id: TimerId,
8232}
8233
8234impl Drop for HostTimerPendingGuard {
8235  fn drop(&mut self) {
8236    self.repoll.close();
8237    self.registry.remove(self.id);
8238    self.drivers.remove_pending(self.id);
8239  }
8240}
8241
8242/// Runtime-owned timer heap for the MultiThread executor.
8243///
8244/// The timekeeper is a dedicated lifecycle-managed OS thread. It never enters
8245/// Rayon and therefore cannot consume one of the configured CPU workers while
8246/// a long timer is armed.
8247#[cfg(not(target_family = "wasm"))]
8248struct TimerHeap {
8249  inner: Arc<Mutex<TimerHeapInner>>,
8250  parker: Arc<DriverParker>,
8251  thread: Mutex<Option<std::thread::JoinHandle<()>>>,
8252  deadlock_state: Arc<MultiThreadDeadlockState>,
8253  #[cfg(test)]
8254  fail_next_start: AtomicBool,
8255  generation: u64,
8256  executor_id: u64,
8257  thread_name: String,
8258}
8259
8260#[cfg(not(target_family = "wasm"))]
8261#[derive(Default)]
8262struct TimerHeapInner {
8263  /// Indexed min-heap of `(deadline, id)`. Cancellation removes its node in
8264  /// `O(log n)`, so non-earliest timer churn cannot accumulate tombstones for
8265  /// one later unbounded cleanup pass.
8266  queue: TimerDeadlineQueue,
8267  entries: FxHashMap<TimerId, HeapTimerEntry>,
8268  /// The parker of the dedicated timekeeper while it is waiting on a timer.
8269  /// `register_timer` unparks it when the earliest deadline moves up, so the
8270  /// timekeeper re-arms (task requirement: re-arm-to-earlier re-wakes).
8271  timekeeper_parker: Option<Arc<DriverParker>>,
8272  /// Set by `shutdown_timers`: every pending entry has been drain-fired and
8273  /// later registrations must fire immediately (a `Sleep` polled after
8274  /// shutdown resolves early instead of parking a closing runtime forever).
8275  closed: bool,
8276}
8277
8278#[cfg(not(target_family = "wasm"))]
8279struct HeapTimerEntry {
8280  waker: Waker,
8281  /// Shared with the owning [`Sleep`]: set (under the heap lock) when this
8282  /// timer fires or the heap shuts down, so the woken poll returns `Ready`
8283  /// even when the wall clock has not reached `deadline` (shutdown
8284  /// drain-fire).
8285  fired: Arc<AtomicBool>,
8286}
8287
8288#[cfg(not(target_family = "wasm"))]
8289#[derive(Default)]
8290struct TimerDeadlineQueue {
8291  nodes: Vec<(Instant, TimerId)>,
8292  positions: FxHashMap<TimerId, usize>,
8293}
8294
8295#[cfg(not(target_family = "wasm"))]
8296impl TimerDeadlineQueue {
8297  #[cfg(test)]
8298  fn len(&self) -> usize {
8299    self.nodes.len()
8300  }
8301
8302  #[cfg(test)]
8303  fn is_empty(&self) -> bool {
8304    self.nodes.is_empty()
8305  }
8306
8307  fn clear(&mut self) {
8308    self.nodes.clear();
8309    self.positions.clear();
8310  }
8311
8312  fn peek(&self) -> Option<(Instant, TimerId)> {
8313    self.nodes.first().copied()
8314  }
8315
8316  fn push(&mut self, deadline: Instant, id: TimerId) {
8317    let index = self.nodes.len();
8318    assert!(
8319      self.positions.insert(id, index).is_none(),
8320      "timer deadline queue received duplicate id {id}"
8321    );
8322    self.nodes.push((deadline, id));
8323    self.sift_up(index);
8324  }
8325
8326  fn pop(&mut self) -> Option<(Instant, TimerId)> {
8327    let first = self.nodes.first().copied()?;
8328    self.positions.remove(&first.1);
8329    let last = self
8330      .nodes
8331      .pop()
8332      .expect("the non-empty timer heap must have a final node");
8333    if !self.nodes.is_empty() {
8334      self.nodes[0] = last;
8335      self.positions.insert(last.1, 0);
8336      self.sift_down(0);
8337    }
8338    Some(first)
8339  }
8340
8341  fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
8342    let index = self.positions.remove(&id)?;
8343    let removed = self.nodes[index];
8344    let last = self.nodes.pop().expect("an indexed timer node must exist");
8345    if index < self.nodes.len() {
8346      self.nodes[index] = last;
8347      self.positions.insert(last.1, index);
8348      if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
8349        self.sift_up(index);
8350      } else {
8351        self.sift_down(index);
8352      }
8353    }
8354    Some(removed)
8355  }
8356
8357  fn sift_up(&mut self, mut index: usize) {
8358    while index > 0 {
8359      let parent = (index - 1) / 2;
8360      if self.nodes[parent] <= self.nodes[index] {
8361        break;
8362      }
8363      self.swap(parent, index);
8364      index = parent;
8365    }
8366  }
8367
8368  fn sift_down(&mut self, mut index: usize) {
8369    loop {
8370      let left = index * 2 + 1;
8371      if left >= self.nodes.len() {
8372        return;
8373      }
8374      let right = left + 1;
8375      let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
8376        right
8377      } else {
8378        left
8379      };
8380      if self.nodes[index] <= self.nodes[child] {
8381        return;
8382      }
8383      self.swap(index, child);
8384      index = child;
8385    }
8386  }
8387
8388  fn swap(&mut self, left: usize, right: usize) {
8389    self.nodes.swap(left, right);
8390    self.positions.insert(self.nodes[left].1, left);
8391    self.positions.insert(self.nodes[right].1, right);
8392  }
8393}
8394
8395#[cfg(not(target_family = "wasm"))]
8396impl TimerHeapInner {
8397  fn next_deadline(&self) -> Option<Instant> {
8398    self.queue.peek().map(|(deadline, _)| deadline)
8399  }
8400}
8401
8402#[cfg(not(target_family = "wasm"))]
8403impl TimerHeap {
8404  fn new(
8405    generation: u64,
8406    executor_id: u64,
8407    thread_name: String,
8408    deadlock_state: Arc<MultiThreadDeadlockState>,
8409  ) -> Self {
8410    Self {
8411      inner: Arc::new(Mutex::new(TimerHeapInner::default())),
8412      parker: Arc::new(DriverParker::default()),
8413      thread: Mutex::new(None),
8414      deadlock_state,
8415      #[cfg(test)]
8416      fail_next_start: AtomicBool::new(false),
8417      generation,
8418      executor_id,
8419      thread_name,
8420    }
8421  }
8422
8423  fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
8424    if self
8425      .inner
8426      .lock()
8427      .unwrap_or_else(std::sync::PoisonError::into_inner)
8428      .closed
8429    {
8430      return Ok(());
8431    }
8432    let current_thread = std::thread::current().id();
8433    loop {
8434      let finished = {
8435        let mut thread = self
8436          .thread
8437          .lock()
8438          .unwrap_or_else(std::sync::PoisonError::into_inner);
8439        match thread.as_ref() {
8440          Some(handle) if handle.thread().id() == current_thread => return Ok(()),
8441          Some(handle) if !handle.is_finished() => return Ok(()),
8442          Some(_) => thread.take(),
8443          None => {
8444            // Shutdown can close the heap after the optimistic check above.
8445            // Recheck before publishing another service thread.
8446            if self
8447              .inner
8448              .lock()
8449              .unwrap_or_else(std::sync::PoisonError::into_inner)
8450              .closed
8451            {
8452              return Ok(());
8453            }
8454            #[cfg(test)]
8455            if self.fail_next_start.swap(false, Ordering::SeqCst) {
8456              return Err(RuntimeConfigError(
8457                "injected runtime timer thread creation failure".to_string(),
8458              ));
8459            }
8460
8461            let inner = Arc::clone(&self.inner);
8462            let parker = Arc::clone(&self.parker);
8463            let deadlock_state = Arc::clone(&self.deadlock_state);
8464            let generation = self.generation;
8465            let executor_id = self.executor_id;
8466            let handle = std::thread::Builder::new()
8467              .name(self.thread_name.clone())
8468              .spawn(move || {
8469                // Keep the identity through OS-thread TLS teardown. Unlike
8470                // `ON_POOL_WORKER`, this marker never authorizes scheduler driving.
8471                ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
8472                let _generation = RuntimeGenerationGuard::enter(generation);
8473                timer_timekeeper_main(&inner, &parker, &deadlock_state);
8474              })
8475              .map_err(|error| {
8476                RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
8477              })?;
8478            *thread = Some(handle);
8479            return Ok(());
8480          }
8481        }
8482      };
8483
8484      // A finished timer thread may still be running TLS destructors that
8485      // re-enter timer code. Never hold the handle mutex while waiting for
8486      // those destructors, and never attempt to join this thread from itself.
8487      if let Some(finished) = finished
8488        && finished.thread().id() != current_thread
8489        && let Err(payload) = finished.join()
8490      {
8491        discard_panic_payload(payload);
8492      }
8493      if self
8494        .inner
8495        .lock()
8496        .unwrap_or_else(std::sync::PoisonError::into_inner)
8497        .closed
8498      {
8499        return Ok(());
8500      }
8501    }
8502  }
8503
8504  fn close(&self) -> Vec<Waker> {
8505    let mut inner = self
8506      .inner
8507      .lock()
8508      .unwrap_or_else(std::sync::PoisonError::into_inner);
8509    inner.closed = true;
8510    inner.queue.clear();
8511    inner.timekeeper_parker = None;
8512    inner
8513      .entries
8514      .drain()
8515      .map(|(_, entry)| {
8516        entry.fired.store(true, Ordering::SeqCst);
8517        entry.waker
8518      })
8519      .collect()
8520  }
8521
8522  fn join(&self) {
8523    let handle = self
8524      .thread
8525      .lock()
8526      .unwrap_or_else(std::sync::PoisonError::into_inner)
8527      .take();
8528    if let Some(handle) = handle {
8529      if handle.thread().id() == std::thread::current().id() {
8530        // A custom RawWaker may own the final direct executor Arc and release
8531        // it from this thread. Closing the heap makes the loop exit after the
8532        // callback returns; joining self would deadlock that exit.
8533        drop(handle);
8534        return;
8535      }
8536      if let Err(payload) = handle.join() {
8537        discard_panic_payload(payload);
8538      }
8539    }
8540  }
8541}
8542
8543#[cfg(not(target_family = "wasm"))]
8544impl Drop for TimerHeap {
8545  fn drop(&mut self) {
8546    let mut admission = self.deadlock_state.begin_admission();
8547    let wakers = self.close();
8548    if !wakers.is_empty()
8549      && let Some(admission) = admission.as_mut()
8550    {
8551      admission.mark_published();
8552    }
8553    self.parker.unpark();
8554    for waker in wakers {
8555      wake_waker_contained(waker);
8556    }
8557    drop(admission);
8558    self.join();
8559  }
8560}
8561
8562#[cfg(not(target_family = "wasm"))]
8563fn timer_timekeeper_main(
8564  inner: &Mutex<TimerHeapInner>,
8565  parker: &Arc<DriverParker>,
8566  deadlock_state: &Arc<MultiThreadDeadlockState>,
8567) {
8568  loop {
8569    // Acquire before touching the heap. If a verdict has already closed the
8570    // gate, a due timer remains visible until the verdict's final timer recheck
8571    // causes that guard to reopen.
8572    let mut admission = deadlock_state.begin_admission();
8573    let (due, deadline) = {
8574      let mut inner = inner
8575        .lock()
8576        .unwrap_or_else(std::sync::PoisonError::into_inner);
8577      if inner.closed {
8578        inner.timekeeper_parker = None;
8579        return;
8580      }
8581      let now = Instant::now();
8582      let mut due = Vec::new();
8583      while let Some((deadline, id)) = inner.queue.peek() {
8584        if deadline > now {
8585          break;
8586        }
8587        inner.queue.pop();
8588        if let Some(entry) = inner.entries.remove(&id) {
8589          entry.fired.store(true, Ordering::SeqCst);
8590          due.push(entry.waker);
8591        }
8592      }
8593      let deadline = inner.next_deadline();
8594      inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
8595      (due, deadline)
8596    };
8597
8598    if !due.is_empty() {
8599      if let Some(admission) = admission.as_mut() {
8600        admission.mark_published();
8601      }
8602      for waker in due {
8603        wake_waker_contained(waker);
8604      }
8605      continue;
8606    }
8607    // Never retain an admission while sleeping: it protects only the
8608    // remove-then-wake publication window.
8609    drop(admission);
8610
8611    match deadline {
8612      Some(deadline) => {
8613        let now = Instant::now();
8614        if deadline > now {
8615          parker.park_timeout(deadline - now);
8616        }
8617      }
8618      None => parker.park(),
8619    }
8620  }
8621}
8622
8623#[cfg(not(target_family = "wasm"))]
8624impl MultiThreadExecutor {
8625  /// Arm (or waker-refresh) heap timer `id` at `deadline`. `fired` is the
8626  /// owning `Sleep`'s completion flag: set under the heap lock on fire and on
8627  /// shutdown, and set here immediately when the heap is already closed (the
8628  /// caller re-checks it after registering, so a post-shutdown poll resolves
8629  /// instead of parking forever).
8630  fn register_timer(
8631    self: &Arc<Self>,
8632    id: TimerId,
8633    deadline: Instant,
8634    waker: Waker,
8635    fired: &Arc<AtomicBool>,
8636  ) {
8637    let mut admission = self.begin_deadlock_admission();
8638    #[cfg(test)]
8639    run_timer_registration_admission_test_hook();
8640    // Establish the service thread before publishing the entry. If thread
8641    // creation fails, `Sleep::poll` unwinds with `registered == false` and no
8642    // heap state exists for Drop to miss.
8643    if let Err(error) = self.timers.ensure_started() {
8644      let _ = catch_unwind_contained(|| drop(waker));
8645      panic!("{error}");
8646    }
8647    let (became_earliest, published, discarded_waker) = {
8648      let mut inner = self
8649        .timers
8650        .inner
8651        .lock()
8652        .unwrap_or_else(std::sync::PoisonError::into_inner);
8653      if inner.closed || fired.load(Ordering::SeqCst) {
8654        fired.store(true, Ordering::SeqCst);
8655        (false, false, Some(waker))
8656      } else if let Some(entry) = inner.entries.get_mut(&id) {
8657        // Re-poll of a still-armed sleep: refresh the waker, nothing else.
8658        (
8659          false,
8660          false,
8661          Some(std::mem::replace(&mut entry.waker, waker)),
8662        )
8663      } else {
8664        let previous = inner.next_deadline();
8665        inner.entries.insert(
8666          id,
8667          HeapTimerEntry {
8668            waker,
8669            fired: Arc::clone(fired),
8670          },
8671        );
8672        inner.queue.push(deadline, id);
8673        (
8674          previous.is_none_or(|previous| deadline < previous),
8675          true,
8676          None,
8677        )
8678      }
8679    };
8680    if published && let Some(admission) = admission.as_mut() {
8681      admission.mark_published();
8682    }
8683    if let Some(waker) = discarded_waker {
8684      // RawWaker destruction is user code and may re-enter timer cancellation.
8685      // Keep it outside the heap mutex and contain hostile destructors.
8686      let _ = catch_unwind_contained(|| drop(waker));
8687    }
8688    if became_earliest {
8689      // Re-arm the lazy service thread and any cooperative timer wait.
8690      self.note_earlier_timer();
8691    }
8692  }
8693
8694  /// Best-effort cancel: remove the entry and its indexed heap node.
8695  /// If cancellation changes the earliest deadline, wake every timer-bounded
8696  /// driver so no pool worker remains parked on the stale deadline.
8697  fn cancel_timer(&self, id: TimerId) {
8698    let (removed, earliest_changed) = {
8699      let mut inner = self
8700        .timers
8701        .inner
8702        .lock()
8703        .unwrap_or_else(std::sync::PoisonError::into_inner);
8704      let previous = inner.next_deadline();
8705      let removed = inner.entries.remove(&id);
8706      if removed.is_some() {
8707        let queued = inner.queue.remove(id);
8708        debug_assert!(
8709          queued.is_some(),
8710          "a live timer entry must have one indexed deadline node"
8711        );
8712      }
8713      let current = inner.next_deadline();
8714      (removed, previous != current)
8715    };
8716    if let Some(entry) = removed {
8717      // A RawWaker destructor may cancel another timer. Never run it while
8718      // holding the heap lock.
8719      let _ = catch_unwind_contained(|| drop(entry));
8720    }
8721    if earliest_changed {
8722      self.timers.parker.unpark();
8723      self.parked_drivers.wake_all();
8724    }
8725  }
8726
8727  /// Earliest pending deadline (None when the heap is empty or closed).
8728  fn next_timer_deadline(&self) -> Option<Instant> {
8729    self
8730      .timers
8731      .inner
8732      .lock()
8733      .unwrap_or_else(std::sync::PoisonError::into_inner)
8734      .next_deadline()
8735  }
8736
8737  /// Pop and wake everything due at/before now. Wakers are collected under
8738  /// the lock but woken OUTSIDE it (a wake runs `schedule`, which must not
8739  /// nest inside the heap lock). Firing sets each sleep's `fired` flag under
8740  /// the lock, before the wake, so the woken poll observes it.
8741  fn fire_due_timers(&self) {
8742    let mut admission = self.begin_deadlock_admission();
8743    let due: Vec<Waker> = {
8744      let mut inner = self
8745        .timers
8746        .inner
8747        .lock()
8748        .unwrap_or_else(std::sync::PoisonError::into_inner);
8749      let now = Instant::now();
8750      let mut due = Vec::new();
8751      while let Some((deadline, id)) = inner.queue.peek() {
8752        if deadline > now {
8753          break;
8754        }
8755        inner.queue.pop();
8756        if let Some(entry) = inner.entries.remove(&id) {
8757          entry.fired.store(true, Ordering::SeqCst);
8758          due.push(entry.waker);
8759        }
8760      }
8761      due
8762    };
8763    if !due.is_empty()
8764      && let Some(admission) = admission.as_mut()
8765    {
8766      admission.mark_published();
8767    }
8768    for waker in due {
8769      // Wakers may originate outside this executor. Isolate user RawWaker
8770      // implementations so a panic cannot unwind the dedicated timekeeper.
8771      wake_waker_contained(waker);
8772    }
8773  }
8774
8775  /// The earliest pending deadline moved up (or the heap went non-empty).
8776  /// Re-arm the dedicated timekeeper and one timer-bounded cooperative driver
8777  /// whose current park may still reflect the previous deadline.
8778  fn note_earlier_timer(self: &Arc<Self>) {
8779    self.timers.parker.unpark();
8780    self.parked_drivers.wake_one();
8781  }
8782
8783  /// Drain-fire every pending heap timer and poison later registrations.
8784  /// `shutdown()` with armed timers must not hang a pending `close()`. Fired
8785  /// sleeps resolve on their next poll; the woken tasks are
8786  /// scheduled through the normal path, so a coordinator awaiting a debounce
8787  /// completes its close sequence instead of parking forever.
8788  fn shutdown_timers(&self) {
8789    let mut admission = self.begin_deadlock_admission();
8790    let wakers = self.timers.close();
8791    if !wakers.is_empty()
8792      && let Some(admission) = admission.as_mut()
8793    {
8794      admission.mark_published();
8795    }
8796    for waker in wakers {
8797      // A user-provided RawWaker may panic. Shutdown is a lifecycle
8798      // transition, so one hostile wake must not leave the controller stuck
8799      // in `Stopping` and prevent every later restart.
8800      wake_waker_contained(waker);
8801    }
8802    self.timers.parker.unpark();
8803  }
8804
8805  fn begin_shutdown(&self) {
8806    let _generation = RuntimeGenerationGuard::enter(self.generation);
8807    // Publish stop before taking the blocking queue. The final deadlock verdict
8808    // holds stop publication while rechecking that queue, so queue -> stop
8809    // would invert its lock order. A concurrent taker either claims while stop
8810    // is still false or observes stop after this publication.
8811    self.stop.begin_shutdown();
8812    let queued = {
8813      let mut queue = self
8814        .blocking_queue
8815        .lock()
8816        .unwrap_or_else(std::sync::PoisonError::into_inner);
8817      // The blocking FIFO mutex closes queue admission. `take_blocking` either
8818      // claimed a job before stop publication or observes stop/closure.
8819      queue.closed = true;
8820      queue.take_all()
8821    };
8822    // Captured values belong to user code and may panic in Drop. Retire each
8823    // rejected closure independently so one destructor cannot strand the
8824    // runtime in `Stopping` or prevent the remaining queued jobs from being
8825    // cancelled.
8826    for queued in queued {
8827      let _ = catch_unwind_contained(|| drop(queued));
8828    }
8829    self.shutdown_timers();
8830  }
8831
8832  fn wait_until_scheduler_idle(&self) {
8833    self.timers.join();
8834    let mut idle = self
8835      .scheduler_idle_lock
8836      .lock()
8837      .unwrap_or_else(std::sync::PoisonError::into_inner);
8838    while self.active_drainers.load(Ordering::Acquire) != 0 {
8839      idle = self
8840        .scheduler_idle
8841        .wait(idle)
8842        .unwrap_or_else(std::sync::PoisonError::into_inner);
8843    }
8844  }
8845}
8846
8847/// Timer future returned by [`sleep_until`]. Resolves at/after its deadline
8848/// (or early on runtime shutdown drain-fire). Dropping it cancels the
8849/// underlying registration -- the `tokio::select!` losing-arm semantics the
8850/// watch coordinator's debounce loop relies on.
8851pub struct Sleep {
8852  deadline: Instant,
8853  inner: SleepInner,
8854}
8855
8856enum SleepInner {
8857  #[cfg(not(target_family = "wasm"))]
8858  Heap(HeapSleep),
8859  Host(HostSleep),
8860}
8861
8862#[cfg(not(target_family = "wasm"))]
8863struct HeapSleep {
8864  id: TimerId,
8865  executor: Weak<MultiThreadExecutor>,
8866  fired: Arc<AtomicBool>,
8867  registered: bool,
8868}
8869
8870struct HostSleep {
8871  id: TimerId,
8872  /// Selection source, NOT a pinned driver: each poll snapshots every LIVE
8873  /// registrant. Arming redundant host timers prevents a live but starved
8874  /// environment from owning the sleep's only wake source.
8875  drivers: Arc<TimerDriverRegistry>,
8876  /// Drivers this sleep last armed on. The first responsive fire wakes the
8877  /// sleep; completion cancels every losing arm, including pre-delivery arms
8878  /// whose binding relay retains a cancellation tombstone.
8879  armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
8880  /// The executor's registry, held so the FIRST poll can arm the wake-token
8881  /// at the same moment `driver.register` arms the host timer.
8882  registry: Arc<HostTimerRegistry>,
8883  fired: Arc<AtomicBool>,
8884  /// Stable, coalescing signal used by [`TimerDriverRegistry::register`] to
8885  /// request a re-poll without retaining or invoking a task waker under the
8886  /// driver-registry locks.
8887  repoll: Arc<HostTimerRepoll>,
8888  /// Retires this sleep's wake-token in the executor's registry exactly once
8889  /// (created at first poll alongside the host registration, taken on
8890  /// completion, dropped with the Sleep on cancellation). `None` before the
8891  /// first poll: an unpolled Sleep holds no wake-token.
8892  pending: Option<HostTimerPendingGuard>,
8893}
8894
8895fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
8896  let _ = catch_unwind_contained(|| driver.cancel(id));
8897  let _ = catch_unwind_contained(|| drop(driver));
8898}
8899
8900fn cancel_host_timer_drivers(
8901  id: TimerId,
8902  armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
8903) {
8904  for (_, driver) in armed.drain() {
8905    cancel_host_timer_driver(id, driver);
8906  }
8907}
8908
8909impl Future for Sleep {
8910  type Output = ();
8911
8912  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
8913    let deadline = self.deadline;
8914    let this = self.get_mut();
8915    match &mut this.inner {
8916      #[cfg(not(target_family = "wasm"))]
8917      SleepInner::Heap(heap) => {
8918        if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
8919          if heap.registered {
8920            // Wall-clock completion may beat the (busy) timekeeper's fire:
8921            // retire the registration so it cannot wake spuriously later.
8922            if let Some(executor) = heap.executor.upgrade() {
8923              executor.cancel_timer(heap.id);
8924            }
8925            heap.registered = false;
8926          }
8927          return Poll::Ready(());
8928        }
8929        let Some(executor) = heap.executor.upgrade() else {
8930          heap.fired.store(true, Ordering::SeqCst);
8931          heap.registered = false;
8932          return Poll::Ready(());
8933        };
8934        executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
8935        heap.registered = true;
8936        // Registration on a closed (shut-down) heap stores nothing and sets
8937        // `fired` instead: re-check so this poll resolves rather than parking
8938        // a closing runtime forever.
8939        if heap.fired.load(Ordering::SeqCst) {
8940          heap.registered = false;
8941          return Poll::Ready(());
8942        }
8943        Poll::Pending
8944      }
8945      SleepInner::Host(host) => {
8946        if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
8947          cancel_host_timer_drivers(host.id, &mut host.armed);
8948          host.pending.take();
8949          return Poll::Ready(());
8950        }
8951        // Clone both wakers before publishing either one. If the second clone
8952        // unwinds, destroy the first clone behind its own panic boundary before
8953        // resuming the original panic, so a hostile RawWaker destructor cannot
8954        // turn clone failure into a process-aborting double panic.
8955        let repoll_waker = cx.waker().clone();
8956        let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
8957          Ok(waker) => waker,
8958          Err(payload) => {
8959            let _ = catch_unwind_contained(|| drop(repoll_waker));
8960            resume_unwind(payload);
8961          }
8962        };
8963        host.repoll.register(repoll_waker);
8964        if !host.registry.register(
8965          host.id,
8966          deadline,
8967          registry_waker,
8968          &host.fired,
8969          &host.drivers,
8970          &host.repoll,
8971        ) {
8972          cancel_host_timer_drivers(host.id, &mut host.armed);
8973          if host.pending.take().is_none() {
8974            host.repoll.close();
8975          }
8976          return Poll::Ready(());
8977        }
8978        if host.pending.is_none() {
8979          // FIRST poll: the registry entry appears at the same moment the
8980          // host timer is armed below -- and only then. An unpolled Sleep has
8981          // no host callback behind it, so counting it at creation would
8982          // wrongly veto the threaded deadline verdict (`has_live_wait`) and
8983          // mislabel the threadless certain diagnostic as timer-backed. The
8984          // certain panic itself no longer consults pending timers for its
8985          // verdict.
8986          host.drivers.register_pending(host.id, &host.repoll);
8987          host.pending = Some(HostTimerPendingGuard {
8988            registry: Arc::clone(&host.registry),
8989            drivers: Arc::clone(&host.drivers),
8990            repoll: Arc::clone(&host.repoll),
8991            id: host.id,
8992          });
8993        }
8994        // Re-select every LIVE driver on every poll. A live but starved
8995        // environment may accept a TSFN call and then stop servicing its
8996        // event loop, so no single host may own the only timer arm.
8997        let current = host.drivers.live();
8998        assert!(
8999          !current.as_slice().is_empty(),
9000          "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
9001           that registered timer drivers (via \
9002           `napi_async_runtime::register_timer_driver`; the napi adapter registers \
9003           one per importing env through `registerTimerHost`) have all been torn down, so this \
9004           `sleep_until` can never be woken."
9005        );
9006        // Publish cancellation ownership for the complete live snapshot before
9007        // invoking any arbitrary callback. An earlier driver may unregister a
9008        // later one and then unwind; both the Sleep and the contained snapshot
9009        // must still own that later driver while the original panic propagates.
9010        // See the implementation notes in this crate's README.
9011        let missing = current
9012          .as_slice()
9013          .iter()
9014          .filter(|(current_id, _)| !host.armed.contains_key(current_id))
9015          .count();
9016        host.armed.reserve(missing);
9017        for (current_id, current_driver) in current.as_slice() {
9018          host
9019            .armed
9020            .entry(*current_id)
9021            .or_insert_with(|| Arc::clone(current_driver));
9022        }
9023        for (current_id, _) in current.as_slice() {
9024          let armed_driver = host
9025            .armed
9026            .get(current_id)
9027            .expect("every live timer driver must have cancellation ownership before callbacks");
9028          armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
9029        }
9030        let current_ids = current
9031          .as_slice()
9032          .iter()
9033          .map(|(current_id, _)| *current_id)
9034          .collect::<FxHashSet<_>>();
9035        let retired = host
9036          .armed
9037          .keys()
9038          .filter(|armed_id| !current_ids.contains(armed_id))
9039          .copied()
9040          .collect::<Vec<_>>();
9041        for retired in retired {
9042          let old_driver = host
9043            .armed
9044            .remove(&retired)
9045            .expect("the retired timer driver must remain armed");
9046          cancel_host_timer_driver(host.id, old_driver);
9047        }
9048        Poll::Pending
9049      }
9050    }
9051  }
9052}
9053
9054impl Drop for Sleep {
9055  fn drop(&mut self) {
9056    match &mut self.inner {
9057      #[cfg(not(target_family = "wasm"))]
9058      SleepInner::Heap(heap) => {
9059        if heap.registered
9060          && let Some(executor) = heap.executor.upgrade()
9061        {
9062          executor.cancel_timer(heap.id);
9063        }
9064      }
9065      SleepInner::Host(host) => {
9066        // Close directly as an unwind backstop even when first-poll setup
9067        // failed before `pending` could install its normal cleanup guard.
9068        host.repoll.close();
9069        cancel_host_timer_drivers(host.id, &mut host.armed);
9070        // `pending` (if still held) drops here and retires the wake-token.
9071      }
9072    }
9073  }
9074}
9075
9076/// Sleep until `deadline` on the runtime's timer facility. MultiThread uses
9077/// the executor-owned heap; CurrentThread requires a LIVE host driver
9078/// registered via [`register_timer_driver`] and otherwise fails LOUD (a
9079/// missing driver must never become a silent never-firing debounce).
9080pub fn sleep_until(deadline: Instant) -> Sleep {
9081  make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
9082}
9083
9084/// Flavor dispatch behind [`sleep_until`], parameterized for tests (which
9085/// build executors and driver registries locally and must not touch the
9086/// process-global ones).
9087fn make_sleep(
9088  backend: &RuntimeBackend,
9089  host_drivers: &Arc<TimerDriverRegistry>,
9090  deadline: Instant,
9091) -> Sleep {
9092  let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
9093  match &backend.executor {
9094    #[cfg(not(target_family = "wasm"))]
9095    RuntimeExecutor::MultiThread(executor) => Sleep {
9096      deadline,
9097      inner: SleepInner::Heap(HeapSleep {
9098        id,
9099        executor: Arc::downgrade(executor),
9100        fired: Arc::new(AtomicBool::new(false)),
9101        registered: false,
9102      }),
9103    },
9104    RuntimeExecutor::CurrentThread(executor) => {
9105      // Fail loud at CREATION when no live driver exists (the
9106      // never-registered case gets its diagnostic at the sleep_until call
9107      // site, not on some later poll). The driver is NOT pinned here: each
9108      // poll re-selects every live registrant, so a driver dying while this
9109      // sleep is in flight is cancelled while the remaining hosts stay armed
9110      // -- and if every driver dies mid-flight, the poll path has its own loud
9111      // panic.
9112      assert!(
9113        host_drivers.has_live_driver(),
9114        "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
9115         single-thread flavor delegates timers to the host event loop. Install one via \
9116         `napi_async_runtime::register_timer_driver` (the napi adapter registers \
9117         a setTimeout-based driver per importing env through `registerTimerHost` at import)."
9118      );
9119      // The registry entry is created at FIRST POLL (when `driver.register`
9120      // arms the host timer), not here: an unpolled Sleep cannot wake anyone
9121      // -- nothing was handed to the host loop -- so counting it at creation
9122      // would wrongly veto the threaded deadline verdict and mislabel the
9123      // threadless certain diagnostic as timer-backed. First-poll accounting
9124      // has no false-panic side.
9125      Sleep {
9126        deadline,
9127        inner: SleepInner::Host(HostSleep {
9128          id,
9129          drivers: Arc::clone(host_drivers),
9130          armed: FxHashMap::default(),
9131          registry: Arc::clone(&executor.host_timers),
9132          fired: Arc::new(AtomicBool::new(false)),
9133          repoll: Arc::new(HostTimerRepoll::default()),
9134          pending: None,
9135        }),
9136      }
9137    }
9138  }
9139}
9140
9141#[derive(Clone)]
9142enum RuntimeExecutor {
9143  CurrentThread(Arc<CurrentThreadExecutor>),
9144  #[cfg(not(target_family = "wasm"))]
9145  MultiThread(Arc<MultiThreadExecutor>),
9146}
9147
9148enum WeakRuntimeExecutor {
9149  CurrentThread(Weak<CurrentThreadExecutor>),
9150  #[cfg(not(target_family = "wasm"))]
9151  MultiThread(Weak<MultiThreadExecutor>),
9152}
9153
9154impl WeakRuntimeExecutor {
9155  fn schedule(&self, runnable: Runnable) {
9156    match self {
9157      Self::CurrentThread(executor) => {
9158        if let Some(executor) = executor.upgrade() {
9159          executor.schedule(runnable);
9160        }
9161      }
9162      #[cfg(not(target_family = "wasm"))]
9163      Self::MultiThread(executor) => {
9164        if let Some(executor) = executor.upgrade() {
9165          executor.schedule(runnable);
9166        }
9167      }
9168    }
9169  }
9170}
9171
9172#[derive(Clone)]
9173struct RuntimeBackend {
9174  work: Arc<GenerationWork>,
9175  executor: RuntimeExecutor,
9176}
9177
9178impl RuntimeBackend {
9179  fn new(
9180    options: &RuntimeOptions,
9181    metrics: Arc<RuntimeMetrics>,
9182  ) -> Result<Self, RuntimeConfigError> {
9183    #[cfg(test)]
9184    if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
9185      return Err(RuntimeConfigError(
9186        "injected async runtime backend creation failure".to_string(),
9187      ));
9188    }
9189
9190    let work = GenerationWork::new();
9191    let stop = Arc::new(GenerationStop::default());
9192    let executor = match options.flavor {
9193      RuntimeFlavor::CurrentThread => {
9194        RuntimeExecutor::CurrentThread(Arc::new(CurrentThreadExecutor::with_generation(
9195          metrics,
9196          THREADLESS_BUILD,
9197          options.park_deadline,
9198          work.id,
9199          Arc::clone(&stop),
9200        )))
9201      }
9202      RuntimeFlavor::MultiThread => {
9203        #[cfg(not(target_family = "wasm"))]
9204        {
9205          RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
9206            options, metrics, work.id, stop,
9207          )?))
9208        }
9209        #[cfg(target_family = "wasm")]
9210        {
9211          let _ = metrics;
9212          return Err(RuntimeConfigError(
9213            "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
9214          ));
9215        }
9216      }
9217    };
9218    Ok(Self { work, executor })
9219  }
9220
9221  #[cfg(test)]
9222  fn from_executor(executor: RuntimeExecutor) -> Self {
9223    Self {
9224      work: GenerationWork::new(),
9225      executor,
9226    }
9227  }
9228
9229  fn generation(&self) -> u64 {
9230    self.work.id
9231  }
9232
9233  fn downgrade_executor(&self) -> WeakRuntimeExecutor {
9234    match &self.executor {
9235      RuntimeExecutor::CurrentThread(executor) => {
9236        WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
9237      }
9238      #[cfg(not(target_family = "wasm"))]
9239      RuntimeExecutor::MultiThread(executor) => {
9240        WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
9241      }
9242    }
9243  }
9244
9245  fn schedule(&self, runnable: Runnable) {
9246    match &self.executor {
9247      RuntimeExecutor::CurrentThread(executor) => executor.schedule(runnable),
9248      #[cfg(not(target_family = "wasm"))]
9249      RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
9250    }
9251  }
9252
9253  fn spawn_registered_blocking<F, T>(
9254    &self,
9255    function: F,
9256    registration: GenerationWorkGuard,
9257    metrics: &Arc<RuntimeMetrics>,
9258  ) -> JoinHandle<T>
9259  where
9260    F: FnOnce() -> T + Send + 'static,
9261    T: Send + 'static,
9262  {
9263    match &self.executor {
9264      RuntimeExecutor::CurrentThread(executor) => {
9265        let _ = metrics;
9266        executor.schedule_blocking(function, registration)
9267      }
9268      #[cfg(not(target_family = "wasm"))]
9269      RuntimeExecutor::MultiThread(executor) => {
9270        let generation = self.generation();
9271        let _ = metrics;
9272        let function = RegisteredBlockingFunction::new(function, registration, generation);
9273        executor.schedule_blocking_result(move || function.run())
9274      }
9275    }
9276  }
9277
9278  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
9279    match &self.executor {
9280      RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
9281      #[cfg(not(target_family = "wasm"))]
9282      RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
9283    }
9284  }
9285
9286  fn generation_stop(&self) -> Arc<GenerationStop> {
9287    match &self.executor {
9288      RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
9289      #[cfg(not(target_family = "wasm"))]
9290      RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
9291    }
9292  }
9293
9294  fn begin_shutdown(&self) {
9295    let _generation = RuntimeGenerationGuard::enter(self.generation());
9296    self.work.close_and_abort();
9297    match &self.executor {
9298      RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
9299      #[cfg(not(target_family = "wasm"))]
9300      RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
9301    }
9302  }
9303
9304  fn wait_until_idle(&self) {
9305    self.work.wait_until_idle();
9306    match &self.executor {
9307      RuntimeExecutor::CurrentThread(executor) => executor.wait_until_scheduler_idle(),
9308      #[cfg(not(target_family = "wasm"))]
9309      RuntimeExecutor::MultiThread(executor) => executor.wait_until_scheduler_idle(),
9310    }
9311  }
9312
9313  #[cfg(not(target_family = "wasm"))]
9314  fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
9315    match &self.executor {
9316      RuntimeExecutor::CurrentThread(_) => None,
9317      RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
9318    }
9319  }
9320
9321  fn stop_identity(&self) -> RuntimeStopIdentity {
9322    RuntimeStopIdentity {
9323      generation: self.generation(),
9324      #[cfg(not(target_family = "wasm"))]
9325      executor_id: match &self.executor {
9326        RuntimeExecutor::CurrentThread(_) => None,
9327        RuntimeExecutor::MultiThread(executor) => Some(executor.id),
9328      },
9329    }
9330  }
9331
9332  fn is_current(&self) -> bool {
9333    self.stop_identity().is_current()
9334  }
9335}
9336
9337#[derive(Clone, Copy)]
9338struct RuntimeStopIdentity {
9339  generation: u64,
9340  #[cfg(not(target_family = "wasm"))]
9341  executor_id: Option<u64>,
9342}
9343
9344impl RuntimeStopIdentity {
9345  fn is_current(self) -> bool {
9346    if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
9347      return true;
9348    }
9349    #[cfg(not(target_family = "wasm"))]
9350    if let Some(executor_id) = self.executor_id
9351      && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
9352        || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
9353    {
9354      return true;
9355    }
9356    false
9357  }
9358}
9359
9360enum RuntimeLifecycle {
9361  Initial,
9362  StoppedBeforeFirstUse,
9363  Running(RuntimeBackend),
9364  Stopping(RuntimeStopIdentity),
9365  StoppingWithoutBackend(ZeroBackendShutdownTarget),
9366  Stopped,
9367}
9368
9369#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9370enum ZeroBackendShutdownTarget {
9371  StoppedBeforeFirstUse,
9372  Stopped,
9373}
9374
9375impl ZeroBackendShutdownTarget {
9376  fn into_lifecycle(self) -> RuntimeLifecycle {
9377    match self {
9378      Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
9379      Self::Stopped => RuntimeLifecycle::Stopped,
9380    }
9381  }
9382}
9383
9384struct RuntimeState {
9385  options: RuntimeOptions,
9386  lifecycle: RuntimeLifecycle,
9387  rejected_drops: usize,
9388}
9389
9390#[cfg(test)]
9391thread_local! {
9392  static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
9393    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9394  static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
9395    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9396  static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
9397    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9398  static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
9399    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9400  static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
9401    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9402  static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
9403    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9404  static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
9405    std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
9406}
9407
9408#[cfg(test)]
9409fn run_before_runtime_submission_lock_test_hook() {
9410  if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9411  {
9412    hook();
9413  }
9414}
9415
9416#[cfg(test)]
9417fn run_after_async_work_registration_test_hook() {
9418  if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9419  {
9420    hook();
9421  }
9422}
9423
9424#[cfg(test)]
9425fn run_after_blocking_work_registration_test_hook() {
9426  if let Some(hook) =
9427    AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9428  {
9429    hook();
9430  }
9431}
9432
9433#[cfg(test)]
9434fn run_after_current_thread_host_admission_test_hook() {
9435  if let Some(hook) =
9436    AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9437  {
9438    hook();
9439  }
9440}
9441
9442#[cfg(test)]
9443fn run_before_initial_start_wait_test_hook() {
9444  if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
9445    hook();
9446  }
9447}
9448
9449#[cfg(test)]
9450fn run_after_generation_stop_publication_test_hook() {
9451  if let Some(hook) =
9452    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9453  {
9454    hook();
9455  }
9456}
9457
9458thread_local! {
9459  static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
9460    const { std::cell::Cell::new(0) };
9461}
9462
9463struct RejectedSubmissionDropContext;
9464
9465impl RejectedSubmissionDropContext {
9466  fn enter() -> Self {
9467    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
9468    Self
9469  }
9470
9471  fn is_current() -> bool {
9472    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
9473  }
9474}
9475
9476impl Drop for RejectedSubmissionDropContext {
9477  fn drop(&mut self) {
9478    IN_REJECTED_SUBMISSION_DROP.with(|depth| {
9479      depth.set(
9480        depth
9481          .get()
9482          .checked_sub(1)
9483          .expect("rejected submission drop depth underflow"),
9484      );
9485    });
9486  }
9487}
9488
9489struct RuntimeController {
9490  state: Mutex<RuntimeState>,
9491  lifecycle_changed: Condvar,
9492  metrics: Arc<RuntimeMetrics>,
9493}
9494
9495struct RejectedSubmissionGuard<'a> {
9496  controller: &'a RuntimeController,
9497}
9498
9499impl Drop for RejectedSubmissionGuard<'_> {
9500  fn drop(&mut self) {
9501    let mut state = self
9502      .controller
9503      .state
9504      .lock()
9505      .unwrap_or_else(std::sync::PoisonError::into_inner);
9506    state.rejected_drops = state
9507      .rejected_drops
9508      .checked_sub(1)
9509      .expect("rejected submission drop count underflow");
9510    self.controller.lifecycle_changed.notify_all();
9511  }
9512}
9513
9514impl RuntimeController {
9515  fn new() -> Self {
9516    let options = RuntimeOptions::default()
9517      .validate()
9518      .expect("default async runtime options must be valid");
9519    Self {
9520      state: Mutex::new(RuntimeState {
9521        options,
9522        lifecycle: RuntimeLifecycle::Initial,
9523        rejected_drops: 0,
9524      }),
9525      lifecycle_changed: Condvar::new(),
9526      metrics: Arc::new(RuntimeMetrics::default()),
9527    }
9528  }
9529
9530  fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
9531    let options = options.validate()?;
9532    let mut state = self
9533      .state
9534      .lock()
9535      .unwrap_or_else(std::sync::PoisonError::into_inner);
9536    Self::ensure_configuration_mutable(&state)?;
9537    state.options = options;
9538    Ok(())
9539  }
9540
9541  fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
9542    self.configure_partial_inner(patch, |_| {})
9543  }
9544
9545  fn configure_partial_inner(
9546    &self,
9547    patch: RuntimeOptionsPatch,
9548    after_merge: impl FnOnce(&RuntimeOptions),
9549  ) -> Result<(), RuntimeConfigError> {
9550    let mut state = self
9551      .state
9552      .lock()
9553      .unwrap_or_else(std::sync::PoisonError::into_inner);
9554    Self::ensure_configuration_mutable(&state)?;
9555
9556    let mut options = state.options.clone();
9557    patch.apply_to(&mut options);
9558    after_merge(&options);
9559    let options = options.validate()?;
9560    state.options = options;
9561    Ok(())
9562  }
9563
9564  fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
9565    if matches!(
9566      &state.lifecycle,
9567      RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
9568    ) {
9569      return Ok(());
9570    }
9571    Err(RuntimeConfigError(
9572      "the async runtime configuration is frozen; configure it before the first async call"
9573        .to_string(),
9574    ))
9575  }
9576
9577  fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
9578    let Some(active_generation) = active_runtime_generation() else {
9579      return Ok(());
9580    };
9581    let lifecycle_generation = match &state.lifecycle {
9582      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
9583      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
9584      RuntimeLifecycle::Initial
9585      | RuntimeLifecycle::StoppedBeforeFirstUse
9586      | RuntimeLifecycle::StoppingWithoutBackend(_)
9587      | RuntimeLifecycle::Stopped => None,
9588    };
9589    if lifecycle_generation == Some(active_generation) {
9590      return Ok(());
9591    }
9592    Err(RuntimeConfigError(
9593      "cannot access or control the async runtime from work owned by a retired generation"
9594        .to_string(),
9595    ))
9596  }
9597
9598  fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
9599    Self::ensure_active_generation_current(state)?;
9600    match &state.lifecycle {
9601      RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
9602      RuntimeLifecycle::StoppedBeforeFirstUse
9603      | RuntimeLifecycle::Stopping(_)
9604      | RuntimeLifecycle::StoppingWithoutBackend(_)
9605      | RuntimeLifecycle::Stopped => {
9606        return Err(RuntimeConfigError(
9607          "the async runtime is stopped; call start before submitting work".to_string(),
9608        ));
9609      }
9610      RuntimeLifecycle::Initial => {}
9611    }
9612    if state.rejected_drops != 0 {
9613      return Err(RuntimeConfigError(
9614        "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
9615      ));
9616    }
9617    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
9618    state.lifecycle = RuntimeLifecycle::Running(backend.clone());
9619    Ok(backend)
9620  }
9621
9622  fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
9623    let mut state = self
9624      .state
9625      .lock()
9626      .unwrap_or_else(std::sync::PoisonError::into_inner);
9627    self.backend_locked(&mut state)
9628  }
9629
9630  fn backend(&self) -> RuntimeBackend {
9631    self.try_backend().unwrap_or_else(|error| panic!("{error}"))
9632  }
9633
9634  fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
9635    let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
9636      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
9637      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
9638      RuntimeLifecycle::Initial
9639      | RuntimeLifecycle::StoppedBeforeFirstUse
9640      | RuntimeLifecycle::StoppingWithoutBackend(_)
9641      | RuntimeLifecycle::Stopped => None,
9642    });
9643    state.rejected_drops += 1;
9644    generation
9645  }
9646
9647  fn try_spawn_tracked<F, T>(
9648    &self,
9649    future: F,
9650  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
9651  where
9652    F: Future<Output = T> + Send + 'static,
9653    T: Send + 'static,
9654  {
9655    #[cfg(test)]
9656    run_before_runtime_submission_lock_test_hook();
9657
9658    let (backend, registration) = {
9659      let mut state = self
9660        .state
9661        .lock()
9662        .unwrap_or_else(std::sync::PoisonError::into_inner);
9663      let backend = match self.backend_locked(&mut state) {
9664        Ok(backend) => backend,
9665        Err(error) => {
9666          let generation = Self::begin_rejected_drop(&mut state);
9667          return Err((error, future, generation));
9668        }
9669      };
9670      let Some(registration) = backend.work.try_register_async() else {
9671        let generation = Self::begin_rejected_drop(&mut state);
9672        return Err((
9673          RuntimeConfigError(
9674            "the async runtime is stopped; call start before submitting work".to_string(),
9675          ),
9676          future,
9677          generation,
9678        ));
9679      };
9680      (backend, registration)
9681    };
9682    #[cfg(test)]
9683    run_after_async_work_registration_test_hook();
9684    Ok(spawn_registered(
9685      &backend,
9686      Arc::clone(&self.metrics),
9687      future,
9688      registration,
9689    ))
9690  }
9691
9692  fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
9693  where
9694    F: Future<Output = T> + Send + 'static,
9695    T: Send + 'static,
9696  {
9697    match self.try_spawn_tracked(future) {
9698      Ok(handle) => Ok(handle),
9699      Err((error, future, _generation)) => {
9700        drop(RejectedSubmissionGuard { controller: self });
9701        Err((error, future))
9702      }
9703    }
9704  }
9705
9706  fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
9707  where
9708    F: Future<Output = T> + Send + 'static,
9709    T: Send + 'static,
9710  {
9711    match self.try_spawn_tracked(future) {
9712      Ok(handle) => handle,
9713      Err((error, future, generation)) => {
9714        let _rejection = RejectedSubmissionGuard { controller: self };
9715        let _drop_context = RejectedSubmissionDropContext::enter();
9716        let _generation = generation.map(RuntimeGenerationGuard::enter);
9717        let _ = catch_unwind_contained(|| drop(future));
9718        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
9719          message: error.to_string(),
9720        }))))
9721      }
9722    }
9723  }
9724
9725  fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
9726  where
9727    F: Future<Output = ()> + Send + 'static,
9728  {
9729    match self.try_spawn(future) {
9730      Ok(handle) => {
9731        handle.detach();
9732        Ok(())
9733      }
9734      Err((_, future)) => Err(future),
9735    }
9736  }
9737
9738  fn try_spawn_blocking_tracked<F, T>(
9739    &self,
9740    function: F,
9741  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
9742  where
9743    F: FnOnce() -> T + Send + 'static,
9744    T: Send + 'static,
9745  {
9746    #[cfg(test)]
9747    run_before_runtime_submission_lock_test_hook();
9748
9749    let (backend, registration) = {
9750      let mut state = self
9751        .state
9752        .lock()
9753        .unwrap_or_else(std::sync::PoisonError::into_inner);
9754      let backend = match self.backend_locked(&mut state) {
9755        Ok(backend) => backend,
9756        Err(error) => {
9757          let generation = Self::begin_rejected_drop(&mut state);
9758          return Err((error, function, generation));
9759        }
9760      };
9761      let Some(registration) = backend.work.try_register_work() else {
9762        let generation = Self::begin_rejected_drop(&mut state);
9763        return Err((
9764          RuntimeConfigError(
9765            "the async runtime is stopped; call start before submitting work".to_string(),
9766          ),
9767          function,
9768          generation,
9769        ));
9770      };
9771      (backend, registration)
9772    };
9773    #[cfg(test)]
9774    run_after_blocking_work_registration_test_hook();
9775    Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
9776  }
9777
9778  fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
9779  where
9780    F: FnOnce() -> T + Send + 'static,
9781    T: Send + 'static,
9782  {
9783    match self.try_spawn_blocking_tracked(function) {
9784      Ok(handle) => Ok(handle),
9785      Err((error, function, _generation)) => {
9786        drop(RejectedSubmissionGuard { controller: self });
9787        Err((error, function))
9788      }
9789    }
9790  }
9791
9792  fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
9793  where
9794    F: FnOnce() -> T + Send + 'static,
9795    T: Send + 'static,
9796  {
9797    match self.try_spawn_blocking_tracked(function) {
9798      Ok(handle) => handle,
9799      Err((error, function, generation)) => {
9800        let _rejection = RejectedSubmissionGuard { controller: self };
9801        let _drop_context = RejectedSubmissionDropContext::enter();
9802        let _generation = generation.map(RuntimeGenerationGuard::enter);
9803        let _ = catch_unwind_contained(|| drop(function));
9804        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
9805          message: error.to_string(),
9806        }))))
9807      }
9808    }
9809  }
9810
9811  #[cfg(test)]
9812  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
9813    self
9814      .try_block_on(future)
9815      .unwrap_or_else(|error| panic!("{error}"))
9816  }
9817
9818  fn try_block_on_scope(
9819    &self,
9820  ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
9821    let mut state = self
9822      .state
9823      .lock()
9824      .unwrap_or_else(std::sync::PoisonError::into_inner);
9825    let backend = match self.backend_locked(&mut state) {
9826      Ok(backend) => backend,
9827      Err(error) => {
9828        let generation = Self::begin_rejected_drop(&mut state);
9829        return Err((error, generation));
9830      }
9831    };
9832    let Some(registration) = backend.work.try_register_work() else {
9833      let generation = Self::begin_rejected_drop(&mut state);
9834      return Err((
9835        RuntimeConfigError(
9836          "the async runtime is stopped; call start before submitting work".to_string(),
9837        ),
9838        generation,
9839      ));
9840    };
9841    Ok((backend, registration))
9842  }
9843
9844  fn try_block_on(
9845    &self,
9846    future: Pin<&mut dyn Future<Output = ()>>,
9847  ) -> Result<BlockOnOutcome, RuntimeConfigError> {
9848    let (backend, registration) = match self.try_block_on_scope() {
9849      Ok(scope) => scope,
9850      Err((error, _generation)) => {
9851        drop(RejectedSubmissionGuard { controller: self });
9852        return Err(error);
9853      }
9854    };
9855    let _registration = registration;
9856    let _generation = RuntimeGenerationGuard::enter(backend.generation());
9857    Ok(backend.block_on(future))
9858  }
9859
9860  fn start(&self) -> Result<(), RuntimeConfigError> {
9861    let mut state = self
9862      .state
9863      .lock()
9864      .unwrap_or_else(std::sync::PoisonError::into_inner);
9865    loop {
9866      Self::ensure_active_generation_current(&state)?;
9867      match &state.lifecycle {
9868        // napi starts the runtime while the addon is still being registered.
9869        // Keep the first backend lazy so configureAsyncRuntime remains usable
9870        // until the first async binding call.
9871        RuntimeLifecycle::Initial => {
9872          if state.rejected_drops == 0 {
9873            return Ok(());
9874          }
9875          if RejectedSubmissionDropContext::is_current() {
9876            return Err(RuntimeConfigError(
9877              "cannot start the async runtime while a rejected submission is being destroyed"
9878                .to_string(),
9879            ));
9880          }
9881          #[cfg(test)]
9882          run_before_initial_start_wait_test_hook();
9883          state = self
9884            .lifecycle_changed
9885            .wait(state)
9886            .unwrap_or_else(std::sync::PoisonError::into_inner);
9887        }
9888        RuntimeLifecycle::Running(_) => return Ok(()),
9889        RuntimeLifecycle::Stopping(identity) => {
9890          if identity.is_current() {
9891            return Err(RuntimeConfigError(
9892              "cannot restart the async runtime from work in the generation being stopped"
9893                .to_string(),
9894            ));
9895          }
9896          state = self
9897            .lifecycle_changed
9898            .wait(state)
9899            .unwrap_or_else(std::sync::PoisonError::into_inner);
9900        }
9901        RuntimeLifecycle::StoppingWithoutBackend(_) => {
9902          if RejectedSubmissionDropContext::is_current() {
9903            return Err(RuntimeConfigError(
9904              "cannot restart the async runtime while a rejected submission is being destroyed"
9905                .to_string(),
9906            ));
9907          }
9908          state = self
9909            .lifecycle_changed
9910            .wait(state)
9911            .unwrap_or_else(std::sync::PoisonError::into_inner);
9912        }
9913        RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
9914          if state.rejected_drops == 0 {
9915            if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
9916              state.lifecycle = RuntimeLifecycle::Initial;
9917              return Ok(());
9918            }
9919            break;
9920          }
9921          if RejectedSubmissionDropContext::is_current() {
9922            return Err(RuntimeConfigError(
9923              "cannot restart the async runtime while a rejected submission is being destroyed"
9924                .to_string(),
9925            ));
9926          }
9927          state = self
9928            .lifecycle_changed
9929            .wait(state)
9930            .unwrap_or_else(std::sync::PoisonError::into_inner);
9931        }
9932      }
9933    }
9934    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
9935    state.lifecycle = RuntimeLifecycle::Running(backend);
9936    Ok(())
9937  }
9938
9939  fn options(&self) -> RuntimeOptions {
9940    self
9941      .state
9942      .lock()
9943      .unwrap_or_else(std::sync::PoisonError::into_inner)
9944      .options
9945      .clone()
9946  }
9947
9948  fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
9949    let state = self
9950      .state
9951      .lock()
9952      .unwrap_or_else(std::sync::PoisonError::into_inner);
9953    let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
9954      return None;
9955    };
9956    match &backend.executor {
9957      RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
9958      #[cfg(not(target_family = "wasm"))]
9959      RuntimeExecutor::MultiThread(_) => None,
9960    }
9961  }
9962
9963  fn request_current_thread_drain(&self) {
9964    if let Some(executor) = self.running_current_thread_executor() {
9965      executor.request_drain_if_queued();
9966    }
9967  }
9968
9969  fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
9970    let executor = {
9971      let state = self
9972        .state
9973        .lock()
9974        .unwrap_or_else(std::sync::PoisonError::into_inner);
9975      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
9976        return;
9977      };
9978      match &backend.executor {
9979        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
9980        #[cfg(not(target_family = "wasm"))]
9981        RuntimeExecutor::MultiThread(_) => return,
9982      }
9983    };
9984    executor.cancel_host_dispatch(failure);
9985  }
9986
9987  #[cfg(test)]
9988  fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
9989    let executor = {
9990      let state = self
9991        .state
9992        .lock()
9993        .unwrap_or_else(std::sync::PoisonError::into_inner);
9994      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
9995        return;
9996      };
9997      match &backend.executor {
9998        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
9999        #[cfg(not(target_family = "wasm"))]
10000        RuntimeExecutor::MultiThread(_) => return,
10001      }
10002    };
10003    executor.cancel_host_dispatch_for_test(dispatch);
10004  }
10005
10006  fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
10007    let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
10008    self.drive_current_thread_dispatch_with_lease(dispatch, true)
10009  }
10010
10011  fn drive_current_thread_dispatch_with_lease(
10012    &self,
10013    dispatch: u64,
10014    mark_serviced: bool,
10015  ) -> Option<CurrentThreadTaskCallbackLease> {
10016    let mut host_turn = {
10017      let state = self
10018        .state
10019        .lock()
10020        .unwrap_or_else(std::sync::PoisonError::into_inner);
10021      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10022        return None;
10023      };
10024      let executor = match &backend.executor {
10025        RuntimeExecutor::CurrentThread(executor) => executor,
10026        #[cfg(not(target_family = "wasm"))]
10027        RuntimeExecutor::MultiThread(_) => return None,
10028      };
10029      let host_turn = executor.try_admit_host_turn(dispatch)?;
10030      if mark_serviced {
10031        CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
10032      }
10033      host_turn
10034    };
10035    #[cfg(test)]
10036    run_after_current_thread_host_admission_test_hook();
10037    host_turn.drive();
10038    Some(CurrentThreadTaskCallbackLease::new(host_turn))
10039  }
10040
10041  #[cfg(test)]
10042  fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
10043    self
10044      .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
10045      .is_some()
10046  }
10047
10048  fn shutdown(&self) -> Result<(), RuntimeConfigError> {
10049    let backend = {
10050      let mut state = self
10051        .state
10052        .lock()
10053        .unwrap_or_else(std::sync::PoisonError::into_inner);
10054      loop {
10055        Self::ensure_active_generation_current(&state)?;
10056        match &state.lifecycle {
10057          RuntimeLifecycle::Initial => {
10058            if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
10059              return Err(RuntimeConfigError(
10060                "cannot shut down the async runtime while a rejected submission is being destroyed"
10061                  .to_string(),
10062              ));
10063            }
10064            let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
10065            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10066            self.lifecycle_changed.notify_all();
10067            while state.rejected_drops != 0 {
10068              state = self
10069                .lifecycle_changed
10070                .wait(state)
10071                .unwrap_or_else(std::sync::PoisonError::into_inner);
10072            }
10073            debug_assert!(matches!(
10074              state.lifecycle,
10075              RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10076            ));
10077            state.lifecycle = target.into_lifecycle();
10078            self.lifecycle_changed.notify_all();
10079            return Ok(());
10080          }
10081          RuntimeLifecycle::Running(backend) => {
10082            if backend.is_current() {
10083              return Err(RuntimeConfigError(
10084                "cannot shut down the async runtime from work running on that runtime".to_string(),
10085              ));
10086            }
10087            let identity = backend.stop_identity();
10088            let stop = backend.generation_stop();
10089            // Lock order is lifecycle -> stop publication. Publish before
10090            // mutating the lifecycle so a gated verdict that acquired the
10091            // publication mutex first wins while this state remains Running;
10092            // otherwise the verdict must observe the published stop.
10093            let stop_publication = stop.publication_guard();
10094            stop_publication.publish_stopping();
10095            let RuntimeLifecycle::Running(backend) =
10096              std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
10097            else {
10098              unreachable!();
10099            };
10100            // Release before aborting tasks or running any user destruction.
10101            // Neither path may retain the publication mutex across scheduler
10102            // admission, lifecycle reentry, or a panic boundary.
10103            drop(stop_publication);
10104            break backend;
10105          }
10106          RuntimeLifecycle::Stopping(identity) => {
10107            if identity.is_current() {
10108              return Err(RuntimeConfigError(
10109                "cannot wait for async runtime shutdown from work in the generation being stopped"
10110                  .to_string(),
10111              ));
10112            }
10113            state = self
10114              .lifecycle_changed
10115              .wait(state)
10116              .unwrap_or_else(std::sync::PoisonError::into_inner);
10117          }
10118          RuntimeLifecycle::StoppingWithoutBackend(_) => {
10119            if RejectedSubmissionDropContext::is_current() {
10120              return Err(RuntimeConfigError(
10121                "cannot shut down the async runtime while a rejected submission is being destroyed"
10122                  .to_string(),
10123              ));
10124            }
10125            state = self
10126              .lifecycle_changed
10127              .wait(state)
10128              .unwrap_or_else(std::sync::PoisonError::into_inner);
10129          }
10130          RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10131            if state.rejected_drops == 0 {
10132              return Ok(());
10133            }
10134            if RejectedSubmissionDropContext::is_current() {
10135              return Err(RuntimeConfigError(
10136                "cannot shut down the async runtime while a rejected submission is being destroyed"
10137                  .to_string(),
10138              ));
10139            }
10140            let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10141              ZeroBackendShutdownTarget::StoppedBeforeFirstUse
10142            } else {
10143              ZeroBackendShutdownTarget::Stopped
10144            };
10145            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10146            self.lifecycle_changed.notify_all();
10147            while state.rejected_drops != 0 {
10148              state = self
10149                .lifecycle_changed
10150                .wait(state)
10151                .unwrap_or_else(std::sync::PoisonError::into_inner);
10152            }
10153            debug_assert!(matches!(
10154              state.lifecycle,
10155              RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10156            ));
10157            state.lifecycle = target.into_lifecycle();
10158            self.lifecycle_changed.notify_all();
10159            return Ok(());
10160          }
10161        }
10162      }
10163    };
10164
10165    #[cfg(test)]
10166    run_after_generation_stop_publication_test_hook();
10167    backend.begin_shutdown();
10168    backend.wait_until_idle();
10169
10170    #[cfg(not(target_family = "wasm"))]
10171    let worker_lifecycle = backend.worker_lifecycle();
10172    drop(backend);
10173
10174    #[cfg(not(target_family = "wasm"))]
10175    if let Some(worker_lifecycle) = worker_lifecycle {
10176      worker_lifecycle.wait_for_all_workers();
10177    }
10178
10179    let mut state = self
10180      .state
10181      .lock()
10182      .unwrap_or_else(std::sync::PoisonError::into_inner);
10183    while state.rejected_drops != 0 {
10184      state = self
10185        .lifecycle_changed
10186        .wait(state)
10187        .unwrap_or_else(std::sync::PoisonError::into_inner);
10188    }
10189    debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
10190    state.lifecycle = RuntimeLifecycle::Stopped;
10191    self.lifecycle_changed.notify_all();
10192    Ok(())
10193  }
10194}
10195
10196static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
10197
10198pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10199  RUNTIME.configure(options)
10200}
10201
10202pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10203  RUNTIME.configure_partial(patch)
10204}
10205
10206pub fn configured_options() -> RuntimeOptions {
10207  RUNTIME.options()
10208}
10209
10210pub fn is_multi_threaded() -> bool {
10211  RUNTIME.options().flavor == RuntimeFlavor::MultiThread
10212}
10213
10214fn spawn_registered<F, T>(
10215  backend: &RuntimeBackend,
10216  metrics: Arc<RuntimeMetrics>,
10217  future: F,
10218  registration: (AbortRegistration, GenerationWorkGuard),
10219) -> JoinHandle<T>
10220where
10221  F: Future<Output = T> + Send + 'static,
10222  T: Send + 'static,
10223{
10224  metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
10225  let generation = backend.generation();
10226  let (abort_registration, work_registration) = registration;
10227  #[cfg(not(target_family = "wasm"))]
10228  let dependency = Arc::new(TaskDependency::new_task(generation));
10229  #[cfg(not(target_family = "wasm"))]
10230  let task_dependency = Arc::clone(&dependency);
10231  let wrapped = async move {
10232    let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
10233    let mut abortable = std::pin::pin!(abortable);
10234    let polled = futures::future::poll_fn(|cx| {
10235      #[cfg(not(target_family = "wasm"))]
10236      task_dependency.begin_poll();
10237      #[cfg(not(target_family = "wasm"))]
10238      let _dependency = TaskDependencyGuard::enter(&task_dependency);
10239      abortable.as_mut().poll(cx)
10240    });
10241    match AssertUnwindSafe(polled).catch_unwind().await {
10242      Ok(Ok(output)) => {
10243        metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
10244        Ok(ContainedTaskOutput::new(output, generation))
10245      }
10246      Ok(Err(_)) => Err(JoinError {
10247        message: "async runtime stopped before the task completed".to_string(),
10248      }),
10249      Err(panic) => {
10250        metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
10251        Err(panic_payload_to_join_error(panic))
10252      }
10253    }
10254  };
10255  let scheduler = backend.downgrade_executor();
10256  // `async-task` aborts the process if its future's destructor unwinds. Keep
10257  // the entire task generator behind our manually-dropped containment wrapper,
10258  // including the never-polled case where `future` is still a generator field.
10259  let (runnable, task) = async_task::spawn(
10260    RegisteredTaskFuture::new(wrapped, work_registration, generation),
10261    move |runnable| {
10262      scheduler.schedule(runnable);
10263    },
10264  );
10265  backend.schedule(runnable);
10266  JoinHandle(JoinHandleInner::Task {
10267    task: task.fallible(),
10268    awaiter: None,
10269    #[cfg(not(target_family = "wasm"))]
10270    dependency,
10271  })
10272}
10273
10274pub fn spawn<F, T>(future: F) -> JoinHandle<T>
10275where
10276  F: Future<Output = T> + Send + 'static,
10277  T: Send + 'static,
10278{
10279  RUNTIME.spawn(future)
10280}
10281
10282/// Submit a future without consuming it when the runtime is not accepting work.
10283///
10284/// Lifecycle owners can retain the returned future and retry it after
10285/// [`start`] completes a runtime restart.
10286pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10287where
10288  F: Future<Output = T> + Send + 'static,
10289  T: Send + 'static,
10290{
10291  RUNTIME.try_spawn(future)
10292}
10293
10294pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
10295where
10296  F: Future<Output = ()> + Send + 'static,
10297{
10298  RUNTIME.try_spawn_detached(future)
10299}
10300
10301pub fn spawn_detached<F>(future: F)
10302where
10303  F: Future<Output = ()> + Send + 'static,
10304{
10305  spawn(future).detach();
10306}
10307
10308pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
10309where
10310  F: FnOnce() -> T + Send + 'static,
10311  T: Send + 'static,
10312{
10313  RUNTIME.spawn_blocking(function)
10314}
10315
10316/// Submit blocking work without consuming it when the runtime is not accepting work.
10317///
10318/// Lifecycle owners can retain the returned closure and retry it after [`start`]
10319/// completes a runtime restart.
10320pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10321where
10322  F: FnOnce() -> T + Send + 'static,
10323  T: Send + 'static,
10324{
10325  RUNTIME.try_spawn_blocking(function)
10326}
10327
10328fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
10329  let (backend, registration) = match controller.try_block_on_scope() {
10330    Ok(scope) => scope,
10331    Err((error, generation)) => {
10332      let _rejection = RejectedSubmissionGuard { controller };
10333      let _drop_context = RejectedSubmissionDropContext::enter();
10334      let _generation = generation.map(RuntimeGenerationGuard::enter);
10335      let _ = catch_unwind_contained(|| drop(future));
10336      panic!("{error}");
10337    }
10338  };
10339  let generation = backend.generation();
10340  let _registration = registration;
10341  let _generation = RuntimeGenerationGuard::enter(generation);
10342  let mut output = None;
10343  {
10344    let future = ContainedFuture::new(future, generation);
10345    let mut erased = std::pin::pin!(async {
10346      output = Some(future.await);
10347    });
10348    let _ = backend.block_on(erased.as_mut());
10349    // `erased` is destroyed before this scope exits, while the controller's
10350    // generation work and identity guards are live. `ContainedFuture` keeps a
10351    // poll panic from becoming a process-aborting double panic if the owned
10352    // future's destructor also unwinds.
10353  }
10354  output.expect("async runtime returned before the future completed")
10355}
10356
10357pub fn block_on<F: Future>(future: F) -> F::Output {
10358  block_on_with_controller(&RUNTIME, future)
10359}
10360
10361pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
10362  try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
10363}
10364
10365/// Drive a borrowed future without consuming it when admission or shutdown fails.
10366pub fn try_block_on_dyn(
10367  future: Pin<&mut dyn Future<Output = ()>>,
10368) -> Result<(), RuntimeConfigError> {
10369  match RUNTIME.try_block_on(future)? {
10370    BlockOnOutcome::Completed => Ok(()),
10371    BlockOnOutcome::Stopped => Err(RuntimeConfigError(
10372      "the async runtime stopped before block_on completed its future".to_string(),
10373    )),
10374  }
10375}
10376
10377pub fn start() -> Result<(), RuntimeConfigError> {
10378  RUNTIME.start()
10379}
10380
10381pub fn shutdown() -> Result<(), RuntimeConfigError> {
10382  RUNTIME.shutdown()
10383}
10384
10385fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
10386  for failure in completion.failed_dispatches {
10387    RUNTIME.cancel_current_thread_dispatch(failure);
10388  }
10389  if completion.redispatch {
10390    RUNTIME.request_current_thread_drain();
10391  }
10392}
10393
10394/// Acknowledge that one exact host delivery callback returned the successful
10395/// claim result from [`drive_current_thread_tasks`].
10396///
10397/// This releases the host's single-flight slot and submits at most its latest
10398/// still-needed pending delivery. The registry verifies that this exact
10399/// delivery was claimed; a forged success for an unclaimed delivery is treated
10400/// as failure. A stale acknowledgement from an unregistered host or retired
10401/// attempt is a no-op.
10402pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10403  complete_current_thread_task_delivery(
10404    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
10405  );
10406}
10407
10408/// Report that one exact host delivery callback failed.
10409///
10410/// Recovery begins only after no other live host attempt can still service the
10411/// same executor dispatch. The registration and delivery identities prevent a
10412/// stale failure from affecting another host or a re-registered generation.
10413pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10414  complete_current_thread_task_delivery(
10415    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
10416  );
10417}
10418
10419/// Cancel an accepted CurrentThread host delivery that failed before it could
10420/// schedule a fresh turn.
10421///
10422/// `delivery` is the opaque registration-scoped driver capability,
10423/// not the executor's shared dispatch capability. Repeated or stale failures
10424/// are no-ops.
10425pub fn cancel_current_thread_task_dispatch(delivery: u64) {
10426  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
10427}
10428
10429/// Poll the shared runtime's CurrentThread queue from a host-dispatched turn.
10430///
10431/// Embedders normally call this from their [`CurrentThreadTaskDriver`]
10432/// implementation with the opaque [`CurrentThreadTaskDelivery::capability`].
10433/// Returns a callback lease only when this exact delivery was claimed, admitted,
10434/// and driven. It returns `None` for stale/unregistered deliveries, before
10435/// backend creation, and on MultiThread. The host must retain the lease through
10436/// delivery acknowledgement/failure and callback payload destruction.
10437pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
10438  RUNTIME.drive_current_thread_tasks(delivery)
10439}
10440
10441pub fn reset_metrics() {
10442  RUNTIME.metrics.reset();
10443}
10444
10445pub fn metrics() -> RuntimeMetricsSnapshot {
10446  let options = RUNTIME.options();
10447  RUNTIME.metrics.snapshot(&options)
10448}
10449
10450#[cfg(test)]
10451mod tests {
10452  use super::*;
10453
10454  static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10455  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10456    LazyLock::new(Mutex::default);
10457  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
10458  static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10459  static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10460  static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10461    LazyLock::new(Mutex::default);
10462  static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10463  static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
10464    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10465  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10466    LazyLock::new(Mutex::default);
10467  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
10468  static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
10469    CurrentThreadTaskDriverRegistry,
10470  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10471  static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
10472  static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
10473  static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10474    LazyLock::new(Mutex::default);
10475  static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
10476    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10477  static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10478    LazyLock::new(Mutex::default);
10479  static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
10480  static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10481    LazyLock::new(Mutex::default);
10482  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10483    LazyLock::new(Mutex::default);
10484  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
10485    CurrentThreadTaskDriverRegistry,
10486  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10487  static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10488    LazyLock::new(Mutex::default);
10489  static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10490  static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
10491    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10492  static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10493
10494  thread_local! {
10495    static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
10496      const { std::cell::Cell::new(0) };
10497    static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
10498      const { std::cell::Cell::new(0) };
10499  }
10500
10501  #[cfg(not(target_family = "wasm"))]
10502  struct StopPollProbe {
10503    polls: Arc<AtomicUsize>,
10504    first_poll: Option<std::sync::mpsc::Sender<()>>,
10505  }
10506
10507  #[cfg(not(target_family = "wasm"))]
10508  impl Future for StopPollProbe {
10509    type Output = ();
10510
10511    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
10512      self.polls.fetch_add(1, Ordering::SeqCst);
10513      if let Some(first_poll) = self.first_poll.take() {
10514        first_poll.send(()).unwrap();
10515      }
10516      Poll::Pending
10517    }
10518  }
10519
10520  #[cfg(not(target_family = "wasm"))]
10521  struct PanickingJoinAwaiter {
10522    wakes: Arc<AtomicUsize>,
10523    drops: Arc<AtomicUsize>,
10524  }
10525
10526  #[cfg(not(target_family = "wasm"))]
10527  impl std::task::Wake for PanickingJoinAwaiter {
10528    fn wake(self: Arc<Self>) {
10529      self.wakes.fetch_add(1, Ordering::SeqCst);
10530      panic!("intentional JoinHandle awaiter wake panic");
10531    }
10532
10533    fn wake_by_ref(self: &Arc<Self>) {
10534      self.wakes.fetch_add(1, Ordering::SeqCst);
10535      panic!("intentional JoinHandle awaiter wake-by-ref panic");
10536    }
10537  }
10538
10539  #[cfg(not(target_family = "wasm"))]
10540  impl Drop for PanickingJoinAwaiter {
10541    fn drop(&mut self) {
10542      self.drops.fetch_add(1, Ordering::SeqCst);
10543      panic!("intentional JoinHandle awaiter destructor panic");
10544    }
10545  }
10546
10547  #[cfg(not(target_family = "wasm"))]
10548  fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
10549    Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
10550  }
10551
10552  #[cfg(not(target_family = "wasm"))]
10553  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
10554  enum JoinAwaiterWakerEventKind {
10555    Clone,
10556    Wake,
10557    WakeByRef,
10558    Drop,
10559  }
10560
10561  #[cfg(not(target_family = "wasm"))]
10562  struct JoinAwaiterWakerState {
10563    events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
10564  }
10565
10566  #[cfg(not(target_family = "wasm"))]
10567  impl JoinAwaiterWakerState {
10568    fn record(&self, kind: JoinAwaiterWakerEventKind) {
10569      self
10570        .events
10571        .lock()
10572        .unwrap_or_else(std::sync::PoisonError::into_inner)
10573        .push((kind, active_runtime_generation()));
10574    }
10575
10576    fn clear(&self) {
10577      self
10578        .events
10579        .lock()
10580        .unwrap_or_else(std::sync::PoisonError::into_inner)
10581        .clear();
10582    }
10583
10584    fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
10585      self
10586        .events
10587        .lock()
10588        .unwrap_or_else(std::sync::PoisonError::into_inner)
10589        .clone()
10590    }
10591  }
10592
10593  #[cfg(not(target_family = "wasm"))]
10594  struct JoinAwaiterWakerToken {
10595    state: Arc<JoinAwaiterWakerState>,
10596  }
10597
10598  #[cfg(not(target_family = "wasm"))]
10599  unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
10600    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
10601    token.state.record(JoinAwaiterWakerEventKind::Clone);
10602    let clone = Box::new(JoinAwaiterWakerToken {
10603      state: Arc::clone(&token.state),
10604    });
10605    std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
10606  }
10607
10608  #[cfg(not(target_family = "wasm"))]
10609  unsafe fn wake_join_awaiter_waker(data: *const ()) {
10610    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
10611    token.state.record(JoinAwaiterWakerEventKind::Wake);
10612  }
10613
10614  #[cfg(not(target_family = "wasm"))]
10615  unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
10616    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
10617    token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
10618  }
10619
10620  #[cfg(not(target_family = "wasm"))]
10621  unsafe fn drop_join_awaiter_waker(data: *const ()) {
10622    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
10623    token.state.record(JoinAwaiterWakerEventKind::Drop);
10624  }
10625
10626  #[cfg(not(target_family = "wasm"))]
10627  static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
10628    clone_join_awaiter_waker,
10629    wake_join_awaiter_waker,
10630    wake_join_awaiter_waker_by_ref,
10631    drop_join_awaiter_waker,
10632  );
10633
10634  #[cfg(not(target_family = "wasm"))]
10635  fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
10636    let token = Box::new(JoinAwaiterWakerToken { state });
10637    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
10638    unsafe { Waker::from_raw(raw) }
10639  }
10640
10641  #[cfg(not(target_family = "wasm"))]
10642  fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
10643    wait_until("the block_on driver to sleep", || {
10644      stop
10645        .parkers
10646        .lock()
10647        .unwrap_or_else(std::sync::PoisonError::into_inner)
10648        .iter()
10649        .filter_map(Weak::upgrade)
10650        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
10651    });
10652  }
10653
10654  #[test]
10655  fn unique_ids_fail_before_wraparound() {
10656    let counter = AtomicU64::new(u64::MAX - 1);
10657    assert_eq!(
10658      next_unique_id(&counter, "test id space exhausted"),
10659      u64::MAX - 1
10660    );
10661
10662    let exhausted = catch_unwind(AssertUnwindSafe(|| {
10663      next_unique_id(&counter, "test id space exhausted");
10664    }));
10665    assert!(exhausted.is_err());
10666    assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
10667  }
10668
10669  #[test]
10670  fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
10671    let work = GenerationWork::new();
10672    {
10673      let mut state = work.state.lock().unwrap();
10674      state.next_task_id = u64::MAX;
10675    }
10676
10677    let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
10678    assert!(exhausted.is_err());
10679
10680    let state = work.state.lock().unwrap();
10681    assert_eq!(state.next_task_id, u64::MAX);
10682    assert_eq!(state.active, 0);
10683    assert!(state.abort_handles.is_empty());
10684  }
10685
10686  #[test]
10687  fn caught_panic_payload_destructor_runs() {
10688    struct DropSignal(Arc<AtomicBool>);
10689
10690    impl Drop for DropSignal {
10691      fn drop(&mut self) {
10692        self.0.store(true, Ordering::SeqCst);
10693      }
10694    }
10695
10696    let dropped = Arc::new(AtomicBool::new(false));
10697    let payload = DropSignal(Arc::clone(&dropped));
10698
10699    assert!(
10700      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
10701      "the original panic must remain contained"
10702    );
10703    assert!(
10704      dropped.load(Ordering::SeqCst),
10705      "an ordinary caught panic payload must be destroyed"
10706    );
10707  }
10708
10709  #[test]
10710  fn hostile_caught_panic_payload_destructor_remains_contained() {
10711    struct PanicOnDrop(Arc<AtomicBool>);
10712
10713    impl Drop for PanicOnDrop {
10714      fn drop(&mut self) {
10715        self.0.store(true, Ordering::SeqCst);
10716        panic!("caught panic payload destructor escaped");
10717      }
10718    }
10719
10720    let dropped = Arc::new(AtomicBool::new(false));
10721    let payload = PanicOnDrop(Arc::clone(&dropped));
10722    let result = catch_unwind(AssertUnwindSafe(|| {
10723      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
10724    }));
10725
10726    assert!(
10727      result.is_ok(),
10728      "the payload destructor's nested panic must remain contained"
10729    );
10730    assert!(
10731      result.unwrap().is_none(),
10732      "the original panic must remain contained"
10733    );
10734    assert!(
10735      dropped.load(Ordering::SeqCst),
10736      "the hostile payload destructor must be attempted"
10737    );
10738  }
10739
10740  fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10741    CurrentThreadHostDispatchResult::Accepted
10742  }
10743
10744  fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10745    CurrentThreadHostDispatchResult::Unavailable
10746  }
10747
10748  fn count_current_thread_recovery_host_dispatch(
10749    _dispatch: u64,
10750  ) -> CurrentThreadHostDispatchResult {
10751    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
10752    CurrentThreadHostDispatchResult::Accepted
10753  }
10754
10755  fn reset_current_thread_recovery_host_dispatches() {
10756    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
10757  }
10758
10759  fn current_thread_recovery_host_dispatches() -> usize {
10760    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
10761  }
10762
10763  fn accept_only_first_current_thread_host_dispatch(
10764    _dispatch: u64,
10765  ) -> CurrentThreadHostDispatchResult {
10766    if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
10767      let previous = dispatches.get();
10768      dispatches.set(previous + 1);
10769      previous == 0
10770    }) {
10771      CurrentThreadHostDispatchResult::Accepted
10772    } else {
10773      CurrentThreadHostDispatchResult::Unavailable
10774    }
10775  }
10776
10777  fn reset_current_thread_one_shot_host_dispatches() {
10778    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
10779  }
10780
10781  fn current_thread_one_shot_host_dispatches() -> usize {
10782    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
10783  }
10784
10785  fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10786    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10787    CurrentThreadHostDispatchResult::Accepted
10788  }
10789
10790  fn count_current_thread_blocking_budget_host_dispatch(
10791    _dispatch: u64,
10792  ) -> CurrentThreadHostDispatchResult {
10793    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10794    CurrentThreadHostDispatchResult::Accepted
10795  }
10796
10797  fn count_current_thread_admission_host_dispatch(
10798    _dispatch: u64,
10799  ) -> CurrentThreadHostDispatchResult {
10800    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10801    CurrentThreadHostDispatchResult::Accepted
10802  }
10803
10804  fn count_current_thread_interleaved_recovery_host_dispatch(
10805    _dispatch: u64,
10806  ) -> CurrentThreadHostDispatchResult {
10807    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10808    CurrentThreadHostDispatchResult::Accepted
10809  }
10810
10811  fn dispatch_late_registration_current_thread_tasks(
10812    dispatch: u64,
10813  ) -> CurrentThreadHostDispatchResult {
10814    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
10815  }
10816
10817  fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
10818    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
10819      .lock()
10820      .unwrap()
10821      .push(dispatch);
10822    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
10823  }
10824
10825  fn dispatch_authoritative_panic_current_thread_tasks(
10826    dispatch: u64,
10827  ) -> CurrentThreadHostDispatchResult {
10828    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
10829      .lock()
10830      .unwrap()
10831      .push(dispatch);
10832    match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
10833      0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
10834      2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
10835      _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
10836    }
10837  }
10838
10839  fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
10840    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
10841  }
10842
10843  fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
10844    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
10845      .lock()
10846      .unwrap()
10847      .push(dispatch);
10848    assert!(
10849      !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
10850      "injected CurrentThread host dispatch panic"
10851    );
10852    CurrentThreadHostDispatchResult::Accepted
10853  }
10854
10855  fn panic_once_current_thread_armed_republish_host_dispatch(
10856    dispatch: u64,
10857  ) -> CurrentThreadHostDispatchResult {
10858    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
10859      .lock()
10860      .unwrap()
10861      .push(dispatch);
10862    assert!(
10863      !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
10864      "injected armed CurrentThread host dispatch panic"
10865    );
10866    CurrentThreadHostDispatchResult::Accepted
10867  }
10868
10869  fn record_current_thread_republish_rearm_dispatch(
10870    dispatch: u64,
10871  ) -> CurrentThreadHostDispatchResult {
10872    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
10873      .lock()
10874      .unwrap()
10875      .push(dispatch);
10876    CurrentThreadHostDispatchResult::Accepted
10877  }
10878
10879  fn reject_initial_current_thread_publication_once(
10880    dispatch: u64,
10881  ) -> CurrentThreadHostDispatchResult {
10882    let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
10883      .lock()
10884      .unwrap();
10885    dispatches.push(dispatch);
10886    if dispatches.len() == 1 {
10887      CurrentThreadHostDispatchResult::Unavailable
10888    } else {
10889      CurrentThreadHostDispatchResult::Accepted
10890    }
10891  }
10892
10893  fn reject_current_thread_replacement_publication_once(
10894    dispatch: u64,
10895  ) -> CurrentThreadHostDispatchResult {
10896    let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
10897      .lock()
10898      .unwrap();
10899    dispatches.push(dispatch);
10900    if dispatches.len() == 2 {
10901      CurrentThreadHostDispatchResult::Unavailable
10902    } else {
10903      CurrentThreadHostDispatchResult::Accepted
10904    }
10905  }
10906
10907  fn fail_current_thread_replacement_publication_once(
10908    dispatch: u64,
10909  ) -> CurrentThreadHostDispatchResult {
10910    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
10911      .lock()
10912      .unwrap()
10913      .push(dispatch);
10914    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
10915  }
10916
10917  fn count_current_thread_shutdown_republish_host_dispatch(
10918    _dispatch: u64,
10919  ) -> CurrentThreadHostDispatchResult {
10920    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10921    CurrentThreadHostDispatchResult::Accepted
10922  }
10923
10924  fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10925    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10926    CurrentThreadHostDispatchResult::Accepted
10927  }
10928
10929  fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
10930    let pending = executor.dispatch_pending.load(Ordering::Acquire);
10931    if pending != 0 {
10932      return pending;
10933    }
10934    let dispatch = next_current_thread_dispatch_id();
10935    match executor.dispatch_pending.compare_exchange(
10936      0,
10937      dispatch,
10938      Ordering::AcqRel,
10939      Ordering::Acquire,
10940    ) {
10941      Ok(_) => dispatch,
10942      Err(pending) => pending,
10943    }
10944  }
10945
10946  fn current_thread_dispatch_failure(
10947    dispatch: u64,
10948    epoch: u64,
10949  ) -> CurrentThreadTaskDispatchFailure {
10950    CurrentThreadTaskDispatchFailure { dispatch, epoch }
10951  }
10952
10953  fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
10954    let executor = controller
10955      .running_current_thread_executor()
10956      .expect("the test controller must have a running CurrentThread executor");
10957    current_thread_dispatch(&executor)
10958  }
10959
10960  #[test]
10961  fn current_thread_executor_drives_spawned_tasks() {
10962    let metrics = Arc::new(RuntimeMetrics::default());
10963    let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
10964    let scheduler = Arc::clone(&executor);
10965    let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
10966      scheduler.schedule(runnable);
10967    });
10968
10969    executor.schedule(runnable);
10970
10971    let mut output = None;
10972    {
10973      let mut future = std::pin::pin!(async {
10974        output = Some(task.await);
10975      });
10976      executor.block_on(future.as_mut());
10977    }
10978    assert_eq!(output, Some(42));
10979    assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
10980    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
10981  }
10982
10983  #[cfg(not(target_family = "wasm"))]
10984  #[test]
10985  fn current_thread_shutdown_wake_does_not_repoll_user_future() {
10986    use std::sync::mpsc;
10987
10988    let executor = Arc::new(CurrentThreadExecutor::with_detection(
10989      Arc::new(RuntimeMetrics::default()),
10990      false,
10991      None,
10992    ));
10993    let polls = Arc::new(AtomicUsize::new(0));
10994    let (first_poll_tx, first_poll_rx) = mpsc::channel();
10995    let (outcome_tx, outcome_rx) = mpsc::channel();
10996    let driver_executor = Arc::clone(&executor);
10997    let driver_polls = Arc::clone(&polls);
10998    let driver = std::thread::spawn(move || {
10999      let mut future = std::pin::pin!(StopPollProbe {
11000        polls: driver_polls,
11001        first_poll: Some(first_poll_tx)
11002      });
11003      outcome_tx
11004        .send(driver_executor.block_on(future.as_mut()))
11005        .unwrap();
11006    });
11007
11008    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11009    wait_for_generation_parker_to_sleep(&executor.stop);
11010    executor.stop.begin_shutdown();
11011
11012    assert_eq!(
11013      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11014      BlockOnOutcome::Stopped
11015    );
11016    driver.join().unwrap();
11017    assert_eq!(
11018      polls.load(Ordering::SeqCst),
11019      1,
11020      "a shutdown wake must be checked before the next CurrentThread user poll"
11021    );
11022  }
11023
11024  #[cfg(not(target_family = "wasm"))]
11025  #[test]
11026  fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
11027    use std::sync::mpsc;
11028
11029    let metrics = Arc::new(RuntimeMetrics::default());
11030    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11031      Arc::clone(&metrics),
11032      accept_current_thread_host_dispatch,
11033    ));
11034    let polls = Arc::new(AtomicUsize::new(0));
11035    let task_polls = Arc::clone(&polls);
11036    let (runnable, task) = async_task::spawn(
11037      async move {
11038        task_polls.fetch_add(1, Ordering::SeqCst);
11039      },
11040      |_| {},
11041    );
11042    executor.schedule(runnable);
11043    let dispatch = current_thread_dispatch(&executor);
11044
11045    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
11046    let (release_claim_tx, release_claim_rx) = mpsc::channel();
11047    let driver_executor = Arc::clone(&executor);
11048    let driver = std::thread::spawn(move || {
11049      CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
11050        *slot.borrow_mut() = Some(Box::new(move || {
11051          claim_locked_tx.send(()).unwrap();
11052          release_claim_rx.recv().unwrap();
11053        }));
11054      });
11055      driver_executor.drive_host_turn_for_dispatch(dispatch);
11056    });
11057    claim_locked_rx
11058      .recv_timeout(Duration::from_secs(2))
11059      .expect("the admitted host turn must pause before its runnable claim");
11060
11061    executor.stop.publish_stopping();
11062    release_claim_tx.send(()).unwrap();
11063    driver.join().unwrap();
11064
11065    assert_eq!(
11066      polls.load(Ordering::SeqCst),
11067      0,
11068      "stop publication must outrank a later runnable claim by an already-admitted host turn"
11069    );
11070    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11071
11072    executor.begin_shutdown();
11073    assert!(
11074      futures::executor::block_on(task.fallible()).is_none(),
11075      "shutdown must cancel the runnable rejected by the stopped claim"
11076    );
11077    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11078  }
11079
11080  #[cfg(not(target_family = "wasm"))]
11081  #[test]
11082  fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
11083    use std::sync::mpsc;
11084
11085    let metrics = Arc::new(RuntimeMetrics::default());
11086    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11087      Arc::clone(&metrics),
11088      accept_current_thread_host_dispatch,
11089    ));
11090    let polls = Arc::new(AtomicUsize::new(0));
11091    let task_polls = Arc::clone(&polls);
11092    let (runnable, task) = async_task::spawn(
11093      async move {
11094        task_polls.fetch_add(1, Ordering::SeqCst);
11095      },
11096      |_| {},
11097    );
11098    executor.schedule(runnable);
11099
11100    let queue = executor
11101      .queue
11102      .lock()
11103      .unwrap_or_else(std::sync::PoisonError::into_inner);
11104    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
11105    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
11106    let cancelling_executor = Arc::clone(&executor);
11107    let cancellation = std::thread::spawn(move || {
11108      let scheduler = cancelling_executor
11109        .scheduler_idle_lock
11110        .lock()
11111        .unwrap_or_else(std::sync::PoisonError::into_inner);
11112      let cancelling = cancelling_executor
11113        .begin_failed_dispatch_cancellation()
11114        .expect("the test must publish terminal dispatch cancellation");
11115      drop(scheduler);
11116      cancellation_published_tx.send(()).unwrap();
11117      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
11118      cancellation_done_tx.send(()).unwrap();
11119    });
11120    cancellation_published_rx
11121      .recv_timeout(Duration::from_secs(2))
11122      .expect("terminal dispatch cancellation must publish before queue draining");
11123
11124    let claimant_executor = Arc::clone(&executor);
11125    let claimant = std::thread::spawn(move || claimant_executor.drain_one());
11126    assert!(
11127      !claimant.join().unwrap(),
11128      "a runnable claim after terminal cancellation publication must be rejected"
11129    );
11130    assert_eq!(polls.load(Ordering::SeqCst), 0);
11131    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11132    assert!(
11133      matches!(
11134        cancellation_done_rx.try_recv(),
11135        Err(mpsc::TryRecvError::Empty)
11136      ),
11137      "terminal cancellation must still be waiting to drain the queued runnable"
11138    );
11139
11140    drop(queue);
11141    cancellation_done_rx
11142      .recv_timeout(Duration::from_secs(2))
11143      .expect("terminal cancellation must drain the rejected runnable");
11144    cancellation.join().unwrap();
11145    assert!(
11146      futures::executor::block_on(task.fallible()).is_none(),
11147      "terminal cancellation must cancel the runnable it prevented from starting"
11148    );
11149    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11150  }
11151
11152  #[cfg(not(target_family = "wasm"))]
11153  #[test]
11154  fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
11155    use std::sync::mpsc;
11156
11157    let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
11158    let polls = Arc::new(AtomicUsize::new(0));
11159    let (first_poll_tx, first_poll_rx) = mpsc::channel();
11160    let (outcome_tx, outcome_rx) = mpsc::channel();
11161    let driver_executor = Arc::clone(&executor);
11162    let driver_polls = Arc::clone(&polls);
11163    let driver = std::thread::spawn(move || {
11164      let mut future = std::pin::pin!(StopPollProbe {
11165        polls: driver_polls,
11166        first_poll: Some(first_poll_tx)
11167      });
11168      outcome_tx
11169        .send(driver_executor.block_on(future.as_mut()))
11170        .unwrap();
11171    });
11172
11173    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11174    wait_for_generation_parker_to_sleep(&executor.stop);
11175    executor.stop.begin_shutdown();
11176
11177    assert_eq!(
11178      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11179      BlockOnOutcome::Stopped
11180    );
11181    driver.join().unwrap();
11182    assert_eq!(
11183      polls.load(Ordering::SeqCst),
11184      1,
11185      "a shutdown wake must be checked before the next parking block_on user poll"
11186    );
11187  }
11188
11189  #[cfg(not(target_family = "wasm"))]
11190  #[test]
11191  fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
11192    use std::sync::mpsc;
11193
11194    let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
11195    let polls = Arc::new(AtomicUsize::new(0));
11196    let (first_poll_tx, first_poll_rx) = mpsc::channel();
11197    let (outcome_tx, outcome_rx) = mpsc::channel();
11198    let driver_executor = Arc::clone(&executor);
11199    let driver_polls = Arc::clone(&polls);
11200    let driver = std::thread::spawn(move || {
11201      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
11202      let mut future = std::pin::pin!(StopPollProbe {
11203        polls: driver_polls,
11204        first_poll: Some(first_poll_tx)
11205      });
11206      outcome_tx
11207        .send(driver_executor.block_on(future.as_mut()))
11208        .unwrap();
11209    });
11210
11211    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11212    wait_for_generation_parker_to_sleep(&executor.stop);
11213    executor.stop.begin_shutdown();
11214
11215    assert_eq!(
11216      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11217      BlockOnOutcome::Stopped
11218    );
11219    driver.join().unwrap();
11220    assert_eq!(
11221      polls.load(Ordering::SeqCst),
11222      1,
11223      "a shutdown wake must be checked before the next cooperative user poll"
11224    );
11225  }
11226
11227  #[test]
11228  fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
11229    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11230      Arc::new(RuntimeMetrics::default()),
11231      accept_current_thread_host_dispatch,
11232    ));
11233    let pending = u64::MAX - 1;
11234    executor.dispatch_pending.store(pending, Ordering::Release);
11235    let mut allocations = 0;
11236
11237    executor.request_drain_with(|| {
11238      allocations += 1;
11239      u64::MAX
11240    });
11241
11242    assert_eq!(allocations, 0);
11243    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
11244  }
11245
11246  #[cfg(not(target_family = "wasm"))]
11247  #[test]
11248  fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
11249    use std::sync::mpsc;
11250
11251    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11252      Arc::new(RuntimeMetrics::default()),
11253      accept_current_thread_host_dispatch,
11254    ));
11255    let dispatch = current_thread_dispatch(&executor);
11256    let scheduler = executor
11257      .scheduler_idle_lock
11258      .lock()
11259      .unwrap_or_else(std::sync::PoisonError::into_inner);
11260    let (claiming_tx, claiming_rx) = mpsc::channel();
11261    let (claimed_tx, claimed_rx) = mpsc::channel();
11262    let claiming_executor = Arc::clone(&executor);
11263    let claimant = std::thread::spawn(move || {
11264      claiming_tx.send(()).unwrap();
11265      claimed_tx
11266        .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
11267        .unwrap();
11268    });
11269    claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11270
11271    executor.stop.begin_shutdown();
11272    drop(scheduler);
11273
11274    assert!(
11275      !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11276      "an old-generation dispatch must not claim a host turn after stop becomes visible"
11277    );
11278    claimant.join().unwrap();
11279    assert_eq!(
11280      executor.dispatch_pending.load(Ordering::Acquire),
11281      dispatch,
11282      "rejecting the stopped claim must not consume its stale capability"
11283    );
11284  }
11285
11286  #[cfg(not(target_family = "wasm"))]
11287  #[test]
11288  fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
11289    use std::sync::mpsc;
11290
11291    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11292    let metrics = Arc::new(RuntimeMetrics::default());
11293    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11294      Arc::clone(&metrics),
11295      count_current_thread_stopped_host_dispatch,
11296    ));
11297    let scheduler = executor
11298      .scheduler_idle_lock
11299      .lock()
11300      .unwrap_or_else(std::sync::PoisonError::into_inner);
11301    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
11302    let scheduling_executor = Arc::clone(&executor);
11303    let scheduling = std::thread::spawn(move || {
11304      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
11305        *slot.borrow_mut() = Some(Box::new(move || {
11306          publication_blocked_tx.send(()).unwrap();
11307        }));
11308      });
11309      let scheduler = Arc::clone(&scheduling_executor);
11310      let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
11311        scheduler.schedule(runnable);
11312      });
11313      task.detach();
11314      scheduling_executor.schedule(runnable);
11315    });
11316
11317    publication_blocked_rx
11318      .recv_timeout(Duration::from_secs(2))
11319      .expect("dispatch publication must block behind the scheduler-idle mutex");
11320    executor.begin_shutdown();
11321    drop(scheduler);
11322    scheduling.join().unwrap();
11323    executor.wait_until_scheduler_idle();
11324
11325    assert_eq!(
11326      CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
11327      0,
11328      "a publication that reaches the scheduler-idle barrier after stop must not call the host"
11329    );
11330    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
11331    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11332  }
11333
11334  #[test]
11335  fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
11336    use std::{sync::mpsc, time::Duration};
11337
11338    let metrics = Arc::new(RuntimeMetrics::default());
11339    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11340      Arc::clone(&metrics),
11341      accept_current_thread_host_dispatch,
11342    ));
11343    let scheduler = Arc::clone(&executor);
11344    let completed = Arc::new(AtomicBool::new(false));
11345    let completed_task = Arc::clone(&completed);
11346    let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
11347    let shared = async move {
11348      wake_rx.await.expect("wake sender dropped");
11349    }
11350    .boxed()
11351    .shared();
11352    let (runnable, task) = async_task::spawn(
11353      async move {
11354        shared.await;
11355        completed_task.store(true, Ordering::SeqCst);
11356      },
11357      move |runnable| scheduler.schedule(runnable),
11358    );
11359
11360    executor.schedule(runnable);
11361    assert!(
11362      !completed.load(Ordering::SeqCst),
11363      "accepted host dispatch must defer the first poll"
11364    );
11365    executor.drive_host_turn();
11366
11367    let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
11368    std::thread::spawn(move || {
11369      wake_tx.send(()).expect("shared wake receiver dropped");
11370      send_returned_tx.send(()).unwrap();
11371    });
11372    send_returned_rx
11373      .recv_timeout(Duration::from_secs(1))
11374      .expect("Shared wake re-entered its own waker mutex through inline polling");
11375    assert!(
11376      !completed.load(Ordering::SeqCst),
11377      "the wake must remain queued until a fresh host turn"
11378    );
11379
11380    executor.drive_host_turn();
11381    futures::executor::block_on(task);
11382    assert!(completed.load(Ordering::SeqCst));
11383    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11384  }
11385
11386  #[test]
11387  fn current_thread_hostless_wake_is_enqueue_only() {
11388    use std::{sync::mpsc, time::Duration};
11389
11390    struct LockOnSecondPoll {
11391      gate: Arc<Mutex<()>>,
11392      first: bool,
11393      waker: Option<mpsc::Sender<Waker>>,
11394      completed: Arc<AtomicBool>,
11395    }
11396
11397    impl Future for LockOnSecondPoll {
11398      type Output = ();
11399
11400      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
11401        if self.first {
11402          self.first = false;
11403          self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
11404          return Poll::Pending;
11405        }
11406        drop(self.gate.lock().unwrap());
11407        self.completed.store(true, Ordering::SeqCst);
11408        Poll::Ready(())
11409      }
11410    }
11411
11412    let metrics = Arc::new(RuntimeMetrics::default());
11413    let executor = Arc::new(CurrentThreadExecutor::new(metrics));
11414    let gate = Arc::new(Mutex::new(()));
11415    let completed = Arc::new(AtomicBool::new(false));
11416    let (waker_tx, waker_rx) = mpsc::channel();
11417    let scheduler = Arc::clone(&executor);
11418    let (runnable, task) = async_task::spawn(
11419      LockOnSecondPoll {
11420        gate: Arc::clone(&gate),
11421        first: true,
11422        waker: Some(waker_tx),
11423        completed: Arc::clone(&completed),
11424      },
11425      move |runnable| scheduler.schedule(runnable),
11426    );
11427    executor.schedule(runnable);
11428    executor.drive_host_turn();
11429    let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
11430
11431    let (returned_tx, returned_rx) = mpsc::channel();
11432    let wake_gate = Arc::clone(&gate);
11433    let waking = std::thread::spawn(move || {
11434      let _held = wake_gate.lock().unwrap();
11435      waker.wake();
11436      returned_tx.send(()).unwrap();
11437    });
11438    returned_rx
11439      .recv_timeout(Duration::from_secs(1))
11440      .expect("hostless wake must enqueue without polling under the caller's lock");
11441    waking.join().unwrap();
11442    assert!(!completed.load(Ordering::SeqCst));
11443
11444    executor.drive_host_turn();
11445    futures::executor::block_on(task);
11446    assert!(completed.load(Ordering::SeqCst));
11447  }
11448
11449  #[cfg(not(target_family = "wasm"))]
11450  #[test]
11451  fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
11452    use std::sync::mpsc;
11453
11454    let controller = Arc::new(current_thread_controller(
11455      "current-thread-hostless-late-work",
11456    ));
11457    let backend = controller.backend();
11458    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
11459      panic!("configured CurrentThread backend must create a CurrentThread executor");
11460    };
11461    let executor = Arc::clone(executor);
11462    drop(backend);
11463
11464    let (value_tx, value_rx) = oneshot::channel();
11465    let (done_tx, done_rx) = mpsc::channel();
11466    let block_on_controller = Arc::clone(&controller);
11467    let runner = std::thread::spawn(move || {
11468      let mut output = None;
11469      let outcome = {
11470        let mut future = std::pin::pin!(async {
11471          output = Some(value_rx.await.expect("scheduled sender must deliver"));
11472        });
11473        block_on_controller.block_on(future.as_mut())
11474      };
11475      done_tx.send((outcome, output)).unwrap();
11476    });
11477
11478    wait_until(
11479      "the hostless CurrentThread block_on driver to sleep",
11480      || {
11481        executor
11482          .stop
11483          .parkers
11484          .lock()
11485          .unwrap_or_else(std::sync::PoisonError::into_inner)
11486          .iter()
11487          .filter_map(Weak::upgrade)
11488          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11489      },
11490    );
11491
11492    controller
11493      .try_spawn_detached(async move {
11494        value_tx
11495          .send(37usize)
11496          .expect("block_on receiver must remain alive");
11497      })
11498      .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
11499
11500    let (outcome, output) = done_rx
11501      .recv_timeout(Duration::from_secs(2))
11502      .expect("queue publication must wake the sleeping explicit driver");
11503    assert_eq!(outcome, BlockOnOutcome::Completed);
11504    assert_eq!(output, Some(37));
11505    runner.join().unwrap();
11506    controller.shutdown().unwrap();
11507  }
11508
11509  #[cfg(not(target_family = "wasm"))]
11510  #[test]
11511  fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
11512    use std::sync::mpsc;
11513
11514    struct BlockingPoll {
11515      entered: Option<mpsc::Sender<()>>,
11516      release: mpsc::Receiver<()>,
11517    }
11518
11519    impl Future for BlockingPoll {
11520      type Output = ();
11521
11522      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
11523        self.entered.take().unwrap().send(()).unwrap();
11524        self.release.recv().unwrap();
11525        Poll::Ready(())
11526      }
11527    }
11528
11529    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11530      Arc::new(RuntimeMetrics::default()),
11531      reject_current_thread_host_dispatch,
11532    ));
11533    let (value_tx, value_rx) = oneshot::channel();
11534    let (older_done_tx, older_done_rx) = mpsc::channel();
11535    let older_executor = Arc::clone(&executor);
11536    let older = std::thread::spawn(move || {
11537      let mut value = None;
11538      {
11539        let mut future = std::pin::pin!(async {
11540          value = Some(value_rx.await.unwrap());
11541        });
11542        assert_eq!(
11543          older_executor.block_on(future.as_mut()),
11544          BlockOnOutcome::Completed
11545        );
11546      }
11547      older_done_tx.send(value.unwrap()).unwrap();
11548    });
11549    wait_until("the older CurrentThread driver to sleep", || {
11550      executor
11551        .stop
11552        .parkers
11553        .lock()
11554        .unwrap_or_else(std::sync::PoisonError::into_inner)
11555        .iter()
11556        .filter_map(Weak::upgrade)
11557        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11558    });
11559
11560    let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
11561    let (release_newer_tx, release_newer_rx) = mpsc::channel();
11562    let newer_executor = Arc::clone(&executor);
11563    let newer = std::thread::spawn(move || {
11564      let mut future = std::pin::pin!(BlockingPoll {
11565        entered: Some(newer_entered_tx),
11566        release: release_newer_rx
11567      });
11568      assert_eq!(
11569        newer_executor.block_on(future.as_mut()),
11570        BlockOnOutcome::Completed
11571      );
11572    });
11573    newer_entered_rx
11574      .recv_timeout(Duration::from_secs(1))
11575      .expect("the newer driver must be blocked inside its poll");
11576
11577    let scheduler = Arc::clone(&executor);
11578    let (runnable, task) = async_task::spawn(
11579      async move {
11580        value_tx.send(41usize).unwrap();
11581      },
11582      move |runnable| scheduler.schedule(runnable),
11583    );
11584    task.detach();
11585    executor.schedule(runnable);
11586
11587    assert_eq!(
11588      older_done_rx
11589        .recv_timeout(Duration::from_secs(2))
11590        .expect("queue publication must also wake the older sleeping driver"),
11591      41
11592    );
11593    release_newer_tx.send(()).unwrap();
11594    older.join().unwrap();
11595    newer.join().unwrap();
11596  }
11597
11598  #[test]
11599  fn current_thread_shutdown_cancels_host_queued_task() {
11600    use std::sync::mpsc;
11601
11602    struct DropSignal(mpsc::Sender<()>);
11603
11604    impl Drop for DropSignal {
11605      fn drop(&mut self) {
11606        self.0.send(()).unwrap();
11607      }
11608    }
11609
11610    let metrics = Arc::new(RuntimeMetrics::default());
11611    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11612      Arc::clone(&metrics),
11613      accept_current_thread_host_dispatch,
11614    ));
11615    let scheduler = Arc::clone(&executor);
11616    let (dropped_tx, dropped_rx) = mpsc::channel();
11617    let signal = DropSignal(dropped_tx);
11618    let (runnable, task) = async_task::spawn(
11619      async move {
11620        let _signal = signal;
11621        futures::future::pending::<()>().await;
11622      },
11623      move |runnable| scheduler.schedule(runnable),
11624    );
11625
11626    executor.schedule(runnable);
11627    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11628    executor.begin_shutdown();
11629
11630    dropped_rx
11631      .recv_timeout(Duration::from_secs(1))
11632      .expect("shutdown must drop a task queued behind a host turn");
11633    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11634    assert!(
11635      futures::executor::block_on(task.fallible()).is_none(),
11636      "the queued task handle must resolve as cancelled"
11637    );
11638  }
11639
11640  #[test]
11641  fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
11642    reset_current_thread_recovery_host_dispatches();
11643    let metrics = Arc::new(RuntimeMetrics::default());
11644    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11645      Arc::clone(&metrics),
11646      count_current_thread_recovery_host_dispatch,
11647    ));
11648    let scheduler = Arc::clone(&executor);
11649    let completed = Arc::new(AtomicBool::new(false));
11650    let completed_task = Arc::clone(&completed);
11651    let (runnable, task) = async_task::spawn(
11652      async move {
11653        completed_task.store(true, Ordering::SeqCst);
11654      },
11655      move |runnable| scheduler.schedule(runnable),
11656    );
11657
11658    executor.schedule(runnable);
11659    assert_eq!(current_thread_recovery_host_dispatches(), 1);
11660    let lost_dispatch = current_thread_dispatch(&executor);
11661
11662    // Model another host registering while the first host still owns an
11663    // accepted delivery. The executor capability stays stable; the host
11664    // registry gives the new registration a distinct delivery token.
11665    executor.request_drain_if_queued();
11666    assert_eq!(
11667      current_thread_recovery_host_dispatches(),
11668      2,
11669      "the newly registered host must receive the pending executor dispatch"
11670    );
11671    assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
11672    executor.drive_host_turn_for_dispatch(lost_dispatch);
11673    futures::executor::block_on(task);
11674    assert!(completed.load(Ordering::SeqCst));
11675    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11676
11677    let second_completed = Arc::new(AtomicBool::new(false));
11678    let second_completed_task = Arc::clone(&second_completed);
11679    let second_scheduler = Arc::clone(&executor);
11680    let (second_runnable, second_task) = async_task::spawn(
11681      async move {
11682        second_completed_task.store(true, Ordering::SeqCst);
11683      },
11684      move |runnable| second_scheduler.schedule(runnable),
11685    );
11686    executor.schedule(second_runnable);
11687    assert_eq!(
11688      current_thread_recovery_host_dispatches(),
11689      3,
11690      "work queued after the stale callback must receive a new dispatch"
11691    );
11692    let third_dispatch = current_thread_dispatch(&executor);
11693
11694    // The already-consumed capability remains stale relative to dispatch 2 and
11695    // cannot service its work.
11696    executor.drive_host_turn_for_dispatch(lost_dispatch);
11697    assert!(!second_completed.load(Ordering::SeqCst));
11698    executor.drive_host_turn_for_dispatch(third_dispatch);
11699    futures::executor::block_on(second_task);
11700    assert!(second_completed.load(Ordering::SeqCst));
11701
11702    let third_scheduler = Arc::clone(&executor);
11703    let (third_runnable, third_task) =
11704      async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
11705    executor.schedule(third_runnable);
11706    assert_eq!(
11707      current_thread_recovery_host_dispatches(),
11708      4,
11709      "a stale callback must not leave the dispatch latch stuck"
11710    );
11711    executor.drive_host_turn();
11712    futures::executor::block_on(third_task);
11713  }
11714
11715  #[test]
11716  fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
11717    let metrics = Arc::new(RuntimeMetrics::default());
11718    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11719      Arc::clone(&metrics),
11720      accept_current_thread_host_dispatch,
11721    ));
11722    let scheduler = Arc::clone(&executor);
11723    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
11724      scheduler.schedule(runnable)
11725    });
11726
11727    executor.schedule(runnable);
11728    let failed_dispatch = current_thread_dispatch(&executor);
11729    executor.cancel_host_dispatch_for_test(failed_dispatch);
11730    let recovery_dispatch = current_thread_dispatch(&executor);
11731    assert_eq!(
11732      executor.recovery_dispatch.load(Ordering::Acquire),
11733      recovery_dispatch
11734    );
11735
11736    executor.request_drain_if_queued();
11737    let new_host_dispatch = current_thread_dispatch(&executor);
11738    assert_eq!(new_host_dispatch, recovery_dispatch);
11739    assert_eq!(
11740      executor.recovery_dispatch.load(Ordering::Acquire),
11741      recovery_dispatch
11742    );
11743    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11744
11745    executor.drive_host_turn_for_dispatch(new_host_dispatch);
11746    assert_eq!(futures::executor::block_on(task), 17);
11747  }
11748
11749  #[cfg(not(target_family = "wasm"))]
11750  #[test]
11751  fn current_thread_host_admission_serializes_dispatch_publication() {
11752    use std::sync::mpsc;
11753
11754    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11755    let metrics = Arc::new(RuntimeMetrics::default());
11756    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11757      Arc::clone(&metrics),
11758      count_current_thread_admission_host_dispatch,
11759    ));
11760    let first_scheduler = Arc::clone(&executor);
11761    let (first_runnable, first_task) =
11762      async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
11763    first_task.detach();
11764    executor.schedule(first_runnable);
11765    let dispatch = current_thread_dispatch(&executor);
11766    assert_eq!(
11767      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
11768      1
11769    );
11770
11771    let (claimed_tx, claimed_rx) = mpsc::channel();
11772    let (release_tx, release_rx) = mpsc::channel();
11773    let driver_executor = Arc::clone(&executor);
11774    let driver = std::thread::spawn(move || {
11775      CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
11776        *slot.borrow_mut() = Some(Box::new(move || {
11777          claimed_tx.send(()).unwrap();
11778          release_rx.recv().unwrap();
11779        }));
11780      });
11781      driver_executor.drive_host_turn_for_dispatch(dispatch);
11782    });
11783    claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11784
11785    let second_ran = Arc::new(AtomicBool::new(false));
11786    let second_ran_task = Arc::clone(&second_ran);
11787    let scheduling_executor = Arc::clone(&executor);
11788    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
11789    let (scheduled_tx, scheduled_rx) = mpsc::channel();
11790    let scheduling = std::thread::spawn(move || {
11791      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
11792        *slot.borrow_mut() = Some(Box::new(move || {
11793          publication_blocked_tx.send(()).unwrap();
11794        }));
11795      });
11796      let scheduler = Arc::clone(&scheduling_executor);
11797      let (runnable, task) = async_task::spawn(
11798        async move {
11799          second_ran_task.store(true, Ordering::SeqCst);
11800        },
11801        move |runnable| scheduler.schedule(runnable),
11802      );
11803      task.detach();
11804      scheduling_executor.schedule(runnable);
11805      scheduled_tx.send(()).unwrap();
11806    });
11807
11808    publication_blocked_rx
11809      .recv_timeout(Duration::from_secs(2))
11810      .unwrap();
11811    assert!(
11812      matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
11813      "dispatch publication must wait until host admission publishes its draining role"
11814    );
11815    assert_eq!(
11816      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
11817      1,
11818      "the admission gap must not publish a duplicate host dispatch"
11819    );
11820
11821    release_tx.send(()).unwrap();
11822    scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11823    scheduling.join().unwrap();
11824    driver.join().unwrap();
11825    if !second_ran.load(Ordering::SeqCst) {
11826      executor.drive_host_turn();
11827    }
11828    assert!(second_ran.load(Ordering::SeqCst));
11829    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11830  }
11831
11832  #[test]
11833  fn current_thread_host_turn_yields_after_runnable_budget() {
11834    struct SelfWakingFuture {
11835      remaining: usize,
11836    }
11837
11838    impl Future for SelfWakingFuture {
11839      type Output = ();
11840
11841      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
11842        if self.remaining == 0 {
11843          return Poll::Ready(());
11844        }
11845        self.remaining -= 1;
11846        cx.waker().wake_by_ref();
11847        Poll::Pending
11848      }
11849    }
11850
11851    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11852    let metrics = Arc::new(RuntimeMetrics::default());
11853    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11854      Arc::clone(&metrics),
11855      count_current_thread_budget_host_dispatch,
11856    ));
11857    let scheduler = Arc::clone(&executor);
11858    let (runnable, task) = async_task::spawn(
11859      SelfWakingFuture {
11860        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
11861      },
11862      move |runnable| scheduler.schedule(runnable),
11863    );
11864
11865    executor.schedule(runnable);
11866    executor.drive_host_turn();
11867
11868    assert_eq!(
11869      metrics.runnable_polls.load(Ordering::Relaxed),
11870      CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
11871    );
11872    assert_eq!(
11873      CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
11874      2,
11875      "remaining hot work must continue through a fresh host turn"
11876    );
11877
11878    executor.drive_host_turn();
11879    futures::executor::block_on(task);
11880    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11881  }
11882
11883  #[cfg(not(target_family = "wasm"))]
11884  #[test]
11885  fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
11886    const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
11887
11888    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11889    let metrics = Arc::new(RuntimeMetrics::default());
11890    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11891      Arc::clone(&metrics),
11892      count_current_thread_blocking_budget_host_dispatch,
11893    ));
11894    let work = GenerationWork::new();
11895    let observed = Arc::new(Mutex::new(Vec::new()));
11896
11897    let task_executor = Arc::clone(&executor);
11898    let wake_executor = Arc::clone(&executor);
11899    let producer_executor = Arc::clone(&executor);
11900    let producer_work = Arc::clone(&work);
11901    let producer_observed = Arc::clone(&observed);
11902    let outer_registration = work
11903      .try_register_work()
11904      .expect("the outer blocking job must be accepted");
11905    let (runnable, task) = async_task::spawn(
11906      async move {
11907        let queued = task_executor
11908          .schedule_blocking(
11909            move || {
11910              std::thread::spawn(move || {
11911                (0..QUEUED_BLOCKING_JOBS)
11912                  .map(|index| {
11913                    let registration = producer_work
11914                      .try_register_work()
11915                      .expect("queued blocking work must be accepted");
11916                    let observed = Arc::clone(&producer_observed);
11917                    producer_executor.schedule_blocking(
11918                      move || {
11919                        observed
11920                          .lock()
11921                          .unwrap_or_else(std::sync::PoisonError::into_inner)
11922                          .push(index);
11923                      },
11924                      registration,
11925                    )
11926                  })
11927                  .collect::<Vec<_>>()
11928              })
11929              .join()
11930              .expect("the blocking producer must finish")
11931            },
11932            outer_registration,
11933          )
11934          .await
11935          .expect("the inline blocking job must finish");
11936        for handle in queued {
11937          handle.await.expect("queued blocking work must finish");
11938        }
11939      },
11940      move |runnable| wake_executor.schedule(runnable),
11941    );
11942
11943    executor.schedule(runnable);
11944    executor.drive_host_turn();
11945
11946    let first_turn = observed
11947      .lock()
11948      .unwrap_or_else(std::sync::PoisonError::into_inner)
11949      .clone();
11950    assert!(
11951      !first_turn.is_empty(),
11952      "the first host turn must service queued blocking work"
11953    );
11954    assert!(
11955      first_turn.len() < QUEUED_BLOCKING_JOBS,
11956      "inline lane release must not drain the entire blocking FIFO in one host turn"
11957    );
11958    assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
11959    assert_eq!(
11960      CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
11961      2,
11962      "remaining blocking work must continue through a fresh host turn"
11963    );
11964
11965    let mut continuation_turns = 0;
11966    while observed
11967      .lock()
11968      .unwrap_or_else(std::sync::PoisonError::into_inner)
11969      .len()
11970      < QUEUED_BLOCKING_JOBS
11971    {
11972      continuation_turns += 1;
11973      assert!(
11974        continuation_turns <= QUEUED_BLOCKING_JOBS,
11975        "queued blocking work failed to make bounded host-turn progress"
11976      );
11977      executor.drive_host_turn();
11978    }
11979    executor.drive_host_turn();
11980    futures::executor::block_on(task);
11981
11982    assert_eq!(
11983      *observed
11984        .lock()
11985        .unwrap_or_else(std::sync::PoisonError::into_inner),
11986      (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
11987      "blocking jobs must preserve FIFO order across host turns"
11988    );
11989    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11990  }
11991
11992  #[cfg(not(target_family = "wasm"))]
11993  #[test]
11994  fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
11995    use std::sync::mpsc;
11996
11997    struct SelfWakingFuture {
11998      remaining: usize,
11999    }
12000
12001    impl Future for SelfWakingFuture {
12002      type Output = ();
12003
12004      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12005        if self.remaining == 0 {
12006          return Poll::Ready(());
12007        }
12008        self.remaining -= 1;
12009        cx.waker().wake_by_ref();
12010        Poll::Pending
12011      }
12012    }
12013
12014    let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
12015    let old_generation = controller.backend().generation();
12016    controller
12017      .try_spawn_detached(SelfWakingFuture {
12018        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12019      })
12020      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
12021    let dispatch = controller_current_thread_dispatch(&controller);
12022
12023    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12024    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12025    let driver_controller = Arc::clone(&controller);
12026    let driver = std::thread::spawn(move || {
12027      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12028        *slot.borrow_mut() = Some(Box::new(move || {
12029          dispatch_entered_tx.send(()).unwrap();
12030          release_dispatch_rx.recv().unwrap();
12031        }));
12032      });
12033      driver_controller.drive_current_thread_dispatch(dispatch, false);
12034    });
12035    dispatch_entered_rx
12036      .recv_timeout(Duration::from_secs(2))
12037      .expect("the bounded host turn must begin publishing its continuation");
12038
12039    let (shutdown_tx, shutdown_rx) = mpsc::channel();
12040    let shutdown_controller = Arc::clone(&controller);
12041    let shutdown = std::thread::spawn(move || {
12042      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12043    });
12044    wait_until("CurrentThread shutdown to leave Running", || {
12045      let state = controller
12046        .state
12047        .lock()
12048        .unwrap_or_else(std::sync::PoisonError::into_inner);
12049      !matches!(
12050        &state.lifecycle,
12051        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12052      )
12053    });
12054
12055    let (restart_tx, restart_rx) = mpsc::channel();
12056    let restart_controller = Arc::clone(&controller);
12057    let restart = std::thread::spawn(move || {
12058      let result = restart_controller
12059        .start()
12060        .map(|()| restart_controller.backend().generation());
12061      restart_tx.send(result).unwrap();
12062    });
12063
12064    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12065    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12066    let shutdown_overlapped = early_shutdown.is_some();
12067    let restart_overlapped = early_restart.is_some();
12068    release_dispatch_tx.send(()).unwrap();
12069    driver.join().unwrap();
12070
12071    let shutdown_result = early_shutdown.unwrap_or_else(|| {
12072      shutdown_rx
12073        .recv_timeout(Duration::from_secs(2))
12074        .expect("shutdown must finish after continuation publication retires")
12075    });
12076    shutdown_result.unwrap();
12077    let new_generation = early_restart.unwrap_or_else(|| {
12078      restart_rx
12079        .recv_timeout(Duration::from_secs(2))
12080        .expect("restart must follow continuation publication and shutdown")
12081    });
12082    let new_generation = new_generation.unwrap();
12083    shutdown.join().unwrap();
12084    restart.join().unwrap();
12085
12086    assert!(
12087      !shutdown_overlapped,
12088      "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
12089    );
12090    assert!(
12091      !restart_overlapped,
12092      "restart must not overlap an old-generation continuation dispatch"
12093    );
12094    assert_ne!(new_generation, old_generation);
12095    controller.shutdown().unwrap();
12096  }
12097
12098  #[cfg(not(target_family = "wasm"))]
12099  #[test]
12100  fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
12101    use std::sync::mpsc;
12102
12103    let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
12104    let old_generation = controller.backend().generation();
12105    let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
12106    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12107    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12108    let submit_controller = Arc::clone(&controller);
12109    let reentrant_controller = Arc::clone(&controller);
12110    let submitter = std::thread::spawn(move || {
12111      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12112        *slot.borrow_mut() = Some(Box::new(move || {
12113          reentrant_shutdown_tx
12114            .send(reentrant_controller.shutdown())
12115            .unwrap();
12116          dispatch_entered_tx.send(()).unwrap();
12117          release_dispatch_rx.recv().unwrap();
12118        }));
12119      });
12120      submit_controller
12121        .try_spawn_detached(std::future::pending::<()>())
12122        .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
12123    });
12124
12125    let reentrant_shutdown = reentrant_shutdown_rx
12126      .recv_timeout(Duration::from_secs(2))
12127      .expect("dispatch publication must not self-deadlock on reentrant shutdown");
12128    dispatch_entered_rx
12129      .recv_timeout(Duration::from_secs(2))
12130      .expect("the initial host dispatch publication must reach the injected pause");
12131
12132    let (shutdown_tx, shutdown_rx) = mpsc::channel();
12133    let shutdown_controller = Arc::clone(&controller);
12134    let shutdown = std::thread::spawn(move || {
12135      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12136    });
12137    wait_until("CurrentThread shutdown to leave Running", || {
12138      let state = controller
12139        .state
12140        .lock()
12141        .unwrap_or_else(std::sync::PoisonError::into_inner);
12142      !matches!(
12143        &state.lifecycle,
12144        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12145      )
12146    });
12147
12148    let (restart_tx, restart_rx) = mpsc::channel();
12149    let restart_controller = Arc::clone(&controller);
12150    let restart = std::thread::spawn(move || {
12151      let result = restart_controller
12152        .start()
12153        .map(|()| restart_controller.backend().generation());
12154      restart_tx.send(result).unwrap();
12155    });
12156
12157    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12158    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12159    let shutdown_overlapped = early_shutdown.is_some();
12160    let restart_overlapped = early_restart.is_some();
12161    release_dispatch_tx.send(()).unwrap();
12162    submitter.join().unwrap();
12163
12164    let shutdown_result = early_shutdown.unwrap_or_else(|| {
12165      shutdown_rx
12166        .recv_timeout(Duration::from_secs(2))
12167        .expect("shutdown must finish after initial dispatch publication retires")
12168    });
12169    shutdown_result.unwrap();
12170    let new_generation = early_restart.unwrap_or_else(|| {
12171      restart_rx
12172        .recv_timeout(Duration::from_secs(2))
12173        .expect("restart must follow initial dispatch publication and shutdown")
12174    });
12175    let new_generation = new_generation.unwrap();
12176    shutdown.join().unwrap();
12177    restart.join().unwrap();
12178
12179    let reentrant_error = reentrant_shutdown
12180      .expect_err("dispatch publication must be generation-scoped scheduler work");
12181    assert!(
12182      reentrant_error
12183        .to_string()
12184        .contains("work running on that runtime")
12185    );
12186    assert!(
12187      !shutdown_overlapped,
12188      "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
12189    );
12190    assert!(
12191      !restart_overlapped,
12192      "restart must not overlap an old-generation host dispatch call"
12193    );
12194    assert_ne!(new_generation, old_generation);
12195    controller.shutdown().unwrap();
12196  }
12197
12198  #[test]
12199  fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
12200    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12201      .lock()
12202      .unwrap()
12203      .clear();
12204    CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12205    let metrics = Arc::new(RuntimeMetrics::default());
12206    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12207      Arc::clone(&metrics),
12208      panic_once_current_thread_host_dispatch,
12209    ));
12210    let scheduler = Arc::clone(&executor);
12211    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12212      scheduler.schedule(runnable)
12213    });
12214
12215    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
12216    assert!(
12217      publication.is_err(),
12218      "the injected host dispatch panic must unwind"
12219    );
12220    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12221    {
12222      let scheduler = executor
12223        .scheduler_idle_lock
12224        .lock()
12225        .unwrap_or_else(std::sync::PoisonError::into_inner);
12226      assert_eq!(scheduler.active_dispatch_calls, 0);
12227      assert!(scheduler.dispatch_publications.is_empty());
12228    }
12229
12230    executor.request_drain_if_queued();
12231
12232    assert_ne!(dispatch, 0);
12233    assert_eq!(
12234      *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12235        .lock()
12236        .unwrap(),
12237      vec![dispatch, dispatch],
12238      "a later host registration must retry the same pending capability"
12239    );
12240    {
12241      let scheduler = executor
12242        .scheduler_idle_lock
12243        .lock()
12244        .unwrap_or_else(std::sync::PoisonError::into_inner);
12245      assert_eq!(scheduler.active_dispatch_calls, 0);
12246      assert!(scheduler.dispatch_publications.is_empty());
12247    }
12248    executor.drive_host_turn_for_dispatch(dispatch);
12249    assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
12250    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12251  }
12252
12253  #[cfg(not(target_family = "wasm"))]
12254  #[test]
12255  fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
12256    use std::sync::mpsc;
12257
12258    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12259      .lock()
12260      .unwrap()
12261      .clear();
12262    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12263    let metrics = Arc::new(RuntimeMetrics::default());
12264    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12265      Arc::clone(&metrics),
12266      panic_once_current_thread_armed_republish_host_dispatch,
12267    ));
12268    let scheduler = Arc::clone(&executor);
12269    let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
12270      scheduler.schedule(runnable)
12271    });
12272
12273    let (called_tx, called_rx) = mpsc::channel();
12274    let (release_tx, release_rx) = mpsc::channel();
12275    let publishing_executor = Arc::clone(&executor);
12276    let publication = std::thread::spawn(move || {
12277      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12278        *slot.borrow_mut() = Some(Box::new(move || {
12279          called_tx.send(()).unwrap();
12280          release_rx.recv().unwrap();
12281        }));
12282      });
12283      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12284    });
12285    called_rx
12286      .recv_timeout(Duration::from_secs(2))
12287      .expect("the first publication must pause before calling the host dispatcher");
12288
12289    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12290    executor.request_drain_if_queued();
12291    let publication_state = {
12292      let scheduler = executor
12293        .scheduler_idle_lock
12294        .lock()
12295        .unwrap_or_else(std::sync::PoisonError::into_inner);
12296      (
12297        scheduler.active_dispatch_calls,
12298        scheduler.dispatch_publications.len(),
12299        scheduler.dispatch_publications[0].dispatch,
12300        scheduler.dispatch_publications[0].republish_requested,
12301      )
12302    };
12303    release_tx.send(()).unwrap();
12304
12305    let publication = publication.join().unwrap();
12306    assert!(
12307      publication.is_err(),
12308      "the original host-dispatch panic must resume"
12309    );
12310    assert_ne!(dispatch, 0);
12311    assert_eq!(publication_state, (1, 1, dispatch, true));
12312    assert_eq!(
12313      *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12314        .lock()
12315        .unwrap(),
12316      vec![dispatch, dispatch],
12317      "the armed request must retry automatically before the original panic resumes"
12318    );
12319    {
12320      let scheduler = executor
12321        .scheduler_idle_lock
12322        .lock()
12323        .unwrap_or_else(std::sync::PoisonError::into_inner);
12324      assert_eq!(scheduler.active_dispatch_calls, 0);
12325      assert!(scheduler.dispatch_publications.is_empty());
12326    }
12327    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
12328    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12329
12330    executor.drive_host_turn_for_dispatch(dispatch);
12331    assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
12332    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12333  }
12334
12335  #[cfg(not(target_family = "wasm"))]
12336  #[test]
12337  fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
12338    use std::sync::mpsc;
12339
12340    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12341      .lock()
12342      .unwrap()
12343      .clear();
12344    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
12345    CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12346    CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12347    {
12348      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12349        .state
12350        .lock()
12351        .unwrap_or_else(std::sync::PoisonError::into_inner);
12352      assert!(state.entries.is_empty());
12353      assert!(state.dispatches.is_empty());
12354    }
12355    let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12356      .register(Arc::new(FailDuringAuthoritativePanicPublication));
12357    let metrics = Arc::new(RuntimeMetrics::default());
12358    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12359      Arc::clone(&metrics),
12360      dispatch_authoritative_panic_current_thread_tasks,
12361      &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
12362    ));
12363    let scheduler = Arc::clone(&executor);
12364    let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
12365      scheduler.schedule(runnable)
12366    });
12367
12368    let (called_tx, called_rx) = mpsc::channel();
12369    let (release_tx, release_rx) = mpsc::channel();
12370    let publishing_executor = Arc::clone(&executor);
12371    let publication = std::thread::spawn(move || {
12372      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12373        *slot.borrow_mut() = Some(Box::new(move || {
12374          called_tx.send(()).unwrap();
12375          release_rx.recv().unwrap();
12376        }));
12377      });
12378      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12379    });
12380    called_rx
12381      .recv_timeout(Duration::from_secs(2))
12382      .expect("the first publication must pause before raising its panic");
12383
12384    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12385    executor.request_drain_if_queued();
12386    release_tx.send(()).unwrap();
12387
12388    let payload = publication
12389      .join()
12390      .unwrap()
12391      .expect_err("the first host-dispatch panic must resume");
12392    assert!(
12393      payload.is::<FirstHostileCurrentThreadPanic>(),
12394      "the first panic must remain authoritative over nested recovery"
12395    );
12396    assert_eq!(
12397      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12398      1,
12399      "the later hostile payload must be discarded under containment"
12400    );
12401    assert_eq!(
12402      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12403      0,
12404      "the authoritative payload must remain owned by the resumed unwind"
12405    );
12406    discard_panic_payload(payload);
12407    assert_eq!(
12408      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12409      1
12410    );
12411
12412    let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12413      .lock()
12414      .unwrap()
12415      .clone();
12416    assert_eq!(dispatches.len(), 3);
12417    let replacement_dispatch = dispatches[2];
12418    assert_eq!(dispatches[0], failed_dispatch);
12419    assert_eq!(dispatches[1], failed_dispatch);
12420    assert_ne!(replacement_dispatch, failed_dispatch);
12421    assert_eq!(
12422      executor.dispatch_pending.load(Ordering::Acquire),
12423      replacement_dispatch
12424    );
12425    assert_eq!(
12426      executor.recovery_dispatch.load(Ordering::Acquire),
12427      replacement_dispatch
12428    );
12429    {
12430      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12431        .state
12432        .lock()
12433        .unwrap_or_else(std::sync::PoisonError::into_inner);
12434      assert!(
12435        !state.dispatches.contains_key(&failed_dispatch),
12436        "nested recovery unwind must retire the exact failed epoch"
12437      );
12438      assert!(!state.dispatches.contains_key(&replacement_dispatch));
12439    }
12440
12441    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
12442    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
12443    let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12444      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
12445    executor.request_drain_if_queued();
12446
12447    assert_eq!(
12448      *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12449        .lock()
12450        .unwrap(),
12451      vec![
12452        failed_dispatch,
12453        failed_dispatch,
12454        replacement_dispatch,
12455        replacement_dispatch
12456      ],
12457      "retry must retain the replacement reserved before the nested panic"
12458    );
12459    let delivery = healthy.deliveries()[0];
12460    assert_eq!(
12461      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
12462      Some(replacement_dispatch)
12463    );
12464    let mut host_turn = executor
12465      .try_admit_host_turn(replacement_dispatch)
12466      .expect("the retried replacement delivery must be admitted");
12467    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
12468    host_turn.drive();
12469    assert!(
12470      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12471        .finish_delivery(delivery, false)
12472        .failed_dispatches
12473        .is_empty()
12474    );
12475    assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
12476    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12477
12478    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
12479    let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12480      .state
12481      .lock()
12482      .unwrap_or_else(std::sync::PoisonError::into_inner);
12483    assert!(state.entries.is_empty());
12484    assert!(state.dispatches.is_empty());
12485  }
12486
12487  #[cfg(not(target_family = "wasm"))]
12488  #[test]
12489  fn current_thread_shutdown_suppresses_queued_republish() {
12490    use std::sync::mpsc;
12491
12492    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12493    let metrics = Arc::new(RuntimeMetrics::default());
12494    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12495      Arc::clone(&metrics),
12496      count_current_thread_shutdown_republish_host_dispatch,
12497    ));
12498    let scheduler = Arc::clone(&executor);
12499    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
12500      scheduler.schedule(runnable)
12501    });
12502
12503    let (returned_tx, returned_rx) = mpsc::channel();
12504    let (release_tx, release_rx) = mpsc::channel();
12505    let publishing_executor = Arc::clone(&executor);
12506    let publication = std::thread::spawn(move || {
12507      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12508        *slot.borrow_mut() = Some(Box::new(move || {
12509          returned_tx.send(()).unwrap();
12510          release_rx.recv().unwrap();
12511        }));
12512      });
12513      publishing_executor.schedule(runnable);
12514    });
12515    returned_rx
12516      .recv_timeout(Duration::from_secs(2))
12517      .expect("the initial publication must reach its completion barrier");
12518
12519    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12520    executor.request_drain_if_queued();
12521    let publication_state = {
12522      let scheduler = executor
12523        .scheduler_idle_lock
12524        .lock()
12525        .unwrap_or_else(std::sync::PoisonError::into_inner);
12526      (
12527        scheduler.active_dispatch_calls,
12528        scheduler.dispatch_publications.len(),
12529        scheduler.dispatch_publications[0].dispatch,
12530        scheduler.dispatch_publications[0].republish_requested,
12531      )
12532    };
12533    executor.begin_shutdown();
12534    release_tx.send(()).unwrap();
12535    publication.join().unwrap();
12536    executor.wait_until_scheduler_idle();
12537
12538    assert_ne!(dispatch, 0);
12539    assert_eq!(publication_state, (1, 1, dispatch, true));
12540    assert_eq!(
12541      CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
12542      1
12543    );
12544    {
12545      let scheduler = executor
12546        .scheduler_idle_lock
12547        .lock()
12548        .unwrap_or_else(std::sync::PoisonError::into_inner);
12549      assert_eq!(scheduler.active_dispatch_calls, 0);
12550      assert!(scheduler.dispatch_publications.is_empty());
12551    }
12552    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
12553    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
12554    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12555    assert_eq!(futures::executor::block_on(task.fallible()), None);
12556  }
12557
12558  #[cfg(not(target_family = "wasm"))]
12559  #[test]
12560  fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
12561    use std::sync::mpsc;
12562
12563    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
12564      .lock()
12565      .unwrap()
12566      .clear();
12567    let metrics = Arc::new(RuntimeMetrics::default());
12568    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12569      Arc::clone(&metrics),
12570      record_current_thread_republish_rearm_dispatch,
12571    ));
12572    let scheduler = Arc::clone(&executor);
12573    let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
12574      scheduler.schedule(runnable)
12575    });
12576
12577    let (first_returned_tx, first_returned_rx) = mpsc::channel();
12578    let (release_first_tx, release_first_rx) = mpsc::channel();
12579    let (second_returned_tx, second_returned_rx) = mpsc::channel();
12580    let (release_second_tx, release_second_rx) = mpsc::channel();
12581    let publishing_executor = Arc::clone(&executor);
12582    let publication = std::thread::spawn(move || {
12583      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12584        *slot.borrow_mut() = Some(Box::new(move || {
12585          first_returned_tx.send(()).unwrap();
12586          release_first_rx.recv().unwrap();
12587          CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12588            *slot.borrow_mut() = Some(Box::new(move || {
12589              second_returned_tx.send(()).unwrap();
12590              release_second_rx.recv().unwrap();
12591            }));
12592          });
12593        }));
12594      });
12595      publishing_executor.schedule(runnable);
12596    });
12597    first_returned_rx
12598      .recv_timeout(Duration::from_secs(2))
12599      .expect("the first publication must reach its completion barrier");
12600
12601    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12602    executor.request_drain_if_queued();
12603    executor.request_drain_if_queued();
12604    let first_publication_state = {
12605      let scheduler = executor
12606        .scheduler_idle_lock
12607        .lock()
12608        .unwrap_or_else(std::sync::PoisonError::into_inner);
12609      (
12610        scheduler.active_dispatch_calls,
12611        scheduler.dispatch_publications.len(),
12612        scheduler.dispatch_publications[0].dispatch,
12613        scheduler.dispatch_publications[0].republish_requested,
12614      )
12615    };
12616    release_first_tx.send(()).unwrap();
12617    second_returned_rx
12618      .recv_timeout(Duration::from_secs(2))
12619      .expect("the second publication must reach its completion barrier");
12620
12621    executor.request_drain_if_queued();
12622    executor.request_drain_if_queued();
12623    let second_publication_state = {
12624      let scheduler = executor
12625        .scheduler_idle_lock
12626        .lock()
12627        .unwrap_or_else(std::sync::PoisonError::into_inner);
12628      (
12629        scheduler.active_dispatch_calls,
12630        scheduler.dispatch_publications.len(),
12631        scheduler.dispatch_publications[0].dispatch,
12632        scheduler.dispatch_publications[0].republish_requested,
12633      )
12634    };
12635    release_second_tx.send(()).unwrap();
12636    publication.join().unwrap();
12637
12638    assert_ne!(dispatch, 0);
12639    assert_eq!(first_publication_state, (1, 1, dispatch, true));
12640    assert_eq!(second_publication_state, (1, 1, dispatch, true));
12641    assert_eq!(
12642      *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
12643      vec![dispatch, dispatch, dispatch],
12644      "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
12645    );
12646    {
12647      let scheduler = executor
12648        .scheduler_idle_lock
12649        .lock()
12650        .unwrap_or_else(std::sync::PoisonError::into_inner);
12651      assert_eq!(scheduler.active_dispatch_calls, 0);
12652      assert!(scheduler.dispatch_publications.is_empty());
12653    }
12654    executor.drive_host_turn_for_dispatch(dispatch);
12655    assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
12656    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12657  }
12658
12659  #[cfg(not(target_family = "wasm"))]
12660  #[test]
12661  fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
12662    use std::sync::mpsc;
12663
12664    CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12665      .lock()
12666      .unwrap()
12667      .clear();
12668    let metrics = Arc::new(RuntimeMetrics::default());
12669    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12670      Arc::clone(&metrics),
12671      reject_initial_current_thread_publication_once,
12672    ));
12673    let scheduler = Arc::clone(&executor);
12674    let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
12675      scheduler.schedule(runnable)
12676    });
12677
12678    let (returned_tx, returned_rx) = mpsc::channel();
12679    let (release_tx, release_rx) = mpsc::channel();
12680    let publishing_executor = Arc::clone(&executor);
12681    let publication = std::thread::spawn(move || {
12682      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12683        *slot.borrow_mut() = Some(Box::new(move || {
12684          returned_tx.send(()).unwrap();
12685          release_rx.recv().unwrap();
12686        }));
12687      });
12688      publishing_executor.schedule(runnable);
12689    });
12690    returned_rx
12691      .recv_timeout(Duration::from_secs(2))
12692      .expect("the unavailable initial publication must reach its completion barrier");
12693
12694    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12695    executor.request_drain_if_queued();
12696    let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12697      .lock()
12698      .unwrap()
12699      .len();
12700
12701    release_tx.send(()).unwrap();
12702    publication.join().unwrap();
12703
12704    let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12705      .lock()
12706      .unwrap()
12707      .clone();
12708    assert_ne!(dispatch, 0);
12709    assert_eq!(
12710      attempts_while_unavailable_is_paused, 1,
12711      "a racing host request must join the in-flight publication instead of retiring it independently"
12712    );
12713    assert_eq!(
12714      dispatches,
12715      vec![dispatch, dispatch],
12716      "the publication owner must retry the same internal capability after the new host joins"
12717    );
12718    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
12719    executor.drive_host_turn_for_dispatch(dispatch);
12720    assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
12721    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12722  }
12723
12724  #[cfg(not(target_family = "wasm"))]
12725  #[test]
12726  fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
12727    use std::sync::mpsc;
12728
12729    CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12730      .lock()
12731      .unwrap()
12732      .clear();
12733    let metrics = Arc::new(RuntimeMetrics::default());
12734    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12735      Arc::clone(&metrics),
12736      reject_current_thread_replacement_publication_once,
12737    ));
12738    let scheduler = Arc::clone(&executor);
12739    let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
12740      scheduler.schedule(runnable)
12741    });
12742    executor.schedule(runnable);
12743    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12744    assert_ne!(failed_dispatch, 0);
12745
12746    let (returned_tx, returned_rx) = mpsc::channel();
12747    let (release_tx, release_rx) = mpsc::channel();
12748    let cancelling_executor = Arc::clone(&executor);
12749    let cancellation = std::thread::spawn(move || {
12750      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12751        *slot.borrow_mut() = Some(Box::new(move || {
12752          returned_tx.send(()).unwrap();
12753          release_rx.recv().unwrap();
12754        }));
12755      });
12756      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
12757    });
12758    returned_rx
12759      .recv_timeout(Duration::from_secs(2))
12760      .expect("the unavailable replacement publication must reach its completion barrier");
12761
12762    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12763    executor.request_drain_if_queued();
12764    let attempts_while_unavailable_is_paused =
12765      CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12766        .lock()
12767        .unwrap()
12768        .len();
12769
12770    release_tx.send(()).unwrap();
12771    cancellation.join().unwrap();
12772
12773    let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12774      .lock()
12775      .unwrap()
12776      .clone();
12777    assert_ne!(replacement_dispatch, 0);
12778    assert_ne!(replacement_dispatch, failed_dispatch);
12779    assert_eq!(
12780      attempts_while_unavailable_is_paused, 2,
12781      "a racing host request must join the in-flight replacement publication"
12782    );
12783    assert_eq!(
12784      dispatches,
12785      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
12786      "republication must reuse the sole replacement capability instead of allocating another"
12787    );
12788    assert_eq!(
12789      executor.dispatch_pending.load(Ordering::Acquire),
12790      replacement_dispatch
12791    );
12792    assert_eq!(
12793      executor.recovery_dispatch.load(Ordering::Acquire),
12794      replacement_dispatch
12795    );
12796    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12797
12798    executor.drive_host_turn_for_dispatch(replacement_dispatch);
12799    assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
12800    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
12801    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12802  }
12803
12804  #[cfg(not(target_family = "wasm"))]
12805  #[test]
12806  fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
12807    use std::sync::mpsc;
12808
12809    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12810      .lock()
12811      .unwrap()
12812      .clear();
12813    {
12814      let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12815        .state
12816        .lock()
12817        .unwrap_or_else(std::sync::PoisonError::into_inner);
12818      assert!(state.entries.is_empty());
12819      assert!(state.dispatches.is_empty());
12820    }
12821    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
12822    let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12823      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
12824    let metrics = Arc::new(RuntimeMetrics::default());
12825    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12826      Arc::clone(&metrics),
12827      fail_current_thread_replacement_publication_once,
12828      &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
12829    ));
12830    let scheduler = Arc::clone(&executor);
12831    let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
12832      scheduler.schedule(runnable)
12833    });
12834    executor.schedule(runnable);
12835    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12836    assert_ne!(failed_dispatch, 0);
12837    let initial_delivery = initial.deliveries()[0];
12838    let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12839      .finish_delivery(initial_delivery, true);
12840    let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
12841      panic!("the initial accepted delivery must report one exact failure");
12842    };
12843    assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
12844    let failed_dispatch_failure = *failed_dispatch_failure;
12845    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
12846    let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12847      .register(Arc::new(FailDuringReplacementRepublishPublication));
12848
12849    let (returned_tx, returned_rx) = mpsc::channel();
12850    let (release_tx, release_rx) = mpsc::channel();
12851    let cancelling_executor = Arc::clone(&executor);
12852    let cancellation = std::thread::spawn(move || {
12853      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12854        *slot.borrow_mut() = Some(Box::new(move || {
12855          returned_tx.send(()).unwrap();
12856          release_rx.recv().unwrap();
12857        }));
12858      });
12859      cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
12860    });
12861    returned_rx
12862      .recv_timeout(Duration::from_secs(2))
12863      .expect("the failed replacement publication must reach its completion barrier");
12864
12865    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12866    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
12867    let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12868      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
12869    executor.request_drain_if_queued();
12870    let publication_state = {
12871      let scheduler = executor
12872        .scheduler_idle_lock
12873        .lock()
12874        .unwrap_or_else(std::sync::PoisonError::into_inner);
12875      (
12876        scheduler.active_dispatch_calls,
12877        scheduler.dispatch_publications.len(),
12878        scheduler.dispatch_publications[0].dispatch,
12879        scheduler.dispatch_publications[0].republish_requested,
12880      )
12881    };
12882    release_tx.send(()).unwrap();
12883    cancellation.join().unwrap();
12884
12885    assert_ne!(replacement_dispatch, 0);
12886    assert_ne!(replacement_dispatch, failed_dispatch);
12887    assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
12888    assert_eq!(
12889      *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12890        .lock()
12891        .unwrap(),
12892      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
12893      "the newly armed host request must receive the exact replacement before cancellation"
12894    );
12895    assert_eq!(
12896      executor.dispatch_pending.load(Ordering::Acquire),
12897      replacement_dispatch
12898    );
12899    assert_eq!(
12900      executor.recovery_dispatch.load(Ordering::Acquire),
12901      replacement_dispatch
12902    );
12903    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12904
12905    let delivery = healthy.deliveries()[0];
12906    assert_eq!(delivery.dispatch, replacement_dispatch);
12907    assert_eq!(
12908      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12909        .claim_delivery(delivery.capability()),
12910      Some(replacement_dispatch)
12911    );
12912    let mut host_turn = executor
12913      .try_admit_host_turn(replacement_dispatch)
12914      .expect("the rearmed replacement delivery must be admitted");
12915    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
12916    host_turn.drive();
12917    assert!(
12918      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12919        .finish_delivery(delivery, false)
12920        .failed_dispatches
12921        .is_empty()
12922    );
12923    assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
12924    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
12925    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12926
12927    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
12928    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
12929    let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12930      .state
12931      .lock()
12932      .unwrap_or_else(std::sync::PoisonError::into_inner);
12933    assert!(state.entries.is_empty());
12934    assert!(state.dispatches.is_empty());
12935  }
12936
12937  #[cfg(not(target_family = "wasm"))]
12938  #[test]
12939  fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
12940    use std::sync::mpsc;
12941
12942    {
12943      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
12944        .state
12945        .lock()
12946        .unwrap_or_else(std::sync::PoisonError::into_inner);
12947      assert!(state.entries.is_empty());
12948      assert!(state.dispatches.is_empty());
12949    }
12950    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
12951    let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
12952      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
12953    let metrics = Arc::new(RuntimeMetrics::default());
12954    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12955      Arc::clone(&metrics),
12956      dispatch_stale_failure_current_thread_tasks,
12957      &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
12958    ));
12959    let scheduler = Arc::clone(&executor);
12960    let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
12961      scheduler.schedule(runnable)
12962    });
12963    executor.schedule(runnable);
12964
12965    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12966    let initial_delivery = initial.deliveries()[0];
12967    let delayed_completion =
12968      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
12969    let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
12970      panic!("the initial accepted delivery must report one exact failure");
12971    };
12972    assert_eq!(
12973      *delayed_failure,
12974      current_thread_dispatch_failure(dispatch, 0)
12975    );
12976    let delayed_failure = *delayed_failure;
12977
12978    let (returned_tx, returned_rx) = mpsc::channel();
12979    let (release_tx, release_rx) = mpsc::channel();
12980    let publishing_executor = Arc::clone(&executor);
12981    let publication = std::thread::spawn(move || {
12982      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12983        *slot.borrow_mut() = Some(Box::new(move || {
12984          returned_tx.send(()).unwrap();
12985          release_rx.recv().unwrap();
12986        }));
12987      });
12988      publishing_executor.request_drain_if_queued();
12989    });
12990    returned_rx
12991      .recv_timeout(Duration::from_secs(2))
12992      .expect("the old failed epoch must pause before executor recovery");
12993
12994    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
12995    let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
12996      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
12997    executor.request_drain_if_queued();
12998    release_tx.send(()).unwrap();
12999    publication.join().unwrap();
13000
13001    let healthy_delivery = healthy.deliveries()[0];
13002    let delayed_initial_delivery = initial.deliveries()[1];
13003    {
13004      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13005        .state
13006        .lock()
13007        .unwrap_or_else(std::sync::PoisonError::into_inner);
13008      let dispatch_state = state
13009        .dispatches
13010        .get(&dispatch)
13011        .expect("the rearmed dispatch must remain registered");
13012      assert_eq!(dispatch_state.epoch, 1);
13013      assert!(!dispatch_state.is_cancelling());
13014    }
13015
13016    executor.cancel_host_dispatch(delayed_failure);
13017
13018    assert_eq!(
13019      executor.dispatch_pending.load(Ordering::Acquire),
13020      dispatch,
13021      "the delayed old-epoch failure must not consume the rearmed executor capability"
13022    );
13023    assert_eq!(
13024      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13025        .state
13026        .lock()
13027        .unwrap_or_else(std::sync::PoisonError::into_inner)
13028        .dispatches
13029        .get(&dispatch)
13030        .map(|state| (state.epoch, state.phase)),
13031      Some((1, CurrentThreadDispatchPhase::Pending)),
13032      "the delayed old-epoch cancellation must not remove the accepted rearm"
13033    );
13034
13035    assert_eq!(
13036      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
13037      Some(dispatch)
13038    );
13039    let mut host_turn = executor
13040      .try_admit_host_turn(dispatch)
13041      .expect("the current-epoch delivery must be admitted");
13042    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
13043    host_turn.drive();
13044    assert!(
13045      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13046        .finish_delivery(healthy_delivery, false)
13047        .failed_dispatches
13048        .is_empty()
13049    );
13050    assert!(
13051      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13052        .finish_delivery(delayed_initial_delivery, false)
13053        .failed_dispatches
13054        .is_empty()
13055    );
13056    assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
13057    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13058
13059    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
13060    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
13061    let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13062      .state
13063      .lock()
13064      .unwrap_or_else(std::sync::PoisonError::into_inner);
13065    assert!(state.entries.is_empty());
13066    assert!(state.dispatches.is_empty());
13067  }
13068
13069  #[cfg(not(target_family = "wasm"))]
13070  #[test]
13071  fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
13072    use std::sync::mpsc;
13073
13074    {
13075      let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13076        .state
13077        .lock()
13078        .unwrap_or_else(std::sync::PoisonError::into_inner);
13079      assert!(state.entries.is_empty());
13080      assert!(state.dispatches.is_empty());
13081    }
13082    let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13083      .register(Arc::new(FailDuringLateRegistrationPublication));
13084
13085    let metrics = Arc::new(RuntimeMetrics::default());
13086    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13087      Arc::clone(&metrics),
13088      dispatch_late_registration_current_thread_tasks,
13089      &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
13090    ));
13091    let scheduler = Arc::clone(&executor);
13092    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13093      scheduler.schedule(runnable)
13094    });
13095    metrics.runnable_scheduled();
13096    executor
13097      .queue
13098      .lock()
13099      .unwrap_or_else(std::sync::PoisonError::into_inner)
13100      .runnables
13101      .push_back(runnable);
13102    let replacement_dispatch = next_current_thread_dispatch_id();
13103    executor
13104      .recovery_dispatch
13105      .store(replacement_dispatch, Ordering::Release);
13106    executor
13107      .dispatch_pending
13108      .store(replacement_dispatch, Ordering::Release);
13109
13110    let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
13111    let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
13112    let publishing_executor = Arc::clone(&executor);
13113    let publication = std::thread::spawn(move || {
13114      CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13115        *slot.borrow_mut() = Some(Box::new(move || {
13116          owner_retired_tx.send(()).unwrap();
13117          request_blocked_rx
13118            .recv_timeout(Duration::from_secs(2))
13119            .expect("the binding-like drain request must wait for the failure transition");
13120        }));
13121      });
13122      publishing_executor.request_drain_if_queued();
13123    });
13124    owner_retired_rx
13125      .recv_timeout(Duration::from_secs(2))
13126      .expect("the failed replacement publication must retire its owner");
13127
13128    // Match the binding sequence: publish the host registration first, then
13129    // request service for work that may have accumulated before registration.
13130    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13131    let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13132      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13133    let requesting_executor = Arc::clone(&executor);
13134    let request = std::thread::spawn(move || {
13135      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13136        *slot.borrow_mut() = Some(Box::new(move || {
13137          request_blocked_tx.send(()).unwrap();
13138        }));
13139      });
13140      requesting_executor.request_drain_if_queued();
13141    });
13142
13143    publication.join().unwrap();
13144    request.join().unwrap();
13145
13146    assert!(
13147      healthy.deliveries().is_empty(),
13148      "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
13149    );
13150    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13151    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13152    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13153    assert_eq!(futures::executor::block_on(task.fallible()), None);
13154    {
13155      let scheduler = executor
13156        .scheduler_idle_lock
13157        .lock()
13158        .unwrap_or_else(std::sync::PoisonError::into_inner);
13159      assert_eq!(scheduler.active_dispatch_calls, 0);
13160      assert!(scheduler.dispatch_publications.is_empty());
13161    }
13162
13163    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
13164
13165    let scheduler = Arc::clone(&executor);
13166    let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
13167      scheduler.schedule(runnable)
13168    });
13169    executor.schedule(later_runnable);
13170    let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13171    let later_delivery = healthy.deliveries()[0];
13172    assert_eq!(
13173      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
13174      Some(later_dispatch)
13175    );
13176    let mut host_turn = executor
13177      .try_admit_host_turn(later_dispatch)
13178      .expect("the later exact delivery must be admitted");
13179    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
13180    host_turn.drive();
13181    assert!(
13182      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13183        .finish_delivery(later_delivery, false)
13184        .failed_dispatches
13185        .is_empty()
13186    );
13187    assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
13188    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13189
13190    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
13191    let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13192      .state
13193      .lock()
13194      .unwrap_or_else(std::sync::PoisonError::into_inner);
13195    assert!(state.entries.is_empty());
13196    assert!(state.dispatches.is_empty());
13197  }
13198
13199  #[cfg(not(target_family = "wasm"))]
13200  #[test]
13201  fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
13202  {
13203    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13204      .lock()
13205      .unwrap()
13206      .clear();
13207    {
13208      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13209        .state
13210        .lock()
13211        .unwrap_or_else(std::sync::PoisonError::into_inner);
13212      assert!(state.entries.is_empty());
13213      assert!(state.dispatches.is_empty());
13214    }
13215    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13216      .next_delivery
13217      .store(0, Ordering::Relaxed);
13218    let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13219      .register(Arc::new(FailDuringNestedUnwindPublication));
13220
13221    let metrics = Arc::new(RuntimeMetrics::default());
13222    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13223      Arc::clone(&metrics),
13224      dispatch_nested_unwind_current_thread_tasks,
13225      &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
13226    ));
13227    let scheduler = Arc::clone(&executor);
13228    let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
13229      scheduler.schedule(runnable)
13230    });
13231
13232    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13233      *slot.borrow_mut() = Some(Box::new(|| {
13234        CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13235          .next_delivery
13236          .store(u64::MAX, Ordering::Relaxed);
13237      }));
13238    });
13239    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
13240    assert!(
13241      publication.is_err(),
13242      "replacement delivery-id exhaustion must unwind through the outer failed publication"
13243    );
13244
13245    let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13246      .lock()
13247      .unwrap()
13248      .clone();
13249    assert_eq!(dispatches.len(), 2);
13250    let failed_dispatch = dispatches[0];
13251    let replacement_dispatch = dispatches[1];
13252    assert_ne!(failed_dispatch, replacement_dispatch);
13253    assert_eq!(
13254      executor.dispatch_pending.load(Ordering::Acquire),
13255      replacement_dispatch
13256    );
13257    assert_eq!(
13258      executor.recovery_dispatch.load(Ordering::Acquire),
13259      replacement_dispatch
13260    );
13261    {
13262      let scheduler = executor
13263        .scheduler_idle_lock
13264        .lock()
13265        .unwrap_or_else(std::sync::PoisonError::into_inner);
13266      assert_eq!(scheduler.active_dispatch_calls, 0);
13267      assert!(scheduler.dispatch_publications.is_empty());
13268    }
13269    {
13270      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13271        .state
13272        .lock()
13273        .unwrap_or_else(std::sync::PoisonError::into_inner);
13274      assert!(
13275        !state.dispatches.contains_key(&failed_dispatch),
13276        "outer unwind cleanup must retire the exact old cancelling dispatch"
13277      );
13278      assert!(
13279        !state.dispatches.contains_key(&replacement_dispatch),
13280        "the replacement publication guard must release its aborted registry state"
13281      );
13282    }
13283
13284    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13285      .next_delivery
13286      .store(1, Ordering::Relaxed);
13287    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
13288    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13289    let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13290      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13291
13292    executor.request_drain_if_queued();
13293
13294    assert_eq!(
13295      *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
13296      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13297      "retry must reuse the replacement reserved before the nested unwind"
13298    );
13299    let delivery = healthy.deliveries()[0];
13300    assert_eq!(delivery.dispatch, replacement_dispatch);
13301    assert_eq!(
13302      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
13303      Some(replacement_dispatch)
13304    );
13305    let mut host_turn = executor
13306      .try_admit_host_turn(replacement_dispatch)
13307      .expect("the retried replacement delivery must be admitted");
13308    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13309    host_turn.drive();
13310    assert!(
13311      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13312        .finish_delivery(delivery, false)
13313        .failed_dispatches
13314        .is_empty()
13315    );
13316    assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
13317    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13318
13319    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
13320    let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13321      .state
13322      .lock()
13323      .unwrap_or_else(std::sync::PoisonError::into_inner);
13324    assert!(state.entries.is_empty());
13325    assert!(state.dispatches.is_empty());
13326  }
13327
13328  #[test]
13329  fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
13330    reset_current_thread_recovery_host_dispatches();
13331    let metrics = Arc::new(RuntimeMetrics::default());
13332    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13333      Arc::clone(&metrics),
13334      count_current_thread_recovery_host_dispatch,
13335    ));
13336
13337    let scheduler = Arc::clone(&executor);
13338    let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
13339      scheduler.schedule(runnable)
13340    });
13341    executor.schedule(runnable);
13342    assert_eq!(current_thread_recovery_host_dispatches(), 1);
13343    let failed_dispatch = current_thread_dispatch(&executor);
13344
13345    // Model the host scheduler throwing after Rust accepted the callback but
13346    // before JavaScript could queue a fresh turn.
13347    executor.cancel_host_dispatch_for_test(failed_dispatch);
13348    assert_eq!(
13349      current_thread_recovery_host_dispatches(),
13350      2,
13351      "cancellation must request one replacement without another submission"
13352    );
13353    let replacement_dispatch = current_thread_dispatch(&executor);
13354    assert_ne!(replacement_dispatch, failed_dispatch);
13355    assert_eq!(
13356      executor.recovery_dispatch.load(Ordering::Acquire),
13357      replacement_dispatch
13358    );
13359
13360    executor.drive_host_turn_for_dispatch(replacement_dispatch);
13361    assert_eq!(futures::executor::block_on(task), 1);
13362    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13363    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13364  }
13365
13366  #[test]
13367  fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
13368    reset_current_thread_recovery_host_dispatches();
13369    let metrics = Arc::new(RuntimeMetrics::default());
13370    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13371      Arc::clone(&metrics),
13372      count_current_thread_recovery_host_dispatch,
13373    ));
13374    let backend =
13375      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13376
13377    let registration = backend
13378      .work
13379      .try_register_async()
13380      .expect("the backend must accept work");
13381    let task = spawn_registered(
13382      &backend,
13383      Arc::clone(&metrics),
13384      std::future::pending::<()>(),
13385      registration,
13386    );
13387    let failed_dispatch = current_thread_dispatch(&executor);
13388    executor.cancel_host_dispatch_for_test(failed_dispatch);
13389    let replacement_dispatch = current_thread_dispatch(&executor);
13390
13391    executor.cancel_host_dispatch_for_test(replacement_dispatch);
13392
13393    assert_eq!(
13394      current_thread_recovery_host_dispatches(),
13395      2,
13396      "a second scheduler failure must not create an unbounded retry loop"
13397    );
13398    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13399    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13400    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13401    assert_eq!(
13402      futures::executor::block_on(task).unwrap_err().to_string(),
13403      "async runtime stopped before the task completed",
13404      "terminal host failure must settle the original operation as cancelled"
13405    );
13406    assert_eq!(backend.work.state.lock().unwrap().active, 0);
13407
13408    let later_registration = backend
13409      .work
13410      .try_register_async()
13411      .expect("terminal cancellation must reopen acceptance");
13412    let later_task = spawn_registered(
13413      &backend,
13414      Arc::clone(&metrics),
13415      async { 2usize },
13416      later_registration,
13417    );
13418    assert_eq!(
13419      current_thread_recovery_host_dispatches(),
13420      3,
13421      "terminal cancellation must not permanently close the executor"
13422    );
13423    executor.drive_host_turn();
13424    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
13425  }
13426
13427  #[cfg(not(target_family = "wasm"))]
13428  #[test]
13429  fn current_thread_recovery_reservation_blocks_new_host_publication() {
13430    use std::sync::mpsc;
13431
13432    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13433    let metrics = Arc::new(RuntimeMetrics::default());
13434    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13435      Arc::clone(&metrics),
13436      count_current_thread_interleaved_recovery_host_dispatch,
13437    ));
13438    let backend =
13439      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13440
13441    let registration = backend
13442      .work
13443      .try_register_async()
13444      .expect("the backend must accept work");
13445    let task = spawn_registered(
13446      &backend,
13447      Arc::clone(&metrics),
13448      std::future::pending::<()>(),
13449      registration,
13450    );
13451    let failed_dispatch = current_thread_dispatch(&executor);
13452    assert_eq!(
13453      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13454      1
13455    );
13456
13457    let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
13458    let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
13459    let cancelling_executor = Arc::clone(&executor);
13460    let cancellation = std::thread::spawn(move || {
13461      CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
13462        *slot.borrow_mut() = Some(Box::new(move || {
13463          recovery_cleared_tx.send(()).unwrap();
13464          release_recovery_rx.recv().unwrap();
13465        }));
13466      });
13467      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
13468    });
13469    recovery_cleared_rx
13470      .recv_timeout(Duration::from_secs(2))
13471      .expect("failed dispatch cancellation must pause after consuming the old capability");
13472
13473    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13474    let (publication_done_tx, publication_done_rx) = mpsc::channel();
13475    let publishing_executor = Arc::clone(&executor);
13476    let publication = std::thread::spawn(move || {
13477      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13478        *slot.borrow_mut() = Some(Box::new(move || {
13479          publication_blocked_tx.send(()).unwrap();
13480        }));
13481      });
13482      publishing_executor.request_drain_if_queued();
13483      publication_done_tx.send(()).unwrap();
13484    });
13485
13486    if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
13487      release_recovery_tx.send(()).unwrap();
13488      cancellation.join().unwrap();
13489      publication.join().unwrap();
13490      panic!("a newly registered host must wait for recovery reservation: {error}");
13491    }
13492    assert!(
13493      matches!(
13494        publication_done_rx.try_recv(),
13495        Err(mpsc::TryRecvError::Empty)
13496      ),
13497      "the new host must not publish in the consumed-to-recovery gap"
13498    );
13499    release_recovery_tx.send(()).unwrap();
13500
13501    cancellation.join().unwrap();
13502    publication_done_rx
13503      .recv_timeout(Duration::from_secs(2))
13504      .unwrap();
13505    publication.join().unwrap();
13506
13507    let replacement_dispatch = current_thread_dispatch(&executor);
13508    assert_ne!(replacement_dispatch, failed_dispatch);
13509    assert_eq!(
13510      executor.recovery_dispatch.load(Ordering::Acquire),
13511      replacement_dispatch
13512    );
13513    assert_eq!(
13514      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13515      3,
13516      "the original publication and two host attempts for its one replacement must run"
13517    );
13518
13519    executor.cancel_host_dispatch_for_test(replacement_dispatch);
13520
13521    assert_eq!(
13522      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13523      3,
13524      "failure of the tagged replacement must cancel work without a second replacement"
13525    );
13526    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13527    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13528    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13529    assert_eq!(
13530      futures::executor::block_on(task).unwrap_err().to_string(),
13531      "async runtime stopped before the task completed"
13532    );
13533
13534    let later_registration = backend
13535      .work
13536      .try_register_async()
13537      .expect("terminal cancellation must reopen acceptance");
13538    let later_task = spawn_registered(
13539      &backend,
13540      Arc::clone(&metrics),
13541      async { 2usize },
13542      later_registration,
13543    );
13544    assert_eq!(
13545      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13546      4
13547    );
13548    executor.drive_host_turn();
13549    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
13550  }
13551
13552  #[cfg(not(target_family = "wasm"))]
13553  #[test]
13554  fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
13555    use std::sync::mpsc;
13556
13557    struct BlockingDrop {
13558      entered: mpsc::Sender<()>,
13559      release: mpsc::Receiver<()>,
13560    }
13561
13562    impl Drop for BlockingDrop {
13563      fn drop(&mut self) {
13564        self.entered.send(()).unwrap();
13565        self.release.recv().unwrap();
13566      }
13567    }
13568
13569    reset_current_thread_recovery_host_dispatches();
13570    let metrics = Arc::new(RuntimeMetrics::default());
13571    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13572      Arc::clone(&metrics),
13573      count_current_thread_recovery_host_dispatch,
13574    ));
13575    let backend =
13576      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13577
13578    let holder_registration = backend
13579      .work
13580      .try_register_work()
13581      .expect("the backend must accept the blocking holder");
13582    let (holder_started_tx, holder_started_rx) = mpsc::channel();
13583    let (release_holder_tx, release_holder_rx) = mpsc::channel();
13584    let holder_executor = Arc::clone(&executor);
13585    let holder = std::thread::spawn(move || {
13586      let handle = holder_executor.schedule_blocking(
13587        move || {
13588          holder_started_tx.send(()).unwrap();
13589          release_holder_rx.recv().unwrap();
13590        },
13591        holder_registration,
13592      );
13593      futures::executor::block_on(handle).unwrap();
13594    });
13595    holder_started_rx
13596      .recv_timeout(Duration::from_secs(2))
13597      .expect("the first blocking call must hold the CurrentThread lane");
13598
13599    let cancelled_registration = backend
13600      .work
13601      .try_register_work()
13602      .expect("the backend must accept queued blocking work");
13603    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
13604    let (release_drop_tx, release_drop_rx) = mpsc::channel();
13605    let blocking_drop = BlockingDrop {
13606      entered: drop_entered_tx,
13607      release: release_drop_rx,
13608    };
13609    let cancelled = executor.schedule_blocking(
13610      move || {
13611        let _blocking_drop = blocking_drop;
13612      },
13613      cancelled_registration,
13614    );
13615
13616    let async_registration = backend
13617      .work
13618      .try_register_async()
13619      .expect("the backend must accept queued async work");
13620    let pending = spawn_registered(
13621      &backend,
13622      Arc::clone(&metrics),
13623      std::future::pending::<()>(),
13624      async_registration,
13625    );
13626    let failed_dispatch = current_thread_dispatch(&executor);
13627    executor.cancel_host_dispatch_for_test(failed_dispatch);
13628    let replacement_dispatch = current_thread_dispatch(&executor);
13629
13630    let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
13631    let cancelling_executor = Arc::clone(&executor);
13632    let cancellation = std::thread::spawn(move || {
13633      cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
13634      cancel_done_tx.send(()).unwrap();
13635    });
13636    drop_entered_rx
13637      .recv_timeout(Duration::from_secs(2))
13638      .expect("terminal dispatch cancellation must destroy the queued blocking capture");
13639    let active_during_drop = backend.work.state.lock().unwrap().active;
13640    let cancellation_blocked = cancel_done_rx.try_recv().is_err();
13641
13642    release_drop_tx.send(()).unwrap();
13643    cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13644    cancellation.join().unwrap();
13645
13646    assert_eq!(
13647      active_during_drop, 2,
13648      "the blocking capture's work registration must remain active until its destructor returns"
13649    );
13650    assert!(
13651      cancellation_blocked,
13652      "terminal cancellation must wait for capture destruction"
13653    );
13654    assert!(futures::executor::block_on(cancelled).is_err());
13655    assert!(futures::executor::block_on(pending).is_err());
13656    assert_eq!(backend.work.state.lock().unwrap().active, 1);
13657
13658    release_holder_tx.send(()).unwrap();
13659    holder.join().unwrap();
13660    assert_eq!(backend.work.state.lock().unwrap().active, 0);
13661  }
13662
13663  #[test]
13664  fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
13665    reset_current_thread_one_shot_host_dispatches();
13666    let metrics = Arc::new(RuntimeMetrics::default());
13667    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13668      Arc::clone(&metrics),
13669      accept_only_first_current_thread_host_dispatch,
13670    ));
13671
13672    let scheduler = Arc::clone(&executor);
13673    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13674      scheduler.schedule(runnable)
13675    });
13676    executor.schedule(runnable);
13677    let failed_dispatch = current_thread_dispatch(&executor);
13678
13679    executor.cancel_host_dispatch_for_test(failed_dispatch);
13680
13681    assert_eq!(
13682      current_thread_one_shot_host_dispatches(),
13683      2,
13684      "the original and its one bounded replacement must be attempted"
13685    );
13686    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13687    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13688    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13689    assert!(
13690      futures::executor::block_on(task.fallible()).is_none(),
13691      "an unavailable replacement host must settle the original task as cancelled"
13692    );
13693  }
13694
13695  enum TestCurrentThreadTaskDriverBehavior {
13696    Healthy,
13697    PanicInLivenessAndSweep,
13698    PanicInDispatchAndSweep,
13699  }
13700
13701  struct TestCurrentThreadTaskDriver {
13702    behavior: TestCurrentThreadTaskDriverBehavior,
13703    dispatched: AtomicBool,
13704  }
13705
13706  #[derive(Default)]
13707  struct ManualCurrentThreadTaskDriver {
13708    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
13709  }
13710
13711  struct FailDuringAuthoritativePanicPublication;
13712  struct FailDuringLateRegistrationPublication;
13713  struct FailDuringNestedUnwindPublication;
13714  struct FailDuringReplacementRepublishPublication;
13715  struct FirstHostileCurrentThreadPanic;
13716  struct LaterHostileCurrentThreadPanic;
13717
13718  impl Drop for FirstHostileCurrentThreadPanic {
13719    fn drop(&mut self) {
13720      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
13721      panic!("the first retained CurrentThread panic payload was dropped");
13722    }
13723  }
13724
13725  impl Drop for LaterHostileCurrentThreadPanic {
13726    fn drop(&mut self) {
13727      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
13728      panic!("a later CurrentThread panic payload escaped containment");
13729    }
13730  }
13731
13732  impl ManualCurrentThreadTaskDriver {
13733    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
13734      self.deliveries.lock().unwrap().clone()
13735    }
13736  }
13737
13738  impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
13739    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13740      self.deliveries.lock().unwrap().push(delivery);
13741      true
13742    }
13743  }
13744
13745  impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
13746    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13747      let completion =
13748        CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
13749      assert!(
13750        completion.failed_dispatches.is_empty(),
13751        "the failed delivery must remain provisional until its publication retires"
13752      );
13753      true
13754    }
13755  }
13756
13757  impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
13758    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13759      let completion =
13760        CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
13761      assert!(
13762        completion.failed_dispatches.is_empty(),
13763        "the failed delivery must remain provisional until its publication retires"
13764      );
13765      true
13766    }
13767  }
13768
13769  impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
13770    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13771      let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
13772      assert!(
13773        completion.failed_dispatches.is_empty(),
13774        "the failed delivery must remain provisional until its publication retires"
13775      );
13776      true
13777    }
13778  }
13779
13780  impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
13781    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13782      let completion =
13783        CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
13784      assert!(
13785        completion.failed_dispatches.is_empty(),
13786        "the failed delivery must remain provisional until its publication retires"
13787      );
13788      true
13789    }
13790  }
13791
13792  struct ReentrantCurrentThreadTaskDriver {
13793    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13794    registration: Mutex<Option<CurrentThreadTaskDriverId>>,
13795  }
13796
13797  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
13798    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13799      true
13800    }
13801
13802    fn is_live(&self) -> bool {
13803      let registration = *self.registration.lock().unwrap();
13804      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
13805        registry.unregister(id);
13806      }
13807      false
13808    }
13809  }
13810
13811  struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
13812    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13813    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
13814  }
13815
13816  impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
13817    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13818      true
13819    }
13820
13821    fn is_live(&self) -> bool {
13822      if let Some(registry) = self.registry.upgrade() {
13823        for registration in self.registrations.lock().unwrap().iter().copied() {
13824          let completion = registry.unregister(registration);
13825          assert!(completion.failed_dispatches.is_empty());
13826          assert!(!completion.redispatch);
13827        }
13828      }
13829      false
13830    }
13831  }
13832
13833  struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
13834    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13835    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
13836    drops: Arc<AtomicUsize>,
13837  }
13838
13839  impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
13840    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13841      if let Some(registry) = self.registry.upgrade() {
13842        for registration in self.registrations.lock().unwrap().iter().copied() {
13843          let completion = registry.unregister(registration);
13844          assert!(completion.failed_dispatches.is_empty());
13845          assert!(!completion.redispatch);
13846        }
13847      }
13848      true
13849    }
13850  }
13851
13852  impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
13853    fn drop(&mut self) {
13854      self.drops.fetch_add(1, Ordering::SeqCst);
13855      panic!("intentional reentrant CurrentThread task-driver destructor panic");
13856    }
13857  }
13858
13859  struct PanickingDropCurrentThreadTaskDriver {
13860    drops: Arc<AtomicUsize>,
13861  }
13862
13863  impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
13864    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13865      true
13866    }
13867  }
13868
13869  impl Drop for PanickingDropCurrentThreadTaskDriver {
13870    fn drop(&mut self) {
13871      self.drops.fetch_add(1, Ordering::SeqCst);
13872      panic!("intentional CurrentThread task-driver destructor panic");
13873    }
13874  }
13875
13876  struct ReentrantCurrentThreadTaskDriverDrop {
13877    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13878    fallback: CurrentThreadTaskDriverId,
13879  }
13880
13881  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
13882    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13883      true
13884    }
13885  }
13886
13887  impl Drop for ReentrantCurrentThreadTaskDriverDrop {
13888    fn drop(&mut self) {
13889      if let Some(registry) = self.registry.upgrade() {
13890        registry.unregister(self.fallback);
13891      }
13892    }
13893  }
13894
13895  impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
13896    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13897      assert!(
13898        !matches!(
13899          self.behavior,
13900          TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
13901        ),
13902        "intentional task-driver dispatch panic"
13903      );
13904      self.dispatched.store(true, Ordering::SeqCst);
13905      true
13906    }
13907
13908    fn is_live(&self) -> bool {
13909      assert!(
13910        !matches!(
13911          self.behavior,
13912          TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
13913        ),
13914        "intentional task-driver liveness panic"
13915      );
13916      true
13917    }
13918
13919    fn on_swept(&self) {
13920      assert!(
13921        matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
13922        "intentional task-driver sweep panic"
13923      );
13924    }
13925  }
13926
13927  #[test]
13928  fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
13929    let registry = CurrentThreadTaskDriverRegistry::default();
13930    let fallback = Arc::new(TestCurrentThreadTaskDriver {
13931      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
13932      dispatched: AtomicBool::new(false),
13933    });
13934    registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
13935    registry.register(Arc::new(TestCurrentThreadTaskDriver {
13936      behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
13937      dispatched: AtomicBool::new(false),
13938    }));
13939    registry.register(Arc::new(TestCurrentThreadTaskDriver {
13940      behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
13941      dispatched: AtomicBool::new(false),
13942    }));
13943
13944    assert_eq!(
13945      registry.dispatch(37),
13946      CurrentThreadHostDispatchResult::Accepted,
13947      "a panicking newest host must fall back to a live host"
13948    );
13949    assert!(fallback.dispatched.load(Ordering::SeqCst));
13950  }
13951
13952  #[test]
13953  fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
13954    let registry = CurrentThreadTaskDriverRegistry::default();
13955    let first = Arc::new(ManualCurrentThreadTaskDriver::default());
13956    let second = Arc::new(ManualCurrentThreadTaskDriver::default());
13957    registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
13958    registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
13959    registry
13960      .next_delivery
13961      .store(u64::MAX - 1, Ordering::Relaxed);
13962
13963    let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
13964    assert!(
13965      publication.is_err(),
13966      "delivery-id exhaustion on the second host must unwind the publication"
13967    );
13968    {
13969      let state = registry
13970        .state
13971        .lock()
13972        .unwrap_or_else(std::sync::PoisonError::into_inner);
13973      assert!(
13974        !state.dispatches.contains_key(&38),
13975        "unwind cleanup must release the exact registry publication barrier"
13976      );
13977      assert!(
13978        state
13979          .entries
13980          .values()
13981          .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
13982        "failed transactional reservation must leave every physical host slot clean"
13983      );
13984    }
13985    assert!(first.deliveries().is_empty());
13986    assert!(second.deliveries().is_empty());
13987
13988    registry.next_delivery.store(0, Ordering::Relaxed);
13989    assert_eq!(
13990      registry.dispatch(38),
13991      CurrentThreadHostDispatchResult::Accepted
13992    );
13993    let first_delivery = first.deliveries()[0];
13994    let second_delivery = second.deliveries()[0];
13995    assert_eq!(
13996      registry.claim_delivery(first_delivery.capability()),
13997      Some(38)
13998    );
13999    assert_eq!(
14000      registry.claim_delivery(second_delivery.capability()),
14001      Some(38)
14002    );
14003    registry.mark_serviced(38);
14004    assert!(
14005      registry
14006        .finish_delivery(first_delivery, false)
14007        .failed_dispatches
14008        .is_empty()
14009    );
14010    assert!(
14011      registry
14012        .finish_delivery(second_delivery, false)
14013        .failed_dispatches
14014        .is_empty()
14015    );
14016
14017    assert_eq!(
14018      registry.dispatch(39),
14019      CurrentThreadHostDispatchResult::Accepted
14020    );
14021    let first_later = first.deliveries()[1];
14022    let second_later = second.deliveries()[1];
14023    assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
14024    assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
14025    registry.mark_serviced(39);
14026    assert!(
14027      registry
14028        .finish_delivery(first_later, false)
14029        .failed_dispatches
14030        .is_empty()
14031    );
14032    assert!(
14033      registry
14034        .finish_delivery(second_later, false)
14035        .failed_dispatches
14036        .is_empty()
14037    );
14038    assert!(
14039      registry
14040        .state
14041        .lock()
14042        .unwrap_or_else(std::sync::PoisonError::into_inner)
14043        .dispatches
14044        .is_empty()
14045    );
14046    let state = registry
14047      .state
14048      .lock()
14049      .unwrap_or_else(std::sync::PoisonError::into_inner);
14050    assert!(
14051      state
14052        .entries
14053        .values()
14054        .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
14055    );
14056  }
14057
14058  #[test]
14059  fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
14060    let registry = CurrentThreadTaskDriverRegistry::default();
14061    let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
14062    let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
14063    let dispatch = 40;
14064
14065    assert_eq!(
14066      registry.dispatch(dispatch),
14067      CurrentThreadHostDispatchResult::Accepted
14068    );
14069    let failed_delivery = failed.deliveries()[0];
14070    let failure = current_thread_dispatch_failure(dispatch, 0);
14071    assert_eq!(
14072      registry
14073        .finish_delivery(failed_delivery, true)
14074        .failed_dispatches,
14075      vec![failure]
14076    );
14077    registry.unregister(failed_id);
14078
14079    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
14080    registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
14081    assert!(
14082      registry.rearm_failed_publication(failure),
14083      "a closed unreferenced dispatch must reopen for its armed publication owner"
14084    );
14085    assert_eq!(
14086      registry.dispatch(dispatch),
14087      CurrentThreadHostDispatchResult::Accepted
14088    );
14089    let replacement_delivery = replacement.deliveries()[0];
14090    assert_eq!(replacement_delivery.dispatch, dispatch);
14091    assert_eq!(
14092      registry.claim_delivery(replacement_delivery.capability()),
14093      Some(dispatch)
14094    );
14095    registry.mark_serviced(dispatch);
14096    assert!(
14097      registry
14098        .finish_delivery(replacement_delivery, false)
14099        .failed_dispatches
14100        .is_empty()
14101    );
14102    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14103  }
14104
14105  #[test]
14106  fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
14107    struct RacingTaskDriver {
14108      executor: Arc<CurrentThreadExecutor>,
14109      drives: bool,
14110      dispatches: Mutex<Vec<u64>>,
14111    }
14112
14113    impl CurrentThreadTaskDriver for RacingTaskDriver {
14114      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14115        self.dispatches.lock().unwrap().push(delivery.dispatch);
14116        if self.drives {
14117          self
14118            .executor
14119            .drive_host_turn_for_dispatch(delivery.dispatch);
14120        }
14121        true
14122      }
14123    }
14124
14125    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14126      Arc::new(RuntimeMetrics::default()),
14127      accept_current_thread_host_dispatch,
14128    ));
14129    let polls = Arc::new(AtomicUsize::new(0));
14130    let task_polls = Arc::clone(&polls);
14131    let scheduler = Arc::clone(&executor);
14132    let (runnable, task) = async_task::spawn(
14133      async move {
14134        task_polls.fetch_add(1, Ordering::SeqCst);
14135      },
14136      move |runnable| scheduler.schedule(runnable),
14137    );
14138    executor.schedule(runnable);
14139    let dispatch = current_thread_dispatch(&executor);
14140
14141    let registry = CurrentThreadTaskDriverRegistry::default();
14142    let responsive = Arc::new(RacingTaskDriver {
14143      executor: Arc::clone(&executor),
14144      drives: true,
14145      dispatches: Mutex::new(Vec::new()),
14146    });
14147    let starved = Arc::new(RacingTaskDriver {
14148      executor: Arc::clone(&executor),
14149      drives: false,
14150      dispatches: Mutex::new(Vec::new()),
14151    });
14152    registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
14153    // The newest host is intentionally live but unresponsive.
14154    registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
14155
14156    assert_eq!(
14157      registry.dispatch(dispatch),
14158      CurrentThreadHostDispatchResult::Accepted
14159    );
14160    assert_eq!(
14161      responsive.dispatches.lock().unwrap().as_slice(),
14162      &[dispatch]
14163    );
14164    assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
14165    assert_eq!(
14166      polls.load(Ordering::SeqCst),
14167      1,
14168      "the responsive older host must win even when the newest host is starved"
14169    );
14170
14171    // The delayed callback from the starved host carries the same consumed
14172    // capability and cannot poll the task a second time.
14173    executor.drive_host_turn_for_dispatch(dispatch);
14174    assert_eq!(polls.load(Ordering::SeqCst), 1);
14175    futures::executor::block_on(task);
14176  }
14177
14178  #[test]
14179  fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
14180    const HOSTS: usize = 1_024;
14181
14182    let registry = CurrentThreadTaskDriverRegistry::default();
14183    let mut ids = Vec::with_capacity(HOSTS);
14184    let mut drivers = Vec::with_capacity(HOSTS);
14185    for _ in 0..HOSTS {
14186      let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
14187      ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
14188      drivers.push(driver);
14189    }
14190
14191    let dispatch = u64::MAX - 300;
14192    assert_eq!(
14193      registry.dispatch(dispatch),
14194      CurrentThreadHostDispatchResult::Accepted
14195    );
14196    let deliveries = drivers
14197      .iter()
14198      .map(|driver| driver.deliveries()[0])
14199      .collect::<Vec<_>>();
14200    {
14201      let state = registry
14202        .state
14203        .lock()
14204        .unwrap_or_else(std::sync::PoisonError::into_inner);
14205      assert_eq!(state.delivery_drivers.len(), HOSTS);
14206      assert_eq!(
14207        state
14208          .dispatches
14209          .get(&dispatch)
14210          .map(|state| state.references),
14211        Some(HOSTS)
14212      );
14213      assert_eq!(
14214        state
14215          .ordered_snapshot()
14216          .iter()
14217          .map(|(id, _)| *id)
14218          .collect::<Vec<_>>(),
14219        ids,
14220        "keyed driver lookup must retain registration order"
14221      );
14222    }
14223
14224    for delivery in &deliveries {
14225      assert_eq!(
14226        registry.claim_delivery(delivery.capability()),
14227        Some(dispatch)
14228      );
14229    }
14230    registry.mark_serviced(dispatch);
14231    for delivery in deliveries {
14232      assert!(
14233        registry
14234          .finish_delivery(delivery, false)
14235          .failed_dispatches
14236          .is_empty()
14237      );
14238    }
14239    {
14240      let state = registry
14241        .state
14242        .lock()
14243        .unwrap_or_else(std::sync::PoisonError::into_inner);
14244      assert!(state.delivery_drivers.is_empty());
14245      assert!(state.dispatches.is_empty());
14246    }
14247
14248    let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
14249    for id in &removed {
14250      let completion = registry.unregister(*id);
14251      assert!(completion.failed_dispatches.is_empty());
14252      assert!(!completion.redispatch);
14253    }
14254    let expected = ids
14255      .iter()
14256      .copied()
14257      .filter(|id| !removed.contains(id))
14258      .collect::<Vec<_>>();
14259    let live = registry.live().live;
14260    assert_eq!(
14261      live
14262        .as_slice()
14263        .iter()
14264        .map(|(id, _)| *id)
14265        .collect::<Vec<_>>(),
14266      expected,
14267      "O(1) removals must preserve the relative order of surviving host registrations"
14268    );
14269    drop(live);
14270
14271    for id in expected {
14272      registry.unregister(id);
14273    }
14274    let state = registry
14275      .state
14276      .lock()
14277      .unwrap_or_else(std::sync::PoisonError::into_inner);
14278    assert!(state.entries.is_empty());
14279    assert!(state.oldest.is_none());
14280    assert!(state.newest.is_none());
14281  }
14282
14283  #[test]
14284  fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
14285    let registry = CurrentThreadTaskDriverRegistry::default();
14286    let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
14287    let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
14288    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14289    registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
14290    registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
14291    registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14292
14293    let dispatch = 41;
14294    assert_eq!(
14295      registry.dispatch(dispatch),
14296      CurrentThreadHostDispatchResult::Accepted
14297    );
14298    let failing_delivery = failing.deliveries()[0];
14299    let delayed_delivery = delayed.deliveries()[0];
14300    let healthy_delivery = healthy.deliveries()[0];
14301    assert_eq!(failing_delivery.dispatch, dispatch);
14302    assert_eq!(delayed_delivery.dispatch, dispatch);
14303    assert_eq!(healthy_delivery.dispatch, dispatch);
14304
14305    assert!(
14306      registry
14307        .finish_delivery(failing_delivery, true)
14308        .failed_dispatches
14309        .is_empty(),
14310      "one host failure must not recover while other accepted attempts remain"
14311    );
14312    assert_eq!(
14313      registry.claim_delivery(healthy_delivery.capability()),
14314      Some(dispatch)
14315    );
14316    registry.mark_serviced(dispatch);
14317    assert!(
14318      registry
14319        .finish_delivery(healthy_delivery, false)
14320        .failed_dispatches
14321        .is_empty()
14322    );
14323    assert_eq!(
14324      registry.claim_delivery(delayed_delivery.capability()),
14325      None,
14326      "a delayed losing delivery must become stale after another host services the dispatch"
14327    );
14328    assert!(
14329      registry
14330        .finish_delivery(delayed_delivery, true)
14331        .failed_dispatches
14332        .is_empty(),
14333      "a stale losing failure must not recover or cancel a serviced dispatch"
14334    );
14335    assert!(
14336      registry.state.lock().unwrap().dispatches.is_empty(),
14337      "all per-dispatch acknowledgement state must retire"
14338    );
14339  }
14340
14341  #[test]
14342  fn current_thread_unclaimed_callback_success_is_delivery_failure() {
14343    let registry = CurrentThreadTaskDriverRegistry::default();
14344    let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
14345    registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
14346
14347    let dispatch = 56;
14348    assert_eq!(
14349      registry.dispatch(dispatch),
14350      CurrentThreadHostDispatchResult::Accepted
14351    );
14352    let delivery = no_op.deliveries()[0];
14353    let completion = registry.finish_delivery(delivery, false);
14354    let failure = current_thread_dispatch_failure(dispatch, 0);
14355    assert_eq!(
14356      completion.failed_dispatches,
14357      vec![failure],
14358      "a callback return without an exact capability claim must enter recovery"
14359    );
14360    assert!(
14361      registry
14362        .state
14363        .lock()
14364        .unwrap()
14365        .dispatches
14366        .get(&dispatch)
14367        .is_some_and(CurrentThreadDispatchState::is_cancelling)
14368    );
14369    registry.finish_cancellation(failure);
14370    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14371  }
14372
14373  #[test]
14374  fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
14375    struct FailDuringPublication {
14376      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14377    }
14378
14379    impl CurrentThreadTaskDriver for FailDuringPublication {
14380      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14381        let completion = self
14382          .registry
14383          .upgrade()
14384          .expect("the registry must outlive publication")
14385          .finish_delivery(delivery, true);
14386        assert!(
14387          completion.failed_dispatches.is_empty(),
14388          "failure must remain provisional until the broadcast closes"
14389        );
14390        true
14391      }
14392    }
14393
14394    struct RejectProvisional;
14395
14396    impl CurrentThreadTaskDriver for RejectProvisional {
14397      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14398        false
14399      }
14400    }
14401
14402    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14403    registry.register(Arc::new(RejectProvisional));
14404    // Newest-first publication calls the accepted-then-failing host while the
14405    // rejecting host still has an unresolved provisional delivery.
14406    registry.register(Arc::new(FailDuringPublication {
14407      registry: Arc::downgrade(&registry),
14408    }));
14409
14410    let failure = current_thread_dispatch_failure(57, 0);
14411    assert_eq!(
14412      registry.dispatch(57),
14413      CurrentThreadHostDispatchResult::Failed(failure),
14414      "the final provisional rejection must expose the earlier accepted failure"
14415    );
14416    assert!(
14417      registry
14418        .state
14419        .lock()
14420        .unwrap()
14421        .dispatches
14422        .get(&57)
14423        .is_some_and(CurrentThreadDispatchState::is_cancelling)
14424    );
14425    assert_eq!(
14426      registry.dispatch(57),
14427      CurrentThreadHostDispatchResult::Failed(failure),
14428      "a racing replacement publication must preserve terminal failure until cancellation runs"
14429    );
14430    registry.finish_cancellation(failure);
14431  }
14432
14433  #[test]
14434  fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
14435    struct RejectProvisional;
14436
14437    impl CurrentThreadTaskDriver for RejectProvisional {
14438      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14439        false
14440      }
14441    }
14442
14443    let registry = CurrentThreadTaskDriverRegistry::default();
14444    registry.register(Arc::new(RejectProvisional));
14445    registry.register(Arc::new(RejectProvisional));
14446
14447    assert_eq!(
14448      registry.dispatch(58),
14449      CurrentThreadHostDispatchResult::Unavailable
14450    );
14451    assert!(
14452      registry.state.lock().unwrap().dispatches.is_empty(),
14453      "a broadcast with no accepted attempt must not enter failure recovery"
14454    );
14455  }
14456
14457  #[test]
14458  fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
14459    let registry = CurrentThreadTaskDriverRegistry::default();
14460    let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
14461    let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
14462
14463    assert_eq!(
14464      registry.dispatch(1),
14465      CurrentThreadHostDispatchResult::Accepted
14466    );
14467    let first = blocked.deliveries()[0];
14468    for dispatch in 2..=256 {
14469      assert_eq!(
14470        registry.dispatch(dispatch),
14471        CurrentThreadHostDispatchResult::Accepted
14472      );
14473      assert_eq!(
14474        blocked.deliveries().len(),
14475        1,
14476        "a blocked host must never receive a second queued or in-flight callback"
14477      );
14478    }
14479    {
14480      let state = registry.state.lock().unwrap();
14481      let entry = state
14482        .entries
14483        .get(&blocked_id)
14484        .expect("the blocked host must remain registered");
14485      assert_eq!(entry.pending, Some(256));
14486      assert_eq!(
14487        state.dispatches.len(),
14488        2,
14489        "only the in-flight and latest pending dispatches may retain registry state"
14490      );
14491    }
14492
14493    assert_eq!(registry.claim_delivery(first.capability()), Some(1));
14494    registry.mark_serviced(1);
14495    let completion = registry.finish_delivery(first, false);
14496    assert!(completion.failed_dispatches.is_empty());
14497    assert!(completion.redispatch);
14498    assert_eq!(
14499      registry.dispatch(256),
14500      CurrentThreadHostDispatchResult::Accepted
14501    );
14502    let deliveries = blocked.deliveries();
14503    assert_eq!(deliveries.len(), 2);
14504    assert_eq!(
14505      deliveries[1].dispatch, 256,
14506      "completion must schedule exactly the latest coalesced capability"
14507    );
14508    assert_eq!(registry.claim_delivery(first.capability()), None);
14509    assert_eq!(
14510      registry.claim_delivery(deliveries[1].capability()),
14511      Some(256)
14512    );
14513    registry.mark_serviced(256);
14514    assert!(
14515      registry
14516        .finish_delivery(deliveries[1], false)
14517        .failed_dispatches
14518        .is_empty()
14519    );
14520    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14521  }
14522
14523  #[test]
14524  fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
14525    struct RejectAfterFirst {
14526      calls: AtomicUsize,
14527      deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
14528    }
14529
14530    impl CurrentThreadTaskDriver for RejectAfterFirst {
14531      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14532        self.deliveries.lock().unwrap().push(delivery);
14533        self.calls.fetch_add(1, Ordering::SeqCst) == 0
14534      }
14535    }
14536
14537    let registry = CurrentThreadTaskDriverRegistry::default();
14538    let host = Arc::new(RejectAfterFirst {
14539      calls: AtomicUsize::new(0),
14540      deliveries: Mutex::new(Vec::new()),
14541    });
14542    registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
14543    assert_eq!(
14544      registry.dispatch(11),
14545      CurrentThreadHostDispatchResult::Accepted
14546    );
14547    let first = host.deliveries.lock().unwrap()[0];
14548    assert_eq!(
14549      registry.dispatch(12),
14550      CurrentThreadHostDispatchResult::Accepted
14551    );
14552
14553    assert_eq!(registry.claim_delivery(first.capability()), Some(11));
14554    registry.mark_serviced(11);
14555    let completion = registry.finish_delivery(first, false);
14556    assert!(completion.redispatch);
14557    let failure = current_thread_dispatch_failure(12, 0);
14558    assert_eq!(
14559      registry.dispatch(12),
14560      CurrentThreadHostDispatchResult::Failed(failure),
14561      "an accepted coalesced capability must report failure if its eventual host queue rejects it"
14562    );
14563    assert!(
14564      registry
14565        .state
14566        .lock()
14567        .unwrap()
14568        .dispatches
14569        .get(&12)
14570        .is_some_and(CurrentThreadDispatchState::is_cancelling)
14571    );
14572    registry.finish_cancellation(failure);
14573    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14574  }
14575
14576  #[test]
14577  fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
14578    let registry = CurrentThreadTaskDriverRegistry::default();
14579    let old = Arc::new(ManualCurrentThreadTaskDriver::default());
14580    let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
14581    assert_eq!(
14582      registry.dispatch(73),
14583      CurrentThreadHostDispatchResult::Accepted
14584    );
14585    let old_delivery = old.deliveries()[0];
14586
14587    let completion = registry.unregister(old_id);
14588    let failure = current_thread_dispatch_failure(73, 0);
14589    assert_eq!(
14590      completion.failed_dispatches,
14591      vec![failure],
14592      "unregistering the final accepting host must retire its exact dispatch"
14593    );
14594    registry.finish_cancellation(failure);
14595    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
14596    let replacement_id =
14597      registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
14598    assert_ne!(replacement_id, old_id);
14599    assert_eq!(
14600      registry.dispatch(74),
14601      CurrentThreadHostDispatchResult::Accepted
14602    );
14603    let replacement_delivery = replacement.deliveries()[0];
14604    assert_ne!(replacement_delivery.capability(), old_delivery.capability());
14605
14606    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
14607    assert!(
14608      registry
14609        .finish_delivery(old_delivery, true)
14610        .failed_dispatches
14611        .is_empty()
14612    );
14613    assert!(
14614      registry
14615        .fail_delivery(old_delivery.capability())
14616        .failed_dispatches
14617        .is_empty()
14618    );
14619    assert_eq!(
14620      registry.claim_delivery(replacement_delivery.capability()),
14621      Some(74)
14622    );
14623    registry.mark_serviced(74);
14624    assert!(
14625      registry
14626        .finish_delivery(replacement_delivery, false)
14627        .failed_dispatches
14628        .is_empty()
14629    );
14630    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14631  }
14632
14633  #[test]
14634  fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
14635    struct QueuePendingThenReject {
14636      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14637    }
14638
14639    impl CurrentThreadTaskDriver for QueuePendingThenReject {
14640      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14641        assert_eq!(delivery.dispatch, 81);
14642        let completion = self
14643          .registry
14644          .upgrade()
14645          .expect("the registry must outlive publication")
14646          .dispatch_with_completion(82);
14647        assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
14648        assert!(completion.failed_dispatches.is_empty());
14649        false
14650      }
14651    }
14652
14653    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14654    registry.register(Arc::new(QueuePendingThenReject {
14655      registry: Arc::downgrade(&registry),
14656    }));
14657
14658    let completion = registry.dispatch_with_completion(81);
14659    assert_eq!(
14660      completion.result,
14661      CurrentThreadHostDispatchResult::Unavailable
14662    );
14663    let failure = current_thread_dispatch_failure(82, 0);
14664    assert_eq!(
14665      completion.failed_dispatches,
14666      vec![failure],
14667      "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
14668    );
14669    assert!(
14670      registry
14671        .state
14672        .lock()
14673        .unwrap()
14674        .dispatches
14675        .get(&82)
14676        .is_some_and(CurrentThreadDispatchState::is_cancelling)
14677    );
14678    registry.finish_cancellation(failure);
14679    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14680  }
14681
14682  #[test]
14683  fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
14684    let registry = CurrentThreadTaskDriverRegistry::default();
14685    let host = Arc::new(ManualCurrentThreadTaskDriver::default());
14686    let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
14687    assert_eq!(
14688      registry.dispatch(91),
14689      CurrentThreadHostDispatchResult::Accepted
14690    );
14691    let old_delivery = host.deliveries()[0];
14692    assert_eq!(
14693      registry.dispatch(92),
14694      CurrentThreadHostDispatchResult::Accepted
14695    );
14696
14697    registry.retire_for_shutdown();
14698    {
14699      let state = registry.state.lock().unwrap();
14700      assert!(state.dispatches.is_empty());
14701      let entry = state
14702        .entries
14703        .get(&host_id)
14704        .expect("the host must remain registered");
14705      assert_eq!(entry.pending, None);
14706      assert!(
14707        entry.delivery.is_some(),
14708        "the physical callback remains single-flight until it actually retires"
14709      );
14710    }
14711    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
14712
14713    assert_eq!(
14714      registry.dispatch(93),
14715      CurrentThreadHostDispatchResult::Accepted
14716    );
14717    assert_eq!(host.deliveries().len(), 1);
14718    let completion = registry.finish_delivery(old_delivery, true);
14719    assert!(
14720      completion.failed_dispatches.is_empty(),
14721      "a retired-generation failure must not cancel restarted work"
14722    );
14723    assert!(completion.redispatch);
14724    assert_eq!(
14725      registry.dispatch(93),
14726      CurrentThreadHostDispatchResult::Accepted
14727    );
14728    let restarted_delivery = host.deliveries()[1];
14729    assert_eq!(restarted_delivery.dispatch, 93);
14730    assert_eq!(
14731      registry.claim_delivery(restarted_delivery.capability()),
14732      Some(93)
14733    );
14734    registry.mark_serviced(93);
14735    assert!(
14736      registry
14737        .finish_delivery(restarted_delivery, false)
14738        .failed_dispatches
14739        .is_empty()
14740    );
14741
14742    registry.unregister(host_id);
14743    let state = registry.state.lock().unwrap();
14744    assert!(state.entries.is_empty());
14745    assert!(state.dispatches.is_empty());
14746  }
14747
14748  #[test]
14749  fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
14750    use std::{sync::mpsc, time::Duration};
14751
14752    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14753    let fallback = Arc::new(TestCurrentThreadTaskDriver {
14754      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
14755      dispatched: AtomicBool::new(false),
14756    });
14757    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
14758    let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
14759      registry: Arc::downgrade(&registry),
14760      registration: Mutex::new(None),
14761    });
14762    let reentrant_id =
14763      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
14764    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
14765
14766    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
14767    let selection_registry = Arc::clone(&registry);
14768    let selection_thread = std::thread::spawn(move || {
14769      selection_tx
14770        .send(selection_registry.current().map(|(id, _)| id))
14771        .unwrap();
14772    });
14773    assert_eq!(
14774      selection_rx
14775        .recv_timeout(Duration::from_secs(10))
14776        .expect("is_live re-entry deadlocked"),
14777      Some(fallback_id)
14778    );
14779    selection_thread.join().unwrap();
14780
14781    let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
14782      Arc::new(ReentrantCurrentThreadTaskDriverDrop {
14783        registry: Arc::downgrade(&registry),
14784        fallback: fallback_id,
14785      });
14786    let drop_id = registry.register(Arc::clone(&drop_driver));
14787    drop(drop_driver);
14788
14789    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
14790    let drop_registry = Arc::clone(&registry);
14791    let drop_thread = std::thread::spawn(move || {
14792      drop_registry.unregister(drop_id);
14793      drop_tx.send(()).unwrap();
14794    });
14795    drop_rx
14796      .recv_timeout(Duration::from_secs(10))
14797      .expect("driver destructor re-entry deadlocked");
14798    drop_thread.join().unwrap();
14799    assert!(
14800      registry.current().is_none(),
14801      "the destructor must unregister the fallback"
14802    );
14803  }
14804
14805  #[test]
14806  fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
14807    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
14808
14809    if std::env::var_os(CHILD_ENV).is_some() {
14810      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14811      let fallback = Arc::new(TestCurrentThreadTaskDriver {
14812        behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
14813        dispatched: AtomicBool::new(false),
14814      });
14815      let fallback_id =
14816        registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
14817      let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
14818        registry: Arc::downgrade(&registry),
14819        registrations: Mutex::new(Vec::new()),
14820      });
14821      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
14822
14823      let first_drops = Arc::new(AtomicUsize::new(0));
14824      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
14825        drops: Arc::clone(&first_drops),
14826      });
14827      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
14828      drop(first);
14829
14830      let second_drops = Arc::new(AtomicUsize::new(0));
14831      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
14832        drops: Arc::clone(&second_drops),
14833      });
14834      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
14835      drop(second);
14836      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
14837
14838      assert_eq!(
14839        registry.current().map(|(id, _)| id),
14840        Some(fallback_id),
14841        "selection must retry after reentrant removals and preserve the healthy fallback"
14842      );
14843      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
14844      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
14845      let state = registry
14846        .state
14847        .lock()
14848        .unwrap_or_else(std::sync::PoisonError::into_inner);
14849      assert_eq!(state.entries.len(), 1);
14850      assert!(state.entries.contains_key(&fallback_id));
14851      assert!(state.dispatches.is_empty());
14852      return;
14853    }
14854
14855    let output = std::process::Command::new(std::env::current_exe().unwrap())
14856      .arg("--exact")
14857      .arg(
14858        "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
14859      )
14860      .arg("--nocapture")
14861      .env(CHILD_ENV, "1")
14862      .output()
14863      .expect("the hostile task-driver snapshot subprocess test must start");
14864    assert!(
14865      output.status.success(),
14866      "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
14867      output.status.code(),
14868      String::from_utf8_lossy(&output.stdout),
14869      String::from_utf8_lossy(&output.stderr)
14870    );
14871  }
14872
14873  #[test]
14874  fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
14875    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
14876
14877    if std::env::var_os(CHILD_ENV).is_some() {
14878      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14879
14880      let first_drops = Arc::new(AtomicUsize::new(0));
14881      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
14882        drops: Arc::clone(&first_drops),
14883      });
14884      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
14885      drop(first);
14886
14887      let second_drops = Arc::new(AtomicUsize::new(0));
14888      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
14889        drops: Arc::clone(&second_drops),
14890      });
14891      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
14892      drop(second);
14893
14894      let reentrant_drops = Arc::new(AtomicUsize::new(0));
14895      let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14896        registry: Arc::downgrade(&registry),
14897        registrations: Mutex::new(Vec::new()),
14898        drops: Arc::clone(&reentrant_drops),
14899      });
14900      let reentrant_id =
14901        registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
14902      *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
14903      drop(reentrant);
14904
14905      let dispatch = 59;
14906      let failure = current_thread_dispatch_failure(dispatch, 0);
14907      assert_eq!(
14908        registry.dispatch(dispatch),
14909        CurrentThreadHostDispatchResult::Failed(failure),
14910        "reentrant removal of every provisional host must report accepted delivery failure"
14911      );
14912      assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
14913      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
14914      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
14915      registry.finish_cancellation(failure);
14916      let state = registry
14917        .state
14918        .lock()
14919        .unwrap_or_else(std::sync::PoisonError::into_inner);
14920      assert!(state.entries.is_empty());
14921      assert!(state.dispatches.is_empty());
14922      return;
14923    }
14924
14925    let output = std::process::Command::new(std::env::current_exe().unwrap())
14926      .arg("--exact")
14927      .arg(
14928        "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
14929      )
14930      .arg("--nocapture")
14931      .env(CHILD_ENV, "1")
14932      .output()
14933      .expect("the hostile task-driver delivery subprocess test must start");
14934    assert!(
14935      output.status.success(),
14936      "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
14937      output.status.code(),
14938      String::from_utf8_lossy(&output.stdout),
14939      String::from_utf8_lossy(&output.stderr)
14940    );
14941  }
14942
14943  #[test]
14944  fn dropping_join_handle_detaches_task_like_tokio() {
14945    let completed = Arc::new(AtomicBool::new(false));
14946    let completed_task = Arc::clone(&completed);
14947    let (runnable, task) = async_task::spawn(
14948      async move {
14949        completed_task.store(true, Ordering::SeqCst);
14950        Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
14951      },
14952      |_| {},
14953    );
14954    let handle = JoinHandle(JoinHandleInner::Task {
14955      task: task.fallible(),
14956      awaiter: None,
14957      dependency: Arc::new(TaskDependency::default()),
14958    });
14959
14960    drop(handle);
14961    runnable.run();
14962    assert!(completed.load(Ordering::SeqCst));
14963  }
14964
14965  #[cfg(not(target_family = "wasm"))]
14966  #[test]
14967  fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
14968    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
14969
14970    if std::env::var_os(CHILD_ENV).is_some() {
14971      let wakes = Arc::new(AtomicUsize::new(0));
14972      let drops = Arc::new(AtomicUsize::new(0));
14973      let (runnable, task) = async_task::spawn(
14974        async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
14975        |_| {},
14976      );
14977      let mut handle = JoinHandle(JoinHandleInner::Task {
14978        task: task.fallible(),
14979        awaiter: None,
14980        dependency: Arc::new(TaskDependency::default()),
14981      });
14982      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
14983      {
14984        let mut cx = Context::from_waker(&waker);
14985        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
14986      }
14987      drop(waker);
14988
14989      runnable.run();
14990
14991      let waker = futures::task::noop_waker();
14992      let mut cx = Context::from_waker(&waker);
14993      assert!(matches!(
14994        Pin::new(&mut handle).poll(&mut cx),
14995        Poll::Ready(Ok(()))
14996      ));
14997      assert_eq!(
14998        drops.load(Ordering::SeqCst),
14999        1,
15000        "a completed handle must not retain the caller's awaiter"
15001      );
15002      drop(handle);
15003      assert!(wakes.load(Ordering::SeqCst) >= 1);
15004      assert_eq!(drops.load(Ordering::SeqCst), 1);
15005      return;
15006    }
15007
15008    let output = std::process::Command::new(std::env::current_exe().unwrap())
15009      .arg("--exact")
15010      .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
15011      .arg("--nocapture")
15012      .env(CHILD_ENV, "1")
15013      .output()
15014      .expect("the JoinHandle completion awaiter subprocess must start");
15015    assert!(
15016      output.status.success(),
15017      "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15018      output.status.code(),
15019      String::from_utf8_lossy(&output.stdout),
15020      String::from_utf8_lossy(&output.stderr)
15021    );
15022  }
15023
15024  #[cfg(not(target_family = "wasm"))]
15025  #[test]
15026  fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
15027    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
15028
15029    if std::env::var_os(CHILD_ENV).is_some() {
15030      let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
15031      let wakes = Arc::new(AtomicUsize::new(0));
15032      let drops = Arc::new(AtomicUsize::new(0));
15033      let mut handle = controller.spawn(std::future::pending::<()>());
15034      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15035      {
15036        let mut cx = Context::from_waker(&waker);
15037        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15038      }
15039      drop(waker);
15040
15041      controller
15042        .shutdown()
15043        .expect("shutdown must survive the hostile awaiter");
15044      drop(handle);
15045      assert!(wakes.load(Ordering::SeqCst) >= 1);
15046      assert_eq!(drops.load(Ordering::SeqCst), 1);
15047      return;
15048    }
15049
15050    let output = std::process::Command::new(std::env::current_exe().unwrap())
15051      .arg("--exact")
15052      .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
15053      .arg("--nocapture")
15054      .env(CHILD_ENV, "1")
15055      .output()
15056      .expect("the JoinHandle shutdown awaiter subprocess must start");
15057    assert!(
15058      output.status.success(),
15059      "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15060      output.status.code(),
15061      String::from_utf8_lossy(&output.stdout),
15062      String::from_utf8_lossy(&output.stderr)
15063    );
15064  }
15065
15066  #[cfg(not(target_family = "wasm"))]
15067  #[test]
15068  fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
15069    let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
15070    let mut handle = controller.spawn(std::future::pending::<()>());
15071    let old_generation = controller.backend().generation();
15072    let state = Arc::new(JoinAwaiterWakerState {
15073      events: Mutex::new(Vec::new()),
15074    });
15075    let waker = {
15076      let _generation = RuntimeGenerationGuard::enter(old_generation);
15077      join_awaiter_waker(Arc::clone(&state))
15078    };
15079    {
15080      let _generation = RuntimeGenerationGuard::enter(old_generation);
15081      let mut cx = Context::from_waker(&waker);
15082      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15083    }
15084    {
15085      let _generation = RuntimeGenerationGuard::enter(old_generation);
15086      drop(waker);
15087    }
15088
15089    controller
15090      .shutdown()
15091      .expect("the first generation must stop");
15092    controller.start().expect("the runtime must restart");
15093    let new_generation = controller.backend().generation();
15094    assert_ne!(new_generation, old_generation);
15095    state.clear();
15096
15097    let replacement = futures::task::noop_waker();
15098    {
15099      let _generation = RuntimeGenerationGuard::enter(new_generation);
15100      let mut cx = Context::from_waker(&replacement);
15101      assert!(matches!(
15102        Pin::new(&mut handle).poll(&mut cx),
15103        Poll::Ready(Err(_))
15104      ));
15105    }
15106
15107    let events = state.snapshot();
15108    assert!(
15109      !events.is_empty(),
15110      "completion must retire the registered awaiter waker"
15111    );
15112    assert!(
15113      events.iter().all(|(kind, generation)| {
15114        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15115      }),
15116      "old-generation awaiter retirement escaped its generation: {events:?}"
15117    );
15118    controller
15119      .shutdown()
15120      .expect("the replacement generation must stop");
15121  }
15122
15123  #[cfg(not(target_family = "wasm"))]
15124  #[test]
15125  fn dropping_join_handle_retains_awaiter_generation_after_restart() {
15126    let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
15127    let mut handle = controller.spawn(std::future::pending::<()>());
15128    let old_generation = controller.backend().generation();
15129    let state = Arc::new(JoinAwaiterWakerState {
15130      events: Mutex::new(Vec::new()),
15131    });
15132    let waker = {
15133      let _generation = RuntimeGenerationGuard::enter(old_generation);
15134      join_awaiter_waker(Arc::clone(&state))
15135    };
15136    {
15137      let _generation = RuntimeGenerationGuard::enter(old_generation);
15138      let mut cx = Context::from_waker(&waker);
15139      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15140    }
15141    {
15142      let _generation = RuntimeGenerationGuard::enter(old_generation);
15143      drop(waker);
15144    }
15145
15146    controller
15147      .shutdown()
15148      .expect("the first generation must stop");
15149    controller.start().expect("the runtime must restart");
15150    let new_generation = controller.backend().generation();
15151    assert_ne!(new_generation, old_generation);
15152    state.clear();
15153
15154    {
15155      let _generation = RuntimeGenerationGuard::enter(new_generation);
15156      drop(handle);
15157    }
15158
15159    let events = state.snapshot();
15160    assert!(
15161      !events.is_empty(),
15162      "handle drop must retire the registered awaiter waker"
15163    );
15164    assert!(
15165      events.iter().all(|(kind, generation)| {
15166        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15167      }),
15168      "old-generation awaiter destruction escaped its generation: {events:?}"
15169    );
15170    controller
15171      .shutdown()
15172      .expect("the replacement generation must stop");
15173  }
15174
15175  #[cfg(not(target_family = "wasm"))]
15176  #[test]
15177  fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
15178    use std::{sync::mpsc, time::Duration};
15179
15180    let options = RuntimeOptions {
15181      flavor: RuntimeFlavor::MultiThread,
15182      worker_threads: 2,
15183      max_blocking_tasks: 1,
15184      thread_name_prefix: "worker-classification".to_string(),
15185      park_deadline: None,
15186    };
15187    let executor =
15188      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
15189    let id = executor.id;
15190    let (tx, rx) = mpsc::sync_channel(1);
15191
15192    executor.pool.spawn(move || {
15193      tx.send((
15194        ON_POOL_WORKER.with(std::cell::Cell::get),
15195        IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
15196      ))
15197      .unwrap();
15198    });
15199
15200    let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
15201    assert_eq!(
15202      worker,
15203      Some(id),
15204      "nested Rayon work must use cooperative block_on"
15205    );
15206    assert_eq!(
15207      driver, None,
15208      "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
15209    );
15210  }
15211
15212  #[cfg(not(target_family = "wasm"))]
15213  #[test]
15214  fn multi_thread_block_on_does_not_park_pool_worker() {
15215    // Pool-worker reentrancy invariant: a task that calls `block_on` on an inner
15216    // future depending on pool-scheduled blocking work must not park its worker.
15217    // If every worker parks this way, no worker remains to pick up the blocking
15218    // jobs they await.
15219    use std::sync::{Barrier, mpsc};
15220    use std::time::Duration;
15221
15222    let (done_tx, done_rx) = mpsc::channel();
15223    let runner = std::thread::spawn(move || {
15224      let metrics = Arc::new(RuntimeMetrics::default());
15225      let options = RuntimeOptions {
15226        flavor: RuntimeFlavor::MultiThread,
15227        worker_threads: 2,
15228        max_blocking_tasks: 2,
15229        thread_name_prefix: "rd1-test".to_string(),
15230        park_deadline: None,
15231      };
15232      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15233
15234      let task_count = 2usize;
15235      // Guarantees every task is being polled on its own pool worker *before*
15236      // any of them parks in `block_on`, so the deadlock is deterministic.
15237      let barrier = Arc::new(Barrier::new(task_count));
15238
15239      let mut tasks = Vec::new();
15240      for _ in 0..task_count {
15241        let exec = Arc::clone(&executor);
15242        let barrier = Arc::clone(&barrier);
15243        let body = async move {
15244          barrier.wait();
15245          let inner_exec = Arc::clone(&exec);
15246          let mut inner = std::pin::pin!(async move {
15247            let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
15248            assert_eq!(value, 7);
15249          });
15250          // Synchronous `block_on` from inside a pool-worker task: the vector.
15251          exec.block_on(inner.as_mut());
15252        };
15253        let scheduler = Arc::clone(&executor);
15254        let (runnable, task) =
15255          async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
15256        executor.schedule(runnable);
15257        tasks.push(task);
15258      }
15259
15260      for task in tasks {
15261        futures::executor::block_on(task);
15262      }
15263      let _ = done_tx.send(());
15264    });
15265
15266    match done_rx.recv_timeout(Duration::from_secs(10)) {
15267      Ok(()) => runner.join().unwrap(),
15268      Err(error) => panic!(
15269        "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
15270      ),
15271    }
15272  }
15273
15274  #[cfg(not(target_family = "wasm"))]
15275  #[test]
15276  fn multi_thread_block_on_from_caller_thread_still_parks() {
15277    // The non-pool (napi caller) path must keep using the plain parking
15278    // `block_on`; here we just assert it drives a ready future to completion.
15279    let metrics = Arc::new(RuntimeMetrics::default());
15280    let options = RuntimeOptions {
15281      flavor: RuntimeFlavor::MultiThread,
15282      worker_threads: 3,
15283      max_blocking_tasks: 2,
15284      thread_name_prefix: "rd1-caller".to_string(),
15285      park_deadline: None,
15286    };
15287    let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15288
15289    let mut output = None;
15290    let mut future = std::pin::pin!(async {
15291      output = Some(41usize + 1);
15292    });
15293    executor.block_on(future.as_mut());
15294    assert_eq!(output, Some(42));
15295  }
15296
15297  #[cfg(not(target_family = "wasm"))]
15298  #[test]
15299  fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
15300    // Cross-thread liveness guard for the NON-pool (napi-caller) path of
15301    // `MultiThreadExecutor::block_on`. The caller thread is not a pool worker, so
15302    // `block_on` takes the parking branch (`futures::executor::block_on`) while a
15303    // SEPARATELY spawned task runs on a pool worker and produces the awaited value.
15304    // This pins ONLY the cross-thread wake path: the pool task completing must wake
15305    // the parked caller's waker so `block_on` returns the pool-computed value.
15306    //
15307    // HONEST ORDERING (no sleeps): the awaited pool task is GATED on a oneshot and
15308    // therefore returns `Pending` on its first poll. A `CallerProbe` future wraps the
15309    // task and, ONLY after the task poll returns `Pending` (i.e. AFTER `block_on`'s
15310    // parking waker is registered on the task), signals "caller parked" to a releaser
15311    // thread. The releaser then -- and only then -- releases the gate, so the pool
15312    // task completes strictly AFTER the caller registered its waker. This forecloses
15313    // the "already-ready before await" interleaving: the task cannot be `Ready` on the
15314    // caller's first poll, because its gate is opened only in response to that first
15315    // `Pending` poll. The completion is thus forced to travel the real cross-thread
15316    // wake (pool worker completes task -> wakes parked caller -> `block_on` returns);
15317    // if that wake is broken the caller parks forever and the bounded recv_timeout
15318    // below fails loudly instead of hanging the suite.
15319    //
15320    // SCOPE: this is the cross-thread liveness guard only. It does NOT exercise the
15321    // caller-is-pool-worker case, which has dedicated reentrancy tests.
15322    use std::sync::mpsc;
15323    use std::time::Duration;
15324
15325    // Wraps the pool task so the caller's FIRST poll registers `block_on`'s waker on
15326    // the task and, only when that poll is `Pending`, hands the "caller parked" signal
15327    // to the releaser. Sending strictly after the `Pending` poll guarantees the waker
15328    // is already registered before the gate can open.
15329    struct CallerProbe {
15330      task: Task<usize>,
15331      parked_tx: Option<mpsc::Sender<()>>,
15332    }
15333    impl Future for CallerProbe {
15334      type Output = usize;
15335      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
15336        // `Task` is `Unpin`; poll it through a fresh `Pin`.
15337        let poll = Pin::new(&mut self.task).poll(cx);
15338        if poll.is_pending()
15339          && let Some(tx) = self.parked_tx.take()
15340        {
15341          // Waker is now registered on `task`; release the gate is safe.
15342          let _ = tx.send(());
15343        }
15344        poll
15345      }
15346    }
15347
15348    let (done_tx, done_rx) = mpsc::channel();
15349    let runner = std::thread::spawn(move || {
15350      let metrics = Arc::new(RuntimeMetrics::default());
15351      let options = RuntimeOptions {
15352        flavor: RuntimeFlavor::MultiThread,
15353        worker_threads: 2,
15354        max_blocking_tasks: 2,
15355        thread_name_prefix: "rd10-caller".to_string(),
15356        park_deadline: None,
15357      };
15358      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15359
15360      // The pool task's inner future is GATED on a oneshot: its first poll on a pool
15361      // worker registers the gate waker and returns `Pending`. When the releaser sends
15362      // on `gate_tx`, the gate's wake reschedules the task (via `schedule` ->
15363      // `ensure_drainer`) onto a pool worker, where it resolves to 42.
15364      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
15365      let scheduler = Arc::clone(&executor);
15366      let (runnable, task) = async_task::spawn(
15367        async move { gate_rx.await.expect("gate sender dropped") },
15368        move |r| {
15369          scheduler.schedule(r);
15370        },
15371      );
15372      executor.schedule(runnable);
15373
15374      // Releaser thread: opens the gate ONLY after the caller has parked (registered
15375      // its waker on the task). This enforces caller-parked HAPPENS-BEFORE pool-task
15376      // completes, so completion must travel the cross-thread wake path.
15377      let (parked_tx, parked_rx) = mpsc::channel::<()>();
15378      let releaser = std::thread::spawn(move || {
15379        if parked_rx.recv().is_ok() {
15380          let _ = gate_tx.send(42usize);
15381        }
15382      });
15383
15384      // The caller (this child) thread is NOT a pool worker, so `block_on` parks via
15385      // `futures::executor::block_on` rather than driving the queue cooperatively.
15386      let mut output = None;
15387      {
15388        let mut future = std::pin::pin!(async {
15389          output = Some(
15390            CallerProbe {
15391              task,
15392              parked_tx: Some(parked_tx),
15393            }
15394            .await,
15395          );
15396        });
15397        executor.block_on(future.as_mut());
15398      }
15399
15400      releaser.join().unwrap();
15401      assert_eq!(
15402        output,
15403        Some(42usize),
15404        "block_on must return the pool-computed value"
15405      );
15406      let _ = done_tx.send(());
15407    });
15408
15409    match done_rx.recv_timeout(Duration::from_secs(10)) {
15410      Ok(()) => runner.join().unwrap(),
15411      Err(error) => panic!(
15412        "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
15413      ),
15414    }
15415  }
15416
15417  #[cfg(not(target_family = "wasm"))]
15418  #[test]
15419  fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
15420    // Parked-driver wake invariant: a pool-worker task may park in `block_on`
15421    // before the work that will wake it is scheduled. Once every pool worker
15422    // is parked this way, `ensure_drainer` cannot spawn a replacement because
15423    // the parked workers are still counted as active drainers. A later
15424    // `schedule()` must wake a parked driver directly so the runnable can run.
15425    use std::sync::{Barrier, mpsc};
15426    use std::time::Duration;
15427
15428    let (done_tx, done_rx) = mpsc::channel();
15429    let runner = std::thread::spawn(move || {
15430      let metrics = Arc::new(RuntimeMetrics::default());
15431      let options = RuntimeOptions {
15432        flavor: RuntimeFlavor::MultiThread,
15433        worker_threads: 2,
15434        max_blocking_tasks: 2,
15435        thread_name_prefix: "rd1-park".to_string(),
15436        park_deadline: None,
15437      };
15438      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15439
15440      let task_count = 2usize;
15441      let barrier = Arc::new(Barrier::new(task_count));
15442
15443      let mut senders = Vec::new();
15444      let mut tasks = Vec::new();
15445      for _ in 0..task_count {
15446        let (tx, rx) = oneshot::channel::<usize>();
15447        senders.push(tx);
15448        let exec = Arc::clone(&executor);
15449        let barrier = Arc::clone(&barrier);
15450        let body = async move {
15451          // Make sure both pool workers are occupied before either parks.
15452          barrier.wait();
15453          // Child task whose wake routes through `schedule()`: the oneshot
15454          // receiver's waker -> child runnable -> `schedule` -> `ensure_drainer`.
15455          let child_exec = Arc::clone(&exec);
15456          let (child_runnable, child_task) =
15457            async_task::spawn(async move { rx.await.unwrap() }, move |r| {
15458              child_exec.schedule(r)
15459            });
15460          exec.schedule(child_runnable);
15461          let mut inner = std::pin::pin!(async move {
15462            assert_eq!(child_task.await, 9usize);
15463          });
15464          exec.block_on(inner.as_mut());
15465        };
15466        let scheduler = Arc::clone(&executor);
15467        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
15468        executor.schedule(runnable);
15469        tasks.push(task);
15470      }
15471
15472      // Give both workers time to poll their child Pending and park in block_on.
15473      std::thread::sleep(Duration::from_millis(200));
15474      for tx in senders {
15475        let _ = tx.send(9usize);
15476      }
15477
15478      for task in tasks {
15479        futures::executor::block_on(task);
15480      }
15481      let _ = done_tx.send(());
15482    });
15483
15484    match done_rx.recv_timeout(Duration::from_secs(10)) {
15485      Ok(()) => runner.join().unwrap(),
15486      Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
15487    }
15488  }
15489
15490  /// Spin (yielding) until `condition` holds, failing loudly on timeout.
15491  /// Used to observe executor-internal states (e.g. "both drivers are
15492  /// registered as parked") without sleeps. The bound is a HANG detector,
15493  /// not a latency assertion: starved CI runners can stall fresh threads for
15494  /// hundreds of milliseconds, so it is deliberately generous.
15495  #[cfg(not(target_family = "wasm"))]
15496  fn wait_until(what: &str, condition: impl Fn() -> bool) {
15497    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
15498    while !condition() {
15499      assert!(
15500        std::time::Instant::now() < deadline,
15501        "timed out waiting for: {what}"
15502      );
15503      std::thread::yield_now();
15504    }
15505  }
15506
15507  /// Bounded join for harness threads that already signalled completion of
15508  /// their assertions: everything left on the thread is teardown (executor
15509  /// drop -- rayon pool release, `TimerHeap::drop`'s timekeeper join -- plus
15510  /// TLS destructors). An unbounded `join()` there turns a teardown wedge
15511  /// into a silent libtest-worker hang for the full CI step timeout (observed
15512  /// on Windows in run 29195160953: several park-deadline-family tests never
15513  /// completed while every in-test assertion had already passed). Fail loudly
15514  /// and NAME the awaited phase instead.
15515  #[cfg(not(target_family = "wasm"))]
15516  fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
15517    let deadline = std::time::Instant::now() + timeout;
15518    while !handle.is_finished() {
15519      assert!(
15520        std::time::Instant::now() < deadline,
15521        "thread wedged past its {timeout:?} bound while: {what}"
15522      );
15523      std::thread::sleep(Duration::from_millis(10));
15524    }
15525    handle.join().unwrap();
15526  }
15527
15528  #[cfg(not(target_family = "wasm"))]
15529  fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
15530    executor: &Arc<MultiThreadExecutor>,
15531    start_counter: &AtomicU64,
15532    claim: impl FnOnce() -> T + Send + 'static,
15533  ) -> T {
15534    use std::sync::mpsc;
15535
15536    let (claimed_tx, claimed_rx) = mpsc::channel();
15537    let (release_claim_tx, release_claim_rx) = mpsc::channel();
15538    let (started_tx, started_rx) = mpsc::channel();
15539    let (release_start_tx, release_start_rx) = mpsc::channel();
15540    let starts_before = start_counter.load(Ordering::SeqCst);
15541    let worker = std::thread::spawn(move || {
15542      MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
15543        *slot.borrow_mut() = Some(Box::new(move || {
15544          claimed_tx.send(()).unwrap();
15545          release_claim_rx.recv().unwrap();
15546        }));
15547      });
15548      MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
15549        *slot.borrow_mut() = Some(Box::new(move || {
15550          started_tx.send(()).unwrap();
15551          release_start_rx.recv().unwrap();
15552        }));
15553      });
15554      claim()
15555    });
15556    claimed_rx
15557      .recv_timeout(Duration::from_secs(2))
15558      .expect("the scheduler claim must pause after removing work");
15559
15560    let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
15561    let verdict = executor.try_begin_deadlock_verdict();
15562    let claim_verdict_rejected = verdict.is_none();
15563    drop(verdict);
15564    release_claim_tx.send(()).unwrap();
15565
15566    started_rx
15567      .recv_timeout(Duration::from_secs(2))
15568      .expect("the scheduler claim must pause after publishing its start");
15569    let admission_active_after_start =
15570      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
15571    let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
15572    let verdict = executor.try_begin_deadlock_verdict();
15573    let start_verdict_rejected = verdict.is_none();
15574    drop(verdict);
15575    release_start_tx.send(()).unwrap();
15576    let output = worker.join().unwrap();
15577
15578    assert!(
15579      admission_active,
15580      "removing scheduler work must retain a detector admission until start accounting"
15581    );
15582    assert!(
15583      claim_verdict_rejected,
15584      "a deadline verdict must not close while removed work has not published its start"
15585    );
15586    assert!(
15587      start_published,
15588      "work start accounting must publish before admission retires"
15589    );
15590    assert!(
15591      admission_active_after_start,
15592      "the detector admission must remain active until after start accounting"
15593    );
15594    assert!(
15595      start_verdict_rejected,
15596      "a deadline verdict must not close between start accounting and admission retirement"
15597    );
15598    output
15599  }
15600
15601  #[cfg(not(target_family = "wasm"))]
15602  #[test]
15603  fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
15604    let executor =
15605      multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
15606
15607    let fifo_ran = Arc::new(AtomicBool::new(false));
15608    let fifo_task_ran = Arc::clone(&fifo_ran);
15609    let (fifo, fifo_task) = async_task::spawn(
15610      async move {
15611        fifo_task_ran.store(true, Ordering::SeqCst);
15612      },
15613      |_| {},
15614    );
15615    executor.metrics.runnable_scheduled();
15616    executor.queue.lock().unwrap().push_back(fifo);
15617    let fifo_executor = Arc::clone(&executor);
15618    assert!(run_claim_with_deadlock_gate_assertion(
15619      &executor,
15620      &executor.metrics.runnable_polls,
15621      move || {
15622        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
15623        fifo_executor.run_one_fifo_runnable()
15624      }
15625    ));
15626    futures::executor::block_on(fifo_task);
15627    assert!(fifo_ran.load(Ordering::SeqCst));
15628
15629    let fifo_order = Arc::new(Mutex::new(Vec::new()));
15630    let mut fifo_tasks = Vec::new();
15631    for name in ["first", "second"] {
15632      let task_order = Arc::clone(&fifo_order);
15633      let (runnable, task) = async_task::spawn(
15634        async move {
15635          task_order.lock().unwrap().push(name);
15636        },
15637        |_| {},
15638      );
15639      executor.metrics.runnable_scheduled();
15640      executor.queue.lock().unwrap().push_back(runnable);
15641      fifo_tasks.push(task);
15642    }
15643    for _ in 0..2 {
15644      let fifo_executor = Arc::clone(&executor);
15645      assert!(run_claim_with_deadlock_gate_assertion(
15646        &executor,
15647        &executor.metrics.runnable_polls,
15648        move || {
15649          let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
15650          fifo_executor.run_one_runnable()
15651        }
15652      ));
15653    }
15654    for task in fifo_tasks {
15655      futures::executor::block_on(task);
15656    }
15657    assert_eq!(
15658      *fifo_order.lock().unwrap(),
15659      ["first", "second"],
15660      "the detector-enabled normal fallback must preserve FIFO order"
15661    );
15662
15663    let lifo_ran = Arc::new(AtomicBool::new(false));
15664    let lifo_task_ran = Arc::clone(&lifo_ran);
15665    let (lifo, lifo_task) = async_task::spawn(
15666      async move {
15667        lifo_task_ran.store(true, Ordering::SeqCst);
15668      },
15669      |_| {},
15670    );
15671    executor.metrics.runnable_scheduled();
15672    let lifo_executor = Arc::clone(&executor);
15673    assert!(run_claim_with_deadlock_gate_assertion(
15674      &executor,
15675      &executor.metrics.runnable_polls,
15676      move || {
15677        let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
15678        LIFO_SLOT.with(|slot| {
15679          assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
15680        });
15681        lifo_executor.run_one_runnable()
15682      }
15683    ));
15684    futures::executor::block_on(lifo_task);
15685    assert!(lifo_ran.load(Ordering::SeqCst));
15686
15687    executor.begin_shutdown();
15688    executor.wait_until_scheduler_idle();
15689  }
15690
15691  #[cfg(not(target_family = "wasm"))]
15692  #[test]
15693  fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
15694    let executor =
15695      multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
15696
15697    let fifo_ran = Arc::new(AtomicBool::new(false));
15698    let fifo_job_ran = Arc::clone(&fifo_ran);
15699    executor.metrics.blocking_scheduled();
15700    executor
15701      .blocking_queue
15702      .lock()
15703      .unwrap()
15704      .push(QueuedBlocking {
15705        id: BlockingJobId(u64::MAX - 90),
15706        run: Box::new(move || {
15707          fifo_job_ran.store(true, Ordering::SeqCst);
15708        }),
15709      });
15710    let fifo_executor = Arc::clone(&executor);
15711    assert!(run_claim_with_deadlock_gate_assertion(
15712      &executor,
15713      &executor.metrics.blocking_tasks_started,
15714      move || {
15715        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
15716        fifo_executor.run_one_blocking()
15717      }
15718    ));
15719    assert!(fifo_ran.load(Ordering::SeqCst));
15720
15721    let owner = executor.fresh_owner_token();
15722    let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
15723    let exact_job = BlockingJobId(u64::MAX - 91);
15724    let exact_ran = Arc::new(AtomicBool::new(false));
15725    let exact_job_ran = Arc::clone(&exact_ran);
15726    executor.metrics.blocking_scheduled();
15727    executor
15728      .blocking_queue
15729      .lock()
15730      .unwrap()
15731      .push(QueuedBlocking {
15732        id: exact_job,
15733        run: Box::new(move || {
15734          exact_job_ran.store(true, Ordering::SeqCst);
15735        }),
15736      });
15737    let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
15738    dependency.set_owned(BlockingDependency {
15739      job: exact_job,
15740      owner: Some(owner),
15741    });
15742    let exact_executor = Arc::clone(&executor);
15743    assert!(run_claim_with_deadlock_gate_assertion(
15744      &executor,
15745      &executor.metrics.blocking_tasks_started,
15746      move || {
15747        let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
15748        let _owner = BlockingOwnerGuard::enter(Some(owner));
15749        exact_executor.try_owned_blocking_over_cap(&dependency)
15750      }
15751    ));
15752    assert!(exact_ran.load(Ordering::SeqCst));
15753
15754    drop(active_owner);
15755    executor.begin_shutdown();
15756    executor.wait_until_scheduler_idle();
15757  }
15758
15759  #[cfg(not(target_family = "wasm"))]
15760  #[test]
15761  fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
15762    // Empty-registry behavior: with nothing registered, `wake_one` must return
15763    // `false` WITHOUT touching the registry mutex (the no-waiter fast path --
15764    // the common case, since queue-wakes fire on every schedule while drivers
15765    // park only in rare re-entrant `block_on`). Proxy for "did not take the
15766    // mutex": the test HOLDS the registry lock; a fast-path wake_one completes
15767    // anyway, while (control) a wake_one issued with a parker registered must
15768    // take the slow path and block on that held lock until it is released.
15769    use std::sync::mpsc;
15770    use std::time::Duration;
15771
15772    let registry = Arc::new(ParkedDrivers::default());
15773
15774    // Fast path: empty registry -> wake_one completes although the lock is held.
15775    let guard = registry.parked.lock().unwrap();
15776    let (done_tx, done_rx) = mpsc::channel();
15777    let waker_thread = {
15778      let registry = Arc::clone(&registry);
15779      std::thread::spawn(move || {
15780        done_tx.send(registry.wake_one()).unwrap();
15781      })
15782    };
15783    let woke = done_rx
15784      .recv_timeout(Duration::from_secs(5))
15785      .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
15786    assert!(!woke, "an empty registry has nobody to wake");
15787    waker_thread.join().unwrap();
15788    drop(guard);
15789
15790    // Control: with a parker registered the SAME held lock must block
15791    // wake_one (slow path). This proves the fast path above genuinely skipped
15792    // the lock rather than wake_one never locking at all.
15793    let parker = Arc::new(DriverParker::default());
15794    registry.register(&parker);
15795    let guard = registry.parked.lock().unwrap();
15796    let (done_tx, done_rx) = mpsc::channel();
15797    let waker_thread = {
15798      let registry = Arc::clone(&registry);
15799      std::thread::spawn(move || {
15800        done_tx.send(registry.wake_one()).unwrap();
15801      })
15802    };
15803    assert!(
15804      done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
15805      "wake_one with a registered parker must take the slow path (block on the held lock)"
15806    );
15807    drop(guard);
15808    let woke = done_rx
15809      .recv_timeout(Duration::from_secs(5))
15810      .expect("slow-path wake_one must complete once the lock is released");
15811    assert!(
15812      woke,
15813      "the slow path must pop and wake the registered parker"
15814    );
15815    waker_thread.join().unwrap();
15816    // The popped parker received its permit: a park now returns immediately
15817    // instead of sleeping (bounded by the test harness if broken).
15818    parker.park();
15819    assert_eq!(
15820      registry.count.load(Ordering::SeqCst),
15821      0,
15822      "wake_one deregisters what it pops"
15823    );
15824  }
15825
15826  #[cfg(not(target_family = "wasm"))]
15827  #[test]
15828  fn driver_parker_permit_granted_while_running_is_not_lost() {
15829    // Stored-permit behavior: a wake delivered while the driver is RUNNING (not
15830    // parked) must be stored as a permit that makes the next `park` return
15831    // immediately; multiple such wakes coalesce into ONE permit.
15832    use std::sync::mpsc;
15833    use std::time::Duration;
15834
15835    let parker = Arc::new(DriverParker::default());
15836    // Two wakes while "running" (nobody parked yet).
15837    parker.unpark();
15838    parker.unpark();
15839
15840    let (tx, rx) = mpsc::channel();
15841    let driver = {
15842      let parker = Arc::clone(&parker);
15843      std::thread::spawn(move || {
15844        parker.park(); // must consume the stored permit without sleeping
15845        tx.send(1).unwrap();
15846        parker.park(); // no permit left: must genuinely sleep
15847        tx.send(2).unwrap();
15848      })
15849    };
15850
15851    assert_eq!(
15852      rx.recv_timeout(Duration::from_secs(5))
15853        .expect("a stored permit must not be lost"),
15854      1
15855    );
15856    assert!(
15857      rx.recv_timeout(Duration::from_millis(300)).is_err(),
15858      "two pre-park wakes must coalesce into one permit (second park must sleep)"
15859    );
15860    parker.unpark();
15861    assert_eq!(
15862      rx.recv_timeout(Duration::from_secs(5))
15863        .expect("unpark must wake a sleeping driver"),
15864      2
15865    );
15866    driver.join().unwrap();
15867  }
15868
15869  #[cfg(not(target_family = "wasm"))]
15870  #[test]
15871  fn driver_parker_unrepresentable_timeout_remains_wakeable() {
15872    use std::sync::mpsc;
15873
15874    let parker = Arc::new(DriverParker::default());
15875    assert!(
15876      Instant::now().checked_add(Duration::MAX).is_none(),
15877      "Duration::MAX must exercise the unrepresentable-deadline path"
15878    );
15879    let (done_tx, done_rx) = mpsc::channel();
15880    let driver = {
15881      let parker = Arc::clone(&parker);
15882      std::thread::spawn(move || {
15883        done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
15884      })
15885    };
15886    wait_until("the unbounded timeout driver to sleep", || {
15887      parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
15888    });
15889    assert!(
15890      done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
15891      "an unrepresentable timeout must not expire immediately"
15892    );
15893
15894    let unparker = {
15895      let parker = Arc::clone(&parker);
15896      std::thread::spawn(move || parker.unpark())
15897    };
15898    assert!(
15899      done_rx
15900        .recv_timeout(Duration::from_secs(1))
15901        .expect("an unrepresentable timeout must remain wakeable"),
15902      "a permit must be reported as a wake"
15903    );
15904    unparker.join().unwrap();
15905    driver.join().unwrap();
15906  }
15907
15908  #[cfg(not(target_family = "wasm"))]
15909  #[test]
15910  fn parked_drivers_wake_one_wakes_exactly_one() {
15911    // Single-wake behavior: 2 parked drivers + 1 wake -> exactly one wakes, the
15912    // other stays parked. Targeted wakes must neither wake everyone nor lose
15913    // the wake.
15914    use std::sync::mpsc;
15915    use std::time::Duration;
15916
15917    let registry = Arc::new(ParkedDrivers::default());
15918    let (woke_tx, woke_rx) = mpsc::channel();
15919    let mut drivers = Vec::new();
15920    for index in 0..2 {
15921      let registry = Arc::clone(&registry);
15922      let woke_tx = woke_tx.clone();
15923      drivers.push(std::thread::spawn(move || {
15924        let parker = Arc::new(DriverParker::default());
15925        registry.register(&parker);
15926        parker.park();
15927        registry.deregister(&parker);
15928        woke_tx.send(index).unwrap();
15929      }));
15930    }
15931    wait_until("both drivers registered as parked", || {
15932      registry.count.load(Ordering::SeqCst) == 2
15933    });
15934
15935    assert!(registry.wake_one(), "wake_one must wake a parked driver");
15936    woke_rx
15937      .recv_timeout(Duration::from_secs(5))
15938      .expect("exactly one driver must wake");
15939    assert!(
15940      woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
15941      "the second driver must stay parked after a single wake_one"
15942    );
15943    assert_eq!(
15944      registry.count.load(Ordering::SeqCst),
15945      1,
15946      "one driver must remain registered"
15947    );
15948
15949    assert!(
15950      registry.wake_one(),
15951      "a second wake_one must wake the remaining driver"
15952    );
15953    woke_rx
15954      .recv_timeout(Duration::from_secs(5))
15955      .expect("the second driver must wake");
15956    for driver in drivers {
15957      driver.join().unwrap();
15958    }
15959  }
15960
15961  #[cfg(not(target_family = "wasm"))]
15962  #[test]
15963  fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
15964    use std::sync::mpsc;
15965
15966    struct ReenterRegistryOnDrop {
15967      registry: Arc<ParkedDrivers>,
15968      dropped: mpsc::Sender<()>,
15969    }
15970
15971    #[expect(
15972      clippy::manual_noop_waker,
15973      reason = "the test observes the waker payload's Drop"
15974    )]
15975    impl std::task::Wake for ReenterRegistryOnDrop {
15976      fn wake(self: Arc<Self>) {}
15977    }
15978
15979    impl Drop for ReenterRegistryOnDrop {
15980      fn drop(&mut self) {
15981        let parker = Arc::new(DriverParker::default());
15982        self.registry.register(&parker);
15983        self.registry.deregister(&parker);
15984        self.dropped.send(()).unwrap();
15985      }
15986    }
15987
15988    let registry = Arc::new(ParkedDrivers::default());
15989    let dependency = Arc::new(TaskDependency::default());
15990    let (dropped_tx, dropped_rx) = mpsc::channel();
15991    let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
15992      registry: Arc::clone(&registry),
15993      dropped: dropped_tx,
15994    }));
15995    dependency.register_waiter(&waiter);
15996    drop(waiter);
15997
15998    let parker = Arc::new(DriverParker::default());
15999    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16000    drop(dependency);
16001
16002    let retiring_registry = Arc::clone(&registry);
16003    let retiring_parker = Arc::clone(&parker);
16004    let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
16005    dropped_rx
16006      .recv_timeout(Duration::from_secs(1))
16007      .expect("retained dependency waiters must be destroyed after releasing the registry lock");
16008    retiring.join().unwrap();
16009  }
16010
16011  #[cfg(not(target_family = "wasm"))]
16012  #[test]
16013  fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
16014    use std::sync::mpsc;
16015
16016    let registry = Arc::new(ParkedDrivers::default());
16017    let owner = BlockingOwnerToken {
16018      executor_id: u64::MAX - 70,
16019      frame: u64::MAX - 71,
16020    };
16021    let dependency = Arc::new(TaskDependency::default());
16022    dependency.set_owned(BlockingDependency {
16023      job: BlockingJobId(u64::MAX - 72),
16024      owner: Some(owner),
16025    });
16026    let parker = Arc::new(DriverParker::default());
16027    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16028
16029    let (predicate_tx, predicate_rx) = mpsc::channel();
16030    let (release_tx, release_rx) = mpsc::channel();
16031    let selecting_registry = Arc::clone(&registry);
16032    let selector = std::thread::spawn(move || {
16033      DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
16034        *slot.borrow_mut() = Some(Box::new(move || {
16035          predicate_tx.send(()).unwrap();
16036          release_rx.recv().unwrap();
16037        }));
16038      });
16039      selecting_registry.wake_one_owner_dependency(owner)
16040    });
16041
16042    predicate_rx
16043      .recv_timeout(Duration::from_secs(2))
16044      .expect("owner selection must reach the dependency predicate");
16045    let registry_was_unlocked = registry.parked.try_lock().is_ok();
16046    release_tx.send(()).unwrap();
16047    assert!(
16048      selector.join().unwrap(),
16049      "the exact-owner parker must still be selected"
16050    );
16051
16052    assert!(
16053      registry_was_unlocked,
16054      "owner dependency predicates must not run while the parked-driver registry is locked"
16055    );
16056    assert!(parker.consume_permit());
16057    assert_eq!(parker.take_owner_handoff(), Some(owner));
16058  }
16059
16060  #[cfg(not(target_family = "wasm"))]
16061  #[test]
16062  fn owner_handoff_skips_newer_untagged_dependency() {
16063    let registry = ParkedDrivers::default();
16064    let owner = BlockingOwnerToken {
16065      executor_id: u64::MAX - 1,
16066      frame: u64::MAX - 2,
16067    };
16068    let owned_dependency = Arc::new(TaskDependency::default());
16069    owned_dependency.set_owned(BlockingDependency {
16070      job: BlockingJobId(u64::MAX - 3),
16071      owner: Some(owner),
16072    });
16073    let untagged_dependency = Arc::new(TaskDependency::default());
16074    untagged_dependency.set_owned(BlockingDependency {
16075      job: BlockingJobId(u64::MAX - 4),
16076      owner: None,
16077    });
16078    let owned = Arc::new(DriverParker::default());
16079    let untagged = Arc::new(DriverParker::default());
16080
16081    registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
16082    // Register the unrelated dependency last so an unfiltered LIFO retry
16083    // would consume the exact owner handoff.
16084    registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
16085
16086    assert!(registry.wake_one_owner_dependency(owner));
16087    assert!(
16088      owned.consume_permit(),
16089      "the exact owner dependency must receive the retry"
16090    );
16091    assert!(
16092      !untagged.consume_permit(),
16093      "an untagged dependency must not consume an exact owner handoff"
16094    );
16095    registry.deregister(&untagged);
16096  }
16097
16098  #[cfg(not(target_family = "wasm"))]
16099  #[test]
16100  fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
16101    let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
16102    let owner = executor.fresh_owner_token();
16103    let valid_dependency = Arc::new(TaskDependency::default());
16104    valid_dependency.set_owned(BlockingDependency {
16105      job: BlockingJobId(u64::MAX - 45),
16106      owner: Some(owner),
16107    });
16108    let withdrawn_dependency = Arc::new(TaskDependency::default());
16109    withdrawn_dependency.set_owned(BlockingDependency {
16110      job: BlockingJobId(u64::MAX - 46),
16111      owner: Some(owner),
16112    });
16113    let valid = Arc::new(DriverParker::default());
16114    let withdrawn = Arc::new(DriverParker::default());
16115
16116    executor
16117      .parked_drivers
16118      .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
16119    executor.parked_drivers.register_with_role(
16120      &withdrawn,
16121      true,
16122      Some(Arc::clone(&withdrawn_dependency)),
16123    );
16124    assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
16125    withdrawn_dependency.clear();
16126
16127    assert_eq!(
16128      executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
16129      Some(false),
16130      "a selected dependency withdrawn before retry must forward the owner handoff"
16131    );
16132    assert!(
16133      valid.consume_permit(),
16134      "the next live same-owner dependency must receive the forwarded wake"
16135    );
16136    assert_eq!(
16137      valid.take_owner_handoff(),
16138      Some(owner),
16139      "the forwarded wake must retain the exact owner identity"
16140    );
16141  }
16142
16143  #[cfg(not(target_family = "wasm"))]
16144  #[test]
16145  fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
16146    use std::sync::mpsc;
16147
16148    let executor = multi_thread_executor(2, None, "owner-handoff-publication");
16149    let owner = executor.fresh_owner_token();
16150    let fallback_dependency = Arc::new(TaskDependency::default());
16151    fallback_dependency.set_owned(BlockingDependency {
16152      job: BlockingJobId(u64::MAX - 47),
16153      owner: Some(owner),
16154    });
16155    let exiting_dependency = Arc::new(TaskDependency::default());
16156    exiting_dependency.set_owned(BlockingDependency {
16157      job: BlockingJobId(u64::MAX - 48),
16158      owner: Some(owner),
16159    });
16160    let fallback = Arc::new(DriverParker::default());
16161    let exiting = Arc::new(DriverParker::default());
16162
16163    executor.parked_drivers.register_with_role(
16164      &fallback,
16165      true,
16166      Some(Arc::clone(&fallback_dependency)),
16167    );
16168    executor.parked_drivers.register_with_role(
16169      &exiting,
16170      true,
16171      Some(Arc::clone(&exiting_dependency)),
16172    );
16173
16174    let (selected_tx, selected_rx) = mpsc::channel();
16175    let (release_selection_tx, release_selection_rx) = mpsc::channel();
16176    let selector_executor = Arc::clone(&executor);
16177    let selector = std::thread::spawn(move || {
16178      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
16179        *slot.borrow_mut() = Some(Box::new(move || {
16180          selected_tx.send(()).unwrap();
16181          release_selection_rx.recv().unwrap();
16182        }));
16183      });
16184      assert!(
16185        selector_executor
16186          .parked_drivers
16187          .wake_one_owner_dependency(owner)
16188      );
16189    });
16190    selected_rx
16191      .recv_timeout(Duration::from_secs(2))
16192      .expect("selection must pause after removing the exact-owner parker");
16193
16194    let (exited_tx, exited_rx) = mpsc::channel();
16195    let exiting_executor = Arc::clone(&executor);
16196    let exiting_driver = Arc::clone(&exiting);
16197    let driver = std::thread::spawn(move || {
16198      exiting_driver.park();
16199      exiting_executor.parked_drivers.deregister(&exiting_driver);
16200      exiting_executor.forward_owner_handoff(&exiting_driver);
16201      exited_tx.send(()).unwrap();
16202    });
16203    exiting.unpark();
16204    exited_rx
16205      .recv_timeout(Duration::from_secs(2))
16206      .expect("the direct future wake must let the selected driver exit");
16207
16208    release_selection_tx.send(()).unwrap();
16209    selector.join().unwrap();
16210    driver.join().unwrap();
16211
16212    assert!(
16213      fallback.consume_permit(),
16214      "an exiting selected driver must forward the owner handoff to the next dependency"
16215    );
16216    assert_eq!(
16217      fallback.take_owner_handoff(),
16218      Some(owner),
16219      "the forwarded handoff must retain the exact owner identity"
16220    );
16221  }
16222
16223  #[cfg(not(target_family = "wasm"))]
16224  #[test]
16225  fn cooperative_panic_forwards_pending_owner_handoff() {
16226    use std::sync::mpsc;
16227
16228    struct PanicAfterWake {
16229      dependency: BlockingDependency,
16230      first_poll: Option<mpsc::Sender<()>>,
16231    }
16232
16233    impl Future for PanicAfterWake {
16234      type Output = ();
16235
16236      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16237        if let Some(first_poll) = self.first_poll.take() {
16238          record_blocking_dependency(self.dependency);
16239          first_poll.send(()).unwrap();
16240          return Poll::Pending;
16241        }
16242        panic!("intentional cooperative driver panic after owner handoff");
16243      }
16244    }
16245
16246    let executor = multi_thread_executor(2, None, "panic-owner-handoff");
16247    let owner = executor.fresh_owner_token();
16248    let fallback_dependency = Arc::new(TaskDependency::default());
16249    fallback_dependency.set_owned(BlockingDependency {
16250      job: BlockingJobId(u64::MAX - 49),
16251      owner: Some(owner),
16252    });
16253    let fallback = Arc::new(DriverParker::default());
16254    executor.parked_drivers.register_with_role(
16255      &fallback,
16256      true,
16257      Some(Arc::clone(&fallback_dependency)),
16258    );
16259
16260    let (first_poll_tx, first_poll_rx) = mpsc::channel();
16261    let (panicked_tx, panicked_rx) = mpsc::channel();
16262    let driver_executor = Arc::clone(&executor);
16263    let driver = std::thread::spawn(move || {
16264      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
16265      let mut future = std::pin::pin!(PanicAfterWake {
16266        dependency: BlockingDependency {
16267          job: BlockingJobId(u64::MAX - 50),
16268          owner: Some(owner)
16269        },
16270        first_poll: Some(first_poll_tx),
16271      });
16272      let panicked = catch_unwind(AssertUnwindSafe(|| {
16273        driver_executor.block_on(future.as_mut());
16274      }))
16275      .is_err();
16276      panicked_tx.send(panicked).unwrap();
16277    });
16278
16279    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16280    wait_until("the panicking cooperative driver to park", || {
16281      executor.parked_drivers.count.load(Ordering::SeqCst) == 2
16282    });
16283    assert!(
16284      executor.parked_drivers.wake_one_owner_dependency(owner),
16285      "the exact owner handoff must select the cooperative driver"
16286    );
16287    assert!(
16288      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
16289      "the test future must unwind from its second poll"
16290    );
16291    driver.join().unwrap();
16292
16293    assert!(
16294      fallback.consume_permit(),
16295      "panic cleanup must forward the unconsumed owner handoff"
16296    );
16297    assert_eq!(
16298      fallback.take_owner_handoff(),
16299      Some(owner),
16300      "panic cleanup must preserve the exact owner identity"
16301    );
16302  }
16303
16304  #[cfg(not(target_family = "wasm"))]
16305  #[test]
16306  fn cooperative_panic_flushes_lifo_slot() {
16307    struct ScheduleThenPanic {
16308      executor: Arc<MultiThreadExecutor>,
16309      ran: Arc<AtomicBool>,
16310    }
16311
16312    impl Future for ScheduleThenPanic {
16313      type Output = ();
16314
16315      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16316        let ran = Arc::clone(&self.ran);
16317        let scheduler = Arc::clone(&self.executor);
16318        let (runnable, task) = async_task::spawn(
16319          async move {
16320            ran.store(true, Ordering::SeqCst);
16321          },
16322          move |runnable| scheduler.schedule(runnable),
16323        );
16324        self.executor.schedule(runnable);
16325        task.detach();
16326        panic!("intentional cooperative driver panic with a populated LIFO slot");
16327      }
16328    }
16329
16330    let executor = multi_thread_executor(2, None, "panic-lifo-flush");
16331    executor
16332      .active_drainers
16333      .store(executor.max_drainers, Ordering::SeqCst);
16334    let ran = Arc::new(AtomicBool::new(false));
16335    let panicked = {
16336      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
16337      let mut future = std::pin::pin!(ScheduleThenPanic {
16338        executor: Arc::clone(&executor),
16339        ran: Arc::clone(&ran),
16340      });
16341      catch_unwind(AssertUnwindSafe(|| {
16342        executor.block_on(future.as_mut());
16343      }))
16344      .is_err()
16345    };
16346
16347    assert!(panicked, "the test future must unwind from its first poll");
16348    assert!(
16349      executor.pop_lifo_slot().is_none(),
16350      "panic cleanup must empty the LIFO slot"
16351    );
16352    assert_eq!(
16353      executor.queue.lock().unwrap().len(),
16354      1,
16355      "panic cleanup must move the slot runnable to the shared FIFO"
16356    );
16357    assert!(
16358      executor.run_one(),
16359      "the flushed runnable must remain executable"
16360    );
16361    assert!(ran.load(Ordering::SeqCst));
16362    executor.active_drainers.store(0, Ordering::SeqCst);
16363  }
16364
16365  #[cfg(not(target_family = "wasm"))]
16366  #[test]
16367  fn cooperative_panic_compensates_absorbed_queue_wake() {
16368    use std::sync::mpsc;
16369
16370    struct PanicAfterWake {
16371      first_poll: Option<mpsc::Sender<()>>,
16372    }
16373
16374    impl Future for PanicAfterWake {
16375      type Output = ();
16376
16377      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16378        if let Some(first_poll) = self.first_poll.take() {
16379          first_poll.send(()).unwrap();
16380          return Poll::Pending;
16381        }
16382        panic!("intentional cooperative driver panic after a queue wake");
16383      }
16384    }
16385
16386    let executor = multi_thread_executor(2, None, "panic-wake-compensation");
16387    executor
16388      .active_drainers
16389      .store(executor.max_drainers, Ordering::SeqCst);
16390    let fallback = Arc::new(DriverParker::default());
16391    executor.parked_drivers.register(&fallback);
16392    let (first_poll_tx, first_poll_rx) = mpsc::channel();
16393    let (panicked_tx, panicked_rx) = mpsc::channel();
16394    let driver_executor = Arc::clone(&executor);
16395    let driver = std::thread::spawn(move || {
16396      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
16397      let mut future = std::pin::pin!(PanicAfterWake {
16398        first_poll: Some(first_poll_tx)
16399      });
16400      let panicked = catch_unwind(AssertUnwindSafe(|| {
16401        driver_executor.block_on(future.as_mut());
16402      }))
16403      .is_err();
16404      panicked_tx.send(panicked).unwrap();
16405    });
16406
16407    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16408    wait_until(
16409      "the panicking cooperative driver to park after the fallback",
16410      || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
16411    );
16412
16413    let ran = Arc::new(AtomicBool::new(false));
16414    let ran_task = Arc::clone(&ran);
16415    let scheduler = Arc::clone(&executor);
16416    let (runnable, task) = async_task::spawn(
16417      async move {
16418        ran_task.store(true, Ordering::SeqCst);
16419      },
16420      move |runnable| scheduler.schedule(runnable),
16421    );
16422    task.detach();
16423    executor.metrics.runnable_scheduled();
16424    executor.queue.lock().unwrap().push_back(runnable);
16425    assert!(
16426      executor.parked_drivers.wake_one(),
16427      "the queued runnable's wake must be absorbed by the cooperative driver"
16428    );
16429
16430    assert!(
16431      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
16432      "the test future must unwind after absorbing the queue wake"
16433    );
16434    driver.join().unwrap();
16435    assert!(
16436      fallback.consume_permit(),
16437      "panic cleanup must compensate the absorbed queue wake to the fallback driver"
16438    );
16439    assert!(
16440      executor.run_one(),
16441      "the queued runnable must remain executable"
16442    );
16443    assert!(ran.load(Ordering::SeqCst));
16444    executor.active_drainers.store(0, Ordering::SeqCst);
16445  }
16446
16447  #[cfg(not(target_family = "wasm"))]
16448  #[test]
16449  fn multi_thread_future_wake_targets_its_own_parked_driver() {
16450    // Future-wake targeting invariant: a future-wake must reach the driver that
16451    // AWAITS that future, not "some parked driver". D1 parks first awaiting
16452    // rx1; D2 parks after it awaiting rx2. Completing rx1 must wake D1 even
16453    // though D2 parked more recently -- a future-wake misrouted through the
16454    // registry (which pops most-recently-parked) would wake D2, leave D1
16455    // parked forever, and trip the bounded timeout. The old notify_all
16456    // broadcast never distinguished drivers, so this pins the new obligation.
16457    use std::sync::mpsc;
16458    use std::time::Duration;
16459
16460    let (done_tx, done_rx) = mpsc::channel();
16461    let runner = std::thread::spawn(move || {
16462      let metrics = Arc::new(RuntimeMetrics::default());
16463      let options = RuntimeOptions {
16464        flavor: RuntimeFlavor::MultiThread,
16465        worker_threads: 2,
16466        max_blocking_tasks: 2,
16467        thread_name_prefix: "wake-target".to_string(),
16468        park_deadline: None,
16469      };
16470      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16471
16472      let (tx1, rx1) = oneshot::channel::<usize>();
16473      let (tx2, rx2) = oneshot::channel::<usize>();
16474      let t1_done = Arc::new(AtomicBool::new(false));
16475      let t2_done = Arc::new(AtomicBool::new(false));
16476
16477      // T1: parks FIRST, awaiting rx1.
16478      let t1_exec = Arc::clone(&executor);
16479      let t1_flag = Arc::clone(&t1_done);
16480      let t1_body = async move {
16481        let mut inner = std::pin::pin!(async move {
16482          assert_eq!(rx1.await.unwrap(), 7usize);
16483        });
16484        t1_exec.block_on(inner.as_mut());
16485        t1_flag.store(true, Ordering::SeqCst);
16486      };
16487      let sched1 = Arc::clone(&executor);
16488      let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
16489      executor.schedule(runnable1);
16490      wait_until("driver 1 parked", || {
16491        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
16492      });
16493
16494      // T2: parks SECOND (most recently). Its runnable is pushed RAW (no
16495      // `schedule`, so no wake_one can pop the already-parked D1) and a
16496      // drainer is spawned for it on the free second worker.
16497      let t2_exec = Arc::clone(&executor);
16498      let t2_flag = Arc::clone(&t2_done);
16499      let t2_body = async move {
16500        let mut inner = std::pin::pin!(async move {
16501          assert_eq!(rx2.await.unwrap(), 9usize);
16502        });
16503        t2_exec.block_on(inner.as_mut());
16504        t2_flag.store(true, Ordering::SeqCst);
16505      };
16506      let sched2 = Arc::clone(&executor);
16507      let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
16508      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
16509      executor.queue.lock().unwrap().push_back(runnable2);
16510      executor.ensure_drainer();
16511      wait_until("driver 2 parked", || {
16512        executor.parked_drivers.count.load(Ordering::SeqCst) == 2
16513      });
16514
16515      // Complete D1's future. The wake must land on D1 (first-parked), the
16516      // driver that owns rx1's waker -- not on the more recently parked D2.
16517      tx1.send(7usize).unwrap();
16518      wait_until("T1 completed via its own driver's wake", || {
16519        t1_done.load(Ordering::SeqCst)
16520      });
16521      futures::executor::block_on(task1);
16522      assert!(
16523        !t2_done.load(Ordering::SeqCst),
16524        "D2 must still be parked: its future was never completed"
16525      );
16526      assert_eq!(
16527        executor.parked_drivers.count.load(Ordering::SeqCst),
16528        1,
16529        "exactly the untargeted driver must remain parked"
16530      );
16531
16532      // Teardown: release D2 as well.
16533      tx2.send(9usize).unwrap();
16534      futures::executor::block_on(task2);
16535      assert!(t2_done.load(Ordering::SeqCst));
16536      let _ = done_tx.send(());
16537    });
16538
16539    match done_rx.recv_timeout(Duration::from_secs(10)) {
16540      Ok(()) => runner.join().unwrap(),
16541      Err(error) => panic!(
16542        "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
16543      ),
16544    }
16545  }
16546
16547  #[cfg(not(target_family = "wasm"))]
16548  #[test]
16549  fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
16550    // Miss-compensation invariant: a driver that exits `cooperative_block_on`
16551    // with queue work remaining must hand the wake on because it may have
16552    // absorbed a `wake_one` permit meant for that work. The SOLE pool worker
16553    // is parked as a cooperative driver, a runnable is pushed without a wake,
16554    // and the test thread forces a ready future through the cooperative branch.
16555    // Exit compensation is the ONLY mechanism left that can wake the parked
16556    // worker to drain the runnable.
16557    use std::sync::mpsc;
16558    use std::time::Duration;
16559
16560    let (done_tx, done_rx) = mpsc::channel();
16561    let runner = std::thread::spawn(move || {
16562      let metrics = Arc::new(RuntimeMetrics::default());
16563      let options = RuntimeOptions {
16564        flavor: RuntimeFlavor::MultiThread,
16565        worker_threads: 1,
16566        max_blocking_tasks: 1,
16567        thread_name_prefix: "wake-compensate".to_string(),
16568        park_deadline: None,
16569      };
16570      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16571
16572      // Park the sole worker as a cooperative driver awaiting a gate.
16573      let (gate_tx, gate_rx) = oneshot::channel::<()>();
16574      let td_exec = Arc::clone(&executor);
16575      let td_body = async move {
16576        let mut inner = std::pin::pin!(async move {
16577          gate_rx.await.unwrap();
16578        });
16579        td_exec.block_on(inner.as_mut());
16580      };
16581      let sched = Arc::clone(&executor);
16582      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
16583      executor.schedule(td_runnable);
16584      wait_until("the sole driver parked", || {
16585        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
16586      });
16587
16588      // Strand a runnable: pushed RAW, so no wake accompanies it, and the
16589      // sole worker is parked-as-drainer, so `ensure_drainer` cannot help.
16590      let (ran_tx, ran_rx) = mpsc::channel::<()>();
16591      let sched_stranded = Arc::clone(&executor);
16592      let (stranded_runnable, stranded_task) = async_task::spawn(
16593        async move {
16594          ran_tx.send(()).unwrap();
16595        },
16596        move |r| sched_stranded.schedule(r),
16597      );
16598      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
16599      executor.queue.lock().unwrap().push_back(stranded_runnable);
16600      stranded_task.detach();
16601
16602      // Drive a ready future through the cooperative branch on THIS thread
16603      // (fake on-pool marker, as in the executor-scoping test). Its first
16604      // poll is Ready, so the loop performs no work itself -- only the exit
16605      // compensation can hand the queued runnable to the parked driver.
16606      {
16607        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
16608        let mut ready = std::pin::pin!(async {});
16609        executor.block_on(ready.as_mut());
16610      }
16611
16612      ran_rx.recv_timeout(Duration::from_secs(5)).expect(
16613        "exit compensation must wake the parked driver to run the stranded queued runnable",
16614      );
16615
16616      // Teardown: release the parked driver.
16617      gate_tx.send(()).unwrap();
16618      futures::executor::block_on(td_task);
16619      let _ = done_tx.send(());
16620    });
16621
16622    match done_rx.recv_timeout(Duration::from_secs(10)) {
16623      Ok(()) => runner.join().unwrap(),
16624      Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
16625    }
16626  }
16627
16628  #[cfg(not(target_family = "wasm"))]
16629  #[test]
16630  fn cooperative_exit_compensates_blocking_only_residue() {
16631    use std::sync::mpsc;
16632
16633    struct Controlled {
16634      ready: Arc<AtomicBool>,
16635      entered: Option<mpsc::Sender<()>>,
16636    }
16637
16638    impl Future for Controlled {
16639      type Output = ();
16640
16641      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16642        if let Some(entered) = self.entered.take() {
16643          entered.send(()).unwrap();
16644        }
16645        if self.ready.load(Ordering::SeqCst) {
16646          Poll::Ready(())
16647        } else {
16648          Poll::Pending
16649        }
16650      }
16651    }
16652
16653    let options = RuntimeOptions {
16654      flavor: RuntimeFlavor::MultiThread,
16655      worker_threads: 2,
16656      max_blocking_tasks: 1,
16657      thread_name_prefix: "blocking-exit-compensation".to_string(),
16658      park_deadline: None,
16659    };
16660    let executor =
16661      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16662    let ready = Arc::new(AtomicBool::new(false));
16663    let (entered_tx, entered_rx) = mpsc::channel();
16664    let (returned_tx, returned_rx) = mpsc::channel();
16665    let (release_tx, release_rx) = mpsc::channel();
16666    let task_exec = Arc::clone(&executor);
16667    let task_ready = Arc::clone(&ready);
16668    let scheduler = Arc::clone(&executor);
16669    let (runnable, task) = async_task::spawn(
16670      async move {
16671        let mut future = std::pin::pin!(Controlled {
16672          ready: task_ready,
16673          entered: Some(entered_tx)
16674        });
16675        task_exec.block_on(future.as_mut());
16676        returned_tx.send(()).unwrap();
16677        release_rx.recv().unwrap();
16678      },
16679      move |runnable| scheduler.schedule(runnable),
16680    );
16681    executor.schedule(runnable);
16682    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16683
16684    let timer_exec = Arc::clone(&executor);
16685    let timer_scheduler = Arc::clone(&executor);
16686    let (timer_runnable, timer_task) = async_task::spawn(
16687      async move {
16688        heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
16689      },
16690      move |runnable| timer_scheduler.schedule(runnable),
16691    );
16692    executor.schedule(timer_runnable);
16693    wait_until("the dedicated timekeeper parked", || {
16694      executor
16695        .timers
16696        .inner
16697        .lock()
16698        .unwrap()
16699        .timekeeper_parker
16700        .is_some()
16701    });
16702
16703    ready.store(true, Ordering::SeqCst);
16704    let (blocking_tx, blocking_rx) = mpsc::channel();
16705    let blocking = executor.schedule_blocking(move || {
16706      blocking_tx.send(()).unwrap();
16707    });
16708    blocking_rx
16709      .recv_timeout(Duration::from_secs(2))
16710      .expect("blocking-only residue must be serviced by a Rayon worker");
16711    returned_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16712    release_tx.send(()).unwrap();
16713    futures::executor::block_on(task);
16714    futures::executor::block_on(blocking).unwrap();
16715    executor.shutdown_timers();
16716    futures::executor::block_on(timer_task);
16717  }
16718
16719  #[cfg(not(target_family = "wasm"))]
16720  #[test]
16721  fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
16722    let options = RuntimeOptions {
16723      flavor: RuntimeFlavor::MultiThread,
16724      worker_threads: 2,
16725      max_blocking_tasks: 1,
16726      thread_name_prefix: "stopped-exit-compensation".to_string(),
16727      park_deadline: None,
16728    };
16729    let executor =
16730      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16731    let ran = Arc::new(AtomicBool::new(false));
16732    let blocking_ran = Arc::clone(&ran);
16733
16734    // Keep schedule_blocking from starting a Rayon drainer so the stop flag can
16735    // become observable while this job remains queued, matching the original
16736    // begin_shutdown ordering window exactly.
16737    executor
16738      .active_drainers
16739      .store(executor.max_drainers, Ordering::SeqCst);
16740    let blocking = executor.schedule_blocking(move || {
16741      blocking_ran.store(true, Ordering::SeqCst);
16742    });
16743    assert!(!executor.blocking_queue.lock().unwrap().is_empty());
16744
16745    executor.stop.begin_shutdown();
16746    executor.compensate_queued_work_wake(true);
16747    let ran_after_stop = ran.load(Ordering::SeqCst);
16748
16749    executor.active_drainers.store(0, Ordering::SeqCst);
16750    executor.begin_shutdown();
16751    let blocking_result = futures::executor::block_on(blocking);
16752
16753    assert!(
16754      !ran_after_stop,
16755      "exit compensation started queued blocking work after stop"
16756    );
16757    assert!(
16758      blocking_result.is_err(),
16759      "shutdown must cancel the queued blocking job"
16760    );
16761  }
16762
16763  #[cfg(not(target_family = "wasm"))]
16764  #[test]
16765  fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
16766    // Targeted-wake stress: N rounds of {producer: publish work,
16767    // wake_one()} racing {consumer: check work, register, re-check, park} --
16768    // the exact schedule-vs-park protocol `schedule` and
16769    // `cooperative_block_on` run. This walks the interleaving at every offset
16770    // the OS scheduler produces; a single lost wakeup parks the consumer
16771    // forever with work pending, and the bounded recv_timeout turns that into
16772    // a loud failure instead of a hung suite. (No loom in this repo; std
16773    // threads.)
16774    use std::sync::mpsc;
16775    use std::time::Duration;
16776
16777    const ROUNDS: usize = 20_000;
16778
16779    let (done_tx, done_rx) = mpsc::channel();
16780    let registry = Arc::new(ParkedDrivers::default());
16781    let work = Arc::new(AtomicBool::new(false));
16782
16783    let consumer = {
16784      let registry = Arc::clone(&registry);
16785      let work = Arc::clone(&work);
16786      std::thread::spawn(move || {
16787        let parker = Arc::new(DriverParker::default());
16788        for _ in 0..ROUNDS {
16789          loop {
16790            if work.swap(false, Ordering::SeqCst) {
16791              break;
16792            }
16793            // The cooperative park protocol: register FIRST, then re-check,
16794            // then park (see `ParkedDrivers::wake_one`).
16795            registry.register(&parker);
16796            if work.load(Ordering::SeqCst) {
16797              registry.deregister(&parker);
16798              continue;
16799            }
16800            parker.park();
16801            registry.deregister(&parker);
16802          }
16803        }
16804        done_tx.send(()).unwrap();
16805      })
16806    };
16807
16808    for _ in 0..ROUNDS {
16809      work.store(true, Ordering::SeqCst);
16810      // The schedule side: publish work, then wake one parked driver. When
16811      // wake_one misses (consumer not registered yet), the consumer's
16812      // post-register re-check must see the work instead.
16813      let _ = registry.wake_one();
16814      while work.load(Ordering::SeqCst) {
16815        std::hint::spin_loop();
16816      }
16817    }
16818
16819    done_rx
16820      .recv_timeout(Duration::from_secs(30))
16821      .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
16822    consumer.join().unwrap();
16823  }
16824
16825  #[cfg(not(target_family = "wasm"))]
16826  #[test]
16827  fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
16828    // LIFO locality invariant: a runnable scheduled from a pool worker of the SAME
16829    // executor lands in that worker's LIFO slot (no FIFO push, no wake) and is
16830    // run by the next drain/run_one iteration BEFORE older shared-FIFO work.
16831    let options = RuntimeOptions {
16832      flavor: RuntimeFlavor::MultiThread,
16833      worker_threads: 2,
16834      max_blocking_tasks: 2,
16835      thread_name_prefix: "lifo-order".to_string(),
16836      park_deadline: None,
16837    };
16838    let executor =
16839      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16840    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
16841
16842    // Older work, pushed RAW onto the FIFO (so no drainer is spawned that
16843    // could race the manual `run_one` calls below).
16844    let old_order = Arc::clone(&order);
16845    let sched = Arc::clone(&executor);
16846    let (old_runnable, old_task) = async_task::spawn(
16847      async move {
16848        old_order.lock().unwrap().push("old");
16849      },
16850      move |r| sched.schedule(r),
16851    );
16852    executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
16853    executor.queue.lock().unwrap().push_back(old_runnable);
16854    old_task.detach();
16855
16856    // Simulate being on this executor's pool worker: the schedule below must
16857    // take the slot path (which never touches the FIFO nor spawns a drainer).
16858    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
16859    let new_order = Arc::clone(&order);
16860    let sched = Arc::clone(&executor);
16861    let (new_runnable, new_task) = async_task::spawn(
16862      async move {
16863        new_order.lock().unwrap().push("new");
16864      },
16865      move |r| sched.schedule(r),
16866    );
16867    executor.schedule(new_runnable);
16868    new_task.detach();
16869    assert_eq!(
16870      executor.queue.lock().unwrap().len(),
16871      1,
16872      "the slot-path schedule must not push to the shared FIFO"
16873    );
16874
16875    assert!(executor.run_one(), "run_one must pop the slot first");
16876    assert!(executor.run_one(), "run_one must then pop the FIFO");
16877    assert!(!executor.run_one(), "no work must remain");
16878    assert_eq!(
16879      *order.lock().unwrap(),
16880      vec!["new", "old"],
16881      "the slot runnable (newest) must run before older FIFO work"
16882    );
16883  }
16884
16885  #[cfg(not(target_family = "wasm"))]
16886  #[test]
16887  fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
16888    let options = RuntimeOptions {
16889      flavor: RuntimeFlavor::MultiThread,
16890      worker_threads: 2,
16891      max_blocking_tasks: 1,
16892      thread_name_prefix: "fifo-owner".to_string(),
16893      park_deadline: None,
16894    };
16895    let executor =
16896      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16897    let observed_owner = Arc::new(Mutex::new(None));
16898    let observed = Arc::clone(&observed_owner);
16899    let (runnable, task) = async_task::spawn(
16900      async move {
16901        *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
16902      },
16903      |_| {},
16904    );
16905    task.detach();
16906    executor.metrics.runnable_scheduled();
16907    executor.queue.lock().unwrap().push_back(runnable);
16908    executor.active_drainers.store(1, Ordering::Release);
16909
16910    let owner = executor.fresh_owner_token();
16911    let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
16912    Arc::clone(&executor).drain();
16913
16914    assert_eq!(
16915      *observed_owner.lock().unwrap(),
16916      Some(None),
16917      "FIFO runnables must not borrow a blocking closure's over-cap privilege"
16918    );
16919    assert_eq!(
16920      BLOCKING_OWNER.with(std::cell::Cell::get),
16921      Some(owner),
16922      "the ambient blocking owner must be restored after the runnable"
16923    );
16924  }
16925
16926  #[cfg(not(target_family = "wasm"))]
16927  #[test]
16928  fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
16929    // Drain integration: with one worker, task P scheduling
16930    // child C mid-drain must see C run IMMEDIATELY after P (via the slot),
16931    // jumping ahead of the older queued T1/T2.
16932    use std::sync::mpsc;
16933    use std::time::Duration;
16934
16935    let (done_tx, done_rx) = mpsc::channel();
16936    let runner = std::thread::spawn(move || {
16937      let options = RuntimeOptions {
16938        flavor: RuntimeFlavor::MultiThread,
16939        worker_threads: 1,
16940        max_blocking_tasks: 1,
16941        thread_name_prefix: "lifo-drain".to_string(),
16942        park_deadline: None,
16943      };
16944      let executor =
16945        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16946      let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
16947
16948      let p_exec = Arc::clone(&executor);
16949      let p_order = Arc::clone(&order);
16950      let p_body = async move {
16951        p_order.lock().unwrap().push("P");
16952        // Scheduled from the pool worker mid-drain: must land in the slot.
16953        let c_order = Arc::clone(&p_order);
16954        let sched = Arc::clone(&p_exec);
16955        let (c_runnable, c_task) = async_task::spawn(
16956          async move {
16957            c_order.lock().unwrap().push("C");
16958          },
16959          move |r| sched.schedule(r),
16960        );
16961        p_exec.schedule(c_runnable);
16962        c_task.detach();
16963      };
16964
16965      // Push P, T1, T2 RAW (deterministic FIFO order, exactly one drainer),
16966      // mirroring the `runnable_scheduled` accounting a real schedule does so
16967      // the queued-runnables counter stays balanced.
16968      let mut tasks = Vec::new();
16969      let sched = Arc::clone(&executor);
16970      let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
16971      executor.metrics.runnable_scheduled();
16972      executor.queue.lock().unwrap().push_back(p_runnable);
16973      tasks.push(p_task);
16974      for name in ["T1", "T2"] {
16975        let t_order = Arc::clone(&order);
16976        let sched = Arc::clone(&executor);
16977        let (t_runnable, t_task) = async_task::spawn(
16978          async move {
16979            t_order.lock().unwrap().push(name);
16980          },
16981          move |r| sched.schedule(r),
16982        );
16983        executor.metrics.runnable_scheduled();
16984        executor.queue.lock().unwrap().push_back(t_runnable);
16985        tasks.push(t_task);
16986      }
16987      executor.ensure_drainer();
16988
16989      for task in tasks {
16990        futures::executor::block_on(task);
16991      }
16992      wait_until("the detached child C ran", || {
16993        order.lock().unwrap().len() == 4
16994      });
16995      assert_eq!(
16996        *order.lock().unwrap(),
16997        vec!["P", "C", "T1", "T2"],
16998        "the slot child C must run right after its scheduler P, ahead of older FIFO work"
16999      );
17000      let _ = done_tx.send(());
17001    });
17002
17003    match done_rx.recv_timeout(Duration::from_secs(10)) {
17004      Ok(()) => runner.join().unwrap(),
17005      Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
17006    }
17007  }
17008
17009  #[cfg(not(target_family = "wasm"))]
17010  #[test]
17011  fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
17012    // Single-slot displacement invariant: two same-executor schedules from one worker -- the
17013    // NEWEST takes the slot; the displaced occupant must land on the shared
17014    // FIFO (with a wake), never be dropped. `active_drainers` is saturated so
17015    // the displacement wake cannot spawn a real drainer racing the manual
17016    // `run_one` calls.
17017    let options = RuntimeOptions {
17018      flavor: RuntimeFlavor::MultiThread,
17019      worker_threads: 2,
17020      max_blocking_tasks: 2,
17021      thread_name_prefix: "lifo-displace".to_string(),
17022      park_deadline: None,
17023    };
17024    let executor =
17025      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17026    executor
17027      .active_drainers
17028      .store(executor.max_drainers, Ordering::SeqCst);
17029    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17030
17031    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17032    for name in ["a", "b"] {
17033      let n_order = Arc::clone(&order);
17034      let sched = Arc::clone(&executor);
17035      let (runnable, task) = async_task::spawn(
17036        async move {
17037          n_order.lock().unwrap().push(name);
17038        },
17039        move |r| sched.schedule(r),
17040      );
17041      executor.schedule(runnable);
17042      task.detach();
17043    }
17044    assert_eq!(
17045      executor.queue.lock().unwrap().len(),
17046      1,
17047      "the displaced occupant (a) must have been pushed to the shared FIFO"
17048    );
17049
17050    assert!(executor.run_one(), "run_one must pop the slot (b) first");
17051    assert!(
17052      executor.run_one(),
17053      "run_one must then pop the displaced (a) from the FIFO"
17054    );
17055    assert!(!executor.run_one(), "no work must remain");
17056    assert_eq!(
17057      *order.lock().unwrap(),
17058      vec!["b", "a"],
17059      "the newest schedule must win the slot; the displaced one must survive via the FIFO"
17060    );
17061    executor.active_drainers.store(0, Ordering::SeqCst);
17062  }
17063
17064  #[cfg(not(target_family = "wasm"))]
17065  #[test]
17066  fn lifo_slot_is_executor_scoped() {
17067    // Executor-scoping invariant: the slot is tagged with the executor id (mirroring the
17068    // ON_POOL_WORKER / BlockingOwnerToken scoping). A worker of executor A
17069    // scheduling onto executor B must bypass the slot (B's FIFO); and with
17070    // A's runnable IN the slot, B must neither pop nor displace it.
17071    let options = RuntimeOptions {
17072      flavor: RuntimeFlavor::MultiThread,
17073      worker_threads: 2,
17074      max_blocking_tasks: 2,
17075      thread_name_prefix: "lifo-scope".to_string(),
17076      park_deadline: None,
17077    };
17078    let exec1 =
17079      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17080    let exec2 =
17081      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17082    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
17083    exec1
17084      .active_drainers
17085      .store(exec1.max_drainers, Ordering::SeqCst);
17086    exec2
17087      .active_drainers
17088      .store(exec2.max_drainers, Ordering::SeqCst);
17089    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17090
17091    // As exec1's worker: r1 goes to the slot, tagged exec1.
17092    let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
17093    let r1_order = Arc::clone(&order);
17094    let sched = Arc::clone(&exec1);
17095    let (r1, t1) = async_task::spawn(
17096      async move {
17097        r1_order.lock().unwrap().push("one");
17098      },
17099      move |r| sched.schedule(r),
17100    );
17101    exec1.schedule(r1);
17102    t1.detach();
17103    assert!(
17104      exec1.queue.lock().unwrap().is_empty(),
17105      "r1 must sit in the slot, not exec1's FIFO"
17106    );
17107
17108    {
17109      // Nested: now act as exec2's worker while exec1's runnable occupies the
17110      // slot. exec2 must not displace the foreign entry; r2 goes to its FIFO.
17111      let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
17112      let r2_order = Arc::clone(&order);
17113      let sched = Arc::clone(&exec2);
17114      let (r2, t2) = async_task::spawn(
17115        async move {
17116          r2_order.lock().unwrap().push("two");
17117        },
17118        move |r| sched.schedule(r),
17119      );
17120      exec2.schedule(r2);
17121      t2.detach();
17122      assert_eq!(
17123        exec2.queue.lock().unwrap().len(),
17124        1,
17125        "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
17126      );
17127      assert!(
17128        exec2.pop_lifo_slot().is_none(),
17129        "exec2 must not pop exec1's slot entry (executor-scoped pop)"
17130      );
17131      assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
17132    }
17133
17134    // Back as exec1's worker: its slot entry is intact and runnable.
17135    assert!(
17136      exec1.run_one(),
17137      "exec1's slot entry must have survived the foreign activity"
17138    );
17139    assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
17140    exec1.active_drainers.store(0, Ordering::SeqCst);
17141    exec2.active_drainers.store(0, Ordering::SeqCst);
17142  }
17143
17144  #[cfg(not(target_family = "wasm"))]
17145  #[test]
17146  fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
17147    // LIFO chain flush invariant: a 200-link chain in which each task schedules the
17148    // next FROM the pool worker (every link lands in the slot) crosses
17149    // multiple RUNNABLE_BUDGET windows. At each exhaustion the slot MUST be
17150    // flushed to the FIFO before `finish_draining` (so its re-check sees the
17151    // runnable and re-arms a drainer); a missing flush strands the chain at
17152    // the first budget boundary (lost task + queued_runnables leak).
17153    use std::sync::mpsc;
17154    use std::time::Duration;
17155
17156    const CHAIN: u64 = 200; // > RUNNABLE_BUDGET (64)
17157
17158    fn spawn_link(
17159      executor: &Arc<MultiThreadExecutor>,
17160      remaining: u64,
17161      counter: Arc<AtomicU64>,
17162      done: mpsc::Sender<()>,
17163    ) {
17164      let body_exec = Arc::clone(executor);
17165      let body = async move {
17166        counter.fetch_add(1, Ordering::SeqCst);
17167        if remaining > 0 {
17168          spawn_link(&body_exec, remaining - 1, counter, done);
17169        } else {
17170          done.send(()).unwrap();
17171        }
17172      };
17173      let sched = Arc::clone(executor);
17174      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17175      executor.schedule(runnable);
17176      task.detach();
17177    }
17178
17179    let (done_tx, done_rx) = mpsc::channel();
17180    let runner = std::thread::spawn(move || {
17181      let metrics = Arc::new(RuntimeMetrics::default());
17182      let options = RuntimeOptions {
17183        flavor: RuntimeFlavor::MultiThread,
17184        worker_threads: 1,
17185        max_blocking_tasks: 1,
17186        thread_name_prefix: "lifo-budget".to_string(),
17187        park_deadline: None,
17188      };
17189      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17190
17191      let counter = Arc::new(AtomicU64::new(0));
17192      let (chain_tx, chain_rx) = mpsc::channel();
17193      spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
17194
17195      chain_rx
17196        .recv_timeout(Duration::from_secs(10))
17197        .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
17198      assert_eq!(
17199        counter.load(Ordering::SeqCst),
17200        CHAIN,
17201        "every chain link must have run"
17202      );
17203      wait_until("runnable counters settle to zero (no slot leak)", || {
17204        metrics.queued_runnables.load(Ordering::Relaxed) == 0
17205          && metrics.active_runnables.load(Ordering::Relaxed) == 0
17206      });
17207      let _ = done_tx.send(());
17208    });
17209
17210    match done_rx.recv_timeout(Duration::from_secs(15)) {
17211      Ok(()) => runner.join().unwrap(),
17212      Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
17213    }
17214  }
17215
17216  #[cfg(not(target_family = "wasm"))]
17217  #[test]
17218  fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
17219    // Direct flush primitive test: pin the flush behavior used at drain
17220    // exits, before cooperative parks and by the unwind guard -- a
17221    // slot-resident runnable moves to the shared FIFO with its task still
17222    // completable, never dropped. (The pre-park call is defensive: `run_one`
17223    // pops the slot before any park is reachable, so this primitive is its
17224    // only observable seam.)
17225    let options = RuntimeOptions {
17226      flavor: RuntimeFlavor::MultiThread,
17227      worker_threads: 2,
17228      max_blocking_tasks: 2,
17229      thread_name_prefix: "lifo-flush".to_string(),
17230      park_deadline: None,
17231    };
17232    let executor =
17233      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17234    executor
17235      .active_drainers
17236      .store(executor.max_drainers, Ordering::SeqCst);
17237    let ran = Arc::new(AtomicBool::new(false));
17238
17239    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17240    let ran_flag = Arc::clone(&ran);
17241    let sched = Arc::clone(&executor);
17242    let (runnable, task) = async_task::spawn(
17243      async move {
17244        ran_flag.store(true, Ordering::SeqCst);
17245      },
17246      move |r| sched.schedule(r),
17247    );
17248    executor.schedule(runnable);
17249    task.detach();
17250    assert!(
17251      executor.queue.lock().unwrap().is_empty(),
17252      "the runnable must start in the slot"
17253    );
17254
17255    executor.flush_lifo_slot();
17256    assert!(
17257      executor.pop_lifo_slot().is_none(),
17258      "the slot must be empty after a flush"
17259    );
17260    assert_eq!(
17261      executor.queue.lock().unwrap().len(),
17262      1,
17263      "the flushed runnable must be on the shared FIFO"
17264    );
17265    assert!(executor.run_one(), "the flushed runnable must still run");
17266    assert!(
17267      ran.load(Ordering::SeqCst),
17268      "the flushed task must complete, not be dropped"
17269    );
17270    executor.active_drainers.store(0, Ordering::SeqCst);
17271  }
17272
17273  #[cfg(not(target_family = "wasm"))]
17274  #[test]
17275  fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
17276    // Blocking-owner isolation must cover the LIFO slot path: a runnable
17277    // popped from the slot runs with BLOCKING_OWNER cleared, so it cannot
17278    // borrow the driving owner frame's over-cap privilege; the ambient owner
17279    // is restored afterwards.
17280    let options = RuntimeOptions {
17281      flavor: RuntimeFlavor::MultiThread,
17282      worker_threads: 2,
17283      max_blocking_tasks: 2,
17284      thread_name_prefix: "lifo-owner".to_string(),
17285      park_deadline: None,
17286    };
17287    let executor =
17288      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17289    let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
17290
17291    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17292
17293    // Install the slot entry from RUNNABLE context (no ambient owner), the
17294    // only context that may use the slot because schedules issued under an
17295    // owner frame are diverted to the shared FIFO.
17296    let observed_slot = Arc::clone(&observed);
17297    let sched = Arc::clone(&executor);
17298    let (runnable, task) = async_task::spawn(
17299      async move {
17300        *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
17301      },
17302      move |r| sched.schedule(r),
17303    );
17304    executor.schedule(runnable);
17305    task.detach();
17306    assert!(
17307      executor.queue.lock().unwrap().is_empty(),
17308      "the runnable must sit in the slot"
17309    );
17310
17311    // The POP can still happen while an owner frame is ambient (a cooperative
17312    // `block_on` entered from a blocking closure drives `run_one` with the
17313    // closure's token on the stack); the slot branch must clear it.
17314    let token = executor.fresh_owner_token();
17315    let _owner = BlockingOwnerGuard::enter(Some(token));
17316    assert!(executor.run_one(), "the slot runnable must run");
17317    assert_eq!(
17318      *observed.lock().unwrap(),
17319      Some(None),
17320      "a slot runnable must observe a CLEARED blocking owner"
17321    );
17322    assert_eq!(
17323      BLOCKING_OWNER.with(std::cell::Cell::get),
17324      Some(token),
17325      "the ambient owner frame must be restored after the slot runnable"
17326    );
17327  }
17328
17329  // Shared ping-pong probe for the LIFO fairness tests: two tasks that wake
17330  // each other on every poll. Scheduled from a pool worker, each wake lands in
17331  // that worker's LIFO slot, keeping the slot hot indefinitely. A task
17332  // finishes when `stop` is raised (or `stop_after` polls happened), releasing
17333  // its partner's stored waker so both complete.
17334  #[cfg(not(target_family = "wasm"))]
17335  struct PingPongShared {
17336    count: AtomicU64,
17337    stop: AtomicBool,
17338    stop_after: u64,
17339    wakers: Mutex<[Option<std::task::Waker>; 2]>,
17340  }
17341
17342  #[cfg(not(target_family = "wasm"))]
17343  struct PingPong {
17344    shared: Arc<PingPongShared>,
17345    me: usize,
17346  }
17347
17348  #[cfg(not(target_family = "wasm"))]
17349  impl Future for PingPong {
17350    type Output = ();
17351    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
17352      let shared = &self.shared;
17353      let polls = shared.count.fetch_add(1, Ordering::SeqCst);
17354      if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
17355        let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
17356        if let Some(partner) = partner {
17357          partner.wake();
17358        }
17359        return Poll::Ready(());
17360      }
17361      let mut wakers = shared.wakers.lock().unwrap();
17362      wakers[self.me] = Some(cx.waker().clone());
17363      if let Some(partner) = wakers[1 - self.me].take() {
17364        drop(wakers);
17365        partner.wake();
17366      }
17367      Poll::Pending
17368    }
17369  }
17370
17371  #[cfg(not(target_family = "wasm"))]
17372  fn spawn_ping_pong_pair(
17373    executor: &Arc<MultiThreadExecutor>,
17374    shared: &Arc<PingPongShared>,
17375  ) -> Vec<Task<()>> {
17376    let mut tasks = Vec::new();
17377    for me in 0..2 {
17378      let sched = Arc::clone(executor);
17379      let (runnable, task) = async_task::spawn(
17380        PingPong {
17381          shared: Arc::clone(shared),
17382          me,
17383        },
17384        move |r| sched.schedule(r),
17385      );
17386      executor.schedule(runnable);
17387      tasks.push(task);
17388    }
17389    tasks
17390  }
17391
17392  #[cfg(not(target_family = "wasm"))]
17393  #[test]
17394  fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
17395    // LIFO streak-cap invariant: slot pops share `drain`'s RUNNABLE_BUDGET, so
17396    // a hot wake/await pair holds a worker for at most one budget window
17397    // before the slot is flushed and the FIFO gets its turn. With ONE worker,
17398    // a third task queued behind an unbounded ping-pong pair must still run;
17399    // if slot pops bypassed the budget the drain would never exit and T3
17400    // would starve forever (caught by the bounded timeout).
17401    use std::sync::mpsc;
17402    use std::time::Duration;
17403
17404    let (done_tx, done_rx) = mpsc::channel();
17405    let runner = std::thread::spawn(move || {
17406      let options = RuntimeOptions {
17407        flavor: RuntimeFlavor::MultiThread,
17408        worker_threads: 1,
17409        max_blocking_tasks: 0,
17410        thread_name_prefix: "lifo-streak".to_string(),
17411        park_deadline: None,
17412      };
17413      let executor =
17414        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17415
17416      let shared = Arc::new(PingPongShared {
17417        count: AtomicU64::new(0),
17418        stop: AtomicBool::new(false),
17419        stop_after: u64::MAX,
17420        wakers: Mutex::new([None, None]),
17421      });
17422      let pair = spawn_ping_pong_pair(&executor, &shared);
17423
17424      // T3: queued on the shared FIFO behind the hot pair.
17425      let (t3_tx, t3_rx) = mpsc::channel::<()>();
17426      let sched = Arc::clone(&executor);
17427      let (t3_runnable, t3_task) = async_task::spawn(
17428        async move {
17429          t3_tx.send(()).unwrap();
17430        },
17431        move |r| sched.schedule(r),
17432      );
17433      executor.schedule(t3_runnable);
17434
17435      t3_rx
17436        .recv_timeout(Duration::from_secs(10))
17437        .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
17438      assert!(
17439        shared.count.load(Ordering::SeqCst) >= 32,
17440        "the pair must have been bouncing through the slot before T3 got its turn"
17441      );
17442
17443      // Teardown: stop the pair and release whichever side is waiting.
17444      shared.stop.store(true, Ordering::SeqCst);
17445      let stored: Vec<_> = shared
17446        .wakers
17447        .lock()
17448        .unwrap()
17449        .iter_mut()
17450        .filter_map(Option::take)
17451        .collect();
17452      for waker in stored {
17453        waker.wake();
17454      }
17455      for task in pair {
17456        futures::executor::block_on(task);
17457      }
17458      futures::executor::block_on(t3_task);
17459      let _ = done_tx.send(());
17460    });
17461
17462    match done_rx.recv_timeout(Duration::from_secs(15)) {
17463      Ok(()) => runner.join().unwrap(),
17464      Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
17465    }
17466  }
17467
17468  #[cfg(not(target_family = "wasm"))]
17469  #[test]
17470  fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
17471    // Runnable/blocking fairness invariant: runnables (slot included) outrank blocking work per
17472    // drain iteration -- unchanged from the FIFO-only drain -- but a hot LIFO
17473    // pair must not turn that priority into a permanent wedge. The budget
17474    // flush keeps the drain exiting, `finish_draining` keeps re-checking the
17475    // blocking queue, and the queued blocking job completes once the pair
17476    // finishes its (long) run on the SOLE worker.
17477    use std::sync::mpsc;
17478    use std::time::Duration;
17479
17480    const BOUNCES: u64 = 2_000; // ~31 budget windows on one worker
17481
17482    let (done_tx, done_rx) = mpsc::channel();
17483    let runner = std::thread::spawn(move || {
17484      let options = RuntimeOptions {
17485        flavor: RuntimeFlavor::MultiThread,
17486        worker_threads: 1,
17487        max_blocking_tasks: 1,
17488        thread_name_prefix: "lifo-blocking".to_string(),
17489        park_deadline: None,
17490      };
17491      let executor =
17492        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17493
17494      let shared = Arc::new(PingPongShared {
17495        count: AtomicU64::new(0),
17496        stop: AtomicBool::new(false),
17497        stop_after: BOUNCES,
17498        wakers: Mutex::new([None, None]),
17499      });
17500      let pair = spawn_ping_pong_pair(&executor, &shared);
17501
17502      // Queued while the pair keeps the slot hot on the only worker.
17503      let blocking = executor.schedule_blocking(|| 11usize);
17504
17505      assert_eq!(
17506        futures::executor::block_on(blocking).unwrap(),
17507        11,
17508        "the queued blocking job must complete despite the hot LIFO pair"
17509      );
17510      for task in pair {
17511        futures::executor::block_on(task);
17512      }
17513      assert!(
17514        shared.count.load(Ordering::SeqCst) >= BOUNCES,
17515        "the pair must have actually run its full bounce quota"
17516      );
17517      let _ = done_tx.send(());
17518    });
17519
17520    match done_rx.recv_timeout(Duration::from_secs(15)) {
17521      Ok(()) => runner.join().unwrap(),
17522      Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
17523    }
17524  }
17525
17526  #[cfg(not(target_family = "wasm"))]
17527  #[test]
17528  fn infinite_hot_runnable_yields_to_blocking_fifo() {
17529    use std::sync::mpsc;
17530
17531    struct HotFuture {
17532      stop: Arc<AtomicBool>,
17533    }
17534
17535    impl Future for HotFuture {
17536      type Output = ();
17537
17538      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
17539        if self.stop.load(Ordering::SeqCst) {
17540          Poll::Ready(())
17541        } else {
17542          cx.waker().wake_by_ref();
17543          Poll::Pending
17544        }
17545      }
17546    }
17547
17548    let options = RuntimeOptions {
17549      flavor: RuntimeFlavor::MultiThread,
17550      worker_threads: 2,
17551      max_blocking_tasks: 1,
17552      thread_name_prefix: "blocking-fairness".to_string(),
17553      park_deadline: None,
17554    };
17555    let executor =
17556      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17557    let stop = Arc::new(AtomicBool::new(false));
17558    let scheduler = Arc::clone(&executor);
17559    let (runnable, task) = async_task::spawn(
17560      HotFuture {
17561        stop: Arc::clone(&stop),
17562      },
17563      move |r| scheduler.schedule(r),
17564    );
17565    task.detach();
17566    let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
17567    executor
17568      .blocking_queue
17569      .lock()
17570      .unwrap()
17571      .push(QueuedBlocking {
17572        id: BlockingJobId(u64::MAX),
17573        run: Box::new(move || blocking_tx.send(()).unwrap()),
17574      });
17575
17576    let _driver = OnPoolWorkerGuard::enter(executor.id);
17577    executor.schedule(runnable);
17578    let mut runnable_streak = 0;
17579    for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
17580      assert!(executor.run_one_fair(&mut runnable_streak));
17581    }
17582    blocking_rx
17583      .try_recv()
17584      .expect("an infinite self-waker must yield a bounded quantum to blocking work");
17585
17586    stop.store(true, Ordering::SeqCst);
17587    assert!(executor.run_one_fair(&mut runnable_streak));
17588  }
17589
17590  #[cfg(not(target_family = "wasm"))]
17591  #[test]
17592  fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
17593    use std::sync::mpsc;
17594
17595    struct HotFuture {
17596      stop: Arc<AtomicBool>,
17597    }
17598
17599    impl Future for HotFuture {
17600      type Output = ();
17601
17602      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
17603        if self.stop.load(Ordering::SeqCst) {
17604          Poll::Ready(())
17605        } else {
17606          cx.waker().wake_by_ref();
17607          Poll::Pending
17608        }
17609      }
17610    }
17611
17612    let options = RuntimeOptions {
17613      flavor: RuntimeFlavor::MultiThread,
17614      worker_threads: 1,
17615      max_blocking_tasks: 1,
17616      thread_name_prefix: "exact-dependency-fairness".to_string(),
17617      park_deadline: None,
17618    };
17619    let executor =
17620      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17621    let stop = Arc::new(AtomicBool::new(false));
17622    let (queued_tx, queued_rx) = mpsc::channel();
17623    let (exact_tx, exact_rx) = mpsc::channel();
17624    let owner_executor = Arc::clone(&executor);
17625    let owner_stop = Arc::clone(&stop);
17626    let owner = executor.schedule_blocking(move || {
17627      let scheduler = Arc::clone(&owner_executor);
17628      let hot_stop = Arc::clone(&owner_stop);
17629      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
17630        scheduler.schedule(runnable);
17631      });
17632      owner_executor.schedule(runnable);
17633
17634      let exact_stop = Arc::clone(&owner_stop);
17635      let exact = owner_executor.schedule_blocking(move || {
17636        exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
17637        41usize
17638      });
17639      queued_tx.send(()).unwrap();
17640
17641      let mut value = None;
17642      {
17643        let mut future = std::pin::pin!(async {
17644          value = Some(exact.await.unwrap());
17645          hot_task.await;
17646        });
17647        assert_eq!(
17648          owner_executor.block_on(future.as_mut()),
17649          BlockOnOutcome::Completed
17650        );
17651      }
17652      value.unwrap()
17653    });
17654
17655    queued_rx
17656      .recv_timeout(Duration::from_secs(2))
17657      .expect("the exact dependency must be queued behind the saturated owner");
17658    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
17659    stop.store(true, Ordering::SeqCst);
17660    assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
17661    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
17662    assert_eq!(
17663      exact_before_stop,
17664      Some(true),
17665      "a hot local runnable must yield to the exact dependency before it is stopped"
17666    );
17667  }
17668
17669  #[cfg(not(target_family = "wasm"))]
17670  #[test]
17671  fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
17672    use std::sync::mpsc;
17673
17674    struct BlockingPoll {
17675      started: Option<mpsc::Sender<()>>,
17676      released: Arc<(Mutex<bool>, Condvar)>,
17677    }
17678
17679    impl Future for BlockingPoll {
17680      type Output = ();
17681
17682      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17683        if let Some(started) = self.started.take() {
17684          started.send(()).unwrap();
17685        }
17686        let (released, changed) = &*self.released;
17687        let mut released = released
17688          .lock()
17689          .unwrap_or_else(std::sync::PoisonError::into_inner);
17690        while !*released {
17691          released = changed
17692            .wait(released)
17693            .unwrap_or_else(std::sync::PoisonError::into_inner);
17694        }
17695        Poll::Ready(())
17696      }
17697    }
17698
17699    let options = RuntimeOptions {
17700      flavor: RuntimeFlavor::MultiThread,
17701      worker_threads: 3,
17702      max_blocking_tasks: 2,
17703      thread_name_prefix: "exact-before-last-slot".to_string(),
17704      park_deadline: None,
17705    };
17706    let executor =
17707      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17708
17709    let released = Arc::new((Mutex::new(false), Condvar::new()));
17710    let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
17711    let mut blockers = Vec::new();
17712    for _ in 0..2 {
17713      let scheduler = Arc::clone(&executor);
17714      let blocker_released = Arc::clone(&released);
17715      let started = blocker_started_tx.clone();
17716      let (runnable, task) = async_task::spawn(
17717        BlockingPoll {
17718          started: Some(started),
17719          released: blocker_released,
17720        },
17721        move |runnable| scheduler.schedule(runnable),
17722      );
17723      executor.schedule(runnable);
17724      blockers.push(task);
17725    }
17726    drop(blocker_started_tx);
17727    for _ in 0..2 {
17728      blocker_started_rx
17729        .recv_timeout(Duration::from_secs(2))
17730        .expect("two workers must be gated before the owner starts");
17731    }
17732
17733    let dependency_started = Arc::new(AtomicBool::new(false));
17734    let owner_executor = Arc::clone(&executor);
17735    let owner_dependency_started = Arc::clone(&dependency_started);
17736    let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
17737    let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
17738    let owner_signal = Arc::clone(&dependency_signal);
17739    let owner = executor.schedule_blocking(move || {
17740      let unrelated_signal = Arc::clone(&owner_signal);
17741      let unrelated_started = Arc::clone(&owner_dependency_started);
17742      let unrelated = owner_executor.schedule_blocking(move || {
17743        let observed = unrelated_started.load(Ordering::SeqCst);
17744        unrelated_observed_tx.send(observed).unwrap();
17745        let (ready, changed) = &*unrelated_signal;
17746        let mut ready = ready
17747          .lock()
17748          .unwrap_or_else(std::sync::PoisonError::into_inner);
17749        while !*ready {
17750          ready = changed
17751            .wait(ready)
17752            .unwrap_or_else(std::sync::PoisonError::into_inner);
17753        }
17754      });
17755
17756      let exact_signal = Arc::clone(&owner_signal);
17757      let exact_started = Arc::clone(&owner_dependency_started);
17758      let exact = owner_executor.schedule_blocking(move || {
17759        exact_started.store(true, Ordering::SeqCst);
17760        let (ready, changed) = &*exact_signal;
17761        *ready
17762          .lock()
17763          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
17764        changed.notify_all();
17765        41usize
17766      });
17767
17768      let mut output = None;
17769      {
17770        let mut future = std::pin::pin!(async {
17771          output = Some(exact.await.unwrap());
17772          unrelated.await.unwrap();
17773        });
17774        assert_eq!(
17775          owner_executor.block_on(future.as_mut()),
17776          BlockOnOutcome::Completed
17777        );
17778      }
17779      output.unwrap()
17780    });
17781
17782    let (owner_done_tx, owner_done_rx) = mpsc::channel();
17783    let owner_waiter = std::thread::spawn(move || {
17784      owner_done_tx
17785        .send(futures::executor::block_on(owner))
17786        .unwrap();
17787    });
17788    let (completed_while_gated, owner_result) =
17789      match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
17790        Ok(result) => (true, result),
17791        Err(_) => {
17792          // Let a broken implementation recover so the regression fails
17793          // deterministically instead of leaving blocked Rayon workers behind.
17794          let (released, changed) = &*released;
17795          *released
17796            .lock()
17797            .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
17798          changed.notify_all();
17799          (
17800            false,
17801            owner_done_rx
17802              .recv_timeout(Duration::from_secs(2))
17803              .expect("releasing the other workers must let the broken ordering recover"),
17804          )
17805        }
17806      };
17807    let unrelated_observed = unrelated_observed_rx
17808      .recv_timeout(Duration::from_secs(2))
17809      .expect("the unrelated FIFO job must eventually run");
17810
17811    {
17812      let (released, changed) = &*released;
17813      *released
17814        .lock()
17815        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
17816      changed.notify_all();
17817    }
17818    owner_waiter.join().unwrap();
17819    for blocker in blockers {
17820      futures::executor::block_on(blocker);
17821    }
17822    executor.begin_shutdown();
17823    executor.wait_until_scheduler_idle();
17824
17825    assert!(
17826      completed_while_gated,
17827      "the exact dependency must complete while unrelated workers remain gated"
17828    );
17829    assert_eq!(owner_result.unwrap(), 41);
17830    assert!(
17831      unrelated_observed,
17832      "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
17833    );
17834  }
17835
17836  #[cfg(not(target_family = "wasm"))]
17837  #[test]
17838  fn blocking_saturation_preserves_a_runnable_execution_lane() {
17839    use std::{sync::mpsc, time::Duration};
17840
17841    let options = RuntimeOptions {
17842      flavor: RuntimeFlavor::MultiThread,
17843      worker_threads: 2,
17844      max_blocking_tasks: 2,
17845      thread_name_prefix: "blocking-reserve".to_string(),
17846      park_deadline: None,
17847    }
17848    .validate()
17849    .expect("production options must reserve a runnable lane");
17850    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
17851    let executor =
17852      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17853    let (release_tx, release_rx) = mpsc::channel();
17854    let (started_tx, started_rx) = mpsc::channel();
17855    let first = executor.schedule_blocking(move || {
17856      started_tx.send(()).unwrap();
17857      release_rx.recv().unwrap();
17858    });
17859    started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
17860
17861    let (queued_tx, queued_rx) = mpsc::channel();
17862    let second = executor.schedule_blocking(move || {
17863      queued_tx.send(()).unwrap();
17864    });
17865    assert!(
17866      queued_rx.try_recv().is_err(),
17867      "the second blocking job must remain queued while the sole blocking slot is occupied"
17868    );
17869
17870    let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
17871    let scheduler = Arc::clone(&executor);
17872    let (runnable, task) = async_task::spawn(
17873      async move {
17874        runnable_tx.send(()).unwrap();
17875      },
17876      move |r| scheduler.schedule(r),
17877    );
17878    task.detach();
17879    executor.schedule(runnable);
17880    runnable_rx
17881      .recv_timeout(Duration::from_secs(1))
17882      .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
17883
17884    release_tx.send(()).unwrap();
17885    futures::executor::block_on(first).unwrap();
17886    futures::executor::block_on(second).unwrap();
17887    queued_rx
17888      .recv_timeout(Duration::from_secs(1))
17889      .expect("queued blocking work must run after the occupied slot is released");
17890  }
17891
17892  #[cfg(not(target_family = "wasm"))]
17893  #[test]
17894  fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
17895    // Final-poll slot-flush invariant: the awaited future's FINAL poll can
17896    // schedule same-executor work, which lands in THIS worker's LIFO slot --
17897    // no FIFO push, no wake. The Ready exit of
17898    // `cooperative_block_on` must flush the slot: the caller may wait on that
17899    // child synchronously (and the enclosing drain frame can be arbitrarily
17900    // far away -- here it does not exist at all, via the fake on-pool
17901    // marker), so a stranded slot entry is an invisible, dead task.
17902    use std::sync::mpsc;
17903    use std::time::Duration;
17904
17905    struct SpawnOnReady {
17906      executor: Arc<MultiThreadExecutor>,
17907      child_tx: Option<mpsc::Sender<u64>>,
17908    }
17909    impl Future for SpawnOnReady {
17910      type Output = ();
17911      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17912        let child_tx = self.child_tx.take().expect("polled once");
17913        let sched = Arc::clone(&self.executor);
17914        let (runnable, task) = async_task::spawn(
17915          async move {
17916            child_tx.send(21).unwrap();
17917          },
17918          move |r| sched.schedule(r),
17919        );
17920        // On-pool + same executor + no ambient owner: lands in the slot.
17921        self.executor.schedule(runnable);
17922        task.detach();
17923        Poll::Ready(())
17924      }
17925    }
17926
17927    let (done_tx, done_rx) = mpsc::channel();
17928    let runner = std::thread::spawn(move || {
17929      let options = RuntimeOptions {
17930        flavor: RuntimeFlavor::MultiThread,
17931        worker_threads: 2,
17932        max_blocking_tasks: 2,
17933        thread_name_prefix: "exit-flush".to_string(),
17934        park_deadline: None,
17935      };
17936      let executor =
17937        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17938
17939      let (child_tx, child_rx) = mpsc::channel::<u64>();
17940      {
17941        // The test-only on-pool marker runs the cooperative branch on THIS
17942        // thread, with NO enclosing drain loop to bail the slot out.
17943        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17944        let mut future = std::pin::pin!(SpawnOnReady {
17945          executor: Arc::clone(&executor),
17946          child_tx: Some(child_tx)
17947        });
17948        executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
17949      }
17950
17951      // Synchronous bounded wait, NOT via the executor: only the exit flush
17952      // (slot -> shared FIFO -> wake/ensure_drainer -> pool worker) can
17953      // complete the child.
17954      let value = child_rx
17955        .recv_timeout(Duration::from_secs(5))
17956        .expect("the child scheduled by the final poll was stranded in the LIFO slot");
17957      assert_eq!(value, 21);
17958      let _ = done_tx.send(());
17959    });
17960
17961    match done_rx.recv_timeout(Duration::from_secs(10)) {
17962      Ok(()) => runner.join().unwrap(),
17963      Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
17964    }
17965  }
17966
17967  /// Perpetual same-executor chain: each link schedules its successor from
17968  /// the pool worker (so every link lands in the LIFO slot) until `stop` is
17969  /// raised. `hops` counts executed links.
17970  #[cfg(not(target_family = "wasm"))]
17971  fn spawn_hot_chain(
17972    executor: &Arc<MultiThreadExecutor>,
17973    stop: Arc<AtomicBool>,
17974    hops: Arc<AtomicU64>,
17975  ) {
17976    if stop.load(Ordering::SeqCst) {
17977      return;
17978    }
17979    let link_exec = Arc::clone(executor);
17980    let body = async move {
17981      hops.fetch_add(1, Ordering::SeqCst);
17982      spawn_hot_chain(&link_exec, stop, hops);
17983    };
17984    let sched = Arc::clone(executor);
17985    let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17986    executor.schedule(runnable);
17987    task.detach();
17988  }
17989
17990  #[cfg(not(target_family = "wasm"))]
17991  #[test]
17992  fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
17993    // Runnable-budget invariant: `cooperative_block_on`'s loop must mirror
17994    // `drain`'s RUNNABLE_BUDGET streak cap. `run_one` pops the slot before
17995    // the FIFO, so a hot chain (every link schedules its successor into the
17996    // slot) keeps run_one succeeding forever and the FIFO is never reached
17997    // from this thread. On the direct 1-worker test topology a FIFO task that
17998    // completes the awaited future then never runs: deadlock. The budgeted
17999    // loop must flush the slot every RUNNABLE_BUDGET runs so the FIFO task
18000    // gets its turn.
18001    use std::sync::mpsc;
18002    use std::time::Duration;
18003
18004    let (done_tx, done_rx) = mpsc::channel();
18005    let runner = std::thread::spawn(move || {
18006      let options = RuntimeOptions {
18007        flavor: RuntimeFlavor::MultiThread,
18008        worker_threads: 1,
18009        // This locality test intentionally bypasses RuntimeOptions::validate
18010        // to isolate one physical worker without admitting blocking work.
18011        max_blocking_tasks: 0,
18012        thread_name_prefix: "coop-budget".to_string(),
18013        park_deadline: None,
18014      };
18015      let executor =
18016        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18017
18018      let stop = Arc::new(AtomicBool::new(false));
18019      let hops = Arc::new(AtomicU64::new(0));
18020
18021      // M runs on the sole worker: it queues F (the task that completes the
18022      // awaited future) on the FIFO, heats the slot with the chain, then
18023      // block_on's the value F produces.
18024      let m_exec = Arc::clone(&executor);
18025      let m_stop = Arc::clone(&stop);
18026      let m_hops = Arc::clone(&hops);
18027      let m_body = async move {
18028        let (tx, rx) = oneshot::channel::<u64>();
18029        // Schedule F first: it takes the slot momentarily...
18030        let sched_f = Arc::clone(&m_exec);
18031        let (f_runnable, f_task) = async_task::spawn(
18032          async move {
18033            let _ = tx.send(7);
18034          },
18035          move |r| sched_f.schedule(r),
18036        );
18037        m_exec.schedule(f_runnable);
18038        f_task.detach();
18039        // ...then the chain starter displaces it to the FIFO and keeps the
18040        // slot hot from here on.
18041        spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
18042
18043        let mut inner = std::pin::pin!(async move {
18044          assert_eq!(
18045            rx.await.unwrap(),
18046            7,
18047            "F must run and complete the awaited future"
18048          );
18049        });
18050        m_exec.block_on(inner.as_mut());
18051        m_stop.store(true, Ordering::SeqCst);
18052      };
18053      let sched_m = Arc::clone(&executor);
18054      let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
18055      executor.schedule(m_runnable);
18056
18057      futures::executor::block_on(m_task);
18058      assert!(
18059        hops.load(Ordering::SeqCst) >= 64,
18060        "the chain must have been genuinely hot (>= one budget window) before F ran"
18061      );
18062      let _ = done_tx.send(());
18063    });
18064
18065    match done_rx.recv_timeout(Duration::from_secs(10)) {
18066      Ok(()) => runner.join().unwrap(),
18067      Err(error) => panic!(
18068        "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
18069      ),
18070    }
18071  }
18072
18073  #[cfg(not(target_family = "wasm"))]
18074  #[test]
18075  fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
18076    use std::sync::mpsc;
18077
18078    struct RefillLifo {
18079      executor: Arc<MultiThreadExecutor>,
18080      fifo_ran: Arc<AtomicBool>,
18081    }
18082
18083    impl Future for RefillLifo {
18084      type Output = ();
18085
18086      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18087        if self.fifo_ran.load(Ordering::SeqCst) {
18088          return Poll::Ready(());
18089        }
18090        let scheduler = Arc::clone(&self.executor);
18091        let (runnable, task) =
18092          async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
18093        self.executor.schedule(runnable);
18094        task.detach();
18095        Poll::Pending
18096      }
18097    }
18098
18099    let (done_tx, done_rx) = mpsc::channel();
18100    let runner = std::thread::spawn(move || {
18101      let options = RuntimeOptions {
18102        flavor: RuntimeFlavor::MultiThread,
18103        worker_threads: 1,
18104        max_blocking_tasks: 0,
18105        thread_name_prefix: "forced-fifo".to_string(),
18106        park_deadline: None,
18107      };
18108      let executor =
18109        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18110      executor
18111        .active_drainers
18112        .store(executor.max_drainers, Ordering::SeqCst);
18113
18114      let fifo_ran = Arc::new(AtomicBool::new(false));
18115      let fifo_flag = Arc::clone(&fifo_ran);
18116      let scheduler = Arc::clone(&executor);
18117      let (fifo, task) = async_task::spawn(
18118        async move {
18119          fifo_flag.store(true, Ordering::SeqCst);
18120        },
18121        move |runnable| scheduler.schedule(runnable),
18122      );
18123      task.detach();
18124      executor.metrics.runnable_scheduled();
18125      executor.queue.lock().unwrap().push_back(fifo);
18126
18127      {
18128        let _driver = OnPoolWorkerGuard::enter(executor.id);
18129        let mut future = std::pin::pin!(RefillLifo {
18130          executor: Arc::clone(&executor),
18131          fifo_ran: Arc::clone(&fifo_ran)
18132        });
18133        executor.block_on(future.as_mut());
18134        while executor.run_one() {}
18135      }
18136      executor.active_drainers.store(0, Ordering::SeqCst);
18137      assert!(fifo_ran.load(Ordering::SeqCst));
18138      done_tx.send(()).unwrap();
18139    });
18140    done_rx
18141      .recv_timeout(Duration::from_secs(3))
18142      .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
18143    runner.join().unwrap();
18144  }
18145
18146  #[cfg(not(target_family = "wasm"))]
18147  #[test]
18148  fn blocking_closure_spawn_is_not_stranded_in_slot() {
18149    // Blocking-closure scheduling invariant: ON_POOL_WORKER spans the whole
18150    // drain frame, INCLUDING blocking-closure bodies. A closure that spawns a
18151    // same-executor task and then waits for it synchronously would deadlock if
18152    // the child were slotted because the slot only drains after the closure
18153    // returns. `schedule` must bypass the slot while BLOCKING_OWNER is set and
18154    // push to the shared FIFO with a wake.
18155    use std::sync::mpsc;
18156    use std::time::Duration;
18157
18158    let (done_tx, done_rx) = mpsc::channel();
18159    let runner = std::thread::spawn(move || {
18160      let options = RuntimeOptions {
18161        flavor: RuntimeFlavor::MultiThread,
18162        worker_threads: 2,
18163        max_blocking_tasks: 2,
18164        thread_name_prefix: "blk-spawn-wait".to_string(),
18165        park_deadline: None,
18166      };
18167      let executor =
18168        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18169
18170      // Closure spawns a child and BLOCKS on the channel the child must fill.
18171      // A spare worker exists; only a real FIFO push + wake can reach it.
18172      let blk_exec = Arc::clone(&executor);
18173      let handle = executor.schedule_blocking(move || {
18174        let (tx, rx) = mpsc::channel::<u64>();
18175        let sched = Arc::clone(&blk_exec);
18176        let (runnable, task) = async_task::spawn(
18177          async move {
18178            tx.send(21).unwrap();
18179          },
18180          move |r| sched.schedule(r),
18181        );
18182        blk_exec.schedule(runnable);
18183        task.detach();
18184        rx.recv_timeout(Duration::from_secs(5))
18185          .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
18186      });
18187      assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
18188
18189      // Variant: the closure exits WITHOUT waiting; the child must still
18190      // complete promptly (no stranding on the closure-exit path either).
18191      let (v_tx, v_rx) = mpsc::channel::<u64>();
18192      let v_exec = Arc::clone(&executor);
18193      let v_handle = executor.schedule_blocking(move || {
18194        let sched = Arc::clone(&v_exec);
18195        let (runnable, task) = async_task::spawn(
18196          async move {
18197            v_tx.send(42).unwrap();
18198          },
18199          move |r| sched.schedule(r),
18200        );
18201        v_exec.schedule(runnable);
18202        task.detach();
18203        1u64
18204      });
18205      assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
18206      assert_eq!(
18207        v_rx
18208          .recv_timeout(Duration::from_secs(5))
18209          .expect("fire-and-forget child must complete"),
18210        42
18211      );
18212      let _ = done_tx.send(());
18213    });
18214
18215    match done_rx.recv_timeout(Duration::from_secs(15)) {
18216      Ok(()) => runner.join().unwrap(),
18217      Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
18218    }
18219  }
18220
18221  #[cfg(not(target_family = "wasm"))]
18222  #[test]
18223  fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
18224    // Saturated nested-blocking invariant: a blocking closure re-enters
18225    // `block_on` and awaits another `spawn_blocking` while
18226    // `max_blocking_tasks` is already saturated by the outer blocking jobs.
18227    // The driver must not park while still holding its blocking slot, because
18228    // the inner job would then have no capacity to run.
18229    use std::sync::{Barrier, mpsc};
18230    use std::time::Duration;
18231
18232    let (done_tx, done_rx) = mpsc::channel();
18233    let runner = std::thread::spawn(move || {
18234      let metrics = Arc::new(RuntimeMetrics::default());
18235      let options = RuntimeOptions {
18236        flavor: RuntimeFlavor::MultiThread,
18237        worker_threads: 2,
18238        max_blocking_tasks: 2,
18239        thread_name_prefix: "rd1-nested-blk".to_string(),
18240        park_deadline: None,
18241      };
18242      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18243
18244      let outer_count = 2usize; // saturates max_blocking_tasks
18245      let barrier = Arc::new(Barrier::new(outer_count));
18246
18247      let mut handles = Vec::new();
18248      for _ in 0..outer_count {
18249        let exec = Arc::clone(&executor);
18250        let barrier = Arc::clone(&barrier);
18251        let handle = executor.schedule_blocking(move || {
18252          // Both outer blocking jobs hold a slot before either nests.
18253          barrier.wait();
18254          let inner_exec = Arc::clone(&exec);
18255          let mut value = 0usize;
18256          {
18257            let mut inner = std::pin::pin!(async {
18258              value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
18259            });
18260            exec.block_on(inner.as_mut());
18261          }
18262          value
18263        });
18264        handles.push(handle);
18265      }
18266
18267      for handle in handles {
18268        assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
18269      }
18270      let _ = done_tx.send(());
18271    });
18272
18273    match done_rx.recv_timeout(Duration::from_secs(10)) {
18274      Ok(()) => runner.join().unwrap(),
18275      Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
18276    }
18277  }
18278
18279  #[cfg(not(target_family = "wasm"))]
18280  #[test]
18281  fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
18282    let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
18283    let owner = executor.fresh_owner_token();
18284    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18285    let job = BlockingJobId(u64::MAX - 5);
18286    let dependency = TaskDependency::default();
18287    dependency.set_owned(BlockingDependency { job, owner: None });
18288    let ran = Arc::new(AtomicBool::new(false));
18289    let job_ran = Arc::clone(&ran);
18290    executor
18291      .blocking_queue
18292      .lock()
18293      .unwrap()
18294      .push(QueuedBlocking {
18295        id: job,
18296        run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
18297      });
18298
18299    assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
18300    assert!(
18301      !executor.try_owned_blocking_over_cap(&dependency),
18302      "an untagged worker must not infer ancestry from a sole active owner"
18303    );
18304    assert!(!ran.load(Ordering::SeqCst));
18305    assert!(
18306      executor
18307        .blocking_queue
18308        .lock()
18309        .unwrap()
18310        .jobs
18311        .contains_key(&job),
18312      "rejected owner inference must leave the exact job queued"
18313    );
18314  }
18315
18316  #[cfg(not(target_family = "wasm"))]
18317  #[test]
18318  fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
18319    use std::sync::{Barrier, mpsc};
18320
18321    let options = RuntimeOptions {
18322      flavor: RuntimeFlavor::MultiThread,
18323      worker_threads: 2,
18324      max_blocking_tasks: 2,
18325      thread_name_prefix: "exact-owner-frame".to_string(),
18326      park_deadline: None,
18327    };
18328    let executor =
18329      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18330    let owners_saturated = Arc::new(Barrier::new(3));
18331    let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
18332    let begin_lending = Arc::new(Barrier::new(3));
18333    let release_dependencies = Arc::new(Barrier::new(3));
18334    let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
18335    let mut owners = Vec::new();
18336
18337    for owner_index in 0..2 {
18338      let owner_executor = Arc::clone(&executor);
18339      let owners_saturated = Arc::clone(&owners_saturated);
18340      let dependency_queued_tx = dependency_queued_tx.clone();
18341      let begin_lending = Arc::clone(&begin_lending);
18342      let release_dependencies = Arc::clone(&release_dependencies);
18343      let dependency_started_tx = dependency_started_tx.clone();
18344      owners.push(executor.schedule_blocking(move || {
18345        let owner_thread = std::thread::current().id();
18346        owners_saturated.wait();
18347        let child_started = dependency_started_tx.clone();
18348        let child_release = Arc::clone(&release_dependencies);
18349        let child = owner_executor.schedule_blocking(move || {
18350          assert_eq!(
18351            std::thread::current().id(),
18352            owner_thread,
18353            "an owner frame must execute only its own exact dependency on its lent lane"
18354          );
18355          child_started.send(owner_index).unwrap();
18356          child_release.wait();
18357          owner_index
18358        });
18359        dependency_queued_tx.send(()).unwrap();
18360        begin_lending.wait();
18361
18362        let mut result = None;
18363        {
18364          let mut future = std::pin::pin!(async {
18365            result = Some(child.await.unwrap());
18366          });
18367          assert_eq!(
18368            owner_executor.block_on(future.as_mut()),
18369            BlockOnOutcome::Completed
18370          );
18371        }
18372        result.unwrap()
18373      }));
18374    }
18375    drop(dependency_queued_tx);
18376    drop(dependency_started_tx);
18377    owners_saturated.wait();
18378
18379    for _ in 0..2 {
18380      dependency_queued_rx
18381        .recv_timeout(Duration::from_secs(2))
18382        .expect("both owner-scoped dependencies must be queued while the cap is saturated");
18383    }
18384    let (unrelated_tx, unrelated_rx) = mpsc::channel();
18385    let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
18386    begin_lending.wait();
18387
18388    let mut started = Vec::new();
18389    for _ in 0..2 {
18390      started.push(
18391        dependency_started_rx
18392          .recv_timeout(Duration::from_secs(2))
18393          .expect("each owner must service its own exact dependency"),
18394      );
18395    }
18396    started.sort_unstable();
18397    assert_eq!(started, vec![0, 1]);
18398    assert!(
18399      unrelated_rx.try_recv().is_err(),
18400      "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
18401    );
18402
18403    release_dependencies.wait();
18404    for (owner_index, owner) in owners.into_iter().enumerate() {
18405      assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
18406    }
18407    unrelated_rx
18408      .recv_timeout(Duration::from_secs(2))
18409      .expect("the unrelated job must run after an owner releases a counted lane");
18410    futures::executor::block_on(unrelated).unwrap();
18411  }
18412
18413  #[cfg(not(target_family = "wasm"))]
18414  #[test]
18415  fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
18416    use std::sync::{Mutex, mpsc};
18417
18418    const DEPENDENCY_COUNT: usize = 4;
18419
18420    let options = RuntimeOptions {
18421      flavor: RuntimeFlavor::MultiThread,
18422      worker_threads: 3,
18423      max_blocking_tasks: 1,
18424      thread_name_prefix: "parked-unrelated-worker".to_string(),
18425      park_deadline: None,
18426    };
18427    let executor =
18428      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18429    let (parked_tx, parked_rx) = mpsc::channel();
18430    let (release_parked_tx, release_parked_rx) = mpsc::channel();
18431    executor.pool.spawn(move || {
18432      parked_tx.send(()).unwrap();
18433      release_parked_rx.recv().unwrap();
18434    });
18435    parked_rx
18436      .recv_timeout(Duration::from_secs(2))
18437      .expect("the unrelated Rayon worker must remain occupied");
18438
18439    let owner_executor = Arc::clone(&executor);
18440    let owner = executor.schedule_blocking(move || {
18441      let owner = BLOCKING_OWNER
18442        .with(std::cell::Cell::get)
18443        .expect("the blocking closure must carry its exact owner frame");
18444      let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
18445      let results = Arc::new(Mutex::new(Vec::new()));
18446      let descendant_results = Arc::clone(&results);
18447      let descendant_executor = Arc::clone(&owner_executor);
18448      rayon::scope(move |scope| {
18449        scope.spawn(move |_| {
18450          assert_eq!(
18451            BLOCKING_OWNER.with(std::cell::Cell::get),
18452            None,
18453            "the parked owner must force its descendant onto the remaining worker"
18454          );
18455          let _owner = BlockingOwnerGuard::enter(Some(owner));
18456          descendant_started_tx.send(()).unwrap();
18457          for value in 0..DEPENDENCY_COUNT {
18458            let handle = descendant_executor.schedule_blocking(move || value);
18459            let mut result = None;
18460            {
18461              let mut future = std::pin::pin!(async {
18462                result = Some(handle.await.unwrap());
18463              });
18464              assert_eq!(
18465                descendant_executor.block_on(future.as_mut()),
18466                BlockOnOutcome::Completed
18467              );
18468            }
18469            descendant_results.lock().unwrap().push(result.unwrap());
18470          }
18471        });
18472        descendant_started_rx
18473          .recv_timeout(Duration::from_secs(2))
18474          .expect("the nested descendant must start on the only unoccupied worker");
18475      });
18476      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
18477    });
18478
18479    assert_eq!(
18480      futures::executor::block_on(owner).unwrap(),
18481      (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
18482    );
18483
18484    let (idle_tx, idle_rx) = mpsc::channel();
18485    let idle_executor = Arc::clone(&executor);
18486    let idle_waiter = std::thread::spawn(move || {
18487      idle_executor.begin_shutdown();
18488      idle_executor.wait_until_scheduler_idle();
18489      idle_tx.send(()).unwrap();
18490    });
18491    idle_rx
18492      .recv_timeout(Duration::from_secs(2))
18493      .expect("an unrelated parked worker must not retain owner-probe idle accounting");
18494
18495    release_parked_tx.send(()).unwrap();
18496    idle_waiter.join().unwrap();
18497  }
18498
18499  #[cfg(not(target_family = "wasm"))]
18500  #[test]
18501  fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
18502    use std::sync::{Condvar, Mutex, mpsc};
18503
18504    let options = RuntimeOptions {
18505      flavor: RuntimeFlavor::MultiThread,
18506      worker_threads: 3,
18507      max_blocking_tasks: 1,
18508      thread_name_prefix: "same-owner-rearm".to_string(),
18509      park_deadline: None,
18510    };
18511    let executor =
18512      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18513    let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
18514    let (job_started_tx, job_started_rx) = mpsc::channel();
18515    let owner_executor = Arc::clone(&executor);
18516    let owner_releases = Arc::clone(&releases);
18517    let owner = executor.schedule_blocking(move || {
18518      let owner = BLOCKING_OWNER
18519        .with(std::cell::Cell::get)
18520        .expect("the blocking closure must carry its exact owner frame");
18521      let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
18522      let results = Arc::new(Mutex::new(Vec::new()));
18523      let scope_results = Arc::clone(&results);
18524      rayon::scope(move |scope| {
18525        for index in 0..2 {
18526          let descendant_ready_tx = descendant_ready_tx.clone();
18527          let descendant_executor = Arc::clone(&owner_executor);
18528          let descendant_results = Arc::clone(&scope_results);
18529          let job_started_tx = job_started_tx.clone();
18530          let releases = Arc::clone(&owner_releases);
18531          scope.spawn(move |_| {
18532            assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
18533            let _owner = BlockingOwnerGuard::enter(Some(owner));
18534            descendant_ready_tx.send(()).unwrap();
18535            let handle = descendant_executor.schedule_blocking(move || {
18536              job_started_tx.send(index).unwrap();
18537              let (released, changed) = &*releases;
18538              let mut state = released.lock().unwrap();
18539              while !state[index] {
18540                state = changed.wait(state).unwrap();
18541              }
18542              index
18543            });
18544            let mut result = None;
18545            {
18546              let mut future = std::pin::pin!(async {
18547                result = Some(handle.await.unwrap());
18548              });
18549              assert_eq!(
18550                descendant_executor.block_on(future.as_mut()),
18551                BlockOnOutcome::Completed
18552              );
18553            }
18554            descendant_results.lock().unwrap().push(result.unwrap());
18555          });
18556        }
18557        drop(descendant_ready_tx);
18558        for _ in 0..2 {
18559          descendant_ready_rx
18560            .recv_timeout(Duration::from_secs(2))
18561            .expect("both descendants must be stolen before the owner leaves its Rayon scope");
18562        }
18563      });
18564      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
18565    });
18566
18567    let first = job_started_rx
18568      .recv_timeout(Duration::from_secs(2))
18569      .expect("one exact dependency must borrow the owner lane");
18570    assert!(
18571      job_started_rx
18572        .recv_timeout(Duration::from_millis(100))
18573        .is_err(),
18574      "one owner frame must not service two dependencies concurrently"
18575    );
18576    let unrelated_parker = Arc::new(DriverParker::default());
18577    executor
18578      .parked_drivers
18579      .register_with_role(&unrelated_parker, true, None);
18580    {
18581      let (released, changed) = &*releases;
18582      released.lock().unwrap()[first] = true;
18583      changed.notify_all();
18584    }
18585
18586    let second = job_started_rx
18587      .recv_timeout(Duration::from_secs(2))
18588      .expect("completion must rearm one parked same-owner dependency");
18589    assert_ne!(second, first);
18590    assert!(
18591      !unrelated_parker.consume_permit(),
18592      "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
18593    );
18594    executor.parked_drivers.deregister(&unrelated_parker);
18595    {
18596      let (released, changed) = &*releases;
18597      released.lock().unwrap()[second] = true;
18598      changed.notify_all();
18599    }
18600
18601    let mut results = futures::executor::block_on(owner).unwrap();
18602    results.sort_unstable();
18603    assert_eq!(results, vec![0, 1]);
18604  }
18605
18606  #[cfg(not(target_family = "wasm"))]
18607  #[test]
18608  fn failed_exact_claim_rearms_parked_same_owner_dependency() {
18609    let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
18610    let owner = executor.fresh_owner_token();
18611    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18612    let failed_dependency = Arc::new(TaskDependency::default());
18613    failed_dependency.set_owned(BlockingDependency {
18614      job: BlockingJobId(u64::MAX - 21),
18615      owner: Some(owner),
18616    });
18617    let parked_dependency = Arc::new(TaskDependency::default());
18618    parked_dependency.set_owned(BlockingDependency {
18619      job: BlockingJobId(u64::MAX - 22),
18620      owner: Some(owner),
18621    });
18622    let parker = Arc::new(DriverParker::default());
18623
18624    let queue = executor.blocking_queue.lock().unwrap();
18625    let claimant_executor = Arc::clone(&executor);
18626    let claimant_dependency = Arc::clone(&failed_dependency);
18627    let claimant = std::thread::spawn(move || {
18628      let _owner = BlockingOwnerGuard::enter(Some(owner));
18629      claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
18630    });
18631    wait_until("the failed claimant to reserve the owner lane", || {
18632      !executor.active_blocking_owners.is_available(owner)
18633    });
18634    executor
18635      .parked_drivers
18636      .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
18637    failed_dependency.clear();
18638    drop(queue);
18639
18640    assert!(
18641      !claimant.join().unwrap(),
18642      "the cancelled dependency must not execute"
18643    );
18644    assert!(
18645      parker.consume_permit(),
18646      "releasing a failed exact claim must hand the owner lane to another parked dependency"
18647    );
18648  }
18649
18650  #[cfg(not(target_family = "wasm"))]
18651  #[test]
18652  fn exact_owner_lending_does_not_start_blocking_after_stop() {
18653    use std::sync::mpsc;
18654
18655    let options = RuntimeOptions {
18656      flavor: RuntimeFlavor::MultiThread,
18657      worker_threads: 2,
18658      max_blocking_tasks: 1,
18659      thread_name_prefix: "stopped-exact-owner".to_string(),
18660      park_deadline: None,
18661    };
18662    let executor =
18663      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18664    let ran = Arc::new(AtomicBool::new(false));
18665    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
18666    let (release_claim_tx, release_claim_rx) = mpsc::channel();
18667    let owner_executor = Arc::clone(&executor);
18668    let child_ran = Arc::clone(&ran);
18669    let owner = executor.schedule_blocking(move || {
18670      BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
18671        *slot.borrow_mut() = Some(Box::new(move || {
18672          claim_entered_tx.send(()).unwrap();
18673          release_claim_rx.recv().unwrap();
18674        }));
18675      });
18676      let child = owner_executor.schedule_blocking(move || {
18677        child_ran.store(true, Ordering::SeqCst);
18678      });
18679      let mut future = std::pin::pin!(async move {
18680        let _ = child.await;
18681      });
18682      owner_executor.block_on(future.as_mut())
18683    });
18684
18685    claim_entered_rx
18686      .recv_timeout(Duration::from_secs(2))
18687      .expect("the cooperative driver must pause before the exact-owner queue claim");
18688    assert_eq!(
18689      executor.blocking_queue.lock().unwrap().jobs.len(),
18690      1,
18691      "the exact dependency must remain queued before stop publication"
18692    );
18693
18694    // Pin the reported race: the cooperative driver already passed its loop's
18695    // stop check, then shutdown publishes stop before the exact queue claim.
18696    executor.stop.begin_shutdown();
18697    release_claim_tx.send(()).unwrap();
18698
18699    assert_eq!(
18700      futures::executor::block_on(owner).unwrap(),
18701      BlockOnOutcome::Stopped,
18702      "the cooperative driver must stop instead of executing its queued dependency"
18703    );
18704    assert!(
18705      !ran.load(Ordering::SeqCst),
18706      "exact-owner lending started blocking work after stop"
18707    );
18708    assert_eq!(
18709      executor.blocking_queue.lock().unwrap().jobs.len(),
18710      1,
18711      "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
18712    );
18713
18714    executor.begin_shutdown();
18715    executor.wait_until_scheduler_idle();
18716    assert!(executor.blocking_queue.lock().unwrap().is_empty());
18717    assert!(!ran.load(Ordering::SeqCst));
18718  }
18719
18720  #[cfg(not(target_family = "wasm"))]
18721  #[test]
18722  fn exact_owner_lending_attempts_scale_linearly_with_topology() {
18723    const DEPENDENCY_COUNT: usize = 1024;
18724
18725    let options = RuntimeOptions {
18726      flavor: RuntimeFlavor::MultiThread,
18727      worker_threads: 2,
18728      max_blocking_tasks: 1,
18729      thread_name_prefix: "linear-owner-lending".to_string(),
18730      park_deadline: None,
18731    };
18732    let executor =
18733      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18734    let completed = Arc::new(AtomicUsize::new(0));
18735
18736    for index in 0..DEPENDENCY_COUNT {
18737      let owner = executor.fresh_owner_token();
18738      let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18739      let dependency = TaskDependency::new(u64::MAX, Some(owner));
18740      let blocking = BlockingDependency {
18741        job: BlockingJobId(u64::MAX - index as u64),
18742        owner: Some(owner),
18743      };
18744      dependency.set_owned(blocking);
18745      let completed = Arc::clone(&completed);
18746      executor
18747        .blocking_queue
18748        .lock()
18749        .unwrap()
18750        .push(QueuedBlocking {
18751          id: blocking.job,
18752          run: Box::new(move || {
18753            completed.fetch_add(1, Ordering::SeqCst);
18754          }),
18755        });
18756
18757      let _owner = BlockingOwnerGuard::enter(Some(owner));
18758      assert!(executor.try_owned_blocking_over_cap(&dependency));
18759    }
18760
18761    assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
18762    assert_eq!(
18763      executor.owner_lending_attempts.load(Ordering::Relaxed),
18764      DEPENDENCY_COUNT,
18765      "targeted lending must perform one O(1) claim attempt per exact dependency"
18766    );
18767    let queue = executor.blocking_queue.lock().unwrap();
18768    assert!(queue.is_empty());
18769    assert_eq!(queue.head, None);
18770    assert_eq!(queue.tail, None);
18771  }
18772
18773  #[cfg(not(target_family = "wasm"))]
18774  #[test]
18775  fn one_dependency_interspersed_owner_publications_stays_linear() {
18776    const PUBLICATION_COUNT: usize = 1024;
18777
18778    let executor = multi_thread_executor(2, None, "linear-publication-lending");
18779    let owner = executor.fresh_owner_token();
18780    let wrong_owner = executor.fresh_owner_token();
18781    let dependency = TaskDependency::new(u64::MAX, Some(owner));
18782    let completed = Arc::new(AtomicUsize::new(0));
18783    for index in 0..PUBLICATION_COUNT {
18784      let cancelled_source = TaskDependency::default();
18785      cancelled_source.set_owned(BlockingDependency {
18786        job: BlockingJobId(u64::MAX - 100_000 - index as u64),
18787        owner: Some(owner),
18788      });
18789      dependency.set_borrowed(
18790        &cancelled_source
18791          .get()
18792          .expect("the source publication must initially be live"),
18793      );
18794      cancelled_source.clear();
18795
18796      dependency.set_owned(BlockingDependency {
18797        job: BlockingJobId(u64::MAX - 200_000 - index as u64),
18798        owner: Some(wrong_owner),
18799      });
18800      let blocking = BlockingDependency {
18801        job: BlockingJobId(u64::MAX - 1_000 - index as u64),
18802        owner: Some(owner),
18803      };
18804      dependency.set_owned(blocking);
18805      let completed = Arc::clone(&completed);
18806      executor
18807        .blocking_queue
18808        .lock()
18809        .unwrap()
18810        .push(QueuedBlocking {
18811          id: blocking.job,
18812          run: Box::new(move || {
18813            completed.fetch_add(1, Ordering::SeqCst);
18814          }),
18815        });
18816    }
18817    dependency.reset_entry_visits();
18818
18819    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18820    let _owner = BlockingOwnerGuard::enter(Some(owner));
18821    for _ in 0..PUBLICATION_COUNT {
18822      assert!(
18823        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18824        "the owner-handoff predicate must find the next exact publication"
18825      );
18826      assert!(
18827        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
18828        "the park-availability predicate must find the next serviceable publication"
18829      );
18830      assert!(executor.try_owned_blocking_over_cap(&dependency));
18831    }
18832
18833    assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
18834    assert!(
18835      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18836      "wrong-owner publications must not satisfy an exact handoff"
18837    );
18838    assert!(
18839      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
18840      "wrong-owner publications must not make the owner's lane appear serviceable"
18841    );
18842    assert!(
18843      dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
18844      "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
18845      dependency.entry_visits()
18846    );
18847    assert!(executor.blocking_queue.lock().unwrap().is_empty());
18848  }
18849
18850  #[cfg(not(target_family = "wasm"))]
18851  #[test]
18852  fn binding_unowned_dependency_updates_exact_owner_predicate() {
18853    let owner = BlockingOwnerToken {
18854      executor_id: u64::MAX - 200,
18855      frame: u64::MAX - 201,
18856    };
18857    let dependency = TaskDependency::default();
18858    let blocking = BlockingDependency {
18859      job: BlockingJobId(u64::MAX - 202),
18860      owner: None,
18861    };
18862    dependency.set_owned(blocking);
18863
18864    assert!(
18865      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18866      "an unowned publication must not satisfy an exact owner handoff"
18867    );
18868    assert!(
18869      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
18870      "an unowned publication is available to the ambient owner"
18871    );
18872
18873    let selected = dependency
18874      .select_for_owner(owner)
18875      .expect("the owner must bind the unowned publication");
18876    assert_eq!(selected.publication.dependency.owner, Some(owner));
18877    assert!(
18878      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18879      "binding must immediately publish the entry to exact-owner predicates"
18880    );
18881    assert!(dependency.try_claim(&selected));
18882    assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
18883  }
18884
18885  #[cfg(not(target_family = "wasm"))]
18886  #[test]
18887  fn owner_selection_preserves_unowned_and_exact_publication_order() {
18888    let owner = BlockingOwnerToken {
18889      executor_id: u64::MAX - 210,
18890      frame: u64::MAX - 211,
18891    };
18892    let dependency = TaskDependency::default();
18893    let first = BlockingDependency {
18894      job: BlockingJobId(u64::MAX - 212),
18895      owner: None,
18896    };
18897    let second = BlockingDependency {
18898      job: BlockingJobId(u64::MAX - 213),
18899      owner: Some(owner),
18900    };
18901    dependency.set_owned(first);
18902    dependency.set_owned(second);
18903
18904    let selected = dependency
18905      .select_for_owner(owner)
18906      .expect("the first publication must be found");
18907    assert_eq!(
18908      selected.publication.dependency,
18909      BlockingDependency {
18910        owner: Some(owner),
18911        ..first
18912      }
18913    );
18914    assert!(dependency.try_claim(&selected));
18915
18916    let selected = dependency
18917      .select_for_owner(owner)
18918      .expect("the second publication must remain eligible");
18919    assert_eq!(selected.publication.dependency, second);
18920  }
18921
18922  #[cfg(not(target_family = "wasm"))]
18923  #[test]
18924  fn blocking_queue_exact_removal_reclaims_fifo_links() {
18925    const JOB_COUNT: u64 = 4096;
18926
18927    let mut queue = BlockingQueue {
18928      closed: false,
18929      head: None,
18930      tail: None,
18931      jobs: FxHashMap::default(),
18932    };
18933    for id in 1..=JOB_COUNT {
18934      queue.push(QueuedBlocking {
18935        id: BlockingJobId(id),
18936        run: Box::new(|| {}),
18937      });
18938    }
18939    for id in (2..=JOB_COUNT).step_by(2) {
18940      drop(
18941        queue
18942          .remove(BlockingJobId(id))
18943          .expect("even job must remain indexed"),
18944      );
18945    }
18946    for id in (1..=JOB_COUNT).step_by(2) {
18947      drop(
18948        queue
18949          .remove(BlockingJobId(id))
18950          .expect("odd job must remain indexed"),
18951      );
18952    }
18953    assert!(queue.jobs.is_empty());
18954    assert_eq!(queue.head, None);
18955    assert_eq!(queue.tail, None);
18956
18957    let order = Arc::new(Mutex::new(Vec::new()));
18958    for id in 1..=3 {
18959      let order = Arc::clone(&order);
18960      queue.push(QueuedBlocking {
18961        id: BlockingJobId(id),
18962        run: Box::new(move || order.lock().unwrap().push(id)),
18963      });
18964    }
18965    drop(
18966      queue
18967        .remove(BlockingJobId(2))
18968        .expect("middle job must be removable"),
18969    );
18970    queue.pop_front().expect("FIFO head must remain linked")();
18971    queue.pop_front().expect("FIFO tail must remain linked")();
18972    assert!(queue.pop_front().is_none());
18973    assert_eq!(*order.lock().unwrap(), vec![1, 3]);
18974    assert_eq!(queue.head, None);
18975    assert_eq!(queue.tail, None);
18976  }
18977
18978  #[cfg(not(target_family = "wasm"))]
18979  #[test]
18980  fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
18981    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
18982
18983    if std::env::var_os(CHILD_ENV).is_some() {
18984      configure(RuntimeOptions {
18985        flavor: RuntimeFlavor::MultiThread,
18986        worker_threads: 2,
18987        max_blocking_tasks: 1,
18988        thread_name_prefix: "dependency-lending-metrics".to_string(),
18989        park_deadline: None,
18990      })
18991      .unwrap();
18992
18993      let outer = spawn_blocking(|| {
18994        let inner = spawn_blocking(|| 42usize);
18995        block_on(inner).unwrap()
18996      });
18997      assert_eq!(block_on(outer).unwrap(), 42);
18998
18999      // Result delivery wakes the joiner from inside the blocking closure,
19000      // immediately before the scheduler-owned metrics guard retires. Use
19001      // shutdown quiescence before sampling completed/live counters so this
19002      // test does not race that final bookkeeping.
19003      shutdown().unwrap();
19004      let snapshot = metrics();
19005      assert_eq!(snapshot.blocking_tasks_started, 2);
19006      assert_eq!(snapshot.blocking_tasks_completed, 2);
19007      assert_eq!(snapshot.active_blocking_tasks, 0);
19008      assert_eq!(
19009        snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
19010        "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
19011      );
19012      return;
19013    }
19014
19015    let output = std::process::Command::new(std::env::current_exe().unwrap())
19016      .arg("--exact")
19017      .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
19018      .arg("--nocapture")
19019      .env(CHILD_ENV, "1")
19020      .output()
19021      .expect("the dependency-lending metrics subprocess must start");
19022    assert!(
19023      output.status.success(),
19024      "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
19025      output.status.code(),
19026      String::from_utf8_lossy(&output.stdout),
19027      String::from_utf8_lossy(&output.stderr)
19028    );
19029  }
19030
19031  #[cfg(not(target_family = "wasm"))]
19032  #[test]
19033  fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
19034    use std::sync::mpsc;
19035
19036    let (done_tx, done_rx) = mpsc::channel();
19037    let runner = std::thread::spawn(move || {
19038      let options = RuntimeOptions {
19039        flavor: RuntimeFlavor::MultiThread,
19040        worker_threads: 2,
19041        max_blocking_tasks: 1,
19042        thread_name_prefix: "exact-blocking-job".to_string(),
19043        park_deadline: None,
19044      };
19045      let executor =
19046        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19047      let outer_exec = Arc::clone(&executor);
19048      let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
19049      let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
19050      let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
19051      let outer = executor.schedule_blocking(move || {
19052        let sibling = outer_exec.schedule_blocking(move || {
19053          sibling_started_tx.send(()).unwrap();
19054          release_sibling_rx.recv().unwrap();
19055        });
19056        let awaited = outer_exec.schedule_blocking(move || {
19057          awaited_ran_tx.send(()).unwrap();
19058          17usize
19059        });
19060        let mut value = None;
19061        {
19062          let mut future = std::pin::pin!(async {
19063            value = Some(awaited.await.unwrap());
19064          });
19065          outer_exec.block_on(future.as_mut());
19066        }
19067        (sibling, value.unwrap())
19068      });
19069
19070      awaited_ran_rx
19071        .recv_timeout(Duration::from_secs(2))
19072        .expect("the exact awaited job must bypass the earlier detached sibling");
19073      let (sibling, value) = futures::executor::block_on(outer).unwrap();
19074      assert_eq!(value, 17);
19075      sibling_started_rx
19076        .recv_timeout(Duration::from_secs(2))
19077        .expect("the sibling must run after the owner releases its counted slot");
19078      release_sibling_tx.send(()).unwrap();
19079      futures::executor::block_on(sibling).unwrap();
19080      done_tx.send(()).unwrap();
19081    });
19082    done_rx
19083      .recv_timeout(Duration::from_secs(5))
19084      .expect("same-owner sibling selection deadlocked the exact awaited job");
19085    runner.join().unwrap();
19086  }
19087
19088  #[cfg(not(target_family = "wasm"))]
19089  #[test]
19090  fn blocking_dependency_propagates_through_async_join_lineage() {
19091    use std::sync::mpsc;
19092
19093    let (done_tx, done_rx) = mpsc::channel();
19094    let runner = std::thread::spawn(move || {
19095      let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
19096      let owner_controller = Arc::clone(&controller);
19097      let (child_tx, child_rx) = mpsc::channel();
19098      let outer = controller
19099        .try_spawn_blocking(move || {
19100          let async_controller = Arc::clone(&owner_controller);
19101          let async_child = owner_controller
19102            .try_spawn(async move {
19103              async_controller
19104                .try_spawn_blocking(move || {
19105                  child_tx.send(()).unwrap();
19106                  23usize
19107                })
19108                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
19109                .await
19110                .unwrap()
19111            })
19112            .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
19113          let mut value = None;
19114          {
19115            let mut future = std::pin::pin!(async {
19116              value = Some(async_child.await.unwrap());
19117            });
19118            owner_controller.block_on(future.as_mut());
19119          }
19120          value.unwrap()
19121        })
19122        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19123
19124      child_rx
19125        .recv_timeout(Duration::from_secs(2))
19126        .expect("the async descendant's exact blocking dependency must inherit the owner lane");
19127      assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
19128      controller.shutdown().unwrap();
19129      done_tx.send(()).unwrap();
19130    });
19131    done_rx
19132      .recv_timeout(Duration::from_secs(5))
19133      .expect("blocking dependency lineage deadlocked");
19134    runner.join().unwrap();
19135  }
19136
19137  #[cfg(not(target_family = "wasm"))]
19138  #[test]
19139  fn blocking_dependency_does_not_escape_a_serviced_runnable() {
19140    use std::sync::mpsc;
19141
19142    let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
19143    let outer_controller = Arc::clone(&controller);
19144    let (child_queued_tx, child_queued_rx) = mpsc::channel();
19145    let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
19146    let (release_outer_tx, release_outer_rx) = oneshot::channel();
19147    let outer = controller
19148      .try_spawn_blocking(move || {
19149        let child_controller = Arc::clone(&outer_controller);
19150        let child = outer_controller
19151          .try_spawn(async move {
19152            let blocking = child_controller
19153              .try_spawn_blocking(move || {
19154                unrelated_ran_tx.send(()).unwrap();
19155                31usize
19156              })
19157              .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
19158            child_queued_tx.send(()).unwrap();
19159            blocking.await.unwrap()
19160          })
19161          .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
19162        let mut released = false;
19163        {
19164          let mut future = std::pin::pin!(async {
19165            release_outer_rx.await.unwrap();
19166            released = true;
19167          });
19168          outer_controller.block_on(future.as_mut());
19169        }
19170        assert!(released);
19171        child
19172      })
19173      .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19174
19175    child_queued_rx
19176      .recv_timeout(Duration::from_secs(2))
19177      .expect("the cooperative driver must service the unrelated async runnable");
19178    assert!(
19179      unrelated_ran_rx
19180        .recv_timeout(Duration::from_millis(200))
19181        .is_err(),
19182      "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
19183    );
19184
19185    release_outer_tx.send(()).unwrap();
19186    let child = futures::executor::block_on(outer).unwrap();
19187    unrelated_ran_rx
19188      .recv_timeout(Duration::from_secs(2))
19189      .expect("the unrelated blocking job must run after the owner releases its counted slot");
19190    assert_eq!(futures::executor::block_on(child).unwrap(), 31);
19191    controller.shutdown().unwrap();
19192  }
19193
19194  #[cfg(not(target_family = "wasm"))]
19195  #[test]
19196  fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
19197    let parent = Arc::new(TaskDependency::default());
19198    let _context = TaskDependencyGuard::enter(&parent);
19199    let dependency = BlockingDependency {
19200      job: BlockingJobId(u64::MAX),
19201      owner: None,
19202    };
19203    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19204    let mut handle = JoinHandle(JoinHandleInner::Blocking {
19205      receiver,
19206      dependency,
19207    });
19208    let waker = futures::task::noop_waker();
19209    let mut cx = Context::from_waker(&waker);
19210
19211    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19212    assert_eq!(parent.get_dependency(), Some(dependency));
19213    drop(handle);
19214    assert_eq!(
19215      parent.get_dependency(),
19216      None,
19217      "an abandoned JoinHandle must clear stale dependency lineage"
19218    );
19219    drop(sender);
19220  }
19221
19222  #[cfg(not(target_family = "wasm"))]
19223  #[test]
19224  fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
19225    let owner = BlockingOwnerToken {
19226      executor_id: u64::MAX - 40,
19227      frame: u64::MAX - 41,
19228    };
19229    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
19230    let _context = TaskDependencyGuard::enter(&parent);
19231    let dependency = BlockingDependency {
19232      job: BlockingJobId(u64::MAX - 42),
19233      owner: None,
19234    };
19235    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19236    let mut handle = JoinHandle(JoinHandleInner::Blocking {
19237      receiver,
19238      dependency,
19239    });
19240    let waker = futures::task::noop_waker();
19241    let mut cx = Context::from_waker(&waker);
19242
19243    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19244    assert_eq!(
19245      parent.get_dependency(),
19246      Some(BlockingDependency {
19247        owner: Some(owner),
19248        ..dependency
19249      }),
19250      "the ambient task owner must enrich an untagged direct blocking dependency"
19251    );
19252    drop(handle);
19253    assert_eq!(
19254      parent.get_dependency(),
19255      None,
19256      "detach cleanup must match the stable job id after owner enrichment"
19257    );
19258    drop(sender);
19259  }
19260
19261  #[cfg(not(target_family = "wasm"))]
19262  #[test]
19263  fn one_poll_preserves_every_observed_blocking_dependency() {
19264    let parent = Arc::new(TaskDependency::default());
19265    let _context = TaskDependencyGuard::enter(&parent);
19266    let first = BlockingDependency {
19267      job: BlockingJobId(u64::MAX - 60),
19268      owner: None,
19269    };
19270    let second = BlockingDependency {
19271      job: BlockingJobId(u64::MAX - 61),
19272      owner: None,
19273    };
19274    let (first_sender, first_receiver) =
19275      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19276    let (second_sender, second_receiver) =
19277      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19278    let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
19279      receiver: first_receiver,
19280      dependency: first,
19281    });
19282    let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
19283      receiver: second_receiver,
19284      dependency: second,
19285    });
19286    let waker = futures::task::noop_waker();
19287    let mut cx = Context::from_waker(&waker);
19288
19289    assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
19290    assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
19291    assert_eq!(
19292      parent.get_dependencies(),
19293      vec![first, second],
19294      "publishing a later dependency in one user poll must not replace an earlier dependency"
19295    );
19296
19297    drop(first_handle);
19298    drop(second_handle);
19299    drop(first_sender);
19300    drop(second_sender);
19301  }
19302
19303  #[cfg(not(target_family = "wasm"))]
19304  #[test]
19305  fn dependency_tls_borrow_is_released_before_reentrant_wake() {
19306    use std::sync::mpsc;
19307
19308    struct ReenterDependencyContext {
19309      dependency: Arc<TaskDependency>,
19310      woke: mpsc::Sender<()>,
19311    }
19312
19313    impl std::task::Wake for ReenterDependencyContext {
19314      fn wake(self: Arc<Self>) {
19315        let _context = TaskDependencyGuard::enter(&self.dependency);
19316        self.woke.send(()).unwrap();
19317      }
19318    }
19319
19320    let parent = Arc::new(TaskDependency::default());
19321    let nested = Arc::new(TaskDependency::default());
19322    let (woke_tx, woke_rx) = mpsc::channel();
19323    let waiter = Waker::from(Arc::new(ReenterDependencyContext {
19324      dependency: Arc::clone(&nested),
19325      woke: woke_tx,
19326    }));
19327    parent.register_waiter(&waiter);
19328    let blocking = BlockingDependency {
19329      job: BlockingJobId(u64::MAX - 62),
19330      owner: None,
19331    };
19332
19333    let _context = TaskDependencyGuard::enter(&parent);
19334    record_blocking_dependency(blocking);
19335    woke_rx
19336      .recv_timeout(Duration::from_secs(1))
19337      .expect("dependency notification must permit reentrant TLS context access");
19338
19339    parent.register_waiter(&waiter);
19340    clear_blocking_dependency(blocking);
19341    woke_rx
19342      .recv_timeout(Duration::from_secs(1))
19343      .expect("dependency clearing must permit reentrant TLS context access");
19344  }
19345
19346  #[cfg(not(target_family = "wasm"))]
19347  #[test]
19348  fn dependency_free_begin_poll_does_not_lock_state() {
19349    use std::sync::mpsc;
19350
19351    let dependency = Arc::new(TaskDependency::default());
19352    let state = dependency.state.lock().unwrap();
19353    let (completed_tx, completed_rx) = mpsc::channel();
19354    let polling_dependency = Arc::clone(&dependency);
19355    let polling = std::thread::spawn(move || {
19356      polling_dependency.begin_poll();
19357      completed_tx.send(()).unwrap();
19358    });
19359
19360    let completed_without_lock = completed_rx
19361      .recv_timeout(Duration::from_millis(200))
19362      .is_ok();
19363    drop(state);
19364    polling.join().unwrap();
19365    assert!(
19366      completed_without_lock,
19367      "a task with no published dependency must skip the dependency-state mutex"
19368    );
19369  }
19370
19371  #[cfg(not(target_family = "wasm"))]
19372  #[test]
19373  fn deep_dependency_liveness_chain_is_iterative() {
19374    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
19375
19376    if std::env::var_os(CHILD_ENV).is_some() {
19377      const DEPTH: usize = 100_000;
19378
19379      let mut links = Vec::with_capacity(DEPTH + 1);
19380      links.push(DependencyLink::root());
19381      for _ in 0..DEPTH {
19382        links.push(DependencyLink::derived(links.last().unwrap()));
19383      }
19384      assert!(links.last().unwrap().is_live());
19385      links[0].cancel();
19386      assert!(!links.last().unwrap().is_live());
19387      return;
19388    }
19389
19390    let output = std::process::Command::new(std::env::current_exe().unwrap())
19391      .arg("--exact")
19392      .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
19393      .arg("--nocapture")
19394      .env(CHILD_ENV, "1")
19395      .output()
19396      .expect("the subprocess test must start");
19397    assert!(
19398      output.status.success(),
19399      "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
19400      output.status.code(),
19401      String::from_utf8_lossy(&output.stdout),
19402      String::from_utf8_lossy(&output.stderr)
19403    );
19404  }
19405
19406  #[cfg(not(target_family = "wasm"))]
19407  #[test]
19408  fn task_dependency_set_contains_panicking_waiter() {
19409    struct PanicWake;
19410    impl std::task::Wake for PanicWake {
19411      fn wake(self: Arc<Self>) {
19412        panic!("intentional TaskDependency::set waiter panic");
19413      }
19414    }
19415
19416    let dependency = TaskDependency::default();
19417    let blocking = BlockingDependency {
19418      job: BlockingJobId(u64::MAX),
19419      owner: None,
19420    };
19421    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
19422
19423    let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
19424    assert!(
19425      result.is_ok(),
19426      "set notification must not unwind its caller"
19427    );
19428    assert_eq!(
19429      dependency.get_dependency(),
19430      Some(blocking),
19431      "set must commit before notifying its waiter"
19432    );
19433  }
19434
19435  #[cfg(not(target_family = "wasm"))]
19436  #[test]
19437  fn task_dependency_clear_contains_panicking_waiter() {
19438    struct PanicWake;
19439    impl std::task::Wake for PanicWake {
19440      fn wake(self: Arc<Self>) {
19441        panic!("intentional TaskDependency::clear waiter panic");
19442      }
19443    }
19444
19445    let dependency = TaskDependency::default();
19446    dependency.set_owned(BlockingDependency {
19447      job: BlockingJobId(u64::MAX),
19448      owner: None,
19449    });
19450    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
19451
19452    let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
19453    assert!(
19454      result.is_ok(),
19455      "clear notification must not unwind its caller"
19456    );
19457    assert_eq!(
19458      dependency.get_dependency(),
19459      None,
19460      "clear must commit before notifying its waiter"
19461    );
19462  }
19463
19464  #[cfg(not(target_family = "wasm"))]
19465  #[test]
19466  fn task_dependency_clear_if_contains_panicking_waiter() {
19467    struct PanicWake;
19468    impl std::task::Wake for PanicWake {
19469      fn wake(self: Arc<Self>) {
19470        panic!("intentional TaskDependency::clear_if waiter panic");
19471      }
19472    }
19473
19474    let dependency = TaskDependency::default();
19475    let blocking = BlockingDependency {
19476      job: BlockingJobId(u64::MAX),
19477      owner: None,
19478    };
19479    dependency.set_owned(blocking);
19480    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
19481
19482    let result = catch_unwind(AssertUnwindSafe(|| {
19483      dependency.clear_if_dependency(blocking)
19484    }));
19485    assert!(
19486      result.is_ok(),
19487      "clear_if notification must not unwind its caller"
19488    );
19489    assert_eq!(
19490      dependency.get_dependency(),
19491      None,
19492      "clear_if must commit before notifying its waiter"
19493    );
19494  }
19495
19496  #[cfg(not(target_family = "wasm"))]
19497  #[test]
19498  fn task_dependency_waiter_replacement_contains_panicking_drop() {
19499    use std::sync::mpsc;
19500
19501    const GENERATION: u64 = u64::MAX - 20;
19502
19503    struct PanicOnDropWake {
19504      dropped: mpsc::Sender<Option<u64>>,
19505    }
19506
19507    #[expect(
19508      clippy::manual_noop_waker,
19509      reason = "the test observes the waker payload's Drop"
19510    )]
19511    impl std::task::Wake for PanicOnDropWake {
19512      fn wake(self: Arc<Self>) {}
19513    }
19514
19515    impl Drop for PanicOnDropWake {
19516      fn drop(&mut self) {
19517        self
19518          .dropped
19519          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
19520          .unwrap();
19521        panic!("intentional replaced dependency waiter destructor panic");
19522      }
19523    }
19524
19525    let dependency = TaskDependency::new(GENERATION, None);
19526    let (dropped_tx, dropped_rx) = mpsc::channel();
19527    let waiter = Waker::from(Arc::new(PanicOnDropWake {
19528      dropped: dropped_tx,
19529    }));
19530    dependency.register_waiter(&waiter);
19531    drop(waiter);
19532
19533    let replacement = futures::task::noop_waker();
19534    let result = catch_unwind(AssertUnwindSafe(|| {
19535      dependency.register_waiter(&replacement)
19536    }));
19537    assert!(
19538      result.is_ok(),
19539      "replacing a hostile waiter must not unwind registration"
19540    );
19541    assert_eq!(
19542      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
19543      Some(GENERATION),
19544      "the replaced waiter must be destroyed under its runtime generation"
19545    );
19546  }
19547
19548  #[cfg(not(target_family = "wasm"))]
19549  #[test]
19550  fn task_dependency_retirement_contains_stored_waiter_drop() {
19551    use std::sync::mpsc;
19552
19553    const GENERATION: u64 = u64::MAX - 21;
19554
19555    struct PanicOnDropWake {
19556      dropped: mpsc::Sender<Option<u64>>,
19557    }
19558
19559    #[expect(
19560      clippy::manual_noop_waker,
19561      reason = "the test observes the waker payload's Drop"
19562    )]
19563    impl std::task::Wake for PanicOnDropWake {
19564      fn wake(self: Arc<Self>) {}
19565    }
19566
19567    impl Drop for PanicOnDropWake {
19568      fn drop(&mut self) {
19569        self
19570          .dropped
19571          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
19572          .unwrap();
19573        panic!("intentional stored dependency waiter destructor panic");
19574      }
19575    }
19576
19577    let dependency = TaskDependency::new(GENERATION, None);
19578    let (dropped_tx, dropped_rx) = mpsc::channel();
19579    let waiter = Waker::from(Arc::new(PanicOnDropWake {
19580      dropped: dropped_tx,
19581    }));
19582    dependency.register_waiter(&waiter);
19583    drop(waiter);
19584
19585    let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
19586    assert!(
19587      result.is_ok(),
19588      "retiring a hostile stored waiter must not unwind"
19589    );
19590    assert_eq!(
19591      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
19592      Some(GENERATION),
19593      "the retained waiter must be destroyed under its runtime generation"
19594    );
19595  }
19596
19597  #[cfg(not(target_family = "wasm"))]
19598  #[test]
19599  fn detaching_task_clears_dependency_waiter_parent_retention() {
19600    struct RetainingWake {
19601      retained_parent: Arc<()>,
19602    }
19603
19604    impl std::task::Wake for RetainingWake {
19605      fn wake(self: Arc<Self>) {
19606        let _ = &self.retained_parent;
19607      }
19608    }
19609
19610    let dependency = Arc::new(TaskDependency::default());
19611    let retained_parent = Arc::new(());
19612    let retained_parent_weak = Arc::downgrade(&retained_parent);
19613    let waiter = Waker::from(Arc::new(RetainingWake {
19614      retained_parent: Arc::clone(&retained_parent),
19615    }));
19616    dependency.register_waiter(&waiter);
19617    drop(waiter);
19618    drop(retained_parent);
19619    assert!(
19620      retained_parent_weak.upgrade().is_some(),
19621      "the stored dependency waiter must retain its parent before detachment"
19622    );
19623
19624    let (runnable, task) = async_task::spawn(
19625      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
19626      |_| {},
19627    );
19628    drop(JoinHandle(JoinHandleInner::Task {
19629      task: task.fallible(),
19630      awaiter: None,
19631      dependency: Arc::clone(&dependency),
19632    }));
19633    assert!(
19634      retained_parent_weak.upgrade().is_none(),
19635      "detachment must clear the dependency waiter instead of retaining its parent task"
19636    );
19637    drop(runnable);
19638  }
19639
19640  #[cfg(not(target_family = "wasm"))]
19641  #[test]
19642  fn detaching_task_clears_many_parent_publications_linearly() {
19643    const PUBLICATION_COUNT: usize = 1024;
19644
19645    struct CountWake(Arc<AtomicUsize>);
19646
19647    impl std::task::Wake for CountWake {
19648      fn wake(self: Arc<Self>) {
19649        self.0.fetch_add(1, Ordering::SeqCst);
19650      }
19651    }
19652
19653    fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
19654      dependency
19655        .state
19656        .lock()
19657        .unwrap_or_else(std::sync::PoisonError::into_inner)
19658        .current
19659        .back()
19660        .expect("the sentinel publication must be appended")
19661        .publication
19662        .clone()
19663    }
19664
19665    let owner = BlockingOwnerToken {
19666      executor_id: u64::MAX - 300,
19667      frame: u64::MAX - 301,
19668    };
19669    let other_owner = BlockingOwnerToken {
19670      executor_id: u64::MAX - 302,
19671      frame: u64::MAX - 303,
19672    };
19673    let child = Arc::new(TaskDependency::default());
19674    for index in 0..PUBLICATION_COUNT {
19675      child.set_owned(BlockingDependency {
19676        job: BlockingJobId(1_000_000 + index as u64),
19677        owner: (index % 2 == 0).then_some(owner),
19678      });
19679    }
19680    let child_publications = child.snapshot_all_live();
19681    let source = child_publications[0].clone();
19682
19683    let parent = Arc::new(TaskDependency::default());
19684    let _context = TaskDependencyGuard::enter(&parent);
19685    let (runnable, task) = async_task::spawn(
19686      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
19687      |_| {},
19688    );
19689    let mut handle = JoinHandle(JoinHandleInner::Task {
19690      task: task.fallible(),
19691      awaiter: None,
19692      dependency: Arc::clone(&child),
19693    });
19694    let waker = futures::task::noop_waker();
19695    let mut cx = Context::from_waker(&waker);
19696    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19697    assert_eq!(
19698      parent.get_dependencies().len(),
19699      PUBLICATION_COUNT,
19700      "polling the child handle must propagate every current publication"
19701    );
19702
19703    let mut wrong_claim_source = source.clone();
19704    wrong_claim_source.claim = Arc::new(DependencyClaim::new());
19705    parent.set_borrowed(&wrong_claim_source);
19706    let wrong_claim_publication = latest_publication(&parent);
19707
19708    let mut wrong_job_source = source.clone();
19709    wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
19710    parent.set_borrowed(&wrong_job_source);
19711    let wrong_job_publication = latest_publication(&parent);
19712
19713    let wrong_immediate_source = source.derived(None);
19714    parent.set_borrowed(&wrong_immediate_source);
19715    let wrong_immediate_publication = latest_publication(&parent);
19716
19717    let wakes = Arc::new(AtomicUsize::new(0));
19718    parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
19719    parent.reset_entry_visits();
19720    drop(handle);
19721
19722    assert_eq!(
19723      wakes.load(Ordering::SeqCst),
19724      1,
19725      "one batched parent cleanup must emit one waiter notification"
19726    );
19727    assert!(
19728      parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
19729      "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
19730      parent.entry_visits()
19731    );
19732    assert!(
19733      wrong_claim_publication.is_live(),
19734      "claim identity must remain part of the match"
19735    );
19736    assert!(
19737      wrong_job_publication.is_live(),
19738      "job identity must remain part of the match"
19739    );
19740    assert!(
19741      wrong_immediate_publication.is_live(),
19742      "only an exact immediate-parent link may be retired"
19743    );
19744
19745    {
19746      let state = parent
19747        .state
19748        .lock()
19749        .unwrap_or_else(std::sync::PoisonError::into_inner);
19750      assert_eq!(state.current.len(), 3);
19751      assert_eq!(
19752        state
19753          .job_sequences
19754          .values()
19755          .map(FxHashSet::len)
19756          .sum::<usize>(),
19757        3,
19758        "the job index must retain only the three non-matching sentinels"
19759      );
19760      assert!(state.unowned_sequences.is_empty());
19761      assert_eq!(state.owner_sequences.len(), 1);
19762      assert_eq!(
19763        state.owner_sequences.get(&owner).map(VecDeque::len),
19764        Some(3)
19765      );
19766    }
19767    assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
19768    assert!(
19769      !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
19770      "cancelled unowned publications must not survive in availability predicates"
19771    );
19772    drop(runnable);
19773  }
19774
19775  #[cfg(not(target_family = "wasm"))]
19776  #[test]
19777  fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
19778    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
19779
19780    if std::env::var_os(CHILD_ENV).is_some() {
19781      struct PanicWake;
19782      impl std::task::Wake for PanicWake {
19783        fn wake(self: Arc<Self>) {
19784          panic!("intentional dependency waker panic");
19785        }
19786      }
19787
19788      struct PanicOnDrop;
19789      impl Drop for PanicOnDrop {
19790        fn drop(&mut self) {
19791          panic!("intentional buffered blocking result destructor panic");
19792        }
19793      }
19794
19795      let parent = Arc::new(TaskDependency::default());
19796      let dependency = BlockingDependency {
19797        job: BlockingJobId(u64::MAX - 1),
19798        owner: None,
19799      };
19800      parent.set_owned(dependency);
19801      parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
19802      let _context = TaskDependencyGuard::enter(&parent);
19803      let (sender, receiver) = oneshot::channel();
19804      let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
19805        ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
19806      ));
19807      drop(JoinHandle(JoinHandleInner::Blocking {
19808        receiver,
19809        dependency,
19810      }));
19811      assert_eq!(parent.get_dependency(), None);
19812      return;
19813    }
19814
19815    let output = std::process::Command::new(std::env::current_exe().unwrap())
19816      .arg("--exact")
19817      .arg(
19818        "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
19819      )
19820      .arg("--nocapture")
19821      .env(CHILD_ENV, "1")
19822      .output()
19823      .expect("the subprocess test must start");
19824    assert!(
19825      output.status.success(),
19826      "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
19827      output.status.code(),
19828      String::from_utf8_lossy(&output.stdout),
19829      String::from_utf8_lossy(&output.stderr)
19830    );
19831  }
19832
19833  #[cfg(not(target_family = "wasm"))]
19834  #[test]
19835  fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
19836    // Non-owner blocking-cap invariant: a plain pool-worker runnable driver
19837    // that does NOT own a counted blocking slot and re-enters `block_on`
19838    // must NOT run queued blocking jobs over `max_blocking_tasks` via the
19839    // cooperative over-cap fallback. Two holder blocking jobs saturate the cap;
19840    // two driver tasks then `block_on(spawn_blocking(..))`. If a non-owner
19841    // driver could use the over-cap fallback, the inner jobs would run while
19842    // both holders still occupy slots and push peak active blocking above 2.
19843    // The cap must be honored AND everything must still complete.
19844    use std::sync::{Barrier, mpsc};
19845    use std::time::Duration;
19846
19847    let (done_tx, done_rx) = mpsc::channel();
19848    let peak_out = Arc::new(Mutex::new(None));
19849    let peak_slot = Arc::clone(&peak_out);
19850    let runner = std::thread::spawn(move || {
19851      let metrics = Arc::new(RuntimeMetrics::default());
19852      let options = RuntimeOptions {
19853        flavor: RuntimeFlavor::MultiThread,
19854        worker_threads: 4,
19855        max_blocking_tasks: 2,
19856        thread_name_prefix: "rd1-cap".to_string(),
19857        park_deadline: None,
19858      };
19859      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
19860
19861      let holder_count = 2usize; // saturates max_blocking_tasks
19862      let driver_count = 2usize;
19863
19864      // Holders signal once they hold a counted slot, then wait to be released.
19865      let (held_tx, held_rx) = mpsc::channel();
19866      let release = Arc::new(Barrier::new(holder_count + 1));
19867
19868      let mut holder_handles = Vec::new();
19869      for _ in 0..holder_count {
19870        let held_tx = held_tx.clone();
19871        let release = Arc::clone(&release);
19872        let handle = executor.schedule_blocking(move || {
19873          held_tx.send(()).unwrap();
19874          release.wait();
19875          0usize
19876        });
19877        holder_handles.push(handle);
19878      }
19879      // Wait until both holders hold their slots (cap fully saturated).
19880      for _ in 0..holder_count {
19881        held_rx.recv().unwrap();
19882      }
19883
19884      // Driver tasks: plain runnables (NOT blocking-slot owners) that block_on
19885      // an inner spawn_blocking.
19886      let mut driver_tasks = Vec::new();
19887      for _ in 0..driver_count {
19888        let exec = Arc::clone(&executor);
19889        let body = async move {
19890          let inner_exec = Arc::clone(&exec);
19891          let mut inner = std::pin::pin!(async move {
19892            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19893            assert_eq!(v, 5);
19894          });
19895          exec.block_on(inner.as_mut());
19896        };
19897        let scheduler = Arc::clone(&executor);
19898        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19899        executor.schedule(runnable);
19900        driver_tasks.push(task);
19901      }
19902
19903      // Let the driver tasks reach block_on. They must park until a holder
19904      // frees a slot instead of running their inner jobs over the cap.
19905      std::thread::sleep(Duration::from_millis(300));
19906
19907      // Release the holders so the inner jobs can run within the cap.
19908      release.wait();
19909
19910      for task in driver_tasks {
19911        futures::executor::block_on(task);
19912      }
19913      for handle in holder_handles {
19914        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
19915      }
19916
19917      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
19918      let _ = done_tx.send(());
19919    });
19920
19921    match done_rx.recv_timeout(Duration::from_secs(10)) {
19922      Ok(()) => runner.join().unwrap(),
19923      Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
19924    }
19925    let peak = peak_out.lock().unwrap().expect("peak captured");
19926    assert!(
19927      peak <= 2,
19928      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
19929    );
19930  }
19931
19932  #[cfg(not(target_family = "wasm"))]
19933  #[test]
19934  fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
19935    // Blocking-owner isolation invariant: a counted blocking OWNER that
19936    // re-enters cooperative `block_on` can drive a plain queued runnable via
19937    // `run_one`. That runnable must NOT inherit the owner's over-cap blocking
19938    // privilege. If it re-enters `block_on` awaiting a `spawn_blocking` while
19939    // the cap is saturated, the inner job must wait for a slot instead of
19940    // borrowing the owner's token.
19941    //
19942    // Setup (worker_threads=2, max_blocking=2): owner O (a counted blocking closure)
19943    // holds slot 1; holder H2 holds slot 2 -> cap saturated. O spawns a plain task R
19944    // and drives it via cooperative `block_on`. With only 2 workers and 2 active
19945    // drainers, no replacement drainer can be spawned, so O itself runs R via
19946    // `run_one`. R re-enters `block_on` awaiting an inner `spawn_blocking` J.
19947    // R must not inherit O's slot flag: it parks until H2 frees a slot, so J
19948    // runs within the cap and the peak remains 2. Everything must still
19949    // complete.
19950    use std::sync::{Barrier, mpsc};
19951    use std::time::Duration;
19952
19953    let (done_tx, done_rx) = mpsc::channel();
19954    let peak_out = Arc::new(Mutex::new(None));
19955    let peak_slot = Arc::clone(&peak_out);
19956    let runner = std::thread::spawn(move || {
19957      let metrics = Arc::new(RuntimeMetrics::default());
19958      let options = RuntimeOptions {
19959        flavor: RuntimeFlavor::MultiThread,
19960        worker_threads: 2,
19961        max_blocking_tasks: 2,
19962        thread_name_prefix: "rd1-owner-runnable".to_string(),
19963        park_deadline: None,
19964      };
19965      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
19966
19967      let saturated = Arc::new(Barrier::new(2));
19968      let release = Arc::new(Barrier::new(2));
19969
19970      // Holder H2: holds the second counted slot until released. Not the driver.
19971      let h2_saturated = Arc::clone(&saturated);
19972      let h2_release = Arc::clone(&release);
19973      let h2 = executor.schedule_blocking(move || {
19974        h2_saturated.wait();
19975        h2_release.wait();
19976        0usize
19977      });
19978
19979      // Owner O: a counted blocking closure (slot 1). Once the cap is saturated it
19980      // spawns plain runnable R and drives it cooperatively via `block_on`.
19981      let o_exec = Arc::clone(&executor);
19982      let o_saturated = Arc::clone(&saturated);
19983      let o = executor.schedule_blocking(move || {
19984        o_saturated.wait(); // both slots held -> cap saturated
19985
19986        let r_exec = Arc::clone(&o_exec);
19987        let body = async move {
19988          let inner_exec = Arc::clone(&r_exec);
19989          let mut inner = std::pin::pin!(async move {
19990            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19991            assert_eq!(v, 5);
19992          });
19993          r_exec.block_on(inner.as_mut());
19994        };
19995        let sched = Arc::clone(&o_exec);
19996        let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
19997        o_exec.schedule(runnable);
19998
19999        let mut wait_r = std::pin::pin!(async move {
20000          task.await;
20001        });
20002        o_exec.block_on(wait_r.as_mut());
20003        1usize
20004      });
20005
20006      // Give O time to drive R into its inner `block_on`; R must be parked
20007      // waiting for a slot.
20008      std::thread::sleep(Duration::from_millis(300));
20009      // Release H2 so the inner job can run within the cap.
20010      release.wait();
20011
20012      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
20013      assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
20014
20015      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
20016      let _ = done_tx.send(());
20017    });
20018
20019    match done_rx.recv_timeout(Duration::from_secs(10)) {
20020      Ok(()) => runner.join().unwrap(),
20021      Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
20022    }
20023    let peak = peak_out.lock().unwrap().expect("peak captured");
20024    assert!(
20025      peak <= 2,
20026      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
20027    );
20028  }
20029
20030  #[cfg(not(target_family = "wasm"))]
20031  #[test]
20032  fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
20033    // The over-cap fallback must select the exact pending JoinHandle job, not
20034    // the front of the shared blocking queue.
20035    //
20036    // Setup (worker_threads=2, max_blocking=2): owner O is a counted blocking
20037    // closure (slot 1); holder H holds slot 2 -> cap saturated. An unrelated job
20038    // U is queued ahead of O's descendant D. O re-enters `block_on` awaiting D.
20039    // The exact dependency must skip U and run D. Because U can only run once
20040    // a real slot frees after O completes, D must STRICTLY precede U.
20041    use std::sync::{Barrier, mpsc};
20042
20043    let (done_tx, done_rx) = mpsc::channel();
20044    let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20045    let order_read = Arc::clone(&order_out);
20046    let runner = std::thread::spawn(move || {
20047      let metrics = Arc::new(RuntimeMetrics::default());
20048      let options = RuntimeOptions {
20049        flavor: RuntimeFlavor::MultiThread,
20050        worker_threads: 2,
20051        max_blocking_tasks: 2,
20052        thread_name_prefix: "rd1-unrelated".to_string(),
20053        park_deadline: None,
20054      };
20055      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
20056
20057      // O and H signal once they each hold a counted slot (cap saturated), then
20058      // wait so U is queued before O nests. H stays held until the very end.
20059      let (held_tx, held_rx) = mpsc::channel();
20060      let u_ready = Arc::new(Barrier::new(2)); // O <-> main: U is queued
20061      let h_release = Arc::new(Barrier::new(2)); // H <-> main: end of test
20062
20063      // Holder H: holds slot 2 for the whole test.
20064      let h_held = held_tx.clone();
20065      let h_rel = Arc::clone(&h_release);
20066      let h = executor.schedule_blocking(move || {
20067        h_held.send(()).unwrap();
20068        h_rel.wait();
20069        0usize
20070      });
20071
20072      // Owner O: holds slot 1, then (once U is queued) nests `block_on` awaiting
20073      // its OWN descendant D.
20074      let o_exec = Arc::clone(&executor);
20075      let o_ready = Arc::clone(&u_ready);
20076      let o_order = Arc::clone(&order_out);
20077      let o = executor.schedule_blocking(move || {
20078        held_tx.send(()).unwrap();
20079        o_ready.wait(); // wait until U is queued ahead of D
20080        let inner_exec = Arc::clone(&o_exec);
20081        let d_order = Arc::clone(&o_order);
20082        let mut inner = std::pin::pin!(async move {
20083          let v = inner_exec
20084            .schedule_blocking(move || {
20085              d_order.lock().unwrap().push("D");
20086              5usize
20087            })
20088            .await
20089            .unwrap();
20090          assert_eq!(v, 5);
20091        });
20092        o_exec.block_on(inner.as_mut());
20093        1usize
20094      });
20095
20096      // Wait until BOTH O and H hold their slots -> cap saturated.
20097      held_rx.recv().unwrap();
20098      held_rx.recv().unwrap();
20099
20100      // Queue U ahead of D; it must not be mistaken for the awaited dependency.
20101      let u_order = Arc::clone(&order_out);
20102      let u = executor.schedule_blocking(move || {
20103        u_order.lock().unwrap().push("U");
20104        2usize
20105      });
20106
20107      // Release O to nest now that U is queued ahead of D.
20108      u_ready.wait();
20109
20110      // O makes progress by running D over cap; U only runs after O frees a slot.
20111      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
20112      assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
20113
20114      // Release H and finish.
20115      h_release.wait();
20116      assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
20117
20118      let _ = done_tx.send(());
20119    });
20120
20121    match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
20122      Ok(()) => runner.join().unwrap(),
20123      Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
20124    }
20125    let order = order_read.lock().unwrap().clone();
20126    assert_eq!(
20127      order,
20128      vec!["D", "U"],
20129      "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
20130    );
20131  }
20132
20133  #[cfg(not(target_family = "wasm"))]
20134  #[test]
20135  fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
20136    use std::sync::{Barrier, mpsc};
20137
20138    let opts = RuntimeOptions {
20139      flavor: RuntimeFlavor::MultiThread,
20140      worker_threads: 2,
20141      max_blocking_tasks: 2,
20142      thread_name_prefix: "rd1-scope".to_string(),
20143      park_deadline: None,
20144    };
20145    let exec1 =
20146      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20147    let exec2 =
20148      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20149    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
20150
20151    let holder_count = opts.max_blocking_tasks;
20152    let (held_tx, held_rx) = mpsc::channel();
20153    let release = Arc::new(Barrier::new(holder_count + 1));
20154    let mut holder_handles = Vec::new();
20155    for _ in 0..holder_count {
20156      let held_tx = held_tx.clone();
20157      let release = Arc::clone(&release);
20158      holder_handles.push(exec2.schedule_blocking(move || {
20159        held_tx.send(()).unwrap();
20160        release.wait();
20161        0usize
20162      }));
20163    }
20164    for _ in 0..holder_count {
20165      held_rx.recv().unwrap();
20166    }
20167    assert_eq!(
20168      exec2.active_blocking.load(Ordering::Acquire),
20169      opts.max_blocking_tasks
20170    );
20171
20172    let exact = BlockingJobId(u64::MAX);
20173    let ran = Arc::new(AtomicBool::new(false));
20174    let ran_job = Arc::clone(&ran);
20175    exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
20176      id: exact,
20177      run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20178    });
20179    let dependency = TaskDependency::default();
20180
20181    let token1 = exec1.fresh_owner_token();
20182    {
20183      let _owner = BlockingOwnerGuard::enter(Some(token1));
20184      dependency.set_owned(BlockingDependency {
20185        job: exact,
20186        owner: Some(token1),
20187      });
20188      assert!(
20189        !exec2.try_owned_blocking_over_cap(&dependency),
20190        "a foreign-executor ambient token must fail the executor gate"
20191      );
20192    }
20193
20194    let token2 = exec2.fresh_owner_token();
20195    {
20196      let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
20197      let _owner = BlockingOwnerGuard::enter(Some(token2));
20198      dependency.set_owned(BlockingDependency {
20199        job: BlockingJobId(u64::MAX - 1),
20200        owner: Some(token2),
20201      });
20202      assert!(
20203        !exec2.try_owned_blocking_over_cap(&dependency),
20204        "a foreign or unrelated dependency id must fail the exact-job gate"
20205      );
20206      dependency.set_owned(BlockingDependency {
20207        job: BlockingJobId(exact.0 - 2),
20208        owner: Some(token2),
20209      });
20210      assert!(
20211        !exec2.try_owned_blocking_over_cap(&dependency),
20212        "a different job id must not consume the queued dependency"
20213      );
20214      dependency.set_owned(BlockingDependency {
20215        job: exact,
20216        owner: Some(token2),
20217      });
20218      assert!(
20219        exec2.try_owned_blocking_over_cap(&dependency),
20220        "the exact awaited job must run over cap"
20221      );
20222    }
20223    assert!(
20224      ran.load(Ordering::SeqCst),
20225      "the exact dependency must have run"
20226    );
20227
20228    release.wait();
20229    for handle in holder_handles {
20230      assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
20231    }
20232  }
20233
20234  #[cfg(not(target_family = "wasm"))]
20235  #[test]
20236  fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
20237    let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
20238    let first_owner = executor.fresh_owner_token();
20239    let second_owner = executor.fresh_owner_token();
20240    let _first_owner =
20241      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
20242    let _second_owner =
20243      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
20244    let exact = BlockingJobId(u64::MAX - 30);
20245    let dependency = TaskDependency::default();
20246    dependency.set_owned(BlockingDependency {
20247      job: exact,
20248      owner: None,
20249    });
20250    let ran = Arc::new(AtomicBool::new(false));
20251    let ran_job = Arc::clone(&ran);
20252    executor
20253      .blocking_queue
20254      .lock()
20255      .unwrap()
20256      .push(QueuedBlocking {
20257        id: exact,
20258        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20259      });
20260
20261    assert!(
20262      !executor.try_owned_blocking_over_cap(&dependency),
20263      "an untagged driver must not choose between multiple active owner frames"
20264    );
20265    assert!(!ran.load(Ordering::SeqCst));
20266    let queued = executor.blocking_queue.lock().unwrap().remove(exact);
20267    assert!(
20268      queued.is_some(),
20269      "ambiguous ownership must leave the exact job queued"
20270    );
20271  }
20272
20273  #[cfg(not(target_family = "wasm"))]
20274  #[test]
20275  fn owner_lending_claims_live_dependency_not_stale_snapshot() {
20276    let executor = multi_thread_executor(2, None, "live-parked-dependency");
20277    let owner = executor.fresh_owner_token();
20278    let dependency = TaskDependency::new(u64::MAX, Some(owner));
20279    let stale = BlockingJobId(u64::MAX - 10);
20280    let live = BlockingJobId(u64::MAX - 9);
20281    dependency.set_owned(BlockingDependency {
20282      job: stale,
20283      owner: Some(owner),
20284    });
20285    dependency.begin_poll();
20286    dependency.set_owned(BlockingDependency {
20287      job: live,
20288      owner: Some(owner),
20289    });
20290
20291    let stale_ran = Arc::new(AtomicBool::new(false));
20292    let stale_job_ran = Arc::clone(&stale_ran);
20293    let live_ran = Arc::new(AtomicBool::new(false));
20294    let live_job_ran = Arc::clone(&live_ran);
20295    {
20296      let mut queue = executor
20297        .blocking_queue
20298        .lock()
20299        .unwrap_or_else(std::sync::PoisonError::into_inner);
20300      queue.push(QueuedBlocking {
20301        id: stale,
20302        run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
20303      });
20304      queue.push(QueuedBlocking {
20305        id: live,
20306        run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
20307      });
20308    }
20309
20310    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20311    let _owner = BlockingOwnerGuard::enter(Some(owner));
20312    assert!(
20313      executor.try_owned_blocking_over_cap(&dependency),
20314      "the owner lane must claim the currently published dependency"
20315    );
20316    assert!(
20317      live_ran.load(Ordering::SeqCst),
20318      "the live dependency must run"
20319    );
20320    assert!(
20321      !stale_ran.load(Ordering::SeqCst),
20322      "the stale registered id must remain queued"
20323    );
20324    let remaining = executor
20325      .blocking_queue
20326      .lock()
20327      .unwrap_or_else(std::sync::PoisonError::into_inner)
20328      .jobs
20329      .keys()
20330      .copied()
20331      .collect::<Vec<_>>();
20332    assert_eq!(remaining, vec![stale]);
20333  }
20334
20335  #[cfg(not(target_family = "wasm"))]
20336  #[test]
20337  fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
20338    let executor = multi_thread_executor(2, None, "cancelled-child-publication");
20339    let owner = executor.fresh_owner_token();
20340    let child = TaskDependency::new_task(u64::MAX);
20341    let parent = TaskDependency::new(u64::MAX, Some(owner));
20342    let exact = BlockingDependency {
20343      job: BlockingJobId(u64::MAX - 31),
20344      owner: Some(owner),
20345    };
20346
20347    child.set_owned(exact);
20348    let stale = child
20349      .get()
20350      .expect("the child must publish its direct dependency");
20351    child.begin_poll();
20352    parent.set_borrowed(&stale);
20353
20354    let ran = Arc::new(AtomicBool::new(false));
20355    let ran_job = Arc::clone(&ran);
20356    executor
20357      .blocking_queue
20358      .lock()
20359      .unwrap()
20360      .push(QueuedBlocking {
20361        id: exact.job,
20362        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20363      });
20364    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20365    let _owner = BlockingOwnerGuard::enter(Some(owner));
20366    assert!(
20367      !executor.try_owned_blocking_over_cap(&parent),
20368      "a publication copied before child cancellation must remain cancelled after parent install"
20369    );
20370    assert!(!ran.load(Ordering::SeqCst));
20371
20372    child.set_owned(exact);
20373    parent.set_borrowed(
20374      &child
20375        .get()
20376        .expect("the replacement publication must be live"),
20377    );
20378    assert!(
20379      executor.try_owned_blocking_over_cap(&parent),
20380      "a fresh child publication must still authorize its exact queued job"
20381    );
20382    assert!(ran.load(Ordering::SeqCst));
20383  }
20384
20385  #[cfg(not(target_family = "wasm"))]
20386  #[test]
20387  fn intermediate_repoll_does_not_cancel_child_source_claim() {
20388    let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
20389    let owner = executor.fresh_owner_token();
20390    let child = TaskDependency::new_task(u64::MAX);
20391    let intermediate = TaskDependency::new_task(u64::MAX);
20392    let parent = TaskDependency::new(u64::MAX, Some(owner));
20393    let exact = BlockingDependency {
20394      job: BlockingJobId(u64::MAX - 43),
20395      owner: Some(owner),
20396    };
20397
20398    child.set_owned(exact);
20399    intermediate.set_borrowed(&child.get().expect("the child source must be live"));
20400    parent.set_borrowed(
20401      &intermediate
20402        .get()
20403        .expect("the intermediate publication must be live"),
20404    );
20405
20406    intermediate.begin_poll();
20407    assert!(
20408      child.get().is_some(),
20409      "withdrawing an intermediate hop must not cancel the child's source claim"
20410    );
20411    assert!(
20412      parent.get().is_none(),
20413      "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
20414    );
20415
20416    intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
20417    parent.set_borrowed(
20418      &intermediate
20419        .get()
20420        .expect("the intermediate must republish the still-live source"),
20421    );
20422    let ran = Arc::new(AtomicBool::new(false));
20423    let ran_job = Arc::clone(&ran);
20424    executor
20425      .blocking_queue
20426      .lock()
20427      .unwrap()
20428      .push(QueuedBlocking {
20429        id: exact.job,
20430        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20431      });
20432
20433    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20434    let _owner = BlockingOwnerGuard::enter(Some(owner));
20435    assert!(
20436      executor.try_owned_blocking_over_cap(&parent),
20437      "a fresh chain through the repolled intermediate must retain the exact source claim"
20438    );
20439    assert!(ran.load(Ordering::SeqCst));
20440  }
20441
20442  #[cfg(not(target_family = "wasm"))]
20443  #[test]
20444  fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
20445    use std::sync::mpsc;
20446
20447    let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
20448    let owner = executor.fresh_owner_token();
20449    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20450    let child = Arc::new(TaskDependency::new_task(u64::MAX));
20451    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20452    let exact = BlockingDependency {
20453      job: BlockingJobId(u64::MAX - 44),
20454      owner: Some(owner),
20455    };
20456    child.set_owned(exact);
20457    parent.set_borrowed(&child.get().expect("the child source must be live"));
20458
20459    let ran = Arc::new(AtomicBool::new(false));
20460    let ran_job = Arc::clone(&ran);
20461    executor
20462      .blocking_queue
20463      .lock()
20464      .unwrap()
20465      .push(QueuedBlocking {
20466        id: exact.job,
20467        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20468      });
20469
20470    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
20471    let (release_claim_tx, release_claim_rx) = mpsc::channel();
20472    let claimant_executor = Arc::clone(&executor);
20473    let claimant_parent = Arc::clone(&parent);
20474    let claimant = std::thread::spawn(move || {
20475      DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
20476        *slot.borrow_mut() = Some(Box::new(move || {
20477          claim_entered_tx.send(()).unwrap();
20478          release_claim_rx.recv().unwrap();
20479        }));
20480      });
20481      let _owner = BlockingOwnerGuard::enter(Some(owner));
20482      claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
20483    });
20484    claim_entered_rx
20485      .recv_timeout(Duration::from_secs(2))
20486      .expect("the exact claimant must hold the shared transition lock");
20487
20488    let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
20489    let withdrawing_child = Arc::clone(&child);
20490    let withdrawal = std::thread::spawn(move || {
20491      withdrawing_child.begin_poll();
20492      withdrawn_tx.send(()).unwrap();
20493    });
20494    let withdrew_before_claim = withdrawn_rx
20495      .recv_timeout(Duration::from_millis(200))
20496      .is_ok();
20497    release_claim_tx.send(()).unwrap();
20498
20499    assert!(
20500      claimant.join().unwrap(),
20501      "the claim that linearized first must execute the exact job"
20502    );
20503    withdrawal.join().unwrap();
20504    assert!(
20505      !withdrew_before_claim,
20506      "source withdrawal must not interleave after claim validation but before claim commit"
20507    );
20508    assert!(ran.load(Ordering::SeqCst));
20509  }
20510
20511  #[cfg(not(target_family = "wasm"))]
20512  #[test]
20513  fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
20514    // Pool-worker-marker scoping invariant: `block_on` may take the cooperative
20515    // (queue-driving) branch ONLY for a re-entrant call OF THE SAME
20516    // executor whose worker set the marker. A worker of executor A that re-enters
20517    // `block_on` on executor B must take the PARKING branch instead -- it owns
20518    // none of B's accounting, so driving B's queue would make it an unaccounted
20519    // within-cap driver. This mirrors the `BlockingOwnerToken.executor_id` gate.
20520    //
20521    // Fully deterministic, no sleeps/threads: we set the ambient pool-worker
20522    // marker to exec1's id by entering the REAL `OnPoolWorkerGuard`, then call
20523    // `block_on` on each executor with the SAME probe shape and observe whether
20524    // the cooperative `run_one` driver actually ran a queued runnable.
20525    //
20526    // The probe future returns `Pending` once (self-waking so the parking branch
20527    // also makes progress) then `Ready`, guaranteeing the cooperative loop reaches
20528    // `run_one` at least once before the future completes. A single runnable is
20529    // pushed DIRECTLY onto the target executor's queue (bypassing `schedule`, so
20530    // no background drainer is ever spawned to steal it). After `block_on`
20531    // returns: the cooperative branch will have driven that runnable
20532    // (`runnable_polls == 1`, queue empty); the parking branch leaves it untouched
20533    // (`runnable_polls == 0`, still queued).
20534    struct PendThenReady {
20535      polls: usize,
20536    }
20537    impl Future for PendThenReady {
20538      type Output = ();
20539      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
20540        self.polls += 1;
20541        if self.polls >= 2 {
20542          Poll::Ready(())
20543        } else {
20544          // Self-wake so the parking branch (`futures::executor::block_on`)
20545          // re-polls to completion without any external waker; the cooperative
20546          // branch re-polls unconditionally each loop iteration regardless.
20547          cx.waker().wake_by_ref();
20548          Poll::Pending
20549        }
20550      }
20551    }
20552
20553    // Push one already-resolvable runnable directly onto `target`'s queue (no
20554    // `schedule`, so no drainer is spawned) and detach its task.
20555    fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
20556      let scheduler = Arc::clone(target);
20557      let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
20558      target.queue.lock().unwrap().push_back(runnable);
20559      task.detach();
20560    }
20561
20562    let opts = RuntimeOptions {
20563      flavor: RuntimeFlavor::MultiThread,
20564      worker_threads: 2,
20565      max_blocking_tasks: 2,
20566      thread_name_prefix: "rd-onpool-scope".to_string(),
20567      park_deadline: None,
20568    };
20569    let exec1 =
20570      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20571    let exec2 =
20572      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20573    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
20574
20575    // Simulate being on exec1's pool worker for the whole scenario.
20576    let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
20577
20578    // FOREIGN call: on exec1's worker, drive exec2.block_on. The marker
20579    // (Some(exec1.id)) does NOT equal exec2.id, so exec2 must PARK and never
20580    // touch its queue.
20581    enqueue_runnable(&exec2);
20582    {
20583      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
20584      exec2.block_on(probe.as_mut());
20585    }
20586    assert_eq!(
20587      exec2.metrics.runnable_polls.load(Ordering::Relaxed),
20588      0,
20589      "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
20590    );
20591    assert_eq!(
20592      exec2.queue.lock().unwrap().len(),
20593      1,
20594      "the foreign call must leave exec2's runnable untouched on its queue"
20595    );
20596
20597    // CONTROL call: on exec1's worker, drive exec1.block_on. The marker equals
20598    // exec1.id, so exec1 takes the COOPERATIVE branch and drives its queued
20599    // runnable via `run_one`.
20600    enqueue_runnable(&exec1);
20601    {
20602      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
20603      exec1.block_on(probe.as_mut());
20604    }
20605    assert_eq!(
20606      exec1.metrics.runnable_polls.load(Ordering::Relaxed),
20607      1,
20608      "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
20609    );
20610    assert!(
20611      exec1.queue.lock().unwrap().is_empty(),
20612      "the cooperative branch must have drained exec1's queued runnable"
20613    );
20614
20615    // Tidy: drop the untouched exec2 runnable so its task does not leak a waker.
20616    exec2.queue.lock().unwrap().clear();
20617  }
20618
20619  #[cfg(not(target_family = "wasm"))]
20620  #[test]
20621  fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
20622    // A long-lived blocking consumer may consume the entire configured
20623    // blocking allowance, but it must not consume the reserve lane required
20624    // by the async producers that feed it.
20625    use std::sync::mpsc;
20626    use std::time::Duration;
20627
20628    const PRODUCER_COUNT: usize = 8;
20629
20630    fn schedule_pool_workload(
20631      executor: &Arc<MultiThreadExecutor>,
20632      tx: &mpsc::Sender<Option<usize>>,
20633      count: usize,
20634    ) {
20635      for i in 0..count {
20636        let tx = tx.clone();
20637        let scheduler = Arc::clone(executor);
20638        let (runnable, task) =
20639          async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
20640            scheduler.schedule(r);
20641          });
20642        executor.schedule(runnable);
20643        task.detach();
20644      }
20645      let tx = tx.clone();
20646      let scheduler = Arc::clone(executor);
20647      let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
20648        scheduler.schedule(r)
20649      });
20650      executor.schedule(runnable);
20651      task.detach();
20652    }
20653
20654    let expected_sum: usize = (0..PRODUCER_COUNT).sum();
20655    let (done_tx, done_rx) = mpsc::sync_channel(1);
20656    let runner = std::thread::spawn(move || {
20657      let options = RuntimeOptions {
20658        flavor: RuntimeFlavor::MultiThread,
20659        worker_threads: 1,
20660        max_blocking_tasks: 1,
20661        thread_name_prefix: "rd2-pre-fix".to_string(),
20662        park_deadline: None,
20663      }
20664      .validate()
20665      .expect("production MultiThread options must provide a reserve lane");
20666      let executor =
20667        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20668
20669      let (tx, rx) = mpsc::channel::<Option<usize>>();
20670      let (started_tx, started_rx) = mpsc::sync_channel(1);
20671      let handle = executor.schedule_blocking(move || {
20672        started_tx.send(()).unwrap();
20673        let mut sum = 0usize;
20674        while let Ok(msg) = rx.recv() {
20675          match msg {
20676            Some(value) => sum += value,
20677            None => break,
20678          }
20679        }
20680        sum
20681      });
20682      started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
20683      schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
20684      let sum = futures::executor::block_on(handle).unwrap();
20685      done_tx.send(sum).unwrap();
20686    });
20687
20688    match done_rx.recv_timeout(Duration::from_secs(10)) {
20689      Ok(sum) => {
20690        runner.join().unwrap();
20691        assert_eq!(sum, expected_sum);
20692      }
20693      Err(error) => {
20694        panic!("a saturated blocking lane starved the async producers that feed it ({error})")
20695      }
20696    }
20697  }
20698
20699  #[test]
20700  fn current_thread_blocking_work_completes_inline() {
20701    let metrics = Arc::new(RuntimeMetrics::default());
20702    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
20703      CurrentThreadExecutor::new(Arc::clone(&metrics)),
20704    )));
20705    let registration = backend.work.try_register_work().unwrap();
20706
20707    let value =
20708      futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
20709        .expect("blocking task should complete");
20710
20711    assert_eq!(value, 7);
20712    assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
20713    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
20714  }
20715
20716  #[cfg(not(target_family = "wasm"))]
20717  #[test]
20718  fn current_thread_blocking_work_obeys_single_lane_across_callers() {
20719    use std::sync::mpsc;
20720
20721    let controller = Arc::new(current_thread_controller(
20722      "current-thread-blocking-admission",
20723    ));
20724    let backend = controller.backend();
20725    let (first_started_tx, first_started_rx) = mpsc::channel();
20726    let (release_first_tx, release_first_rx) = mpsc::channel();
20727    let first_controller = Arc::clone(&controller);
20728    let first = std::thread::spawn(move || {
20729      let handle = first_controller
20730        .try_spawn_blocking(move || {
20731          first_started_tx.send(()).unwrap();
20732          release_first_rx.recv().unwrap();
20733          1usize
20734        })
20735        .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
20736      futures::executor::block_on(handle).unwrap()
20737    });
20738    first_started_rx
20739      .recv_timeout(Duration::from_secs(1))
20740      .unwrap();
20741
20742    let (second_started_tx, second_started_rx) = mpsc::channel();
20743    let second_controller = Arc::clone(&controller);
20744    let second = std::thread::spawn(move || {
20745      let handle = second_controller
20746        .try_spawn_blocking(move || {
20747          second_started_tx.send(()).unwrap();
20748          2usize
20749        })
20750        .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
20751      futures::executor::block_on(handle).unwrap()
20752    });
20753
20754    wait_until("both CurrentThread blocking calls to be accepted", || {
20755      backend
20756        .work
20757        .state
20758        .lock()
20759        .unwrap_or_else(std::sync::PoisonError::into_inner)
20760        .active
20761        == 2
20762    });
20763    assert!(
20764      second_started_rx
20765        .recv_timeout(Duration::from_millis(100))
20766        .is_err(),
20767      "CurrentThread must not execute two blocking closures concurrently"
20768    );
20769
20770    release_first_tx.send(()).unwrap();
20771    second_started_rx
20772      .recv_timeout(Duration::from_secs(1))
20773      .expect("the waiting blocking call must acquire the released lane");
20774    assert_eq!(first.join().unwrap(), 1);
20775    assert_eq!(second.join().unwrap(), 2);
20776    assert_eq!(
20777      controller
20778        .metrics
20779        .blocking_tasks_started
20780        .load(Ordering::Relaxed),
20781      2
20782    );
20783    assert_eq!(
20784      controller
20785        .metrics
20786        .blocking_tasks_completed
20787        .load(Ordering::Relaxed),
20788      2
20789    );
20790    assert_eq!(
20791      controller
20792        .metrics
20793        .active_blocking_tasks
20794        .load(Ordering::Relaxed),
20795      0
20796    );
20797    assert_eq!(
20798      controller
20799        .metrics
20800        .max_active_blocking_tasks
20801        .load(Ordering::Relaxed),
20802      1,
20803      "the public active-lane metric must remain within CurrentThread's reported capacity"
20804    );
20805    controller.shutdown().unwrap();
20806  }
20807
20808  #[cfg(not(target_family = "wasm"))]
20809  #[test]
20810  fn current_thread_nested_blocking_reuses_the_active_lane() {
20811    use std::sync::mpsc;
20812
20813    let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
20814    let (done_tx, done_rx) = mpsc::channel();
20815    let runner_controller = Arc::clone(&controller);
20816    let runner = std::thread::spawn(move || {
20817      let nested_controller = Arc::clone(&runner_controller);
20818      let outer = runner_controller
20819        .try_spawn_blocking(move || {
20820          let inner = nested_controller
20821            .try_spawn_blocking(|| 41usize)
20822            .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
20823          futures::executor::block_on(inner).unwrap() + 1
20824        })
20825        .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
20826      done_tx
20827        .send(futures::executor::block_on(outer).unwrap())
20828        .unwrap();
20829    });
20830
20831    assert_eq!(
20832      done_rx
20833        .recv_timeout(Duration::from_secs(2))
20834        .expect("a nested CurrentThread blocking call must not wait on its own lane"),
20835      42
20836    );
20837    runner.join().unwrap();
20838    assert_eq!(
20839      controller
20840        .metrics
20841        .blocking_tasks_started
20842        .load(Ordering::Relaxed),
20843      2
20844    );
20845    assert_eq!(
20846      controller
20847        .metrics
20848        .blocking_tasks_completed
20849        .load(Ordering::Relaxed),
20850      2
20851    );
20852    assert_eq!(
20853      controller
20854        .metrics
20855        .active_blocking_tasks
20856        .load(Ordering::Relaxed),
20857      0
20858    );
20859    assert_eq!(
20860      controller
20861        .metrics
20862        .max_active_blocking_tasks
20863        .load(Ordering::Relaxed),
20864      1,
20865      "nested work borrows the outer lane instead of reporting a second physical lane"
20866    );
20867    controller.shutdown().unwrap();
20868  }
20869
20870  #[cfg(not(target_family = "wasm"))]
20871  #[test]
20872  fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
20873    use std::sync::mpsc;
20874
20875    struct HotFuture {
20876      stop: Arc<AtomicBool>,
20877    }
20878
20879    impl Future for HotFuture {
20880      type Output = ();
20881
20882      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
20883        if self.stop.load(Ordering::SeqCst) {
20884          Poll::Ready(())
20885        } else {
20886          cx.waker().wake_by_ref();
20887          Poll::Pending
20888        }
20889      }
20890    }
20891
20892    let stop = Arc::new(AtomicBool::new(false));
20893    let (ready_tx, ready_rx) = mpsc::channel();
20894    let (exact_tx, exact_rx) = mpsc::channel();
20895    let (done_tx, done_rx) = mpsc::channel();
20896    let runner_stop = Arc::clone(&stop);
20897    let runner = std::thread::spawn(move || {
20898      let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
20899        RuntimeMetrics::default(),
20900      )));
20901      let owner = executor.fresh_owner_token();
20902      let job = BlockingJobId(u64::MAX - 70);
20903      let (result_tx, result_rx) = oneshot::channel();
20904      {
20905        let mut state = executor
20906          .blocking_admission
20907          .state
20908          .lock()
20909          .unwrap_or_else(std::sync::PoisonError::into_inner);
20910        state.owner = Some(owner);
20911        let exact_stop = Arc::clone(&runner_stop);
20912        state.queue.push(QueuedBlocking {
20913          id: job,
20914          run: Box::new(move || {
20915            exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
20916            let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
20917          }),
20918        });
20919      }
20920      let exact = JoinHandle(JoinHandleInner::Blocking {
20921        receiver: result_rx,
20922        dependency: BlockingDependency {
20923          job,
20924          owner: Some(owner),
20925        },
20926      });
20927      let _owner = executor.enter_blocking_owner(owner, false);
20928
20929      let scheduler = Arc::clone(&executor);
20930      let hot_stop = Arc::clone(&runner_stop);
20931      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
20932        scheduler.schedule(runnable);
20933      });
20934      executor.schedule(runnable);
20935      ready_tx.send(()).unwrap();
20936
20937      let mut value = None;
20938      {
20939        let mut future = std::pin::pin!(async {
20940          value = Some(exact.await.unwrap());
20941          hot_task.await;
20942        });
20943        assert_eq!(
20944          executor.block_on(future.as_mut()),
20945          BlockOnOutcome::Completed
20946        );
20947      }
20948      executor
20949        .blocking_admission
20950        .state
20951        .lock()
20952        .unwrap_or_else(std::sync::PoisonError::into_inner)
20953        .owner = None;
20954      done_tx.send(value.unwrap()).unwrap();
20955    });
20956
20957    ready_rx
20958      .recv_timeout(Duration::from_secs(2))
20959      .expect("the fairness driver must start");
20960    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
20961    stop.store(true, Ordering::SeqCst);
20962    assert_eq!(
20963      done_rx
20964        .recv_timeout(Duration::from_secs(2))
20965        .expect("the CurrentThread driver must retire"),
20966      41
20967    );
20968    runner.join().unwrap();
20969    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
20970    assert_eq!(
20971      exact_before_stop,
20972      Some(true),
20973      "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
20974    );
20975  }
20976
20977  #[cfg(not(target_family = "wasm"))]
20978  #[test]
20979  fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
20980    use std::sync::mpsc;
20981
20982    let controller = Arc::new(current_thread_controller(
20983      "current-thread-cross-driver-blocking",
20984    ));
20985    let backend = controller.backend();
20986    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
20987      panic!("configured CurrentThread backend must create a CurrentThread executor");
20988    };
20989    let executor = Arc::clone(executor);
20990    drop(backend);
20991
20992    let (release_driver_tx, release_driver_rx) = oneshot::channel();
20993    let (driver_started_tx, driver_started_rx) = mpsc::channel();
20994    let driver_controller = Arc::clone(&controller);
20995    let driver = std::thread::spawn(move || {
20996      driver_started_tx.send(std::thread::current().id()).unwrap();
20997      block_on_with_controller(&driver_controller, async {
20998        release_driver_rx
20999          .await
21000          .expect("driver release sender must remain alive");
21001      });
21002    });
21003    let driver_thread = driver_started_rx
21004      .recv_timeout(Duration::from_secs(1))
21005      .unwrap();
21006    wait_until("the secondary CurrentThread driver to park", || {
21007      executor
21008        .stop
21009        .parkers
21010        .lock()
21011        .unwrap_or_else(std::sync::PoisonError::into_inner)
21012        .iter()
21013        .filter_map(Weak::upgrade)
21014        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
21015    });
21016
21017    let (child_polled_tx, child_polled_rx) = mpsc::channel();
21018    let (release_child_tx, release_child_rx) = mpsc::channel();
21019    let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
21020    let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
21021    let (owner_done_tx, owner_done_rx) = mpsc::channel();
21022    let owner_controller = Arc::clone(&controller);
21023    let owner = std::thread::spawn(move || {
21024      let owner_thread = std::thread::current().id();
21025      let await_controller = Arc::clone(&owner_controller);
21026      let child_controller = Arc::clone(&owner_controller);
21027      let outer = owner_controller
21028        .try_spawn_blocking(move || {
21029          let spawn_controller = Arc::clone(&child_controller);
21030          let child = spawn_controller
21031            .try_spawn(async move {
21032              child_polled_tx.send(std::thread::current().id()).unwrap();
21033              release_child_rx.recv().unwrap();
21034              child_controller
21035                .try_spawn_blocking(move || {
21036                  blocking_ran_tx.send(std::thread::current().id()).unwrap();
21037                  41usize
21038                })
21039                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
21040                .await
21041                .unwrap()
21042                + 1
21043            })
21044            .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
21045
21046          assert_eq!(
21047            child_polled_rx
21048              .recv_timeout(Duration::from_secs(1))
21049              .unwrap(),
21050            driver_thread,
21051            "the secondary driver must own the child poll that reaches blocking admission"
21052          );
21053          owner_awaiting_tx.send(owner_thread).unwrap();
21054          block_on_with_controller(&await_controller, child).unwrap()
21055        })
21056        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
21057      owner_done_tx
21058        .send(futures::executor::block_on(outer))
21059        .unwrap();
21060    });
21061
21062    let owner_thread = owner_awaiting_rx
21063      .recv_timeout(Duration::from_secs(1))
21064      .unwrap();
21065    wait_until(
21066      "the blocking owner to park while awaiting the cross-driver child",
21067      || {
21068        executor
21069          .stop
21070          .parkers
21071          .lock()
21072          .unwrap_or_else(std::sync::PoisonError::into_inner)
21073          .iter()
21074          .filter_map(Weak::upgrade)
21075          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
21076      },
21077    );
21078    release_child_tx.send(()).unwrap();
21079
21080    assert_eq!(
21081      blocking_ran_rx
21082        .recv_timeout(Duration::from_secs(2))
21083        .unwrap(),
21084      owner_thread,
21085      "the exact descendant must be serviced by the lane-owning driver"
21086    );
21087    assert_eq!(
21088      owner_done_rx
21089        .recv_timeout(Duration::from_secs(2))
21090        .expect("cross-driver blocking dependency must not deadlock")
21091        .unwrap(),
21092      42
21093    );
21094    release_driver_tx.send(()).unwrap();
21095    owner.join().unwrap();
21096    driver.join().unwrap();
21097    controller.shutdown().unwrap();
21098    assert_eq!(
21099      controller
21100        .metrics
21101        .blocking_tasks_started
21102        .load(Ordering::Relaxed),
21103      2
21104    );
21105    assert_eq!(
21106      controller
21107        .metrics
21108        .blocking_tasks_completed
21109        .load(Ordering::Relaxed),
21110      2
21111    );
21112    assert_eq!(
21113      controller
21114        .metrics
21115        .active_blocking_tasks
21116        .load(Ordering::Relaxed),
21117      0
21118    );
21119    assert_eq!(
21120      controller
21121        .metrics
21122        .max_active_blocking_tasks
21123        .load(Ordering::Relaxed),
21124      1,
21125      "cross-driver dependency service must reuse the owner's physical lane"
21126    );
21127  }
21128
21129  #[cfg(not(target_family = "wasm"))]
21130  #[test]
21131  fn current_thread_blocking_admission_rechecks_stop_under_lock() {
21132    use std::sync::mpsc;
21133
21134    let controller = Arc::new(current_thread_controller(
21135      "current-thread-blocking-stop-race",
21136    ));
21137    let backend = controller.backend();
21138    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
21139      panic!("configured CurrentThread backend must create a CurrentThread executor");
21140    };
21141    let executor = Arc::clone(executor);
21142    drop(backend);
21143
21144    let ran = Arc::new(AtomicBool::new(false));
21145    let blocking_ran = Arc::clone(&ran);
21146    let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
21147    let (release_admission_tx, release_admission_rx) = mpsc::channel();
21148    let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
21149    let submit_controller = Arc::clone(&controller);
21150    let submitter = std::thread::spawn(move || {
21151      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21152        *slot.borrow_mut() = Some(Box::new(move || {
21153          admission_locked_tx.send(()).unwrap();
21154          release_admission_rx.recv().unwrap();
21155        }));
21156      });
21157      let handle = submit_controller
21158        .try_spawn_blocking(move || {
21159          blocking_ran.store(true, Ordering::SeqCst);
21160        })
21161        .unwrap_or_else(|_| {
21162          panic!("the submission linearized before shutdown and must be accepted")
21163        });
21164      blocking_result_tx
21165        .send(futures::executor::block_on(handle))
21166        .unwrap();
21167    });
21168    admission_locked_rx
21169      .recv_timeout(Duration::from_secs(2))
21170      .expect("the blocking submitter must pause while holding the admission lock");
21171
21172    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
21173    let shutdown_controller = Arc::clone(&controller);
21174    let shutdown = std::thread::spawn(move || {
21175      shutdown_result_tx
21176        .send(shutdown_controller.shutdown())
21177        .unwrap();
21178    });
21179    wait_until("CurrentThread shutdown to publish generation stop", || {
21180      executor.stop.is_stopping()
21181    });
21182    release_admission_tx.send(()).unwrap();
21183
21184    let blocking_error = blocking_result_rx
21185      .recv_timeout(Duration::from_secs(2))
21186      .expect("the admitted blocking handle must resolve")
21187      .expect_err("stop publication must reject the blocking lane claim");
21188    assert_eq!(
21189      blocking_error.to_string(),
21190      "the async runtime stopped before the blocking task could start"
21191    );
21192    assert!(
21193      !ran.load(Ordering::SeqCst),
21194      "blocking work must not start after stop publication"
21195    );
21196    shutdown_result_rx
21197      .recv_timeout(Duration::from_secs(2))
21198      .expect("shutdown must finish after the rejected admission retires")
21199      .unwrap();
21200    submitter.join().unwrap();
21201    shutdown.join().unwrap();
21202  }
21203
21204  #[cfg(not(target_family = "wasm"))]
21205  #[test]
21206  fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
21207    use std::sync::mpsc;
21208
21209    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21210      RuntimeMetrics::default(),
21211    )));
21212    let ran = Arc::new(AtomicBool::new(false));
21213    let job_ran = Arc::clone(&ran);
21214    {
21215      let mut state = executor
21216        .blocking_admission
21217        .state
21218        .lock()
21219        .unwrap_or_else(std::sync::PoisonError::into_inner);
21220      state.queue.push(QueuedBlocking {
21221        id: BlockingJobId(u64::MAX - 71),
21222        run: Box::new(move || {
21223          job_ran.store(true, Ordering::SeqCst);
21224        }),
21225      });
21226    }
21227
21228    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21229    let (release_claim_tx, release_claim_rx) = mpsc::channel();
21230    let (claim_result_tx, claim_result_rx) = mpsc::channel();
21231    let claim_executor = Arc::clone(&executor);
21232    let claimant = std::thread::spawn(move || {
21233      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21234        *slot.borrow_mut() = Some(Box::new(move || {
21235          claim_locked_tx.send(()).unwrap();
21236          release_claim_rx.recv().unwrap();
21237        }));
21238      });
21239      claim_result_tx
21240        .send(claim_executor.run_one_blocking())
21241        .unwrap();
21242    });
21243    claim_locked_rx
21244      .recv_timeout(Duration::from_secs(2))
21245      .expect("the FIFO claimant must pause while holding the admission lock");
21246
21247    executor.stop.begin_shutdown();
21248    release_claim_tx.send(()).unwrap();
21249    assert!(
21250      !claim_result_rx
21251        .recv_timeout(Duration::from_secs(2))
21252        .expect("the stopped FIFO claim must finish"),
21253      "stop publication must reject the queued FIFO claim"
21254    );
21255    claimant.join().unwrap();
21256    assert!(
21257      !ran.load(Ordering::SeqCst),
21258      "queued FIFO work must not start after stop publication"
21259    );
21260    assert!(
21261      !executor
21262        .blocking_admission
21263        .state
21264        .lock()
21265        .unwrap_or_else(std::sync::PoisonError::into_inner)
21266        .queue
21267        .is_empty(),
21268      "the rejected FIFO job must remain queued for shutdown cancellation"
21269    );
21270
21271    executor.close_blocking_admission();
21272    assert!(
21273      executor
21274        .blocking_admission
21275        .state
21276        .lock()
21277        .unwrap_or_else(std::sync::PoisonError::into_inner)
21278        .queue
21279        .is_empty()
21280    );
21281  }
21282
21283  #[cfg(not(target_family = "wasm"))]
21284  #[test]
21285  fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
21286    use std::sync::mpsc;
21287
21288    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21289      RuntimeMetrics::default(),
21290    )));
21291    let ran = Arc::new(AtomicBool::new(false));
21292    let job_ran = Arc::clone(&ran);
21293    {
21294      let mut state = executor
21295        .blocking_admission
21296        .state
21297        .lock()
21298        .unwrap_or_else(std::sync::PoisonError::into_inner);
21299      state.queue.push(QueuedBlocking {
21300        id: BlockingJobId(u64::MAX - 73),
21301        run: Box::new(move || {
21302          job_ran.store(true, Ordering::SeqCst);
21303        }),
21304      });
21305    }
21306
21307    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21308    let (release_claim_tx, release_claim_rx) = mpsc::channel();
21309    let (claim_result_tx, claim_result_rx) = mpsc::channel();
21310    let claim_executor = Arc::clone(&executor);
21311    let claimant = std::thread::spawn(move || {
21312      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21313        *slot.borrow_mut() = Some(Box::new(move || {
21314          claim_locked_tx.send(()).unwrap();
21315          release_claim_rx.recv().unwrap();
21316        }));
21317      });
21318      claim_result_tx
21319        .send(claim_executor.run_one_blocking())
21320        .unwrap();
21321    });
21322    claim_locked_rx
21323      .recv_timeout(Duration::from_secs(2))
21324      .expect("the FIFO claimant must pause while holding the admission lock");
21325
21326    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
21327    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
21328    let cancelling_executor = Arc::clone(&executor);
21329    let cancellation = std::thread::spawn(move || {
21330      let cancelling = cancelling_executor
21331        .begin_failed_dispatch_cancellation()
21332        .expect("the test must publish terminal dispatch cancellation");
21333      cancellation_published_tx.send(()).unwrap();
21334      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
21335      cancellation_done_tx.send(()).unwrap();
21336    });
21337    cancellation_published_rx
21338      .recv_timeout(Duration::from_secs(2))
21339      .expect("terminal dispatch cancellation must publish before queue cancellation");
21340    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21341    assert!(
21342      matches!(
21343        cancellation_done_rx.try_recv(),
21344        Err(mpsc::TryRecvError::Empty)
21345      ),
21346      "terminal cancellation must wait for the blocking-admission lock"
21347    );
21348
21349    release_claim_tx.send(()).unwrap();
21350    assert!(
21351      !claim_result_rx
21352        .recv_timeout(Duration::from_secs(2))
21353        .expect("the cancelled FIFO claim must finish"),
21354      "terminal cancellation publication must reject the queued FIFO claim"
21355    );
21356    cancellation_done_rx
21357      .recv_timeout(Duration::from_secs(2))
21358      .expect("terminal cancellation must drain the rejected FIFO job");
21359    claimant.join().unwrap();
21360    cancellation.join().unwrap();
21361
21362    assert!(
21363      !ran.load(Ordering::SeqCst),
21364      "terminally cancelled FIFO work must not start"
21365    );
21366    assert!(
21367      executor
21368        .blocking_admission
21369        .state
21370        .lock()
21371        .unwrap_or_else(std::sync::PoisonError::into_inner)
21372        .queue
21373        .is_empty(),
21374      "terminal cancellation must drain the rejected FIFO job"
21375    );
21376    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21377  }
21378
21379  #[cfg(not(target_family = "wasm"))]
21380  #[test]
21381  fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
21382    use std::sync::mpsc;
21383
21384    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21385      RuntimeMetrics::default(),
21386    )));
21387    let owner = executor.fresh_owner_token();
21388    let job = BlockingJobId(u64::MAX - 72);
21389    let ran = Arc::new(AtomicBool::new(false));
21390    let job_ran = Arc::clone(&ran);
21391    {
21392      let mut state = executor
21393        .blocking_admission
21394        .state
21395        .lock()
21396        .unwrap_or_else(std::sync::PoisonError::into_inner);
21397      state.owner = Some(owner);
21398      state.queue.push(QueuedBlocking {
21399        id: job,
21400        run: Box::new(move || {
21401          job_ran.store(true, Ordering::SeqCst);
21402        }),
21403      });
21404    }
21405    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21406    dependency.set_owned(BlockingDependency {
21407      job,
21408      owner: Some(owner),
21409    });
21410
21411    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21412    let (release_claim_tx, release_claim_rx) = mpsc::channel();
21413    let (claim_result_tx, claim_result_rx) = mpsc::channel();
21414    let claim_executor = Arc::clone(&executor);
21415    let claim_dependency = Arc::clone(&dependency);
21416    let claimant = std::thread::spawn(move || {
21417      let _owner = claim_executor.enter_blocking_owner(owner, false);
21418      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21419        *slot.borrow_mut() = Some(Box::new(move || {
21420          claim_locked_tx.send(()).unwrap();
21421          release_claim_rx.recv().unwrap();
21422        }));
21423      });
21424      claim_result_tx
21425        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
21426        .unwrap();
21427    });
21428    claim_locked_rx
21429      .recv_timeout(Duration::from_secs(2))
21430      .expect("the owner claimant must pause while holding the admission lock");
21431
21432    executor.stop.begin_shutdown();
21433    release_claim_tx.send(()).unwrap();
21434    assert!(
21435      !claim_result_rx
21436        .recv_timeout(Duration::from_secs(2))
21437        .expect("the stopped owner claim must finish"),
21438      "stop publication must reject the exact-owner claim"
21439    );
21440    claimant.join().unwrap();
21441    assert!(
21442      !ran.load(Ordering::SeqCst),
21443      "queued exact-owner work must not start after stop publication"
21444    );
21445    assert!(
21446      !executor
21447        .blocking_admission
21448        .state
21449        .lock()
21450        .unwrap_or_else(std::sync::PoisonError::into_inner)
21451        .queue
21452        .is_empty(),
21453      "the rejected exact-owner job must remain queued for shutdown cancellation"
21454    );
21455
21456    executor
21457      .blocking_admission
21458      .state
21459      .lock()
21460      .unwrap_or_else(std::sync::PoisonError::into_inner)
21461      .owner = None;
21462    executor.close_blocking_admission();
21463    assert!(
21464      executor
21465        .blocking_admission
21466        .state
21467        .lock()
21468        .unwrap_or_else(std::sync::PoisonError::into_inner)
21469        .queue
21470        .is_empty()
21471    );
21472  }
21473
21474  #[cfg(not(target_family = "wasm"))]
21475  #[test]
21476  fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
21477    use std::sync::mpsc;
21478
21479    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21480      RuntimeMetrics::default(),
21481    )));
21482    let owner = executor.fresh_owner_token();
21483    let job = BlockingJobId(u64::MAX - 74);
21484    let ran = Arc::new(AtomicBool::new(false));
21485    let job_ran = Arc::clone(&ran);
21486    {
21487      let mut state = executor
21488        .blocking_admission
21489        .state
21490        .lock()
21491        .unwrap_or_else(std::sync::PoisonError::into_inner);
21492      state.owner = Some(owner);
21493      state.queue.push(QueuedBlocking {
21494        id: job,
21495        run: Box::new(move || {
21496          job_ran.store(true, Ordering::SeqCst);
21497        }),
21498      });
21499    }
21500    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21501    dependency.set_owned(BlockingDependency {
21502      job,
21503      owner: Some(owner),
21504    });
21505
21506    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21507    let (release_claim_tx, release_claim_rx) = mpsc::channel();
21508    let (claim_result_tx, claim_result_rx) = mpsc::channel();
21509    let claim_executor = Arc::clone(&executor);
21510    let claim_dependency = Arc::clone(&dependency);
21511    let claimant = std::thread::spawn(move || {
21512      let _owner = claim_executor.enter_blocking_owner(owner, false);
21513      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21514        *slot.borrow_mut() = Some(Box::new(move || {
21515          claim_locked_tx.send(()).unwrap();
21516          release_claim_rx.recv().unwrap();
21517        }));
21518      });
21519      claim_result_tx
21520        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
21521        .unwrap();
21522    });
21523    claim_locked_rx
21524      .recv_timeout(Duration::from_secs(2))
21525      .expect("the owner claimant must pause while holding the admission lock");
21526
21527    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
21528    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
21529    let cancelling_executor = Arc::clone(&executor);
21530    let cancellation = std::thread::spawn(move || {
21531      let cancelling = cancelling_executor
21532        .begin_failed_dispatch_cancellation()
21533        .expect("the test must publish terminal dispatch cancellation");
21534      cancellation_published_tx.send(()).unwrap();
21535      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
21536      cancellation_done_tx.send(()).unwrap();
21537    });
21538    cancellation_published_rx
21539      .recv_timeout(Duration::from_secs(2))
21540      .expect("terminal dispatch cancellation must publish before queue cancellation");
21541    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21542    assert!(
21543      matches!(
21544        cancellation_done_rx.try_recv(),
21545        Err(mpsc::TryRecvError::Empty)
21546      ),
21547      "terminal cancellation must wait for the blocking-admission lock"
21548    );
21549
21550    release_claim_tx.send(()).unwrap();
21551    assert!(
21552      !claim_result_rx
21553        .recv_timeout(Duration::from_secs(2))
21554        .expect("the cancelled owner claim must finish"),
21555      "terminal cancellation publication must reject the exact-owner claim"
21556    );
21557    cancellation_done_rx
21558      .recv_timeout(Duration::from_secs(2))
21559      .expect("terminal cancellation must drain the rejected exact-owner job");
21560    claimant.join().unwrap();
21561    cancellation.join().unwrap();
21562
21563    assert!(
21564      !ran.load(Ordering::SeqCst),
21565      "terminally cancelled exact-owner work must not start"
21566    );
21567    assert!(
21568      executor
21569        .blocking_admission
21570        .state
21571        .lock()
21572        .unwrap_or_else(std::sync::PoisonError::into_inner)
21573        .queue
21574        .is_empty(),
21575      "terminal cancellation must drain the rejected exact-owner job"
21576    );
21577    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21578  }
21579
21580  #[cfg(not(target_family = "wasm"))]
21581  #[test]
21582  fn current_thread_shutdown_cancels_waiting_blocking_work() {
21583    use std::sync::mpsc;
21584
21585    struct DropSignal(mpsc::Sender<()>);
21586
21587    impl Drop for DropSignal {
21588      fn drop(&mut self) {
21589        let _ = self.0.send(());
21590      }
21591    }
21592
21593    let controller = Arc::new(current_thread_controller(
21594      "current-thread-blocking-shutdown",
21595    ));
21596    let backend = controller.backend();
21597    let (first_started_tx, first_started_rx) = mpsc::channel();
21598    let (release_first_tx, release_first_rx) = mpsc::channel();
21599    let first_controller = Arc::clone(&controller);
21600    let first = std::thread::spawn(move || {
21601      let handle = first_controller
21602        .try_spawn_blocking(move || {
21603          first_started_tx.send(()).unwrap();
21604          release_first_rx.recv().unwrap();
21605        })
21606        .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
21607      futures::executor::block_on(handle)
21608    });
21609    first_started_rx
21610      .recv_timeout(Duration::from_secs(1))
21611      .unwrap();
21612
21613    let waiting_ran = Arc::new(AtomicBool::new(false));
21614    let waiting_ran_task = Arc::clone(&waiting_ran);
21615    let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
21616    let waiting_drop = DropSignal(waiting_dropped_tx);
21617    let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
21618    let waiting_controller = Arc::clone(&controller);
21619    let waiting = std::thread::spawn(move || {
21620      let handle = waiting_controller
21621        .try_spawn_blocking(move || {
21622          let _waiting_drop = waiting_drop;
21623          waiting_ran_task.store(true, Ordering::SeqCst);
21624        })
21625        .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
21626      waiting_result_tx
21627        .send(futures::executor::block_on(handle))
21628        .unwrap();
21629    });
21630    wait_until("both CurrentThread blocking calls to be accepted", || {
21631      backend
21632        .work
21633        .state
21634        .lock()
21635        .unwrap_or_else(std::sync::PoisonError::into_inner)
21636        .active
21637        == 2
21638    });
21639
21640    let (shutdown_tx, shutdown_rx) = mpsc::channel();
21641    let shutdown_controller = Arc::clone(&controller);
21642    let shutdown = std::thread::spawn(move || {
21643      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
21644    });
21645    waiting_dropped_rx
21646      .recv_timeout(Duration::from_secs(1))
21647      .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
21648    assert!(
21649      !waiting_ran.load(Ordering::SeqCst),
21650      "waiting blocking work must not start"
21651    );
21652    assert!(
21653      waiting_result_rx
21654        .recv_timeout(Duration::from_secs(1))
21655        .unwrap()
21656        .is_err(),
21657      "the waiting blocking handle must resolve as cancelled"
21658    );
21659    assert!(
21660      shutdown_rx
21661        .recv_timeout(Duration::from_millis(100))
21662        .is_err(),
21663      "shutdown must still wait for the blocking closure that already owns the lane"
21664    );
21665
21666    release_first_tx.send(()).unwrap();
21667    first.join().unwrap().unwrap();
21668    waiting.join().unwrap();
21669    shutdown_rx
21670      .recv_timeout(Duration::from_secs(1))
21671      .unwrap()
21672      .unwrap();
21673    shutdown.join().unwrap();
21674    assert_eq!(
21675      controller
21676        .metrics
21677        .blocking_tasks_started
21678        .load(Ordering::Relaxed),
21679      1
21680    );
21681    assert_eq!(
21682      controller
21683        .metrics
21684        .blocking_tasks_completed
21685        .load(Ordering::Relaxed),
21686      1
21687    );
21688    assert_eq!(
21689      controller
21690        .metrics
21691        .active_blocking_tasks
21692        .load(Ordering::Relaxed),
21693      0
21694    );
21695  }
21696
21697  #[cfg(not(target_family = "wasm"))]
21698  #[test]
21699  fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
21700    use std::{panic::panic_any, sync::mpsc};
21701
21702    struct ReentrantPayload {
21703      controller: Arc<RuntimeController>,
21704      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
21705    }
21706
21707    impl Drop for ReentrantPayload {
21708      fn drop(&mut self) {
21709        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
21710        let shutdown = self.controller.shutdown();
21711        self.result.send((generation, shutdown)).unwrap();
21712      }
21713    }
21714
21715    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
21716
21717    impl Drop for PanicPayloadOnDrop {
21718      fn drop(&mut self) {
21719        panic_any(self.0.take().expect("the result must panic only once"));
21720      }
21721    }
21722
21723    let controller = Arc::new(current_thread_controller(
21724      "current-thread-panic-payload-generation",
21725    ));
21726    let backend = controller.backend();
21727    let generation = backend.generation();
21728    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
21729      panic!("configured CurrentThread backend must create a CurrentThread executor");
21730    };
21731    let executor = Arc::clone(executor);
21732    drop(backend);
21733
21734    // Hold the physical lane long enough to force scheduler-owned delivery.
21735    // Dropping the receiver then makes the queued sender destroy the result.
21736    let owner = executor.fresh_owner_token();
21737    executor
21738      .blocking_admission
21739      .state
21740      .lock()
21741      .unwrap_or_else(std::sync::PoisonError::into_inner)
21742      .owner = Some(owner);
21743    let (result_tx, result_rx) = mpsc::channel();
21744    let payload_controller = Arc::clone(&controller);
21745    let handle = controller
21746      .try_spawn_blocking(move || {
21747        PanicPayloadOnDrop(Some(ReentrantPayload {
21748          controller: payload_controller,
21749          result: result_tx,
21750        }))
21751      })
21752      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
21753    drop(handle);
21754    executor
21755      .blocking_admission
21756      .state
21757      .lock()
21758      .unwrap_or_else(std::sync::PoisonError::into_inner)
21759      .owner = None;
21760    let dispatch = current_thread_dispatch(&executor);
21761    controller.drive_current_thread_dispatch(dispatch, false);
21762
21763    let (observed_generation, shutdown) = result_rx
21764      .recv_timeout(Duration::from_secs(1))
21765      .expect("the caught result panic payload must be destroyed in the scheduler turn");
21766    assert_eq!(observed_generation, Some(generation));
21767    let error = shutdown.expect_err("payload destruction is still work in the active generation");
21768    assert!(error.to_string().contains("work running on that runtime"));
21769    controller.shutdown().unwrap();
21770  }
21771
21772  #[test]
21773  fn current_thread_detached_blocking_result_drop_is_contained() {
21774    use std::sync::mpsc;
21775
21776    struct PanicOnDrop(mpsc::Sender<()>);
21777
21778    impl Drop for PanicOnDrop {
21779      fn drop(&mut self) {
21780        self.0.send(()).unwrap();
21781        panic!("intentional CurrentThread blocking result destructor panic");
21782      }
21783    }
21784
21785    let controller = current_thread_controller("current-thread-blocking-result-drop");
21786    let (dropped_tx, dropped_rx) = mpsc::channel();
21787    let handle = controller
21788      .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
21789      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
21790
21791    let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
21792    dropped_rx
21793      .recv_timeout(Duration::from_secs(1))
21794      .expect("dropping the detached handle must reclaim its blocking result");
21795    assert!(
21796      result.is_ok(),
21797      "the blocking result destructor panic must be contained"
21798    );
21799    controller.shutdown().unwrap();
21800  }
21801
21802  // ---- Runtime option validation and configuration immutability -------------
21803  // These build `RuntimeOptions`/`RuntimeController` LOCALLY and never touch the
21804  // global `RUNTIME` singleton, so they stay deterministic and order-independent.
21805
21806  #[test]
21807  fn validate_rejects_zero_worker_threads() {
21808    let error = RuntimeOptions {
21809      flavor: RuntimeFlavor::MultiThread,
21810      worker_threads: 0,
21811      max_blocking_tasks: 1,
21812      thread_name_prefix: "rd8".to_string(),
21813      park_deadline: None,
21814    }
21815    .validate()
21816    .expect_err("worker_threads == 0 must be rejected");
21817    assert_eq!(
21818      error.to_string(),
21819      "worker_threads must be greater than zero"
21820    );
21821  }
21822
21823  #[test]
21824  fn validate_rejects_zero_max_blocking_tasks() {
21825    let error = RuntimeOptions {
21826      flavor: RuntimeFlavor::MultiThread,
21827      worker_threads: 2,
21828      max_blocking_tasks: 0,
21829      thread_name_prefix: "rd8".to_string(),
21830      park_deadline: None,
21831    }
21832    .validate()
21833    .expect_err("max_blocking_tasks == 0 must be rejected");
21834    assert_eq!(
21835      error.to_string(),
21836      "max_blocking_tasks must be greater than zero"
21837    );
21838  }
21839
21840  #[test]
21841  fn validate_current_thread_coerces_worker_threads_to_one() {
21842    // CurrentThread forces `worker_threads` to 1; `max_blocking_tasks` is then
21843    // clamped to `.min(worker_threads)` == 1.
21844    let validated = RuntimeOptions {
21845      flavor: RuntimeFlavor::CurrentThread,
21846      worker_threads: 8,
21847      max_blocking_tasks: 8,
21848      thread_name_prefix: "rd8".to_string(),
21849      park_deadline: None,
21850    }
21851    .validate()
21852    .expect("CurrentThread options must validate");
21853    assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
21854    assert_eq!(validated.worker_threads, 1);
21855    assert_eq!(validated.max_blocking_tasks, 1);
21856  }
21857
21858  // Native-only: `validate()` rejects `MultiThread` under `cfg(target_family = "wasm")`
21859  // (after applying the clamp), so this success assertion is valid off-wasm only.
21860  #[cfg(not(target_family = "wasm"))]
21861  #[test]
21862  fn validate_reserves_one_worker_from_blocking_admission() {
21863    let validated = RuntimeOptions {
21864      flavor: RuntimeFlavor::MultiThread,
21865      worker_threads: 2,
21866      max_blocking_tasks: 8,
21867      thread_name_prefix: "rd8".to_string(),
21868      park_deadline: None,
21869    }
21870    .validate()
21871    .expect("MultiThread options must validate");
21872    assert_eq!(validated.worker_threads, 2);
21873    assert_eq!(validated.max_blocking_tasks, 1);
21874  }
21875
21876  #[cfg(not(target_family = "wasm"))]
21877  #[test]
21878  fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
21879    let validated = RuntimeOptions {
21880      flavor: RuntimeFlavor::MultiThread,
21881      worker_threads: 1,
21882      max_blocking_tasks: 8,
21883      thread_name_prefix: "rd8-minimum".to_string(),
21884      park_deadline: None,
21885    }
21886    .validate()
21887    .expect("MultiThread options must validate");
21888    assert_eq!(validated.worker_threads, 2);
21889    assert_eq!(validated.max_blocking_tasks, 1);
21890  }
21891
21892  #[cfg(not(target_family = "wasm"))]
21893  #[test]
21894  fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
21895    let simulated_32_bit = RuntimeOptions {
21896      flavor: RuntimeFlavor::MultiThread,
21897      worker_threads: 256,
21898      max_blocking_tasks: 256,
21899      thread_name_prefix: "rd8-rayon-cap-32".to_string(),
21900      park_deadline: None,
21901    }
21902    .validate_with_rayon_max_threads(Some(255))
21903    .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
21904    assert_eq!(simulated_32_bit.worker_threads, 255);
21905    assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
21906
21907    let rayon_max_threads = max_num_threads();
21908    let requested_threads = rayon_max_threads
21909      .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
21910      .checked_add(1)
21911      .expect("supported worker counts must leave room for an oversized request");
21912    let controller = RuntimeController::new();
21913    controller
21914      .configure(RuntimeOptions {
21915        flavor: RuntimeFlavor::MultiThread,
21916        worker_threads: requested_threads,
21917        max_blocking_tasks: requested_threads,
21918        thread_name_prefix: "rd8-rayon-cap".to_string(),
21919        park_deadline: None,
21920      })
21921      .expect("an oversized request must clamp to the production worker ceiling");
21922
21923    let reported = controller.options();
21924    let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
21925    assert_eq!(reported.worker_threads, expected_workers);
21926    assert_eq!(
21927      reported.max_blocking_tasks,
21928      expected_workers - 1,
21929      "the blocking cap must reserve a lane from the physically realizable worker count"
21930    );
21931  }
21932
21933  #[cfg(not(target_family = "wasm"))]
21934  #[test]
21935  fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
21936    let controller = RuntimeController::new();
21937    let options = controller.options();
21938    assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
21939    assert_eq!(
21940      options.max_blocking_tasks,
21941      options.worker_threads.saturating_sub(1).max(1),
21942      "the unconfigured Rust API must use the same validated reserve as the binding"
21943    );
21944  }
21945
21946  #[cfg(not(target_family = "wasm"))]
21947  #[test]
21948  fn concurrent_partial_configuration_merges_from_latest_committed_options() {
21949    use std::{sync::mpsc, time::Duration};
21950
21951    let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
21952    let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
21953    let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
21954    let first_controller = Arc::clone(&controller);
21955    let first = std::thread::spawn(move || {
21956      first_controller
21957        .configure_partial_inner(
21958          RuntimeOptionsPatch {
21959            max_blocking_tasks: Some(3),
21960            ..RuntimeOptionsPatch::default()
21961          },
21962          |candidate| {
21963            assert_eq!(
21964              (candidate.worker_threads, candidate.max_blocking_tasks),
21965              (8, 3)
21966            );
21967            first_merged_tx
21968              .send(())
21969              .expect("the race coordinator must still be listening");
21970            release_first_rx
21971              .recv_timeout(Duration::from_secs(5))
21972              .expect("the first partial configuration must be released");
21973          },
21974        )
21975        .expect("the first partial configuration must commit");
21976    });
21977    first_merged_rx
21978      .recv_timeout(Duration::from_secs(5))
21979      .expect("the first partial configuration must hold the controller lock after merging");
21980
21981    let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
21982    let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
21983    let second_controller = Arc::clone(&controller);
21984    let second = std::thread::spawn(move || {
21985      second_started_tx
21986        .send(())
21987        .expect("the race coordinator must still be listening");
21988      second_controller
21989        .configure_partial_inner(
21990          RuntimeOptionsPatch {
21991            worker_threads: Some(6),
21992            ..RuntimeOptionsPatch::default()
21993          },
21994          |candidate| {
21995            second_candidate_tx
21996              .send((candidate.worker_threads, candidate.max_blocking_tasks))
21997              .expect("the race coordinator must still be listening");
21998          },
21999        )
22000        .expect("the second partial configuration must commit");
22001    });
22002    second_started_rx
22003      .recv_timeout(Duration::from_secs(5))
22004      .expect("the second partial configuration thread must start");
22005    release_first_tx
22006      .send(())
22007      .expect("the first partial configuration must still be waiting");
22008
22009    first
22010      .join()
22011      .expect("the first configuration thread must not panic");
22012    second
22013      .join()
22014      .expect("the second configuration thread must not panic");
22015    assert_eq!(
22016      second_candidate_rx
22017        .recv_timeout(Duration::from_secs(5))
22018        .expect("the second partial configuration must expose its merged candidate"),
22019      (6, 3),
22020      "the second caller must merge against the first caller's committed options"
22021    );
22022
22023    let options = controller.options();
22024    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
22025    assert_eq!(options.thread_name_prefix, "partial-config-race");
22026  }
22027
22028  #[cfg(not(target_family = "wasm"))]
22029  #[test]
22030  fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
22031    use std::{sync::mpsc, time::Duration};
22032
22033    let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
22034    let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
22035    let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
22036    let invalid_controller = Arc::clone(&controller);
22037    let invalid = std::thread::spawn(move || {
22038      invalid_controller
22039        .configure_partial_inner(
22040          RuntimeOptionsPatch {
22041            worker_threads: Some(0),
22042            ..RuntimeOptionsPatch::default()
22043          },
22044          |candidate| {
22045            assert_eq!(
22046              (candidate.worker_threads, candidate.max_blocking_tasks),
22047              (0, 2)
22048            );
22049            invalid_merged_tx
22050              .send(())
22051              .expect("the race coordinator must still be listening");
22052            release_invalid_rx
22053              .recv_timeout(Duration::from_secs(5))
22054              .expect("the invalid partial configuration must be released");
22055          },
22056        )
22057        .expect_err("the invalid partial configuration must be rejected")
22058        .to_string()
22059    });
22060    invalid_merged_rx
22061      .recv_timeout(Duration::from_secs(5))
22062      .expect("the invalid partial configuration must hold the lock after merging");
22063
22064    let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
22065    let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
22066    let valid_controller = Arc::clone(&controller);
22067    let valid = std::thread::spawn(move || {
22068      valid_started_tx
22069        .send(())
22070        .expect("the race coordinator must still be listening");
22071      valid_controller
22072        .configure_partial_inner(
22073          RuntimeOptionsPatch {
22074            max_blocking_tasks: Some(4),
22075            ..RuntimeOptionsPatch::default()
22076          },
22077          |candidate| {
22078            valid_candidate_tx
22079              .send((candidate.worker_threads, candidate.max_blocking_tasks))
22080              .expect("the race coordinator must still be listening");
22081          },
22082        )
22083        .expect("the valid partial configuration must commit");
22084    });
22085    valid_started_rx
22086      .recv_timeout(Duration::from_secs(5))
22087      .expect("the valid partial configuration thread must start");
22088    release_invalid_tx
22089      .send(())
22090      .expect("the invalid partial configuration must still be waiting");
22091
22092    assert_eq!(
22093      invalid
22094        .join()
22095        .expect("the invalid configuration thread must not panic"),
22096      "worker_threads must be greater than zero"
22097    );
22098    valid
22099      .join()
22100      .expect("the valid configuration thread must not panic");
22101    assert_eq!(
22102      valid_candidate_rx
22103        .recv_timeout(Duration::from_secs(5))
22104        .expect("the valid partial configuration must expose its merged candidate"),
22105      (6, 4),
22106      "the valid caller must merge against the last committed options, not the rejected candidate"
22107    );
22108
22109    let options = controller.options();
22110    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
22111    assert_eq!(options.thread_name_prefix, "partial-config-rollback");
22112  }
22113
22114  #[test]
22115  fn configure_after_backend_started_is_rejected() {
22116    // A local controller, not the global `RUNTIME`. Use a CurrentThread backend
22117    // so starting it is cheap and spawns no OS threads.
22118    let controller = RuntimeController::new();
22119    controller
22120      .configure(RuntimeOptions {
22121        flavor: RuntimeFlavor::CurrentThread,
22122        worker_threads: 1,
22123        max_blocking_tasks: 1,
22124        thread_name_prefix: "rd8".to_string(),
22125        park_deadline: None,
22126      })
22127      .expect("first configure before the backend exists must succeed");
22128
22129    // Materialize the backend; further configuration must now be refused.
22130    let _backend = controller.backend();
22131
22132    let error = controller
22133      .configure(RuntimeOptions::default())
22134      .expect_err("configure after the backend started must be rejected");
22135    assert_eq!(
22136      error.to_string(),
22137      "the async runtime configuration is frozen; configure it before the first async call"
22138    );
22139  }
22140
22141  #[test]
22142  fn partial_configuration_after_backend_started_is_rejected() {
22143    let controller = current_thread_controller("partial-config-frozen");
22144    let _backend = controller.backend();
22145
22146    let error = controller
22147      .configure_partial(RuntimeOptionsPatch {
22148        worker_threads: Some(2),
22149        ..RuntimeOptionsPatch::default()
22150      })
22151      .expect_err("partial configuration after the backend started must be rejected");
22152    assert_eq!(
22153      error.to_string(),
22154      "the async runtime configuration is frozen; configure it before the first async call"
22155    );
22156  }
22157
22158  fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
22159    let controller = RuntimeController::new();
22160    controller
22161      .configure(RuntimeOptions {
22162        flavor: RuntimeFlavor::CurrentThread,
22163        worker_threads: 1,
22164        max_blocking_tasks: 1,
22165        thread_name_prefix: thread_name_prefix.to_string(),
22166        park_deadline: None,
22167      })
22168      .expect("CurrentThread test configuration must be accepted");
22169    controller
22170  }
22171
22172  #[cfg(not(target_family = "wasm"))]
22173  fn multi_thread_controller(
22174    thread_name_prefix: &str,
22175    worker_threads: usize,
22176    max_blocking_tasks: usize,
22177  ) -> RuntimeController {
22178    let controller = RuntimeController::new();
22179    controller
22180      .configure(RuntimeOptions {
22181        flavor: RuntimeFlavor::MultiThread,
22182        worker_threads,
22183        max_blocking_tasks,
22184        thread_name_prefix: thread_name_prefix.to_string(),
22185        park_deadline: None,
22186      })
22187      .expect("MultiThread test configuration must be accepted");
22188    controller
22189  }
22190
22191  #[test]
22192  fn blocking_backend_creation_failure_preserves_diagnostic() {
22193    let controller = current_thread_controller("blocking-backend-creation-failure");
22194    let ran = Arc::new(AtomicBool::new(false));
22195    let work_ran = Arc::clone(&ran);
22196
22197    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22198    let Err((error, work)) = controller.try_spawn_blocking(move || {
22199      work_ran.store(true, Ordering::SeqCst);
22200      17usize
22201    }) else {
22202      panic!("the injected backend creation failure must reject blocking work");
22203    };
22204    assert_eq!(
22205      error.to_string(),
22206      "injected async runtime backend creation failure"
22207    );
22208    assert!(
22209      !ran.load(Ordering::SeqCst),
22210      "rejected blocking work must not run"
22211    );
22212    assert_eq!(
22213      work(),
22214      17,
22215      "the rejected blocking closure must remain usable"
22216    );
22217    assert!(ran.load(Ordering::SeqCst));
22218
22219    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22220    let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
22221      .expect_err("infallible blocking submission must return a failed join handle");
22222    assert_eq!(
22223      error.to_string(),
22224      "injected async runtime backend creation failure"
22225    );
22226
22227    let handle = controller
22228      .try_spawn_blocking(|| 31usize)
22229      .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
22230    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
22231    controller.shutdown().unwrap();
22232  }
22233
22234  #[cfg(not(target_family = "wasm"))]
22235  #[test]
22236  fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
22237    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
22238
22239    if std::env::var_os(CHILD_ENV).is_some() {
22240      struct PollProbe {
22241        polls: Arc<AtomicUsize>,
22242      }
22243
22244      impl Future for PollProbe {
22245        type Output = ();
22246
22247        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22248          self.polls.fetch_add(1, Ordering::SeqCst);
22249          Poll::Ready(())
22250        }
22251      }
22252
22253      shutdown().expect("the isolated runtime must stop before block_on admission");
22254      let polls = Arc::new(AtomicUsize::new(0));
22255      let mut future = std::pin::pin!(PollProbe {
22256        polls: Arc::clone(&polls)
22257      });
22258      let error = try_block_on_dyn(future.as_mut())
22259        .expect_err("the stopped runtime must reject the borrowed future");
22260      assert_eq!(
22261        error.to_string(),
22262        "the async runtime is stopped; call start before submitting work"
22263      );
22264      assert_eq!(
22265        polls.load(Ordering::SeqCst),
22266        0,
22267        "rejected block_on must not poll its future"
22268      );
22269
22270      start().expect("the isolated runtime must restart");
22271      try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
22272      assert_eq!(polls.load(Ordering::SeqCst), 1);
22273      shutdown().expect("the restarted isolated runtime must stop");
22274      return;
22275    }
22276
22277    let output = std::process::Command::new(std::env::current_exe().unwrap())
22278      .arg("--exact")
22279      .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
22280      .arg("--nocapture")
22281      .env(CHILD_ENV, "1")
22282      .output()
22283      .expect("the stopped block_on subprocess must start");
22284    assert!(
22285      output.status.success(),
22286      "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
22287      output.status.code(),
22288      String::from_utf8_lossy(&output.stdout),
22289      String::from_utf8_lossy(&output.stderr)
22290    );
22291  }
22292
22293  #[cfg(not(target_family = "wasm"))]
22294  #[test]
22295  fn try_block_on_dyn_reports_concurrent_shutdown() {
22296    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
22297
22298    if std::env::var_os(CHILD_ENV).is_some() {
22299      use std::sync::mpsc;
22300
22301      struct PendingProbe {
22302        entered: Option<mpsc::Sender<()>>,
22303        complete: Arc<AtomicBool>,
22304      }
22305
22306      impl Future for PendingProbe {
22307        type Output = ();
22308
22309        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22310          if let Some(entered) = self.entered.take() {
22311            entered.send(()).unwrap();
22312          }
22313          if self.complete.load(Ordering::SeqCst) {
22314            Poll::Ready(())
22315          } else {
22316            Poll::Pending
22317          }
22318        }
22319      }
22320
22321      configure(RuntimeOptions {
22322        flavor: RuntimeFlavor::CurrentThread,
22323        worker_threads: 1,
22324        max_blocking_tasks: 1,
22325        thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
22326        park_deadline: None,
22327      })
22328      .expect("the isolated runtime must accept CurrentThread configuration");
22329      start().expect("the isolated runtime must start");
22330
22331      let (entered_tx, entered_rx) = mpsc::channel();
22332      let (error_tx, error_rx) = mpsc::channel();
22333      let (retry_tx, retry_rx) = mpsc::channel();
22334      let complete = Arc::new(AtomicBool::new(false));
22335      let future_complete = Arc::clone(&complete);
22336      let runner = std::thread::spawn(move || {
22337        let mut future = std::pin::pin!(PendingProbe {
22338          entered: Some(entered_tx),
22339          complete: future_complete
22340        });
22341        let error = try_block_on_dyn(future.as_mut())
22342          .expect_err("concurrent shutdown must interrupt the borrowed future")
22343          .to_string();
22344        error_tx.send(error).unwrap();
22345        retry_rx.recv().unwrap();
22346        try_block_on_dyn(future.as_mut())
22347          .expect("restart must accept the same interrupted borrowed future");
22348      });
22349      entered_rx
22350        .recv_timeout(Duration::from_secs(2))
22351        .expect("the borrowed future must be polled before shutdown");
22352      shutdown().expect("shutdown must wait for the active block_on scope to retire");
22353      assert_eq!(
22354        error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
22355        "the async runtime stopped before block_on completed its future"
22356      );
22357      start().expect("the isolated runtime must restart after the interrupted drive");
22358      complete.store(true, Ordering::SeqCst);
22359      retry_tx.send(()).unwrap();
22360      runner.join().unwrap();
22361      shutdown().expect("the retried isolated runtime must stop");
22362      return;
22363    }
22364
22365    let output = std::process::Command::new(std::env::current_exe().unwrap())
22366      .arg("--exact")
22367      .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
22368      .arg("--nocapture")
22369      .env(CHILD_ENV, "1")
22370      .output()
22371      .expect("the concurrent block_on shutdown subprocess must start");
22372    assert!(
22373      output.status.success(),
22374      "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
22375      output.status.code(),
22376      String::from_utf8_lossy(&output.stdout),
22377      String::from_utf8_lossy(&output.stderr)
22378    );
22379  }
22380
22381  #[test]
22382  fn initial_lifecycle_start_preserves_lazy_configuration() {
22383    let controller = RuntimeController::new();
22384    controller
22385      .start()
22386      .expect("initial lifecycle start must succeed");
22387    controller
22388      .configure(RuntimeOptions {
22389        flavor: RuntimeFlavor::CurrentThread,
22390        worker_threads: 1,
22391        max_blocking_tasks: 1,
22392        thread_name_prefix: "lifecycle-initial-start".to_string(),
22393        park_deadline: None,
22394      })
22395      .expect("module-registration start must not consume the configuration window");
22396
22397    let state = controller
22398      .state
22399      .lock()
22400      .unwrap_or_else(std::sync::PoisonError::into_inner);
22401    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22402  }
22403
22404  #[test]
22405  fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
22406    let controller = RuntimeController::new();
22407
22408    for _ in 0..3 {
22409      controller
22410        .start()
22411        .expect("zero-work lifecycle start must succeed");
22412      assert!(matches!(
22413        &controller
22414          .state
22415          .lock()
22416          .unwrap_or_else(std::sync::PoisonError::into_inner)
22417          .lifecycle,
22418        RuntimeLifecycle::Initial
22419      ));
22420
22421      controller
22422        .shutdown()
22423        .expect("zero-work lifecycle shutdown must succeed");
22424      assert!(matches!(
22425        &controller
22426          .state
22427          .lock()
22428          .unwrap_or_else(std::sync::PoisonError::into_inner)
22429          .lifecycle,
22430        RuntimeLifecycle::StoppedBeforeFirstUse
22431      ));
22432    }
22433
22434    controller
22435      .start()
22436      .expect("a later zero-work restart must remain lazy");
22437    controller
22438      .configure(RuntimeOptions {
22439        flavor: RuntimeFlavor::CurrentThread,
22440        worker_threads: 1,
22441        max_blocking_tasks: 1,
22442        thread_name_prefix: "lifecycle-zero-work-config".to_string(),
22443        park_deadline: None,
22444      })
22445      .expect("zero-work lifecycle cycles must not consume the configuration window");
22446
22447    let state = controller
22448      .state
22449      .lock()
22450      .unwrap_or_else(std::sync::PoisonError::into_inner);
22451    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22452  }
22453
22454  #[cfg(not(target_family = "wasm"))]
22455  #[test]
22456  fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
22457    use std::sync::mpsc;
22458
22459    struct BlockingDropFuture {
22460      entered: mpsc::Sender<()>,
22461      release: mpsc::Receiver<()>,
22462    }
22463
22464    impl Future for BlockingDropFuture {
22465      type Output = ();
22466
22467      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22468        Poll::Pending
22469      }
22470    }
22471
22472    impl Drop for BlockingDropFuture {
22473      fn drop(&mut self) {
22474        self.entered.send(()).unwrap();
22475        self.release.recv().unwrap();
22476      }
22477    }
22478
22479    let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
22480    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
22481    let (release_drop_tx, release_drop_rx) = mpsc::channel();
22482    let submit_controller = Arc::clone(&controller);
22483    let submitter = std::thread::spawn(move || {
22484      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22485      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
22486        entered: drop_entered_tx,
22487        release: release_drop_rx,
22488      }));
22489      assert!(
22490        result.is_err(),
22491        "the injected backend creation failure must reject the submission"
22492      );
22493    });
22494    drop_entered_rx
22495      .recv_timeout(Duration::from_secs(2))
22496      .expect("the rejected future destructor must start");
22497
22498    let retry_ran = Arc::new(AtomicBool::new(false));
22499    let retry_ran_task = Arc::clone(&retry_ran);
22500    let Err(retry) = controller.try_spawn_detached(async move {
22501      retry_ran_task.store(true, Ordering::SeqCst);
22502    }) else {
22503      panic!("backend creation must remain closed during rejected destruction");
22504    };
22505    drop(retry);
22506    assert!(!retry_ran.load(Ordering::SeqCst));
22507
22508    let (shutdown_tx, shutdown_rx) = mpsc::channel();
22509    let shutdown_controller = Arc::clone(&controller);
22510    let shutdown = std::thread::spawn(move || {
22511      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22512    });
22513    wait_until("initial shutdown to close runtime admission", || {
22514      matches!(
22515        controller
22516          .state
22517          .lock()
22518          .unwrap_or_else(std::sync::PoisonError::into_inner)
22519          .lifecycle,
22520        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
22521      )
22522    });
22523    assert!(
22524      shutdown_rx
22525        .recv_timeout(Duration::from_millis(200))
22526        .is_err(),
22527      "initial shutdown must wait for rejected user state destruction"
22528    );
22529    assert!(
22530      controller.try_spawn_detached(async {}).is_err(),
22531      "shutdown must keep first-generation admission closed while it waits"
22532    );
22533
22534    let (start_tx, start_rx) = mpsc::channel();
22535    let start_controller = Arc::clone(&controller);
22536    let starter = std::thread::spawn(move || {
22537      start_tx.send(start_controller.start()).unwrap();
22538    });
22539    assert!(
22540      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
22541      "start must wait for the zero-backend shutdown transition to finish"
22542    );
22543
22544    release_drop_tx.send(()).unwrap();
22545    submitter.join().unwrap();
22546    shutdown_rx
22547      .recv_timeout(Duration::from_secs(2))
22548      .unwrap()
22549      .unwrap();
22550    shutdown.join().unwrap();
22551    start_rx
22552      .recv_timeout(Duration::from_secs(2))
22553      .unwrap()
22554      .unwrap();
22555    starter.join().unwrap();
22556
22557    let ran = Arc::new(AtomicBool::new(false));
22558    let ran_task = Arc::clone(&ran);
22559    controller
22560      .try_spawn_detached(async move {
22561        ran_task.store(true, Ordering::SeqCst);
22562      })
22563      .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
22564    let dispatch = controller_current_thread_dispatch(&controller);
22565    controller.drive_current_thread_dispatch(dispatch, false);
22566    assert!(ran.load(Ordering::SeqCst));
22567    controller.shutdown().unwrap();
22568  }
22569
22570  #[cfg(not(target_family = "wasm"))]
22571  #[test]
22572  fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
22573    use std::sync::mpsc;
22574
22575    struct BlockingDropFuture {
22576      entered: mpsc::Sender<()>,
22577      release: mpsc::Receiver<()>,
22578    }
22579
22580    impl Future for BlockingDropFuture {
22581      type Output = ();
22582
22583      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22584        Poll::Pending
22585      }
22586    }
22587
22588    impl Drop for BlockingDropFuture {
22589      fn drop(&mut self) {
22590        self.entered.send(()).unwrap();
22591        self.release.recv().unwrap();
22592      }
22593    }
22594
22595    let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
22596    let old_generation = controller.backend().generation();
22597    controller.shutdown().unwrap();
22598
22599    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
22600    let (release_drop_tx, release_drop_rx) = mpsc::channel();
22601    let submit_controller = Arc::clone(&controller);
22602    let submitter = std::thread::spawn(move || {
22603      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
22604        entered: drop_entered_tx,
22605        release: release_drop_rx,
22606      }));
22607      assert!(
22608        result.is_err(),
22609        "the stopped runtime must reject the submission"
22610      );
22611    });
22612    drop_entered_rx
22613      .recv_timeout(Duration::from_secs(2))
22614      .unwrap();
22615
22616    let (shutdown_tx, shutdown_rx) = mpsc::channel();
22617    let shutdown_controller = Arc::clone(&controller);
22618    let shutdown = std::thread::spawn(move || {
22619      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22620    });
22621    wait_until(
22622      "stopped shutdown to enter its zero-backend transition",
22623      || {
22624        matches!(
22625          controller
22626            .state
22627            .lock()
22628            .unwrap_or_else(std::sync::PoisonError::into_inner)
22629            .lifecycle,
22630          RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
22631        )
22632      },
22633    );
22634
22635    let (start_tx, start_rx) = mpsc::channel();
22636    let start_controller = Arc::clone(&controller);
22637    let starter = std::thread::spawn(move || {
22638      start_tx.send(start_controller.start()).unwrap();
22639    });
22640    assert!(
22641      shutdown_rx
22642        .recv_timeout(Duration::from_millis(200))
22643        .is_err(),
22644      "stopped shutdown must wait for rejected destruction"
22645    );
22646    assert!(
22647      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
22648      "restart must wait for the stopped zero-backend transition"
22649    );
22650
22651    release_drop_tx.send(()).unwrap();
22652    submitter.join().unwrap();
22653    shutdown_rx
22654      .recv_timeout(Duration::from_secs(2))
22655      .unwrap()
22656      .unwrap();
22657    shutdown.join().unwrap();
22658    start_rx
22659      .recv_timeout(Duration::from_secs(2))
22660      .unwrap()
22661      .unwrap();
22662    starter.join().unwrap();
22663
22664    let new_generation = controller.backend().generation();
22665    assert_ne!(new_generation, old_generation);
22666    controller.shutdown().unwrap();
22667  }
22668
22669  #[test]
22670  fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
22671    use std::sync::mpsc;
22672
22673    struct ShutdownOnDrop {
22674      controller: Arc<RuntimeController>,
22675      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
22676    }
22677
22678    impl Future for ShutdownOnDrop {
22679      type Output = ();
22680
22681      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22682        Poll::Pending
22683      }
22684    }
22685
22686    impl Drop for ShutdownOnDrop {
22687      fn drop(&mut self) {
22688        self.result.send(self.controller.shutdown()).unwrap();
22689      }
22690    }
22691
22692    let controller = Arc::new(current_thread_controller(
22693      "failed-initial-reentrant-shutdown",
22694    ));
22695    let (result_tx, result_rx) = mpsc::channel();
22696    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22697    let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
22698      controller: Arc::clone(&controller),
22699      result: result_tx,
22700    }));
22701    assert!(rejected.is_err());
22702
22703    let error = result_rx
22704      .recv_timeout(Duration::from_secs(1))
22705      .expect("the rejected destructor must not self-deadlock")
22706      .expect_err("rejected destruction cannot wait for its own shutdown");
22707    assert!(error.to_string().contains("rejected submission"));
22708    {
22709      let state = controller
22710        .state
22711        .lock()
22712        .unwrap_or_else(std::sync::PoisonError::into_inner);
22713      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22714      assert_eq!(state.rejected_drops, 0);
22715    }
22716
22717    let handle = controller
22718      .try_spawn(async { 23usize })
22719      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
22720    let dispatch = controller_current_thread_dispatch(&controller);
22721    controller.drive_current_thread_dispatch(dispatch, false);
22722    assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
22723    controller.shutdown().unwrap();
22724  }
22725
22726  #[test]
22727  fn failed_initial_submission_destructor_cannot_reenter_start() {
22728    use std::sync::mpsc;
22729
22730    struct StartOnDrop {
22731      controller: Arc<RuntimeController>,
22732      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
22733      release: mpsc::Receiver<()>,
22734    }
22735
22736    impl Future for StartOnDrop {
22737      type Output = ();
22738
22739      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22740        Poll::Pending
22741      }
22742    }
22743
22744    impl Drop for StartOnDrop {
22745      fn drop(&mut self) {
22746        self.result.send(self.controller.start()).unwrap();
22747        self.release.recv().unwrap();
22748      }
22749    }
22750
22751    let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
22752    let (result_tx, result_rx) = mpsc::channel();
22753    let (release_drop_tx, release_drop_rx) = mpsc::channel();
22754    let (submission_tx, submission_rx) = mpsc::channel();
22755    let submit_controller = Arc::clone(&controller);
22756    let submitter = std::thread::spawn(move || {
22757      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22758      let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
22759        controller: Arc::clone(&submit_controller),
22760        result: result_tx,
22761        release: release_drop_rx,
22762      }));
22763      submission_tx.send(rejected).unwrap();
22764    });
22765
22766    let error = result_rx
22767      .recv_timeout(Duration::from_secs(1))
22768      .expect("the rejected destructor must not self-deadlock")
22769      .expect_err("rejected destruction must not report a successful runtime start");
22770    assert!(error.to_string().contains("rejected submission"));
22771    {
22772      let state = controller
22773        .state
22774        .lock()
22775        .unwrap_or_else(std::sync::PoisonError::into_inner);
22776      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22777      assert_eq!(state.rejected_drops, 1);
22778    }
22779
22780    let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
22781    let (external_start_tx, external_start_rx) = mpsc::channel();
22782    let start_controller = Arc::clone(&controller);
22783    let starter = std::thread::spawn(move || {
22784      BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
22785        *slot.borrow_mut() = Some(Box::new(move || {
22786          wait_entered_tx.send(()).unwrap();
22787        }));
22788      });
22789      external_start_tx.send(start_controller.start()).unwrap();
22790    });
22791    wait_entered_rx
22792      .recv_timeout(Duration::from_secs(1))
22793      .expect("external start must reach the Initial rejected-destruction wait");
22794    assert!(
22795      external_start_rx.try_recv().is_err(),
22796      "external start must not return while rejected destruction remains active"
22797    );
22798
22799    release_drop_tx.send(()).unwrap();
22800    assert!(
22801      submission_rx
22802        .recv_timeout(Duration::from_secs(2))
22803        .unwrap()
22804        .is_err()
22805    );
22806    submitter.join().unwrap();
22807    external_start_rx
22808      .recv_timeout(Duration::from_secs(2))
22809      .unwrap()
22810      .unwrap();
22811    starter.join().unwrap();
22812
22813    {
22814      let state = controller
22815        .state
22816        .lock()
22817        .unwrap_or_else(std::sync::PoisonError::into_inner);
22818      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22819      assert_eq!(state.rejected_drops, 0);
22820    }
22821
22822    let handle = controller
22823      .try_spawn(async { 31usize })
22824      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
22825    let dispatch = controller_current_thread_dispatch(&controller);
22826    controller.drive_current_thread_dispatch(dispatch, false);
22827    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
22828    controller.shutdown().unwrap();
22829  }
22830
22831  #[test]
22832  fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
22833    use std::sync::mpsc;
22834
22835    struct BlockingDropFuture {
22836      controller: Arc<RuntimeController>,
22837      entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
22838      release: mpsc::Receiver<()>,
22839    }
22840
22841    impl Future for BlockingDropFuture {
22842      type Output = ();
22843
22844      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22845        Poll::Pending
22846      }
22847    }
22848
22849    impl Drop for BlockingDropFuture {
22850      fn drop(&mut self) {
22851        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
22852        self
22853          .entered
22854          .send((generation, self.controller.shutdown()))
22855          .unwrap();
22856        self.release.recv().unwrap();
22857      }
22858    }
22859
22860    let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
22861    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
22862    let (release_drop_tx, release_drop_rx) = mpsc::channel();
22863    let (runner_done_tx, runner_done_rx) = mpsc::channel();
22864    let runner_controller = Arc::clone(&controller);
22865    let runner = std::thread::spawn(move || {
22866      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22867      let result = catch_unwind(AssertUnwindSafe(|| {
22868        block_on_with_controller(
22869          &runner_controller,
22870          BlockingDropFuture {
22871            controller: Arc::clone(&runner_controller),
22872            entered: drop_entered_tx,
22873            release: release_drop_rx,
22874          },
22875        );
22876      }));
22877      runner_done_tx.send(result.is_err()).unwrap();
22878    });
22879
22880    let (generation, reentrant_shutdown) = drop_entered_rx
22881      .recv_timeout(Duration::from_secs(2))
22882      .expect("failed block_on acquisition must destroy its owned future");
22883    assert_eq!(generation, None, "no runtime generation was created");
22884    let error = reentrant_shutdown
22885      .expect_err("rejected block_on destruction cannot wait for its own shutdown");
22886    assert!(error.to_string().contains("rejected submission"));
22887
22888    let Err(retry_error) = controller.try_backend() else {
22889      panic!("backend retry must remain closed during rejected block_on destruction");
22890    };
22891    assert!(retry_error.to_string().contains("rejected submission"));
22892
22893    let (shutdown_tx, shutdown_rx) = mpsc::channel();
22894    let shutdown_controller = Arc::clone(&controller);
22895    let shutdown = std::thread::spawn(move || {
22896      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22897    });
22898    assert!(
22899      shutdown_rx
22900        .recv_timeout(Duration::from_millis(200))
22901        .is_err(),
22902      "initial shutdown must wait for rejected block_on destruction"
22903    );
22904
22905    release_drop_tx.send(()).unwrap();
22906    assert!(
22907      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
22908      "block_on must preserve its infallible panic contract after contained cleanup"
22909    );
22910    runner.join().unwrap();
22911    shutdown_rx
22912      .recv_timeout(Duration::from_secs(2))
22913      .unwrap()
22914      .unwrap();
22915    shutdown.join().unwrap();
22916
22917    controller
22918      .start()
22919      .expect("zero-generation shutdown must remain restartable");
22920    let ran = Arc::new(AtomicBool::new(false));
22921    let ran_task = Arc::clone(&ran);
22922    controller
22923      .try_spawn_detached(async move {
22924        ran_task.store(true, Ordering::SeqCst);
22925      })
22926      .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
22927    let dispatch = controller_current_thread_dispatch(&controller);
22928    controller.drive_current_thread_dispatch(dispatch, false);
22929    assert!(ran.load(Ordering::SeqCst));
22930    controller.shutdown().unwrap();
22931  }
22932
22933  #[test]
22934  fn public_block_on_contains_poll_and_drop_double_panic() {
22935    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
22936
22937    if std::env::var_os(CHILD_ENV).is_some() {
22938      struct PanicInPollAndDrop;
22939
22940      impl Future for PanicInPollAndDrop {
22941        type Output = ();
22942
22943        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22944          panic!("intentional block_on poll panic");
22945        }
22946      }
22947
22948      impl Drop for PanicInPollAndDrop {
22949        fn drop(&mut self) {
22950          panic!("intentional block_on future destructor panic");
22951        }
22952      }
22953
22954      let controller = current_thread_controller("block-on-double-panic");
22955      assert!(
22956        catch_unwind(AssertUnwindSafe(|| {
22957          block_on_with_controller(&controller, PanicInPollAndDrop);
22958        }))
22959        .is_err(),
22960        "the original poll panic must still reach the caller"
22961      );
22962      controller
22963        .shutdown()
22964        .expect("contained future destruction must not strand the runtime");
22965      return;
22966    }
22967
22968    let output = std::process::Command::new(std::env::current_exe().unwrap())
22969      .arg("--exact")
22970      .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
22971      .arg("--nocapture")
22972      .env(CHILD_ENV, "1")
22973      .output()
22974      .expect("the subprocess test must start");
22975    assert!(
22976      output.status.success(),
22977      "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
22978      output.status.code(),
22979      String::from_utf8_lossy(&output.stdout),
22980      String::from_utf8_lossy(&output.stderr)
22981    );
22982  }
22983
22984  #[test]
22985  fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
22986    let controller = RuntimeController::new();
22987    controller
22988      .start()
22989      .expect("initial lifecycle start must succeed");
22990    controller
22991      .shutdown()
22992      .expect("zero-work lifecycle shutdown must succeed");
22993    controller
22994      .start()
22995      .expect("zero-work lifecycle restart must remain lazy");
22996    controller
22997      .configure(RuntimeOptions {
22998        flavor: RuntimeFlavor::CurrentThread,
22999        worker_threads: 1,
23000        max_blocking_tasks: 1,
23001        thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
23002        park_deadline: None,
23003      })
23004      .expect("configuration before first real async use must succeed");
23005
23006    let first_generation = controller.backend().generation();
23007    controller
23008      .shutdown()
23009      .expect("the first real generation must stop");
23010    assert!(matches!(
23011      &controller
23012        .state
23013        .lock()
23014        .unwrap_or_else(std::sync::PoisonError::into_inner)
23015        .lifecycle,
23016      RuntimeLifecycle::Stopped
23017    ));
23018    let error = controller
23019      .configure(RuntimeOptions::default())
23020      .expect_err("a real generation must permanently freeze configuration");
23021    assert_eq!(
23022      error.to_string(),
23023      "the async runtime configuration is frozen; configure it before the first async call"
23024    );
23025
23026    controller
23027      .start()
23028      .expect("the stopped real generation must restart");
23029    let restarted_generation = {
23030      let state = controller
23031        .state
23032        .lock()
23033        .unwrap_or_else(std::sync::PoisonError::into_inner);
23034      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
23035        panic!("a real generation restart must eagerly create its replacement backend");
23036      };
23037      backend.generation()
23038    };
23039    assert_ne!(
23040      restarted_generation, first_generation,
23041      "a real generation restart must not reuse the retired backend"
23042    );
23043    controller
23044      .shutdown()
23045      .expect("the replacement generation must stop");
23046  }
23047
23048  #[test]
23049  fn runtime_controller_rejects_submissions_after_shutdown() {
23050    let controller = current_thread_controller("lifecycle-reject");
23051    controller.start().expect("runtime must start");
23052    drop(controller.backend());
23053    controller.shutdown().expect("runtime must stop");
23054
23055    let async_ran = Arc::new(AtomicBool::new(false));
23056    let async_ran_task = Arc::clone(&async_ran);
23057    let Err(rejected_future) = controller.try_spawn_detached(async move {
23058      async_ran_task.store(true, Ordering::SeqCst);
23059    }) else {
23060      panic!("shutdown must reject async work");
23061    };
23062    assert!(
23063      !async_ran.load(Ordering::SeqCst),
23064      "a rejected future must not be polled"
23065    );
23066    futures::executor::block_on(rejected_future);
23067    assert!(
23068      async_ran.load(Ordering::SeqCst),
23069      "the original future must remain usable"
23070    );
23071
23072    let blocking_ran = Arc::new(AtomicBool::new(false));
23073    let blocking_ran_task = Arc::clone(&blocking_ran);
23074    let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
23075      blocking_ran_task.store(true, Ordering::SeqCst);
23076      7
23077    }) else {
23078      panic!("shutdown must reject blocking work");
23079    };
23080    assert_eq!(
23081      blocking_error.to_string(),
23082      "the async runtime is stopped; call start before submitting work"
23083    );
23084    assert!(
23085      !blocking_ran.load(Ordering::SeqCst),
23086      "rejected blocking work must not run"
23087    );
23088    assert_eq!(
23089      rejected_work(),
23090      7,
23091      "the original closure must remain usable"
23092    );
23093
23094    let config_error = controller
23095      .configure(RuntimeOptions::default())
23096      .expect_err("shutdown must not make runtime configuration mutable again");
23097    assert_eq!(
23098      config_error.to_string(),
23099      "the async runtime configuration is frozen; configure it before the first async call"
23100    );
23101
23102    let state = controller
23103      .state
23104      .lock()
23105      .unwrap_or_else(std::sync::PoisonError::into_inner);
23106    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
23107  }
23108
23109  #[test]
23110  fn rejected_future_can_be_resubmitted_after_restart() {
23111    let controller = current_thread_controller("lifecycle-retry-future");
23112    controller.start().expect("runtime must start");
23113    drop(controller.backend());
23114    controller.shutdown().expect("runtime must stop");
23115
23116    let runs = Arc::new(AtomicUsize::new(0));
23117    let runs_task = Arc::clone(&runs);
23118    let future = async move {
23119      runs_task.fetch_add(1, Ordering::SeqCst);
23120      23
23121    };
23122    let Err((error, future)) = controller.try_spawn(future) else {
23123      panic!("stopped runtime must reject the future");
23124    };
23125    assert!(error.to_string().contains("runtime is stopped"));
23126    assert_eq!(runs.load(Ordering::SeqCst), 0);
23127
23128    controller.start().expect("runtime must restart");
23129    let handle = controller
23130      .try_spawn(future)
23131      .unwrap_or_else(|_| panic!("restart must accept retained future"));
23132    let dispatch = controller_current_thread_dispatch(&controller);
23133    controller.drive_current_thread_dispatch(dispatch, false);
23134    assert_eq!(
23135      futures::executor::block_on(handle).expect("retained future must complete"),
23136      23
23137    );
23138    assert_eq!(runs.load(Ordering::SeqCst), 1);
23139    controller
23140      .shutdown()
23141      .expect("replacement runtime must stop");
23142  }
23143
23144  #[test]
23145  fn runtime_controller_start_after_shutdown_accepts_work() {
23146    let controller = current_thread_controller("lifecycle-restart");
23147    controller.start().expect("runtime must start");
23148    drop(controller.backend());
23149    controller.shutdown().expect("runtime must stop");
23150    controller.start().expect("runtime must restart");
23151
23152    let async_ran = Arc::new(AtomicBool::new(false));
23153    let async_ran_task = Arc::clone(&async_ran);
23154    controller
23155      .try_spawn_detached(async move {
23156        async_ran_task.store(true, Ordering::SeqCst);
23157      })
23158      .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
23159    assert!(
23160      !async_ran.load(Ordering::SeqCst),
23161      "hostless CurrentThread submission must remain enqueue-only"
23162    );
23163    let dispatch = controller_current_thread_dispatch(&controller);
23164    controller.drive_current_thread_dispatch(dispatch, false);
23165    assert!(
23166      async_ran.load(Ordering::SeqCst),
23167      "restarted runtime must poll submitted work"
23168    );
23169
23170    let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
23171      panic!("restarted runtime must accept blocking work");
23172    };
23173    assert_eq!(
23174      futures::executor::block_on(handle).expect("blocking work must complete after restart"),
23175      11
23176    );
23177  }
23178
23179  #[test]
23180  fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
23181    let controller = current_thread_controller("stale-host-generation");
23182    let old_generation = controller.backend().generation();
23183    let old_dispatch = controller_current_thread_dispatch(&controller);
23184    controller.shutdown().unwrap();
23185    controller.start().unwrap();
23186    let new_generation = controller.backend().generation();
23187    assert_ne!(new_generation, old_generation);
23188
23189    let ran = Arc::new(AtomicBool::new(false));
23190    let ran_task = Arc::clone(&ran);
23191    controller
23192      .try_spawn_detached(async move {
23193        ran_task.store(true, Ordering::SeqCst);
23194      })
23195      .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
23196    let new_dispatch = controller_current_thread_dispatch(&controller);
23197    assert_ne!(new_dispatch, old_dispatch);
23198
23199    controller.drive_current_thread_dispatch(old_dispatch, false);
23200    assert!(
23201      !ran.load(Ordering::SeqCst),
23202      "a callback from the retired generation must not touch the restarted executor"
23203    );
23204    controller.drive_current_thread_dispatch(new_dispatch, false);
23205    assert!(ran.load(Ordering::SeqCst));
23206    controller.shutdown().unwrap();
23207  }
23208
23209  #[test]
23210  fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
23211    let controller = current_thread_controller("stale-host-cancellation");
23212    let old_generation = controller.backend().generation();
23213    let old_dispatch = controller_current_thread_dispatch(&controller);
23214    controller.shutdown().unwrap();
23215    controller.start().unwrap();
23216    let backend = controller.backend();
23217    let new_generation = backend.generation();
23218    assert_ne!(new_generation, old_generation);
23219    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
23220      panic!("configured CurrentThread backend must create a CurrentThread executor");
23221    };
23222    let new_dispatch = current_thread_dispatch(executor);
23223    assert_ne!(new_dispatch, old_dispatch);
23224
23225    controller.cancel_current_thread_dispatch_for_test(old_dispatch);
23226    assert_eq!(
23227      executor.dispatch_pending.load(Ordering::Acquire),
23228      new_dispatch,
23229      "a delayed failure from the retired generation must not release the replacement latch"
23230    );
23231
23232    controller.cancel_current_thread_dispatch_for_test(new_dispatch);
23233    assert_eq!(
23234      executor.dispatch_pending.load(Ordering::Acquire),
23235      0,
23236      "the live generation must be able to cancel its accepted dispatch"
23237    );
23238    drop(backend);
23239    controller.shutdown().unwrap();
23240  }
23241
23242  #[cfg(not(target_family = "wasm"))]
23243  #[test]
23244  fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
23245    use std::sync::mpsc;
23246
23247    type ProbeResult = (
23248      &'static str,
23249      Option<u64>,
23250      Result<(), String>,
23251      Result<(), String>,
23252      Result<(), String>,
23253    );
23254
23255    struct StaleGenerationProbe {
23256      label: &'static str,
23257      controller: Arc<RuntimeController>,
23258      result: mpsc::Sender<ProbeResult>,
23259    }
23260
23261    impl Drop for StaleGenerationProbe {
23262      fn drop(&mut self) {
23263        let generation = active_runtime_generation();
23264        let submission = match self.controller.try_spawn(async {}) {
23265          Ok(handle) => {
23266            drop(handle);
23267            Ok(())
23268          }
23269          Err((error, future)) => {
23270            drop(future);
23271            Err(error.to_string())
23272          }
23273        };
23274        let start = self.controller.start().map_err(|error| error.to_string());
23275        let shutdown = self
23276          .controller
23277          .shutdown()
23278          .map_err(|error| error.to_string());
23279        self
23280          .result
23281          .send((self.label, generation, submission, start, shutdown))
23282          .unwrap();
23283      }
23284    }
23285
23286    let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
23287    let old_generation = controller.backend().generation();
23288    let (result_tx, result_rx) = mpsc::channel();
23289
23290    let async_controller = Arc::clone(&controller);
23291    let async_result = result_tx.clone();
23292    let async_handle = controller
23293      .try_spawn(async move {
23294        StaleGenerationProbe {
23295          label: "async",
23296          controller: async_controller,
23297          result: async_result,
23298        }
23299      })
23300      .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
23301    let old_dispatch = controller_current_thread_dispatch(&controller);
23302    controller.drive_current_thread_dispatch(old_dispatch, false);
23303
23304    let ready_controller = Arc::clone(&controller);
23305    let ready_handle = controller
23306      .try_spawn_blocking(move || StaleGenerationProbe {
23307        label: "ready",
23308        controller: ready_controller,
23309        result: result_tx,
23310      })
23311      .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
23312
23313    controller.shutdown().unwrap();
23314    controller.start().unwrap();
23315    let new_generation = controller.backend().generation();
23316    assert_ne!(new_generation, old_generation);
23317
23318    drop(async_handle);
23319    let async_result = result_rx
23320      .recv_timeout(Duration::from_secs(2))
23321      .expect("the retained async output must be destroyed without waiting on the replacement");
23322    drop(ready_handle);
23323    let ready_result = result_rx
23324      .recv_timeout(Duration::from_secs(2))
23325      .expect("the retained immediate output must be destroyed without touching the replacement");
23326
23327    for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
23328      let (label, generation, submission, start, shutdown) = result;
23329      assert_eq!(label, expected_label);
23330      assert_eq!(generation, Some(old_generation));
23331      for error in [submission, start, shutdown] {
23332        assert!(
23333          error
23334            .expect_err("retired-generation output destruction must be rejected")
23335            .contains("retired generation")
23336        );
23337      }
23338    }
23339
23340    let survivor = controller
23341      .try_spawn(async { 29usize })
23342      .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
23343    let new_dispatch = controller_current_thread_dispatch(&controller);
23344    controller.drive_current_thread_dispatch(new_dispatch, false);
23345    assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
23346    controller.shutdown().unwrap();
23347  }
23348
23349  #[cfg(not(target_family = "wasm"))]
23350  #[test]
23351  fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
23352    use std::sync::mpsc;
23353
23354    struct ShutdownOnDrop {
23355      controller: Arc<RuntimeController>,
23356      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
23357    }
23358
23359    impl Drop for ShutdownOnDrop {
23360      fn drop(&mut self) {
23361        self
23362          .result
23363          .send((active_runtime_generation(), self.controller.shutdown()))
23364          .unwrap();
23365      }
23366    }
23367
23368    let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
23369    let old_generation = controller.backend().generation();
23370    let (result_tx, result_rx) = mpsc::channel();
23371    let old_handle = controller
23372      .try_spawn_blocking({
23373        let controller = Arc::clone(&controller);
23374        move || ShutdownOnDrop {
23375          controller,
23376          result: result_tx,
23377        }
23378      })
23379      .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
23380    wait_until(
23381      "the first-generation blocking result to be buffered",
23382      || {
23383        controller
23384          .metrics
23385          .blocking_tasks_completed
23386          .load(Ordering::Acquire)
23387          == 1
23388      },
23389    );
23390    controller.shutdown().unwrap();
23391    controller.start().unwrap();
23392    let new_generation = controller.backend().generation();
23393    assert_ne!(new_generation, old_generation);
23394
23395    let (holder_started_tx, holder_started_rx) = mpsc::channel();
23396    let (release_holder_tx, release_holder_rx) = mpsc::channel();
23397    let holder = controller
23398      .try_spawn_blocking(move || {
23399        holder_started_tx.send(()).unwrap();
23400        release_holder_rx.recv().unwrap();
23401      })
23402      .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
23403    holder_started_rx
23404      .recv_timeout(Duration::from_secs(2))
23405      .unwrap();
23406
23407    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23408    let shutdown_controller = Arc::clone(&controller);
23409    let shutdown = std::thread::spawn(move || {
23410      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23411    });
23412    wait_until("the replacement generation to enter Stopping", || {
23413      matches!(
23414        controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
23415        RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
23416      )
23417    });
23418
23419    let dropper = std::thread::spawn(move || drop(old_handle));
23420    let (observed_generation, reentrant_shutdown) =
23421      match result_rx.recv_timeout(Duration::from_secs(2)) {
23422        Ok(result) => result,
23423        Err(error) => {
23424          release_holder_tx.send(()).unwrap();
23425          let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
23426          let _ = dropper.join();
23427          let _ = shutdown.join();
23428          panic!("retained first-generation output waited on replacement shutdown: {error}");
23429        }
23430      };
23431    assert_eq!(observed_generation, Some(old_generation));
23432    let error = reentrant_shutdown
23433      .expect_err("a retained first-generation output must not control replacement shutdown");
23434    assert!(error.to_string().contains("retired generation"));
23435
23436    release_holder_tx.send(()).unwrap();
23437    shutdown_rx
23438      .recv_timeout(Duration::from_secs(2))
23439      .unwrap()
23440      .unwrap();
23441    dropper.join().unwrap();
23442    shutdown.join().unwrap();
23443    futures::executor::block_on(holder).unwrap();
23444  }
23445
23446  #[cfg(not(target_family = "wasm"))]
23447  #[test]
23448  fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
23449    use std::sync::mpsc;
23450
23451    let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
23452    let generation = controller.backend().generation();
23453    let dispatch = controller_current_thread_dispatch(&controller);
23454    let (admitted_tx, admitted_rx) = mpsc::channel();
23455    let (release_tx, release_rx) = mpsc::channel();
23456    let driver_controller = Arc::clone(&controller);
23457    let driver = std::thread::spawn(move || {
23458      AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
23459        *slot.borrow_mut() = Some(Box::new(move || {
23460          admitted_tx.send(()).unwrap();
23461          release_rx.recv().unwrap();
23462        }));
23463      });
23464      driver_controller.drive_current_thread_dispatch(dispatch, false);
23465    });
23466    admitted_rx
23467      .recv_timeout(Duration::from_secs(2))
23468      .expect("the host turn must claim its scheduler role before execution");
23469
23470    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23471    let shutdown_controller = Arc::clone(&controller);
23472    let shutdown = std::thread::spawn(move || {
23473      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23474    });
23475    wait_until(
23476      "shutdown to close the admitted CurrentThread generation",
23477      || {
23478        let state = controller
23479          .state
23480          .lock()
23481          .unwrap_or_else(std::sync::PoisonError::into_inner);
23482        matches!(
23483          &state.lifecycle,
23484          RuntimeLifecycle::Stopping(identity) if identity.generation == generation
23485        )
23486      },
23487    );
23488    assert!(
23489      shutdown_rx
23490        .recv_timeout(Duration::from_millis(200))
23491        .is_err(),
23492      "shutdown must wait for an admitted host callback even before it starts draining"
23493    );
23494
23495    release_tx.send(()).unwrap();
23496    driver.join().unwrap();
23497    shutdown_rx
23498      .recv_timeout(Duration::from_secs(2))
23499      .unwrap()
23500      .unwrap();
23501    shutdown.join().unwrap();
23502  }
23503
23504  #[test]
23505  fn spawn_racing_shutdown_cannot_recreate_backend() {
23506    use std::sync::mpsc;
23507
23508    let controller = Arc::new(current_thread_controller("lifecycle-race"));
23509    controller.start().expect("runtime must start");
23510    drop(controller.backend());
23511
23512    let (at_hook_tx, at_hook_rx) = mpsc::channel();
23513    let (release_tx, release_rx) = mpsc::channel();
23514    let ran = Arc::new(AtomicBool::new(false));
23515    let ran_task = Arc::clone(&ran);
23516    let worker_controller = Arc::clone(&controller);
23517    let worker = std::thread::spawn(move || {
23518      BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
23519        *slot.borrow_mut() = Some(Box::new(move || {
23520          at_hook_tx
23521            .send(())
23522            .expect("test coordinator must still be listening");
23523          release_rx
23524            .recv()
23525            .expect("test coordinator must release the submission");
23526        }));
23527      });
23528
23529      let future = async move {
23530        ran_task.store(true, Ordering::SeqCst);
23531      };
23532      match worker_controller.try_spawn_detached(future) {
23533        Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
23534        Err(future) => drop(future),
23535      }
23536    });
23537
23538    at_hook_rx
23539      .recv_timeout(Duration::from_secs(5))
23540      .expect("submitting thread must stop before taking the controller lock");
23541    controller.shutdown().expect("runtime must stop");
23542    release_tx
23543      .send(())
23544      .expect("submitting thread must still be waiting");
23545    worker.join().expect("submitting thread must not panic");
23546
23547    assert!(
23548      !ran.load(Ordering::SeqCst),
23549      "the rejected task must never run"
23550    );
23551    let state = controller
23552      .state
23553      .lock()
23554      .unwrap_or_else(std::sync::PoisonError::into_inner);
23555    assert!(
23556      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
23557      "the rejected submission must not lazily recreate a backend"
23558    );
23559  }
23560
23561  #[cfg(not(target_family = "wasm"))]
23562  #[test]
23563  fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
23564    use std::sync::mpsc;
23565
23566    struct BlockingDropFuture {
23567      work: Arc<GenerationWork>,
23568      entered: mpsc::Sender<(Option<u64>, usize)>,
23569      release: mpsc::Receiver<()>,
23570    }
23571
23572    impl Future for BlockingDropFuture {
23573      type Output = ();
23574
23575      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23576        Poll::Pending
23577      }
23578    }
23579
23580    impl Drop for BlockingDropFuture {
23581      fn drop(&mut self) {
23582        let active = self
23583          .work
23584          .state
23585          .lock()
23586          .unwrap_or_else(std::sync::PoisonError::into_inner)
23587          .active;
23588        self
23589          .entered
23590          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
23591          .unwrap();
23592        self.release.recv().unwrap();
23593      }
23594    }
23595
23596    let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
23597    let backend = controller.backend();
23598    let old_generation = backend.generation();
23599    let work = Arc::clone(&backend.work);
23600    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
23601      panic!("configured CurrentThread backend must create a CurrentThread executor");
23602    };
23603    let executor = Arc::clone(executor);
23604    drop(backend);
23605
23606    let (registered_tx, registered_rx) = mpsc::channel();
23607    let (release_submission_tx, release_submission_rx) = mpsc::channel();
23608    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23609    let (release_drop_tx, release_drop_rx) = mpsc::channel();
23610    let submit_controller = Arc::clone(&controller);
23611    let submit_work = Arc::clone(&work);
23612    let submitter = std::thread::spawn(move || {
23613      AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
23614        *slot.borrow_mut() = Some(Box::new(move || {
23615          registered_tx.send(()).unwrap();
23616          release_submission_rx.recv().unwrap();
23617        }));
23618      });
23619      submit_controller
23620        .try_spawn_detached(BlockingDropFuture {
23621          work: submit_work,
23622          entered: drop_entered_tx,
23623          release: release_drop_rx,
23624        })
23625        .unwrap_or_else(|_| {
23626          panic!("the submission linearized before shutdown and must be accepted")
23627        });
23628    });
23629    registered_rx
23630      .recv_timeout(Duration::from_secs(2))
23631      .expect("the submitting thread must register generation work before shutdown");
23632
23633    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23634    let shutdown_controller = Arc::clone(&controller);
23635    let shutdown = std::thread::spawn(move || {
23636      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23637    });
23638    wait_until("CurrentThread shutdown to close the runnable queue", || {
23639      executor
23640        .queue
23641        .lock()
23642        .unwrap_or_else(std::sync::PoisonError::into_inner)
23643        .closed
23644    });
23645    release_submission_tx.send(()).unwrap();
23646
23647    let (drop_generation, active_during_drop) = drop_entered_rx
23648      .recv_timeout(Duration::from_secs(2))
23649      .expect("the closed queue must destroy the accepted unpolled future");
23650    assert_eq!(drop_generation, Some(old_generation));
23651    assert_eq!(
23652      active_during_drop, 1,
23653      "accepted generation work must remain registered through user future destruction"
23654    );
23655
23656    let (start_tx, start_rx) = mpsc::channel();
23657    let start_controller = Arc::clone(&controller);
23658    let starter = std::thread::spawn(move || {
23659      start_tx.send(start_controller.start()).unwrap();
23660    });
23661    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
23662    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
23663    let shutdown_overlapped = early_shutdown.is_some();
23664    let restart_overlapped = early_start.is_some();
23665
23666    release_drop_tx.send(()).unwrap();
23667    submitter.join().unwrap();
23668    let shutdown_result = early_shutdown.unwrap_or_else(|| {
23669      shutdown_rx
23670        .recv_timeout(Duration::from_secs(2))
23671        .expect("shutdown must finish after drop")
23672    });
23673    shutdown_result.unwrap();
23674    shutdown.join().unwrap();
23675    let start_result = early_start.unwrap_or_else(|| {
23676      start_rx
23677        .recv_timeout(Duration::from_secs(2))
23678        .expect("restart must follow shutdown")
23679    });
23680    start_result.unwrap();
23681    starter.join().unwrap();
23682
23683    assert!(
23684      !shutdown_overlapped,
23685      "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
23686    );
23687    assert!(
23688      !restart_overlapped,
23689      "restart must not create a new generation while an accepted old-generation future is being destroyed"
23690    );
23691    assert_ne!(controller.backend().generation(), old_generation);
23692    controller.shutdown().unwrap();
23693  }
23694
23695  #[cfg(not(target_family = "wasm"))]
23696  #[test]
23697  fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
23698    use std::sync::mpsc;
23699
23700    struct BlockingDrop {
23701      work: Arc<GenerationWork>,
23702      entered: mpsc::Sender<(Option<u64>, usize)>,
23703      release: mpsc::Receiver<()>,
23704    }
23705
23706    impl Drop for BlockingDrop {
23707      fn drop(&mut self) {
23708        let active = self
23709          .work
23710          .state
23711          .lock()
23712          .unwrap_or_else(std::sync::PoisonError::into_inner)
23713          .active;
23714        self
23715          .entered
23716          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
23717          .unwrap();
23718        self.release.recv().unwrap();
23719      }
23720    }
23721
23722    let controller = Arc::new(multi_thread_controller(
23723      "accepted-blocking-submission-drop",
23724      2,
23725      1,
23726    ));
23727    let backend = controller.backend();
23728    let old_generation = backend.generation();
23729    let work = Arc::clone(&backend.work);
23730    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23731      panic!("configured MultiThread backend must create a MultiThread executor");
23732    };
23733    let executor = Arc::downgrade(executor);
23734    drop(backend);
23735
23736    let (registered_tx, registered_rx) = mpsc::channel();
23737    let (release_submission_tx, release_submission_rx) = mpsc::channel();
23738    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23739    let (release_drop_tx, release_drop_rx) = mpsc::channel();
23740    let submit_controller = Arc::clone(&controller);
23741    let submitter = std::thread::spawn(move || {
23742      AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
23743        *slot.borrow_mut() = Some(Box::new(move || {
23744          registered_tx.send(()).unwrap();
23745          release_submission_rx.recv().unwrap();
23746        }));
23747      });
23748      let drop_signal = BlockingDrop {
23749        work,
23750        entered: drop_entered_tx,
23751        release: release_drop_rx,
23752      };
23753      let handle = submit_controller
23754        .try_spawn_blocking(move || {
23755          let _drop_signal = drop_signal;
23756        })
23757        .unwrap_or_else(|_| {
23758          panic!("the submission linearized before shutdown and must be accepted")
23759        });
23760      assert!(
23761        futures::executor::block_on(handle).is_err(),
23762        "the executor must cancel work that reaches its queue after shutdown closed it"
23763      );
23764    });
23765    registered_rx
23766      .recv_timeout(Duration::from_secs(2))
23767      .expect("the submitting thread must register generation work before shutdown");
23768
23769    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23770    let shutdown_controller = Arc::clone(&controller);
23771    let shutdown = std::thread::spawn(move || {
23772      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23773    });
23774    wait_until("MultiThread shutdown to close the blocking queue", || {
23775      executor
23776        .upgrade()
23777        .expect("the retiring executor must remain alive until shutdown completes")
23778        .blocking_queue
23779        .lock()
23780        .unwrap_or_else(std::sync::PoisonError::into_inner)
23781        .closed
23782    });
23783    release_submission_tx.send(()).unwrap();
23784
23785    let (drop_generation, active_during_drop) = drop_entered_rx
23786      .recv_timeout(Duration::from_secs(2))
23787      .expect("the closed queue must destroy the accepted blocking closure");
23788    assert_eq!(drop_generation, Some(old_generation));
23789    assert_eq!(
23790      active_during_drop, 1,
23791      "accepted generation work must remain registered through blocking closure destruction"
23792    );
23793
23794    let (start_tx, start_rx) = mpsc::channel();
23795    let start_controller = Arc::clone(&controller);
23796    let starter = std::thread::spawn(move || {
23797      start_tx.send(start_controller.start()).unwrap();
23798    });
23799    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
23800    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
23801    let shutdown_overlapped = early_shutdown.is_some();
23802    let restart_overlapped = early_start.is_some();
23803
23804    release_drop_tx.send(()).unwrap();
23805    submitter.join().unwrap();
23806    let shutdown_result = early_shutdown.unwrap_or_else(|| {
23807      shutdown_rx
23808        .recv_timeout(Duration::from_secs(2))
23809        .expect("shutdown must finish after drop")
23810    });
23811    shutdown_result.unwrap();
23812    shutdown.join().unwrap();
23813    let start_result = early_start.unwrap_or_else(|| {
23814      start_rx
23815        .recv_timeout(Duration::from_secs(2))
23816        .expect("restart must follow shutdown")
23817    });
23818    start_result.unwrap();
23819    starter.join().unwrap();
23820
23821    assert!(
23822      !shutdown_overlapped,
23823      "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
23824    );
23825    assert!(
23826      !restart_overlapped,
23827      "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
23828    );
23829    assert_ne!(controller.backend().generation(), old_generation);
23830    controller.shutdown().unwrap();
23831  }
23832
23833  #[cfg(not(target_family = "wasm"))]
23834  #[test]
23835  fn multi_thread_controller_reports_its_exact_physical_worker_count() {
23836    let controller = multi_thread_controller("lifecycle-physical", 1, 8);
23837    let options = controller.options();
23838    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
23839
23840    let backend = controller.backend();
23841    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23842      panic!("configured MultiThread backend must create a Rayon executor");
23843    };
23844    assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
23845    let lifecycle = Arc::clone(&executor.worker_lifecycle);
23846    drop(backend);
23847
23848    controller.shutdown().expect("runtime must stop");
23849    assert_eq!(
23850      lifecycle.remaining(),
23851      0,
23852      "shutdown must observe every physical worker exit"
23853    );
23854  }
23855
23856  #[cfg(not(target_family = "wasm"))]
23857  #[test]
23858  fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
23859    // Invariant: `shutdown()`'s worker retirement is a JOIN barrier that
23860    // includes worker TLS-destructor completion -- `wait_for_all_workers`
23861    // joins the OS threads, and an OS thread only becomes joinable after its
23862    // TLS destructors ran. Proven by RECORDING (an atomic bump) from each
23863    // worker's destructor and sampling the counter on the shutdown thread at
23864    // the very instant `shutdown()` returns: a barrier that excluded the
23865    // destructors would race that sample and fail loudly.
23866    //
23867    // The destructor must never do more than record. The previous shape
23868    // parked it on a condvar until the harness released it -- on Windows TLS
23869    // destructors run under the process loader lock, so the first parked
23870    // worker blocked the second from even ENTERING its destructor, the
23871    // release condition (both entered) became unsatisfiable, and the parked
23872    // destructor froze every thread spawn/exit in the process: the
23873    // whole-suite silent CI wedge (see the LOADER-LOCK RULE at
23874    // `WorkerTlsDropAction`).
23875    use std::sync::mpsc;
23876
23877    let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
23878    let backend = controller.backend();
23879    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23880      panic!("configured MultiThread backend must create a Rayon executor");
23881    };
23882    let worker_count = executor.pool.current_num_threads();
23883    let completed = Arc::new(AtomicUsize::new(0));
23884    let probe_completed = Arc::clone(&completed);
23885    executor.pool.broadcast(move |_| {
23886      WORKER_TLS_DROP_PROBE.with(|slot| {
23887        let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
23888          action: Some(WorkerTlsDropAction::Record {
23889            completed: Arc::clone(&probe_completed),
23890          }),
23891        });
23892        assert!(
23893          previous.is_none(),
23894          "each worker must install exactly one TLS drop probe"
23895        );
23896      });
23897    });
23898    drop(backend);
23899
23900    assert_eq!(
23901      completed.load(Ordering::SeqCst),
23902      0,
23903      "no worker TLS destructor may run before shutdown retires the workers"
23904    );
23905
23906    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23907    let shutdown_controller = Arc::clone(&controller);
23908    let sample_completed = Arc::clone(&completed);
23909    let shutdown = std::thread::spawn(move || {
23910      let result = shutdown_controller.shutdown();
23911      // Sample on the SAME thread, before any further synchronization could
23912      // mask a barrier gap: at this instant every destructor must already
23913      // have recorded itself.
23914      let seen_at_return = sample_completed.load(Ordering::SeqCst);
23915      shutdown_tx.send((result, seen_at_return)).unwrap();
23916    });
23917    let (shutdown_result, seen_at_return) = shutdown_rx
23918      .recv_timeout(Duration::from_secs(10))
23919      .expect("shutdown must finish once worker TLS destructors have run");
23920    shutdown_result.unwrap();
23921    assert_eq!(
23922      seen_at_return, worker_count,
23923      "every worker TLS destructor must have completed before shutdown() returned: \
23924       the worker-join barrier must include TLS-destructor completion"
23925    );
23926    join_within(
23927      "the shutdown thread exits after teardown",
23928      shutdown,
23929      Duration::from_secs(30),
23930    );
23931  }
23932
23933  #[cfg(not(target_family = "wasm"))]
23934  #[test]
23935  fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
23936    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
23937
23938    if std::env::var_os(CHILD_ENV).is_some() {
23939      use std::sync::mpsc;
23940
23941      let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
23942      let backend = controller.backend();
23943      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23944        panic!("configured MultiThread backend must create a Rayon executor");
23945      };
23946      let installed = Arc::new(AtomicBool::new(false));
23947      let (completed_tx, completed_rx) = mpsc::channel();
23948      let probe_controller = Arc::clone(&controller);
23949      let probe_installed = Arc::clone(&installed);
23950      executor.pool.broadcast(move |_| {
23951        if probe_installed.swap(true, Ordering::SeqCst) {
23952          return;
23953        }
23954        WORKER_TLS_DROP_PROBE.with(|slot| {
23955          let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
23956            action: Some(WorkerTlsDropAction::ReenterLifecycle {
23957              controller: Arc::clone(&probe_controller),
23958              completed: completed_tx.clone(),
23959            }),
23960          });
23961          assert!(
23962            previous.is_none(),
23963            "the selected worker must have no prior TLS probe"
23964          );
23965        });
23966      });
23967      assert!(
23968        installed.load(Ordering::SeqCst),
23969        "one worker must install the lifecycle probe"
23970      );
23971      drop(backend);
23972
23973      controller
23974        .shutdown()
23975        .expect("external shutdown must join the retiring workers");
23976      let (start, shutdown) = completed_rx
23977        .recv_timeout(Duration::from_secs(2))
23978        .expect("the TLS destructor must complete both lifecycle reentry attempts");
23979      assert_eq!(
23980        start.unwrap_err(),
23981        "cannot restart the async runtime from work in the generation being stopped"
23982      );
23983      assert_eq!(
23984        shutdown.unwrap_err(),
23985        "cannot wait for async runtime shutdown from work in the generation being stopped"
23986      );
23987      return;
23988    }
23989
23990    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
23991      .arg("--exact")
23992      .arg(
23993        "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
23994      )
23995      .arg("--nocapture")
23996      .env(CHILD_ENV, "1")
23997      .stdout(std::process::Stdio::piped())
23998      .stderr(std::process::Stdio::piped())
23999      .spawn()
24000      .expect("the TLS lifecycle-reentry subprocess must start");
24001    let deadline = Instant::now() + Duration::from_secs(5);
24002    let status = loop {
24003      if let Some(status) = child
24004        .try_wait()
24005        .expect("the subprocess status must be readable")
24006      {
24007        break status;
24008      }
24009      if Instant::now() >= deadline {
24010        let _ = child.kill();
24011        let output = child
24012          .wait_with_output()
24013          .expect("the timed-out subprocess must be reaped");
24014        panic!(
24015          "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
24016          String::from_utf8_lossy(&output.stdout),
24017          String::from_utf8_lossy(&output.stderr)
24018        );
24019      }
24020      std::thread::sleep(Duration::from_millis(10));
24021    };
24022    let output = child
24023      .wait_with_output()
24024      .expect("the completed subprocess output must be read");
24025    assert!(
24026      status.success(),
24027      "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
24028      status.code(),
24029      String::from_utf8_lossy(&output.stdout),
24030      String::from_utf8_lossy(&output.stderr)
24031    );
24032  }
24033
24034  #[cfg(not(target_family = "wasm"))]
24035  #[test]
24036  fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
24037    use std::sync::mpsc;
24038
24039    struct DropSignal(mpsc::Sender<()>);
24040    impl Drop for DropSignal {
24041      fn drop(&mut self) {
24042        let _ = self.0.send(());
24043      }
24044    }
24045
24046    let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
24047    let (polled_tx, polled_rx) = mpsc::channel();
24048    let (dropped_tx, dropped_rx) = mpsc::channel();
24049    controller
24050      .try_spawn_detached(async move {
24051        let _drop_signal = DropSignal(dropped_tx);
24052        polled_tx.send(()).unwrap();
24053        std::future::pending::<()>().await;
24054      })
24055      .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
24056    polled_rx
24057      .recv_timeout(Duration::from_secs(5))
24058      .expect("the pending task must be polled before shutdown");
24059
24060    controller
24061      .shutdown()
24062      .expect("shutdown must cancel accepted async work");
24063    dropped_rx
24064      .recv_timeout(Duration::from_secs(5))
24065      .expect("shutdown must drop the accepted task future before returning");
24066  }
24067
24068  #[cfg(not(target_family = "wasm"))]
24069  #[test]
24070  fn current_thread_shutdown_outranks_perpetual_self_wake() {
24071    use std::sync::mpsc;
24072
24073    struct SelfWakingPending {
24074      entered: Option<mpsc::Sender<()>>,
24075    }
24076
24077    impl Future for SelfWakingPending {
24078      type Output = ();
24079
24080      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24081        if let Some(entered) = self.entered.take() {
24082          entered.send(()).unwrap();
24083        }
24084        cx.waker().wake_by_ref();
24085        Poll::Pending
24086      }
24087    }
24088
24089    let controller = Arc::new(current_thread_controller(
24090      "current-thread-self-wake-shutdown",
24091    ));
24092    let (entered_tx, entered_rx) = mpsc::channel();
24093    let (outcome_tx, outcome_rx) = mpsc::channel();
24094    let block_on_controller = Arc::clone(&controller);
24095    let runner = std::thread::spawn(move || {
24096      let mut future = std::pin::pin!(SelfWakingPending {
24097        entered: Some(entered_tx)
24098      });
24099      outcome_tx
24100        .send(block_on_controller.block_on(future.as_mut()))
24101        .unwrap();
24102    });
24103    entered_rx
24104      .recv_timeout(Duration::from_secs(2))
24105      .expect("the self-waking future must be polled before shutdown");
24106
24107    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24108    let shutdown_controller = Arc::clone(&controller);
24109    let shutdown = std::thread::spawn(move || {
24110      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24111    });
24112    shutdown_rx
24113      .recv_timeout(Duration::from_secs(2))
24114      .expect("shutdown must outrank a stored self-wake permit")
24115      .unwrap();
24116    assert_eq!(
24117      outcome_rx
24118        .recv_timeout(Duration::from_secs(2))
24119        .expect("the raw block_on driver must report shutdown"),
24120      BlockOnOutcome::Stopped
24121    );
24122    runner.join().unwrap();
24123    shutdown.join().unwrap();
24124  }
24125
24126  #[cfg(not(target_family = "wasm"))]
24127  #[test]
24128  fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
24129    use std::sync::mpsc;
24130
24131    let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
24132    let backend = controller.backend();
24133    let executor = match &backend.executor {
24134      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
24135      RuntimeExecutor::CurrentThread(_) => unreachable!(),
24136    };
24137    let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
24138    let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
24139    for _ in 0..2 {
24140      let worker_ready_tx = worker_ready_tx.clone();
24141      let release_workers = Arc::clone(&release_workers);
24142      executor.pool.spawn(move || {
24143        worker_ready_tx.send(()).unwrap();
24144        let (released, changed) = &*release_workers;
24145        let mut released = released
24146          .lock()
24147          .unwrap_or_else(std::sync::PoisonError::into_inner);
24148        while !*released {
24149          released = changed
24150            .wait(released)
24151            .unwrap_or_else(std::sync::PoisonError::into_inner);
24152        }
24153      });
24154    }
24155    drop(worker_ready_tx);
24156    for _ in 0..2 {
24157      worker_ready_rx
24158        .recv_timeout(Duration::from_secs(5))
24159        .expect("both workers must be occupied before the FIFO runnable is submitted");
24160    }
24161    drop(executor);
24162    drop(backend);
24163
24164    let polls = Arc::new(AtomicUsize::new(0));
24165    let handle = controller.spawn(StopPollProbe {
24166      polls: Arc::clone(&polls),
24167      first_poll: None,
24168    });
24169    wait_until("the FIFO runnable to be queued", || {
24170      controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
24171    });
24172
24173    let observed_queue = Arc::new(Mutex::new(None));
24174    let hook_observed_queue = Arc::clone(&observed_queue);
24175    let hook_metrics = Arc::clone(&controller.metrics);
24176    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
24177      *slot.borrow_mut() = Some(Box::new(move || {
24178        let (released, changed) = &*release_workers;
24179        *released
24180          .lock()
24181          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
24182        changed.notify_all();
24183        wait_until("the stopped FIFO runnable claim", || {
24184          hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
24185        });
24186        *hook_observed_queue
24187          .lock()
24188          .unwrap_or_else(std::sync::PoisonError::into_inner) =
24189          Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
24190      }));
24191    });
24192
24193    controller
24194      .shutdown()
24195      .expect("shutdown must cancel the stopped FIFO claim");
24196    assert_eq!(polls.load(Ordering::SeqCst), 0);
24197    assert_eq!(
24198      *observed_queue
24199        .lock()
24200        .unwrap_or_else(std::sync::PoisonError::into_inner),
24201      Some(0)
24202    );
24203    assert_eq!(
24204      controller.metrics.queued_runnables.load(Ordering::SeqCst),
24205      0
24206    );
24207    drop(handle);
24208  }
24209
24210  #[cfg(not(target_family = "wasm"))]
24211  #[test]
24212  fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
24213    use std::sync::mpsc;
24214
24215    let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
24216    let backend = controller.backend();
24217    let executor = match &backend.executor {
24218      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
24219      RuntimeExecutor::CurrentThread(_) => unreachable!(),
24220    };
24221    let polls = Arc::new(AtomicUsize::new(0));
24222    let scheduler = Arc::downgrade(&executor);
24223    let (runnable, task) = async_task::spawn(
24224      StopPollProbe {
24225        polls: Arc::clone(&polls),
24226        first_poll: None,
24227      },
24228      move |runnable| {
24229        if let Some(executor) = scheduler.upgrade() {
24230          executor.schedule(runnable);
24231        }
24232      },
24233    );
24234    let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
24235    let (release_slot_tx, release_slot_rx) = mpsc::channel();
24236    let (claim_done_tx, claim_done_rx) = mpsc::channel();
24237    let worker_executor = Arc::clone(&executor);
24238    let worker_metrics = Arc::clone(&controller.metrics);
24239    executor.pool.spawn(move || {
24240      let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
24241      worker_executor.schedule(runnable);
24242      slot_ready_tx.send(()).unwrap();
24243      release_slot_rx.recv().unwrap();
24244      let claimed = worker_executor.run_one_runnable();
24245      claim_done_tx
24246        .send((
24247          claimed,
24248          worker_metrics.queued_runnables.load(Ordering::SeqCst),
24249        ))
24250        .unwrap();
24251    });
24252    slot_ready_rx
24253      .recv_timeout(Duration::from_secs(5))
24254      .expect("the worker must publish the LIFO runnable before shutdown");
24255    assert_eq!(
24256      controller.metrics.queued_runnables.load(Ordering::SeqCst),
24257      1
24258    );
24259    drop(executor);
24260    drop(backend);
24261
24262    let observed_claim = Arc::new(Mutex::new(None));
24263    let hook_observed_claim = Arc::clone(&observed_claim);
24264    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
24265      *slot.borrow_mut() = Some(Box::new(move || {
24266        release_slot_tx.send(()).unwrap();
24267        let result = claim_done_rx
24268          .recv_timeout(Duration::from_secs(5))
24269          .expect("the stopped LIFO runnable claim must complete before abort");
24270        *hook_observed_claim
24271          .lock()
24272          .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
24273      }));
24274    });
24275
24276    controller
24277      .shutdown()
24278      .expect("shutdown must cancel the stopped LIFO claim");
24279    assert_eq!(polls.load(Ordering::SeqCst), 0);
24280    assert_eq!(
24281      *observed_claim
24282        .lock()
24283        .unwrap_or_else(std::sync::PoisonError::into_inner),
24284      Some((true, 0))
24285    );
24286    assert_eq!(
24287      controller.metrics.queued_runnables.load(Ordering::SeqCst),
24288      0
24289    );
24290    drop(task);
24291  }
24292
24293  #[cfg(not(target_family = "wasm"))]
24294  #[test]
24295  fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
24296    use std::sync::mpsc;
24297
24298    struct BlockingDrop {
24299      entered: mpsc::Sender<()>,
24300      release: mpsc::Receiver<()>,
24301    }
24302
24303    impl Drop for BlockingDrop {
24304      fn drop(&mut self) {
24305        self.entered.send(()).unwrap();
24306        self.release.recv().unwrap();
24307      }
24308    }
24309
24310    struct TwoPollProbe {
24311      first_poll: Option<mpsc::Sender<()>>,
24312      release_first_poll: mpsc::Receiver<()>,
24313      second_poll: mpsc::Sender<()>,
24314    }
24315
24316    impl Future for TwoPollProbe {
24317      type Output = ();
24318
24319      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24320        if let Some(first_poll) = self.first_poll.take() {
24321          first_poll.send(()).unwrap();
24322          self.release_first_poll.recv().unwrap();
24323        } else {
24324          self.second_poll.send(()).unwrap();
24325        }
24326        cx.waker().wake_by_ref();
24327        Poll::Pending
24328      }
24329    }
24330
24331    let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
24332    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24333    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24334    let blocking_drop = BlockingDrop {
24335      entered: drop_entered_tx,
24336      release: release_drop_rx,
24337    };
24338    controller
24339      .try_spawn_detached(async move {
24340        let _drop = blocking_drop;
24341        std::future::pending::<()>().await;
24342      })
24343      .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
24344
24345    let (first_poll_tx, first_poll_rx) = mpsc::channel();
24346    let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
24347    let (second_poll_tx, second_poll_rx) = mpsc::channel();
24348    let (outcome_tx, outcome_rx) = mpsc::channel();
24349    let runner_controller = Arc::clone(&controller);
24350    let runner = std::thread::spawn(move || {
24351      let mut future = std::pin::pin!(TwoPollProbe {
24352        first_poll: Some(first_poll_tx),
24353        release_first_poll: release_first_poll_rx,
24354        second_poll: second_poll_tx,
24355      });
24356      outcome_tx
24357        .send(runner_controller.block_on(future.as_mut()))
24358        .unwrap();
24359    });
24360    first_poll_rx
24361      .recv_timeout(Duration::from_secs(2))
24362      .expect("the active block_on poll must enter before shutdown");
24363
24364    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24365    let shutdown_controller = Arc::clone(&controller);
24366    let shutdown = std::thread::spawn(move || {
24367      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24368    });
24369    drop_entered_rx
24370      .recv_timeout(Duration::from_secs(2))
24371      .expect("shutdown must begin destroying queued work");
24372
24373    release_first_poll_tx.send(()).unwrap();
24374    let polled_again = second_poll_rx
24375      .recv_timeout(Duration::from_millis(200))
24376      .is_ok();
24377    release_drop_tx.send(()).unwrap();
24378
24379    assert!(
24380      !polled_again,
24381      "an active block_on driver must observe generation stop before queued user cleanup blocks"
24382    );
24383    assert_eq!(
24384      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24385      BlockOnOutcome::Stopped
24386    );
24387    shutdown_rx
24388      .recv_timeout(Duration::from_secs(2))
24389      .unwrap()
24390      .unwrap();
24391    runner.join().unwrap();
24392    shutdown.join().unwrap();
24393  }
24394
24395  #[cfg(not(target_family = "wasm"))]
24396  #[test]
24397  fn shutdown_cancels_nested_block_on_within_deadline() {
24398    use std::sync::mpsc;
24399
24400    let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
24401    let task_controller = Arc::clone(&controller);
24402    let (entered_tx, entered_rx) = mpsc::channel();
24403    let task = controller
24404      .try_spawn(async move {
24405        entered_tx.send(()).unwrap();
24406        block_on_with_controller(&task_controller, std::future::pending::<()>());
24407      })
24408      .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
24409    entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
24410
24411    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24412    let shutdown_controller = Arc::clone(&controller);
24413    let shutdown = std::thread::spawn(move || {
24414      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24415    });
24416    shutdown_rx
24417      .recv_timeout(Duration::from_secs(2))
24418      .expect("shutdown must wake and cancel an active nested block_on")
24419      .unwrap();
24420    shutdown.join().unwrap();
24421    assert!(
24422      futures::executor::block_on(task).is_err(),
24423      "the cancelled nested block_on task must resolve as stopped"
24424    );
24425  }
24426
24427  #[cfg(not(target_family = "wasm"))]
24428  #[test]
24429  fn stopped_public_block_on_drops_future_before_generation_retirement() {
24430    use std::sync::mpsc;
24431
24432    struct PendingDrop {
24433      entered: Option<mpsc::Sender<()>>,
24434      dropped: mpsc::Sender<Option<u64>>,
24435      release: mpsc::Receiver<()>,
24436    }
24437
24438    impl Future for PendingDrop {
24439      type Output = ();
24440
24441      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24442        if let Some(entered) = self.entered.take() {
24443          entered.send(()).unwrap();
24444        }
24445        Poll::Pending
24446      }
24447    }
24448
24449    impl Drop for PendingDrop {
24450      fn drop(&mut self) {
24451        self
24452          .dropped
24453          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24454          .unwrap();
24455        self.release.recv().unwrap();
24456      }
24457    }
24458
24459    let controller = Arc::new(current_thread_controller(
24460      "public-block-on-generation-destruction",
24461    ));
24462    let generation = controller.backend().generation();
24463    let (entered_tx, entered_rx) = mpsc::channel();
24464    let (dropped_tx, dropped_rx) = mpsc::channel();
24465    let (release_tx, release_rx) = mpsc::channel();
24466    let (runner_done_tx, runner_done_rx) = mpsc::channel();
24467    let block_controller = Arc::clone(&controller);
24468    let runner = std::thread::spawn(move || {
24469      let result = catch_unwind(AssertUnwindSafe(|| {
24470        block_on_with_controller(
24471          &block_controller,
24472          PendingDrop {
24473            entered: Some(entered_tx),
24474            dropped: dropped_tx,
24475            release: release_rx,
24476          },
24477        );
24478      }));
24479      runner_done_tx.send(result.is_err()).unwrap();
24480    });
24481    entered_rx
24482      .recv_timeout(Duration::from_secs(2))
24483      .expect("the public block_on future must be polled before shutdown");
24484
24485    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24486    let shutdown_controller = Arc::clone(&controller);
24487    let shutdown = std::thread::spawn(move || {
24488      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24489    });
24490
24491    assert_eq!(
24492      dropped_rx
24493        .recv_timeout(Duration::from_secs(2))
24494        .expect("shutdown must wake block_on and begin destroying its future"),
24495      Some(generation),
24496      "the stopped future must be destroyed under its retiring generation identity"
24497    );
24498    assert!(
24499      shutdown_rx
24500        .recv_timeout(Duration::from_millis(200))
24501        .is_err(),
24502      "shutdown must not publish Stopped while the public block_on future destructor is running"
24503    );
24504
24505    release_tx.send(()).unwrap();
24506    assert!(
24507      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24508      "the public helper must retain its existing early-stop panic behavior"
24509    );
24510    shutdown_rx
24511      .recv_timeout(Duration::from_secs(2))
24512      .unwrap()
24513      .unwrap();
24514    runner.join().unwrap();
24515    shutdown.join().unwrap();
24516  }
24517
24518  #[cfg(not(target_family = "wasm"))]
24519  #[test]
24520  fn shutdown_contains_panicking_nested_block_on_future_drop() {
24521    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
24522
24523    if std::env::var_os(CHILD_ENV).is_some() {
24524      use std::sync::mpsc;
24525
24526      struct PendingPanicOnDrop {
24527        entered: Option<mpsc::Sender<()>>,
24528      }
24529
24530      impl Future for PendingPanicOnDrop {
24531        type Output = ();
24532
24533        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24534          if let Some(entered) = self.entered.take() {
24535            entered.send(()).unwrap();
24536          }
24537          Poll::Pending
24538        }
24539      }
24540
24541      impl Drop for PendingPanicOnDrop {
24542        fn drop(&mut self) {
24543          panic!("intentional nested block_on future destructor panic");
24544        }
24545      }
24546
24547      let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
24548      let task_controller = Arc::clone(&controller);
24549      let (entered_tx, entered_rx) = mpsc::channel();
24550      let task = controller
24551        .try_spawn(async move {
24552          block_on_with_controller(
24553            &task_controller,
24554            PendingPanicOnDrop {
24555              entered: Some(entered_tx),
24556            },
24557          );
24558        })
24559        .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
24560      entered_rx
24561        .recv_timeout(Duration::from_secs(2))
24562        .expect("the nested block_on future must be polled before shutdown");
24563
24564      controller
24565        .shutdown()
24566        .expect("shutdown must contain nested future destruction");
24567      assert!(
24568        futures::executor::block_on(task).is_err(),
24569        "shutdown cancellation must still surface as a task error"
24570      );
24571      return;
24572    }
24573
24574    let output = std::process::Command::new(std::env::current_exe().unwrap())
24575      .arg("--exact")
24576      .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
24577      .arg("--nocapture")
24578      .env(CHILD_ENV, "1")
24579      .output()
24580      .expect("the subprocess test must start");
24581    assert!(
24582      output.status.success(),
24583      "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
24584      output.status.code(),
24585      String::from_utf8_lossy(&output.stdout),
24586      String::from_utf8_lossy(&output.stderr)
24587    );
24588  }
24589
24590  #[test]
24591  fn panicking_future_drop_is_contained_before_async_task_drop() {
24592    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
24593
24594    if std::env::var_os(CHILD_ENV).is_some() {
24595      struct PanicOnDrop;
24596      impl Drop for PanicOnDrop {
24597        fn drop(&mut self) {
24598          panic!("intentional future destructor panic");
24599        }
24600      }
24601
24602      let controller = current_thread_controller("future-drop-subprocess");
24603      let bomb = PanicOnDrop;
24604      controller
24605        .try_spawn_detached(async move {
24606          let _bomb = bomb;
24607          std::future::pending::<()>().await;
24608        })
24609        .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
24610      controller
24611        .shutdown()
24612        .expect("shutdown must contain the future destructor panic");
24613      return;
24614    }
24615
24616    let output = std::process::Command::new(std::env::current_exe().unwrap())
24617      .arg("--exact")
24618      .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
24619      .arg("--nocapture")
24620      .env(CHILD_ENV, "1")
24621      .output()
24622      .expect("the subprocess test must start");
24623    assert!(
24624      output.status.success(),
24625      "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
24626      output.status.code(),
24627      String::from_utf8_lossy(&output.stdout),
24628      String::from_utf8_lossy(&output.stderr)
24629    );
24630  }
24631
24632  #[test]
24633  fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
24634    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
24635
24636    if std::env::var_os(CHILD_ENV).is_some() {
24637      struct PanicOnDrop;
24638      impl Drop for PanicOnDrop {
24639        fn drop(&mut self) {
24640          panic!("intentional detached output destructor panic");
24641        }
24642      }
24643
24644      let controller = current_thread_controller("output-drop-subprocess");
24645      let handle = controller
24646        .try_spawn(async { PanicOnDrop })
24647        .unwrap_or_else(|_| panic!("runtime must accept the task"));
24648      drop(handle);
24649      let dispatch = controller_current_thread_dispatch(&controller);
24650      controller.drive_current_thread_dispatch(dispatch, false);
24651      controller.shutdown().unwrap();
24652      return;
24653    }
24654
24655    let output = std::process::Command::new(std::env::current_exe().unwrap())
24656      .arg("--exact")
24657      .arg(
24658        "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
24659      )
24660      .arg("--nocapture")
24661      .env(CHILD_ENV, "1")
24662      .output()
24663      .expect("the subprocess test must start");
24664    assert!(
24665      output.status.success(),
24666      "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
24667      output.status.code(),
24668      String::from_utf8_lossy(&output.stdout),
24669      String::from_utf8_lossy(&output.stderr)
24670    );
24671  }
24672
24673  #[cfg(not(target_family = "wasm"))]
24674  #[test]
24675  fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
24676    use std::sync::mpsc;
24677
24678    struct BlockingDrop {
24679      controller: Arc<RuntimeController>,
24680      entered: mpsc::Sender<Option<u64>>,
24681      release: mpsc::Receiver<()>,
24682      reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
24683    }
24684
24685    impl Drop for BlockingDrop {
24686      fn drop(&mut self) {
24687        self
24688          .entered
24689          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24690          .unwrap();
24691        self.release.recv().unwrap();
24692        self
24693          .reentrant_shutdown
24694          .send(self.controller.shutdown())
24695          .unwrap();
24696      }
24697    }
24698
24699    let controller = Arc::new(current_thread_controller(
24700      "current-thread-host-drain-output-retirement",
24701    ));
24702    let old_generation = controller.backend().generation();
24703    let (entered_tx, entered_rx) = mpsc::channel();
24704    let (release_tx, release_rx) = mpsc::channel();
24705    let (reentrant_tx, reentrant_rx) = mpsc::channel();
24706    let output_controller = Arc::clone(&controller);
24707    let handle = controller
24708      .try_spawn(async move {
24709        BlockingDrop {
24710          controller: output_controller,
24711          entered: entered_tx,
24712          release: release_rx,
24713          reentrant_shutdown: reentrant_tx,
24714        }
24715      })
24716      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
24717    drop(handle);
24718    let dispatch = controller_current_thread_dispatch(&controller);
24719
24720    let driver_controller = Arc::clone(&controller);
24721    let driver =
24722      std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
24723    assert_eq!(
24724      entered_rx
24725        .recv_timeout(Duration::from_secs(2))
24726        .expect("the host turn must begin destroying the detached output"),
24727      Some(old_generation),
24728      "detached output destruction must retain the generation that produced it"
24729    );
24730
24731    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24732    let shutdown_controller = Arc::clone(&controller);
24733    let shutdown = std::thread::spawn(move || {
24734      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24735    });
24736    wait_until(
24737      "CurrentThread shutdown to enter the old generation's stopping state",
24738      || {
24739        let state = controller
24740          .state
24741          .lock()
24742          .unwrap_or_else(std::sync::PoisonError::into_inner);
24743        matches!(
24744          &state.lifecycle,
24745          RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
24746        )
24747      },
24748    );
24749
24750    let (restart_tx, restart_rx) = mpsc::channel();
24751    let restart_controller = Arc::clone(&controller);
24752    let restart = std::thread::spawn(move || {
24753      restart_controller.start().unwrap();
24754      restart_tx
24755        .send(restart_controller.backend().generation())
24756        .unwrap();
24757    });
24758
24759    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
24760    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
24761    let shutdown_overlapped = early_shutdown.is_some();
24762    let restart_overlapped = early_restart.is_some();
24763    release_tx.send(()).unwrap();
24764
24765    let reentrant_error = reentrant_rx
24766      .recv_timeout(Duration::from_secs(2))
24767      .expect("the retiring output destructor must finish its lifecycle check")
24768      .expect_err("old-generation output destruction must not shut down a replacement runtime");
24769    assert!(
24770      reentrant_error
24771        .to_string()
24772        .contains("generation being stopped"),
24773      "the destructor must still observe its old generation as Stopping: {reentrant_error}"
24774    );
24775
24776    let shutdown_result = early_shutdown.unwrap_or_else(|| {
24777      shutdown_rx
24778        .recv_timeout(Duration::from_secs(2))
24779        .expect("shutdown must finish after detached output destruction retires")
24780    });
24781    shutdown_result.unwrap();
24782    let new_generation = early_restart.unwrap_or_else(|| {
24783      restart_rx
24784        .recv_timeout(Duration::from_secs(2))
24785        .expect("restart must finish after the old host turn retires")
24786    });
24787
24788    driver.join().unwrap();
24789    shutdown.join().unwrap();
24790    restart.join().unwrap();
24791    assert!(
24792      !shutdown_overlapped,
24793      "shutdown must not publish Stopped while a host turn is destroying detached output"
24794    );
24795    assert!(
24796      !restart_overlapped,
24797      "restart must not overlap a host turn from the retiring generation"
24798    );
24799    assert_ne!(new_generation, old_generation);
24800    controller.shutdown().unwrap();
24801  }
24802
24803  #[cfg(not(target_family = "wasm"))]
24804  #[test]
24805  fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
24806    use std::sync::mpsc;
24807
24808    struct ShutdownOnDrop {
24809      controller: Arc<RuntimeController>,
24810      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
24811    }
24812
24813    impl Drop for ShutdownOnDrop {
24814      fn drop(&mut self) {
24815        let _ = self.result.send(self.controller.shutdown());
24816      }
24817    }
24818
24819    let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
24820    let (running_tx, running_rx) = mpsc::channel();
24821    let (release_tx, release_rx) = mpsc::channel();
24822    let running = controller
24823      .try_spawn_blocking(move || {
24824        running_tx.send(()).unwrap();
24825        release_rx.recv().unwrap();
24826      })
24827      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
24828    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
24829
24830    let (reentrant_tx, reentrant_rx) = mpsc::channel();
24831    let bomb = ShutdownOnDrop {
24832      controller: Arc::clone(&controller),
24833      result: reentrant_tx,
24834    };
24835    let queued = controller
24836      .try_spawn_blocking(move || {
24837        let _bomb = &bomb;
24838      })
24839      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
24840
24841    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24842    let shutdown_controller = Arc::clone(&controller);
24843    let shutdown = std::thread::spawn(move || {
24844      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24845    });
24846    let error = reentrant_rx
24847      .recv_timeout(Duration::from_secs(2))
24848      .expect("queued destruction must not self-deadlock")
24849      .expect_err("the retiring generation must reject reentrant shutdown");
24850    assert!(error.to_string().contains("generation being stopped"));
24851    release_tx.send(()).unwrap();
24852    shutdown_rx
24853      .recv_timeout(Duration::from_secs(2))
24854      .unwrap()
24855      .unwrap();
24856    shutdown.join().unwrap();
24857    futures::executor::block_on(running).unwrap();
24858    assert!(futures::executor::block_on(queued).is_err());
24859  }
24860
24861  #[cfg(not(target_family = "wasm"))]
24862  #[test]
24863  fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
24864    use std::sync::mpsc;
24865
24866    struct ShutdownAfterRelease {
24867      controller: Arc<RuntimeController>,
24868      entered: mpsc::Sender<()>,
24869      release: mpsc::Receiver<()>,
24870      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
24871    }
24872
24873    impl Drop for ShutdownAfterRelease {
24874      fn drop(&mut self) {
24875        self.entered.send(()).unwrap();
24876        self.release.recv().unwrap();
24877        self.result.send(self.controller.shutdown()).unwrap();
24878      }
24879    }
24880
24881    let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
24882    let (holder_started_tx, holder_started_rx) = mpsc::channel();
24883    let (release_holder_tx, release_holder_rx) = mpsc::channel();
24884    let holder = controller
24885      .try_spawn_blocking(move || {
24886        holder_started_tx.send(()).unwrap();
24887        release_holder_rx.recv().unwrap();
24888      })
24889      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
24890    holder_started_rx
24891      .recv_timeout(Duration::from_secs(2))
24892      .unwrap();
24893
24894    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24895    let shutdown_controller = Arc::clone(&controller);
24896    let shutdown = std::thread::spawn(move || {
24897      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24898    });
24899    wait_until("the runtime entered Stopping", || {
24900      matches!(
24901        controller
24902          .state
24903          .lock()
24904          .unwrap_or_else(std::sync::PoisonError::into_inner)
24905          .lifecycle,
24906        RuntimeLifecycle::Stopping(_)
24907      )
24908    });
24909
24910    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24911    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24912    let (reentrant_tx, reentrant_rx) = mpsc::channel();
24913    let rejected_controller = Arc::clone(&controller);
24914    let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
24915    let rejected = std::thread::spawn(move || {
24916      let bomb = ShutdownAfterRelease {
24917        controller: Arc::clone(&rejected_controller),
24918        entered: drop_entered_tx,
24919        release: release_drop_rx,
24920        result: reentrant_tx,
24921      };
24922      let handle = rejected_controller.spawn(async move {
24923        let _bomb = bomb;
24924        std::future::pending::<()>().await;
24925      });
24926      rejected_done_tx
24927        .send(futures::executor::block_on(handle))
24928        .unwrap();
24929    });
24930    drop_entered_rx
24931      .recv_timeout(Duration::from_secs(2))
24932      .unwrap();
24933
24934    release_holder_tx.send(()).unwrap();
24935    assert!(
24936      shutdown_rx
24937        .recv_timeout(Duration::from_millis(200))
24938        .is_err(),
24939      "shutdown crossed into Stopped before rejected destruction finished"
24940    );
24941
24942    let (start_tx, start_rx) = mpsc::channel();
24943    let start_controller = Arc::clone(&controller);
24944    let starter = std::thread::spawn(move || {
24945      start_tx.send(start_controller.start()).unwrap();
24946    });
24947    assert!(
24948      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
24949      "restart crossed into a new generation before rejected destruction finished"
24950    );
24951
24952    release_drop_tx.send(()).unwrap();
24953    let error = reentrant_rx
24954      .recv_timeout(Duration::from_secs(2))
24955      .expect("the rejected destructor must complete")
24956      .expect_err("the retiring generation must reject the destructor's shutdown");
24957    assert!(error.to_string().contains("generation being stopped"));
24958    assert!(
24959      rejected_done_rx
24960        .recv_timeout(Duration::from_secs(2))
24961        .unwrap()
24962        .is_err()
24963    );
24964    rejected.join().unwrap();
24965
24966    shutdown_rx
24967      .recv_timeout(Duration::from_secs(2))
24968      .unwrap()
24969      .unwrap();
24970    shutdown.join().unwrap();
24971    start_rx
24972      .recv_timeout(Duration::from_secs(2))
24973      .unwrap()
24974      .unwrap();
24975    starter.join().unwrap();
24976    futures::executor::block_on(holder).unwrap();
24977    controller.shutdown().unwrap();
24978  }
24979
24980  #[cfg(not(target_family = "wasm"))]
24981  #[test]
24982  fn shutdown_contains_hostile_caught_panic_payload() {
24983    use std::{panic::panic_any, sync::mpsc};
24984
24985    struct PanicAgain;
24986    impl Drop for PanicAgain {
24987      fn drop(&mut self) {
24988        panic!("caught panic payload destructor escaped");
24989      }
24990    }
24991
24992    struct PanicPayloadOnDrop(mpsc::Sender<()>);
24993    impl Drop for PanicPayloadOnDrop {
24994      fn drop(&mut self) {
24995        let _ = self.0.send(());
24996        panic_any(PanicAgain);
24997      }
24998    }
24999
25000    let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
25001    let (running_tx, running_rx) = mpsc::channel();
25002    let (release_tx, release_rx) = mpsc::channel();
25003    let running = controller
25004      .try_spawn_blocking(move || {
25005        running_tx.send(()).unwrap();
25006        release_rx.recv().unwrap();
25007      })
25008      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
25009    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25010
25011    let (drop_tx, drop_rx) = mpsc::channel();
25012    let payload = PanicPayloadOnDrop(drop_tx);
25013    let queued = controller
25014      .try_spawn_blocking(move || {
25015        let _payload = &payload;
25016      })
25017      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
25018
25019    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25020    let shutdown_controller = Arc::clone(&controller);
25021    let shutdown = std::thread::spawn(move || {
25022      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25023    });
25024    drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
25025    release_tx.send(()).unwrap();
25026    shutdown_rx
25027      .recv_timeout(Duration::from_secs(2))
25028      .expect("hostile panic payload destruction must remain contained")
25029      .unwrap();
25030    shutdown
25031      .join()
25032      .expect("the caught payload must not panic during destruction");
25033    futures::executor::block_on(running).unwrap();
25034    assert!(futures::executor::block_on(queued).is_err());
25035  }
25036
25037  #[cfg(not(target_family = "wasm"))]
25038  #[test]
25039  fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
25040    use std::sync::mpsc;
25041
25042    struct DropSignal(mpsc::Sender<()>);
25043    impl Drop for DropSignal {
25044      fn drop(&mut self) {
25045        let _ = self.0.send(());
25046      }
25047    }
25048
25049    let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
25050    let (running_tx, running_rx) = mpsc::channel();
25051    let (release_tx, release_rx) = mpsc::channel();
25052    let running = controller
25053      .try_spawn_blocking(move || {
25054        running_tx.send(()).unwrap();
25055        release_rx.recv().unwrap();
25056        7usize
25057      })
25058      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25059    running_rx
25060      .recv_timeout(Duration::from_secs(5))
25061      .expect("the first blocking job must hold the only blocking slot");
25062
25063    let queued_ran = Arc::new(AtomicBool::new(false));
25064    let queued_ran_work = Arc::clone(&queued_ran);
25065    let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
25066    let queued_drop_signal = DropSignal(queued_dropped_tx);
25067    let queued = controller
25068      .try_spawn_blocking(move || {
25069        let _drop_signal = queued_drop_signal;
25070        queued_ran_work.store(true, Ordering::SeqCst);
25071        11usize
25072      })
25073      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
25074
25075    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25076    let shutdown_controller = Arc::clone(&controller);
25077    let shutdown = std::thread::spawn(move || {
25078      shutdown_controller.shutdown().unwrap();
25079      shutdown_done_tx.send(()).unwrap();
25080    });
25081
25082    queued_dropped_rx
25083      .recv_timeout(Duration::from_secs(5))
25084      .expect("shutdown must drop queued blocking work");
25085    assert!(
25086      !queued_ran.load(Ordering::SeqCst),
25087      "queued blocking work must not execute"
25088    );
25089    assert!(
25090      shutdown_done_rx.try_recv().is_err(),
25091      "shutdown must wait while an accepted blocking closure is still running"
25092    );
25093
25094    release_tx.send(()).unwrap();
25095    shutdown_done_rx
25096      .recv_timeout(Duration::from_secs(5))
25097      .expect("shutdown must finish after running blocking work retires");
25098    shutdown.join().unwrap();
25099    assert_eq!(futures::executor::block_on(running).unwrap(), 7);
25100    assert!(
25101      futures::executor::block_on(queued).is_err(),
25102      "the queued blocking handle must resolve as cancelled"
25103    );
25104  }
25105
25106  #[cfg(not(target_family = "wasm"))]
25107  #[test]
25108  fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
25109    use std::sync::mpsc;
25110
25111    struct PanicOnDrop(mpsc::Sender<()>);
25112    impl Drop for PanicOnDrop {
25113      fn drop(&mut self) {
25114        let _ = self.0.send(());
25115        panic!("intentional queued blocking destructor panic");
25116      }
25117    }
25118
25119    let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
25120    let (running_tx, running_rx) = mpsc::channel();
25121    let (release_tx, release_rx) = mpsc::channel();
25122    let running = controller
25123      .try_spawn_blocking(move || {
25124        running_tx.send(()).unwrap();
25125        release_rx.recv().unwrap();
25126      })
25127      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25128    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25129
25130    let (drop_tx, drop_rx) = mpsc::channel();
25131    let panic_on_drop = PanicOnDrop(drop_tx);
25132    let queued = controller
25133      .try_spawn_blocking(move || {
25134        let _keep_captured_until_run = &panic_on_drop;
25135      })
25136      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
25137
25138    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25139    let shutdown_controller = Arc::clone(&controller);
25140    let shutdown = std::thread::spawn(move || {
25141      shutdown_controller.shutdown().unwrap();
25142      shutdown_done_tx.send(()).unwrap();
25143    });
25144
25145    drop_rx
25146      .recv_timeout(Duration::from_secs(5))
25147      .expect("shutdown must attempt to drop the queued closure");
25148    release_tx.send(()).unwrap();
25149    shutdown_done_rx
25150      .recv_timeout(Duration::from_secs(5))
25151      .expect("a panicking queued destructor must not strand shutdown");
25152    shutdown
25153      .join()
25154      .expect("shutdown must contain the destructor panic");
25155    futures::executor::block_on(running).unwrap();
25156    assert!(futures::executor::block_on(queued).is_err());
25157
25158    controller
25159      .start()
25160      .expect("the stopped controller must restart");
25161    let restarted = controller
25162      .try_spawn_blocking(|| 17usize)
25163      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
25164    assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
25165    controller.shutdown().unwrap();
25166  }
25167
25168  #[cfg(not(target_family = "wasm"))]
25169  #[test]
25170  fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
25171    use std::{panic::panic_any, sync::mpsc};
25172
25173    struct ReentrantPayload {
25174      controller: Arc<RuntimeController>,
25175      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
25176    }
25177
25178    impl Drop for ReentrantPayload {
25179      fn drop(&mut self) {
25180        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
25181        let shutdown = self.controller.shutdown();
25182        self.result.send((generation, shutdown)).unwrap();
25183      }
25184    }
25185
25186    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
25187
25188    impl Drop for PanicPayloadOnDrop {
25189      fn drop(&mut self) {
25190        panic_any(self.0.take().expect("the result must panic only once"));
25191      }
25192    }
25193
25194    let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
25195    let generation = controller.backend().generation();
25196    let (started_tx, started_rx) = mpsc::channel();
25197    let (release_tx, release_rx) = mpsc::channel();
25198    let (result_tx, result_rx) = mpsc::channel();
25199    let payload_controller = Arc::clone(&controller);
25200    let handle = controller
25201      .try_spawn_blocking(move || {
25202        started_tx.send(()).unwrap();
25203        release_rx.recv().unwrap();
25204        PanicPayloadOnDrop(Some(ReentrantPayload {
25205          controller: payload_controller,
25206          result: result_tx,
25207        }))
25208      })
25209      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25210    started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25211
25212    // Detach the receiver before the result is delivered. Sending then drops
25213    // the result on the worker, whose destructor intentionally panics.
25214    drop(handle);
25215    release_tx.send(()).unwrap();
25216    let (observed_generation, reentrant_shutdown) = result_rx
25217      .recv_timeout(Duration::from_secs(5))
25218      .expect("the dropped result panic payload must not self-deadlock");
25219    assert_eq!(observed_generation, Some(generation));
25220    let error =
25221      reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
25222    assert!(error.to_string().contains("work running on that runtime"));
25223
25224    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25225    let shutdown_controller = Arc::clone(&controller);
25226    let shutdown = std::thread::spawn(move || {
25227      shutdown_controller.shutdown().unwrap();
25228      shutdown_done_tx.send(()).unwrap();
25229    });
25230    shutdown_done_rx
25231      .recv_timeout(Duration::from_secs(5))
25232      .expect("a dropped-result destructor panic must not strand shutdown");
25233    shutdown
25234      .join()
25235      .expect("shutdown must contain the destructor panic");
25236
25237    controller
25238      .start()
25239      .expect("the stopped controller must restart");
25240    let restarted = controller
25241      .try_spawn_blocking(|| 19usize)
25242      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
25243    assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
25244    controller.shutdown().unwrap();
25245  }
25246
25247  #[cfg(not(target_family = "wasm"))]
25248  #[test]
25249  fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
25250    use std::sync::mpsc;
25251
25252    struct PanicOnDrop(mpsc::Sender<()>);
25253
25254    impl Drop for PanicOnDrop {
25255      fn drop(&mut self) {
25256        self.0.send(()).unwrap();
25257        panic!("intentional buffered blocking result destructor panic");
25258      }
25259    }
25260
25261    let options = RuntimeOptions {
25262      flavor: RuntimeFlavor::MultiThread,
25263      worker_threads: 2,
25264      max_blocking_tasks: 1,
25265      thread_name_prefix: "buffered-blocking-result-drop".to_string(),
25266      park_deadline: None,
25267    };
25268    let executor =
25269      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25270    let (started_tx, started_rx) = mpsc::channel();
25271    let (dropped_tx, dropped_rx) = mpsc::channel();
25272    let handle = executor.schedule_blocking(move || {
25273      started_tx.send(()).unwrap();
25274      PanicOnDrop(dropped_tx)
25275    });
25276    started_rx
25277      .recv_timeout(Duration::from_secs(2))
25278      .expect("the blocking closure must run");
25279    wait_until("the completed blocking result to be buffered", || {
25280      executor.active_blocking.load(Ordering::Acquire) == 0
25281    });
25282    assert!(
25283      dropped_rx.try_recv().is_err(),
25284      "the buffered result must remain owned by the live receiver"
25285    );
25286
25287    let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
25288    assert!(
25289      dropped.is_ok(),
25290      "dropping the buffered result must contain its destructor panic"
25291    );
25292    dropped_rx
25293      .recv_timeout(Duration::from_secs(2))
25294      .expect("dropping the receiver must reclaim the buffered result");
25295  }
25296
25297  #[cfg(not(target_family = "wasm"))]
25298  #[test]
25299  fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
25300    use std::sync::mpsc;
25301
25302    struct PanicOnDrop(mpsc::Sender<()>);
25303    impl Drop for PanicOnDrop {
25304      fn drop(&mut self) {
25305        let _ = self.0.send(());
25306        panic!("intentional late blocking destructor panic");
25307      }
25308    }
25309
25310    let executor = multi_thread_executor(2, None, "closed-queue-drop");
25311    executor.begin_shutdown();
25312
25313    let (drop_tx, drop_rx) = mpsc::channel();
25314    let panic_on_drop = PanicOnDrop(drop_tx);
25315    let handle = executor.schedule_blocking(move || {
25316      let _keep_captured_until_run = &panic_on_drop;
25317    });
25318
25319    drop_rx
25320      .recv_timeout(Duration::from_secs(5))
25321      .expect("the closed queue must retire the rejected closure");
25322    assert!(
25323      futures::executor::block_on(handle).is_err(),
25324      "the rejected blocking handle must resolve as cancelled"
25325    );
25326  }
25327
25328  #[cfg(not(target_family = "wasm"))]
25329  #[test]
25330  fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
25331    struct PanicWake;
25332    impl std::task::Wake for PanicWake {
25333      fn wake(self: Arc<Self>) {
25334        panic!("intentional timer waker panic");
25335      }
25336    }
25337
25338    let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
25339    let backend = controller.backend();
25340    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25341      panic!("configured MultiThread backend must create a Rayon executor");
25342    };
25343    let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
25344    let waker = Waker::from(Arc::new(PanicWake));
25345    let mut cx = Context::from_waker(&waker);
25346    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
25347    drop(backend);
25348
25349    controller
25350      .shutdown()
25351      .expect("shutdown must contain the timer waker panic");
25352    controller
25353      .start()
25354      .expect("the stopped controller must restart");
25355    let restarted = controller
25356      .try_spawn_blocking(|| 23usize)
25357      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
25358    assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
25359    controller.shutdown().unwrap();
25360  }
25361
25362  #[cfg(not(target_family = "wasm"))]
25363  #[test]
25364  fn multi_thread_restart_waits_for_old_generation_worker_exit() {
25365    use std::sync::mpsc;
25366
25367    let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
25368    let backend = controller.backend();
25369    let old_generation = backend.generation();
25370    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25371      panic!("configured MultiThread backend must create a Rayon executor");
25372    };
25373    let old_workers = Arc::clone(&executor.worker_lifecycle);
25374    drop(backend);
25375
25376    let (running_tx, running_rx) = mpsc::channel();
25377    let (release_tx, release_rx) = mpsc::channel();
25378    let running = controller
25379      .try_spawn_blocking(move || {
25380        running_tx.send(()).unwrap();
25381        release_rx.recv().unwrap();
25382      })
25383      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25384    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25385
25386    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25387    let shutdown_controller = Arc::clone(&controller);
25388    let shutdown = std::thread::spawn(move || {
25389      shutdown_controller.shutdown().unwrap();
25390      shutdown_done_tx.send(()).unwrap();
25391    });
25392    wait_until("controller entered Stopping", || {
25393      let state = controller
25394        .state
25395        .lock()
25396        .unwrap_or_else(std::sync::PoisonError::into_inner);
25397      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
25398    });
25399
25400    let (start_done_tx, start_done_rx) = mpsc::channel();
25401    let start_controller = Arc::clone(&controller);
25402    let starter = std::thread::spawn(move || {
25403      start_controller.start().unwrap();
25404      start_done_tx.send(()).unwrap();
25405    });
25406    assert!(
25407      start_done_rx.try_recv().is_err(),
25408      "start must wait while shutdown is quiescing"
25409    );
25410    assert!(
25411      old_workers.remaining() > 0,
25412      "the held old generation must still own workers"
25413    );
25414
25415    release_tx.send(()).unwrap();
25416    futures::executor::block_on(running).unwrap();
25417    shutdown_done_rx
25418      .recv_timeout(Duration::from_secs(5))
25419      .unwrap();
25420    start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25421    shutdown.join().unwrap();
25422    starter.join().unwrap();
25423    assert_eq!(
25424      old_workers.remaining(),
25425      0,
25426      "restart may begin only after every old worker exits"
25427    );
25428
25429    let backend = controller.backend();
25430    assert_ne!(backend.generation(), old_generation);
25431    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25432      panic!("restarted backend must remain MultiThread");
25433    };
25434    assert_eq!(
25435      executor.pool.current_num_threads(),
25436      controller.options().worker_threads
25437    );
25438    drop(backend);
25439    controller.shutdown().unwrap();
25440  }
25441
25442  #[cfg(not(target_family = "wasm"))]
25443  #[test]
25444  fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
25445    let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
25446    let task_controller = Arc::clone(&controller);
25447    let handle = controller
25448      .try_spawn(async move {
25449        task_controller
25450          .shutdown()
25451          .expect_err("runtime work must not wait for its own generation")
25452          .to_string()
25453      })
25454      .unwrap_or_else(|_| panic!("running runtime must accept the task"));
25455    let message = futures::executor::block_on(handle).unwrap();
25456    assert!(message.contains("cannot shut down"));
25457    controller.shutdown().unwrap();
25458  }
25459
25460  // ---- MultiThread blocking cap, drain budget, and panic propagation ---------
25461  // All three construct a `MultiThreadExecutor`/`RuntimeMetrics` LOCALLY (never the
25462  // global `RUNTIME`) and run the workload on a child thread guarded by a
25463  // `recv_timeout`, so a regression that hangs is reported as a failure instead of
25464  // wedging the whole suite.
25465
25466  #[cfg(not(target_family = "wasm"))]
25467  #[test]
25468  fn multi_thread_blocking_active_never_exceeds_max_blocking() {
25469    // Blocking-cap invariant: `take_blocking` refuses to pop or run a queued
25470    // blocking job while `active_blocking >= max_blocking`. The guard is
25471    // exercised deterministically (no sleeps, no incidental worker overlap):
25472    //
25473    //   1. EXACTLY `max_blocking_tasks` HELD holders saturate the cap. Each holder
25474    //      occupies a counted slot (`take_blocking` increments `active_blocking`
25475    //      BEFORE running the closure), signals `held`, then parks on a release
25476    //      barrier -- so all slots are provably occupied before we proceed.
25477    //   2. While the cap is saturated, we queue MORE blocking jobs ("extras").
25478    //      With the cap intact `take_blocking` returns `None` for them, so the
25479    //      idle workers cannot pop them: no extra may signal `extra_started`.
25480    //   3. We release the holders, then assert EVERY job (holders + extras)
25481    //      completes with the right result and the recorded peak never exceeded
25482    //      the cap.
25483    //
25484    // Decisive property: if the `active >= self.max_blocking` guard were removed,
25485    // the two idle workers would pop the extras while both holders are still
25486    // parked, driving the recorded peak (`max_active_blocking_tasks`, a monotonic
25487    // fetch_max set at job entry) above the cap -- so this test fails on EVERY run.
25488    // The release barrier is sized `holder_count + 1` (<= max_blocking + 1), so it
25489    // can never self-deadlock on the over-cap extras.
25490    use std::sync::{Barrier, mpsc};
25491    use std::time::Duration;
25492
25493    let (done_tx, done_rx) = mpsc::channel();
25494    let peak_out = Arc::new(Mutex::new(None));
25495    let peak_slot = Arc::clone(&peak_out);
25496    let runner = std::thread::spawn(move || {
25497      let metrics = Arc::new(RuntimeMetrics::default());
25498      let options = RuntimeOptions {
25499        flavor: RuntimeFlavor::MultiThread,
25500        worker_threads: 4,
25501        max_blocking_tasks: 2,
25502        thread_name_prefix: "rd9-blk-cap".to_string(),
25503        park_deadline: None,
25504      };
25505      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
25506
25507      let holder_count = options.max_blocking_tasks; // saturates the cap exactly
25508      let extra_count = 4usize; // beyond-cap jobs that must NOT run while held
25509
25510      // Holders: signal once they hold a counted slot, then park until released.
25511      let (held_tx, held_rx) = mpsc::channel();
25512      let release = Arc::new(Barrier::new(holder_count + 1));
25513      let mut holder_handles = Vec::new();
25514      for _ in 0..holder_count {
25515        let held_tx = held_tx.clone();
25516        let release = Arc::clone(&release);
25517        holder_handles.push(executor.schedule_blocking(move || {
25518          held_tx.send(()).unwrap();
25519          release.wait();
25520          0usize
25521        }));
25522      }
25523      // Wait until every slot is provably occupied (cap fully saturated).
25524      for _ in 0..holder_count {
25525        held_rx.recv().unwrap();
25526      }
25527
25528      // Queue the beyond-cap extras. Each signals the instant it begins running.
25529      let (extra_started_tx, extra_started_rx) = mpsc::channel();
25530      let mut extra_handles = Vec::new();
25531      for i in 0..extra_count {
25532        let extra_started_tx = extra_started_tx.clone();
25533        extra_handles.push(executor.schedule_blocking(move || {
25534          extra_started_tx.send(i).unwrap();
25535          i * 2
25536        }));
25537      }
25538
25539      // With the cap intact, `take_blocking` returns `None` while both slots are
25540      // held, so no extra can have started running yet.
25541      assert!(
25542        matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
25543        "a beyond-cap blocking job ran before the cap was released (cap check broken)"
25544      );
25545
25546      // Release the holders so the queued extras can run within the cap.
25547      release.wait();
25548
25549      for handle in holder_handles {
25550        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
25551      }
25552      // Each handle owns its own oneshot receiver, so awaiting in order returns
25553      // each extra's own result regardless of the order the pool ran them.
25554      for (i, handle) in extra_handles.into_iter().enumerate() {
25555        assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
25556      }
25557
25558      // `blocking_tasks_started` is incremented at job entry, so once every result
25559      // is in, all holder + extra starts are recorded.
25560      assert_eq!(
25561        metrics.blocking_tasks_started.load(Ordering::Relaxed),
25562        (holder_count + extra_count) as u64
25563      );
25564
25565      *peak_slot
25566        .lock()
25567        .unwrap_or_else(std::sync::PoisonError::into_inner) =
25568        Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
25569      let _ = done_tx.send(());
25570    });
25571
25572    match done_rx.recv_timeout(Duration::from_secs(10)) {
25573      Ok(()) => runner.join().unwrap(),
25574      Err(error) => panic!("blocking-cap test did not complete ({error})"),
25575    }
25576    let peak = peak_out
25577      .lock()
25578      .unwrap_or_else(std::sync::PoisonError::into_inner)
25579      .expect("peak captured");
25580    assert!(
25581      peak >= 1,
25582      "at least one blocking job must have run (peak {peak})"
25583    );
25584    assert!(
25585      peak <= 2,
25586      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
25587    );
25588  }
25589
25590  #[cfg(not(target_family = "wasm"))]
25591  #[test]
25592  fn multi_thread_drain_budget_completes_large_runnable_batch() {
25593    // `drain` processes at most `RUNNABLE_BUDGET` (64) runnables per pass, then
25594    // yields and re-drains via `finish_draining` -> `ensure_drainer`. A batch of
25595    // MORE than 64 runnables must therefore exercise the budget yield/re-drain and
25596    // still complete every task, with the runnable counters settling back to 0.
25597    use std::sync::mpsc;
25598    use std::time::{Duration, Instant};
25599
25600    let (done_tx, done_rx) = mpsc::channel();
25601    let runner = std::thread::spawn(move || {
25602      let metrics = Arc::new(RuntimeMetrics::default());
25603      let options = RuntimeOptions {
25604        flavor: RuntimeFlavor::MultiThread,
25605        worker_threads: 2,
25606        max_blocking_tasks: 2,
25607        thread_name_prefix: "rd9-budget".to_string(),
25608        park_deadline: None,
25609      };
25610      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
25611
25612      let task_count = 200usize; // > RUNNABLE_BUDGET (64): forces budget re-drain
25613      let completed = Arc::new(AtomicU64::new(0));
25614      let mut tasks = Vec::new();
25615      for _ in 0..task_count {
25616        let completed = Arc::clone(&completed);
25617        let scheduler = Arc::clone(&executor);
25618        let (runnable, task) = async_task::spawn(
25619          async move {
25620            completed.fetch_add(1, Ordering::Relaxed);
25621          },
25622          move |r| scheduler.schedule(r),
25623        );
25624        executor.schedule(runnable);
25625        tasks.push(task);
25626      }
25627      for task in tasks {
25628        futures::executor::block_on(task);
25629      }
25630
25631      // Every immediately-ready future is polled exactly once; `runnable_polls`
25632      // (incremented at poll entry, before completion) is therefore exact here.
25633      assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
25634      assert_eq!(
25635        metrics.runnable_polls.load(Ordering::Relaxed),
25636        task_count as u64
25637      );
25638
25639      // The active-runnable guard for the LAST task drops just after its Task
25640      // resolves (i.e. just after `block_on` returns), so give the counters a
25641      // brief, bounded moment to settle to 0 rather than asserting them racily.
25642      let deadline = Instant::now() + Duration::from_secs(5);
25643      loop {
25644        let active = metrics.active_runnables.load(Ordering::Relaxed);
25645        let queued = metrics.queued_runnables.load(Ordering::Relaxed);
25646        if active == 0 && queued == 0 {
25647          break;
25648        }
25649        assert!(
25650          Instant::now() < deadline,
25651          "runnable counters did not settle to 0: active={active} queued={queued}"
25652        );
25653        std::thread::yield_now();
25654      }
25655      let _ = done_tx.send(());
25656    });
25657
25658    match done_rx.recv_timeout(Duration::from_secs(15)) {
25659      Ok(()) => runner.join().unwrap(),
25660      Err(error) => panic!("drain-budget test did not complete ({error})"),
25661    }
25662  }
25663
25664  #[cfg(not(target_family = "wasm"))]
25665  #[test]
25666  fn multi_thread_spawned_panic_surfaces_as_join_error() {
25667    // A panicking spawned future must surface as `Err(JoinError)` carrying the
25668    // panic payload's message. We mirror the production `spawn` wrapper LOCALLY
25669    // (catch_unwind the future, map the panic via `JoinError::from_panic`) and
25670    // drive the resulting `JoinHandle` on this executor -- without touching the
25671    // global `RUNTIME`. `JoinError::from_panic` downcasts a `&str` payload, so the
25672    // `panic!("x")` message round-trips as `"x"`.
25673    use std::sync::mpsc;
25674    use std::time::Duration;
25675
25676    let (done_tx, done_rx) = mpsc::channel();
25677    let runner = std::thread::spawn(move || {
25678      let metrics = Arc::new(RuntimeMetrics::default());
25679      let options = RuntimeOptions {
25680        flavor: RuntimeFlavor::MultiThread,
25681        worker_threads: 2,
25682        max_blocking_tasks: 2,
25683        thread_name_prefix: "rd9-panic".to_string(),
25684        park_deadline: None,
25685      };
25686      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
25687
25688      let scheduler = Arc::clone(&executor);
25689      let wrapped = async {
25690        AssertUnwindSafe(async { panic!("x") })
25691          .catch_unwind()
25692          .await
25693          .map(|()| ContainedTaskOutput::new((), u64::MAX))
25694          .map_err(panic_payload_to_join_error)
25695      };
25696      let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
25697      executor.schedule(runnable);
25698
25699      let result: Result<(), JoinError> =
25700        futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
25701          task: task.fallible(),
25702          awaiter: None,
25703          dependency: Arc::new(TaskDependency::default()),
25704        }));
25705      let _ = done_tx.send(result);
25706    });
25707
25708    let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
25709      Ok(result) => {
25710        runner.join().unwrap();
25711        result
25712      }
25713      Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
25714    };
25715    let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
25716    assert_eq!(error.to_string(), "x");
25717  }
25718
25719  #[test]
25720  fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
25721    use std::sync::mpsc;
25722
25723    let metrics = Arc::new(RuntimeMetrics::default());
25724    metrics.tasks_spawned.store(1, Ordering::Relaxed);
25725    metrics.tasks_completed.store(2, Ordering::Relaxed);
25726    metrics.tasks_panicked.store(3, Ordering::Relaxed);
25727    metrics.runnable_schedules.store(4, Ordering::Relaxed);
25728    metrics.runnable_polls.store(5, Ordering::Relaxed);
25729    metrics
25730      .blocking_tasks_scheduled
25731      .store(14, Ordering::Relaxed);
25732    metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
25733    metrics
25734      .blocking_tasks_completed
25735      .store(11, Ordering::Relaxed);
25736    metrics.runnable_scheduled();
25737    let (guards_live_tx, guards_live_rx) = mpsc::channel();
25738    let (release_tx, release_rx) = mpsc::channel();
25739    let guard_metrics = Arc::clone(&metrics);
25740    let guard_thread = std::thread::spawn(move || {
25741      let runnable_guard = guard_metrics.runnable_started();
25742      let blocking_guard = guard_metrics.blocking_started(true);
25743      guards_live_tx.send(()).unwrap();
25744      release_rx.recv().unwrap();
25745      drop(runnable_guard);
25746      drop(blocking_guard);
25747    });
25748    guards_live_rx.recv().unwrap();
25749    let fingerprint_before_reset = metrics.progress_fingerprint();
25750
25751    metrics.reset();
25752
25753    assert!(
25754      metrics.progress_fingerprint() != fingerprint_before_reset,
25755      "a reset must advance the deadlock detector generation even if counters later repeat"
25756    );
25757    assert_eq!(
25758      metrics.tasks_spawned.load(Ordering::Relaxed),
25759      0,
25760      "tasks_spawned"
25761    );
25762    assert_eq!(
25763      metrics.tasks_completed.load(Ordering::Relaxed),
25764      0,
25765      "tasks_completed"
25766    );
25767    assert_eq!(
25768      metrics.tasks_panicked.load(Ordering::Relaxed),
25769      0,
25770      "tasks_panicked"
25771    );
25772    assert_eq!(
25773      metrics.runnable_schedules.load(Ordering::Relaxed),
25774      0,
25775      "runnable_schedules"
25776    );
25777    assert_eq!(
25778      metrics.runnable_polls.load(Ordering::Relaxed),
25779      0,
25780      "runnable_polls"
25781    );
25782    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
25783    assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
25784    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
25785    assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
25786    assert_eq!(
25787      metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
25788      0,
25789      "blocking_tasks_scheduled"
25790    );
25791    assert_eq!(
25792      metrics.blocking_tasks_started.load(Ordering::Relaxed),
25793      0,
25794      "blocking_tasks_started"
25795    );
25796    assert_eq!(
25797      metrics.blocking_tasks_completed.load(Ordering::Relaxed),
25798      0,
25799      "blocking_tasks_completed"
25800    );
25801    assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
25802    assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
25803
25804    release_tx.send(()).unwrap();
25805    guard_thread.join().unwrap();
25806    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
25807    assert_eq!(
25808      metrics.active_blocking_tasks.load(Ordering::Relaxed),
25809      0,
25810      "a live blocking guard completed after reset without unsigned underflow"
25811    );
25812    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25813  }
25814
25815  #[test]
25816  fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
25817    use std::sync::mpsc;
25818
25819    fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
25820      RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
25821        *slot.borrow_mut() = Some(Box::new(move || {
25822          entered.send(()).unwrap();
25823          release.recv().unwrap();
25824        }));
25825      });
25826    }
25827
25828    let options = RuntimeOptions {
25829      flavor: RuntimeFlavor::CurrentThread,
25830      worker_threads: 1,
25831      max_blocking_tasks: 1,
25832      thread_name_prefix: "metrics-snapshot".to_string(),
25833      park_deadline: None,
25834    };
25835
25836    let queued_metrics = Arc::new(RuntimeMetrics::default());
25837    let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
25838    let (queued_release_tx, queued_release_rx) = mpsc::channel();
25839    let queued_writer_metrics = Arc::clone(&queued_metrics);
25840    let queued_writer = std::thread::spawn(move || {
25841      pause_next_high_water_update(queued_entered_tx, queued_release_rx);
25842      queued_writer_metrics.runnable_scheduled();
25843    });
25844    queued_entered_rx.recv().unwrap();
25845    assert_eq!(
25846      queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
25847      0
25848    );
25849    let snapshot = queued_metrics.snapshot(&options);
25850    assert_eq!(snapshot.queued_runnables, 1);
25851    assert_eq!(snapshot.max_queued_runnables, 1);
25852    queued_release_tx.send(()).unwrap();
25853    queued_writer.join().unwrap();
25854    queued_metrics.runnable_cancelled();
25855
25856    let active_metrics = Arc::new(RuntimeMetrics::default());
25857    active_metrics.queued_runnables.store(1, Ordering::Relaxed);
25858    let (active_entered_tx, active_entered_rx) = mpsc::channel();
25859    let (active_release_tx, active_release_rx) = mpsc::channel();
25860    let active_writer_metrics = Arc::clone(&active_metrics);
25861    let active_writer = std::thread::spawn(move || {
25862      pause_next_high_water_update(active_entered_tx, active_release_rx);
25863      let _active = active_writer_metrics.runnable_started();
25864    });
25865    active_entered_rx.recv().unwrap();
25866    assert_eq!(
25867      active_metrics.max_active_runnables.load(Ordering::Relaxed),
25868      0
25869    );
25870    let snapshot = active_metrics.snapshot(&options);
25871    assert_eq!(snapshot.active_runnables, 1);
25872    assert_eq!(snapshot.max_active_runnables, 1);
25873    active_release_tx.send(()).unwrap();
25874    active_writer.join().unwrap();
25875
25876    let blocking_metrics = Arc::new(RuntimeMetrics::default());
25877    let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
25878    let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
25879    let blocking_writer_metrics = Arc::clone(&blocking_metrics);
25880    let blocking_writer = std::thread::spawn(move || {
25881      pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
25882      let _active = blocking_writer_metrics.blocking_started(true);
25883    });
25884    blocking_entered_rx.recv().unwrap();
25885    assert_eq!(
25886      blocking_metrics
25887        .max_active_blocking_tasks
25888        .load(Ordering::Relaxed),
25889      0
25890    );
25891    let snapshot = blocking_metrics.snapshot(&options);
25892    assert_eq!(snapshot.active_blocking_tasks, 1);
25893    assert_eq!(snapshot.max_active_blocking_tasks, 1);
25894    blocking_release_tx.send(()).unwrap();
25895    blocking_writer.join().unwrap();
25896  }
25897
25898  #[test]
25899  fn runtime_metrics_reset_fails_before_generation_wraparound() {
25900    let metrics = RuntimeMetrics::default();
25901    metrics
25902      .reset_generation
25903      .store(u64::MAX - 1, Ordering::SeqCst);
25904    metrics.tasks_spawned.store(7, Ordering::Relaxed);
25905
25906    let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
25907    assert!(exhausted.is_err());
25908    assert!(!metrics.reset_lock.is_poisoned());
25909    assert_eq!(
25910      metrics.reset_generation.load(Ordering::SeqCst),
25911      u64::MAX - 1
25912    );
25913    assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
25914  }
25915
25916  // ---- Self-detecting block_on deadlocks -------------------------------------
25917  // Five deadlock-detection shapes are pinned here: (1) a threadless CurrentThread
25918  // park decision with no pending wake panics with the typed diagnostic; (2) a
25919  // self-waking future (the PendThenReady shape) must NOT panic -- the panic
25920  // lives at the PARK DECISION, not at Pending-return; (3) an
25921  // armed deadline fires on a genuinely dead cooperative MT park; (4) runtime
25922  // progress RESETS the deadline instead of firing; (5) the
25923  // foreign/napi whole-build park is excluded from the deadline entirely.
25924
25925  /// A future that never completes and never wakes anyone: once its driver
25926  /// parks, no wake can ever arrive. The deadlock-detection probe shape.
25927  struct NeverReady;
25928  impl Future for NeverReady {
25929    type Output = ();
25930    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25931      Poll::Pending
25932    }
25933  }
25934
25935  #[cfg(not(target_family = "wasm"))]
25936  struct ReadyAfterOwnerLanePublication {
25937    dependency: BlockingDependency,
25938    ready: Arc<AtomicBool>,
25939  }
25940
25941  #[cfg(not(target_family = "wasm"))]
25942  impl Future for ReadyAfterOwnerLanePublication {
25943    type Output = ();
25944
25945    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25946      if self.ready.load(Ordering::SeqCst) {
25947        Poll::Ready(())
25948      } else {
25949        record_blocking_dependency(self.dependency);
25950        Poll::Pending
25951      }
25952    }
25953  }
25954
25955  #[test]
25956  fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
25957    // Shape (1): on a threadless build a CT park decision with an empty queue
25958    // and no wake token pending is a PROVABLE deadlock (no other thread exists
25959    // to deliver the wake) -- `block_on` must panic immediately with the typed
25960    // `BlockOnDeadlock` diagnostic instead of parking forever. The workload
25961    // runs on a child thread bounded by recv_timeout so a missing detection
25962    // (a hang) fails the test rather than wedging the suite.
25963    use std::sync::mpsc;
25964
25965    let (done_tx, done_rx) = mpsc::channel();
25966    let runner = std::thread::spawn(move || {
25967      let metrics = Arc::new(RuntimeMetrics::default());
25968      // Native stand-in for the threadless wasm build; no deadline because the
25969      // certain case is not timing-based.
25970      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
25971
25972      let payload = catch_unwind(AssertUnwindSafe(|| {
25973        let mut future = std::pin::pin!(NeverReady);
25974        executor.block_on(future.as_mut());
25975      }))
25976      .expect_err("a threadless park with an empty queue and no pending wake must panic");
25977      let diagnostic = payload
25978        .downcast_ref::<BlockOnDeadlock>()
25979        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
25980      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
25981      assert_eq!(
25982        diagnostic.park_deadline, None,
25983        "the certain case carries no deadline"
25984      );
25985      let message = diagnostic.to_string();
25986      assert!(
25987        message.contains("block_on") && message.contains("JS"),
25988        "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
25989      );
25990      let _ = done_tx.send(());
25991    });
25992
25993    match done_rx.recv_timeout(Duration::from_secs(10)) {
25994      Ok(()) => runner.join().unwrap(),
25995      Err(error) => panic!(
25996        "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
25997      ),
25998    }
25999  }
26000
26001  #[test]
26002  fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
26003    // Shape (2), pinning panic-at-PARK-DECISION: a future that returns
26004    // `Pending` but wakes itself synchronously during the poll leaves
26005    // a wake token pending, so the loop must consume the token and re-poll --
26006    // NOT panic -- even on a threadless build. "Pending returned" alone is
26007    // never a deadlock.
26008    use std::sync::mpsc;
26009
26010    struct PendThenReady {
26011      polls: usize,
26012    }
26013    impl Future for PendThenReady {
26014      type Output = ();
26015      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
26016        self.polls += 1;
26017        if self.polls >= 2 {
26018          Poll::Ready(())
26019        } else {
26020          cx.waker().wake_by_ref();
26021          Poll::Pending
26022        }
26023      }
26024    }
26025
26026    let (done_tx, done_rx) = mpsc::channel();
26027    let runner = std::thread::spawn(move || {
26028      let metrics = Arc::new(RuntimeMetrics::default());
26029      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
26030      let mut future = std::pin::pin!(PendThenReady { polls: 0 });
26031      executor.block_on(future.as_mut());
26032      assert_eq!(
26033        future.polls, 2,
26034        "the self-woken future must have been re-polled to completion"
26035      );
26036      let _ = done_tx.send(());
26037    });
26038
26039    match done_rx.recv_timeout(Duration::from_secs(10)) {
26040      Ok(()) => runner.join().unwrap(),
26041      Err(error) => panic!(
26042        "a self-waking Pending future must complete without panicking on a threadless build ({error})"
26043      ),
26044    }
26045  }
26046
26047  #[cfg(not(target_family = "wasm"))]
26048  #[test]
26049  fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
26050    // Threaded CT is NOT provably dead at the park decision (another OS thread
26051    // could deliver a wake later), so detection there is deadline-based and
26052    // opt-in. A park that outlives the armed deadline with ZERO runtime
26053    // progress must panic with the typed diagnostic.
26054    use std::sync::mpsc;
26055
26056    let (done_tx, done_rx) = mpsc::channel();
26057    let runner = std::thread::spawn(move || {
26058      let metrics = Arc::new(RuntimeMetrics::default());
26059      let executor = Arc::new(CurrentThreadExecutor::with_detection(
26060        metrics,
26061        false,
26062        Some(Duration::from_millis(50)),
26063      ));
26064
26065      let payload = catch_unwind(AssertUnwindSafe(|| {
26066        let mut future = std::pin::pin!(NeverReady);
26067        executor.block_on(future.as_mut());
26068      }))
26069      .expect_err("a dead native CT park must fire the armed deadline");
26070      let diagnostic = payload
26071        .downcast_ref::<BlockOnDeadlock>()
26072        .expect("typed payload");
26073      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
26074      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
26075      // Teardown inside the bounded window: a wedged executor drop must fail
26076      // the recv below loudly, not hang the final join silently.
26077      drop(executor);
26078      let _ = done_tx.send(());
26079    });
26080
26081    match done_rx.recv_timeout(Duration::from_secs(10)) {
26082      Ok(()) => join_within(
26083        "the CT deadline-fire runner exits after teardown",
26084        runner,
26085        Duration::from_secs(30),
26086      ),
26087      Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
26088    }
26089  }
26090
26091  #[cfg(not(target_family = "wasm"))]
26092  #[test]
26093  fn current_thread_native_park_deadline_resets_on_runtime_progress() {
26094    // PROGRESS-BASED RESET, CT side: metrics advancing while parked means the
26095    // runtime is alive (e.g. a slow JS plugin with other
26096    // work proceeding), so the deadline must re-arm instead of firing.
26097    // Progress is injected by advancing the very counter the reset reads --
26098    // the exact seam, isolated from wake delivery (production advances it via
26099    // real runnable polls).
26100    //
26101    // Deadline/cadence ratio: the reset is only exercised when at least one
26102    // tick lands inside EVERY armed window, so the window must dwarf the
26103    // injector thread's worst-case scheduling stall. On starved CI hosts
26104    // (GitHub macOS runners: ~3 heavily oversubscribed cores) a 30ms sleep
26105    // can overshoot past a 60ms window, leaving one window with zero
26106    // progress -- which IS a deadline fire by design. Ticks every ~25ms
26107    // against a 1s window tolerate a ~975ms stall while the park is still
26108    // held across multiple full windows, so the repeated RE-ARM (not merely
26109    // the first arm) is what keeps the park alive.
26110    use std::sync::mpsc;
26111
26112    let deadline = Duration::from_millis(1000);
26113    let metrics = Arc::new(RuntimeMetrics::default());
26114    let executor = Arc::new(CurrentThreadExecutor::with_detection(
26115      Arc::clone(&metrics),
26116      false,
26117      Some(deadline),
26118    ));
26119
26120    let (value_tx, value_rx) = oneshot::channel::<usize>();
26121    let (done_tx, done_rx) = mpsc::channel();
26122    let runner = std::thread::spawn(move || {
26123      let mut output = None;
26124      {
26125        let mut future = std::pin::pin!(async {
26126          output = Some(value_rx.await.unwrap());
26127        });
26128        executor.block_on(future.as_mut());
26129      }
26130      assert_eq!(output, Some(9));
26131      // Teardown inside the bounded window (see `join_within`).
26132      drop(executor);
26133      let _ = done_tx.send(());
26134    });
26135
26136    // Hold the park across several full deadline windows, every one
26137    // containing many injected progress ticks, then deliver the (legitimately
26138    // late) wake. The loop is elapsed-based so an oversleeping iteration
26139    // extends the hold (more windows) instead of thinning the tick supply,
26140    // and the final tick lands at most one iteration before the wake.
26141    let hold_until = Instant::now() + deadline * 5 / 2;
26142    while Instant::now() < hold_until {
26143      std::thread::sleep(Duration::from_millis(25));
26144      metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
26145    }
26146    value_tx.send(9).unwrap();
26147
26148    match done_rx.recv_timeout(Duration::from_secs(30)) {
26149      Ok(()) => join_within(
26150        "the CT deadline-reset runner exits after teardown",
26151        runner,
26152        Duration::from_secs(30),
26153      ),
26154      Err(error) => panic!(
26155        "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
26156      ),
26157    }
26158  }
26159
26160  #[cfg(not(target_family = "wasm"))]
26161  #[test]
26162  fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
26163    // Shape (3): the MT deadline applies to the DriverParker inside
26164    // `cooperative_block_on`. Run the cooperative branch directly on this
26165    // thread with the test-only on-pool marker so the panic is observable as
26166    // a typed payload without rayon in between.
26167    use std::sync::mpsc;
26168
26169    let (done_tx, done_rx) = mpsc::channel();
26170    let runner = std::thread::spawn(move || {
26171      let options = RuntimeOptions {
26172        flavor: RuntimeFlavor::MultiThread,
26173        worker_threads: 1,
26174        max_blocking_tasks: 1,
26175        thread_name_prefix: "deadline-fire".to_string(),
26176        park_deadline: Some(Duration::from_millis(50)),
26177      };
26178      let executor =
26179        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26180
26181      let payload = {
26182        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26183        catch_unwind(AssertUnwindSafe(|| {
26184          let mut future = std::pin::pin!(NeverReady);
26185          executor.block_on(future.as_mut());
26186        }))
26187        .expect_err("a dead cooperative park must fire the armed deadline")
26188      };
26189      let diagnostic = payload
26190        .downcast_ref::<BlockOnDeadlock>()
26191        .expect("typed payload");
26192      assert_eq!(
26193        diagnostic.kind,
26194        BlockOnDeadlockKind::MultiThreadCooperativeDeadline
26195      );
26196      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
26197      let message = diagnostic.to_string();
26198      assert!(
26199        message.contains("block_on"),
26200        "diagnostic must name the hazard, got: {message}"
26201      );
26202      assert_eq!(
26203        executor.parked_drivers.count.load(Ordering::SeqCst),
26204        0,
26205        "the firing driver must deregister itself before panicking"
26206      );
26207      // Teardown inside the bounded window (see `join_within`).
26208      drop(executor);
26209      let _ = done_tx.send(());
26210    });
26211
26212    match done_rx.recv_timeout(Duration::from_secs(10)) {
26213      Ok(()) => join_within(
26214        "the MT deadline-fire runner exits after executor teardown",
26215        runner,
26216        Duration::from_secs(30),
26217      ),
26218      Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
26219    }
26220  }
26221
26222  #[cfg(not(target_family = "wasm"))]
26223  #[test]
26224  fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
26225    // Shape (4), MT side of the progress reset: a legitimately slow wake under
26226    // an armed deadline must NOT fire while the executor's
26227    // metrics keep advancing. Topology: the sole worker is parked as a
26228    // cooperative driver (real pool thread, real DriverParker park); the test
26229    // injects progress ticks across several full deadline windows, then
26230    // delivers the late wake.
26231    //
26232    // Deadline/cadence ratio: one tick must land inside EVERY armed window,
26233    // so the window must dwarf the injector thread's worst-case scheduling
26234    // stall -- on starved CI hosts (GitHub macOS runners: ~3 heavily
26235    // oversubscribed cores) a 30ms sleep can overshoot past a 60ms window,
26236    // and a full window with zero progress IS a deadline fire by design.
26237    // Ticks every ~25ms against a 1s window tolerate a ~975ms stall while
26238    // still holding the park across multiple full windows, proving the
26239    // repeated RE-ARM rather than a single lucky first window.
26240    use std::sync::mpsc;
26241
26242    let (done_tx, done_rx) = mpsc::channel();
26243    let runner = std::thread::spawn(move || {
26244      let deadline = Duration::from_millis(1000);
26245      let metrics = Arc::new(RuntimeMetrics::default());
26246      let options = RuntimeOptions {
26247        flavor: RuntimeFlavor::MultiThread,
26248        worker_threads: 1,
26249        max_blocking_tasks: 1,
26250        thread_name_prefix: "deadline-reset".to_string(),
26251        park_deadline: Some(deadline),
26252      };
26253      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26254
26255      // Park the sole worker as a cooperative driver awaiting a gate (same
26256      // topology as the miss-compensation test).
26257      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
26258      let td_exec = Arc::clone(&executor);
26259      let completed = Arc::new(AtomicBool::new(false));
26260      let td_flag = Arc::clone(&completed);
26261      let td_body = async move {
26262        let mut inner = std::pin::pin!(async move {
26263          assert_eq!(gate_rx.await.unwrap(), 9);
26264        });
26265        td_exec.block_on(inner.as_mut());
26266        td_flag.store(true, Ordering::SeqCst);
26267      };
26268      let sched = Arc::clone(&executor);
26269      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
26270      executor.schedule(td_runnable);
26271      wait_until("the sole driver parked", || {
26272        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
26273      });
26274
26275      // Outlive the armed deadline several times over, with every window
26276      // containing many injected progress ticks (the counter the reset
26277      // reads). Elapsed-based so an oversleeping iteration extends the hold
26278      // instead of thinning the tick supply, and the final tick lands at
26279      // most one iteration before the late wake below.
26280      let hold_until = Instant::now() + deadline * 5 / 2;
26281      while Instant::now() < hold_until {
26282        std::thread::sleep(Duration::from_millis(25));
26283        metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
26284      }
26285      assert!(
26286        !completed.load(Ordering::SeqCst),
26287        "the driver must still be parked awaiting its late wake"
26288      );
26289      gate_tx.send(9).unwrap();
26290      futures::executor::block_on(td_task);
26291      assert!(
26292        completed.load(Ordering::SeqCst),
26293        "the late wake must complete the parked driver"
26294      );
26295      // Teardown inside the bounded window (see `join_within`).
26296      drop(executor);
26297      let _ = done_tx.send(());
26298    });
26299
26300    match done_rx.recv_timeout(Duration::from_secs(30)) {
26301      Ok(()) => join_within(
26302        "the MT deadline-reset runner exits after executor teardown",
26303        runner,
26304        Duration::from_secs(30),
26305      ),
26306      Err(error) => panic!(
26307        "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
26308      ),
26309    }
26310  }
26311
26312  #[cfg(not(target_family = "wasm"))]
26313  #[test]
26314  fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
26315    // Shape (5): the foreign/napi caller thread parks in
26316    // `futures::executor::block_on` for the ENTIRE build -- the normal
26317    // production shape. Even with an aggressively short deadline armed and
26318    // ZERO executor progress for many windows, that park must never fire.
26319    use std::sync::mpsc;
26320
26321    let (done_tx, done_rx) = mpsc::channel();
26322    let runner = std::thread::spawn(move || {
26323      let options = RuntimeOptions {
26324        flavor: RuntimeFlavor::MultiThread,
26325        worker_threads: 2,
26326        max_blocking_tasks: 2,
26327        thread_name_prefix: "foreign-exempt".to_string(),
26328        park_deadline: Some(Duration::from_millis(40)),
26329      };
26330      let executor =
26331        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26332
26333      let (value_tx, value_rx) = oneshot::channel::<usize>();
26334      let waker_thread = std::thread::spawn(move || {
26335        // Well past several deadline windows, with zero executor progress.
26336        std::thread::sleep(Duration::from_millis(300));
26337        let _ = value_tx.send(7);
26338      });
26339
26340      let started = Instant::now();
26341      let mut output = None;
26342      {
26343        let mut future = std::pin::pin!(async {
26344          output = Some(value_rx.await.unwrap());
26345        });
26346        // This (non-pool) thread takes the parking branch: exempt by design.
26347        executor.block_on(future.as_mut());
26348      }
26349      assert_eq!(output, Some(7));
26350      assert!(
26351        started.elapsed() >= Duration::from_millis(250),
26352        "the park must genuinely have outlived the 40ms deadline many times over"
26353      );
26354      waker_thread.join().unwrap();
26355      // Teardown inside the bounded window: this test was one of the silent
26356      // wedges on the Windows CI runners -- every assertion above had
26357      // passed, so the hang lived in the unbounded residue (executor drop /
26358      // thread teardown). Keep that phase inside the loud bounds.
26359      drop(executor);
26360      let _ = done_tx.send(());
26361    });
26362
26363    match done_rx.recv_timeout(Duration::from_secs(10)) {
26364      Ok(()) => join_within(
26365        "the exempt foreign-park runner exits after executor teardown",
26366        runner,
26367        Duration::from_secs(30),
26368      ),
26369      Err(error) => {
26370        panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
26371      }
26372    }
26373  }
26374
26375  #[cfg(not(target_family = "wasm"))]
26376  #[test]
26377  fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
26378    // Deadline-expiry wake race, pinned DETERMINISTICALLY via the expiry test
26379    // hook instead of a wall-clock lottery. Interleaving:
26380    //   (1) the cooperative park's deadline expires with genuinely zero
26381    //       progress (nothing else touches the executor before the park);
26382    //   (2) BEFORE the driver's expiry decision runs -- parker STILL
26383    //       REGISTERED -- a scheduler queues a blocking job; its `wake_one`
26384    //       pops this parker, stores the permit and reports the wake as
26385    //       DELIVERED (so no drainer is spawned). The wake was counted as
26386    //       delivered TO US, so the post-deregister permit re-check is the
26387    //       first-line catch; queued work and the enqueue counters in the
26388    //       fingerprint are further backstops;
26389    //   (3) the expiry decision runs. It must treat the permit as a wake, run
26390    //       the job (which sends the gate), and complete the awaited future
26391    //       instead of reporting `BlockOnDeadlock`.
26392    use std::sync::mpsc;
26393
26394    let (done_tx, done_rx) = mpsc::channel();
26395    let runner = std::thread::spawn(move || {
26396      let options = RuntimeOptions {
26397        flavor: RuntimeFlavor::MultiThread,
26398        worker_threads: 1,
26399        max_blocking_tasks: 1,
26400        thread_name_prefix: "edge-wake".to_string(),
26401        park_deadline: Some(Duration::from_millis(40)),
26402      };
26403      let executor =
26404        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26405
26406      // Until the racing job sends this gate, the awaited future is Pending.
26407      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
26408      // (registered_before_wake, registered_after_wake) observed by the hook.
26409      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
26410
26411      let hook_exec = Arc::clone(&executor);
26412      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
26413        *slot.borrow_mut() = Some(Box::new(move || {
26414          let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
26415          // The racing schedule: pushes the job, then `wake_one` pops the
26416          // still-registered parker (permit stored, wake counted as
26417          // delivered, no drainer).
26418          let handle = hook_exec.schedule_blocking(move || {
26419            let _ = gate_tx.send(5usize);
26420            0usize
26421          });
26422          drop(handle); // result unobserved; the gate send is the signal
26423          let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
26424          hook_saw_tx.send((before, after)).unwrap();
26425        }));
26426      });
26427
26428      // The test-only on-pool marker runs the cooperative branch and its
26429      // thread-local hook on THIS thread.
26430      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26431      let mut output = None;
26432      {
26433        let mut future = std::pin::pin!(async {
26434          output = Some(gate_rx.await.unwrap());
26435        });
26436        executor.block_on(future.as_mut());
26437      }
26438      // Clear the hook slot BEFORE any assert can panic: a hook left
26439      // installed captures an executor Arc, and dropping the last executor
26440      // Arc from a TLS destructor at thread exit is loader-lock hostile on
26441      // Windows (see the LOADER-LOCK RULE at `WorkerTlsDropAction`).
26442      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
26443      assert_eq!(
26444        output,
26445        Some(5),
26446        "the edge-delivered wake's job must have run and completed the future"
26447      );
26448
26449      let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
26450      assert_eq!(
26451        before, 1,
26452        "the hook must observe the parker STILL registered (race window)"
26453      );
26454      assert_eq!(
26455        after, 0,
26456        "the racing wake_one must have popped the parker (wake DELIVERED)"
26457      );
26458      // Teardown inside the bounded window (see `join_within`).
26459      drop(executor);
26460      let _ = done_tx.send(());
26461    });
26462
26463    match done_rx.recv_timeout(Duration::from_secs(10)) {
26464      Ok(()) => join_within(
26465        "the expiry-edge-wake runner exits after executor teardown",
26466        runner,
26467        Duration::from_secs(30),
26468      ),
26469      Err(error) => panic!(
26470        "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
26471      ),
26472    }
26473  }
26474
26475  #[cfg(not(target_family = "wasm"))]
26476  #[test]
26477  fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
26478    // Enqueue/fingerprint invariant: both runnable and blocking submissions
26479    // must advance the progress fingerprint at ENQUEUE. A blocking submission
26480    // landing after `has_queued_work()` but before the final fingerprint read
26481    // has already pushed deliverable work, while its wake targets an EMPTY
26482    // parked-driver registry. Only `blocking_tasks_scheduled` makes that work
26483    // visible to the verdict. DEADLINE_VERDICT_TEST_HOOK deterministically
26484    // fires in this window on the driver thread:
26485    //   * `active_drainers` is saturated so `ensure_drainer` cannot start the
26486    //     job on the real worker -- `blocking_tasks_started` provably cannot
26487    //     advance behind our back;
26488    //   * the enqueue counter must therefore save the healthy park.
26489    use std::sync::mpsc;
26490
26491    let (done_tx, done_rx) = mpsc::channel();
26492    let runner = std::thread::spawn(move || {
26493      let options = RuntimeOptions {
26494        flavor: RuntimeFlavor::MultiThread,
26495        worker_threads: 1,
26496        max_blocking_tasks: 1,
26497        thread_name_prefix: "verdict-blocking".to_string(),
26498        park_deadline: Some(Duration::from_millis(40)),
26499      };
26500      let executor =
26501        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26502      executor
26503        .active_drainers
26504        .store(executor.max_drainers, Ordering::SeqCst);
26505
26506      // Until the racing job sends this gate, the awaited future is Pending.
26507      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
26508      // Registry size observed by the hook (must be 0: already deregistered,
26509      // so no permit can possibly be delivered for this enqueue).
26510      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
26511
26512      let hook_exec = Arc::clone(&executor);
26513      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
26514        *slot.borrow_mut() = Some(Box::new(move || {
26515          let handle = hook_exec.schedule_blocking(move || {
26516            let _ = gate_tx.send(11usize);
26517            0usize
26518          });
26519          drop(handle); // result unobserved; the gate send is the signal
26520          hook_saw_tx
26521            .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
26522            .unwrap();
26523        }));
26524      });
26525
26526      // The test-only on-pool marker runs the cooperative branch and its
26527      // thread-local hook on THIS thread.
26528      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26529      let mut output = None;
26530      {
26531        let mut future = std::pin::pin!(async {
26532          output = Some(gate_rx.await.unwrap());
26533        });
26534        executor.block_on(future.as_mut());
26535      }
26536      assert_eq!(
26537        output,
26538        Some(11),
26539        "the enqueued job must have been run by the surviving driver via run_one"
26540      );
26541
26542      let registered_at_hook = hook_saw_rx
26543        .recv()
26544        .expect("the verdict hook must have fired");
26545      assert_eq!(
26546        registered_at_hook, 0,
26547        "the hook fires post-deregister: no permit can be delivered, only the fingerprint's          enqueue counter can catch this submission"
26548      );
26549      executor.active_drainers.store(0, Ordering::SeqCst);
26550      let _ = done_tx.send(());
26551    });
26552
26553    match done_rx.recv_timeout(Duration::from_secs(10)) {
26554      Ok(()) => runner.join().unwrap(),
26555      Err(error) => panic!(
26556        "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
26557      ),
26558    }
26559  }
26560
26561  #[cfg(not(target_family = "wasm"))]
26562  #[test]
26563  fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
26564    // Publish only through the deadlock-admission protocol after every legacy
26565    // final check. No runnable, blocking job, permit, timer, or metrics
26566    // fingerprint changes, so only the synchronized publication epoch can
26567    // prevent the false verdict. The future observes a separate test flag on
26568    // its next poll; that flag is deliberately not a scheduler wake source.
26569    use std::sync::mpsc;
26570
26571    struct ReadyAfterPublication(Arc<AtomicBool>);
26572
26573    impl Future for ReadyAfterPublication {
26574      type Output = ();
26575
26576      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26577        if self.0.load(Ordering::SeqCst) {
26578          Poll::Ready(())
26579        } else {
26580          Poll::Pending
26581        }
26582      }
26583    }
26584
26585    let (done_tx, done_rx) = mpsc::channel();
26586    let runner = std::thread::spawn(move || {
26587      let options = RuntimeOptions {
26588        flavor: RuntimeFlavor::MultiThread,
26589        worker_threads: 1,
26590        max_blocking_tasks: 1,
26591        thread_name_prefix: "final-verdict-publication".to_string(),
26592        park_deadline: Some(Duration::from_millis(40)),
26593      };
26594      let executor =
26595        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26596
26597      let ready = Arc::new(AtomicBool::new(false));
26598      let baseline_fingerprint = executor.metrics.progress_fingerprint();
26599      let baseline_publications = executor.deadlock_state.publications();
26600      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
26601      let hook_exec = Arc::clone(&executor);
26602      let hook_ready = Arc::clone(&ready);
26603      DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
26604        *slot.borrow_mut() = Some(Box::new(move || {
26605          let publication_executor = Arc::clone(&hook_exec);
26606          std::thread::spawn(move || {
26607            let mut admission = publication_executor
26608              .begin_deadlock_admission()
26609              .expect("the deadline detector must enable admission accounting");
26610            hook_ready.store(true, Ordering::SeqCst);
26611            admission.mark_published();
26612          })
26613          .join()
26614          .unwrap();
26615          hook_saw_tx
26616            .send((
26617              hook_exec.deadlock_state.publications(),
26618              !hook_exec.has_queued_runnable(),
26619              hook_exec
26620                .blocking_queue
26621                .lock()
26622                .unwrap_or_else(std::sync::PoisonError::into_inner)
26623                .is_empty(),
26624              hook_exec.next_timer_deadline().is_none(),
26625              hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
26626              hook_exec.parked_drivers.count.load(Ordering::SeqCst),
26627            ))
26628            .unwrap();
26629        }));
26630      });
26631
26632      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26633      let mut future = std::pin::pin!(ReadyAfterPublication(ready));
26634      assert_eq!(
26635        executor.block_on(future.as_mut()),
26636        BlockOnOutcome::Completed,
26637        "the publication epoch must veto the first verdict and permit the next poll"
26638      );
26639      let (
26640        publications,
26641        runnable_queue_empty,
26642        blocking_queue_empty,
26643        timer_heap_empty,
26644        fingerprint_unchanged,
26645        parked_drivers,
26646      ) = hook_saw_rx
26647        .recv()
26648        .expect("the final-verdict hook must have fired");
26649      assert_eq!(
26650        publications,
26651        baseline_publications
26652          .checked_add(1)
26653          .expect("test publication epoch must not overflow"),
26654        "the cross-thread admission must publish exactly one epoch"
26655      );
26656      assert!(
26657        runnable_queue_empty,
26658        "the publication test must not queue a runnable"
26659      );
26660      assert!(
26661        blocking_queue_empty,
26662        "the publication test must not queue blocking work"
26663      );
26664      assert!(
26665        timer_heap_empty,
26666        "the publication test must not register a timer"
26667      );
26668      assert!(
26669        fingerprint_unchanged,
26670        "ordinary metrics progress must not veto the publication-only verdict"
26671      );
26672      assert_eq!(
26673        parked_drivers, 0,
26674        "the publication must land after the cooperative parker was deregistered"
26675      );
26676      let _ = done_tx.send(());
26677    });
26678
26679    match done_rx.recv_timeout(Duration::from_secs(10)) {
26680      Ok(()) => runner.join().unwrap(),
26681      Err(error) => panic!(
26682        "a publication-only cross-thread admission was not observed by the final verdict ({error})"
26683      ),
26684    }
26685  }
26686
26687  #[cfg(not(target_family = "wasm"))]
26688  #[test]
26689  fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
26690    // The exact owner lane is saturated and reserved while the cooperative
26691    // driver parks. At expiry, release selects that driver and publishes its
26692    // handoff, then pauses before unpark. The driver reaches the gated verdict
26693    // with no queue, timer, metric, or ordinary publication change. The
26694    // exact-owner publication boundary must serialize the final check. The
26695    // hook then consumes the ordinary permit and renews the lane reservation,
26696    // leaving only the pending-handoff predicate to veto the false deadlock.
26697    use std::sync::mpsc;
26698
26699    let executor = multi_thread_executor(
26700      1,
26701      Some(Duration::from_millis(40)),
26702      "deadline-selected-handoff",
26703    );
26704    let owner = executor.fresh_owner_token();
26705    executor.active_blocking_owners.register(owner);
26706    executor
26707      .active_blocking
26708      .store(executor.max_blocking, Ordering::SeqCst);
26709
26710    let ready = Arc::new(AtomicBool::new(false));
26711    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
26712    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
26713    let (selected_tx, selected_rx) = mpsc::channel();
26714    let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
26715    let (released_tx, released_rx) = mpsc::channel();
26716    let (renewed_tx, renewed_rx) = mpsc::channel();
26717    let release_executor = Arc::clone(&executor);
26718    let renewal_executor = Arc::clone(&executor);
26719    let release_ready = Arc::clone(&ready);
26720    let releaser = std::thread::spawn(move || {
26721      let reservation =
26722        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
26723          .expect("the synthetic exact-owner lane must start reserved");
26724      reservation_ready_tx.send(()).unwrap();
26725      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
26726        *slot.borrow_mut() = Some(Box::new(move || {
26727          selected_tx.send(()).unwrap();
26728          allow_unpark_rx.recv().unwrap();
26729          let renewed = renewal_executor
26730            .active_blocking_owners
26731            .reserve(owner)
26732            .expect("the released exact-owner lane must be reservable before unpark");
26733          release_ready.store(true, Ordering::SeqCst);
26734          renewed_tx.send(renewed).unwrap();
26735        }));
26736      });
26737      release_reservation_rx.recv().unwrap();
26738      released_tx
26739        .send(release_executor.release_owner_lane_and_wake(reservation))
26740        .unwrap();
26741    });
26742    reservation_ready_rx
26743      .recv_timeout(Duration::from_secs(2))
26744      .expect("the exact-owner reservation must be established before the park");
26745
26746    let baseline_fingerprint = executor.metrics.progress_fingerprint();
26747    let baseline_publications = executor.deadlock_state.publications();
26748    let expiry_executor = Arc::clone(&executor);
26749    DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
26750      *slot.borrow_mut() = Some(Box::new(move || {
26751        release_reservation_tx.send(()).unwrap();
26752        selected_rx
26753          .recv_timeout(Duration::from_secs(2))
26754          .expect("reservation release must select the still-registered exact-owner driver");
26755        assert_eq!(
26756          expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
26757          0,
26758          "handoff selection must remove the driver before pausing ahead of unpark"
26759        );
26760      }));
26761    });
26762
26763    let verdict_executor = Arc::clone(&executor);
26764    let verdict_ready = Arc::clone(&ready);
26765    DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
26766      *slot.borrow_mut() = Some(Box::new(move || {
26767        assert!(
26768          verdict_executor.active_blocking_owners.is_available(owner),
26769          "reservation release must publish lane availability before handoff selection"
26770        );
26771        assert!(!verdict_executor.has_queued_runnable());
26772        assert!(
26773          verdict_executor
26774            .blocking_queue
26775            .lock()
26776            .unwrap_or_else(std::sync::PoisonError::into_inner)
26777            .is_empty()
26778        );
26779        assert!(verdict_executor.next_timer_deadline().is_none());
26780        assert!(
26781          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
26782          "ordinary metrics progress must not save the selected-handoff verdict"
26783        );
26784        assert_eq!(
26785          verdict_executor.deadlock_state.publications(),
26786          baseline_publications
26787        );
26788        assert!(
26789          !verdict_ready.load(Ordering::SeqCst),
26790          "the selected handoff must still be paused before unpark at the gated verdict"
26791        );
26792        allow_unpark_tx.send(()).unwrap();
26793      }));
26794    });
26795    let exact_owner_executor = Arc::clone(&executor);
26796    DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
26797      *slot.borrow_mut() = Some(Box::new(move |parker| {
26798        assert!(
26799          parker.consume_permit(),
26800          "the selected handoff must unpark before exact-owner final predicates run"
26801        );
26802        assert!(
26803          parker.has_owner_handoff(),
26804          "consuming the ordinary permit must leave the exact-owner handoff pending"
26805        );
26806        assert!(
26807          !exact_owner_executor
26808            .active_blocking_owners
26809            .is_available(owner),
26810          "the renewed reservation must prevent lane availability from saving the verdict"
26811        );
26812      }));
26813    });
26814
26815    {
26816      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26817      let _owner = BlockingOwnerGuard::enter(Some(owner));
26818      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
26819        dependency: BlockingDependency {
26820          job: BlockingJobId(u64::MAX - 80),
26821          owner: Some(owner)
26822        },
26823        ready: Arc::clone(&ready),
26824      });
26825      assert_eq!(
26826        executor.block_on(future.as_mut()),
26827        BlockOnOutcome::Completed
26828      );
26829    }
26830    assert!(
26831      released_rx
26832        .recv_timeout(Duration::from_secs(2))
26833        .expect("the selected handoff publication must finish"),
26834      "reservation release must have selected the cooperative driver"
26835    );
26836    let renewed = renewed_rx
26837      .recv_timeout(Duration::from_secs(2))
26838      .expect("the selected handoff must renew the owner reservation before unpark");
26839    releaser.join().unwrap();
26840
26841    executor.active_blocking_owners.release(owner, renewed);
26842    executor.active_blocking.store(0, Ordering::SeqCst);
26843    executor.active_blocking_owners.unregister(owner);
26844    executor.begin_shutdown();
26845    executor.wait_until_scheduler_idle();
26846  }
26847
26848  #[cfg(not(target_family = "wasm"))]
26849  #[test]
26850  fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
26851    // Release begins only after the deadline path has deregistered its parker
26852    // and completed every legacy final check. No targeted wake or ordinary
26853    // scheduler publication can save the verdict; the synchronized exact-owner
26854    // availability predicate must observe the released lane.
26855    use std::sync::mpsc;
26856
26857    let executor = multi_thread_executor(
26858      1,
26859      Some(Duration::from_millis(40)),
26860      "deadline-owner-no-parker",
26861    );
26862    let owner = executor.fresh_owner_token();
26863    executor.active_blocking_owners.register(owner);
26864    executor
26865      .active_blocking
26866      .store(executor.max_blocking, Ordering::SeqCst);
26867
26868    let ready = Arc::new(AtomicBool::new(false));
26869    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
26870    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
26871    let (released_tx, released_rx) = mpsc::channel();
26872    let release_executor = Arc::clone(&executor);
26873    let release_ready = Arc::clone(&ready);
26874    let releaser = std::thread::spawn(move || {
26875      let reservation =
26876        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
26877          .expect("the synthetic exact-owner lane must start reserved");
26878      reservation_ready_tx.send(()).unwrap();
26879      release_reservation_rx.recv().unwrap();
26880      let woke = release_executor.release_owner_lane_and_wake(reservation);
26881      release_ready.store(true, Ordering::SeqCst);
26882      released_tx.send(woke).unwrap();
26883    });
26884    reservation_ready_rx
26885      .recv_timeout(Duration::from_secs(2))
26886      .expect("the exact-owner reservation must be established before the park");
26887
26888    let baseline_fingerprint = executor.metrics.progress_fingerprint();
26889    let baseline_publications = executor.deadlock_state.publications();
26890    let verdict_executor = Arc::clone(&executor);
26891    DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
26892      *slot.borrow_mut() = Some(Box::new(move || {
26893        assert_eq!(
26894          verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
26895          0,
26896          "the release must begin only after deadline deregistration"
26897        );
26898        release_reservation_tx.send(()).unwrap();
26899        assert!(
26900          !released_rx
26901            .recv_timeout(Duration::from_secs(2))
26902            .expect("the no-parker owner release must finish"),
26903          "no registered parker may receive the released owner lane"
26904        );
26905        assert!(verdict_executor.active_blocking_owners.is_available(owner));
26906        assert!(!verdict_executor.has_queued_runnable());
26907        assert!(
26908          verdict_executor
26909            .blocking_queue
26910            .lock()
26911            .unwrap_or_else(std::sync::PoisonError::into_inner)
26912            .is_empty()
26913        );
26914        assert!(verdict_executor.next_timer_deadline().is_none());
26915        assert!(
26916          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
26917          "ordinary metrics progress must not save the no-parker release verdict"
26918        );
26919        assert_eq!(
26920          verdict_executor.deadlock_state.publications(),
26921          baseline_publications
26922        );
26923      }));
26924    });
26925
26926    {
26927      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26928      let _owner = BlockingOwnerGuard::enter(Some(owner));
26929      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
26930        dependency: BlockingDependency {
26931          job: BlockingJobId(u64::MAX - 81),
26932          owner: Some(owner)
26933        },
26934        ready: Arc::clone(&ready),
26935      });
26936      assert_eq!(
26937        executor.block_on(future.as_mut()),
26938        BlockOnOutcome::Completed
26939      );
26940    }
26941    releaser.join().unwrap();
26942
26943    executor.active_blocking.store(0, Ordering::SeqCst);
26944    executor.active_blocking_owners.unregister(owner);
26945    executor.begin_shutdown();
26946    executor.wait_until_scheduler_idle();
26947  }
26948
26949  #[cfg(not(target_family = "wasm"))]
26950  #[test]
26951  fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
26952    // Pin the shutdown race after the final verdict gate has closed:
26953    //   1. an accepted task is pending, so shutdown abort must wake it;
26954    //   2. a cooperative block_on reaches its deadline and pauses while
26955    //      holding the closed verdict gate;
26956    //   3. shutdown publishes generation stop, transitions
26957    //      Running -> Stopping, then its abort wake blocks entering scheduler
26958    //      admission behind that gate;
26959    //   4. the gated final verdict must observe stop and return Stopped,
26960    //      reopening the gate so the abort wake and shutdown can finish.
26961    //
26962    // Before stop was published by the controller transition, step 3 blocked
26963    // inside abort before executor shutdown could set it. The driver then saw
26964    // empty queues and emitted a false BlockOnDeadlock.
26965    use std::sync::mpsc;
26966
26967    let controller = Arc::new(RuntimeController::new());
26968    controller
26969      .configure(RuntimeOptions {
26970        flavor: RuntimeFlavor::MultiThread,
26971        worker_threads: 2,
26972        max_blocking_tasks: 1,
26973        thread_name_prefix: "shutdown-gated-verdict".to_string(),
26974        park_deadline: Some(Duration::from_millis(40)),
26975      })
26976      .expect("the shutdown race test configuration must be accepted");
26977
26978    let backend = controller.backend();
26979    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26980      panic!("the configured backend must be MultiThread");
26981    };
26982    let executor_id = executor.id;
26983    let weak_executor = Arc::downgrade(executor);
26984    drop(backend);
26985
26986    let (task_polled_tx, task_polled_rx) = mpsc::channel();
26987    let task = controller
26988      .try_spawn(async move {
26989        task_polled_tx.send(()).unwrap();
26990        std::future::pending::<()>().await;
26991      })
26992      .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
26993    task_polled_rx
26994      .recv_timeout(Duration::from_secs(2))
26995      .expect("the accepted task must publish an abortable pending waker");
26996
26997    let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
26998    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
26999    let (driver_result_tx, driver_result_rx) = mpsc::channel();
27000    let driver_controller = Arc::clone(&controller);
27001    let driver = std::thread::spawn(move || {
27002      DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
27003        *slot.borrow_mut() = Some(Box::new(move || {
27004          gate_closed_tx.send(()).unwrap();
27005          release_verdict_rx.recv().unwrap();
27006        }));
27007      });
27008
27009      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
27010      let mut pending = std::pin::pin!(NeverReady);
27011      let result = catch_unwind(AssertUnwindSafe(|| {
27012        driver_controller.block_on(pending.as_mut())
27013      }));
27014      let result = match result {
27015        Ok(outcome) => Ok(outcome),
27016        Err(payload) => {
27017          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
27018          discard_panic_payload(payload);
27019          Err(was_deadlock)
27020        }
27021      };
27022      driver_result_tx.send(result).unwrap();
27023    });
27024
27025    gate_closed_rx
27026      .recv_timeout(Duration::from_secs(2))
27027      .expect("the cooperative driver must pause with the verdict gate closed");
27028
27029    let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
27030    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
27031    let shutdown_controller = Arc::clone(&controller);
27032    let shutdown = std::thread::spawn(move || {
27033      DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
27034        *slot.borrow_mut() = Some(Box::new(move || {
27035          abort_blocked_tx.send(()).unwrap();
27036        }));
27037      });
27038      shutdown_result_tx
27039        .send(shutdown_controller.shutdown())
27040        .unwrap();
27041    });
27042
27043    abort_blocked_rx
27044      .recv_timeout(Duration::from_secs(2))
27045      .expect("the accepted task's abort wake must reach the closed verdict gate");
27046    let lifecycle_is_stopping = {
27047      let state = controller
27048        .state
27049        .lock()
27050        .unwrap_or_else(std::sync::PoisonError::into_inner);
27051      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
27052    };
27053    let stop_was_published = weak_executor
27054      .upgrade()
27055      .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
27056
27057    release_verdict_tx.send(()).unwrap();
27058
27059    let driver_result = driver_result_rx
27060      .recv_timeout(Duration::from_secs(2))
27061      .expect("the gated driver must resolve after observing shutdown");
27062    let shutdown_result = shutdown_result_rx
27063      .recv_timeout(Duration::from_secs(5))
27064      .expect("shutdown must finish after the verdict gate reopens");
27065    // Both threads have sent their results; what remains is teardown (and on
27066    // the shutdown thread, the controller's `wait_for_all_workers` already
27067    // completed inside `shutdown()`). This test was one of the silent wedges
27068    // on the Windows CI runners -- bound the residue loudly.
27069    join_within(
27070      "the gated cooperative driver exits after teardown",
27071      driver,
27072      Duration::from_secs(30),
27073    );
27074    join_within(
27075      "the shutdown thread exits after teardown",
27076      shutdown,
27077      Duration::from_secs(30),
27078    );
27079
27080    assert!(
27081      lifecycle_is_stopping,
27082      "the abort wake must occur after Running -> Stopping"
27083    );
27084    assert!(
27085      stop_was_published,
27086      "generation stop must be visible before shutdown invokes accepted-task aborts"
27087    );
27088    assert_eq!(
27089      driver_result,
27090      Ok(BlockOnOutcome::Stopped),
27091      "shutdown must not be reported as a gated BlockOnDeadlock"
27092    );
27093    shutdown_result.unwrap();
27094    assert!(
27095      futures::executor::block_on(task).is_err(),
27096      "the accepted pending task must resolve as cancelled"
27097    );
27098  }
27099
27100  #[cfg(not(target_family = "wasm"))]
27101  #[test]
27102  fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
27103    // Complement the publication-wins race above. The verdict owns both its
27104    // admission gate and the stop-publication mutex before shutdown starts.
27105    // Shutdown reaches the lifecycle -> publication edge but cannot mutate
27106    // Running -> Stopping until the verdict finishes its synchronized checks.
27107    use std::sync::mpsc;
27108
27109    let controller = Arc::new(RuntimeController::new());
27110    controller
27111      .configure(RuntimeOptions {
27112        flavor: RuntimeFlavor::MultiThread,
27113        worker_threads: 2,
27114        max_blocking_tasks: 1,
27115        thread_name_prefix: "verdict-before-shutdown".to_string(),
27116        park_deadline: Some(Duration::from_millis(40)),
27117      })
27118      .expect("the verdict-first test configuration must be accepted");
27119
27120    let backend = controller.backend();
27121    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27122      panic!("the configured backend must be MultiThread");
27123    };
27124    let executor_id = executor.id;
27125    drop(backend);
27126
27127    let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
27128    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
27129    let (driver_result_tx, driver_result_rx) = mpsc::channel();
27130    let driver_controller = Arc::clone(&controller);
27131    let driver = std::thread::spawn(move || {
27132      DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
27133        *slot.borrow_mut() = Some(Box::new(move || {
27134          verdict_locked_tx.send(()).unwrap();
27135          release_verdict_rx.recv().unwrap();
27136        }));
27137      });
27138
27139      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
27140      let mut pending = std::pin::pin!(NeverReady);
27141      let result = catch_unwind(AssertUnwindSafe(|| {
27142        driver_controller.block_on(pending.as_mut())
27143      }));
27144      let result = match result {
27145        Ok(outcome) => Ok(outcome),
27146        Err(payload) => {
27147          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
27148          discard_panic_payload(payload);
27149          Err(was_deadlock)
27150        }
27151      };
27152      driver_result_tx.send(result).unwrap();
27153    });
27154
27155    verdict_locked_rx
27156      .recv_timeout(Duration::from_secs(2))
27157      .expect("the verdict must hold the synchronized stop-publication guard");
27158
27159    let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
27160    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
27161    let shutdown_controller = Arc::clone(&controller);
27162    let shutdown = std::thread::spawn(move || {
27163      BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
27164        *slot.borrow_mut() = Some(Box::new(move || {
27165          shutdown_waiting_tx.send(()).unwrap();
27166        }));
27167      });
27168      shutdown_result_tx
27169        .send(shutdown_controller.shutdown())
27170        .unwrap();
27171    });
27172
27173    shutdown_waiting_rx
27174      .recv_timeout(Duration::from_secs(2))
27175      .expect("shutdown must reach the lifecycle-to-publication lock edge");
27176    let early_shutdown = shutdown_result_rx
27177      .recv_timeout(Duration::from_millis(100))
27178      .ok();
27179    let shutdown_was_blocked = early_shutdown.is_none();
27180    release_verdict_tx.send(()).unwrap();
27181
27182    let driver_result = driver_result_rx
27183      .recv_timeout(Duration::from_secs(2))
27184      .expect("the verdict-first driver must finish after release");
27185    let shutdown_result = early_shutdown.unwrap_or_else(|| {
27186      shutdown_result_rx
27187        .recv_timeout(Duration::from_secs(5))
27188        .expect("shutdown must finish after the verdict releases publication")
27189    });
27190    driver.join().unwrap();
27191    shutdown.join().unwrap();
27192
27193    assert!(
27194      shutdown_was_blocked,
27195      "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
27196    );
27197    assert_eq!(
27198      driver_result,
27199      Err(true),
27200      "a verdict that linearizes before shutdown must retain the typed deadlock"
27201    );
27202    shutdown_result.unwrap();
27203  }
27204
27205  #[cfg(not(target_family = "wasm"))]
27206  #[test]
27207  fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
27208    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
27209
27210    if std::env::var_os(CHILD_ENV).is_some() {
27211      let executor =
27212        multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
27213      // Keep the synthetic cooperative driver from arming a Rayon drainer. The
27214      // hook only needs to prove that scheduler admission returns.
27215      executor
27216        .active_drainers
27217        .store(executor.max_drainers, Ordering::SeqCst);
27218
27219      let hook_returned = Arc::new(AtomicBool::new(false));
27220      let hook_flag = Arc::clone(&hook_returned);
27221      let hook_executor = Arc::clone(&executor);
27222      let previous_hook = std::panic::take_hook();
27223      std::panic::set_hook(Box::new(move |_| {
27224        let handle = hook_executor.schedule_blocking(|| ());
27225        drop(handle);
27226        hook_flag.store(true, Ordering::SeqCst);
27227      }));
27228
27229      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27230      let result = catch_unwind(AssertUnwindSafe(|| {
27231        let mut pending = std::pin::pin!(std::future::pending::<()>());
27232        executor.block_on(pending.as_mut());
27233      }));
27234      std::panic::set_hook(previous_hook);
27235
27236      let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
27237      assert!(
27238        payload.downcast_ref::<BlockOnDeadlock>().is_some(),
27239        "the deadline must retain its structured diagnostic"
27240      );
27241      assert!(
27242        hook_returned.load(Ordering::SeqCst),
27243        "the panic hook's scheduler submission must return before unwind"
27244      );
27245
27246      executor.active_drainers.store(0, Ordering::SeqCst);
27247      executor.begin_shutdown();
27248      executor.wait_until_scheduler_idle();
27249      return;
27250    }
27251
27252    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
27253      .arg("--exact")
27254      .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
27255      .arg("--nocapture")
27256      .env(CHILD_ENV, "1")
27257      .stdout(std::process::Stdio::piped())
27258      .stderr(std::process::Stdio::piped())
27259      .spawn()
27260      .expect("the panic-hook subprocess must start");
27261    let deadline = Instant::now() + Duration::from_secs(5);
27262    let status = loop {
27263      if let Some(status) = child
27264        .try_wait()
27265        .expect("the subprocess status must be readable")
27266      {
27267        break status;
27268      }
27269      if Instant::now() >= deadline {
27270        let _ = child.kill();
27271        let output = child
27272          .wait_with_output()
27273          .expect("the timed-out subprocess must be reaped");
27274        panic!(
27275          "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
27276          String::from_utf8_lossy(&output.stdout),
27277          String::from_utf8_lossy(&output.stderr)
27278        );
27279      }
27280      std::thread::sleep(Duration::from_millis(10));
27281    };
27282    let output = child
27283      .wait_with_output()
27284      .expect("the completed subprocess output must be read");
27285    assert!(
27286      status.success(),
27287      "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
27288      status.code(),
27289      String::from_utf8_lossy(&output.stdout),
27290      String::from_utf8_lossy(&output.stderr)
27291    );
27292  }
27293
27294  // The PARK_DEADLINE_ENV parse (unset/zero/garbage => disabled) lives in
27295  // the embedding host's env-resolution pipeline together with the read
27296  // itself; its tests live there.
27297
27298  // ---- Timer facility: heap driver + timekeeper (MT), host delegation (CT) --
27299  // The tests cover both deadlock-detection interactions. All tests build
27300  // executors LOCALLY (never the global RUNTIME or the process-global host
27301  // driver) and run hang-prone workloads on a child thread bounded by
27302  // `recv_timeout`, so a regression fails loudly instead of wedging the suite.
27303
27304  #[test]
27305  fn contained_waker_separates_wake_and_drop_panics() {
27306    struct PanicWakeAndDrop;
27307
27308    impl std::task::Wake for PanicWakeAndDrop {
27309      fn wake(self: Arc<Self>) {
27310        panic!("intentional consuming wake panic");
27311      }
27312
27313      fn wake_by_ref(self: &Arc<Self>) {
27314        panic!("intentional borrowed wake panic");
27315      }
27316    }
27317
27318    impl Drop for PanicWakeAndDrop {
27319      fn drop(&mut self) {
27320        panic!("intentional RawWaker destructor panic");
27321      }
27322    }
27323
27324    wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
27325  }
27326
27327  struct TestHostileRawWakerState {
27328    clones: AtomicUsize,
27329    drop_panics: AtomicUsize,
27330    panic_on_clone: Option<usize>,
27331  }
27332
27333  struct TestHostileRawWakerToken {
27334    state: Arc<TestHostileRawWakerState>,
27335    panic_on_drop: bool,
27336  }
27337
27338  unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
27339    let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
27340    let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
27341    assert_ne!(
27342      token.state.panic_on_clone,
27343      Some(clone),
27344      "intentional RawWaker clone panic"
27345    );
27346    let clone = Box::new(TestHostileRawWakerToken {
27347      state: Arc::clone(&token.state),
27348      panic_on_drop: true,
27349    });
27350    std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
27351  }
27352
27353  unsafe fn wake_test_hostile_waker(data: *const ()) {
27354    unsafe { drop_test_hostile_waker(data) };
27355  }
27356
27357  unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
27358
27359  unsafe fn drop_test_hostile_waker(data: *const ()) {
27360    let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
27361    if token.panic_on_drop {
27362      token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
27363      panic!("intentional cloned RawWaker destructor panic");
27364    }
27365  }
27366
27367  static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
27368    clone_test_hostile_waker,
27369    wake_test_hostile_waker,
27370    wake_test_hostile_waker_by_ref,
27371    drop_test_hostile_waker,
27372  );
27373
27374  fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
27375    let token = Box::new(TestHostileRawWakerToken {
27376      state,
27377      panic_on_drop: false,
27378    });
27379    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
27380    unsafe { Waker::from_raw(raw) }
27381  }
27382
27383  /// Waker that only counts. For polling a raw `Sleep` without an executor.
27384  struct CountingWake(AtomicUsize);
27385  impl std::task::Wake for CountingWake {
27386    fn wake(self: Arc<Self>) {
27387      self.0.fetch_add(1, Ordering::SeqCst);
27388    }
27389  }
27390
27391  /// One armed stub timer: the latest waker plus a cancellation flag.
27392  type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
27393
27394  /// Test-local host driver: fires each registered timer from a helper OS
27395  /// thread at its deadline (re-registration under the same id refreshes the
27396  /// waker without arming a second thread, per the `TimerDriver` contract).
27397  struct StubHostTimerDriver {
27398    timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
27399  }
27400
27401  impl StubHostTimerDriver {
27402    fn new() -> Self {
27403      Self {
27404        timers: Mutex::new(FxHashMap::default()),
27405      }
27406    }
27407  }
27408
27409  impl TimerDriver for StubHostTimerDriver {
27410    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
27411      let mut timers = self.timers.lock().unwrap();
27412      if let Some(cell) = timers.get(&id) {
27413        *cell.0.lock().unwrap() = waker;
27414        return;
27415      }
27416      let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
27417      timers.insert(id, Arc::clone(&cell));
27418      std::thread::spawn(move || {
27419        let now = Instant::now();
27420        if deadline > now {
27421          std::thread::sleep(deadline - now);
27422        }
27423        if !cell.1.load(Ordering::SeqCst) {
27424          cell.0.lock().unwrap().wake_by_ref();
27425        }
27426      });
27427    }
27428
27429    fn cancel(&self, id: TimerId) {
27430      let removed = self.timers.lock().unwrap().remove(&id);
27431      if let Some(cell) = removed {
27432        cell.1.store(true, Ordering::SeqCst);
27433      }
27434    }
27435  }
27436
27437  /// Manual stub for registry-lifetime tests: records `register`/`cancel`
27438  /// calls, keeps the binding-shaped pending-waker bookkeeping (latest waker
27439  /// per timer id), never fires on its own, and reports the controllable
27440  /// `live` flag (a killed stub plays a driver whose owning host context --
27441  /// napi env in the binding -- was torn down).
27442  struct ManualStubDriver {
27443    live: AtomicBool,
27444    registers: Mutex<Vec<(TimerId, Instant)>>,
27445    cancels: Mutex<Vec<TimerId>>,
27446    pending: Mutex<FxHashMap<TimerId, Waker>>,
27447    swept: AtomicBool,
27448  }
27449
27450  impl ManualStubDriver {
27451    fn new() -> Arc<Self> {
27452      Arc::new(Self {
27453        live: AtomicBool::new(true),
27454        registers: Mutex::new(Vec::new()),
27455        cancels: Mutex::new(Vec::new()),
27456        pending: Mutex::new(FxHashMap::default()),
27457        swept: AtomicBool::new(false),
27458      })
27459    }
27460
27461    fn kill(&self) {
27462      self.live.store(false, Ordering::SeqCst);
27463    }
27464  }
27465
27466  impl TimerDriver for ManualStubDriver {
27467    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
27468      self.registers.lock().unwrap().push((id, deadline));
27469      self.pending.lock().unwrap().insert(id, waker);
27470    }
27471
27472    fn cancel(&self, id: TimerId) {
27473      self.cancels.lock().unwrap().push(id);
27474      self.pending.lock().unwrap().remove(&id);
27475    }
27476
27477    fn is_live(&self) -> bool {
27478      self.live.load(Ordering::SeqCst)
27479    }
27480
27481    fn on_swept(&self) {
27482      // The binding shape: drain the pending map and wake everything armed
27483      // here so those sleeps re-poll onto the next live registrant.
27484      self.swept.store(true, Ordering::SeqCst);
27485      let wakers: Vec<Waker> = self
27486        .pending
27487        .lock()
27488        .unwrap()
27489        .drain()
27490        .map(|(_, w)| w)
27491        .collect();
27492      for waker in wakers {
27493        waker.wake();
27494      }
27495    }
27496  }
27497
27498  struct PanickingTimerDriver {
27499    swept: AtomicBool,
27500  }
27501
27502  impl TimerDriver for PanickingTimerDriver {
27503    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27504
27505    fn cancel(&self, _id: TimerId) {}
27506
27507    fn is_live(&self) -> bool {
27508      panic!("intentional timer-driver liveness panic");
27509    }
27510
27511    fn on_swept(&self) {
27512      self.swept.store(true, Ordering::SeqCst);
27513      panic!("intentional timer-driver sweep panic");
27514    }
27515  }
27516
27517  struct PanickingRegisterTimerDriver {
27518    registers: AtomicUsize,
27519    cancels: Mutex<Vec<TimerId>>,
27520    pending: Mutex<FxHashMap<TimerId, Waker>>,
27521  }
27522
27523  impl PanickingRegisterTimerDriver {
27524    fn new() -> Arc<Self> {
27525      Arc::new(Self {
27526        registers: AtomicUsize::new(0),
27527        cancels: Mutex::new(Vec::new()),
27528        pending: Mutex::new(FxHashMap::default()),
27529      })
27530    }
27531  }
27532
27533  impl TimerDriver for PanickingRegisterTimerDriver {
27534    fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
27535      self.registers.fetch_add(1, Ordering::SeqCst);
27536      self.pending.lock().unwrap().insert(id, waker);
27537      panic!("intentional timer-driver register panic after arming");
27538    }
27539
27540    fn cancel(&self, id: TimerId) {
27541      self.cancels.lock().unwrap().push(id);
27542      self.pending.lock().unwrap().remove(&id);
27543    }
27544  }
27545
27546  struct PanicBeforeRetainingTimerDriver {
27547    registers: AtomicUsize,
27548  }
27549
27550  impl TimerDriver for PanicBeforeRetainingTimerDriver {
27551    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
27552      self.registers.fetch_add(1, Ordering::SeqCst);
27553      panic!("intentional timer-driver register panic before retaining its waker");
27554    }
27555
27556    fn cancel(&self, _id: TimerId) {}
27557  }
27558
27559  struct PanickingCancelDropTimerDriver {
27560    cancels: Arc<AtomicUsize>,
27561    drops: Arc<AtomicUsize>,
27562  }
27563
27564  impl TimerDriver for PanickingCancelDropTimerDriver {
27565    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27566
27567    fn cancel(&self, _id: TimerId) {
27568      self.cancels.fetch_add(1, Ordering::SeqCst);
27569      panic!("intentional timer-driver cancel panic");
27570    }
27571  }
27572
27573  impl Drop for PanickingCancelDropTimerDriver {
27574    fn drop(&mut self) {
27575      self.drops.fetch_add(1, Ordering::SeqCst);
27576      panic!("intentional timer-driver destructor panic");
27577    }
27578  }
27579
27580  struct UnregisterLaterThenPanicTimerDriver {
27581    registry: Weak<TimerDriverRegistry>,
27582    later: Mutex<Option<TimerDriverId>>,
27583    registers: AtomicUsize,
27584  }
27585
27586  impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
27587    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
27588      self.registers.fetch_add(1, Ordering::SeqCst);
27589      let later = *self.later.lock().unwrap();
27590      if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
27591        registry.unregister(later);
27592      }
27593      panic!("intentional earlier timer-driver panic after unregistering a later driver");
27594    }
27595
27596    fn cancel(&self, _id: TimerId) {}
27597  }
27598
27599  struct ReentrantLivenessDriver {
27600    registry: std::sync::Weak<TimerDriverRegistry>,
27601    registration: Mutex<Option<TimerDriverId>>,
27602  }
27603
27604  impl TimerDriver for ReentrantLivenessDriver {
27605    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27606
27607    fn cancel(&self, _id: TimerId) {}
27608
27609    fn is_live(&self) -> bool {
27610      let registration = *self.registration.lock().unwrap();
27611      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
27612        registry.unregister(id);
27613      }
27614      false
27615    }
27616  }
27617
27618  struct ReentrantMultiUnregisterTimerDriver {
27619    registry: std::sync::Weak<TimerDriverRegistry>,
27620    registrations: Mutex<Vec<TimerDriverId>>,
27621  }
27622
27623  impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
27624    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27625
27626    fn cancel(&self, _id: TimerId) {}
27627
27628    fn is_live(&self) -> bool {
27629      if let Some(registry) = self.registry.upgrade() {
27630        for registration in self.registrations.lock().unwrap().iter().copied() {
27631          registry.unregister(registration);
27632        }
27633      }
27634      false
27635    }
27636  }
27637
27638  struct ReentrantSweepTimerDriver {
27639    registry: std::sync::Weak<TimerDriverRegistry>,
27640    registration: Mutex<Option<TimerDriverId>>,
27641    sweeps: AtomicUsize,
27642    removed_before_sweep: AtomicBool,
27643  }
27644
27645  impl TimerDriver for ReentrantSweepTimerDriver {
27646    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27647
27648    fn cancel(&self, _id: TimerId) {}
27649
27650    fn is_live(&self) -> bool {
27651      false
27652    }
27653
27654    fn on_swept(&self) {
27655      self.sweeps.fetch_add(1, Ordering::SeqCst);
27656      let registration = self.registration.lock().unwrap().take();
27657      if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
27658        let state = registry
27659          .state
27660          .try_lock()
27661          .expect("the sweep callback must run outside the timer-driver registry lock");
27662        assert!(
27663          !state.entries.contains_key(&registration),
27664          "the dead driver must be unlinked before its sweep callback runs"
27665        );
27666        self.removed_before_sweep.store(true, Ordering::SeqCst);
27667        drop(state);
27668        // Mirrors JsTimerHostInner::evict: the sweep already removed this
27669        // registration, so explicit host cleanup must remain a cheap no-op.
27670        registry.unregister(registration);
27671      }
27672    }
27673  }
27674
27675  struct ReentrantDropDriver {
27676    registry: std::sync::Weak<TimerDriverRegistry>,
27677    fallback: TimerDriverId,
27678  }
27679
27680  impl TimerDriver for ReentrantDropDriver {
27681    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27682
27683    fn cancel(&self, _id: TimerId) {}
27684  }
27685
27686  impl Drop for ReentrantDropDriver {
27687    fn drop(&mut self) {
27688      if let Some(registry) = self.registry.upgrade() {
27689        registry.unregister(self.fallback);
27690      }
27691    }
27692  }
27693
27694  /// Waker whose `wake` just latches a flag -- observable evidence that a
27695  /// wake was DELIVERED (not merely that eviction bookkeeping ran).
27696  #[derive(Default)]
27697  struct WakeFlag(AtomicBool);
27698
27699  impl std::task::Wake for WakeFlag {
27700    fn wake(self: Arc<Self>) {
27701      self.0.store(true, Ordering::SeqCst);
27702    }
27703  }
27704
27705  #[cfg(not(target_family = "wasm"))]
27706  fn multi_thread_executor(
27707    workers: usize,
27708    park_deadline: Option<Duration>,
27709    prefix: &str,
27710  ) -> Arc<MultiThreadExecutor> {
27711    let options = RuntimeOptions {
27712      flavor: RuntimeFlavor::MultiThread,
27713      worker_threads: workers,
27714      max_blocking_tasks: workers,
27715      thread_name_prefix: prefix.to_string(),
27716      park_deadline,
27717    };
27718    Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
27719  }
27720
27721  /// Local driver registry holding exactly `driver` (the common single-host
27722  /// shape of the ported pre-registry tests).
27723  fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
27724    let registry = Arc::new(TimerDriverRegistry::default());
27725    registry.register(driver);
27726    registry
27727  }
27728
27729  #[cfg(not(target_family = "wasm"))]
27730  fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
27731    // The MultiThread arm never touches the driver registry; empty is fine.
27732    make_sleep(
27733      &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
27734      &Arc::new(TimerDriverRegistry::default()),
27735      deadline,
27736    )
27737  }
27738
27739  #[cfg(not(target_family = "wasm"))]
27740  #[test]
27741  fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
27742    use std::sync::mpsc;
27743
27744    let executor = multi_thread_executor(
27745      1,
27746      Some(Duration::from_secs(1)),
27747      "timer-registration-admission",
27748    );
27749    let (entered_tx, entered_rx) = mpsc::channel();
27750    let (release_tx, release_rx) = mpsc::channel();
27751    let registration_executor = Arc::clone(&executor);
27752    let registration = std::thread::spawn(move || {
27753      TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
27754        *slot.borrow_mut() = Some(Box::new(move || {
27755          entered_tx.send(()).unwrap();
27756          release_rx.recv().unwrap();
27757        }));
27758      });
27759      registration_executor.register_timer(
27760        1,
27761        Instant::now() + Duration::from_secs(60),
27762        futures::task::noop_waker(),
27763        &Arc::new(AtomicBool::new(false)),
27764      );
27765    });
27766
27767    entered_rx
27768      .recv_timeout(Duration::from_secs(2))
27769      .expect("timer registration must pause after entering verdict admission");
27770    let verdict = executor.try_begin_deadlock_verdict();
27771    let admission_blocked_verdict = verdict.is_none();
27772    drop(verdict);
27773    release_tx.send(()).unwrap();
27774    registration.join().unwrap();
27775
27776    assert!(
27777      admission_blocked_verdict,
27778      "a timer registration must remain announced until its heap entry is visible"
27779    );
27780    assert!(
27781      executor.next_timer_deadline().is_some(),
27782      "the admitted registration must publish its timer after release"
27783    );
27784    executor.begin_shutdown();
27785    executor.wait_until_scheduler_idle();
27786  }
27787
27788  #[cfg(not(target_family = "wasm"))]
27789  #[derive(Clone, Copy, Debug)]
27790  enum TimerFireAdmissionPath {
27791    Timekeeper,
27792    Cooperative,
27793    Shutdown,
27794  }
27795
27796  #[cfg(not(target_family = "wasm"))]
27797  struct ScheduleAfterTimerWakeRelease {
27798    executor: Arc<MultiThreadExecutor>,
27799    runnable: Mutex<Option<Runnable>>,
27800    entered: std::sync::mpsc::Sender<()>,
27801    release: Mutex<std::sync::mpsc::Receiver<()>>,
27802    scheduled: std::sync::mpsc::Sender<()>,
27803  }
27804
27805  #[cfg(not(target_family = "wasm"))]
27806  impl ScheduleAfterTimerWakeRelease {
27807    fn publish(&self) {
27808      self.entered.send(()).unwrap();
27809      self
27810        .release
27811        .lock()
27812        .unwrap_or_else(std::sync::PoisonError::into_inner)
27813        .recv()
27814        .unwrap();
27815      let runnable = self
27816        .runnable
27817        .lock()
27818        .unwrap_or_else(std::sync::PoisonError::into_inner)
27819        .take();
27820      if let Some(runnable) = runnable {
27821        self.executor.schedule(runnable);
27822      }
27823      self.scheduled.send(()).unwrap();
27824    }
27825  }
27826
27827  #[cfg(not(target_family = "wasm"))]
27828  impl std::task::Wake for ScheduleAfterTimerWakeRelease {
27829    fn wake(self: Arc<Self>) {
27830      self.publish();
27831    }
27832
27833    fn wake_by_ref(self: &Arc<Self>) {
27834      self.publish();
27835    }
27836  }
27837
27838  #[cfg(not(target_family = "wasm"))]
27839  fn insert_timer_for_fire_admission_test(
27840    executor: &MultiThreadExecutor,
27841    id: TimerId,
27842    deadline: Instant,
27843    waker: Waker,
27844    fired: Arc<AtomicBool>,
27845  ) {
27846    let mut inner = executor
27847      .timers
27848      .inner
27849      .lock()
27850      .unwrap_or_else(std::sync::PoisonError::into_inner);
27851    inner.entries.insert(id, HeapTimerEntry { waker, fired });
27852    inner.queue.push(deadline, id);
27853  }
27854
27855  #[cfg(not(target_family = "wasm"))]
27856  fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
27857    use std::sync::mpsc;
27858
27859    let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
27860    let (runnable, task) = async_task::spawn(async {}, |_| {});
27861    task.detach();
27862    let (entered_tx, entered_rx) = mpsc::channel();
27863    let (release_tx, release_rx) = mpsc::channel();
27864    let (scheduled_tx, scheduled_rx) = mpsc::channel();
27865    let fired = Arc::new(AtomicBool::new(false));
27866    let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
27867      executor: Arc::clone(&executor),
27868      runnable: Mutex::new(Some(runnable)),
27869      entered: entered_tx,
27870      release: Mutex::new(release_rx),
27871      scheduled: scheduled_tx,
27872    }));
27873
27874    let fire_thread = match path {
27875      TimerFireAdmissionPath::Timekeeper => {
27876        executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
27877        None
27878      }
27879      TimerFireAdmissionPath::Cooperative => {
27880        insert_timer_for_fire_admission_test(
27881          &executor,
27882          1,
27883          Instant::now(),
27884          waker,
27885          Arc::clone(&fired),
27886        );
27887        let fire_executor = Arc::clone(&executor);
27888        Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
27889      }
27890      TimerFireAdmissionPath::Shutdown => {
27891        insert_timer_for_fire_admission_test(
27892          &executor,
27893          1,
27894          Instant::now() + Duration::from_secs(60),
27895          waker,
27896          Arc::clone(&fired),
27897        );
27898        let fire_executor = Arc::clone(&executor);
27899        Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
27900      }
27901    };
27902
27903    // Hang detector on a guaranteed event that needs a freshly spawned
27904    // thread (timekeeper / fire thread) to get CPU -- generous for starved
27905    // CI runners.
27906    entered_rx
27907      .recv_timeout(Duration::from_secs(10))
27908      .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
27909    assert!(
27910      executor.next_timer_deadline().is_none(),
27911      "{path:?} must exercise the remove-before-wake window"
27912    );
27913    assert!(
27914      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
27915      "{path:?} must retain admission while its waker is publishing"
27916    );
27917    let verdict = executor.try_begin_deadlock_verdict();
27918    let admission_blocked_verdict = verdict.is_none();
27919    drop(verdict);
27920
27921    release_tx.send(()).unwrap();
27922    scheduled_rx
27923      .recv_timeout(Duration::from_secs(10))
27924      .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
27925    if let Some(fire_thread) = fire_thread {
27926      fire_thread.join().unwrap();
27927    }
27928    assert!(
27929      admission_blocked_verdict,
27930      "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
27931    );
27932
27933    executor.begin_shutdown();
27934    executor.wait_until_scheduler_idle();
27935  }
27936
27937  #[cfg(not(target_family = "wasm"))]
27938  #[test]
27939  fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
27940    for path in [
27941      TimerFireAdmissionPath::Timekeeper,
27942      TimerFireAdmissionPath::Cooperative,
27943      TimerFireAdmissionPath::Shutdown,
27944    ] {
27945      assert_timer_fire_path_holds_deadlock_admission(path);
27946    }
27947  }
27948
27949  #[cfg(not(target_family = "wasm"))]
27950  #[test]
27951  fn multi_thread_sleep_fires_from_foreign_block_on() {
27952    // The napi-caller shape: a NON-pool thread parks in
27953    // `futures::executor::block_on` awaiting a sleep. Nothing else runs on
27954    // the executor, so ONLY the dedicated timekeeper can fire the timer --
27955    // this is the timekeeper's existence test.
27956    use std::sync::mpsc;
27957
27958    let (done_tx, done_rx) = mpsc::channel();
27959    let runner = std::thread::spawn(move || {
27960      let executor = multi_thread_executor(2, None, "timer-foreign");
27961      let started = Instant::now();
27962      let mut future = std::pin::pin!(async {
27963        heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
27964      });
27965      executor.block_on(future.as_mut());
27966      assert!(
27967        started.elapsed() >= Duration::from_millis(80),
27968        "the sleep must not resolve before its deadline"
27969      );
27970      let _ = done_tx.send(());
27971    });
27972
27973    match done_rx.recv_timeout(Duration::from_secs(10)) {
27974      Ok(()) => runner.join().unwrap(),
27975      Err(error) => {
27976        panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
27977      }
27978    }
27979  }
27980
27981  #[cfg(not(target_family = "wasm"))]
27982  #[test]
27983  fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
27984    // A long timer waits on the dedicated timer thread, leaving the sole
27985    // low-level test worker available for ordinary scheduler work.
27986    use std::sync::mpsc;
27987
27988    let (done_tx, done_rx) = mpsc::channel();
27989    let runner = std::thread::spawn(move || {
27990      let executor = multi_thread_executor(1, None, "timer-drainer");
27991
27992      // Arm a long timer through a real spawned task so the sole worker
27993      // returns to rayon afterwards (the watch-idle Debouncing shape).
27994      let sleep_exec = Arc::clone(&executor);
27995      let sleep_done = Arc::new(AtomicBool::new(false));
27996      let sleep_flag = Arc::clone(&sleep_done);
27997      let sched = Arc::clone(&executor);
27998      let (sleep_runnable, sleep_task) = async_task::spawn(
27999        async move {
28000          heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
28001          sleep_flag.store(true, Ordering::SeqCst);
28002        },
28003        move |r| sched.schedule(r),
28004      );
28005      executor.schedule(sleep_runnable);
28006      wait_until("the timekeeper parked on the 60s deadline", || {
28007        executor
28008          .timers
28009          .inner
28010          .lock()
28011          .unwrap()
28012          .timekeeper_parker
28013          .is_some()
28014      });
28015
28016      // Queued work must run NOW, not in 60s.
28017      let counter = Arc::new(AtomicUsize::new(0));
28018      let mut tasks = Vec::new();
28019      for _ in 0..32 {
28020        let counter = Arc::clone(&counter);
28021        let sched = Arc::clone(&executor);
28022        let (runnable, task) = async_task::spawn(
28023          async move {
28024            counter.fetch_add(1, Ordering::SeqCst);
28025          },
28026          move |r| sched.schedule(r),
28027        );
28028        executor.schedule(runnable);
28029        tasks.push(task);
28030      }
28031      for task in tasks {
28032        futures::executor::block_on(task);
28033      }
28034      assert_eq!(counter.load(Ordering::SeqCst), 32);
28035      assert!(
28036        !sleep_done.load(Ordering::SeqCst),
28037        "the 60s timer must still be pending: the work ran DURING the timer wait"
28038      );
28039      // Cancel the long sleep so the executor tears down promptly.
28040      executor.shutdown_timers();
28041      futures::executor::block_on(sleep_task);
28042      let _ = done_tx.send(());
28043    });
28044
28045    match done_rx.recv_timeout(Duration::from_secs(15)) {
28046      Ok(()) => runner.join().unwrap(),
28047      Err(error) => {
28048        panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
28049      }
28050    }
28051  }
28052
28053  #[cfg(not(target_family = "wasm"))]
28054  #[test]
28055  fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
28056    let executor = multi_thread_executor(2, None, "timer-lazy-start");
28057    assert!(
28058      executor.timers.thread.lock().unwrap().is_none(),
28059      "constructing a runtime without timers must not create an extra OS thread"
28060    );
28061
28062    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28063    assert!(
28064      executor.timers.thread.lock().unwrap().is_none(),
28065      "an unpolled sleep has not registered a deadline"
28066    );
28067    let waker = futures::task::noop_waker();
28068    let mut cx = Context::from_waker(&waker);
28069    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28070    assert!(
28071      executor.timers.thread.lock().unwrap().is_some(),
28072      "the first timer registration must start the dedicated service"
28073    );
28074  }
28075
28076  #[cfg(not(target_family = "wasm"))]
28077  #[test]
28078  fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
28079    use std::sync::mpsc;
28080
28081    let executor = multi_thread_executor(1, None, "timer-closed-start");
28082    executor.shutdown_timers();
28083    let handle_guard = executor.timers.thread.lock().unwrap();
28084    let ensure_executor = Arc::clone(&executor);
28085    let (completed_tx, completed_rx) = mpsc::channel();
28086    let ensure = std::thread::spawn(move || {
28087      ensure_executor.timers.ensure_started().unwrap();
28088      completed_tx.send(()).unwrap();
28089    });
28090
28091    // Hang detector on a freshly spawned thread getting CPU -- generous for
28092    // starved CI runners (a real regression blocks forever on the held lock).
28093    completed_rx.recv_timeout(Duration::from_secs(10)).expect(
28094      "ensure_started consulted or joined the timer handle before observing the closed heap",
28095    );
28096    drop(handle_guard);
28097    ensure.join().unwrap();
28098  }
28099
28100  #[cfg(not(target_family = "wasm"))]
28101  #[test]
28102  fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
28103    const TIMERS: u64 = 4096;
28104
28105    let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
28106    let base = Instant::now() + Duration::from_secs(3600);
28107    for id in 0..TIMERS {
28108      executor.register_timer(
28109        id,
28110        base + Duration::from_secs(id),
28111        futures::task::noop_waker(),
28112        &Arc::new(AtomicBool::new(false)),
28113      );
28114    }
28115    {
28116      let inner = executor.timers.inner.lock().unwrap();
28117      assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
28118      assert_eq!(inner.queue.len(), inner.entries.len());
28119    }
28120
28121    // Preserve the earliest node while removing every later node. A lazy
28122    // BinaryHeap would retain all 4095 tombstones until that first deadline.
28123    for id in 1..TIMERS {
28124      executor.cancel_timer(id);
28125    }
28126    {
28127      let inner = executor.timers.inner.lock().unwrap();
28128      assert_eq!(inner.entries.len(), 1);
28129      assert_eq!(inner.queue.len(), 1);
28130      assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
28131    }
28132
28133    executor.cancel_timer(0);
28134    {
28135      let inner = executor.timers.inner.lock().unwrap();
28136      assert!(inner.entries.is_empty());
28137      assert!(inner.queue.is_empty());
28138    }
28139    executor.shutdown_timers();
28140    executor.timers.join();
28141  }
28142
28143  #[cfg(not(target_family = "wasm"))]
28144  #[test]
28145  fn timer_registration_is_transactional_when_timekeeper_start_fails() {
28146    let executor = multi_thread_executor(2, None, "timer-start-failure");
28147    executor
28148      .timers
28149      .fail_next_start
28150      .store(true, Ordering::SeqCst);
28151
28152    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
28153    let waker = Waker::from(Arc::clone(&wake_count));
28154    let baseline_waker_refs = Arc::strong_count(&wake_count);
28155    let mut cx = Context::from_waker(&waker);
28156    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28157
28158    let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
28159    assert!(
28160      failure.is_err(),
28161      "the injected timer-thread start failure must panic the poll"
28162    );
28163    {
28164      let inner = executor.timers.inner.lock().unwrap();
28165      assert!(
28166        inner.entries.is_empty(),
28167        "a failed start must not retain a timer entry"
28168      );
28169      assert!(
28170        inner.queue.is_empty(),
28171        "a failed start must not retain a stale heap node"
28172      );
28173      assert!(
28174        inner.timekeeper_parker.is_none(),
28175        "a failed start must not publish a timekeeper parker"
28176      );
28177    }
28178    assert!(
28179      executor.timers.thread.lock().unwrap().is_none(),
28180      "a failed start must not retain a thread handle"
28181    );
28182    assert_eq!(
28183      Arc::strong_count(&wake_count),
28184      baseline_waker_refs,
28185      "a failed start must release the cloned timer waker"
28186    );
28187
28188    assert!(
28189      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
28190      "the same Sleep must be safely retryable after a transient start failure"
28191    );
28192    {
28193      let inner = executor.timers.inner.lock().unwrap();
28194      assert_eq!(
28195        inner.entries.len(),
28196        1,
28197        "the retry must publish exactly one timer entry"
28198      );
28199      assert_eq!(
28200        inner.queue.len(),
28201        1,
28202        "the retry must publish exactly one heap node"
28203      );
28204    }
28205    assert!(
28206      executor.timers.thread.lock().unwrap().is_some(),
28207      "the retry must start the dedicated timekeeper"
28208    );
28209
28210    drop(sleep);
28211    assert!(
28212      executor.timers.inner.lock().unwrap().entries.is_empty(),
28213      "dropping the retried Sleep must cancel its registration"
28214    );
28215    executor.shutdown_timers();
28216    executor.wait_until_scheduler_idle();
28217    assert!(
28218      executor.timers.thread.lock().unwrap().is_none(),
28219      "shutdown after a recovered start failure must join the timekeeper"
28220    );
28221  }
28222
28223  #[cfg(not(target_family = "wasm"))]
28224  #[test]
28225  fn pending_timer_preserves_every_configured_rayon_worker() {
28226    use std::sync::mpsc;
28227
28228    let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
28229    let waker = futures::task::noop_waker();
28230    let mut cx = Context::from_waker(&waker);
28231    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28232    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28233    wait_until("the dedicated timer thread to park", || {
28234      executor
28235        .timers
28236        .inner
28237        .lock()
28238        .unwrap()
28239        .timekeeper_parker
28240        .is_some()
28241    });
28242
28243    let released = Arc::new((Mutex::new(false), Condvar::new()));
28244    let (started_tx, started_rx) = mpsc::channel();
28245    let (completed_tx, completed_rx) = mpsc::channel();
28246    for _ in 0..2 {
28247      let released = Arc::clone(&released);
28248      let started_tx = started_tx.clone();
28249      let completed_tx = completed_tx.clone();
28250      executor.pool.spawn(move || {
28251        started_tx.send(()).unwrap();
28252        let (lock, changed) = &*released;
28253        let mut released = lock
28254          .lock()
28255          .unwrap_or_else(std::sync::PoisonError::into_inner);
28256        while !*released {
28257          released = changed
28258            .wait(released)
28259            .unwrap_or_else(std::sync::PoisonError::into_inner);
28260        }
28261        completed_tx.send(()).unwrap();
28262      });
28263    }
28264    drop(started_tx);
28265    drop(completed_tx);
28266
28267    let first = started_rx.recv_timeout(Duration::from_secs(2));
28268    let second = started_rx.recv_timeout(Duration::from_secs(2));
28269    {
28270      let (lock, changed) = &*released;
28271      *lock
28272        .lock()
28273        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
28274      changed.notify_all();
28275    }
28276    drop(sleep);
28277    for _ in 0..2 {
28278      completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
28279    }
28280
28281    assert!(
28282      first.is_ok(),
28283      "the first configured Rayon worker did not start"
28284    );
28285    assert!(
28286      second.is_ok(),
28287      "a pending timer consumed one of the two configured Rayon workers"
28288    );
28289  }
28290
28291  #[cfg(not(target_family = "wasm"))]
28292  #[test]
28293  fn timekeeper_never_enters_a_blocking_closure() {
28294    use std::{
28295      sync::{Barrier, mpsc},
28296      time::Duration,
28297    };
28298
28299    let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
28300    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
28301    let waker = Waker::from(Arc::clone(&wake_count));
28302    let mut cx = Context::from_waker(&waker);
28303    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
28304    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28305    wait_until(
28306      "the timekeeper parked before blocking work was submitted",
28307      || {
28308        executor
28309          .timers
28310          .inner
28311          .lock()
28312          .unwrap()
28313          .timekeeper_parker
28314          .is_some()
28315      },
28316    );
28317
28318    let release = Arc::new(Barrier::new(2));
28319    let blocker_release = Arc::clone(&release);
28320    let (started_tx, started_rx) = mpsc::sync_channel(1);
28321    let blocking = executor.schedule_blocking(move || {
28322      started_tx.send(()).unwrap();
28323      blocker_release.wait();
28324    });
28325    // Hang detector (the closure start needs a worker to get CPU) --
28326    // generous for starved CI runners.
28327    started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
28328
28329    wait_until(
28330      "the timer waker to fire while the Rayon worker is blocked",
28331      || wake_count.0.load(Ordering::SeqCst) != 0,
28332    );
28333    release.wait();
28334    futures::executor::block_on(blocking).unwrap();
28335    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
28336  }
28337
28338  #[cfg(not(target_family = "wasm"))]
28339  #[test]
28340  fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
28341    use std::sync::mpsc;
28342
28343    struct PendingEntered(Option<mpsc::Sender<()>>);
28344
28345    impl Future for PendingEntered {
28346      type Output = ();
28347
28348      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28349        if let Some(entered) = self.0.take() {
28350          entered.send(()).unwrap();
28351        }
28352        Poll::Pending
28353      }
28354    }
28355
28356    let options = RuntimeOptions {
28357      flavor: RuntimeFlavor::MultiThread,
28358      worker_threads: 2,
28359      max_blocking_tasks: 1,
28360      thread_name_prefix: "nested-timer-wake".to_string(),
28361      park_deadline: None,
28362    };
28363    let executor =
28364      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
28365
28366    let timer_exec = Arc::clone(&executor);
28367    let (timer_tx, timer_rx) = mpsc::channel();
28368    let timer_scheduler = Arc::clone(&executor);
28369    let (timer_runnable, timer_task) = async_task::spawn(
28370      async move {
28371        heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
28372        timer_tx.send(()).unwrap();
28373      },
28374      move |runnable| timer_scheduler.schedule(runnable),
28375    );
28376    executor.schedule(timer_runnable);
28377    wait_until(
28378      "the dedicated timekeeper parked before nested block_on",
28379      || {
28380        executor
28381          .timers
28382          .inner
28383          .lock()
28384          .unwrap()
28385          .timekeeper_parker
28386          .is_some()
28387      },
28388    );
28389
28390    let (entered_tx, entered_rx) = mpsc::channel();
28391    let nested_exec = Arc::clone(&executor);
28392    let nested_scheduler = Arc::clone(&executor);
28393    let (nested_runnable, nested_task) = async_task::spawn(
28394      async move {
28395        let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
28396        let outcome = nested_exec.block_on(pending.as_mut());
28397        drop(pending);
28398        assert_eq!(
28399          outcome,
28400          BlockOnOutcome::Stopped,
28401          "shutdown must stop the raw nested block_on driver"
28402        );
28403      },
28404      move |runnable| nested_scheduler.schedule(runnable),
28405    );
28406    executor.schedule(nested_runnable);
28407    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
28408
28409    let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
28410    let (release_tx, release_rx) = mpsc::channel();
28411    let blocking = executor.schedule_blocking(move || {
28412      blocking_started_tx.send(()).unwrap();
28413      release_rx.recv().unwrap();
28414    });
28415    blocking_started_rx
28416      .recv_timeout(Duration::from_secs(2))
28417      .expect("a general worker must service blocking work");
28418    timer_rx
28419      .recv_timeout(Duration::from_secs(2))
28420      .expect("the timer wake must reach the nested cooperative driver");
28421
28422    release_tx.send(()).unwrap();
28423    futures::executor::block_on(blocking).unwrap();
28424    executor.begin_shutdown();
28425    assert_eq!(
28426      futures::executor::block_on(nested_task.fallible()),
28427      Some(())
28428    );
28429    futures::executor::block_on(timer_task);
28430  }
28431
28432  #[cfg(not(target_family = "wasm"))]
28433  #[test]
28434  fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
28435    // Task requirement: a NEW earliest deadline (heap peek changes) must
28436    // re-arm the already-parked timekeeper. The 60s sentinel timer parks the
28437    // timekeeper far in the future; the 60ms timer must still fire on time.
28438    use std::sync::mpsc;
28439
28440    let (done_tx, done_rx) = mpsc::channel();
28441    let runner = std::thread::spawn(move || {
28442      let executor = multi_thread_executor(2, None, "timer-rearm");
28443
28444      let long_exec = Arc::clone(&executor);
28445      let long_done = Arc::new(AtomicBool::new(false));
28446      let long_flag = Arc::clone(&long_done);
28447      let sched = Arc::clone(&executor);
28448      let (long_runnable, long_task) = async_task::spawn(
28449        async move {
28450          heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
28451          long_flag.store(true, Ordering::SeqCst);
28452        },
28453        move |r| sched.schedule(r),
28454      );
28455      executor.schedule(long_runnable);
28456      wait_until("the timekeeper parked on the 60s sentinel", || {
28457        executor
28458          .timers
28459          .inner
28460          .lock()
28461          .unwrap()
28462          .timekeeper_parker
28463          .is_some()
28464      });
28465
28466      let started = Instant::now();
28467      let mut future = std::pin::pin!(async {
28468        heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
28469      });
28470      executor.block_on(future.as_mut());
28471      let elapsed = started.elapsed();
28472      assert!(
28473        elapsed >= Duration::from_millis(60),
28474        "fired before its deadline: {elapsed:?}"
28475      );
28476      assert!(
28477        !long_done.load(Ordering::SeqCst),
28478        "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
28479         timekeeper, not by the sentinel expiring"
28480      );
28481      executor.shutdown_timers();
28482      futures::executor::block_on(long_task);
28483      let _ = done_tx.send(());
28484    });
28485
28486    match done_rx.recv_timeout(Duration::from_secs(15)) {
28487      Ok(()) => runner.join().unwrap(),
28488      Err(error) => panic!(
28489        "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
28490      ),
28491    }
28492  }
28493
28494  #[cfg(not(target_family = "wasm"))]
28495  #[test]
28496  fn multi_thread_sleep_cancels_registration_on_drop() {
28497    // `tokio::select!` losing-arm semantics: dropping the Sleep must retire
28498    // its heap registration (no waker fired later, heap drains empty).
28499    let executor = multi_thread_executor(1, None, "timer-cancel");
28500    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
28501    let waker = Waker::from(Arc::clone(&wake_count));
28502    let mut cx = Context::from_waker(&waker);
28503
28504    // The deadline's pre-poll budget is one statement; 250ms dwarfs it on a
28505    // starved runner (a past-due Sleep would resolve inline instead of
28506    // registering, failing the assert below for the wrong reason).
28507    let deadline = Instant::now() + Duration::from_millis(250);
28508    let mut sleep = heap_sleep(&executor, deadline);
28509    assert!(
28510      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
28511      "must register, not resolve"
28512    );
28513    assert!(
28514      executor.next_timer_deadline().is_some(),
28515      "the registration must be in the heap"
28516    );
28517    drop(sleep);
28518    assert!(
28519      executor.next_timer_deadline().is_none(),
28520      "cancel-on-drop must retire the heap registration"
28521    );
28522    // Outlive the would-be deadline (event-exact, not a fixed sleep): the
28523    // cancelled timer must not wake. The negative assert is structural once
28524    // the heap is empty; waiting until the wall clock is genuinely past the
28525    // deadline keeps the ghost-fire probe honest.
28526    while Instant::now() < deadline + Duration::from_millis(25) {
28527      std::thread::sleep(Duration::from_millis(10));
28528    }
28529    executor.fire_due_timers();
28530    assert_eq!(
28531      wake_count.0.load(Ordering::SeqCst),
28532      0,
28533      "a dropped Sleep's waker must never fire"
28534    );
28535  }
28536
28537  #[cfg(not(target_family = "wasm"))]
28538  #[test]
28539  fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
28540    let executor = multi_thread_executor(2, None, "timer-cancel-retire");
28541    let waker = futures::task::noop_waker();
28542    let mut cx = Context::from_waker(&waker);
28543    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28544
28545    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28546    wait_until(
28547      "the timekeeper parked on the timer that will be cancelled",
28548      || {
28549        executor
28550          .timers
28551          .inner
28552          .lock()
28553          .unwrap()
28554          .timekeeper_parker
28555          .is_some()
28556      },
28557    );
28558
28559    drop(sleep);
28560    wait_until(
28561      "the cancelled timer's timekeeper returned to its idle wait",
28562      || {
28563        executor.next_timer_deadline().is_none()
28564          && executor
28565            .timers
28566            .inner
28567            .lock()
28568            .unwrap()
28569            .timekeeper_parker
28570            .is_none()
28571      },
28572    );
28573  }
28574
28575  #[cfg(not(target_family = "wasm"))]
28576  #[test]
28577  fn timer_waker_replacement_drops_outside_heap_lock() {
28578    use std::sync::mpsc;
28579
28580    struct DropOtherTimer(Mutex<Option<Sleep>>);
28581
28582    #[expect(
28583      clippy::manual_noop_waker,
28584      reason = "the no-op wake type has a reentrant Drop used by this regression"
28585    )]
28586    impl std::task::Wake for DropOtherTimer {
28587      fn wake(self: Arc<Self>) {}
28588    }
28589
28590    impl Drop for DropOtherTimer {
28591      fn drop(&mut self) {
28592        drop(self.0.lock().unwrap().take());
28593      }
28594    }
28595
28596    let (done_tx, done_rx) = mpsc::channel();
28597    let runner = std::thread::spawn(move || {
28598      let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
28599      let deadline = Instant::now() + Duration::from_secs(120);
28600      let mut other = heap_sleep(&executor, deadline);
28601      let noop = futures::task::noop_waker();
28602      let mut noop_cx = Context::from_waker(&noop);
28603      assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
28604
28605      let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
28606      let mut primary = heap_sleep(&executor, deadline);
28607      let mut old_cx = Context::from_waker(&old);
28608      assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
28609      drop(old);
28610
28611      let replacement = futures::task::noop_waker();
28612      let mut replacement_cx = Context::from_waker(&replacement);
28613      assert!(
28614        Pin::new(&mut primary)
28615          .poll(&mut replacement_cx)
28616          .is_pending()
28617      );
28618      drop(primary);
28619      executor.shutdown_timers();
28620      done_tx.send(()).unwrap();
28621    });
28622    // Hang detector on a freshly spawned runner thread -- generous for
28623    // starved CI runners (a real regression deadlocks forever).
28624    done_rx
28625      .recv_timeout(Duration::from_secs(10))
28626      .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
28627    runner.join().unwrap();
28628  }
28629
28630  #[cfg(not(target_family = "wasm"))]
28631  #[test]
28632  fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
28633    use std::sync::mpsc;
28634
28635    struct DropFinalExecutorOnWake {
28636      executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
28637      completed: mpsc::Sender<()>,
28638    }
28639
28640    impl std::task::Wake for DropFinalExecutorOnWake {
28641      fn wake(self: Arc<Self>) {
28642        let executor = self
28643          .executor
28644          .lock()
28645          .unwrap_or_else(std::sync::PoisonError::into_inner)
28646          .take()
28647          .expect("the timer waker must own the final executor reference");
28648        assert_eq!(
28649          ON_TIMER_THREAD.with(std::cell::Cell::get),
28650          Some(executor.id),
28651          "the dedicated timer thread must deliver the timer wake"
28652        );
28653        drop(executor);
28654        self
28655          .completed
28656          .send(())
28657          .expect("the test receiver must outlive the timer-thread callback");
28658      }
28659    }
28660
28661    let executor = multi_thread_executor(1, None, "timer-final-arc");
28662    let (completed_tx, completed_rx) = mpsc::channel();
28663    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
28664    {
28665      let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
28666        executor: Mutex::new(Some(Arc::clone(&executor))),
28667        completed: completed_tx,
28668      }));
28669      let mut cx = Context::from_waker(&waker);
28670      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28671    }
28672    wait_until(
28673      "the timer thread parked before owning the final executor Arc",
28674      || {
28675        executor
28676          .timers
28677          .inner
28678          .lock()
28679          .unwrap()
28680          .timekeeper_parker
28681          .is_some()
28682      },
28683    );
28684
28685    // `Sleep` retains only a Weak reference. Once this local Arc is gone, the
28686    // heap waker owns the final direct executor Arc and releases it from the
28687    // timer thread while `TimerHeap::drop` still owns that thread's handle.
28688    drop(executor);
28689    // Hang detector: the fire needs the (already spawned) timer thread to
28690    // reach its 80ms deadline -- generous for starved CI runners.
28691    completed_rx
28692      .recv_timeout(Duration::from_secs(10))
28693      .expect("dropping the final executor Arc on the timer thread attempted to join itself");
28694  }
28695
28696  #[cfg(not(target_family = "wasm"))]
28697  #[test]
28698  fn multi_thread_shutdown_with_pending_timers_drain_fires() {
28699    // Shutdown invariant: `shutdown()` with armed timers must drain-fire the
28700    // pending wakers -- a `watcher.close()` awaiting a
28701    // debounce sleep must complete, not hang. Also pins fire-on-register for
28702    // a Sleep polled after shutdown.
28703    use std::sync::mpsc;
28704
28705    let (done_tx, done_rx) = mpsc::channel();
28706    let runner = std::thread::spawn(move || {
28707      let controller = Arc::new(RuntimeController::new());
28708      // Default options: MultiThread on native.
28709      let backend = controller.backend();
28710      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28711        panic!("default native backend must be MultiThread");
28712      };
28713      let executor = Arc::downgrade(executor);
28714      let mut late = heap_sleep(
28715        &executor
28716          .upgrade()
28717          .expect("the initial executor must be live"),
28718        Instant::now() + Duration::from_secs(120),
28719      );
28720      drop(backend);
28721
28722      let (parked_tx, parked_rx) = mpsc::channel();
28723      let block_controller = Arc::clone(&controller);
28724      let block_exec = Weak::clone(&executor);
28725      let blocker = std::thread::spawn(move || {
28726        let mut future = std::pin::pin!(async {
28727          let executor = block_exec
28728            .upgrade()
28729            .expect("the runtime must remain live while blocking");
28730          let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
28731          drop(executor);
28732          parked_tx.send(()).unwrap();
28733          sleep.await;
28734        });
28735        block_controller.block_on(future.as_mut());
28736      });
28737      parked_rx.recv().unwrap();
28738      wait_until("the 120s sleep registered in the heap", || {
28739        executor
28740          .upgrade()
28741          .is_some_and(|executor| executor.next_timer_deadline().is_some())
28742      });
28743
28744      // The acceptance surface: controller shutdown while a sleep is armed.
28745      controller
28746        .shutdown()
28747        .expect("runtime shutdown must succeed");
28748      blocker
28749        .join()
28750        .expect("the blocked sleep must be drain-fired by shutdown, not hang");
28751
28752      // A Sleep polled AFTER shutdown resolves immediately instead of
28753      // registering with a dead heap.
28754      let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
28755      let mut cx = Context::from_waker(&waker);
28756      assert!(
28757        Pin::new(&mut late).poll(&mut cx).is_ready(),
28758        "a Sleep polled after shutdown must fire early, never park a closing runtime"
28759      );
28760      let _ = done_tx.send(());
28761    });
28762
28763    match done_rx.recv_timeout(Duration::from_secs(10)) {
28764      Ok(()) => runner.join().unwrap(),
28765      Err(error) => {
28766        panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
28767      }
28768    }
28769  }
28770
28771  #[cfg(not(target_family = "wasm"))]
28772  #[test]
28773  fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
28774    // Timer-wait/deadline invariant: the park deadline is armed
28775    // SHORTER than the next timer deadline. The cooperative driver awaiting
28776    // the sleep sits with ZERO
28777    // executor progress for several full deadline windows -- a timer wait is
28778    // a legitimate park (guaranteed wall-clock wake), so it must NOT be
28779    // declared a BlockOnDeadlock; the timer must still fire. worker_threads=1
28780    // also allows the parked driver to race the dedicated timekeeper and fire
28781    // the timer itself, pinning the cooperative timer-bounded park.
28782    use std::sync::mpsc;
28783
28784    let (done_tx, done_rx) = mpsc::channel();
28785    let runner = std::thread::spawn(move || {
28786      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
28787
28788      let task_exec = Arc::clone(&executor);
28789      let body = async move {
28790        let started = Instant::now();
28791        let mut inner = std::pin::pin!(async {
28792          heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
28793        });
28794        // Re-entrant block_on from the pool worker: the cooperative park.
28795        task_exec.block_on(inner.as_mut());
28796        assert!(started.elapsed() >= Duration::from_millis(250));
28797      };
28798      let sched = Arc::clone(&executor);
28799      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
28800      executor.schedule(runnable);
28801
28802      // A spurious BlockOnDeadlock panic would unwind the task body (swallowed
28803      // by run_runnable's catch_unwind), so the task would never complete and
28804      // the bounded harness below would report the failure.
28805      futures::executor::block_on(task);
28806      // Teardown inside the bounded window: this test was one of the silent
28807      // wedges on the Windows CI runners with every assertion already
28808      // passed. Its teardown includes `TimerHeap::drop`'s timekeeper JOIN
28809      // (a timer was polled, so the timekeeper thread is live) -- exactly
28810      // the kind of phase that must fail loudly rather than hang.
28811      drop(executor);
28812      let _ = done_tx.send(());
28813    });
28814
28815    match done_rx.recv_timeout(Duration::from_secs(10)) {
28816      Ok(()) => join_within(
28817        "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
28818        runner,
28819        Duration::from_secs(30),
28820      ),
28821      Err(error) => {
28822        panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
28823      }
28824    }
28825  }
28826
28827  #[cfg(not(target_family = "wasm"))]
28828  #[test]
28829  fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
28830    // Timer-registration/verdict race, deterministic via the verdict hook: a
28831    // timer registered AFTER a deadline park was committed bumps no progress
28832    // counter by design, lands after the permit and queued-work re-checks, and
28833    // is invisible to the fingerprint. The heap re-check must recognize that
28834    // guaranteed future wake, veto the panic, and let the driver re-park
28835    // timer-bounded until the fire's task-wake completes the future.
28836    use std::sync::mpsc;
28837
28838    // A raw Sleep the hook registers is woken by this waker, which releases
28839    // the awaited future's gate.
28840    struct GateWake {
28841      gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
28842    }
28843    impl std::task::Wake for GateWake {
28844      fn wake(self: Arc<Self>) {
28845        let gate = self.gate.lock().unwrap().take();
28846        if let Some(gate) = gate {
28847          let _ = gate.send(23);
28848        }
28849      }
28850    }
28851
28852    let (done_tx, done_rx) = mpsc::channel();
28853    let runner = std::thread::spawn(move || {
28854      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
28855      // Saturate drainer accounting so `wake_one` fallbacks cannot start a
28856      // replacement drainer behind our back; only the verdict's heap re-check
28857      // can save this park.
28858      executor
28859        .active_drainers
28860        .store(executor.max_drainers, Ordering::SeqCst);
28861
28862      // Completion condition: the timer registered inside the hook fires and
28863      // its waker (the gate) releases the awaited future.
28864      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
28865      let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
28866
28867      let hook_exec = Arc::clone(&executor);
28868      let hook_gate = Arc::clone(&gate_tx);
28869      // The Sleep must outlive the hook (drop cancels): park it in a slot the
28870      // runner thread later cleans up.
28871      let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
28872      let hook_sleep_slot = Arc::clone(&parked_sleep);
28873      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
28874        *slot.borrow_mut() = Some(Box::new(move || {
28875          let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
28876          let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
28877          let mut cx = Context::from_waker(&waker);
28878          assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28879          *hook_sleep_slot.lock().unwrap() = Some(sleep);
28880        }));
28881      });
28882
28883      // The test-only on-pool marker runs the cooperative branch and its
28884      // thread-local hook on THIS thread.
28885      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28886      let mut output = None;
28887      {
28888        let mut future = std::pin::pin!(async {
28889          output = Some(gate_rx.await.unwrap());
28890        });
28891        executor.block_on(future.as_mut());
28892      }
28893      // Clear the hook slot BEFORE any assert can panic: this hook captures
28894      // an executor Arc and THIS executor's timekeeper is live (a timer was
28895      // polled), so a leftover hook dropped by a TLS destructor would join
28896      // the timekeeper under the Windows loader lock (see the LOADER-LOCK
28897      // RULE at `WorkerTlsDropAction`).
28898      DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
28899      assert_eq!(
28900        output,
28901        Some(23),
28902        "the edge-registered timer must have fired and released the gate"
28903      );
28904      executor.active_drainers.store(0, Ordering::SeqCst);
28905      // Teardown inside the bounded window; includes the timekeeper join
28906      // (a timer was polled). See `join_within`.
28907      drop(executor);
28908      let _ = done_tx.send(());
28909    });
28910
28911    match done_rx.recv_timeout(Duration::from_secs(10)) {
28912      Ok(()) => join_within(
28913        "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
28914        runner,
28915        Duration::from_secs(30),
28916      ),
28917      Err(error) => panic!(
28918        "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
28919      ),
28920    }
28921  }
28922
28923  #[test]
28924  fn current_thread_sleep_without_host_driver_fails_loud() {
28925    // Missing-driver invariant: a CT runtime without a registered host driver
28926    // must fail LOUD with this exact diagnostic -- never a silent never-firing
28927    // debounce or a misleading certain-deadlock panic later.
28928    let metrics = Arc::new(RuntimeMetrics::default());
28929    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
28930    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
28931
28932    let payload = catch_unwind(AssertUnwindSafe(|| {
28933      let _sleep = make_sleep(
28934        &backend,
28935        &Arc::new(TimerDriverRegistry::default()),
28936        Instant::now() + Duration::from_millis(10),
28937      );
28938    }))
28939    .expect_err("sleep_until on CurrentThread without a driver must panic");
28940    let message = payload
28941      .downcast_ref::<String>()
28942      .cloned()
28943      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
28944      .expect("panic payload must be a message");
28945    assert!(
28946      message.contains("CurrentThread runtime has no live timer driver registered"),
28947      "the diagnostic must name the missing driver, got: {message}"
28948    );
28949  }
28950
28951  #[test]
28952  fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
28953    // Registry semantics: all-live snapshots with newest-live compatibility
28954    // selection, dead entries swept on contact, explicit unregister, and a
28955    // dead-only registry reading as NO driver.
28956    let registry = TimerDriverRegistry::default();
28957    let a = ManualStubDriver::new();
28958    let b = ManualStubDriver::new();
28959    let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
28960    let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
28961    assert_ne!(a_id, b_id, "registration handles must be distinct");
28962    assert_eq!(
28963      registry.current().map(|(id, _)| id),
28964      Some(b_id),
28965      "newest live registrant serves"
28966    );
28967    assert!(registry.has_live_driver());
28968
28969    // A dead newest (stub for: its owning env torn down) is skipped AND
28970    // swept; selection falls back to the older live registrant.
28971    b.kill();
28972    assert_eq!(
28973      registry.current().map(|(id, _)| id),
28974      Some(a_id),
28975      "dead newest must be skipped"
28976    );
28977    // The sweep is permanent: resurrecting the flag must not bring the
28978    // evicted registration back.
28979    b.live.store(true, Ordering::SeqCst);
28980    assert_eq!(
28981      registry.current().map(|(id, _)| id),
28982      Some(a_id),
28983      "swept entries stay gone"
28984    );
28985
28986    // Explicit unregister (the binding's env-cleanup path) empties the
28987    // registry; empty and dead-only both read as NO live driver.
28988    registry.unregister(a_id);
28989    assert!(registry.current().is_none());
28990    assert!(
28991      !registry.has_live_driver(),
28992      "an emptied registry must not report a live driver"
28993    );
28994  }
28995
28996  #[test]
28997  fn timer_driver_registry_contains_callback_panics_and_falls_back() {
28998    let registry = TimerDriverRegistry::default();
28999    let fallback = ManualStubDriver::new();
29000    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
29001    let panicking = Arc::new(PanickingTimerDriver {
29002      swept: AtomicBool::new(false),
29003    });
29004    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
29005
29006    assert_eq!(
29007      registry.current().map(|(id, _)| id),
29008      Some(fallback_id),
29009      "a panicking liveness callback must be treated as a dead driver"
29010    );
29011    assert!(
29012      panicking.swept.load(Ordering::SeqCst),
29013      "a panicking liveness callback must still run its contained sweep hook"
29014    );
29015    assert!(
29016      registry.has_live_driver(),
29017      "the healthy fallback must remain selectable"
29018    );
29019  }
29020
29021  #[test]
29022  fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
29023    use std::{sync::mpsc, time::Duration};
29024
29025    let registry = Arc::new(TimerDriverRegistry::default());
29026    let fallback = ManualStubDriver::new();
29027    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
29028    let reentrant = Arc::new(ReentrantLivenessDriver {
29029      registry: Arc::downgrade(&registry),
29030      registration: Mutex::new(None),
29031    });
29032    let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
29033    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
29034
29035    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
29036    let selection_registry = Arc::clone(&registry);
29037    let selection_thread = std::thread::spawn(move || {
29038      selection_tx
29039        .send(selection_registry.current().map(|(id, _)| id))
29040        .unwrap();
29041    });
29042    assert_eq!(
29043      selection_rx
29044        .recv_timeout(Duration::from_secs(10))
29045        .expect("is_live re-entry deadlocked"),
29046      Some(fallback_id)
29047    );
29048    selection_thread.join().unwrap();
29049
29050    let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
29051      registry: Arc::downgrade(&registry),
29052      fallback: fallback_id,
29053    });
29054    let drop_id = registry.register(Arc::clone(&drop_driver));
29055    drop(drop_driver);
29056
29057    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
29058    let drop_registry = Arc::clone(&registry);
29059    let drop_thread = std::thread::spawn(move || {
29060      drop_registry.unregister(drop_id);
29061      drop_tx.send(()).unwrap();
29062    });
29063    drop_rx
29064      .recv_timeout(Duration::from_secs(10))
29065      .expect("driver destructor re-entry deadlocked");
29066    drop_thread.join().unwrap();
29067    assert!(
29068      registry.current().is_none(),
29069      "the destructor must be able to unregister the fallback"
29070    );
29071  }
29072
29073  #[test]
29074  fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
29075    const DRIVER_PAIRS: usize = 2_048;
29076
29077    let registry = Arc::new(TimerDriverRegistry::default());
29078    let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
29079    let mut live = Vec::with_capacity(DRIVER_PAIRS);
29080    let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
29081    for _ in 0..DRIVER_PAIRS {
29082      let driver = Arc::new(ReentrantSweepTimerDriver {
29083        registry: Arc::downgrade(&registry),
29084        registration: Mutex::new(None),
29085        sweeps: AtomicUsize::new(0),
29086        removed_before_sweep: AtomicBool::new(false),
29087      });
29088      let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29089      *driver.registration.lock().unwrap() = Some(id);
29090      dead.push(driver);
29091
29092      let driver = ManualStubDriver::new();
29093      live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
29094      live.push(driver);
29095    }
29096    let dead_tail = Arc::new(ReentrantSweepTimerDriver {
29097      registry: Arc::downgrade(&registry),
29098      registration: Mutex::new(None),
29099      sweeps: AtomicUsize::new(0),
29100      removed_before_sweep: AtomicBool::new(false),
29101    });
29102    let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
29103    *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
29104    dead.push(dead_tail);
29105
29106    let selection = registry.live();
29107    assert_eq!(
29108      selection
29109        .as_slice()
29110        .iter()
29111        .map(|(id, _)| *id)
29112        .collect::<Vec<_>>(),
29113      live_ids,
29114      "sweeping every interleaved dead host must retain live registration order"
29115    );
29116    drop(selection);
29117    assert!(
29118      dead.iter().all(|driver| {
29119        driver.sweeps.load(Ordering::SeqCst) == 1
29120          && driver.removed_before_sweep.load(Ordering::SeqCst)
29121          && driver.registration.lock().unwrap().is_none()
29122      }),
29123      "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
29124    );
29125
29126    {
29127      let state = registry
29128        .state
29129        .lock()
29130        .unwrap_or_else(std::sync::PoisonError::into_inner);
29131      assert_eq!(state.entries.len(), DRIVER_PAIRS);
29132      assert_eq!(state.oldest, live_ids.first().copied());
29133      assert_eq!(state.newest, live_ids.last().copied());
29134      assert_eq!(
29135        state
29136          .ordered_snapshot()
29137          .iter()
29138          .map(|(id, _)| *id)
29139          .collect::<Vec<_>>(),
29140        live_ids,
29141        "keyed removals must preserve the linked order of every live host"
29142      );
29143    }
29144    let second_selection = registry.live();
29145    assert_eq!(
29146      second_selection
29147        .as_slice()
29148        .iter()
29149        .map(|(id, _)| *id)
29150        .collect::<Vec<_>>(),
29151      live_ids,
29152      "a second selection must retain the same live order"
29153    );
29154    drop(second_selection);
29155    assert!(
29156      dead
29157        .iter()
29158        .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
29159      "permanently swept hosts must not receive repeated eviction callbacks"
29160    );
29161    assert_eq!(
29162      registry.current().map(|(id, _)| id),
29163      live_ids.last().copied(),
29164      "newest-live compatibility selection must survive the mass sweep"
29165    );
29166
29167    drop(live);
29168    drop(dead);
29169  }
29170
29171  #[test]
29172  fn timer_driver_registry_snapshot_contains_each_driver_drop() {
29173    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
29174
29175    if std::env::var_os(CHILD_ENV).is_some() {
29176      let registry = Arc::new(TimerDriverRegistry::default());
29177      let fallback = ManualStubDriver::new();
29178      let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
29179      let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
29180        registry: Arc::downgrade(&registry),
29181        registrations: Mutex::new(Vec::new()),
29182      });
29183      registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
29184
29185      let first_cancels = Arc::new(AtomicUsize::new(0));
29186      let first_drops = Arc::new(AtomicUsize::new(0));
29187      let first = Arc::new(PanickingCancelDropTimerDriver {
29188        cancels: Arc::clone(&first_cancels),
29189        drops: Arc::clone(&first_drops),
29190      });
29191      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
29192      drop(first);
29193
29194      let second_cancels = Arc::new(AtomicUsize::new(0));
29195      let second_drops = Arc::new(AtomicUsize::new(0));
29196      let second = Arc::new(PanickingCancelDropTimerDriver {
29197        cancels: Arc::clone(&second_cancels),
29198        drops: Arc::clone(&second_drops),
29199      });
29200      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
29201      drop(second);
29202      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
29203
29204      assert_eq!(
29205        registry.current().map(|(id, _)| id),
29206        Some(fallback_id),
29207        "selection must retry after reentrant removals and preserve the healthy fallback"
29208      );
29209      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
29210      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
29211      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
29212      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
29213      assert_eq!(
29214        registry
29215          .state
29216          .lock()
29217          .unwrap_or_else(std::sync::PoisonError::into_inner)
29218          .entries
29219          .len(),
29220        1
29221      );
29222      return;
29223    }
29224
29225    let output = std::process::Command::new(std::env::current_exe().unwrap())
29226      .arg("--exact")
29227      .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
29228      .arg("--nocapture")
29229      .env(CHILD_ENV, "1")
29230      .output()
29231      .expect("the hostile timer-driver snapshot subprocess test must start");
29232    assert!(
29233      output.status.success(),
29234      "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
29235      output.status.code(),
29236      String::from_utf8_lossy(&output.stdout),
29237      String::from_utf8_lossy(&output.stderr)
29238    );
29239  }
29240
29241  #[test]
29242  fn timer_driver_registry_live_result_contains_each_driver_drop() {
29243    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
29244
29245    if std::env::var_os(CHILD_ENV).is_some() {
29246      let registry = TimerDriverRegistry::default();
29247
29248      let first_cancels = Arc::new(AtomicUsize::new(0));
29249      let first_drops = Arc::new(AtomicUsize::new(0));
29250      let first = Arc::new(PanickingCancelDropTimerDriver {
29251        cancels: Arc::clone(&first_cancels),
29252        drops: Arc::clone(&first_drops),
29253      });
29254      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
29255      drop(first);
29256
29257      let second_cancels = Arc::new(AtomicUsize::new(0));
29258      let second_drops = Arc::new(AtomicUsize::new(0));
29259      let second = Arc::new(PanickingCancelDropTimerDriver {
29260        cancels: Arc::clone(&second_cancels),
29261        drops: Arc::clone(&second_drops),
29262      });
29263      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
29264      drop(second);
29265
29266      let live = registry.live();
29267      assert_eq!(live.len(), 2);
29268      registry.unregister(first_id);
29269      registry.unregister(second_id);
29270      drop(live);
29271
29272      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
29273      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
29274      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
29275      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
29276      assert!(
29277        registry
29278          .state
29279          .lock()
29280          .unwrap_or_else(std::sync::PoisonError::into_inner)
29281          .entries
29282          .is_empty()
29283      );
29284      return;
29285    }
29286
29287    let output = std::process::Command::new(std::env::current_exe().unwrap())
29288      .arg("--exact")
29289      .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
29290      .arg("--nocapture")
29291      .env(CHILD_ENV, "1")
29292      .output()
29293      .expect("the hostile live timer-driver snapshot subprocess test must start");
29294    assert!(
29295      output.status.success(),
29296      "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
29297      output.status.code(),
29298      String::from_utf8_lossy(&output.stdout),
29299      String::from_utf8_lossy(&output.stderr)
29300    );
29301  }
29302
29303  #[test]
29304  fn timer_driver_registry_drop_contains_each_driver_destructor() {
29305    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
29306
29307    if std::env::var_os(CHILD_ENV).is_some() {
29308      let first_drops = Arc::new(AtomicUsize::new(0));
29309      let second_drops = Arc::new(AtomicUsize::new(0));
29310      let registry = TimerDriverRegistry::default();
29311      let first = Arc::new(PanickingCancelDropTimerDriver {
29312        cancels: Arc::new(AtomicUsize::new(0)),
29313        drops: Arc::clone(&first_drops),
29314      });
29315      let second = Arc::new(PanickingCancelDropTimerDriver {
29316        cancels: Arc::new(AtomicUsize::new(0)),
29317        drops: Arc::clone(&second_drops),
29318      });
29319      registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
29320      registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
29321      drop(first);
29322      drop(second);
29323
29324      drop(registry);
29325
29326      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
29327      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
29328      return;
29329    }
29330
29331    let output = std::process::Command::new(std::env::current_exe().unwrap())
29332      .arg("--exact")
29333      .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
29334      .arg("--nocapture")
29335      .env(CHILD_ENV, "1")
29336      .output()
29337      .expect("the hostile timer-driver registry drop subprocess test must start");
29338    assert!(
29339      output.status.success(),
29340      "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
29341      output.status.code(),
29342      String::from_utf8_lossy(&output.stdout),
29343      String::from_utf8_lossy(&output.stderr)
29344    );
29345  }
29346
29347  #[test]
29348  fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
29349    const DRIVERS: usize = 512;
29350
29351    let metrics = Arc::new(RuntimeMetrics::default());
29352    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29353    let backend =
29354      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29355    let registry = Arc::new(TimerDriverRegistry::default());
29356    let drivers = (0..DRIVERS)
29357      .map(|_| {
29358        let driver = ManualStubDriver::new();
29359        registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29360        driver
29361      })
29362      .collect::<Vec<_>>();
29363    let mut sleep = make_sleep(
29364      &backend,
29365      &registry,
29366      Instant::now() + Duration::from_secs(60),
29367    );
29368    let mut cx = Context::from_waker(Waker::noop());
29369
29370    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29371    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29372    let timer_id = drivers[0].registers.lock().unwrap()[0].0;
29373    match &sleep.inner {
29374      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
29375      #[cfg(not(target_family = "wasm"))]
29376      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
29377    }
29378    for driver in &drivers {
29379      assert_eq!(
29380        driver.registers.lock().unwrap().len(),
29381        2,
29382        "a re-poll must refresh each live driver exactly once"
29383      );
29384    }
29385
29386    for driver in drivers.iter().step_by(2) {
29387      driver.kill();
29388    }
29389    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29390    match &sleep.inner {
29391      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
29392      #[cfg(not(target_family = "wasm"))]
29393      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
29394    }
29395    for (index, driver) in drivers.iter().enumerate() {
29396      let registers = driver.registers.lock().unwrap().len();
29397      let cancels = driver.cancels.lock().unwrap().clone();
29398      if index % 2 == 0 {
29399        assert_eq!(registers, 2);
29400        assert_eq!(cancels, vec![timer_id]);
29401      } else {
29402        assert_eq!(registers, 3);
29403        assert!(cancels.is_empty());
29404      }
29405    }
29406
29407    drop(sleep);
29408    for driver in drivers {
29409      assert_eq!(
29410        driver.cancels.lock().unwrap().as_slice(),
29411        &[timer_id],
29412        "every physical arm must be cancelled exactly once"
29413      );
29414    }
29415    assert_eq!(registry.pending_len(), 0);
29416    assert!(!executor.host_timers.has_pending());
29417  }
29418
29419  #[test]
29420  fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
29421    let metrics = Arc::new(RuntimeMetrics::default());
29422    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29423    let backend =
29424      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29425    let healthy = ManualStubDriver::new();
29426    let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
29427    let deadline = Instant::now() + Duration::from_secs(60);
29428    let mut sleep = make_sleep(&backend, &registry, deadline);
29429    let mut cx = Context::from_waker(Waker::noop());
29430
29431    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29432    let timer_id = healthy.registers.lock().unwrap()[0].0;
29433
29434    let panicking = PanickingRegisterTimerDriver::new();
29435    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
29436    let result = catch_unwind(AssertUnwindSafe(|| {
29437      let _ = Pin::new(&mut sleep).poll(&mut cx);
29438    }));
29439    assert!(
29440      result.is_err(),
29441      "the hostile register panic must retain its normal propagation"
29442    );
29443    assert_eq!(
29444      healthy.registers.lock().unwrap().len(),
29445      2,
29446      "the previously armed driver must have completed its refresh before the later panic"
29447    );
29448    assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
29449    assert!(
29450      panicking.pending.lock().unwrap().contains_key(&timer_id),
29451      "the hostile driver must model an arm committed before it panics"
29452    );
29453
29454    drop(sleep);
29455    assert_eq!(
29456      healthy.cancels.lock().unwrap().as_slice(),
29457      &[timer_id],
29458      "unwind must preserve cancellation ownership for the previously armed driver"
29459    );
29460    assert_eq!(
29461      panicking.cancels.lock().unwrap().as_slice(),
29462      &[timer_id],
29463      "unwind must preserve cancellation ownership for the driver that armed and then panicked"
29464    );
29465    assert!(panicking.pending.lock().unwrap().is_empty());
29466    assert_eq!(registry.pending_len(), 0);
29467    assert!(!executor.host_timers.has_pending());
29468  }
29469
29470  #[test]
29471  fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
29472    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
29473
29474    if std::env::var_os(CHILD_ENV).is_some() {
29475      let metrics = Arc::new(RuntimeMetrics::default());
29476      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29477      let backend =
29478        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29479      let registry = Arc::new(TimerDriverRegistry::default());
29480      let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
29481        registry: Arc::downgrade(&registry),
29482        later: Mutex::new(None),
29483        registers: AtomicUsize::new(0),
29484      });
29485      registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
29486
29487      let later_cancels = Arc::new(AtomicUsize::new(0));
29488      let later_drops = Arc::new(AtomicUsize::new(0));
29489      let later = Arc::new(PanickingCancelDropTimerDriver {
29490        cancels: Arc::clone(&later_cancels),
29491        drops: Arc::clone(&later_drops),
29492      });
29493      let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
29494      *earlier.later.lock().unwrap() = Some(later_id);
29495      drop(later);
29496
29497      let mut sleep = make_sleep(
29498        &backend,
29499        &registry,
29500        Instant::now() + Duration::from_secs(60),
29501      );
29502      let mut cx = Context::from_waker(Waker::noop());
29503      let result = catch_unwind(AssertUnwindSafe(|| {
29504        let _ = Pin::new(&mut sleep).poll(&mut cx);
29505      }));
29506
29507      assert!(
29508        result.is_err(),
29509        "the earlier driver's register panic must still propagate"
29510      );
29511      assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
29512      assert_eq!(
29513        later_drops.load(Ordering::SeqCst),
29514        0,
29515        "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
29516      );
29517
29518      drop(sleep);
29519      assert_eq!(
29520        later_cancels.load(Ordering::SeqCst),
29521        1,
29522        "the later driver must remain owned for cancellation despite its registry removal"
29523      );
29524      assert_eq!(
29525        later_drops.load(Ordering::SeqCst),
29526        1,
29527        "the later driver's final destructor must run once behind containment"
29528      );
29529      assert_eq!(registry.pending_len(), 0);
29530      assert!(!executor.host_timers.has_pending());
29531      return;
29532    }
29533
29534    let output = std::process::Command::new(std::env::current_exe().unwrap())
29535      .arg("--exact")
29536      .arg(
29537        "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
29538      )
29539      .arg("--nocapture")
29540      .env(CHILD_ENV, "1")
29541      .output()
29542      .expect("the hostile host timer snapshot subprocess test must start");
29543    assert!(
29544      output.status.success(),
29545      "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
29546      output.status.code(),
29547      String::from_utf8_lossy(&output.stdout),
29548      String::from_utf8_lossy(&output.stderr)
29549    );
29550  }
29551
29552  #[test]
29553  fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
29554    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
29555
29556    if std::env::var_os(CHILD_ENV).is_some() {
29557      let metrics = Arc::new(RuntimeMetrics::default());
29558      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29559      let backend =
29560        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29561      let cancels = Arc::new(AtomicUsize::new(0));
29562      let drops = Arc::new(AtomicUsize::new(0));
29563      let hostile = Arc::new(PanickingCancelDropTimerDriver {
29564        cancels: Arc::clone(&cancels),
29565        drops: Arc::clone(&drops),
29566      });
29567      let healthy = ManualStubDriver::new();
29568      let registry = Arc::new(TimerDriverRegistry::default());
29569      let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
29570      registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
29571      let mut sleep = make_sleep(
29572        &backend,
29573        &registry,
29574        Instant::now() + Duration::from_secs(60),
29575      );
29576      let mut cx = Context::from_waker(Waker::noop());
29577
29578      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29579      let timer_id = healthy.registers.lock().unwrap()[0].0;
29580
29581      // Leave the Sleep's arm as the hostile driver's final Arc, so cleanup must
29582      // independently contain both its cancel panic and its destructor panic.
29583      registry.unregister(hostile_id);
29584      drop(hostile);
29585      drop(sleep);
29586      assert_eq!(cancels.load(Ordering::SeqCst), 1);
29587      assert_eq!(drops.load(Ordering::SeqCst), 1);
29588      assert_eq!(
29589        healthy.cancels.lock().unwrap().as_slice(),
29590        &[timer_id],
29591        "one hostile cancellation must not skip later armed drivers"
29592      );
29593      assert_eq!(registry.pending_len(), 0);
29594      assert!(!executor.host_timers.has_pending());
29595      return;
29596    }
29597
29598    let output = std::process::Command::new(std::env::current_exe().unwrap())
29599      .arg("--exact")
29600      .arg(
29601        "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
29602      )
29603      .arg("--nocapture")
29604      .env(CHILD_ENV, "1")
29605      .output()
29606      .expect("the hostile timer-driver cancellation subprocess test must start");
29607    assert!(
29608      output.status.success(),
29609      "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
29610      output.status.code(),
29611      String::from_utf8_lossy(&output.stdout),
29612      String::from_utf8_lossy(&output.stderr)
29613    );
29614  }
29615
29616  #[test]
29617  fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
29618    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
29619
29620    if std::env::var_os(CHILD_ENV).is_some() {
29621      let metrics = Arc::new(RuntimeMetrics::default());
29622      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29623      let backend =
29624        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29625      let driver = ManualStubDriver::new();
29626      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29627      let state = Arc::new(TestHostileRawWakerState {
29628        clones: AtomicUsize::new(0),
29629        drop_panics: AtomicUsize::new(0),
29630        panic_on_clone: Some(2),
29631      });
29632
29633      let result = catch_unwind(AssertUnwindSafe(|| {
29634        let mut sleep = make_sleep(
29635          &backend,
29636          &registry,
29637          Instant::now() + Duration::from_secs(60),
29638        );
29639        let waker = test_hostile_waker(Arc::clone(&state));
29640        let mut cx = Context::from_waker(&waker);
29641        let _ = Pin::new(&mut sleep).poll(&mut cx);
29642      }));
29643      assert!(
29644        result.is_err(),
29645        "the original RawWaker clone panic must still propagate"
29646      );
29647      assert_eq!(state.clones.load(Ordering::SeqCst), 2);
29648      assert_eq!(
29649        state.drop_panics.load(Ordering::SeqCst),
29650        1,
29651        "the first clone must be destroyed once behind a contained boundary"
29652      );
29653      assert!(driver.registers.lock().unwrap().is_empty());
29654      assert_eq!(registry.pending_len(), 0);
29655      assert!(!executor.host_timers.has_pending());
29656      return;
29657    }
29658
29659    let output = std::process::Command::new(std::env::current_exe().unwrap())
29660      .arg("--exact")
29661      .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
29662      .arg("--nocapture")
29663      .env(CHILD_ENV, "1")
29664      .output()
29665      .expect("the hostile RawWaker subprocess test must start");
29666    assert!(
29667      output.status.success(),
29668      "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
29669      output.status.code(),
29670      String::from_utf8_lossy(&output.stdout),
29671      String::from_utf8_lossy(&output.stderr)
29672    );
29673  }
29674
29675  #[test]
29676  fn host_sleep_register_unwind_contains_unretained_waker_drop() {
29677    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
29678
29679    if std::env::var_os(CHILD_ENV).is_some() {
29680      let metrics = Arc::new(RuntimeMetrics::default());
29681      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29682      let backend =
29683        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29684      let driver = Arc::new(PanicBeforeRetainingTimerDriver {
29685        registers: AtomicUsize::new(0),
29686      });
29687      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29688      let state = Arc::new(TestHostileRawWakerState {
29689        clones: AtomicUsize::new(0),
29690        drop_panics: AtomicUsize::new(0),
29691        panic_on_clone: None,
29692      });
29693      let mut sleep = make_sleep(
29694        &backend,
29695        &registry,
29696        Instant::now() + Duration::from_secs(60),
29697      );
29698      let waker = test_hostile_waker(Arc::clone(&state));
29699      let mut cx = Context::from_waker(&waker);
29700
29701      let result = catch_unwind(AssertUnwindSafe(|| {
29702        let _ = Pin::new(&mut sleep).poll(&mut cx);
29703      }));
29704      assert!(
29705        result.is_err(),
29706        "the timer-driver register panic must still propagate"
29707      );
29708      assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
29709      assert_eq!(state.clones.load(Ordering::SeqCst), 3);
29710      assert_eq!(
29711        state.drop_panics.load(Ordering::SeqCst),
29712        1,
29713        "the unretained register argument must be destroyed once behind the proxy boundary"
29714      );
29715      assert_eq!(registry.pending_len(), 1);
29716      assert!(executor.host_timers.has_pending());
29717
29718      drop(sleep);
29719      assert_eq!(
29720        state.drop_panics.load(Ordering::SeqCst),
29721        3,
29722        "repoll and registry wakers must retain their existing contained cleanup"
29723      );
29724      assert_eq!(registry.pending_len(), 0);
29725      assert!(!executor.host_timers.has_pending());
29726      return;
29727    }
29728
29729    let output = std::process::Command::new(std::env::current_exe().unwrap())
29730      .arg("--exact")
29731      .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
29732      .arg("--nocapture")
29733      .env(CHILD_ENV, "1")
29734      .output()
29735      .expect("the hostile register-waker subprocess test must start");
29736    assert!(
29737      output.status.success(),
29738      "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
29739      output.status.code(),
29740      String::from_utf8_lossy(&output.stdout),
29741      String::from_utf8_lossy(&output.stderr)
29742    );
29743  }
29744
29745  #[test]
29746  fn host_sleep_rearms_on_next_live_driver_after_eviction() {
29747    // A sleep armed on a driver whose host dies must keep every remaining live
29748    // registrant armed with its remaining time preserved
29749    // (absolute deadline), keep the executor's wake-token accounting at
29750    // exactly one entry throughout, and cancel on the driver it is CURRENTLY
29751    // armed on when dropped.
29752    let metrics = Arc::new(RuntimeMetrics::default());
29753    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29754    let backend =
29755      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29756
29757    let a = ManualStubDriver::new();
29758    let b = ManualStubDriver::new();
29759    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
29760    let deadline = Instant::now() + Duration::from_secs(60);
29761
29762    let (timer_id, b_id) = {
29763      let mut sleep = std::pin::pin!(make_sleep(&backend, &registry, deadline));
29764      let mut cx = Context::from_waker(Waker::noop());
29765
29766      // First poll arms on A (the only registrant) and takes the wake-token.
29767      assert!(sleep.as_mut().poll(&mut cx).is_pending());
29768      let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
29769      assert_eq!(armed_deadline, deadline);
29770      assert!(
29771        executor.host_timers.has_pending(),
29772        "first poll must arm the wake-token"
29773      );
29774
29775      // A's host dies; a newer live registrant appears (the binding shape:
29776      // eviction wakes every pending sleep, which then re-polls -- here the
29777      // re-poll is driven manually).
29778      a.kill();
29779      let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
29780      assert!(sleep.as_mut().poll(&mut cx).is_pending());
29781      assert_eq!(
29782        b.registers.lock().unwrap().as_slice(),
29783        &[(timer_id, deadline)],
29784        "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
29785      );
29786      assert_eq!(
29787        a.cancels.lock().unwrap().as_slice(),
29788        &[timer_id],
29789        "the dead driver gets a best-effort cancel for the old arm"
29790      );
29791      assert_eq!(
29792        registry.current().map(|(id, _)| id),
29793        Some(b_id),
29794        "the dead driver was evicted"
29795      );
29796      assert!(
29797        executor.host_timers.has_pending(),
29798        "the wake-token survives the re-arm (still exactly this sleep's entry)"
29799      );
29800      (timer_id, b_id)
29801    };
29802
29803    // Drop (the pin! scope ended): cancel goes to the CURRENT driver, and the
29804    // wake-token retires.
29805    let _ = b_id;
29806    assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
29807    assert!(
29808      !executor.host_timers.has_pending(),
29809      "drop must retire the wake-token"
29810    );
29811  }
29812
29813  #[test]
29814  fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
29815    let metrics = Arc::new(RuntimeMetrics::default());
29816    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29817    let backend =
29818      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29819    let responsive = Arc::new(StubHostTimerDriver::new());
29820    let starved = ManualStubDriver::new();
29821    let registry = Arc::new(TimerDriverRegistry::default());
29822    registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
29823    // Newest-live-only selection would arm exclusively on this host and never
29824    // receive a wake. The responsive older host must be raced in parallel.
29825    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
29826
29827    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29828    let waker = Waker::from(Arc::clone(&wake_count));
29829    let mut cx = Context::from_waker(&waker);
29830    // This deadline's pre-fire budget covers only `make_sleep` plus the very
29831    // next poll (a past-due Sleep completes inline and would bypass the
29832    // racing-arm machinery under test); 500ms dwarfs those two statements on
29833    // a starved runner without dragging the fire wait out.
29834    let deadline = Instant::now() + Duration::from_millis(500);
29835    let mut sleep = make_sleep(&backend, &registry, deadline);
29836    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29837
29838    let responsive_timer = *responsive
29839      .timers
29840      .lock()
29841      .unwrap()
29842      .keys()
29843      .next()
29844      .expect("responsive timer must be armed");
29845    assert_eq!(
29846      starved.registers.lock().unwrap().as_slice(),
29847      &[(responsive_timer, deadline)],
29848      "every live host must receive the same timer identity and absolute deadline"
29849    );
29850
29851    // Event-based fire wait (this was a fixed 140ms sleep, which required
29852    // the responsive stub's freshly spawned fire thread to get CPU inside
29853    // ~80ms on a starved CI host). The wait is structurally sound: the
29854    // starved ManualStubDriver has NO fire path (it only records), so the
29855    // one-and-only wake can come from the responsive host -- the bound is a
29856    // pure hang detector.
29857    let fire_bound = Instant::now() + Duration::from_secs(30);
29858    while wake_count.0.load(Ordering::SeqCst) == 0 {
29859      assert!(
29860        Instant::now() < fire_bound,
29861        "the responsive host must wake a sleep whose newest host is live but starved"
29862      );
29863      std::thread::sleep(Duration::from_millis(10));
29864    }
29865    // The stub fires at (or after) the shared absolute deadline, so the wall
29866    // clock is past `deadline` here and the poll completes.
29867    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
29868    assert_eq!(
29869      starved.cancels.lock().unwrap().as_slice(),
29870      &[responsive_timer],
29871      "completion through the winning host must cancel the starved losing arm"
29872    );
29873  }
29874
29875  #[test]
29876  fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
29877    use std::sync::mpsc;
29878
29879    struct ReentrantTimerRegistrationWake {
29880      registry: Weak<TimerDriverRegistry>,
29881      wakes: AtomicUsize,
29882      reentered: AtomicBool,
29883    }
29884
29885    impl std::task::Wake for ReentrantTimerRegistrationWake {
29886      fn wake(self: Arc<Self>) {
29887        self.wakes.fetch_add(1, Ordering::SeqCst);
29888        if let Some(registry) = self.registry.upgrade() {
29889          let _ = registry.has_live_driver();
29890          self.reentered.store(true, Ordering::SeqCst);
29891        }
29892      }
29893    }
29894
29895    let metrics = Arc::new(RuntimeMetrics::default());
29896    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29897    let backend =
29898      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29899    let registry = Arc::new(TimerDriverRegistry::default());
29900    let starved = ManualStubDriver::new();
29901    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
29902
29903    // The whole pre-fire phase (four polls plus a registration-thread round
29904    // trip) must finish while the wall clock is still BEFORE this shared
29905    // deadline: a past-due `Sleep` completes inline at poll
29906    // (`Instant::now() >= deadline`) and bypasses the repoll machinery under
29907    // test. 2s dwarfs the phase's worst observed CI overhead (tens of ms on
29908    // a starved GitHub macOS runner) without dragging the fire wait out.
29909    let deadline = Instant::now() + Duration::from_secs(2);
29910    let mut first = make_sleep(&backend, &registry, deadline);
29911    let mut second = make_sleep(&backend, &registry, deadline);
29912    let first_wake = Arc::new(ReentrantTimerRegistrationWake {
29913      registry: Arc::downgrade(&registry),
29914      wakes: AtomicUsize::new(0),
29915      reentered: AtomicBool::new(false),
29916    });
29917    let second_wake = Arc::new(ReentrantTimerRegistrationWake {
29918      registry: Arc::downgrade(&registry),
29919      wakes: AtomicUsize::new(0),
29920      reentered: AtomicBool::new(false),
29921    });
29922    let first_waker = Waker::from(Arc::clone(&first_wake));
29923    let second_waker = Waker::from(Arc::clone(&second_wake));
29924    let mut first_cx = Context::from_waker(&first_waker);
29925    let mut second_cx = Context::from_waker(&second_waker);
29926
29927    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
29928    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
29929    let initial = starved.registers.lock().unwrap().clone();
29930    let first_id = initial[0].0;
29931    let second_id = initial[1].0;
29932    assert_ne!(first_id, second_id);
29933    assert_eq!(
29934      registry.pending_len(),
29935      2,
29936      "one stable repoll entry is retained per sleep"
29937    );
29938
29939    let responsive = Arc::new(StubHostTimerDriver::new());
29940    let (registered_tx, registered_rx) = mpsc::sync_channel(1);
29941    let registration_registry = Arc::clone(&registry);
29942    let registration_driver = Arc::clone(&responsive);
29943    let registration = std::thread::spawn(move || {
29944      let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
29945      registered_tx.send(id).unwrap();
29946    });
29947    // Hang detector only (the regression it pins is a PERMANENT deadlock:
29948    // `register` re-entering the repoll callback), so the bound is generous
29949    // rather than a latency assertion a starved runner could trip.
29950    registered_rx
29951      .recv_timeout(Duration::from_secs(10))
29952      .expect("late driver registration or repoll callback re-entry deadlocked");
29953    registration.join().unwrap();
29954
29955    assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
29956    assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
29957    assert!(first_wake.reentered.load(Ordering::SeqCst));
29958    assert!(second_wake.reentered.load(Ordering::SeqCst));
29959    assert!(
29960      responsive.timers.lock().unwrap().is_empty(),
29961      "registration requests bounded re-polls instead of invoking driver callbacks inline"
29962    );
29963
29964    // Both sleeps must still be PENDING when they re-arm on the late driver;
29965    // see the deadline-margin note above. Fail with a diagnosis instead of a
29966    // misleading Pending-assert failure if a pathological stall consumed the
29967    // whole margin anyway.
29968    assert!(
29969      Instant::now() < deadline,
29970      "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
29971       this runner is starved beyond the margin this test budgets for"
29972    );
29973    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
29974    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
29975    let responsive_ids = responsive
29976      .timers
29977      .lock()
29978      .unwrap()
29979      .keys()
29980      .copied()
29981      .collect::<FxHashSet<_>>();
29982    assert_eq!(
29983      responsive_ids,
29984      FxHashSet::from_iter([first_id, second_id]),
29985      "every pending sleep must re-arm its existing identity on the new driver"
29986    );
29987    assert_eq!(
29988      starved
29989        .registers
29990        .lock()
29991        .unwrap()
29992        .iter()
29993        .filter(|(id, _)| *id == first_id)
29994        .count(),
29995      2,
29996      "the old host receives only the normal waker refresh on the one re-poll"
29997    );
29998    assert_eq!(
29999      starved
30000        .registers
30001        .lock()
30002        .unwrap()
30003        .iter()
30004        .filter(|(id, _)| *id == second_id)
30005        .count(),
30006      2,
30007      "the old host receives only the normal waker refresh on the one re-poll"
30008    );
30009    assert_eq!(
30010      registry.pending_len(),
30011      2,
30012      "late registration must not duplicate pending state"
30013    );
30014
30015    // Event-based fire wait instead of a fixed sleep: the starved host has
30016    // NO fire path by construction (ManualStubDriver only records), so the
30017    // second wake per sleep can only come from the late responsive host --
30018    // "while the original host remains starved" is structural, not a
30019    // wall-clock race. The bound is purely a hang detector, generous enough
30020    // for a starved runner to schedule the stub's two fire threads.
30021    let fire_bound = Instant::now() + Duration::from_secs(30);
30022    while first_wake.wakes.load(Ordering::SeqCst) < 2
30023      || second_wake.wakes.load(Ordering::SeqCst) < 2
30024    {
30025      assert!(
30026        Instant::now() < fire_bound,
30027        "the late responsive host must fire both timers while the original host remains starved"
30028      );
30029      std::thread::sleep(Duration::from_millis(10));
30030    }
30031    assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
30032    assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
30033
30034    let mut cancelled = starved.cancels.lock().unwrap().clone();
30035    cancelled.sort_unstable();
30036    let mut expected = vec![first_id, second_id];
30037    expected.sort_unstable();
30038    assert_eq!(
30039      cancelled, expected,
30040      "completion must cancel both losing starved-host arms"
30041    );
30042    assert_eq!(
30043      registry.pending_len(),
30044      0,
30045      "completion retires every repoll entry"
30046    );
30047    assert!(!executor.host_timers.has_pending());
30048  }
30049
30050  #[test]
30051  fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
30052    // With NO live registrant left mid-flight there is nothing to re-arm on:
30053    // the poll must fail LOUD with the typed diagnostic, never park a wake-less
30054    // sleep silently.
30055    let metrics = Arc::new(RuntimeMetrics::default());
30056    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30057    let backend =
30058      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30059
30060    let a = ManualStubDriver::new();
30061    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
30062    let mut sleep = std::pin::pin!(make_sleep(
30063      &backend,
30064      &registry,
30065      Instant::now() + Duration::from_secs(60)
30066    ));
30067    let mut cx = Context::from_waker(Waker::noop());
30068    assert!(sleep.as_mut().poll(&mut cx).is_pending());
30069
30070    a.kill();
30071    let payload = catch_unwind(AssertUnwindSafe(|| {
30072      let _ = sleep.as_mut().poll(&mut cx);
30073    }))
30074    .expect_err("a sleep with every driver dead must panic, not pend silently");
30075    let message = payload
30076      .downcast_ref::<String>()
30077      .cloned()
30078      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
30079      .expect("panic payload must be a message");
30080    assert!(
30081      message.contains("lost every live timer driver mid-sleep"),
30082      "the diagnostic must name the mid-flight driver loss, got: {message}"
30083    );
30084  }
30085
30086  #[test]
30087  fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
30088    // Sweep-eviction invariant: the registry's selection sweep can be the
30089    // FIRST layer to notice a dead driver, before the owner's env-cleanup hook
30090    // or any call failure. A retain-only sweep would strand every sleep whose
30091    // armed waker lives in the swept driver's pending map. The sweep must call
30092    // `on_swept` outside the registry lock so the driver wakes those sleeps
30093    // into re-selection.
30094    let metrics = Arc::new(RuntimeMetrics::default());
30095    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30096    let backend =
30097      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30098
30099    let a = ManualStubDriver::new();
30100    let b = ManualStubDriver::new();
30101    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
30102    let mut sleep = std::pin::pin!(make_sleep(
30103      &backend,
30104      &registry,
30105      Instant::now() + Duration::from_secs(60)
30106    ));
30107
30108    // Arm on A with an observable waker.
30109    let flag = Arc::new(WakeFlag::default());
30110    let waker = Waker::from(Arc::clone(&flag));
30111    let mut cx = Context::from_waker(&waker);
30112    assert!(sleep.as_mut().poll(&mut cx).is_pending());
30113    assert!(!flag.0.load(Ordering::SeqCst));
30114
30115    // A's host dies; a DIFFERENT selection (any registry touch -- here a
30116    // liveness query, in production another sleep's poll) sweeps it.
30117    a.kill();
30118    registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
30119    let _ = registry.has_live_driver();
30120
30121    assert!(
30122      a.swept.load(Ordering::SeqCst),
30123      "the sweep must notify the swept driver"
30124    );
30125    assert!(
30126      flag.0.load(Ordering::SeqCst),
30127      "a sleep armed on the swept driver must be woken into re-selection, not stranded"
30128    );
30129
30130    // The woken sleep's re-poll lands on the live registrant.
30131    assert!(sleep.as_mut().poll(&mut cx).is_pending());
30132    assert_eq!(
30133      b.registers.lock().unwrap().len(),
30134      1,
30135      "the re-poll must re-arm on the live driver"
30136    );
30137  }
30138
30139  #[test]
30140  fn current_thread_sleep_fires_through_stub_host_driver() {
30141    // CT delegation: a registered host driver's fire must complete a sleep
30142    // awaited through the CT `block_on` drive loop.
30143    use std::sync::mpsc;
30144
30145    let (done_tx, done_rx) = mpsc::channel();
30146    let runner = std::thread::spawn(move || {
30147      let metrics = Arc::new(RuntimeMetrics::default());
30148      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30149      let backend =
30150        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30151      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30152
30153      let started = Instant::now();
30154      let mut future = std::pin::pin!(async {
30155        make_sleep(
30156          &backend,
30157          &registry_with(driver),
30158          Instant::now() + Duration::from_millis(60),
30159        )
30160        .await;
30161      });
30162      executor.block_on(future.as_mut());
30163      assert!(started.elapsed() >= Duration::from_millis(60));
30164      assert!(
30165        !executor.host_timers.has_pending(),
30166        "a completed sleep must retire its pending-registry entry"
30167      );
30168      let _ = done_tx.send(());
30169    });
30170
30171    match done_rx.recv_timeout(Duration::from_secs(10)) {
30172      Ok(()) => runner.join().unwrap(),
30173      Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
30174    }
30175  }
30176
30177  #[test]
30178  fn current_thread_timer_shutdown_retires_pending_repoll_state() {
30179    let metrics = Arc::new(RuntimeMetrics::default());
30180    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30181    let backend =
30182      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30183    let drivers = Arc::new(TimerDriverRegistry::default());
30184    let driver = ManualStubDriver::new();
30185    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30186    let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
30187    let mut cx = Context::from_waker(Waker::noop());
30188
30189    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30190    let timer_id = driver.registers.lock().unwrap()[0].0;
30191    assert_eq!(drivers.pending_len(), 1);
30192
30193    executor.host_timers.shutdown();
30194    assert_eq!(
30195      drivers.pending_len(),
30196      0,
30197      "shutdown must retire the registration-repoll index before the Sleep is dropped"
30198    );
30199    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
30200    assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
30201  }
30202
30203  #[test]
30204  fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
30205    use std::sync::mpsc;
30206
30207    let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
30208    let backend = controller.backend();
30209    let drivers = Arc::new(TimerDriverRegistry::default());
30210    let driver = ManualStubDriver::new();
30211    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30212    let sleep = make_sleep(
30213      &backend,
30214      &drivers,
30215      Instant::now() + Duration::from_secs(120),
30216    );
30217    drop(backend);
30218
30219    let (polled_tx, polled_rx) = mpsc::channel();
30220    let block_controller = Arc::clone(&controller);
30221    let blocker = std::thread::spawn(move || {
30222      let mut announced = false;
30223      let mut sleep = std::pin::pin!(sleep);
30224      let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
30225        let poll = sleep.as_mut().poll(cx);
30226        if !announced {
30227          announced = true;
30228          polled_tx.send(()).unwrap();
30229        }
30230        poll
30231      }));
30232      block_controller.block_on(future.as_mut());
30233    });
30234    polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
30235
30236    let (shutdown_tx, shutdown_rx) = mpsc::channel();
30237    let shutdown_controller = Arc::clone(&controller);
30238    let shutdown = std::thread::spawn(move || {
30239      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30240    });
30241    shutdown_rx
30242      .recv_timeout(Duration::from_secs(2))
30243      .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
30244      .unwrap();
30245    shutdown.join().unwrap();
30246    blocker.join().unwrap();
30247    assert_eq!(driver.cancels.lock().unwrap().len(), 1);
30248  }
30249
30250  #[test]
30251  fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
30252    // Threadless pending-timer invariant: on a
30253    // THREADLESS build a pending host timer is NOT a wake-token source -- the
30254    // host event loop's `setTimeout` relay can only run on the very thread
30255    // that is about to park, so it can never fire while `block_on` holds it.
30256    // The certain check must panic with the typed diagnostic anyway (the
30257    // timer-backed kind, so the message names the shape), never fall through
30258    // to the park -- on the real wasip1 target that park is an untyped
30259    // "condvar wait not supported" abort. A native helper thread can fire the
30260    // timer in this test harness, but that concurrency cannot exist on the
30261    // threadless target and must not rescue the park.
30262    use std::sync::mpsc;
30263
30264    let (done_tx, done_rx) = mpsc::channel();
30265    let runner = std::thread::spawn(move || {
30266      let metrics = Arc::new(RuntimeMetrics::default());
30267      // Native stand-in for the threadless wasm build; the stub driver would
30268      // play the host loop from a helper thread, but the panic must land at
30269      // the first park decision, long before its 60ms fire.
30270      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30271      let backend =
30272        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30273      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30274
30275      let payload = catch_unwind(AssertUnwindSafe(|| {
30276        let mut future = std::pin::pin!(async {
30277          make_sleep(
30278            &backend,
30279            &registry_with(driver),
30280            Instant::now() + Duration::from_millis(60),
30281          )
30282          .await;
30283        });
30284        executor.block_on(future.as_mut());
30285      }))
30286      .expect_err("a threadless park must self-detect even while a host timer is pending");
30287      let diagnostic = payload
30288        .downcast_ref::<BlockOnDeadlock>()
30289        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
30290      assert_eq!(
30291        diagnostic.kind,
30292        BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
30293      );
30294      assert_eq!(
30295        diagnostic.park_deadline, None,
30296        "the certain case carries no deadline"
30297      );
30298      let message = diagnostic.to_string();
30299      assert!(
30300        message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
30301        "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
30302      );
30303      let _ = done_tx.send(());
30304    });
30305
30306    match done_rx.recv_timeout(Duration::from_secs(10)) {
30307      Ok(()) => runner.join().unwrap(),
30308      Err(error) => panic!(
30309        "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})"
30310      ),
30311    }
30312  }
30313
30314  #[test]
30315  fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
30316    // Unpolled-sleep invariant: the registry entry only exists once the host
30317    // timer is ARMED (first poll runs `driver.register`), never at `Sleep`
30318    // creation. A created-but-never-polled Sleep has no host callback behind
30319    // it, and on a parked single thread nothing can ever poll it into
30320    // arming -- it is provably NOT a future wake source. Counting it would
30321    // mislabel the certain diagnostic as timer-backed. (The armed twin above
30322    // pins the polled side: a POLLED pending timer still certain-panics, just
30323    // with the timer-backed kind.)
30324    use std::sync::mpsc;
30325
30326    let (done_tx, done_rx) = mpsc::channel();
30327    let runner = std::thread::spawn(move || {
30328      let metrics = Arc::new(RuntimeMetrics::default());
30329      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30330      let backend =
30331        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30332      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30333
30334      // Created and held across the park decision, but NEVER polled: no host
30335      // timer armed, no wake source.
30336      let _unpolled = make_sleep(
30337        &backend,
30338        &registry_with(driver),
30339        Instant::now() + Duration::from_secs(60),
30340      );
30341
30342      let payload = catch_unwind(AssertUnwindSafe(|| {
30343        let mut future = std::pin::pin!(NeverReady);
30344        executor.block_on(future.as_mut());
30345      }))
30346      .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
30347      let diagnostic = payload
30348        .downcast_ref::<BlockOnDeadlock>()
30349        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
30350      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
30351      let _ = done_tx.send(());
30352    });
30353
30354    match done_rx.recv_timeout(Duration::from_secs(10)) {
30355      Ok(()) => runner.join().unwrap(),
30356      Err(error) => panic!(
30357        "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
30358      ),
30359    }
30360  }
30361
30362  #[cfg(not(target_family = "wasm"))]
30363  #[test]
30364  fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
30365    // CurrentThread timer/deadline invariant: deadline armed (30ms) shorter than a pending
30366    // host timer (200ms). Waiting out a LIVE host timer with zero progress is
30367    // legitimate (the wake is scheduled on the host loop); the deadline
30368    // verdict must not fire. Once the timer fires, the sleep completes.
30369    use std::sync::mpsc;
30370
30371    let (done_tx, done_rx) = mpsc::channel();
30372    let runner = std::thread::spawn(move || {
30373      let metrics = Arc::new(RuntimeMetrics::default());
30374      // Deadline/grace ratio: the armed park deadline doubles as the
30375      // `has_live_wait` GRACE -- once the host timer is past due by more
30376      // than one deadline window without firing, the veto stops holding BY
30377      // DESIGN (that is the frozen-JS-loop signature). With a 30ms window
30378      // the stub host's freshly spawned fire thread had to get CPU within
30379      // ~230ms of test start or the verdict fired -- the starved-CI failure.
30380      // A 2s window keeps the invariant identical (a LIVE host-timer wait
30381      // with zero progress must not be declared dead) while tolerating
30382      // multi-second scheduling stalls of the fire thread: the veto holds
30383      // until deadline+2s, and the expiry checks land on 2s boundaries, so
30384      // the fire has >4s before a verdict could stick.
30385      let executor = Arc::new(CurrentThreadExecutor::with_detection(
30386        metrics,
30387        false,
30388        Some(Duration::from_secs(2)),
30389      ));
30390      let backend =
30391        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30392      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30393
30394      let started = Instant::now();
30395      let mut future = std::pin::pin!(async {
30396        make_sleep(
30397          &backend,
30398          &registry_with(driver),
30399          Instant::now() + Duration::from_millis(200),
30400        )
30401        .await;
30402      });
30403      executor.block_on(future.as_mut());
30404      assert!(started.elapsed() >= Duration::from_millis(200));
30405      let _ = done_tx.send(());
30406    });
30407
30408    match done_rx.recv_timeout(Duration::from_secs(30)) {
30409      Ok(()) => runner.join().unwrap(),
30410      Err(error) => {
30411        panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
30412      }
30413    }
30414  }
30415
30416  #[test]
30417  fn host_timer_unrepresentable_grace_remains_live() {
30418    let registry = HostTimerRegistry::default();
30419    let fired = Arc::new(AtomicBool::new(false));
30420    let drivers = Arc::new(TimerDriverRegistry::default());
30421    let repoll = Arc::new(HostTimerRepoll::default());
30422    assert!(
30423      Instant::now().checked_add(Duration::MAX).is_none(),
30424      "Duration::MAX must exercise the unrepresentable-grace path"
30425    );
30426    assert!(
30427      registry.register(
30428        1,
30429        Instant::now(),
30430        futures::task::noop_waker(),
30431        &fired,
30432        &drivers,
30433        &repoll,
30434      ),
30435      "the open registry must accept the timer"
30436    );
30437
30438    assert!(
30439      registry.has_live_wait(Duration::MAX),
30440      "an unrepresentable grace deadline is effectively unbounded"
30441    );
30442    registry.remove(1);
30443  }
30444
30445  #[test]
30446  fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
30447    // A deadline firing inside a spawned task's poll unwinds into the spawn
30448    // wrapper's catch_unwind and becomes a JoinError; the typed diagnostic's
30449    // message must survive that trip (JoinError::from_panic downcasts it), or
30450    // the build error would read "async runtime task panicked" with no clue.
30451    let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
30452    let expected = diagnostic.to_string();
30453    let payload: Box<dyn Any + Send> = Box::new(diagnostic);
30454    assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
30455  }
30456}