1use std::{
5 any::Any,
6 collections::VecDeque,
7 fmt,
8 future::Future,
9 mem::ManuallyDrop,
10 panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
11 pin::Pin,
12 sync::{
13 Arc, Condvar, LazyLock, Mutex, Weak,
14 atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
15 },
16 task::{Context, Poll, Waker},
17 time::{Duration, Instant},
18};
19
20use arc_swap::ArcSwapOption;
21use rustc_hash::{FxHashMap, FxHashSet};
22
23use crate::MAX_ASYNC_RUNTIME_WORKER_THREADS;
24#[cfg(test)]
25use async_task::Task;
26use async_task::{FallibleTask, Runnable};
27use futures::{
28 FutureExt,
29 future::{AbortHandle, AbortRegistration, Abortable},
30};
31
32#[cfg(not(target_family = "wasm"))]
33use crossbeam_deque::{Injector, Steal};
34#[cfg(not(target_family = "wasm"))]
35use futures::channel::oneshot;
36#[cfg(all(test, not(target_family = "wasm")))]
37use rayon::max_num_threads;
38#[cfg(not(target_family = "wasm"))]
39use rayon::{ThreadPool, ThreadPoolBuilder};
40
41fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
44 counter
45 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
46 .expect(exhausted)
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum RuntimeFlavor {
51 CurrentThread,
52 MultiThread,
53}
54
55#[derive(Debug, Clone)]
56pub struct RuntimeOptions {
57 pub flavor: RuntimeFlavor,
58 pub worker_threads: usize,
59 pub max_blocking_tasks: usize,
60 pub thread_name_prefix: String,
61 pub park_deadline: Option<Duration>,
74 pub drain_linger: Duration,
89}
90
91#[derive(Debug, Clone, Copy, Default)]
98pub struct RuntimeOptionsPatch {
99 pub flavor: Option<RuntimeFlavor>,
100 pub worker_threads: Option<usize>,
101 pub max_blocking_tasks: Option<usize>,
102 pub drain_linger: Option<Duration>,
105}
106
107impl RuntimeOptionsPatch {
108 fn apply_to(self, options: &mut RuntimeOptions) {
109 if let Some(flavor) = self.flavor {
110 options.flavor = flavor;
111 }
112 if let Some(worker_threads) = self.worker_threads {
113 options.worker_threads = worker_threads;
114 }
115 if let Some(max_blocking_tasks) = self.max_blocking_tasks {
116 options.max_blocking_tasks = max_blocking_tasks;
117 }
118 if let Some(drain_linger) = self.drain_linger {
119 options.drain_linger = drain_linger;
120 }
121 }
122}
123
124impl Default for RuntimeOptions {
125 fn default() -> Self {
126 let worker_threads = std::thread::available_parallelism()
127 .map_or(1, usize::from)
128 .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
129 Self {
130 flavor: if cfg!(target_family = "wasm") {
131 RuntimeFlavor::CurrentThread
132 } else {
133 RuntimeFlavor::MultiThread
134 },
135 worker_threads,
136 max_blocking_tasks: worker_threads,
137 thread_name_prefix: "napi-async-runtime".to_string(),
138 park_deadline: None,
139 drain_linger: DEFAULT_DRAIN_LINGER,
140 }
141 }
142}
143
144impl RuntimeOptions {
145 fn validate(self) -> Result<Self, RuntimeConfigError> {
146 #[cfg(target_family = "wasm")]
147 let rayon_max_threads = None;
148 #[cfg(not(target_family = "wasm"))]
149 let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
150 self.validate_with_rayon_max_threads(rayon_max_threads)
151 }
152
153 fn validate_with_rayon_max_threads(
154 mut self,
155 rayon_max_threads: Option<usize>,
156 ) -> Result<Self, RuntimeConfigError> {
157 if self.worker_threads == 0 {
158 return Err(RuntimeConfigError(
159 "worker_threads must be greater than zero".to_string(),
160 ));
161 }
162 if self.max_blocking_tasks == 0 {
163 return Err(RuntimeConfigError(
164 "max_blocking_tasks must be greater than zero".to_string(),
165 ));
166 }
167 self.drain_linger = self.drain_linger.min(MAX_DRAIN_LINGER);
174 if self.flavor == RuntimeFlavor::CurrentThread {
175 self.worker_threads = 1;
176 self.max_blocking_tasks = 1;
177 } else {
178 let Some(rayon_max_threads) = rayon_max_threads else {
179 return Err(RuntimeConfigError(
180 "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
181 ));
182 };
183 if rayon_max_threads < 2 {
184 return Err(RuntimeConfigError(
185 "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
186 ));
187 }
188 self.worker_threads = self
196 .worker_threads
197 .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
198 .min(rayon_max_threads);
199 let blocking_capacity = self.worker_threads - 1;
200 self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
201 }
202 Ok(self)
203 }
204}
205
206pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
215
216pub const DRAIN_LINGER_ENV: &str = "NAPI_RUNTIME_DRAIN_LINGER_US";
240
241pub const DEFAULT_DRAIN_LINGER_MICROS: u64 = 500;
245
246pub const MAX_DRAIN_LINGER_MICROS: u64 = 100_000;
253
254const DEFAULT_DRAIN_LINGER: Duration = Duration::from_micros(DEFAULT_DRAIN_LINGER_MICROS);
256
257const MAX_DRAIN_LINGER: Duration = Duration::from_micros(MAX_DRAIN_LINGER_MICROS);
259
260#[cfg(not(target_family = "wasm"))]
274const DRAIN_LINGER_FRAME_FACTOR: u32 = 16;
275
276#[cfg(not(target_family = "wasm"))]
279const MAX_DRAIN_LINGER_FRAME_RESIDENCE: Duration = Duration::from_secs(60);
280
281const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
297
298#[derive(Debug, Clone)]
299pub struct RuntimeConfigError(String);
300
301impl fmt::Display for RuntimeConfigError {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 f.write_str(&self.0)
304 }
305}
306
307impl std::error::Error for RuntimeConfigError {}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum BlockOnDeadlockKind {
312 CurrentThreadCertain,
317 CurrentThreadCertainTimerBacked,
324 CurrentThreadDeadline,
327 MultiThreadCooperativeDeadline,
331}
332
333#[derive(Debug, Clone)]
341pub struct BlockOnDeadlock {
342 pub kind: BlockOnDeadlockKind,
343 pub park_deadline: Option<Duration>,
346}
347
348impl BlockOnDeadlock {
349 fn current_thread_certain() -> Self {
350 Self {
351 kind: BlockOnDeadlockKind::CurrentThreadCertain,
352 park_deadline: None,
353 }
354 }
355
356 fn current_thread_certain_timer_backed() -> Self {
357 Self {
358 kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
359 park_deadline: None,
360 }
361 }
362
363 fn current_thread_deadline(deadline: Duration) -> Self {
364 Self {
365 kind: BlockOnDeadlockKind::CurrentThreadDeadline,
366 park_deadline: Some(deadline),
367 }
368 }
369
370 #[cfg(not(target_family = "wasm"))]
373 fn multi_thread_cooperative(deadline: Duration) -> Self {
374 Self {
375 kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
376 park_deadline: Some(deadline),
377 }
378 }
379}
380
381impl fmt::Display for BlockOnDeadlock {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 let deadline_ms = self
384 .park_deadline
385 .map_or(0, |deadline| deadline.as_millis());
386 match self.kind {
387 BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
388 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
389 park with an empty task queue and no pending wake on a threadless build -- no other \
390 thread exists that could ever deliver the wake, so this park would never end. This is \
391 the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
392 (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
393 dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
394 continuation is the one now parking.",
395 ),
396 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
397 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
398 park with an empty task queue on a threadless build while a host timer is pending. \
399 The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
400 only run on the very thread that is now parking, so it can never fire, and no other \
401 thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
402 hazard: native code called `block_on` on a future that (transitively) awaits a JS \
403 continuation, but the only thread that could run that continuation is the one now \
404 parking.",
405 ),
406 BlockOnDeadlockKind::CurrentThreadDeadline => write!(
407 f,
408 "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
409 {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
410 task queue, no pending wake and ZERO runtime progress across the whole window. This is \
411 the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
412 awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
413 once this thread returns to the event loop. If this park is legitimate, raise or unset \
414 the deadline.",
415 ),
416 BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
417 f,
418 "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
419 parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
420 no runnable or blocking work available and ZERO executor progress across the whole \
421 window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
422 The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
423 If this park is legitimate, raise or unset the deadline.",
424 ),
425 }
426 }
427}
428
429impl std::error::Error for BlockOnDeadlock {}
430
431#[derive(Debug)]
432pub struct JoinError {
433 message: String,
434}
435
436impl JoinError {
437 fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
438 Self {
439 message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
443 deadlock.to_string()
444 } else if let Some(message) = panic.downcast_ref::<&str>() {
445 (*message).to_string()
446 } else if let Some(message) = panic.downcast_ref::<String>() {
447 message.clone()
448 } else {
449 "async runtime task panicked".to_string()
450 },
451 }
452 }
453}
454
455impl fmt::Display for JoinError {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 f.write_str(&self.message)
458 }
459}
460
461impl std::error::Error for JoinError {}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464enum BlockOnOutcome {
465 Completed,
466 Stopped,
467}
468
469fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
470 if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
475 std::mem::forget(nested_payload);
476 }
477}
478
479#[derive(Default)]
480struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
481
482impl RetainedPanicPayload {
483 fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
484 if self.0.is_some() {
485 discard_panic_payload(payload);
486 } else {
487 self.0 = Some(payload);
488 }
489 }
490
491 fn is_some(&self) -> bool {
492 self.0.is_some()
493 }
494
495 fn resume(mut self) -> ! {
496 resume_unwind(
497 self
498 .0
499 .take()
500 .expect("a retained panic payload must remain owned"),
501 )
502 }
503}
504
505impl Drop for RetainedPanicPayload {
506 fn drop(&mut self) {
507 if let Some(payload) = self.0.take() {
508 discard_panic_payload(payload);
509 }
510 }
511}
512
513fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
514 match catch_unwind(AssertUnwindSafe(function)) {
515 Ok(value) => Some(value),
516 Err(payload) => {
517 discard_panic_payload(payload);
518 None
519 }
520 }
521}
522
523struct ContainedArc<T: ?Sized> {
524 value: Option<Arc<T>>,
525}
526
527impl<T: ?Sized> ContainedArc<T> {
528 fn new(value: Arc<T>) -> Self {
529 Self { value: Some(value) }
530 }
531
532 fn get(&self) -> &T {
533 self
534 .value
535 .as_deref()
536 .expect("the contained Arc must remain owned")
537 }
538
539 #[cfg(test)]
540 fn into_inner(mut self) -> Arc<T> {
541 self
542 .value
543 .take()
544 .expect("the contained Arc must remain owned")
545 }
546}
547
548impl<T: ?Sized> Drop for ContainedArc<T> {
549 fn drop(&mut self) {
550 if let Some(value) = self.value.take() {
551 let _ = catch_unwind_contained(|| drop(value));
552 }
553 }
554}
555
556struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
557 values: Vec<(I, Arc<T>)>,
558}
559
560impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
561 fn new(values: Vec<(I, Arc<T>)>) -> Self {
562 Self { values }
563 }
564
565 fn as_slice(&self) -> &[(I, Arc<T>)] {
566 &self.values
567 }
568
569 fn len(&self) -> usize {
570 self.values.len()
571 }
572
573 fn is_empty(&self) -> bool {
574 self.values.is_empty()
575 }
576
577 fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
578 self
579 .values
580 .pop()
581 .map(|(id, value)| (id, ContainedArc::new(value)))
582 }
583}
584
585impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
586 fn drop(&mut self) {
587 while let Some((_, value)) = self.values.pop() {
588 let _ = catch_unwind_contained(|| drop(value));
589 }
590 }
591}
592
593struct PanicContainedWaker {
594 waker: ManuallyDrop<Waker>,
595 generation: Option<u64>,
596}
597
598impl std::task::Wake for PanicContainedWaker {
599 fn wake(self: Arc<Self>) {
600 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
601 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
602 }
603
604 fn wake_by_ref(self: &Arc<Self>) {
605 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
606 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
607 }
608}
609
610impl Drop for PanicContainedWaker {
611 fn drop(&mut self) {
612 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
613 let _ = catch_unwind_contained(|| {
614 unsafe { ManuallyDrop::drop(&mut self.waker) };
616 });
617 }
618}
619
620fn panic_contained_waker(waker: Waker) -> Waker {
621 Waker::from(Arc::new(PanicContainedWaker {
622 waker: ManuallyDrop::new(waker),
623 generation: active_runtime_generation(),
624 }))
625}
626
627struct PanicContainedWakerSource {
628 waker: ManuallyDrop<Waker>,
629 generation: Option<u64>,
630}
631
632impl PanicContainedWakerSource {
633 fn new(waker: Waker) -> Self {
634 Self {
635 waker: ManuallyDrop::new(waker),
636 generation: active_runtime_generation(),
637 }
638 }
639
640 fn as_waker(&self) -> &Waker {
641 &self.waker
642 }
643}
644
645impl Drop for PanicContainedWakerSource {
646 fn drop(&mut self) {
647 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
648 let _ = catch_unwind_contained(|| {
649 unsafe { ManuallyDrop::drop(&mut self.waker) };
651 });
652 }
653}
654
655struct PanicContainedWakerCache {
656 source: PanicContainedWakerSource,
657 contained: Waker,
658}
659
660impl PanicContainedWakerCache {
661 fn new(waker: &Waker) -> Self {
662 let generation = active_runtime_generation();
663 let _generation = RuntimeGenerationGuard::enter_optional(generation);
664 let source = PanicContainedWakerSource::new(waker.clone());
665 let contained = panic_contained_waker(source.as_waker().clone());
666 Self { source, contained }
667 }
668
669 fn update(&mut self, waker: &Waker) {
670 let will_wake = {
671 let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
672 self.source.as_waker().will_wake(waker)
673 };
674 if !will_wake {
675 *self = Self::new(waker);
676 }
677 }
678
679 fn contained(&self) -> &Waker {
680 &self.contained
681 }
682}
683
684fn wake_waker_contained(waker: Waker) {
685 let _ = catch_unwind_contained(|| waker.wake_by_ref());
690 let _ = catch_unwind_contained(|| drop(waker));
691}
692
693fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
694 match catch_unwind(AssertUnwindSafe(function)) {
695 Ok(value) => Ok(value),
696 Err(payload) => {
697 let error = JoinError::from_panic(&*payload);
698 discard_panic_payload(payload);
699 Err(error)
700 }
701 }
702}
703
704fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
705 let error = JoinError::from_panic(&*payload);
706 discard_panic_payload(payload);
707 error
708}
709
710struct ContainedTaskOutput<T> {
711 output: ManuallyDrop<T>,
712 generation: u64,
713}
714
715impl<T> ContainedTaskOutput<T> {
716 fn new(output: T, generation: u64) -> Self {
717 Self {
718 output: ManuallyDrop::new(output),
719 generation,
720 }
721 }
722
723 fn into_inner(mut self) -> T {
724 let output = unsafe { ManuallyDrop::take(&mut self.output) };
727 std::mem::forget(self);
728 output
729 }
730}
731
732impl<T> Drop for ContainedTaskOutput<T> {
733 fn drop(&mut self) {
734 let _generation = RuntimeGenerationGuard::enter(self.generation);
735 let _ = catch_unwind_contained(|| {
736 unsafe { ManuallyDrop::drop(&mut self.output) };
739 });
740 }
741}
742
743#[cfg(not(target_family = "wasm"))]
744#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
745struct BlockingJobId(u64);
746
747#[cfg(not(target_family = "wasm"))]
748static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
749
750#[cfg(all(test, not(target_family = "wasm")))]
751thread_local! {
752 static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
753 const { std::cell::RefCell::new(None) };
754 static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
755 const { std::cell::RefCell::new(None) };
756 static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
757 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
758 const { std::cell::RefCell::new(None) };
759 static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
760 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
761 const { std::cell::RefCell::new(None) };
762 static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
763 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
764 const { std::cell::RefCell::new(None) };
765 static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
766 const { std::cell::RefCell::new(None) };
767 static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
768 const { std::cell::RefCell::new(None) };
769 static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
770 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
771 const { std::cell::RefCell::new(None) };
772 static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
773 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
774 const { std::cell::RefCell::new(None) };
775 static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
776 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
777 const { std::cell::RefCell::new(None) };
778 static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
779 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
780 const { std::cell::RefCell::new(None) };
781 static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
782 const { std::cell::RefCell::new(None) };
783 static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
784 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
785 const { std::cell::RefCell::new(None) };
786 static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
787 const { std::cell::RefCell::new(None) };
788}
789
790#[cfg(all(test, not(target_family = "wasm")))]
791struct WorkerTlsDropProbe {
792 action: Option<WorkerTlsDropAction>,
793}
794
795#[cfg(all(test, not(target_family = "wasm")))]
809enum WorkerTlsDropAction {
810 Record { completed: Arc<AtomicUsize> },
814 ReenterLifecycle {
815 controller: Arc<RuntimeController>,
816 completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
817 },
818}
819
820#[cfg(all(test, not(target_family = "wasm")))]
821impl Drop for WorkerTlsDropProbe {
822 fn drop(&mut self) {
823 match self
824 .action
825 .take()
826 .expect("worker TLS drop probe action already consumed")
827 {
828 WorkerTlsDropAction::Record { completed } => {
829 completed.fetch_add(1, Ordering::SeqCst);
830 }
831 WorkerTlsDropAction::ReenterLifecycle {
836 controller,
837 completed,
838 } => {
839 let start = controller.start().map_err(|error| error.to_string());
840 let shutdown = controller.shutdown().map_err(|error| error.to_string());
841 let _ = completed.send((start, shutdown));
842 }
843 }
844 }
845}
846
847#[cfg(all(test, not(target_family = "wasm")))]
848fn run_dependency_claim_test_hook() {
849 if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
850 hook();
851 }
852}
853
854#[cfg(all(test, not(target_family = "wasm")))]
855fn run_current_thread_dispatch_claim_test_hook() {
856 if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
857 {
858 hook();
859 }
860}
861
862#[cfg(all(test, not(target_family = "wasm")))]
863fn run_current_thread_dispatch_publication_blocked_test_hook() {
864 if let Some(hook) =
865 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
866 {
867 hook();
868 }
869}
870
871#[cfg(all(test, not(target_family = "wasm")))]
872fn run_current_thread_dispatch_recovery_test_hook() {
873 if let Some(hook) =
874 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
875 {
876 hook();
877 }
878}
879
880#[cfg(all(test, not(target_family = "wasm")))]
881fn run_current_thread_failed_publication_retired_test_hook() {
882 if let Some(hook) =
883 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
884 {
885 hook();
886 }
887}
888
889#[cfg(all(test, not(target_family = "wasm")))]
890fn run_current_thread_dispatch_call_test_hook() {
891 if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
892 hook();
893 }
894}
895
896#[cfg(all(test, not(target_family = "wasm")))]
897fn run_current_thread_dispatch_returned_test_hook() {
898 if let Some(hook) =
899 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
900 {
901 hook();
902 }
903}
904
905#[cfg(all(test, not(target_family = "wasm")))]
906fn run_current_thread_blocking_claim_test_hook() {
907 if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
908 {
909 hook();
910 }
911}
912
913#[cfg(all(test, not(target_family = "wasm")))]
914fn run_current_thread_runnable_claim_test_hook() {
915 if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
916 {
917 hook();
918 }
919}
920
921#[cfg(all(test, not(target_family = "wasm")))]
922fn run_multi_thread_work_claimed_test_hook() {
923 if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
924 hook();
925 }
926}
927
928#[cfg(all(test, not(target_family = "wasm")))]
929fn run_multi_thread_work_started_test_hook() {
930 if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
931 hook();
932 }
933}
934
935#[cfg(all(test, not(target_family = "wasm")))]
936fn run_dependency_predicate_test_hook() {
937 if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
938 hook();
939 }
940}
941
942#[cfg(all(test, not(target_family = "wasm")))]
943fn run_before_owned_blocking_queue_claim_test_hook() {
944 if let Some(hook) =
945 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
946 {
947 hook();
948 }
949}
950
951#[cfg(not(target_family = "wasm"))]
952#[derive(Clone, Copy, Debug, PartialEq, Eq)]
953struct BlockingDependency {
954 job: BlockingJobId,
955 owner: Option<BlockingOwnerToken>,
956}
957
958#[cfg(not(target_family = "wasm"))]
959#[derive(Debug)]
960struct DependencyClaim {
961 claimed: AtomicBool,
962 transition: Mutex<()>,
963}
964
965#[cfg(not(target_family = "wasm"))]
966impl DependencyClaim {
967 fn new() -> Self {
968 Self {
969 claimed: AtomicBool::new(false),
970 transition: Mutex::new(()),
971 }
972 }
973
974 fn is_live(&self) -> bool {
975 !self.claimed.load(Ordering::Acquire)
976 }
977
978 fn cancel_link(&self, link: &DependencyLink) {
979 let _transition = self
980 .transition
981 .lock()
982 .unwrap_or_else(std::sync::PoisonError::into_inner);
983 link.cancel();
984 }
985
986 fn try_claim(&self, link: &DependencyLink) -> bool {
987 let _transition = self
988 .transition
989 .lock()
990 .unwrap_or_else(std::sync::PoisonError::into_inner);
991 #[cfg(test)]
992 run_dependency_claim_test_hook();
993 if self.claimed.load(Ordering::Acquire) || !link.is_live() {
994 return false;
995 }
996 self.claimed.store(true, Ordering::Release);
997 true
998 }
999}
1000
1001#[cfg(not(target_family = "wasm"))]
1002#[derive(Debug)]
1003struct DependencyLink {
1004 live: AtomicBool,
1005 parent: Option<Weak<DependencyLink>>,
1006}
1007
1008#[cfg(not(target_family = "wasm"))]
1009impl DependencyLink {
1010 fn root() -> Arc<Self> {
1011 Arc::new(Self {
1012 live: AtomicBool::new(true),
1013 parent: None,
1014 })
1015 }
1016
1017 fn derived(parent: &Arc<Self>) -> Arc<Self> {
1018 Arc::new(Self {
1019 live: AtomicBool::new(true),
1020 parent: Some(Arc::downgrade(parent)),
1021 })
1022 }
1023
1024 fn is_live(&self) -> bool {
1025 if !self.live.load(Ordering::Acquire) {
1026 return false;
1027 }
1028 let Some(parent) = &self.parent else {
1029 return true;
1030 };
1031 let Some(mut current) = parent.upgrade() else {
1032 return false;
1033 };
1034 loop {
1035 if !current.live.load(Ordering::Acquire) {
1036 return false;
1037 }
1038 let Some(parent) = ¤t.parent else {
1039 return true;
1040 };
1041 let Some(next) = parent.upgrade() else {
1042 return false;
1043 };
1044 current = next;
1045 }
1046 }
1047
1048 fn cancel(&self) {
1049 self.live.store(false, Ordering::Release);
1050 }
1051}
1052
1053#[cfg(not(target_family = "wasm"))]
1054#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1055struct DependencyPublicationIdentity {
1056 job: BlockingJobId,
1057 claim: *const DependencyClaim,
1058 link: *const DependencyLink,
1059}
1060
1061#[cfg(not(target_family = "wasm"))]
1062#[derive(Clone, Debug)]
1063struct DependencyPublication {
1064 dependency: BlockingDependency,
1065 claim: Arc<DependencyClaim>,
1066 link: Arc<DependencyLink>,
1067}
1068
1069#[cfg(not(target_family = "wasm"))]
1070impl DependencyPublication {
1071 fn owned(dependency: BlockingDependency) -> Self {
1072 Self {
1073 dependency,
1074 claim: Arc::new(DependencyClaim::new()),
1075 link: DependencyLink::root(),
1076 }
1077 }
1078
1079 fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1080 Self {
1081 dependency: BlockingDependency {
1082 owner: self.dependency.owner.or(owner),
1083 ..self.dependency
1084 },
1085 claim: Arc::clone(&self.claim),
1086 link: DependencyLink::derived(&self.link),
1087 }
1088 }
1089
1090 fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1091 self.dependency.owner = self.dependency.owner.or(owner);
1092 self
1093 }
1094
1095 fn same_as(&self, other: &Self) -> bool {
1096 self.dependency == other.dependency
1097 && Arc::ptr_eq(&self.claim, &other.claim)
1098 && Arc::ptr_eq(&self.link, &other.link)
1099 }
1100
1101 fn identity(&self) -> DependencyPublicationIdentity {
1102 DependencyPublicationIdentity {
1103 job: self.dependency.job,
1104 claim: Arc::as_ptr(&self.claim),
1105 link: Arc::as_ptr(&self.link),
1106 }
1107 }
1108
1109 fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1110 Some(DependencyPublicationIdentity {
1111 job: self.dependency.job,
1112 claim: Arc::as_ptr(&self.claim),
1113 link: self.link.parent.as_ref()?.as_ptr(),
1114 })
1115 }
1116
1117 fn is_derived_from(&self, source: &Self) -> bool {
1118 self.immediate_source_identity() == Some(source.identity())
1119 }
1120
1121 fn is_live(&self) -> bool {
1122 self.claim.is_live() && self.link.is_live()
1123 }
1124
1125 fn cancel(&self) {
1126 self.claim.cancel_link(&self.link);
1127 }
1128
1129 fn try_claim(&self) -> bool {
1130 self.claim.try_claim(&self.link)
1131 }
1132}
1133
1134#[cfg(not(target_family = "wasm"))]
1135struct TaskDependencyEntry {
1136 sequence: u64,
1137 publication: DependencyPublication,
1138}
1139
1140#[cfg(not(target_family = "wasm"))]
1141struct SelectedDependencyPublication {
1142 sequence: u64,
1143 publication: DependencyPublication,
1144}
1145
1146#[cfg(not(target_family = "wasm"))]
1147#[derive(Clone, Copy)]
1148enum OwnerDependencyMode {
1149 Available,
1150 Exact,
1151}
1152
1153#[cfg(not(target_family = "wasm"))]
1154#[derive(Default)]
1155struct TaskDependencyState {
1156 current: VecDeque<TaskDependencyEntry>,
1157 next_sequence: u64,
1158 unowned_sequences: VecDeque<u64>,
1159 owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1160 waiter: Option<Waker>,
1170}
1171
1172#[cfg(not(target_family = "wasm"))]
1173impl TaskDependencyState {
1174 fn append(&mut self, publication: DependencyPublication) -> u64 {
1177 let sequence = self.next_sequence;
1178 self.next_sequence = self
1179 .next_sequence
1180 .checked_add(1)
1181 .expect("task dependency sequence space exhausted");
1182 match publication.dependency.owner {
1183 Some(owner) => self
1184 .owner_sequences
1185 .entry(owner)
1186 .or_default()
1187 .push_back(sequence),
1188 None => self.unowned_sequences.push_back(sequence),
1189 }
1190 self.current.push_back(TaskDependencyEntry {
1191 sequence,
1192 publication,
1193 });
1194 sequence
1195 }
1196
1197 fn entry_in(
1198 current: &VecDeque<TaskDependencyEntry>,
1199 sequence: u64,
1200 ) -> Option<&TaskDependencyEntry> {
1201 let front = current.front()?.sequence;
1202 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1203 let entry = current.get(index)?;
1204 (entry.sequence == sequence).then_some(entry)
1205 }
1206
1207 fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1208 Self::entry_in(&self.current, sequence)
1209 }
1210
1211 fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1212 let front = self.current.front()?.sequence;
1213 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1214 let entry = self.current.get_mut(index)?;
1215 (entry.sequence == sequence).then_some(entry)
1216 }
1217
1218 fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1219 match dependency.owner {
1220 Some(owner) => {
1221 let remove_owner = self
1222 .owner_sequences
1223 .get_mut(&owner)
1224 .is_some_and(|sequences| {
1225 if sequences.front() == Some(&sequence) {
1226 sequences.pop_front();
1227 }
1228 sequences.is_empty()
1229 });
1230 if remove_owner {
1231 self.owner_sequences.remove(&owner);
1232 }
1233 }
1234 None => {
1235 if self.unowned_sequences.front() == Some(&sequence) {
1236 self.unowned_sequences.pop_front();
1237 }
1238 }
1239 }
1240 }
1241
1242 fn clear_indexes(&mut self) {
1243 self.unowned_sequences.clear();
1244 self.owner_sequences.clear();
1245 }
1246}
1247
1248#[cfg(not(target_family = "wasm"))]
1249struct TaskDependency {
1250 state: Mutex<TaskDependencyState>,
1251 owner_hint: Option<BlockingOwnerToken>,
1252 has_current: AtomicBool,
1253 generation: u64,
1254 #[cfg(test)]
1255 entry_visits: AtomicUsize,
1256}
1257
1258#[cfg(not(target_family = "wasm"))]
1259impl Default for TaskDependency {
1260 fn default() -> Self {
1261 Self::new(u64::MAX, None)
1262 }
1263}
1264
1265#[cfg(not(target_family = "wasm"))]
1266impl TaskDependency {
1267 fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1268 Self {
1269 state: Mutex::new(TaskDependencyState::default()),
1270 owner_hint,
1271 has_current: AtomicBool::new(false),
1272 generation,
1273 #[cfg(test)]
1274 entry_visits: AtomicUsize::new(0),
1275 }
1276 }
1277
1278 fn new_task(generation: u64) -> Self {
1279 Self::new(generation, None)
1280 }
1281
1282 fn notify_waiter(&self, waiter: Option<Waker>) {
1283 if let Some(waiter) = waiter {
1284 let _generation = RuntimeGenerationGuard::enter(self.generation);
1285 wake_waker_contained(waiter);
1286 }
1287 }
1288
1289 fn clear_waiter(&self) {
1290 let waiter = self
1291 .state
1292 .lock()
1293 .unwrap_or_else(std::sync::PoisonError::into_inner)
1294 .waiter
1295 .take();
1296 if let Some(waiter) = waiter {
1297 let _generation = RuntimeGenerationGuard::enter(self.generation);
1298 let _ = catch_unwind_contained(|| drop(waiter));
1299 }
1300 }
1301
1302 fn clear(&self) {
1303 let waiter = {
1304 let mut state = self
1305 .state
1306 .lock()
1307 .unwrap_or_else(std::sync::PoisonError::into_inner);
1308 if state.current.is_empty() {
1309 None
1310 } else {
1311 for current in state.current.drain(..) {
1312 current.publication.cancel();
1313 }
1314 state.clear_indexes();
1315 self.has_current.store(false, Ordering::Release);
1316 state.waiter.take()
1317 }
1318 };
1319 self.notify_waiter(waiter);
1320 }
1321
1322 fn retire(&self, sequence: u64) {
1332 let waiter = {
1333 let mut state = self
1334 .state
1335 .lock()
1336 .unwrap_or_else(std::sync::PoisonError::into_inner);
1337 let cancelled = match state.entry_mut(sequence) {
1338 Some(current) => {
1339 self.note_entry_visit();
1340 current.publication.cancel();
1341 true
1342 }
1343 None => false,
1344 };
1345 if cancelled {
1346 self.prune_stale_prefix(&mut state);
1347 state.waiter.take()
1348 } else {
1349 None
1350 }
1351 };
1352 self.notify_waiter(waiter);
1353 }
1354
1355 fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1356 let Some(first_publication) = publications.first() else {
1357 return;
1358 };
1359 let publication_identities = (publications.len() > 1).then(|| {
1360 publications
1361 .iter()
1362 .map(DependencyPublication::identity)
1363 .collect::<FxHashSet<_>>()
1364 });
1365 let waiter = {
1366 let mut state = self
1367 .state
1368 .lock()
1369 .unwrap_or_else(std::sync::PoisonError::into_inner);
1370 let removed = state
1371 .current
1372 .iter_mut()
1373 .filter_map(|current| {
1374 self.note_entry_visit();
1375 let is_derived = publication_identities.as_ref().map_or_else(
1376 || current.publication.is_derived_from(first_publication),
1377 |identities| {
1378 current
1379 .publication
1380 .immediate_source_identity()
1381 .is_some_and(|identity| identities.contains(&identity))
1382 },
1383 );
1384 if !is_derived {
1385 return None;
1386 }
1387 current.publication.cancel();
1388 Some(current.sequence)
1389 })
1390 .collect::<Vec<_>>();
1391 if removed.is_empty() {
1394 None
1395 } else {
1396 self.prune_stale_prefix(&mut state);
1397 state.waiter.take()
1398 }
1399 };
1400 self.notify_waiter(waiter);
1401 }
1402
1403 fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1406 let publication = DependencyPublication::owned(BlockingDependency {
1407 owner: dependency.owner.or(self.owner_hint),
1408 ..dependency
1409 });
1410 self.set_entry(publication)
1411 }
1412
1413 fn holds_live(&self, sequence: u64) -> bool {
1419 let state = self
1420 .state
1421 .lock()
1422 .unwrap_or_else(std::sync::PoisonError::into_inner);
1423 TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1424 self.note_entry_visit();
1425 entry.publication.is_live()
1426 })
1427 }
1428
1429 fn set_borrowed(&self, publication: &DependencyPublication) {
1430 let _sequence = self.set_entry(publication.derived(self.owner_hint));
1431 }
1432
1433 fn set_entry(&self, publication: DependencyPublication) -> u64 {
1434 let (sequence, waiter) = {
1435 let mut state = self
1436 .state
1437 .lock()
1438 .unwrap_or_else(std::sync::PoisonError::into_inner);
1439 let sequence = state.append(publication);
1442 self.has_current.store(true, Ordering::Release);
1443 (sequence, state.waiter.take())
1444 };
1445 self.notify_waiter(waiter);
1446 sequence
1447 }
1448
1449 fn note_entry_visit(&self) {
1450 #[cfg(test)]
1451 self.entry_visits.fetch_add(1, Ordering::Relaxed);
1452 }
1453
1454 fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1455 while state.current.front().is_some_and(|current| {
1456 self.note_entry_visit();
1457 !current.publication.is_live()
1458 }) {
1459 let current = state
1460 .current
1461 .pop_front()
1462 .expect("the stale prefix entry must exist");
1463 state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1464 }
1465 self
1466 .has_current
1467 .store(!state.current.is_empty(), Ordering::Release);
1468 }
1469
1470 fn first_live_sequence(
1471 &self,
1472 current: &VecDeque<TaskDependencyEntry>,
1473 sequences: &mut VecDeque<u64>,
1474 ) -> Option<u64> {
1475 loop {
1476 let sequence = *sequences.front()?;
1477 let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1478 sequences.pop_front();
1479 continue;
1480 };
1481 self.note_entry_visit();
1482 if entry.publication.is_live() {
1483 return Some(sequence);
1484 }
1485 sequences.pop_front();
1486 }
1487 }
1488
1489 fn first_live_owner_sequence(
1490 &self,
1491 state: &mut TaskDependencyState,
1492 owner: BlockingOwnerToken,
1493 ) -> Option<u64> {
1494 let sequence = {
1495 let current = &state.current;
1496 state
1497 .owner_sequences
1498 .get_mut(&owner)
1499 .and_then(|sequences| self.first_live_sequence(current, sequences))
1500 };
1501 if sequence.is_none() {
1502 state.owner_sequences.remove(&owner);
1503 }
1504 sequence
1505 }
1506
1507 fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1508 self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1509 }
1510
1511 #[cfg(test)]
1512 fn get(&self) -> Option<DependencyPublication> {
1513 self
1514 .state
1515 .lock()
1516 .unwrap_or_else(std::sync::PoisonError::into_inner)
1517 .current
1518 .iter()
1519 .find(|current| current.publication.is_live())
1520 .map(|current| current.publication.clone())
1521 }
1522
1523 fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1524 self
1525 .state
1526 .lock()
1527 .unwrap_or_else(std::sync::PoisonError::into_inner)
1528 .current
1529 .iter()
1530 .filter(|current| {
1531 self.note_entry_visit();
1532 current.publication.is_live()
1533 })
1534 .map(|current| current.publication.clone())
1535 .collect()
1536 }
1537
1538 fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1539 let mut state = self
1540 .state
1541 .lock()
1542 .unwrap_or_else(std::sync::PoisonError::into_inner);
1543 self.prune_stale_prefix(&mut state);
1544 let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1545 let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1546 let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1547 (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1548 (Some(_) | None, Some(unowned)) => (unowned, true),
1549 (Some(exact), None) => (exact, false),
1550 (None, None) => return None,
1551 };
1552 if bind_owner {
1553 let removed = state.unowned_sequences.pop_front();
1554 debug_assert_eq!(removed, Some(sequence));
1555 let current = state
1556 .entry_mut(sequence)
1557 .expect("the selected dependency must remain indexed");
1558 debug_assert_eq!(current.publication.dependency.owner, None);
1559 current.publication.dependency.owner = Some(owner);
1560 state
1564 .owner_sequences
1565 .entry(owner)
1566 .or_default()
1567 .push_front(sequence);
1568 }
1569 let publication = state
1570 .entry(sequence)
1571 .expect("the selected dependency must remain indexed")
1572 .publication
1573 .clone();
1574 Some(SelectedDependencyPublication {
1575 sequence,
1576 publication,
1577 })
1578 }
1579
1580 fn has_live_owner_dependency(
1581 &self,
1582 owner: BlockingOwnerToken,
1583 mode: OwnerDependencyMode,
1584 ) -> bool {
1585 #[cfg(all(test, not(target_family = "wasm")))]
1586 run_dependency_predicate_test_hook();
1587 let mut state = self
1588 .state
1589 .lock()
1590 .unwrap_or_else(std::sync::PoisonError::into_inner);
1591 self.prune_stale_prefix(&mut state);
1592 if self.first_live_owner_sequence(&mut state, owner).is_some() {
1593 return true;
1594 }
1595 matches!(mode, OwnerDependencyMode::Available)
1596 && self.first_live_unowned_sequence(&mut state).is_some()
1597 }
1598
1599 #[cfg(test)]
1600 fn get_dependency(&self) -> Option<BlockingDependency> {
1601 self.get().map(|publication| publication.dependency)
1602 }
1603
1604 #[cfg(test)]
1605 fn get_dependencies(&self) -> Vec<BlockingDependency> {
1606 self
1607 .snapshot_all_live()
1608 .into_iter()
1609 .map(|publication| publication.dependency)
1610 .collect()
1611 }
1612
1613 #[cfg(test)]
1614 fn reset_entry_visits(&self) {
1615 self.entry_visits.store(0, Ordering::Relaxed);
1616 }
1617
1618 #[cfg(test)]
1619 fn entry_visits(&self) -> usize {
1620 self.entry_visits.load(Ordering::Relaxed)
1621 }
1622
1623 fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1627 let mut state = self
1628 .state
1629 .lock()
1630 .unwrap_or_else(std::sync::PoisonError::into_inner);
1631 let Some(current) = state.entry(selected.sequence) else {
1632 return false;
1633 };
1634 self.note_entry_visit();
1635 if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1636 return false;
1637 }
1638 let dependency = current.publication.dependency;
1639 state
1640 .entry_mut(selected.sequence)
1641 .expect("the claimed dependency must remain indexed")
1642 .publication
1643 .cancel();
1644 state.remove_sequence_indexes(selected.sequence, dependency);
1645 self.prune_stale_prefix(&mut state);
1646 true
1647 }
1648
1649 fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1650 let _generation = RuntimeGenerationGuard::enter(self.generation);
1651 let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1652 return Vec::new();
1653 };
1654 let (previous, unused, current) = {
1655 let mut state = self
1656 .state
1657 .lock()
1658 .unwrap_or_else(std::sync::PoisonError::into_inner);
1659 let current = state
1660 .current
1661 .iter()
1662 .filter(|current| current.publication.is_live())
1663 .map(|current| current.publication.clone())
1664 .collect();
1665 if state
1666 .waiter
1667 .as_ref()
1668 .is_some_and(|previous| previous.will_wake(&waker))
1669 {
1670 (None, Some(waker), current)
1671 } else {
1672 (state.waiter.replace(waker), None, current)
1673 }
1674 };
1675 if let Some(previous) = previous {
1676 let _ = catch_unwind_contained(|| drop(previous));
1677 }
1678 if let Some(unused) = unused {
1679 let _ = catch_unwind_contained(|| drop(unused));
1680 }
1681 current
1682 }
1683
1684 fn begin_poll(&self) {
1685 if !self.has_current.load(Ordering::Acquire) {
1686 return;
1687 }
1688 let waiter = {
1689 let mut state = self
1690 .state
1691 .lock()
1692 .unwrap_or_else(std::sync::PoisonError::into_inner);
1693 if state.current.is_empty() {
1694 self.has_current.store(false, Ordering::Release);
1695 None
1696 } else {
1697 for previous in state.current.drain(..) {
1698 previous.publication.cancel();
1699 }
1700 state.clear_indexes();
1701 self.has_current.store(false, Ordering::Release);
1702 state.waiter.take()
1703 }
1704 };
1705 self.notify_waiter(waiter);
1706 }
1707
1708 #[cfg(test)]
1709 fn register_waiter(&self, waker: &Waker) {
1710 let _ = self.register_waiter_and_get(waker);
1711 }
1712}
1713
1714#[cfg(not(target_family = "wasm"))]
1715impl Drop for TaskDependency {
1716 fn drop(&mut self) {
1717 let state = self
1718 .state
1719 .get_mut()
1720 .unwrap_or_else(std::sync::PoisonError::into_inner);
1721 for current in state.current.drain(..) {
1722 current.publication.cancel();
1723 }
1724 if let Some(waiter) = state.waiter.take() {
1725 let _generation = RuntimeGenerationGuard::enter(self.generation);
1726 let _ = catch_unwind_contained(|| drop(waiter));
1727 }
1728 }
1729}
1730
1731#[cfg(not(target_family = "wasm"))]
1732thread_local! {
1733 static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1734 const { std::cell::RefCell::new(Vec::new()) };
1735}
1736
1737#[cfg(not(target_family = "wasm"))]
1738struct TaskDependencyGuard(Arc<TaskDependency>);
1739
1740#[cfg(not(target_family = "wasm"))]
1741impl TaskDependencyGuard {
1742 fn enter(dependency: &Arc<TaskDependency>) -> Self {
1743 let dependency = Arc::clone(dependency);
1744 CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1745 Self(dependency)
1746 }
1747}
1748
1749#[cfg(not(target_family = "wasm"))]
1750impl Drop for TaskDependencyGuard {
1751 fn drop(&mut self) {
1752 CURRENT_DEPENDENCY_STACK.with(|stack| {
1753 let current = stack
1754 .borrow_mut()
1755 .pop()
1756 .expect("blocking dependency context underflow");
1757 debug_assert!(Arc::ptr_eq(¤t, &self.0));
1758 });
1759 }
1760}
1761
1762#[cfg(not(target_family = "wasm"))]
1763struct BlockOnDependencyGuard {
1764 dependency: Arc<TaskDependency>,
1765 _context: TaskDependencyGuard,
1766}
1767
1768#[cfg(not(target_family = "wasm"))]
1769impl BlockOnDependencyGuard {
1770 fn enter(generation: u64) -> Self {
1771 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1772 let dependency = Arc::new(TaskDependency::new(generation, owner));
1773 let context = TaskDependencyGuard::enter(&dependency);
1774 Self {
1775 dependency,
1776 _context: context,
1777 }
1778 }
1779
1780 fn clear(&self) {
1781 self.dependency.clear();
1782 }
1783}
1784
1785#[cfg(not(target_family = "wasm"))]
1800struct PublishedBlockingDependency {
1801 state: Weak<TaskDependency>,
1802 sequence: u64,
1803}
1804
1805#[cfg(not(target_family = "wasm"))]
1824#[derive(Default)]
1825struct PublishedBlockingDependencies {
1826 first: Option<PublishedBlockingDependency>,
1827 rest: Vec<PublishedBlockingDependency>,
1828}
1829
1830#[cfg(not(target_family = "wasm"))]
1831impl PublishedBlockingDependency {
1832 fn still_stands(&self) -> bool {
1847 self
1848 .state
1849 .upgrade()
1850 .is_some_and(|state| state.holds_live(self.sequence))
1851 }
1852}
1853
1854#[cfg(not(target_family = "wasm"))]
1855impl PublishedBlockingDependencies {
1856 fn prune(&mut self) {
1881 if self.rest.is_empty() {
1882 if self
1883 .first
1884 .as_ref()
1885 .is_some_and(|first| first.state.strong_count() == 0)
1886 {
1887 self.first = None;
1888 }
1889 return;
1890 }
1891 self.rest.retain(PublishedBlockingDependency::still_stands);
1892 if self
1893 .first
1894 .as_ref()
1895 .is_some_and(|first| !first.still_stands())
1896 {
1897 self.first = self.rest.pop();
1898 }
1899 }
1900
1901 fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1902 self.prune();
1903 let target = Arc::as_ptr(&state);
1904 if let Some(existing) = self
1905 .first
1906 .iter_mut()
1907 .chain(self.rest.iter_mut())
1908 .find(|published| std::ptr::eq(published.state.as_ptr(), target))
1909 {
1910 if state.holds_live(existing.sequence) {
1921 return;
1922 }
1923 existing.sequence = state.set_owned(dependency);
1927 return;
1928 }
1929 let sequence = state.set_owned(dependency);
1930 let published = PublishedBlockingDependency {
1931 state: Arc::downgrade(&state),
1932 sequence,
1933 };
1934 match &mut self.first {
1935 slot @ None => *slot = Some(published),
1936 Some(_) => self.rest.push(published),
1937 }
1938 }
1939
1940 fn retire_all(self) {
1950 for published in self.first.into_iter().chain(self.rest) {
1951 if let Some(state) = published.state.upgrade() {
1952 state.retire(published.sequence);
1953 }
1954 }
1955 }
1956}
1957
1958#[cfg(not(target_family = "wasm"))]
1959fn record_blocking_dependency(
1960 published: &mut PublishedBlockingDependencies,
1961 dependency: BlockingDependency,
1962) {
1963 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1964 if let Some(current) = current {
1965 let dependency = BlockingDependency {
1966 owner: dependency
1967 .owner
1968 .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
1969 ..dependency
1970 };
1971 published.publish(current, dependency);
1972 }
1973}
1974
1975#[cfg(not(target_family = "wasm"))]
1976fn propagate_blocking_dependency(publication: DependencyPublication) {
1977 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1978 if let Some(current) = current {
1979 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1980 let publication = publication.with_owner_if_none(owner);
1981 current.set_borrowed(&publication);
1982 }
1983}
1984
1985#[cfg(not(target_family = "wasm"))]
1986fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
1987 published.retire_all();
1991}
1992
1993#[cfg(not(target_family = "wasm"))]
1994fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
1995 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1996 if let Some(current) = current {
1997 current.clear_if_publications(publications);
1998 }
1999}
2000
2001enum JoinHandleInner<T> {
2002 Task {
2003 task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2004 awaiter: Option<PanicContainedWakerCache>,
2005 #[cfg(not(target_family = "wasm"))]
2006 dependency: Arc<TaskDependency>,
2007 },
2008 #[cfg(not(target_family = "wasm"))]
2009 Blocking {
2010 receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2011 dependency: BlockingDependency,
2012 published: PublishedBlockingDependencies,
2015 },
2016 Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2017}
2018
2019pub struct JoinHandle<T>(JoinHandleInner<T>);
2020
2021impl<T> Unpin for JoinHandle<T> {}
2022
2023impl<T> JoinHandle<T> {
2024 fn detach_task(&mut self) {
2025 let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2026 match inner {
2027 JoinHandleInner::Task {
2028 task,
2029 awaiter: _,
2030 #[cfg(not(target_family = "wasm"))]
2031 dependency,
2032 } => {
2033 #[cfg(not(target_family = "wasm"))]
2034 let current_dependencies = dependency.snapshot_all_live();
2035 #[cfg(not(target_family = "wasm"))]
2036 dependency.clear_waiter();
2037 task.detach();
2038 #[cfg(not(target_family = "wasm"))]
2039 let _ = catch_unwind_contained(|| {
2043 clear_propagated_blocking_dependencies(¤t_dependencies);
2044 });
2045 }
2046 #[cfg(not(target_family = "wasm"))]
2047 JoinHandleInner::Blocking {
2048 receiver,
2049 dependency: _,
2050 published,
2051 } => {
2052 let _ = catch_unwind_contained(|| drop(receiver));
2056 let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2059 }
2060 JoinHandleInner::Ready(result) => {
2061 let _ = catch_unwind_contained(|| drop(result));
2062 }
2063 }
2064 }
2065
2066 pub fn detach(mut self) {
2067 self.detach_task();
2068 }
2069}
2070
2071impl<T> Drop for JoinHandle<T> {
2072 fn drop(&mut self) {
2073 self.detach_task();
2077 }
2078}
2079
2080impl<T> Future for JoinHandle<T> {
2081 type Output = Result<T, JoinError>;
2082
2083 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2084 match &mut self.get_mut().0 {
2085 JoinHandleInner::Task {
2086 task,
2087 awaiter: awaiter_slot,
2088 #[cfg(not(target_family = "wasm"))]
2089 dependency,
2090 } => {
2091 let awaiter = match awaiter_slot {
2092 Some(awaiter) => {
2093 awaiter.update(cx.waker());
2094 awaiter
2095 }
2096 None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2097 };
2098 let contained_waker = awaiter.contained();
2099 let mut contained_cx = Context::from_waker(contained_waker);
2100 let poll = Pin::new(task).poll(&mut contained_cx);
2101 #[cfg(not(target_family = "wasm"))]
2102 if poll.is_pending() {
2103 for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2104 propagate_blocking_dependency(current_dependency);
2105 }
2106 } else {
2107 dependency.clear_waiter();
2108 }
2109 if poll.is_ready() {
2110 *awaiter_slot = None;
2111 }
2112 match poll {
2113 Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2114 Poll::Ready(None) => Poll::Ready(Err(JoinError {
2115 message: "async runtime stopped before the task completed".to_string(),
2116 })),
2117 Poll::Pending => Poll::Pending,
2118 }
2119 }
2120 #[cfg(not(target_family = "wasm"))]
2121 JoinHandleInner::Blocking {
2122 receiver,
2123 dependency,
2124 published,
2125 } => {
2126 let poll = Pin::new(receiver).poll(cx);
2127 if poll.is_pending() {
2128 record_blocking_dependency(published, *dependency);
2129 }
2130 match poll {
2131 Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2132 Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2133 message: "async runtime stopped before the blocking task completed".to_string(),
2134 })),
2135 Poll::Pending => Poll::Pending,
2136 }
2137 }
2138 JoinHandleInner::Ready(result) => Poll::Ready(
2139 result
2140 .take()
2141 .expect("JoinHandle polled after completion")
2142 .map(ContainedTaskOutput::into_inner),
2143 ),
2144 }
2145 }
2146}
2147
2148#[derive(Debug, Clone, Copy)]
2149pub struct RuntimeMetricsSnapshot {
2150 pub flavor: RuntimeFlavor,
2151 pub worker_threads: usize,
2152 pub max_blocking_tasks: usize,
2153 pub tasks_spawned: u64,
2154 pub tasks_completed: u64,
2155 pub tasks_panicked: u64,
2156 pub runnable_schedules: u64,
2157 pub runnable_polls: u64,
2158 pub queued_runnables: u64,
2159 pub max_queued_runnables: u64,
2160 pub active_runnables: u64,
2161 pub max_active_runnables: u64,
2162 pub blocking_tasks_started: u64,
2163 pub blocking_tasks_completed: u64,
2164 pub active_blocking_tasks: u64,
2165 pub max_active_blocking_tasks: u64,
2166}
2167
2168#[derive(Default)]
2169struct RuntimeMetrics {
2170 tasks_spawned: AtomicU64,
2171 tasks_completed: AtomicU64,
2172 tasks_panicked: AtomicU64,
2173 runnable_schedules: AtomicU64,
2174 runnable_polls: AtomicU64,
2175 queued_runnables: AtomicU64,
2176 max_queued_runnables: AtomicU64,
2177 active_runnables: AtomicU64,
2178 max_active_runnables: AtomicU64,
2179 blocking_tasks_scheduled: AtomicU64,
2185 blocking_tasks_started: AtomicU64,
2186 blocking_tasks_completed: AtomicU64,
2187 active_blocking_tasks: AtomicU64,
2188 max_active_blocking_tasks: AtomicU64,
2189 reset_generation: AtomicU64,
2193 reset_lock: Mutex<()>,
2194}
2195
2196#[cfg(test)]
2197thread_local! {
2198 static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2199 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2200 const { std::cell::RefCell::new(None) };
2201}
2202
2203#[cfg(test)]
2204fn run_runtime_metrics_high_water_test_hook() {
2205 if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2206 hook();
2207 }
2208}
2209
2210impl RuntimeMetrics {
2211 fn runnable_scheduled(&self) {
2212 self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2213 let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2214 #[cfg(test)]
2215 run_runtime_metrics_high_water_test_hook();
2216 self
2217 .max_queued_runnables
2218 .fetch_max(queued, Ordering::Relaxed);
2219 }
2220
2221 fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2222 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2223 self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2224 let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2225 #[cfg(test)]
2226 run_runtime_metrics_high_water_test_hook();
2227 self
2228 .max_active_runnables
2229 .fetch_max(active, Ordering::Relaxed);
2230 ActiveRunnableGuard { metrics: self }
2231 }
2232
2233 #[cfg(any(not(target_family = "wasm"), test))]
2234 fn runnable_cancelled(&self) {
2235 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2236 }
2237
2238 fn blocking_scheduled(&self) {
2242 self
2243 .blocking_tasks_scheduled
2244 .fetch_add(1, Ordering::Relaxed);
2245 }
2246
2247 fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2248 self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2249 if count_active_lane {
2250 let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2251 #[cfg(test)]
2252 run_runtime_metrics_high_water_test_hook();
2253 self
2254 .max_active_blocking_tasks
2255 .fetch_max(active, Ordering::Relaxed);
2256 }
2257 BlockingTaskGuard {
2258 metrics: Arc::clone(self),
2259 count_active_lane,
2260 }
2261 }
2262
2263 fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2264 let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2265 let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2266 let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2267 RuntimeMetricsSnapshot {
2268 flavor: options.flavor,
2269 worker_threads: options.worker_threads,
2270 max_blocking_tasks: options.max_blocking_tasks,
2271 tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2272 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2273 tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2274 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2275 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2276 queued_runnables,
2277 max_queued_runnables: self
2278 .max_queued_runnables
2279 .load(Ordering::Relaxed)
2280 .max(queued_runnables),
2281 active_runnables,
2282 max_active_runnables: self
2283 .max_active_runnables
2284 .load(Ordering::Relaxed)
2285 .max(active_runnables),
2286 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2287 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2288 active_blocking_tasks,
2289 max_active_blocking_tasks: self
2290 .max_active_blocking_tasks
2291 .load(Ordering::Relaxed)
2292 .max(active_blocking_tasks),
2293 }
2294 }
2295
2296 fn progress_fingerprint(&self) -> ProgressFingerprint {
2305 loop {
2306 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2307 if !reset_generation.is_multiple_of(2) {
2308 std::hint::spin_loop();
2309 continue;
2310 }
2311 let fingerprint = ProgressFingerprint {
2312 reset_generation,
2313 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2314 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2315 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2316 blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2317 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2318 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2319 };
2320 if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2321 return fingerprint;
2322 }
2323 }
2324 }
2325
2326 fn reset(&self) {
2327 let reset_guard = self
2328 .reset_lock
2329 .lock()
2330 .unwrap_or_else(std::sync::PoisonError::into_inner);
2331 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2339 let Some(resetting_generation) = reset_generation.checked_add(1) else {
2340 drop(reset_guard);
2341 panic!("runtime metrics reset generation space exhausted");
2342 };
2343 let Some(settled_generation) = resetting_generation.checked_add(1) else {
2344 drop(reset_guard);
2345 panic!("runtime metrics reset generation space exhausted");
2346 };
2347 self
2348 .reset_generation
2349 .store(resetting_generation, Ordering::SeqCst);
2350 self.tasks_spawned.store(0, Ordering::Relaxed);
2351 self.tasks_completed.store(0, Ordering::Relaxed);
2352 self.tasks_panicked.store(0, Ordering::Relaxed);
2353 self.runnable_schedules.store(0, Ordering::Relaxed);
2354 self.runnable_polls.store(0, Ordering::Relaxed);
2355 self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2356 self.blocking_tasks_started.store(0, Ordering::Relaxed);
2357 self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2358 self
2359 .reset_generation
2360 .store(settled_generation, Ordering::SeqCst);
2361 }
2362}
2363
2364#[derive(Clone, Copy, PartialEq, Eq)]
2366struct ProgressFingerprint {
2367 reset_generation: u64,
2368 runnable_schedules: u64,
2369 runnable_polls: u64,
2370 tasks_completed: u64,
2371 blocking_tasks_scheduled: u64,
2372 blocking_tasks_started: u64,
2373 blocking_tasks_completed: u64,
2374}
2375
2376struct ActiveRunnableGuard<'a> {
2377 metrics: &'a RuntimeMetrics,
2378}
2379
2380impl Drop for ActiveRunnableGuard<'_> {
2381 fn drop(&mut self) {
2382 self
2383 .metrics
2384 .active_runnables
2385 .fetch_sub(1, Ordering::Relaxed);
2386 }
2387}
2388
2389struct BlockingTaskGuard {
2390 metrics: Arc<RuntimeMetrics>,
2391 count_active_lane: bool,
2392}
2393
2394impl Drop for BlockingTaskGuard {
2395 fn drop(&mut self) {
2396 if self.count_active_lane {
2397 self
2398 .metrics
2399 .active_blocking_tasks
2400 .fetch_sub(1, Ordering::Relaxed);
2401 }
2402 self
2403 .metrics
2404 .blocking_tasks_completed
2405 .fetch_add(1, Ordering::Relaxed);
2406 }
2407}
2408
2409static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2410
2411fn next_runtime_generation() -> u64 {
2412 next_unique_id(
2413 &NEXT_RUNTIME_GENERATION,
2414 "async runtime generation id space exhausted",
2415 )
2416}
2417
2418thread_local! {
2419 static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2420 const { std::cell::Cell::new(None) };
2421}
2422
2423fn active_runtime_generation() -> Option<u64> {
2424 ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2425}
2426
2427struct RuntimeGenerationGuard {
2428 previous: Option<u64>,
2429}
2430
2431impl RuntimeGenerationGuard {
2432 fn enter(generation: u64) -> Self {
2433 Self::enter_optional(Some(generation))
2434 }
2435
2436 fn enter_optional(generation: Option<u64>) -> Self {
2437 Self {
2438 previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2439 }
2440 }
2441}
2442
2443impl Drop for RuntimeGenerationGuard {
2444 fn drop(&mut self) {
2445 ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2446 }
2447}
2448
2449struct ContainedFuture<F> {
2450 future: ManuallyDrop<F>,
2451 generation: u64,
2452}
2453
2454impl<F> ContainedFuture<F> {
2455 fn new(future: F, generation: u64) -> Self {
2456 Self {
2457 future: ManuallyDrop::new(future),
2458 generation,
2459 }
2460 }
2461}
2462
2463impl<F: Future> Future for ContainedFuture<F> {
2464 type Output = F::Output;
2465
2466 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2467 let this = unsafe { self.get_unchecked_mut() };
2469 let _generation = RuntimeGenerationGuard::enter(this.generation);
2470 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2473 }
2474}
2475
2476impl<F> Drop for ContainedFuture<F> {
2477 fn drop(&mut self) {
2478 let _generation = RuntimeGenerationGuard::enter(self.generation);
2479 let _ = catch_unwind_contained(|| {
2480 unsafe { ManuallyDrop::drop(&mut self.future) };
2482 });
2483 }
2484}
2485
2486#[derive(Default)]
2487struct GenerationStop {
2488 stopping: AtomicBool,
2489 publication: Mutex<()>,
2490 parkers: Mutex<Vec<Weak<DriverParker>>>,
2491}
2492
2493impl GenerationStop {
2494 fn is_stopping(&self) -> bool {
2495 self.stopping.load(Ordering::Acquire)
2496 }
2497
2498 fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2499 #[cfg(all(test, not(target_family = "wasm")))]
2500 run_before_generation_stop_publication_lock_test_hook();
2501 self.runnable_claim_guard()
2502 }
2503
2504 fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2505 GenerationStopPublicationGuard {
2506 stop: self,
2507 _publication: self
2508 .publication
2509 .lock()
2510 .unwrap_or_else(std::sync::PoisonError::into_inner),
2511 }
2512 }
2513
2514 fn publish_stopping(&self) {
2515 self.publication_guard().publish_stopping();
2516 }
2517
2518 fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2519 {
2520 let mut parkers = self
2521 .parkers
2522 .lock()
2523 .unwrap_or_else(std::sync::PoisonError::into_inner);
2524 parkers.retain(|candidate| candidate.strong_count() != 0);
2525 parkers.push(Arc::downgrade(parker));
2526 }
2527 if self.is_stopping() {
2528 parker.unpark();
2529 }
2530 GenerationStopGuard {
2531 stop: Arc::clone(self),
2532 parker: Arc::downgrade(parker),
2533 }
2534 }
2535
2536 fn wake_all(&self) -> usize {
2541 let mut count = 0;
2542 let mut registered = self
2543 .parkers
2544 .lock()
2545 .unwrap_or_else(std::sync::PoisonError::into_inner);
2546 registered.retain(|candidate| {
2547 let Some(parker) = candidate.upgrade() else {
2548 return false;
2549 };
2550 count += 1;
2551 parker.unpark();
2554 true
2555 });
2556 count
2557 }
2558
2559 fn begin_shutdown(&self) {
2560 self.publish_stopping();
2561 let parkers = {
2562 let mut registered = self
2563 .parkers
2564 .lock()
2565 .unwrap_or_else(std::sync::PoisonError::into_inner);
2566 let parkers = registered
2567 .iter()
2568 .filter_map(Weak::upgrade)
2569 .collect::<Vec<_>>();
2570 registered.clear();
2571 parkers
2572 };
2573 for parker in parkers {
2574 parker.unpark();
2575 }
2576 }
2577}
2578
2579struct GenerationStopPublicationGuard<'a> {
2580 stop: &'a GenerationStop,
2581 _publication: std::sync::MutexGuard<'a, ()>,
2582}
2583
2584impl GenerationStopPublicationGuard<'_> {
2585 fn is_stopping(&self) -> bool {
2586 self.stop.is_stopping()
2587 }
2588
2589 fn publish_stopping(&self) {
2590 self.stop.stopping.store(true, Ordering::Release);
2591 }
2592}
2593
2594struct GenerationStopGuard {
2595 stop: Arc<GenerationStop>,
2596 parker: Weak<DriverParker>,
2597}
2598
2599impl Drop for GenerationStopGuard {
2600 fn drop(&mut self) {
2601 let Some(parker) = self.parker.upgrade() else {
2602 return;
2603 };
2604 let mut registered = self
2605 .stop
2606 .parkers
2607 .lock()
2608 .unwrap_or_else(std::sync::PoisonError::into_inner);
2609 if let Some(index) = registered.iter().position(|candidate| {
2610 candidate
2611 .upgrade()
2612 .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2613 }) {
2614 registered.swap_remove(index);
2615 }
2616 }
2617}
2618
2619struct GenerationWorkState {
2620 closed: bool,
2621 next_task_id: u64,
2622 active: usize,
2623 abort_handles: FxHashMap<u64, AbortHandle>,
2624}
2625
2626struct GenerationWork {
2627 id: u64,
2628 state: Mutex<GenerationWorkState>,
2629 idle: Condvar,
2630}
2631
2632impl GenerationWork {
2633 fn new() -> Arc<Self> {
2634 Arc::new(Self {
2635 id: next_runtime_generation(),
2636 state: Mutex::new(GenerationWorkState {
2637 closed: false,
2638 next_task_id: 0,
2639 active: 0,
2640 abort_handles: FxHashMap::default(),
2641 }),
2642 idle: Condvar::new(),
2643 })
2644 }
2645
2646 fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2647 let (abort_handle, abort_registration) = AbortHandle::new_pair();
2648 let mut state = self
2649 .state
2650 .lock()
2651 .unwrap_or_else(std::sync::PoisonError::into_inner);
2652 if state.closed {
2653 return None;
2654 }
2655 let task_id = state.next_task_id;
2656 let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2657 drop(state);
2658 panic!("async runtime generation task id space exhausted");
2659 };
2660 state.next_task_id = next_task_id;
2661 state.active += 1;
2662 state.abort_handles.insert(task_id, abort_handle);
2663 Some((
2664 abort_registration,
2665 GenerationWorkGuard {
2666 work: Arc::clone(self),
2667 task_id: Some(task_id),
2668 },
2669 ))
2670 }
2671
2672 fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2673 let mut state = self
2674 .state
2675 .lock()
2676 .unwrap_or_else(std::sync::PoisonError::into_inner);
2677 if state.closed {
2678 return None;
2679 }
2680 state.active += 1;
2681 Some(GenerationWorkGuard {
2682 work: Arc::clone(self),
2683 task_id: None,
2684 })
2685 }
2686
2687 fn close_and_abort(&self) {
2688 let abort_handles = {
2689 let mut state = self
2690 .state
2691 .lock()
2692 .unwrap_or_else(std::sync::PoisonError::into_inner);
2693 state.closed = true;
2694 state.abort_handles.values().cloned().collect::<Vec<_>>()
2695 };
2696 for abort_handle in abort_handles {
2697 abort_handle.abort();
2698 }
2699 }
2700
2701 fn wait_until_idle(&self) {
2702 let mut state = self
2703 .state
2704 .lock()
2705 .unwrap_or_else(std::sync::PoisonError::into_inner);
2706 while state.active != 0 {
2707 state = self
2708 .idle
2709 .wait(state)
2710 .unwrap_or_else(std::sync::PoisonError::into_inner);
2711 }
2712 }
2713}
2714
2715struct GenerationWorkGuard {
2716 work: Arc<GenerationWork>,
2717 task_id: Option<u64>,
2718}
2719
2720impl Drop for GenerationWorkGuard {
2721 fn drop(&mut self) {
2722 let mut state = self
2723 .work
2724 .state
2725 .lock()
2726 .unwrap_or_else(std::sync::PoisonError::into_inner);
2727 if let Some(task_id) = self.task_id {
2728 state.abort_handles.remove(&task_id);
2729 }
2730 state.active = state
2731 .active
2732 .checked_sub(1)
2733 .expect("generation work count underflow");
2734 if state.active == 0 {
2735 self.work.idle.notify_all();
2736 }
2737 }
2738}
2739
2740struct RegisteredTaskFuture<F> {
2746 future: ManuallyDrop<F>,
2747 registration: Option<GenerationWorkGuard>,
2748 generation: u64,
2749}
2750
2751impl<F> RegisteredTaskFuture<F> {
2752 fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2753 Self {
2754 future: ManuallyDrop::new(future),
2755 registration: Some(registration),
2756 generation,
2757 }
2758 }
2759}
2760
2761impl<F: Future> Future for RegisteredTaskFuture<F> {
2762 type Output = F::Output;
2763
2764 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2765 let this = unsafe { self.get_unchecked_mut() };
2767 let _generation = RuntimeGenerationGuard::enter(this.generation);
2768 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2771 }
2772}
2773
2774impl<F> Drop for RegisteredTaskFuture<F> {
2775 fn drop(&mut self) {
2776 let _generation = RuntimeGenerationGuard::enter(self.generation);
2777 let _ = catch_unwind_contained(|| {
2778 unsafe { ManuallyDrop::drop(&mut self.future) };
2780 });
2781 drop(self.registration.take());
2784 }
2785}
2786
2787#[cfg(not(target_family = "wasm"))]
2788struct RegisteredBlockingFunction<F> {
2789 function: Option<F>,
2790 registration: Option<GenerationWorkGuard>,
2791 generation: u64,
2792}
2793
2794#[cfg(not(target_family = "wasm"))]
2795impl<F> RegisteredBlockingFunction<F> {
2796 fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2797 Self {
2798 function: Some(function),
2799 registration: Some(registration),
2800 generation,
2801 }
2802 }
2803
2804 fn run<T>(mut self) -> Result<T, JoinError>
2805 where
2806 F: FnOnce() -> T,
2807 {
2808 let _generation = RuntimeGenerationGuard::enter(self.generation);
2809 let result = catch_unwind_join_error(
2810 self
2811 .function
2812 .take()
2813 .expect("blocking function must be available"),
2814 );
2815 drop(self.registration.take());
2816 result
2817 }
2818}
2819
2820#[cfg(not(target_family = "wasm"))]
2821impl<F> Drop for RegisteredBlockingFunction<F> {
2822 fn drop(&mut self) {
2823 let _generation = RuntimeGenerationGuard::enter(self.generation);
2824 if let Some(function) = self.function.take() {
2825 let _ = catch_unwind_contained(|| drop(function));
2826 }
2827 drop(self.registration.take());
2831 }
2832}
2833
2834fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2835 let _active = metrics.runnable_started();
2836 let _ = catch_unwind_contained(|| runnable.run());
2837}
2838
2839#[cfg(not(target_family = "wasm"))]
2840enum RunnableClaim {
2841 Empty,
2842 Run(Runnable),
2843 Cancel(Runnable),
2844}
2845
2846static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2847static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2848
2849fn next_current_thread_dispatch_id() -> u64 {
2850 next_unique_id(
2851 &NEXT_CURRENT_THREAD_DISPATCH_ID,
2852 "CurrentThread dispatch id space exhausted",
2853 )
2854 .checked_add(1)
2855 .expect("CurrentThread dispatch id space exhausted")
2856}
2857
2858struct CurrentThreadBlockingAdmission {
2859 state: Mutex<CurrentThreadBlockingAdmissionState>,
2860}
2861
2862struct CurrentThreadBlockingAdmissionState {
2863 owner: Option<BlockingOwnerToken>,
2864 closed: bool,
2865 #[cfg(not(target_family = "wasm"))]
2866 queue: BlockingQueue,
2867}
2868
2869struct CurrentThreadExecutor {
2870 id: u64,
2871 generation: u64,
2872 stop: Arc<GenerationStop>,
2873 queue: Mutex<CurrentThreadQueue>,
2874 blocking_admission: CurrentThreadBlockingAdmission,
2875 draining: AtomicBool,
2876 scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2877 scheduler_idle: Condvar,
2878 dispatch_pending: AtomicU64,
2882 recovery_dispatch: AtomicU64,
2886 cancelling_failed_dispatch: AtomicBool,
2889 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2890 task_drivers: &'static CurrentThreadTaskDriverRegistry,
2891 metrics: Arc<RuntimeMetrics>,
2892 threadless: bool,
2900 park_deadline: Option<Duration>,
2901 host_timers: Arc<HostTimerRegistry>,
2911}
2912
2913struct CurrentThreadQueue {
2914 closed: bool,
2915 runnables: VecDeque<Runnable>,
2916}
2917
2918#[derive(Default)]
2919struct CurrentThreadSchedulerState {
2920 active_host_turns: usize,
2921 active_dispatch_calls: usize,
2922 dispatch_publications: Vec<CurrentThreadDispatchPublication>,
2923}
2924
2925struct CurrentThreadDispatchPublication {
2926 dispatch: u64,
2927 republish_requested: bool,
2928}
2929
2930#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2931enum CurrentThreadDispatchRequest {
2932 Accepted,
2933 Coalesced,
2934 Unavailable,
2935}
2936
2937#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2938struct CurrentThreadTaskDispatchFailure {
2939 dispatch: u64,
2940 epoch: u64,
2941}
2942
2943#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2944enum CurrentThreadHostDispatchResult {
2945 Accepted,
2946 Unavailable,
2947 Failed(CurrentThreadTaskDispatchFailure),
2948}
2949
2950enum CurrentThreadDispatchPublicationStep {
2951 Republish,
2952 Complete(Option<CurrentThreadHostDispatchResult>),
2953}
2954
2955struct CurrentThreadHostTurn {
2956 executor: Arc<CurrentThreadExecutor>,
2957 draining: bool,
2958 _generation: RuntimeGenerationGuard,
2959}
2960
2961impl CurrentThreadHostTurn {
2962 fn drive(&mut self) {
2963 let executor = Arc::clone(&self.executor);
2964 executor.drive_admitted_host_turn(self);
2965 }
2966
2967 fn release_draining(&mut self) {
2968 if !self.draining {
2969 return;
2970 }
2971 let _scheduler = self
2972 .executor
2973 .scheduler_idle_lock
2974 .lock()
2975 .unwrap_or_else(std::sync::PoisonError::into_inner);
2976 self.executor.draining.store(false, Ordering::Release);
2977 self.draining = false;
2978 }
2979}
2980
2981impl Drop for CurrentThreadHostTurn {
2982 fn drop(&mut self) {
2983 let mut scheduler = self
2984 .executor
2985 .scheduler_idle_lock
2986 .lock()
2987 .unwrap_or_else(std::sync::PoisonError::into_inner);
2988 if self.draining {
2989 self.executor.draining.store(false, Ordering::Release);
2990 self.draining = false;
2991 }
2992 scheduler.active_host_turns = scheduler
2993 .active_host_turns
2994 .checked_sub(1)
2995 .expect("CurrentThread active host-turn count underflow");
2996 self.executor.scheduler_idle.notify_all();
2997 }
2998}
2999
3000#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3007pub struct CurrentThreadTaskCallbackLease {
3008 _host_turn: CurrentThreadHostTurn,
3009 _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3010}
3011
3012impl CurrentThreadTaskCallbackLease {
3013 fn new(host_turn: CurrentThreadHostTurn) -> Self {
3014 Self {
3015 _host_turn: host_turn,
3016 _not_send: std::marker::PhantomData,
3017 }
3018 }
3019}
3020
3021struct CurrentThreadDispatchCall<'a> {
3022 executor: &'a CurrentThreadExecutor,
3023 publication: Option<u64>,
3024 _generation: RuntimeGenerationGuard,
3025}
3026
3027impl CurrentThreadDispatchCall<'_> {
3028 fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3029 let dispatch = self
3030 .publication
3031 .expect("the CurrentThread dispatch call must own a publication");
3032 let index = scheduler
3033 .dispatch_publications
3034 .iter()
3035 .position(|publication| publication.dispatch == dispatch)
3036 .expect("the CurrentThread dispatch publication must remain registered");
3037 scheduler.dispatch_publications.swap_remove(index);
3038 self.publication = None;
3039 }
3040}
3041
3042impl Drop for CurrentThreadDispatchCall<'_> {
3043 fn drop(&mut self) {
3044 let mut scheduler = self
3045 .executor
3046 .scheduler_idle_lock
3047 .lock()
3048 .unwrap_or_else(std::sync::PoisonError::into_inner);
3049 if let Some(dispatch) = self.publication.take()
3050 && let Some(index) = scheduler
3051 .dispatch_publications
3052 .iter()
3053 .position(|publication| publication.dispatch == dispatch)
3054 {
3055 scheduler.dispatch_publications.swap_remove(index);
3056 }
3057 scheduler.active_dispatch_calls = scheduler
3058 .active_dispatch_calls
3059 .checked_sub(1)
3060 .expect("CurrentThread active dispatch-call count underflow");
3061 self.executor.scheduler_idle.notify_all();
3062 }
3063}
3064
3065struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3066
3067impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3068 fn drop(&mut self) {
3069 self
3070 .0
3071 .cancelling_failed_dispatch
3072 .store(false, Ordering::Release);
3073 }
3074}
3075
3076enum CurrentThreadDispatchFailureAction<'a> {
3077 None,
3078 Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3079 Publish {
3080 dispatch: u64,
3081 call: CurrentThreadDispatchCall<'a>,
3082 },
3083}
3084
3085struct CurrentThreadDispatchFailureRecovery<'a> {
3086 action: CurrentThreadDispatchFailureAction<'a>,
3087 _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3088}
3089
3090struct CurrentThreadRegistryCancellationGuard<'a> {
3091 registry: &'a CurrentThreadTaskDriverRegistry,
3092 failure: Option<CurrentThreadTaskDispatchFailure>,
3093}
3094
3095impl CurrentThreadRegistryCancellationGuard<'_> {
3096 fn new(
3097 registry: &CurrentThreadTaskDriverRegistry,
3098 failure: CurrentThreadTaskDispatchFailure,
3099 ) -> CurrentThreadRegistryCancellationGuard<'_> {
3100 CurrentThreadRegistryCancellationGuard {
3101 registry,
3102 failure: Some(failure),
3103 }
3104 }
3105}
3106
3107impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3108 fn drop(&mut self) {
3109 if let Some(failure) = self.failure.take() {
3110 let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3111 }
3112 }
3113}
3114
3115struct CurrentThreadBlockingGuard<'a> {
3116 executor: &'a CurrentThreadExecutor,
3117 owner: BlockingOwnerToken,
3118 previous_owner: Option<BlockingOwnerToken>,
3119 owns_lane: bool,
3120}
3121
3122impl CurrentThreadBlockingGuard<'_> {
3123 fn counts_active_lane(&self) -> bool {
3124 self.owns_lane
3125 }
3126}
3127
3128impl Drop for CurrentThreadBlockingGuard<'_> {
3129 fn drop(&mut self) {
3130 BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3131 if !self.owns_lane {
3132 return;
3133 }
3134 let mut state = self
3135 .executor
3136 .blocking_admission
3137 .state
3138 .lock()
3139 .unwrap_or_else(std::sync::PoisonError::into_inner);
3140 debug_assert_eq!(state.owner, Some(self.owner));
3141 state.owner = None;
3142 drop(state);
3143 self.executor.stop.wake_all();
3144 }
3145}
3146
3147impl CurrentThreadExecutor {
3148 const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3151 #[cfg(not(target_family = "wasm"))]
3153 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3154
3155 #[cfg(test)]
3159 fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3160 Self::with_detection(metrics, THREADLESS_BUILD, None)
3161 }
3162
3163 #[cfg(test)]
3169 fn with_detection(
3170 metrics: Arc<RuntimeMetrics>,
3171 threadless: bool,
3172 park_deadline: Option<Duration>,
3173 ) -> Self {
3174 Self::with_generation(
3175 metrics,
3176 threadless,
3177 park_deadline,
3178 u64::MAX,
3179 Arc::new(GenerationStop::default()),
3180 )
3181 }
3182
3183 fn with_generation(
3184 metrics: Arc<RuntimeMetrics>,
3185 threadless: bool,
3186 park_deadline: Option<Duration>,
3187 generation: u64,
3188 stop: Arc<GenerationStop>,
3189 ) -> Self {
3190 Self {
3191 id: next_unique_id(
3192 &NEXT_RUNTIME_EXECUTOR_ID,
3193 "async runtime executor id space exhausted",
3194 ),
3195 generation,
3196 stop,
3197 queue: Mutex::new(CurrentThreadQueue {
3198 closed: false,
3199 runnables: VecDeque::new(),
3200 }),
3201 blocking_admission: CurrentThreadBlockingAdmission {
3202 state: Mutex::new(CurrentThreadBlockingAdmissionState {
3203 owner: None,
3204 closed: false,
3205 #[cfg(not(target_family = "wasm"))]
3206 queue: BlockingQueue {
3207 closed: false,
3208 head: None,
3209 tail: None,
3210 jobs: FxHashMap::default(),
3211 },
3212 }),
3213 },
3214 draining: AtomicBool::new(false),
3215 scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3216 scheduler_idle: Condvar::new(),
3217 dispatch_pending: AtomicU64::new(0),
3218 recovery_dispatch: AtomicU64::new(0),
3219 cancelling_failed_dispatch: AtomicBool::new(false),
3220 task_dispatch: dispatch_current_thread_tasks,
3221 task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3222 metrics,
3223 threadless,
3224 park_deadline,
3225 host_timers: Arc::new(HostTimerRegistry::default()),
3226 }
3227 }
3228
3229 fn fresh_owner_token(&self) -> BlockingOwnerToken {
3230 BlockingOwnerToken {
3231 executor_id: self.id,
3232 frame: next_unique_id(
3233 &NEXT_BLOCKING_FRAME,
3234 "blocking owner frame id space exhausted",
3235 ),
3236 }
3237 }
3238
3239 fn enter_blocking_owner(
3240 &self,
3241 owner: BlockingOwnerToken,
3242 owns_lane: bool,
3243 ) -> CurrentThreadBlockingGuard<'_> {
3244 let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3245 CurrentThreadBlockingGuard {
3246 executor: self,
3247 owner,
3248 previous_owner,
3249 owns_lane,
3250 }
3251 }
3252
3253 fn schedule_blocking<F, T>(
3254 self: &Arc<Self>,
3255 function: F,
3256 registration: GenerationWorkGuard,
3257 ) -> JoinHandle<T>
3258 where
3259 F: FnOnce() -> T + Send + 'static,
3260 T: Send + 'static,
3261 {
3262 self.metrics.blocking_scheduled();
3264 let mut function = Some(function);
3265 let mut registration = Some(registration);
3266 let ambient_owner = BLOCKING_OWNER
3267 .with(std::cell::Cell::get)
3268 .filter(|owner| owner.executor_id == self.id);
3269 let mut run_owner = None;
3270 let mut owns_lane = false;
3271 let mut rejected = false;
3272
3273 #[cfg(not(target_family = "wasm"))]
3274 let (sender, receiver) = oneshot::channel();
3275 #[cfg(not(target_family = "wasm"))]
3276 let dependency = BlockingDependency {
3277 job: BlockingJobId(next_unique_id(
3278 &NEXT_BLOCKING_JOB_ID,
3279 "blocking job id space exhausted",
3280 )),
3281 owner: ambient_owner,
3282 };
3283 #[cfg(not(target_family = "wasm"))]
3284 let mut queued = false;
3285
3286 {
3287 let mut state = self
3288 .blocking_admission
3289 .state
3290 .lock()
3291 .unwrap_or_else(std::sync::PoisonError::into_inner);
3292 #[cfg(all(test, not(target_family = "wasm")))]
3293 run_current_thread_blocking_claim_test_hook();
3294 if self.stop.is_stopping()
3298 || state.closed
3299 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3300 {
3301 rejected = true;
3302 } else if ambient_owner.is_some() && state.owner == ambient_owner {
3303 run_owner = ambient_owner;
3304 } else {
3305 #[cfg(not(target_family = "wasm"))]
3306 let queue_empty = state.queue.is_empty();
3307 #[cfg(target_family = "wasm")]
3308 let queue_empty = true;
3309
3310 if state.owner.is_none() && queue_empty {
3311 let owner = self.fresh_owner_token();
3312 state.owner = Some(owner);
3313 run_owner = Some(owner);
3314 owns_lane = true;
3315 } else {
3316 #[cfg(not(target_family = "wasm"))]
3317 {
3318 let function = function
3319 .take()
3320 .expect("blocking function must be available");
3321 let registration = registration
3322 .take()
3323 .expect("blocking registration must be available");
3324 let generation = self.generation;
3325 let function = RegisteredBlockingFunction::new(function, registration, generation);
3326 state.queue.push(QueuedBlocking {
3327 id: dependency.job,
3328 run: Box::new(move || {
3329 let _generation = RuntimeGenerationGuard::enter(generation);
3330 let result = function
3331 .run()
3332 .map(|output| ContainedTaskOutput::new(output, generation));
3333 let _ = sender.send(result);
3334 }),
3335 });
3336 queued = true;
3337 }
3338 #[cfg(target_family = "wasm")]
3339 {
3340 rejected = true;
3344 }
3345 }
3346 }
3347 }
3348
3349 #[cfg(not(target_family = "wasm"))]
3350 if queued {
3351 self.stop.wake_all();
3352 if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3353 self.request_drain();
3354 }
3355 return JoinHandle(JoinHandleInner::Blocking {
3356 receiver,
3357 dependency,
3358 published: PublishedBlockingDependencies::default(),
3359 });
3360 }
3361
3362 if rejected {
3363 let _generation = RuntimeGenerationGuard::enter(self.generation);
3364 let _ = catch_unwind_contained(|| drop(function.take()));
3365 drop(registration.take());
3366 return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3367 message: "the async runtime stopped before the blocking task could start".to_string(),
3368 }))));
3369 }
3370
3371 let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3372 let blocking = self.enter_blocking_owner(owner, owns_lane);
3373 let _registration = registration
3374 .take()
3375 .expect("blocking registration must be available");
3376 let _generation = RuntimeGenerationGuard::enter(self.generation);
3377 let result = {
3378 let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3379 catch_unwind_join_error(
3380 function
3381 .take()
3382 .expect("blocking function must be available"),
3383 )
3384 }
3385 .map(|output| ContainedTaskOutput::new(output, self.generation));
3386 drop(blocking);
3387 #[cfg(not(target_family = "wasm"))]
3388 if owns_lane && !self.draining.load(Ordering::Acquire) {
3389 self.service_queued_blocking_work();
3393 }
3394 JoinHandle(JoinHandleInner::Ready(Some(result)))
3395 }
3396
3397 #[cfg(not(target_family = "wasm"))]
3398 fn take_queued_blocking(
3399 &self,
3400 ) -> Option<(
3401 Box<dyn FnOnce() + Send + 'static>,
3402 CurrentThreadBlockingGuard<'_>,
3403 )> {
3404 let (job, owner) = {
3405 let mut state = self
3406 .blocking_admission
3407 .state
3408 .lock()
3409 .unwrap_or_else(std::sync::PoisonError::into_inner);
3410 #[cfg(all(test, not(target_family = "wasm")))]
3411 run_current_thread_blocking_claim_test_hook();
3412 if self.stop.is_stopping()
3413 || state.closed
3414 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3415 || state.owner.is_some()
3416 {
3417 return None;
3418 }
3419 let job = state.queue.pop_front()?;
3420 let owner = self.fresh_owner_token();
3421 state.owner = Some(owner);
3422 (job, owner)
3423 };
3424 Some((job, self.enter_blocking_owner(owner, true)))
3425 }
3426
3427 #[cfg(not(target_family = "wasm"))]
3428 fn run_one_blocking(&self) -> bool {
3429 let Some((job, blocking)) = self.take_queued_blocking() else {
3430 return false;
3431 };
3432 let _generation = RuntimeGenerationGuard::enter(self.generation);
3433 {
3434 let _task = self.metrics.blocking_started(true);
3435 let _ = catch_unwind_contained(job);
3436 }
3437 drop(blocking);
3438 true
3439 }
3440
3441 #[cfg(not(target_family = "wasm"))]
3442 fn service_queued_blocking_work(&self) {
3443 while self.run_one_blocking() {}
3444 }
3445
3446 #[cfg(not(target_family = "wasm"))]
3447 fn has_serviceable_blocking_work(&self) -> bool {
3448 let state = self
3449 .blocking_admission
3450 .state
3451 .lock()
3452 .unwrap_or_else(std::sync::PoisonError::into_inner);
3453 !state.closed && state.owner.is_none() && !state.queue.is_empty()
3454 }
3455
3456 #[cfg(not(target_family = "wasm"))]
3457 fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3458 let Some(ambient_owner) = BLOCKING_OWNER
3459 .with(std::cell::Cell::get)
3460 .filter(|owner| owner.executor_id == self.id)
3461 else {
3462 return false;
3463 };
3464 let Some(current) = dependency.select_for_owner(ambient_owner) else {
3465 return false;
3466 };
3467 let job = {
3468 let mut state = self
3469 .blocking_admission
3470 .state
3471 .lock()
3472 .unwrap_or_else(std::sync::PoisonError::into_inner);
3473 #[cfg(all(test, not(target_family = "wasm")))]
3474 run_current_thread_blocking_claim_test_hook();
3475 if self.stop.is_stopping()
3476 || state.closed
3477 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3478 || state.owner != Some(ambient_owner)
3479 {
3480 return false;
3481 }
3482 if dependency.try_claim(¤t) {
3483 state.queue.remove(current.publication.dependency.job)
3484 } else {
3485 None
3486 }
3487 };
3488 let Some(job) = job else {
3489 return false;
3490 };
3491 let _generation = RuntimeGenerationGuard::enter(self.generation);
3492 let _task = self.metrics.blocking_started(false);
3493 let _ = catch_unwind_contained(job);
3494 true
3495 }
3496
3497 fn close_blocking_admission(&self) {
3498 #[cfg(not(target_family = "wasm"))]
3499 let queued = {
3500 let mut state = self
3501 .blocking_admission
3502 .state
3503 .lock()
3504 .unwrap_or_else(std::sync::PoisonError::into_inner);
3505 state.closed = true;
3506 state.queue.closed = true;
3507 state.queue.take_all()
3508 };
3509 #[cfg(target_family = "wasm")]
3510 {
3511 let mut state = self
3512 .blocking_admission
3513 .state
3514 .lock()
3515 .unwrap_or_else(std::sync::PoisonError::into_inner);
3516 state.closed = true;
3517 }
3518 #[cfg(not(target_family = "wasm"))]
3519 for job in queued {
3520 let _ = catch_unwind_contained(|| drop(job));
3521 }
3522 }
3523
3524 #[cfg(test)]
3525 fn with_task_dispatch(
3526 metrics: Arc<RuntimeMetrics>,
3527 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3528 ) -> Self {
3529 Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
3530 }
3531
3532 #[cfg(test)]
3533 fn with_task_dispatch_registry(
3534 metrics: Arc<RuntimeMetrics>,
3535 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3536 task_drivers: &'static CurrentThreadTaskDriverRegistry,
3537 ) -> Self {
3538 let mut executor = Self::new(metrics);
3539 executor.task_dispatch = task_dispatch;
3540 executor.task_drivers = task_drivers;
3541 executor
3542 }
3543
3544 fn schedule(self: &Arc<Self>, runnable: Runnable) {
3545 self.metrics.runnable_scheduled();
3546 let rejected = {
3547 let mut queue = self
3548 .queue
3549 .lock()
3550 .unwrap_or_else(std::sync::PoisonError::into_inner);
3551 if queue.closed || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3552 Some(runnable)
3553 } else {
3554 queue.runnables.push_back(runnable);
3555 None
3556 }
3557 };
3558 if let Some(runnable) = rejected {
3559 self
3560 .metrics
3561 .queued_runnables
3562 .fetch_sub(1, Ordering::Relaxed);
3563 let _generation = RuntimeGenerationGuard::enter(self.generation);
3564 let _ = catch_unwind_contained(|| drop(runnable));
3565 return;
3566 }
3567 self.stop.wake_all();
3571 if !self.draining.load(Ordering::Acquire) {
3572 self.request_drain();
3573 }
3574 }
3575
3576 fn request_drain(self: &Arc<Self>) {
3585 let _ = self.request_drain_with(next_current_thread_dispatch_id);
3586 }
3587
3588 fn lock_scheduler_for_publication(
3589 &self,
3590 ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
3591 #[cfg(all(test, not(target_family = "wasm")))]
3592 match self.scheduler_idle_lock.try_lock() {
3593 Ok(scheduler) => scheduler,
3594 Err(std::sync::TryLockError::WouldBlock) => {
3595 run_current_thread_dispatch_publication_blocked_test_hook();
3596 self
3597 .scheduler_idle_lock
3598 .lock()
3599 .unwrap_or_else(std::sync::PoisonError::into_inner)
3600 }
3601 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
3602 }
3603 #[cfg(any(not(test), target_family = "wasm"))]
3604 self
3605 .scheduler_idle_lock
3606 .lock()
3607 .unwrap_or_else(std::sync::PoisonError::into_inner)
3608 }
3609
3610 fn request_drain_with(
3611 self: &Arc<Self>,
3612 next_dispatch: impl FnOnce() -> u64,
3613 ) -> CurrentThreadDispatchRequest {
3614 let mut scheduler = self.lock_scheduler_for_publication();
3615 if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3616 return CurrentThreadDispatchRequest::Unavailable;
3617 }
3618 if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
3619 return CurrentThreadDispatchRequest::Coalesced;
3620 }
3621 let dispatch = next_dispatch();
3622 if self
3623 .dispatch_pending
3624 .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
3625 .is_err()
3626 {
3627 return CurrentThreadDispatchRequest::Coalesced;
3628 }
3629 let dispatch_call = self
3630 .begin_host_dispatch_publication(&mut scheduler, dispatch)
3631 .expect("a fresh CurrentThread dispatch must own its publication");
3632 drop(scheduler);
3633 match self.publish_host_dispatch(dispatch, dispatch_call) {
3634 CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
3635 CurrentThreadDispatchRequest::Accepted
3636 }
3637 CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
3638 }
3639 }
3640
3641 fn claim_dispatch_call<'a>(
3642 &'a self,
3643 scheduler: &mut CurrentThreadSchedulerState,
3644 ) -> CurrentThreadDispatchCall<'a> {
3645 let generation = RuntimeGenerationGuard::enter(self.generation);
3646 let active_dispatch_calls = scheduler
3647 .active_dispatch_calls
3648 .checked_add(1)
3649 .expect("CurrentThread active dispatch-call count exhausted");
3650 scheduler.active_dispatch_calls = active_dispatch_calls;
3651 CurrentThreadDispatchCall {
3652 executor: self,
3653 publication: None,
3654 _generation: generation,
3655 }
3656 }
3657
3658 fn begin_host_dispatch_publication<'a>(
3659 &'a self,
3660 scheduler: &mut CurrentThreadSchedulerState,
3661 dispatch: u64,
3662 ) -> Option<CurrentThreadDispatchCall<'a>> {
3663 if let Some(publication) = scheduler
3665 .dispatch_publications
3666 .iter_mut()
3667 .find(|publication| publication.dispatch == dispatch)
3668 {
3669 publication.republish_requested = true;
3670 return None;
3671 }
3672 let mut dispatch_call = self.claim_dispatch_call(scheduler);
3673 scheduler
3674 .dispatch_publications
3675 .push(CurrentThreadDispatchPublication {
3676 dispatch,
3677 republish_requested: false,
3678 });
3679 dispatch_call.publication = Some(dispatch);
3680 Some(dispatch_call)
3681 }
3682
3683 fn publish_host_dispatch(
3684 self: &Arc<Self>,
3685 dispatch: u64,
3686 mut dispatch_call: CurrentThreadDispatchCall<'_>,
3687 ) -> CurrentThreadHostDispatchResult {
3688 let mut panic_payload = RetainedPanicPayload::default();
3689 loop {
3690 let result = match catch_unwind(AssertUnwindSafe(|| {
3691 #[cfg(all(test, not(target_family = "wasm")))]
3692 run_current_thread_dispatch_call_test_hook();
3693 let result = (self.task_dispatch)(dispatch);
3694 #[cfg(all(test, not(target_family = "wasm")))]
3695 run_current_thread_dispatch_returned_test_hook();
3696 result
3697 })) {
3698 Ok(result) => Some(result),
3699 Err(payload) => {
3700 panic_payload.capture(payload);
3701 None
3702 }
3703 };
3704
3705 let step = catch_unwind(AssertUnwindSafe(|| {
3706 let (republish, cancelling, failure, result) = {
3707 let mut scheduler = self
3708 .scheduler_idle_lock
3709 .lock()
3710 .unwrap_or_else(std::sync::PoisonError::into_inner);
3711 let index = scheduler
3712 .dispatch_publications
3713 .iter()
3714 .position(|publication| publication.dispatch == dispatch)
3715 .expect("the CurrentThread dispatch publication must remain registered");
3716 let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
3717 let republish = dispatch_remains_pending
3718 && !self.stop.is_stopping()
3719 && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
3720 && scheduler.dispatch_publications[index].republish_requested;
3721 if republish {
3722 if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3723 let _ = self.task_drivers.rearm_failed_publication(failure);
3724 }
3725 scheduler.dispatch_publications[index].republish_requested = false;
3726 (true, None, None, result)
3727 } else {
3728 dispatch_call.finish_publication(&mut scheduler);
3729 let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3730 #[cfg(all(test, not(target_family = "wasm")))]
3731 run_current_thread_failed_publication_retired_test_hook();
3732 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3733 } else {
3734 None
3735 };
3736 let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
3737 && dispatch_remains_pending)
3738 .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
3739 .flatten();
3740 let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
3741 && !dispatch_remains_pending
3742 {
3743 Some(CurrentThreadHostDispatchResult::Accepted)
3746 } else {
3747 result
3748 };
3749 (false, cancelling, failure, result)
3750 }
3751 };
3752
3753 if republish {
3754 return CurrentThreadDispatchPublicationStep::Republish;
3755 }
3756 if let Some(cancelling) = cancelling {
3757 self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
3758 }
3759 if let Some(failure) = failure {
3760 self.run_host_dispatch_failure(failure.action);
3761 }
3762 CurrentThreadDispatchPublicationStep::Complete(result)
3763 }));
3764
3765 match step {
3766 Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
3767 Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
3768 if panic_payload.is_some() {
3769 panic_payload.resume();
3770 }
3771 return result
3772 .expect("a CurrentThread host dispatch without a result must resume its panic");
3773 }
3774 Err(payload) => {
3775 panic_payload.capture(payload);
3776 panic_payload.resume();
3777 }
3778 }
3779 }
3780 }
3781
3782 fn claim_host_turn(
3783 self: &Arc<Self>,
3784 scheduler: &mut CurrentThreadSchedulerState,
3785 ) -> CurrentThreadHostTurn {
3786 scheduler.active_host_turns = scheduler
3787 .active_host_turns
3788 .checked_add(1)
3789 .expect("CurrentThread active host-turn count exhausted");
3790 self.draining.store(true, Ordering::Release);
3791 CurrentThreadHostTurn {
3792 executor: Arc::clone(self),
3793 draining: true,
3794 _generation: RuntimeGenerationGuard::enter(self.generation),
3795 }
3796 }
3797
3798 fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
3799 let mut scheduler = self
3800 .scheduler_idle_lock
3801 .lock()
3802 .unwrap_or_else(std::sync::PoisonError::into_inner);
3803 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3804 return None;
3805 }
3806 if dispatch == 0
3807 || self
3808 .dispatch_pending
3809 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3810 .is_err()
3811 {
3812 return None;
3813 }
3814 #[cfg(all(test, not(target_family = "wasm")))]
3815 run_current_thread_dispatch_claim_test_hook();
3816 let _ =
3817 self
3818 .recovery_dispatch
3819 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
3820 Some(self.claim_host_turn(&mut scheduler))
3821 }
3822
3823 #[cfg(test)]
3824 fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
3825 let mut scheduler = self
3826 .scheduler_idle_lock
3827 .lock()
3828 .unwrap_or_else(std::sync::PoisonError::into_inner);
3829 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3830 return None;
3831 }
3832 Some(self.claim_host_turn(&mut scheduler))
3833 }
3834
3835 #[cfg(test)]
3836 fn drive_host_turn(self: &Arc<Self>) {
3837 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3838 let host_turn = if dispatch == 0 {
3839 self.try_admit_host_turn_without_dispatch()
3840 } else {
3841 self.try_admit_host_turn(dispatch)
3842 };
3843 if let Some(mut host_turn) = host_turn {
3844 host_turn.drive();
3845 }
3846 }
3847
3848 #[cfg(test)]
3849 fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
3850 if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
3851 host_turn.drive();
3852 }
3853 }
3854
3855 fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
3859 #[cfg(not(target_family = "wasm"))]
3860 let mut runnable_streak = 0usize;
3861 for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
3862 #[cfg(not(target_family = "wasm"))]
3863 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
3864 runnable_streak = 0;
3865 if self.run_one_blocking() {
3866 continue;
3867 }
3868 }
3869 if self.drain_one() {
3870 #[cfg(not(target_family = "wasm"))]
3871 {
3872 runnable_streak += 1;
3873 }
3874 continue;
3875 }
3876 #[cfg(not(target_family = "wasm"))]
3877 if self.run_one_blocking() {
3878 runnable_streak = 0;
3879 continue;
3880 }
3881 break;
3882 }
3883 host_turn.release_draining();
3888 self.request_drain_if_work_remains();
3889 }
3890
3891 fn request_drain_if_queued(self: &Arc<Self>) {
3892 if !self.has_serviceable_work() {
3893 return;
3894 }
3895 let mut scheduler = self.lock_scheduler_for_publication();
3896 if self.stop.is_stopping()
3897 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3898 || self.draining.load(Ordering::Acquire)
3899 {
3900 return;
3901 }
3902 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3903 if dispatch == 0 {
3904 drop(scheduler);
3905 self.request_drain_if_work_remains();
3906 return;
3907 }
3908
3909 let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
3913 return;
3914 };
3915 drop(scheduler);
3916 let _ = self.publish_host_dispatch(dispatch, dispatch_call);
3917 }
3918
3919 fn retire_unavailable_host_dispatch_locked(
3920 &self,
3921 dispatch: u64,
3922 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
3923 if self
3924 .dispatch_pending
3925 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3926 .is_err()
3927 {
3928 return None;
3929 }
3930 if self
3931 .recovery_dispatch
3932 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3933 .is_ok()
3934 {
3935 self.begin_failed_dispatch_cancellation()
3936 } else {
3937 None
3938 }
3939 }
3940
3941 fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
3942 let failure = {
3944 let mut scheduler = self
3945 .scheduler_idle_lock
3946 .lock()
3947 .unwrap_or_else(std::sync::PoisonError::into_inner);
3948 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3949 };
3950 if let Some(failure) = failure {
3951 self.run_host_dispatch_failure(failure.action);
3952 }
3953 }
3954
3955 fn begin_host_dispatch_failure_locked<'a>(
3956 &'a self,
3957 scheduler: &mut CurrentThreadSchedulerState,
3958 failure: CurrentThreadTaskDispatchFailure,
3959 ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
3960 if !self.task_drivers.is_current_failure(failure) {
3961 return None;
3962 }
3963 let registry_cancellation =
3964 CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
3965 let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
3966 Some(CurrentThreadDispatchFailureRecovery {
3967 action,
3968 _registry_cancellation: registry_cancellation,
3969 })
3970 }
3971
3972 fn begin_host_dispatch_failure_transition_locked<'a>(
3973 &'a self,
3974 scheduler: &mut CurrentThreadSchedulerState,
3975 dispatch: u64,
3976 ) -> CurrentThreadDispatchFailureAction<'a> {
3977 if dispatch == 0
3978 || self
3979 .dispatch_pending
3980 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3981 .is_err()
3982 {
3983 return CurrentThreadDispatchFailureAction::None;
3984 }
3985 #[cfg(all(test, not(target_family = "wasm")))]
3986 run_current_thread_dispatch_recovery_test_hook();
3987
3988 if self
3989 .recovery_dispatch
3990 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3991 .is_ok()
3992 {
3993 return self.begin_failed_dispatch_cancellation().map_or(
3997 CurrentThreadDispatchFailureAction::None,
3998 CurrentThreadDispatchFailureAction::Cancel,
3999 );
4000 }
4001
4002 if self.stop.is_stopping()
4003 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4004 || self.draining.load(Ordering::Acquire)
4005 || !self.has_serviceable_work()
4006 {
4007 return CurrentThreadDispatchFailureAction::None;
4008 }
4009
4010 let replacement = next_current_thread_dispatch_id();
4014 self.recovery_dispatch.store(replacement, Ordering::Release);
4015 self.dispatch_pending.store(replacement, Ordering::Release);
4016 let dispatch_call = self
4017 .begin_host_dispatch_publication(scheduler, replacement)
4018 .expect("a reserved CurrentThread replacement must own its publication");
4019 CurrentThreadDispatchFailureAction::Publish {
4020 dispatch: replacement,
4021 call: dispatch_call,
4022 }
4023 }
4024
4025 #[cfg(test)]
4026 fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4027 let failure = {
4028 let mut scheduler = self
4029 .scheduler_idle_lock
4030 .lock()
4031 .unwrap_or_else(std::sync::PoisonError::into_inner);
4032 self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4033 };
4034 self.run_host_dispatch_failure(failure);
4035 }
4036
4037 fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4038 match failure {
4039 CurrentThreadDispatchFailureAction::None => {}
4040 CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4041 self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
4042 }
4043 CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4044 let _ = self.publish_host_dispatch(dispatch, call);
4045 }
4046 }
4047 }
4048
4049 fn has_serviceable_work(&self) -> bool {
4050 let has_queued_runnable = {
4051 let queue = self
4052 .queue
4053 .lock()
4054 .unwrap_or_else(std::sync::PoisonError::into_inner);
4055 !queue.closed && !queue.runnables.is_empty()
4056 };
4057 #[cfg(not(target_family = "wasm"))]
4058 let has_queued_blocking = self.has_serviceable_blocking_work();
4059 #[cfg(target_family = "wasm")]
4060 let has_queued_blocking = false;
4061 has_queued_runnable || has_queued_blocking
4062 }
4063
4064 fn request_drain_if_work_remains(self: &Arc<Self>) {
4065 if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4066 self.request_drain();
4067 }
4068 }
4069
4070 fn begin_failed_dispatch_cancellation(
4071 &self,
4072 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4073 if self
4074 .cancelling_failed_dispatch
4075 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4076 .is_err()
4077 {
4078 return None;
4079 }
4080 self.dispatch_pending.store(0, Ordering::Release);
4081 self.recovery_dispatch.store(0, Ordering::Release);
4082 Some(CurrentThreadDispatchCancellationGuard(self))
4083 }
4084
4085 fn cancel_work_after_host_dispatch_failure_claimed(
4086 &self,
4087 _cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4088 ) {
4089 let queued = {
4090 let mut queue = self
4091 .queue
4092 .lock()
4093 .unwrap_or_else(std::sync::PoisonError::into_inner);
4094 std::mem::take(&mut queue.runnables)
4095 };
4096 #[cfg(not(target_family = "wasm"))]
4097 let queued_blocking = {
4098 let mut state = self
4099 .blocking_admission
4100 .state
4101 .lock()
4102 .unwrap_or_else(std::sync::PoisonError::into_inner);
4103 state.queue.take_all()
4104 };
4105
4106 let _generation = RuntimeGenerationGuard::enter(self.generation);
4107 for runnable in queued {
4108 self
4109 .metrics
4110 .queued_runnables
4111 .fetch_sub(1, Ordering::Relaxed);
4112 let _ = catch_unwind_contained(|| drop(runnable));
4113 }
4114 #[cfg(not(target_family = "wasm"))]
4115 for job in queued_blocking {
4116 let _ = catch_unwind_contained(|| drop(job));
4117 }
4118 }
4119
4120 fn take_runnable(&self) -> Option<Runnable> {
4121 let scheduler = self
4126 .scheduler_idle_lock
4127 .lock()
4128 .unwrap_or_else(std::sync::PoisonError::into_inner);
4129 #[cfg(all(test, not(target_family = "wasm")))]
4130 run_current_thread_runnable_claim_test_hook();
4131 let stop_publication = self.stop.publication_guard();
4132 if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4133 return None;
4134 }
4135 let runnable = {
4136 let mut queue = self
4137 .queue
4138 .lock()
4139 .unwrap_or_else(std::sync::PoisonError::into_inner);
4140 if queue.closed {
4141 None
4142 } else {
4143 queue.runnables.pop_front()
4144 }
4145 };
4146 drop(stop_publication);
4147 drop(scheduler);
4148 runnable
4149 }
4150
4151 fn drain_one(&self) -> bool {
4152 let runnable = self.take_runnable();
4153 if let Some(runnable) = runnable {
4154 #[cfg(not(target_family = "wasm"))]
4155 let _non_owner = BlockingOwnerGuard::enter(None);
4156 run_runnable(&self.metrics, runnable);
4157 true
4158 } else {
4159 false
4160 }
4161 }
4162
4163 fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4185 let parker = Arc::new(DriverParker::default());
4186 let _stop_registration = self.stop.register(&parker);
4187 #[cfg(not(target_family = "wasm"))]
4188 let dependency = BlockOnDependencyGuard::enter(self.generation);
4189 let waker = std::task::Waker::from(Arc::clone(&parker));
4190 let mut cx = Context::from_waker(&waker);
4191 #[cfg(not(target_family = "wasm"))]
4192 let mut runnable_streak = 0usize;
4193 loop {
4194 if self.stop.is_stopping() {
4195 return BlockOnOutcome::Stopped;
4196 }
4197 #[cfg(not(target_family = "wasm"))]
4198 dependency.clear();
4199 if future.as_mut().poll(&mut cx).is_ready() {
4200 return BlockOnOutcome::Completed;
4201 }
4202 if self.stop.is_stopping() {
4206 return BlockOnOutcome::Stopped;
4207 }
4208 #[cfg(not(target_family = "wasm"))]
4209 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4210 runnable_streak = 0;
4211 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4212 continue;
4213 }
4214 }
4215 if self.drain_one() {
4216 #[cfg(not(target_family = "wasm"))]
4217 {
4218 runnable_streak += 1;
4219 }
4220 continue;
4221 }
4222 #[cfg(not(target_family = "wasm"))]
4223 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4224 runnable_streak = 0;
4225 continue;
4226 }
4227 #[cfg(not(target_family = "wasm"))]
4228 {
4229 runnable_streak = 0;
4230 }
4231 if parker.consume_permit() {
4235 continue;
4236 }
4237 if self.threadless {
4249 if self.host_timers.has_pending() {
4250 std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
4251 }
4252 std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
4253 }
4254 match self.park_deadline {
4255 None => parker.park(),
4259 Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
4260 DeadlineParkOutcome::Woken => {}
4261 DeadlineParkOutcome::Expired(armed) => {
4262 if parker.consume_permit()
4277 || self.metrics.progress_fingerprint() != armed
4278 || self.host_timers.has_live_wait(deadline)
4279 {
4280 continue;
4281 }
4282 std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
4283 }
4284 },
4285 }
4286 }
4287 }
4288
4289 fn begin_shutdown(&self) {
4290 let _generation = RuntimeGenerationGuard::enter(self.generation);
4291 self.stop.begin_shutdown();
4292 self.close_blocking_admission();
4293 let queued = {
4294 let mut queue = self
4295 .queue
4296 .lock()
4297 .unwrap_or_else(std::sync::PoisonError::into_inner);
4298 queue.closed = true;
4299 std::mem::take(&mut queue.runnables)
4300 };
4301 self.dispatch_pending.store(0, Ordering::Release);
4302 self.recovery_dispatch.store(0, Ordering::Release);
4303 self.task_drivers.retire_for_shutdown();
4304 for runnable in queued {
4305 self
4306 .metrics
4307 .queued_runnables
4308 .fetch_sub(1, Ordering::Relaxed);
4309 let _ = catch_unwind_contained(|| drop(runnable));
4312 }
4313 self.host_timers.shutdown();
4314 }
4315
4316 fn wait_until_scheduler_idle(&self) {
4317 let mut idle = self
4318 .scheduler_idle_lock
4319 .lock()
4320 .unwrap_or_else(std::sync::PoisonError::into_inner);
4321 while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
4322 idle = self
4323 .scheduler_idle
4324 .wait(idle)
4325 .unwrap_or_else(std::sync::PoisonError::into_inner);
4326 }
4327 debug_assert!(
4328 idle.dispatch_publications.is_empty(),
4329 "an idle CurrentThread scheduler must not retain dispatch publications"
4330 );
4331 }
4332}
4333
4334#[cfg(not(target_family = "wasm"))]
4347thread_local! {
4348 static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4349 static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4353}
4354
4355#[cfg(not(target_family = "wasm"))]
4361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4362struct SchedulerDriverRole {
4363 executor_id: u64,
4364 can_run_blocking: bool,
4365}
4366
4367#[cfg(not(target_family = "wasm"))]
4368thread_local! {
4369 static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
4370 const { std::cell::Cell::new(None) };
4371}
4372
4373#[cfg(not(target_family = "wasm"))]
4390thread_local! {
4391 static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
4392 const { std::cell::Cell::new(None) };
4393}
4394
4395#[cfg(not(target_family = "wasm"))]
4396struct OnPoolWorkerGuard {
4397 previous_worker: Option<u64>,
4398 previous_driver: Option<SchedulerDriverRole>,
4399}
4400
4401#[cfg(not(target_family = "wasm"))]
4402impl OnPoolWorkerGuard {
4403 fn enter(id: u64) -> Self {
4407 Self::enter_with_role(id, true)
4408 }
4409
4410 fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
4411 Self {
4412 previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
4413 previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
4414 flag.replace(Some(SchedulerDriverRole {
4415 executor_id: id,
4416 can_run_blocking,
4417 }))
4418 }),
4419 }
4420 }
4421}
4422
4423#[cfg(not(target_family = "wasm"))]
4424impl Drop for OnPoolWorkerGuard {
4425 fn drop(&mut self) {
4426 IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
4427 ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
4428 }
4429}
4430
4431#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
4444struct BlockingOwnerToken {
4445 executor_id: u64,
4446 frame: u64,
4447}
4448
4449#[cfg(not(target_family = "wasm"))]
4450enum OwnedBlockingOutcome {
4451 Ineligible,
4452 OwnerBusy(BlockingOwnerToken),
4453 ReservationReleased {
4454 owner: BlockingOwnerToken,
4455 ran: bool,
4456 },
4457}
4458
4459static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
4462#[cfg(not(target_family = "wasm"))]
4463static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
4464
4465thread_local! {
4468 static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
4469 const { std::cell::Cell::new(None) };
4470}
4471
4472#[cfg(not(target_family = "wasm"))]
4479struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
4480
4481#[cfg(not(target_family = "wasm"))]
4482impl BlockingOwnerGuard {
4483 fn enter(token: Option<BlockingOwnerToken>) -> Self {
4484 Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
4485 }
4486}
4487
4488#[cfg(not(target_family = "wasm"))]
4489impl Drop for BlockingOwnerGuard {
4490 fn drop(&mut self) {
4491 BLOCKING_OWNER.with(|cell| cell.set(self.0));
4492 }
4493}
4494
4495#[cfg(not(target_family = "wasm"))]
4496#[derive(Default)]
4497struct ActiveBlockingOwners {
4498 owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
4502}
4503
4504#[cfg(not(target_family = "wasm"))]
4505impl ActiveBlockingOwners {
4506 fn register(&self, owner: BlockingOwnerToken) {
4507 let previous = self
4508 .owners
4509 .lock()
4510 .unwrap_or_else(std::sync::PoisonError::into_inner)
4511 .insert(owner, None);
4512 debug_assert!(previous.is_none());
4513 }
4514
4515 fn unregister(&self, owner: BlockingOwnerToken) {
4516 let removed = self
4517 .owners
4518 .lock()
4519 .unwrap_or_else(std::sync::PoisonError::into_inner)
4520 .remove(&owner);
4521 debug_assert!(removed.is_some());
4522 }
4523
4524 fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
4525 let mut owners = self
4526 .owners
4527 .lock()
4528 .unwrap_or_else(std::sync::PoisonError::into_inner);
4529 let reservation = owners.get_mut(&owner)?;
4530 if reservation.is_some() {
4531 return None;
4532 }
4533 let id = next_unique_id(
4534 &NEXT_OWNER_RESERVATION,
4535 "blocking owner reservation id space exhausted",
4536 );
4537 *reservation = Some(id);
4538 Some(id)
4539 }
4540
4541 fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
4542 let mut owners = self
4543 .owners
4544 .lock()
4545 .unwrap_or_else(std::sync::PoisonError::into_inner);
4546 if let Some(current) = owners.get_mut(&owner)
4547 && *current == Some(reservation)
4548 {
4549 *current = None;
4550 }
4551 }
4552
4553 fn is_available(&self, owner: BlockingOwnerToken) -> bool {
4554 self
4555 .owners
4556 .lock()
4557 .unwrap_or_else(std::sync::PoisonError::into_inner)
4558 .get(&owner)
4559 .is_some_and(Option::is_none)
4560 }
4561}
4562
4563#[cfg(not(target_family = "wasm"))]
4564struct ActiveBlockingOwnerGuard<'a> {
4565 owners: &'a ActiveBlockingOwners,
4566 owner: BlockingOwnerToken,
4567}
4568
4569#[cfg(not(target_family = "wasm"))]
4570impl<'a> ActiveBlockingOwnerGuard<'a> {
4571 fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
4572 owners.register(owner);
4573 Self { owners, owner }
4574 }
4575}
4576
4577#[cfg(not(target_family = "wasm"))]
4578impl Drop for ActiveBlockingOwnerGuard<'_> {
4579 fn drop(&mut self) {
4580 self.owners.unregister(self.owner);
4581 }
4582}
4583
4584#[cfg(not(target_family = "wasm"))]
4585struct OwnerLaneReservation<'a> {
4586 owners: &'a ActiveBlockingOwners,
4587 owner: BlockingOwnerToken,
4588 reservation: u64,
4589}
4590
4591#[cfg(not(target_family = "wasm"))]
4592impl<'a> OwnerLaneReservation<'a> {
4593 fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
4594 let reservation = owners.reserve(owner)?;
4595 Some(Self {
4596 owners,
4597 owner,
4598 reservation,
4599 })
4600 }
4601}
4602
4603#[cfg(not(target_family = "wasm"))]
4604impl Drop for OwnerLaneReservation<'_> {
4605 fn drop(&mut self) {
4606 self.owners.release(self.owner, self.reservation);
4607 }
4608}
4609
4610#[cfg(not(target_family = "wasm"))]
4614type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
4615
4616#[cfg(not(target_family = "wasm"))]
4617struct QueuedBlocking {
4618 id: BlockingJobId,
4619 run: BlockingClosure,
4620}
4621
4622#[cfg(not(target_family = "wasm"))]
4623struct BlockingQueueNode {
4624 previous: Option<BlockingJobId>,
4625 next: Option<BlockingJobId>,
4626 run: Box<dyn FnOnce() + Send + 'static>,
4627}
4628
4629#[cfg(not(target_family = "wasm"))]
4630struct BlockingQueue {
4631 closed: bool,
4632 head: Option<BlockingJobId>,
4633 tail: Option<BlockingJobId>,
4634 jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
4635}
4636
4637#[cfg(not(target_family = "wasm"))]
4638impl BlockingQueue {
4639 fn push(&mut self, job: QueuedBlocking) {
4640 let previous = self.tail;
4641 let replaced = self.jobs.insert(
4642 job.id,
4643 BlockingQueueNode {
4644 previous,
4645 next: None,
4646 run: job.run,
4647 },
4648 );
4649 debug_assert!(replaced.is_none());
4650 if let Some(previous) = previous {
4651 self
4652 .jobs
4653 .get_mut(&previous)
4654 .expect("blocking queue tail must be indexed")
4655 .next = Some(job.id);
4656 } else {
4657 self.head = Some(job.id);
4658 }
4659 self.tail = Some(job.id);
4660 }
4661
4662 fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4663 self.head.and_then(|id| self.remove(id))
4664 }
4665
4666 fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4667 let node = self.jobs.remove(&id)?;
4668 if let Some(previous) = node.previous {
4669 self
4670 .jobs
4671 .get_mut(&previous)
4672 .expect("blocking queue predecessor must be indexed")
4673 .next = node.next;
4674 } else {
4675 self.head = node.next;
4676 }
4677 if let Some(next) = node.next {
4678 self
4679 .jobs
4680 .get_mut(&next)
4681 .expect("blocking queue successor must be indexed")
4682 .previous = node.previous;
4683 } else {
4684 self.tail = node.previous;
4685 }
4686 Some(node.run)
4687 }
4688
4689 fn is_empty(&self) -> bool {
4690 debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
4691 debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
4692 self.jobs.is_empty()
4693 }
4694
4695 fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
4696 self.head = None;
4697 self.tail = None;
4698 std::mem::take(&mut self.jobs)
4699 .into_values()
4700 .map(|node| node.run)
4701 .collect()
4702 }
4703}
4704
4705#[cfg(not(target_family = "wasm"))]
4706struct WorkerLifecycle {
4707 remaining: Mutex<usize>,
4708 handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
4709}
4710
4711#[cfg(not(target_family = "wasm"))]
4712impl WorkerLifecycle {
4713 fn new(worker_threads: usize) -> Arc<Self> {
4714 Arc::new(Self {
4715 remaining: Mutex::new(worker_threads),
4716 handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
4717 })
4718 }
4719
4720 fn retain(&self, handle: std::thread::JoinHandle<()>) {
4721 self
4722 .handles
4723 .lock()
4724 .unwrap_or_else(std::sync::PoisonError::into_inner)
4725 .as_mut()
4726 .expect("runtime worker handles already joined")
4727 .push(handle);
4728 }
4729
4730 fn worker_exited(&self) {
4731 let mut remaining = self
4732 .remaining
4733 .lock()
4734 .unwrap_or_else(std::sync::PoisonError::into_inner);
4735 *remaining = remaining
4736 .checked_sub(1)
4737 .expect("runtime worker exit count underflow");
4738 }
4739
4740 fn wait_for_all_workers(&self) {
4741 let handles = self
4742 .handles
4743 .lock()
4744 .unwrap_or_else(std::sync::PoisonError::into_inner)
4745 .take()
4746 .expect("runtime worker handles already joined");
4747 for handle in handles {
4748 if let Err(payload) = handle.join() {
4749 discard_panic_payload(payload);
4750 }
4751 }
4752 }
4753
4754 #[cfg(test)]
4755 fn remaining(&self) -> usize {
4756 *self
4757 .remaining
4758 .lock()
4759 .unwrap_or_else(std::sync::PoisonError::into_inner)
4760 }
4761}
4762
4763#[cfg(not(target_family = "wasm"))]
4764struct MultiThreadExecutor {
4765 generation: u64,
4766 stop: Arc<GenerationStop>,
4767 id: u64,
4770 pool: ThreadPool,
4771 injector: Injector<Runnable>,
4778 blocking_queue: Mutex<BlockingQueue>,
4779 active_drainers: AtomicUsize,
4780 active_blocking: AtomicUsize,
4781 scheduler_idle_lock: Mutex<usize>,
4789 scheduler_idle: Condvar,
4790 worker_lifecycle: Arc<WorkerLifecycle>,
4791 parked_drivers: ParkedDrivers,
4799 active_blocking_owners: ActiveBlockingOwners,
4800 #[cfg(test)]
4801 owner_lending_attempts: AtomicUsize,
4802 #[cfg(test)]
4812 drain_linger_registrations: AtomicUsize,
4813 #[cfg(test)]
4814 drain_linger_expiries: AtomicUsize,
4815 max_drainers: usize,
4816 max_blocking: usize,
4817 park_deadline: Option<Duration>,
4824 drain_linger: Option<Duration>,
4829 deadlock_state: Arc<MultiThreadDeadlockState>,
4833 timers: TimerHeap,
4836 metrics: Arc<RuntimeMetrics>,
4837}
4838
4839#[cfg(not(target_family = "wasm"))]
4840impl MultiThreadExecutor {
4841 const RUNNABLE_BUDGET: usize = 64;
4844 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
4847
4848 #[cfg(test)]
4849 fn new(
4850 options: &RuntimeOptions,
4851 metrics: Arc<RuntimeMetrics>,
4852 ) -> Result<Self, RuntimeConfigError> {
4853 Self::new_for_generation(
4854 options,
4855 metrics,
4856 u64::MAX,
4857 Arc::new(GenerationStop::default()),
4858 )
4859 }
4860
4861 fn new_for_generation(
4862 options: &RuntimeOptions,
4863 metrics: Arc<RuntimeMetrics>,
4864 generation: u64,
4865 stop: Arc<GenerationStop>,
4866 ) -> Result<Self, RuntimeConfigError> {
4867 let id = next_unique_id(
4868 &NEXT_RUNTIME_EXECUTOR_ID,
4869 "async runtime executor id space exhausted",
4870 );
4871 let thread_name_prefix = options.thread_name_prefix.clone();
4872 let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
4873 options.park_deadline.is_some(),
4874 ));
4875 let timers = TimerHeap::new(
4876 generation,
4877 id,
4878 format!("{thread_name_prefix}-timer"),
4879 Arc::clone(&deadlock_state),
4880 );
4881 let pool_threads = options.worker_threads;
4885 let worker_lifecycle = WorkerLifecycle::new(pool_threads);
4886 let exit_lifecycle = Arc::clone(&worker_lifecycle);
4887 let spawn_lifecycle = Arc::clone(&worker_lifecycle);
4888 let pool = ThreadPoolBuilder::new()
4889 .num_threads(pool_threads)
4890 .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
4891 .start_handler(move |_| {
4892 ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
4893 })
4894 .exit_handler(move |_| {
4895 IN_SCHEDULER_DRIVER.with(|driver| {
4896 if driver.get().is_some_and(|role| role.executor_id == id) {
4897 driver.set(None);
4898 }
4899 });
4900 exit_lifecycle.worker_exited();
4906 })
4907 .spawn_handler(move |thread| {
4908 let mut builder = std::thread::Builder::new();
4909 if let Some(name) = thread.name() {
4910 builder = builder.name(name.to_string());
4911 }
4912 if let Some(stack_size) = thread.stack_size() {
4913 builder = builder.stack_size(stack_size);
4914 }
4915 let handle = builder.spawn(move || thread.run())?;
4916 spawn_lifecycle.retain(handle);
4917 Ok(())
4918 })
4919 .build()
4920 .map_err(|error| {
4921 worker_lifecycle.wait_for_all_workers();
4922 RuntimeConfigError(format!("failed to create runtime workers: {error}"))
4923 })?;
4924 Ok(Self {
4925 generation,
4926 stop,
4927 id,
4928 pool,
4929 injector: Injector::new(),
4930 blocking_queue: Mutex::new(BlockingQueue {
4931 closed: false,
4932 head: None,
4933 tail: None,
4934 jobs: FxHashMap::default(),
4935 }),
4936 active_drainers: AtomicUsize::new(0),
4937 active_blocking: AtomicUsize::new(0),
4938 scheduler_idle_lock: Mutex::new(0),
4939 scheduler_idle: Condvar::new(),
4940 worker_lifecycle,
4941 parked_drivers: ParkedDrivers::default(),
4942 active_blocking_owners: ActiveBlockingOwners::default(),
4943 #[cfg(test)]
4944 owner_lending_attempts: AtomicUsize::new(0),
4945 #[cfg(test)]
4946 drain_linger_registrations: AtomicUsize::new(0),
4947 #[cfg(test)]
4948 drain_linger_expiries: AtomicUsize::new(0),
4949 max_drainers: pool_threads,
4950 max_blocking: options.max_blocking_tasks,
4951 park_deadline: options.park_deadline,
4952 drain_linger: {
4953 let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
4957 (!budget.is_zero()).then_some(budget)
4958 },
4959 deadlock_state,
4960 timers,
4961 metrics,
4962 })
4963 }
4964
4965 fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
4966 self.deadlock_state.begin_admission()
4967 }
4968
4969 fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
4970 self.deadlock_state.try_begin_verdict()
4971 }
4972
4973 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
4974 let _publication = self.deadlock_state.owner_handoff_publication_guard();
4976 self.parked_drivers.wake_one_owner_dependency(owner)
4977 }
4978
4979 fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
4980 let owner = reservation.owner;
4981 let _publication = self.deadlock_state.owner_handoff_publication_guard();
4985 drop(reservation);
4986 self.parked_drivers.wake_one_owner_dependency(owner)
4987 }
4988
4989 fn schedule(self: &Arc<Self>, runnable: Runnable) {
4990 let mut admission = self.begin_deadlock_admission();
4991 self.metrics.runnable_scheduled();
4994 if IN_SCHEDULER_DRIVER
5014 .with(std::cell::Cell::get)
5015 .is_some_and(|role| role.executor_id == self.id)
5016 && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5017 {
5018 match LIFO_SLOT.with(std::cell::Cell::take) {
5019 None => {
5020 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5021 if let Some(admission) = admission.as_mut() {
5022 admission.mark_published();
5023 }
5024 return;
5025 }
5026 Some((id, displaced)) if id == self.id => {
5030 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5031 self.push_to_queue_and_wake(displaced);
5032 if let Some(admission) = admission.as_mut() {
5033 admission.mark_published();
5034 }
5035 return;
5036 }
5037 Some(foreign) => {
5044 LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5045 }
5046 }
5047 }
5048 self.push_to_queue_and_wake(runnable);
5049 if let Some(admission) = admission.as_mut() {
5050 admission.mark_published();
5051 }
5052 }
5053
5054 fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5058 self.injector.push(runnable);
5059 std::sync::atomic::fence(Ordering::SeqCst);
5068 self.wake_for_new_work();
5069 }
5070
5071 fn wake_for_new_work(self: &Arc<Self>) {
5080 if !self.parked_drivers.wake_one() {
5081 self.ensure_drainer();
5082 }
5083 }
5084
5085 fn wake_for_blocking_work(self: &Arc<Self>) {
5086 if !self.parked_drivers.wake_one_blocking() {
5089 self.ensure_drainer();
5090 }
5091 }
5092
5093 #[cfg(test)]
5094 fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5095 where
5096 F: FnOnce() -> T + Send + 'static,
5097 T: Send + 'static,
5098 {
5099 self.schedule_blocking_result(move || catch_unwind_join_error(function))
5100 }
5101
5102 fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5103 where
5104 F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5105 T: Send + 'static,
5106 {
5107 let mut admission = self.begin_deadlock_admission();
5108 let (sender, receiver) = oneshot::channel();
5109 let generation = self.generation;
5110 let dependency = BlockingDependency {
5111 job: BlockingJobId(next_unique_id(
5112 &NEXT_BLOCKING_JOB_ID,
5113 "blocking job id space exhausted",
5114 )),
5115 owner: BLOCKING_OWNER
5116 .with(std::cell::Cell::get)
5117 .filter(|owner| owner.executor_id == self.id),
5118 };
5119 self.metrics.blocking_scheduled();
5124 let queued = QueuedBlocking {
5125 id: dependency.job,
5126 run: Box::new(move || {
5127 let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5128 let _ = sender.send(result);
5129 }),
5130 };
5131 let rejected = {
5132 let mut queue = self
5133 .blocking_queue
5134 .lock()
5135 .unwrap_or_else(std::sync::PoisonError::into_inner);
5136 if queue.closed {
5137 Some(queued)
5138 } else {
5139 queue.push(queued);
5140 None
5141 }
5142 };
5143 if let Some(rejected) = rejected {
5144 let _generation = RuntimeGenerationGuard::enter(self.generation);
5149 let _ = catch_unwind_contained(|| drop(rejected));
5150 } else {
5151 self.wake_for_blocking_work();
5153 if let Some(admission) = admission.as_mut() {
5154 admission.mark_published();
5155 }
5156 }
5157 JoinHandle(JoinHandleInner::Blocking {
5158 receiver,
5159 dependency,
5160 published: PublishedBlockingDependencies::default(),
5161 })
5162 }
5163
5164 fn ensure_drainer(self: &Arc<Self>) {
5165 loop {
5166 let active = self.active_drainers.load(Ordering::Acquire);
5167 if active >= self.max_drainers {
5168 return;
5169 }
5170 if self
5171 .active_drainers
5172 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5173 .is_ok()
5174 {
5175 let executor = Arc::clone(self);
5176 self.pool.spawn_fifo(move || executor.drain());
5177 return;
5178 }
5179 }
5180 }
5181
5182 fn drain(self: Arc<Self>) {
5183 let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5189 let _on_pool = OnPoolWorkerGuard::enter(self.id);
5192 let _slot_backstop = LifoSlotFlushGuard(&self);
5198
5199 let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5200 let mut linger: Option<DrainLinger> = None;
5203 let mut idle_deadline: Option<Instant> = None;
5209 let mut frame_deadline: Option<Instant> = None;
5215
5216 'frame: loop {
5217 let mut runnable_streak = 0usize;
5218 for _ in 0..Self::RUNNABLE_BUDGET {
5219 if self.run_one_fair(&mut runnable_streak) {
5220 idle_deadline = None;
5221 continue;
5222 }
5223 self.flush_lifo_slot();
5231 let Some(budget) = linger_budget else {
5253 break 'frame;
5254 };
5255 if self.stop.is_stopping() {
5256 break 'frame;
5257 }
5258 let now = Instant::now();
5259 let frame = *frame_deadline.get_or_insert_with(|| {
5265 now
5266 + budget
5267 .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
5268 .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
5269 });
5270 let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
5271 if now >= deadline {
5272 #[cfg(test)]
5279 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5280 break 'frame;
5281 }
5282 let state = match &linger {
5283 Some(state) => state,
5284 None => linger.insert(DrainLinger::new(&self)),
5285 };
5286 self
5287 .parked_drivers
5288 .register_with_role(&state.parker, true, None);
5289 #[cfg(test)]
5290 self
5291 .drain_linger_registrations
5292 .fetch_add(1, Ordering::SeqCst);
5293 if self.has_queued_work_for_role(true) {
5294 self.parked_drivers.deregister(&state.parker);
5295 continue 'frame;
5296 }
5297 if self.stop.is_stopping() {
5302 self.parked_drivers.deregister(&state.parker);
5303 break 'frame;
5304 }
5305 let woken = state.parker.park_timeout(deadline - now);
5306 self.parked_drivers.deregister(&state.parker);
5307 if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
5308 continue 'frame;
5309 }
5310 #[cfg(test)]
5315 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5316 break 'frame;
5317 }
5318 break 'frame;
5323 }
5324
5325 self.flush_lifo_slot();
5328 self.finish_draining();
5329 }
5330
5331 fn pop_lifo_slot(&self) -> Option<Runnable> {
5335 LIFO_SLOT.with(|slot| match slot.take() {
5336 Some((id, runnable)) if id == self.id => Some(runnable),
5337 other => {
5338 slot.set(other);
5339 None
5340 }
5341 })
5342 }
5343
5344 fn flush_lifo_slot(self: &Arc<Self>) {
5351 let mut admission = self.begin_deadlock_admission();
5352 if let Some(runnable) = self.pop_lifo_slot() {
5353 self.push_to_queue_and_wake(runnable);
5354 if let Some(admission) = admission.as_mut() {
5355 admission.mark_published();
5356 }
5357 }
5358 }
5359
5360 fn fresh_owner_token(&self) -> BlockingOwnerToken {
5364 BlockingOwnerToken {
5365 executor_id: self.id,
5366 frame: next_unique_id(
5367 &NEXT_BLOCKING_FRAME,
5368 "blocking owner frame id space exhausted",
5369 ),
5370 }
5371 }
5372
5373 fn finish_draining(self: &Arc<Self>) {
5374 self.active_drainers.fetch_sub(1, Ordering::AcqRel);
5384 let has_runnable = self.has_queued_runnable();
5385 let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5386 && !self
5387 .blocking_queue
5388 .lock()
5389 .unwrap_or_else(std::sync::PoisonError::into_inner)
5390 .is_empty();
5391 if has_runnable || has_blocking {
5392 self.ensure_drainer();
5393 }
5394 let idle = self
5400 .scheduler_idle_lock
5401 .lock()
5402 .unwrap_or_else(std::sync::PoisonError::into_inner);
5403 if *idle > 0 {
5404 self.scheduler_idle.notify_all();
5405 }
5406 }
5407
5408 fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
5409 if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
5410 self.cooperative_block_on(future)
5418 } else {
5419 self.parking_block_on(future)
5423 }
5424 }
5425
5426 fn parking_block_on(
5427 self: &Arc<Self>,
5428 mut future: Pin<&mut dyn Future<Output = ()>>,
5429 ) -> BlockOnOutcome {
5430 let parker = Arc::new(DriverParker::default());
5431 let _stop_registration = self.stop.register(&parker);
5432 let waker = Waker::from(Arc::clone(&parker));
5433 let mut cx = Context::from_waker(&waker);
5434 loop {
5435 if self.stop.is_stopping() {
5436 return BlockOnOutcome::Stopped;
5437 }
5438 if future.as_mut().poll(&mut cx).is_ready() {
5439 return BlockOnOutcome::Completed;
5440 }
5441 if self.stop.is_stopping() {
5442 return BlockOnOutcome::Stopped;
5443 }
5444 parker.park();
5445 }
5446 }
5447
5448 fn run_runnable(
5449 self: &Arc<Self>,
5450 runnable: Runnable,
5451 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5452 ) {
5453 let _non_owner = BlockingOwnerGuard::enter(None);
5456 let _active = self.metrics.runnable_started();
5457 #[cfg(test)]
5458 run_multi_thread_work_started_test_hook();
5459 drop(admission);
5463 let _ = catch_unwind_contained(|| runnable.run());
5464 }
5465
5466 fn cancel_runnable(
5467 &self,
5468 runnable: Runnable,
5469 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5470 ) {
5471 self.metrics.runnable_cancelled();
5472 drop(admission);
5475 let _generation = RuntimeGenerationGuard::enter(self.generation);
5476 let _ = catch_unwind_contained(|| drop(runnable));
5477 }
5478
5479 fn finish_runnable_claim(
5480 self: &Arc<Self>,
5481 claim: RunnableClaim,
5482 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5483 ) -> bool {
5484 match claim {
5485 RunnableClaim::Empty => {
5486 drop(admission);
5487 false
5488 }
5489 RunnableClaim::Run(runnable) => {
5490 #[cfg(test)]
5491 run_multi_thread_work_claimed_test_hook();
5492 self.run_runnable(runnable, admission);
5493 true
5494 }
5495 RunnableClaim::Cancel(runnable) => {
5496 #[cfg(test)]
5497 run_multi_thread_work_claimed_test_hook();
5498 self.cancel_runnable(runnable, admission);
5499 true
5500 }
5501 }
5502 }
5503
5504 fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
5505 match runnable {
5506 Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
5507 Some(runnable) => RunnableClaim::Run(runnable),
5508 None => RunnableClaim::Empty,
5509 }
5510 }
5511
5512 fn steal_one(&self) -> Option<Runnable> {
5516 loop {
5517 match self.injector.steal() {
5518 Steal::Success(runnable) => return Some(runnable),
5519 Steal::Empty => return None,
5520 Steal::Retry => continue,
5521 }
5522 }
5523 }
5524
5525 #[cfg(test)]
5532 fn push_fifo_raw(&self, runnable: Runnable) {
5533 self.injector.push(runnable);
5534 std::sync::atomic::fence(Ordering::SeqCst);
5535 }
5536
5537 fn claim_fifo_runnable(&self) -> RunnableClaim {
5538 let stop_publication = self.stop.runnable_claim_guard();
5539 let runnable = self.steal_one();
5540 let stopping = stop_publication.is_stopping();
5541 drop(stop_publication);
5542 Self::runnable_claim(stopping, runnable)
5543 }
5544
5545 fn claim_runnable(&self) -> RunnableClaim {
5546 let stop_publication = self.stop.runnable_claim_guard();
5547 let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
5548 let stopping = stop_publication.is_stopping();
5549 drop(stop_publication);
5550 Self::runnable_claim(stopping, runnable)
5551 }
5552
5553 fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
5554 let admission = self.begin_deadlock_admission();
5555 let claim = self.claim_fifo_runnable();
5556 self.finish_runnable_claim(claim, admission)
5557 }
5558
5559 fn run_one_runnable(self: &Arc<Self>) -> bool {
5561 let admission = self.begin_deadlock_admission();
5562 let claim = self.claim_runnable();
5565 self.finish_runnable_claim(claim, admission)
5566 }
5567
5568 fn run_one_blocking(self: &Arc<Self>) -> bool {
5569 if let Some((blocking, admission)) = self.take_blocking() {
5570 let _generation = RuntimeGenerationGuard::enter(self.generation);
5571 {
5572 let _task = self.metrics.blocking_started(true);
5573 #[cfg(test)]
5574 run_multi_thread_work_started_test_hook();
5575 drop(admission);
5578 let owner = self.fresh_owner_token();
5579 let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
5580 let _owner = BlockingOwnerGuard::enter(Some(owner));
5581 let _ = catch_unwind_contained(blocking);
5586 }
5587 self.active_blocking.fetch_sub(1, Ordering::Release);
5588 return true;
5589 }
5590 false
5591 }
5592
5593 fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
5597 self.run_one_fair_with_exact(runnable_streak, None)
5598 }
5599
5600 fn run_one_fair_with_exact(
5604 self: &Arc<Self>,
5605 runnable_streak: &mut usize,
5606 exact_dependency: Option<&TaskDependency>,
5607 ) -> bool {
5608 let mut exact_attempted = false;
5609 if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
5610 *runnable_streak = 0;
5611 if let Some(dependency) = exact_dependency {
5612 exact_attempted = true;
5613 if self.try_owned_blocking_over_cap(dependency) {
5614 return true;
5615 }
5616 }
5617 if self.run_one_blocking() {
5618 return true;
5619 }
5620 }
5621 if self.run_one_runnable() {
5622 *runnable_streak += 1;
5623 return true;
5624 }
5625 if !exact_attempted
5626 && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
5627 {
5628 *runnable_streak = 0;
5629 return true;
5630 }
5631 if self.run_one_blocking() {
5632 *runnable_streak = 0;
5633 return true;
5634 }
5635 false
5636 }
5637
5638 #[cfg(test)]
5639 fn run_one(self: &Arc<Self>) -> bool {
5640 let mut runnable_streak = 0;
5641 self.run_one_fair(&mut runnable_streak)
5642 }
5643
5644 fn run_owned_blocking_over_cap(
5654 self: &Arc<Self>,
5655 dependency: &TaskDependency,
5656 ) -> OwnedBlockingOutcome {
5657 #[cfg(test)]
5658 self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
5659
5660 let Some(ambient_owner) = BLOCKING_OWNER
5661 .with(std::cell::Cell::get)
5662 .filter(|owner| owner.executor_id == self.id)
5663 else {
5664 return OwnedBlockingOutcome::Ineligible;
5665 };
5666 let Some(current) = dependency.select_for_owner(ambient_owner) else {
5667 return OwnedBlockingOutcome::Ineligible;
5668 };
5669 let admission = self.begin_deadlock_admission();
5670 let owner = current
5671 .publication
5672 .dependency
5673 .owner
5674 .expect("selected dependency must carry its owner");
5675 let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
5676 else {
5677 return OwnedBlockingOutcome::OwnerBusy(owner);
5678 };
5679 #[cfg(test)]
5680 run_before_owned_blocking_queue_claim_test_hook();
5681 let job = {
5682 let mut queue = self
5683 .blocking_queue
5684 .lock()
5685 .unwrap_or_else(std::sync::PoisonError::into_inner);
5686 if queue.closed || self.stop.is_stopping() {
5690 None
5691 } else if dependency.try_claim(¤t) {
5695 queue.remove(current.publication.dependency.job)
5696 } else {
5697 None
5698 }
5699 };
5700 let ran = if let Some(job) = job {
5701 #[cfg(test)]
5702 run_multi_thread_work_claimed_test_hook();
5703 let _generation = RuntimeGenerationGuard::enter(self.generation);
5704 {
5705 let _task = self.metrics.blocking_started(false);
5706 #[cfg(test)]
5707 run_multi_thread_work_started_test_hook();
5708 drop(admission);
5711 let nested_owner = self.fresh_owner_token();
5712 let _active_owner =
5713 ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
5714 let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
5715 let _ = catch_unwind_contained(job);
5716 }
5717 true
5718 } else {
5719 drop(admission);
5720 false
5721 };
5722 self.release_owner_lane_and_wake(reservation);
5725 OwnedBlockingOutcome::ReservationReleased { owner, ran }
5726 }
5727
5728 fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
5729 matches!(
5730 self.run_owned_blocking_over_cap(dependency),
5731 OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
5732 )
5733 }
5734
5735 fn service_owner_handoff(
5736 self: &Arc<Self>,
5737 parker: &DriverParker,
5738 dependency: &TaskDependency,
5739 can_run_blocking: bool,
5740 ) -> Option<bool> {
5741 let handoff_owner = parker.take_owner_handoff()?;
5742 if !can_run_blocking {
5743 self.wake_one_owner_dependency(handoff_owner);
5744 return Some(false);
5745 }
5746 match self.run_owned_blocking_over_cap(dependency) {
5747 OwnedBlockingOutcome::Ineligible => {
5748 self.wake_one_owner_dependency(handoff_owner);
5749 Some(false)
5750 }
5751 OwnedBlockingOutcome::OwnerBusy(owner) => {
5752 if owner != handoff_owner {
5753 self.wake_one_owner_dependency(handoff_owner);
5754 }
5755 Some(false)
5756 }
5757 OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
5758 if owner != handoff_owner {
5759 self.wake_one_owner_dependency(handoff_owner);
5760 }
5761 Some(ran)
5762 }
5763 }
5764 }
5765
5766 fn forward_owner_handoff(&self, parker: &DriverParker) {
5767 if let Some(owner) = parker.take_owner_handoff() {
5768 self.wake_one_owner_dependency(owner);
5769 }
5770 }
5771
5772 fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
5773 let Some(ambient_owner) = BLOCKING_OWNER
5774 .with(std::cell::Cell::get)
5775 .filter(|owner| owner.executor_id == self.id)
5776 else {
5777 return false;
5778 };
5779 self.active_blocking_owners.is_available(ambient_owner)
5780 && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
5781 }
5782
5783 fn has_exact_owner_work_for_role(
5784 &self,
5785 parker: &DriverParker,
5786 dependency: &TaskDependency,
5787 can_run_blocking: bool,
5788 ) -> bool {
5789 parker.has_owner_handoff()
5790 || (can_run_blocking && self.owner_dependency_lane_available(dependency))
5791 }
5792
5793 fn cooperative_block_on(
5802 self: &Arc<Self>,
5803 mut future: Pin<&mut dyn Future<Output = ()>>,
5804 ) -> BlockOnOutcome {
5805 let parker = Arc::new(DriverParker::default());
5810 let _stop_registration = self.stop.register(&parker);
5811 let dependency = BlockOnDependencyGuard::enter(self.generation);
5812 let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
5813 let can_run_blocking =
5814 driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
5815 let _exit = CooperativeDriverExitGuard {
5816 executor: self,
5817 parker: &parker,
5818 can_run_blocking,
5819 };
5820 let waker = std::task::Waker::from(Arc::clone(&parker));
5825 let mut cx = Context::from_waker(&waker);
5826 let mut runnable_streak = 0usize;
5834 let mut work_streak = 0usize;
5835 let mut force_fifo = false;
5836 loop {
5837 if self.stop.is_stopping() {
5838 return BlockOnOutcome::Stopped;
5839 }
5840 dependency.clear();
5841 if future.as_mut().poll(&mut cx).is_ready() {
5842 return BlockOnOutcome::Completed;
5843 }
5844 if self.stop.is_stopping() {
5845 return BlockOnOutcome::Stopped;
5846 }
5847 if self
5848 .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
5849 .is_some_and(|ran| ran)
5850 {
5851 continue;
5852 }
5853 if force_fifo {
5854 force_fifo = false;
5855 if self.run_one_fifo_runnable() {
5856 runnable_streak += 1;
5857 work_streak += 1;
5858 continue;
5859 }
5860 }
5861 let ran_work = if can_run_blocking {
5862 self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
5863 } else if self.run_one_runnable() {
5864 runnable_streak += 1;
5865 true
5866 } else {
5867 false
5868 };
5869 if ran_work {
5870 work_streak += 1;
5871 if work_streak >= Self::RUNNABLE_BUDGET {
5872 self.flush_lifo_slot();
5877 work_streak = 0;
5878 force_fifo = true;
5879 }
5880 continue;
5881 }
5882 runnable_streak = 0;
5883 work_streak = 0;
5884 self.flush_lifo_slot();
5889 self.parked_drivers.register_with_role(
5893 &parker,
5894 can_run_blocking,
5895 Some(Arc::clone(&dependency.dependency)),
5896 );
5897 if self.has_queued_work_for_role(can_run_blocking)
5898 || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
5899 {
5900 self.parked_drivers.deregister(&parker);
5901 continue;
5902 }
5903 if self.stop.is_stopping() {
5904 return BlockOnOutcome::Stopped;
5905 }
5906 if let Some(next) = self.next_timer_deadline() {
5921 let now = Instant::now();
5922 if next > now {
5923 parker.park_timeout(next - now);
5924 }
5925 self.parked_drivers.deregister(&parker);
5926 self.fire_due_timers();
5927 continue;
5928 }
5929 match self.park_deadline {
5930 None => parker.park(),
5931 Some(deadline) => {
5932 let deadlock_publications = self.deadlock_state.publications();
5938 match park_with_deadline(&parker, deadline, &self.metrics) {
5939 DeadlineParkOutcome::Woken => {}
5940 DeadlineParkOutcome::Expired(armed) => {
5941 #[cfg(test)]
5942 run_deadline_expiry_test_hook();
5943 self.parked_drivers.deregister(&parker);
5980 if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
5981 continue;
5982 }
5983 #[cfg(test)]
5986 run_deadline_verdict_test_hook();
5987 if self.metrics.progress_fingerprint() != armed {
5988 continue;
5989 }
5990 if self.next_timer_deadline().is_some() {
5999 continue;
6000 }
6001 #[cfg(test)]
6004 run_deadline_final_verdict_test_hook();
6005 let Some(verdict) = self.try_begin_deadlock_verdict() else {
6006 continue;
6007 };
6008 #[cfg(test)]
6009 run_deadline_gated_verdict_test_hook();
6010 let stop_publication = self.stop.publication_guard();
6015 #[cfg(test)]
6016 run_deadline_synchronized_stop_test_hook();
6017 if stop_publication.is_stopping() {
6018 return BlockOnOutcome::Stopped;
6019 }
6020 let owner_handoff_publication = self
6021 .deadlock_state
6022 .owner_handoff_publication_guard()
6023 .expect("a deadline verdict requires exact-owner publication");
6024 #[cfg(test)]
6025 run_deadline_exact_owner_verdict_test_hook(&parker);
6026 if self.has_exact_owner_work_for_role(
6034 &parker,
6035 &dependency.dependency,
6036 can_run_blocking,
6037 ) || parker.consume_permit()
6038 || self.has_queued_work_for_role(can_run_blocking)
6039 || self.deadlock_state.publications() != deadlock_publications
6040 || self.metrics.progress_fingerprint() != armed
6041 || self.next_timer_deadline().is_some()
6042 {
6043 continue;
6044 }
6045 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6046 drop(owner_handoff_publication);
6051 drop(stop_publication);
6052 drop(verdict);
6053 std::panic::panic_any(diagnostic);
6054 }
6055 }
6056 }
6057 }
6058 self.parked_drivers.deregister(&parker);
6061 }
6062 }
6063
6064 fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6070 if self.has_queued_runnable() {
6071 return true;
6072 }
6073 can_run_blocking
6074 && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6075 && !self
6076 .blocking_queue
6077 .lock()
6078 .unwrap_or_else(std::sync::PoisonError::into_inner)
6079 .is_empty()
6080 }
6081
6082 fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6083 if self.has_queued_runnable() {
6084 self.wake_for_new_work();
6085 }
6086 if self.stop.is_stopping() {
6090 return;
6091 }
6092 if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6093 && !self
6094 .blocking_queue
6095 .lock()
6096 .unwrap_or_else(std::sync::PoisonError::into_inner)
6097 .is_empty()
6098 {
6099 let consumed = can_run_blocking && self.run_one_blocking();
6107 let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6108 && !self
6109 .blocking_queue
6110 .lock()
6111 .unwrap_or_else(std::sync::PoisonError::into_inner)
6112 .is_empty();
6113 if !self.stop.is_stopping() && (!consumed || has_residue) {
6114 self.wake_for_blocking_work();
6115 }
6116 }
6117 }
6118
6119 fn has_queued_runnable(&self) -> bool {
6120 std::sync::atomic::fence(Ordering::SeqCst);
6127 !self.injector.is_empty()
6128 }
6129
6130 fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6134 let admission = self.begin_deadlock_admission();
6135 loop {
6136 let active = self.active_blocking.load(Ordering::Acquire);
6137 if active >= self.max_blocking {
6138 return None;
6139 }
6140 let mut queue = self
6141 .blocking_queue
6142 .lock()
6143 .unwrap_or_else(std::sync::PoisonError::into_inner);
6144 if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6145 return None;
6146 }
6147 if self
6148 .active_blocking
6149 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6150 .is_ok()
6151 {
6152 let blocking = queue
6153 .pop_front()
6154 .expect("a non-empty blocking queue must have a FIFO head");
6155 drop(queue);
6156 #[cfg(test)]
6157 run_multi_thread_work_claimed_test_hook();
6158 return Some((blocking, admission));
6159 }
6160 }
6161 }
6162}
6163
6164#[cfg(not(target_family = "wasm"))]
6165struct MultiThreadDeadlockState {
6166 enabled: bool,
6167 verdict_closed: AtomicBool,
6168 admissions: AtomicUsize,
6169 publications: AtomicU64,
6170 owner_handoff_publication: Mutex<()>,
6171}
6172
6173#[cfg(not(target_family = "wasm"))]
6174impl MultiThreadDeadlockState {
6175 fn new(enabled: bool) -> Self {
6176 Self {
6177 enabled,
6178 verdict_closed: AtomicBool::new(false),
6179 admissions: AtomicUsize::new(0),
6180 publications: AtomicU64::new(0),
6181 owner_handoff_publication: Mutex::new(()),
6182 }
6183 }
6184
6185 fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6186 if !self.enabled {
6187 return None;
6188 }
6189 loop {
6190 while self.verdict_closed.load(Ordering::SeqCst) {
6191 #[cfg(test)]
6192 run_deadlock_closed_gate_admission_test_hook();
6193 std::hint::spin_loop();
6194 }
6195 self.admissions.fetch_add(1, Ordering::SeqCst);
6196 if !self.verdict_closed.load(Ordering::SeqCst) {
6197 return Some(MultiThreadDeadlockAdmissionGuard {
6198 state: Arc::clone(self),
6199 published: false,
6200 });
6201 }
6202 self.admissions.fetch_sub(1, Ordering::SeqCst);
6203 }
6204 }
6205
6206 fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6207 debug_assert!(
6208 self.enabled,
6209 "a deadline verdict requires an enabled admission state"
6210 );
6211 if self
6212 .verdict_closed
6213 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6214 .is_err()
6215 {
6216 return None;
6217 }
6218 if self.admissions.load(Ordering::SeqCst) != 0 {
6219 self.verdict_closed.store(false, Ordering::SeqCst);
6220 return None;
6221 }
6222 Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6223 }
6224
6225 fn publications(&self) -> u64 {
6226 self.publications.load(Ordering::SeqCst)
6227 }
6228
6229 fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
6230 self.enabled.then(|| {
6231 self
6232 .owner_handoff_publication
6233 .lock()
6234 .unwrap_or_else(std::sync::PoisonError::into_inner)
6235 })
6236 }
6237}
6238
6239#[cfg(not(target_family = "wasm"))]
6240struct MultiThreadDeadlockAdmissionGuard {
6241 state: Arc<MultiThreadDeadlockState>,
6242 published: bool,
6243}
6244
6245#[cfg(not(target_family = "wasm"))]
6246impl MultiThreadDeadlockAdmissionGuard {
6247 fn mark_published(&mut self) {
6248 self.published = true;
6249 }
6250}
6251
6252#[cfg(not(target_family = "wasm"))]
6253impl Drop for MultiThreadDeadlockAdmissionGuard {
6254 fn drop(&mut self) {
6255 if self.published {
6256 self.state.publications.fetch_add(1, Ordering::SeqCst);
6257 }
6258 self.state.admissions.fetch_sub(1, Ordering::SeqCst);
6261 }
6262}
6263
6264#[cfg(not(target_family = "wasm"))]
6265struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
6266
6267#[cfg(not(target_family = "wasm"))]
6268impl Drop for MultiThreadDeadlockVerdictGuard {
6269 fn drop(&mut self) {
6270 self.0.verdict_closed.store(false, Ordering::SeqCst);
6271 }
6272}
6273
6274#[cfg(not(target_family = "wasm"))]
6280struct CooperativeDriverExitGuard<'a> {
6281 executor: &'a Arc<MultiThreadExecutor>,
6282 parker: &'a Arc<DriverParker>,
6283 can_run_blocking: bool,
6284}
6285
6286#[cfg(not(target_family = "wasm"))]
6287impl Drop for CooperativeDriverExitGuard<'_> {
6288 fn drop(&mut self) {
6289 let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
6290 let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
6291 let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
6292 let _ = catch_unwind_contained(|| {
6293 self
6294 .executor
6295 .compensate_queued_work_wake(self.can_run_blocking);
6296 });
6297 }
6298}
6299
6300#[cfg(not(target_family = "wasm"))]
6306struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
6307
6308#[cfg(not(target_family = "wasm"))]
6309impl Drop for LifoSlotFlushGuard<'_> {
6310 fn drop(&mut self) {
6311 self.0.flush_lifo_slot();
6312 }
6313}
6314
6315#[cfg(not(target_family = "wasm"))]
6322struct DrainLinger {
6323 parker: Arc<DriverParker>,
6324 _stop_registration: GenerationStopGuard,
6325}
6326
6327#[cfg(not(target_family = "wasm"))]
6328impl DrainLinger {
6329 fn new(executor: &MultiThreadExecutor) -> Self {
6330 let parker = Arc::new(DriverParker::default());
6331 let stop_registration = executor.stop.register(&parker);
6332 Self {
6333 parker,
6334 _stop_registration: stop_registration,
6335 }
6336 }
6337}
6338
6339const PARKER_EMPTY: usize = 0;
6361const PARKER_NOTIFIED: usize = 1;
6363const PARKER_SLEEPING: usize = 2;
6365
6366#[derive(Default)]
6370struct DriverParker {
6371 state: AtomicUsize,
6372 lock: Mutex<()>,
6373 condvar: std::sync::Condvar,
6374 #[cfg(not(target_family = "wasm"))]
6375 owner_handoff_pending: AtomicBool,
6376 #[cfg(not(target_family = "wasm"))]
6377 owner_handoff: Mutex<Option<BlockingOwnerToken>>,
6378}
6379
6380impl DriverParker {
6381 #[cfg(not(target_family = "wasm"))]
6382 fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
6383 let mut pending = self
6384 .owner_handoff
6385 .lock()
6386 .unwrap_or_else(std::sync::PoisonError::into_inner);
6387 debug_assert!(pending.is_none());
6388 *pending = Some(owner);
6389 self.owner_handoff_pending.store(true, Ordering::Release);
6390 }
6391
6392 #[cfg(not(target_family = "wasm"))]
6393 fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
6394 if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
6395 return None;
6396 }
6397 self
6398 .owner_handoff
6399 .lock()
6400 .unwrap_or_else(std::sync::PoisonError::into_inner)
6401 .take()
6402 }
6403
6404 #[cfg(not(target_family = "wasm"))]
6405 fn has_owner_handoff(&self) -> bool {
6406 self.owner_handoff_pending.load(Ordering::Acquire)
6407 }
6408
6409 fn unpark(&self) {
6416 if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
6417 drop(
6422 self
6423 .lock
6424 .lock()
6425 .unwrap_or_else(std::sync::PoisonError::into_inner),
6426 );
6427 self.condvar.notify_one();
6428 }
6429 }
6430
6431 fn consume_permit(&self) -> bool {
6436 self
6437 .state
6438 .compare_exchange(
6439 PARKER_NOTIFIED,
6440 PARKER_EMPTY,
6441 Ordering::SeqCst,
6442 Ordering::SeqCst,
6443 )
6444 .is_ok()
6445 }
6446
6447 fn park(&self) {
6451 if self.consume_permit() {
6453 return;
6454 }
6455 let mut guard = self
6456 .lock
6457 .lock()
6458 .unwrap_or_else(std::sync::PoisonError::into_inner);
6459 match self.state.compare_exchange(
6460 PARKER_EMPTY,
6461 PARKER_SLEEPING,
6462 Ordering::SeqCst,
6463 Ordering::SeqCst,
6464 ) {
6465 Ok(_) => {}
6466 Err(_) => {
6470 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6471 return;
6472 }
6473 }
6474 loop {
6475 guard = self
6476 .condvar
6477 .wait(guard)
6478 .unwrap_or_else(std::sync::PoisonError::into_inner);
6479 if self
6480 .state
6481 .compare_exchange(
6482 PARKER_NOTIFIED,
6483 PARKER_EMPTY,
6484 Ordering::SeqCst,
6485 Ordering::SeqCst,
6486 )
6487 .is_ok()
6488 {
6489 return;
6490 }
6491 }
6493 }
6494
6495 fn park_timeout(&self, timeout: Duration) -> bool {
6501 if self.consume_permit() {
6502 return true;
6503 }
6504 let mut guard = self
6505 .lock
6506 .lock()
6507 .unwrap_or_else(std::sync::PoisonError::into_inner);
6508 match self.state.compare_exchange(
6509 PARKER_EMPTY,
6510 PARKER_SLEEPING,
6511 Ordering::SeqCst,
6512 Ordering::SeqCst,
6513 ) {
6514 Ok(_) => {}
6515 Err(_) => {
6519 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6520 return true;
6521 }
6522 }
6523 let Some(deadline) = Instant::now().checked_add(timeout) else {
6524 loop {
6528 guard = self
6529 .condvar
6530 .wait(guard)
6531 .unwrap_or_else(std::sync::PoisonError::into_inner);
6532 if self
6533 .state
6534 .compare_exchange(
6535 PARKER_NOTIFIED,
6536 PARKER_EMPTY,
6537 Ordering::SeqCst,
6538 Ordering::SeqCst,
6539 )
6540 .is_ok()
6541 {
6542 return true;
6543 }
6544 }
6545 };
6546 loop {
6547 if self
6548 .state
6549 .compare_exchange(
6550 PARKER_NOTIFIED,
6551 PARKER_EMPTY,
6552 Ordering::SeqCst,
6553 Ordering::SeqCst,
6554 )
6555 .is_ok()
6556 {
6557 return true;
6558 }
6559 let now = Instant::now();
6560 if now >= deadline {
6561 return match self.state.compare_exchange(
6566 PARKER_SLEEPING,
6567 PARKER_EMPTY,
6568 Ordering::SeqCst,
6569 Ordering::SeqCst,
6570 ) {
6571 Ok(_) => false,
6572 Err(_) => {
6573 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6574 true
6575 }
6576 };
6577 }
6578 let (returned_guard, _timed_out) = self
6579 .condvar
6580 .wait_timeout(guard, deadline - now)
6581 .unwrap_or_else(std::sync::PoisonError::into_inner);
6582 guard = returned_guard;
6583 }
6586 }
6587}
6588
6589impl std::task::Wake for DriverParker {
6590 fn wake(self: Arc<Self>) {
6591 self.unpark();
6592 }
6593
6594 fn wake_by_ref(self: &Arc<Self>) {
6595 self.unpark();
6596 }
6597}
6598
6599enum DeadlineParkOutcome {
6601 Woken,
6603 Expired(ProgressFingerprint),
6612}
6613
6614fn park_with_deadline(
6623 parker: &DriverParker,
6624 deadline: Duration,
6625 metrics: &RuntimeMetrics,
6626) -> DeadlineParkOutcome {
6627 let mut armed = metrics.progress_fingerprint();
6628 loop {
6629 if parker.park_timeout(deadline) {
6630 return DeadlineParkOutcome::Woken;
6631 }
6632 let current = metrics.progress_fingerprint();
6633 if current != armed {
6634 armed = current;
6635 continue;
6636 }
6637 return DeadlineParkOutcome::Expired(armed);
6638 }
6639}
6640
6641#[cfg(all(test, not(target_family = "wasm")))]
6649thread_local! {
6650 static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6651 const { std::cell::RefCell::new(None) };
6652}
6653
6654#[cfg(all(test, not(target_family = "wasm")))]
6655fn run_deadline_expiry_test_hook() {
6656 if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6657 hook();
6658 }
6659}
6660
6661#[cfg(all(test, not(target_family = "wasm")))]
6668thread_local! {
6669 static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6670 const { std::cell::RefCell::new(None) };
6671}
6672
6673#[cfg(all(test, not(target_family = "wasm")))]
6674fn run_deadline_verdict_test_hook() {
6675 if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6676 hook();
6677 }
6678}
6679
6680#[cfg(all(test, not(target_family = "wasm")))]
6685thread_local! {
6686 static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6687 const { std::cell::RefCell::new(None) };
6688}
6689
6690#[cfg(all(test, not(target_family = "wasm")))]
6691fn run_deadline_final_verdict_test_hook() {
6692 if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6693 hook();
6694 }
6695}
6696
6697#[cfg(all(test, not(target_family = "wasm")))]
6702thread_local! {
6703 static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6704 const { std::cell::RefCell::new(None) };
6705}
6706
6707#[cfg(all(test, not(target_family = "wasm")))]
6708fn run_deadline_gated_verdict_test_hook() {
6709 if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6710 hook();
6711 }
6712}
6713
6714#[cfg(all(test, not(target_family = "wasm")))]
6718thread_local! {
6719 static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6720 const { std::cell::RefCell::new(None) };
6721}
6722
6723#[cfg(all(test, not(target_family = "wasm")))]
6724fn run_deadline_synchronized_stop_test_hook() {
6725 if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6726 hook();
6727 }
6728}
6729
6730#[cfg(all(test, not(target_family = "wasm")))]
6734type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
6735
6736#[cfg(all(test, not(target_family = "wasm")))]
6737thread_local! {
6738 static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
6739 std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
6740 const { std::cell::RefCell::new(None) };
6741}
6742
6743#[cfg(all(test, not(target_family = "wasm")))]
6744fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
6745 if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6746 hook(parker);
6747 }
6748}
6749
6750#[cfg(all(test, not(target_family = "wasm")))]
6754thread_local! {
6755 static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
6756 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6757 const { std::cell::RefCell::new(None) };
6758}
6759
6760#[cfg(all(test, not(target_family = "wasm")))]
6761fn run_before_generation_stop_publication_lock_test_hook() {
6762 if let Some(hook) =
6763 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6764 {
6765 hook();
6766 }
6767}
6768
6769#[cfg(all(test, not(target_family = "wasm")))]
6773thread_local! {
6774 static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
6775 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6776 const { std::cell::RefCell::new(None) };
6777}
6778
6779#[cfg(all(test, not(target_family = "wasm")))]
6780fn run_deadlock_closed_gate_admission_test_hook() {
6781 if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6782 {
6783 hook();
6784 }
6785}
6786
6787#[cfg(all(test, not(target_family = "wasm")))]
6791thread_local! {
6792 static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6793 const { std::cell::RefCell::new(None) };
6794}
6795
6796#[cfg(all(test, not(target_family = "wasm")))]
6797fn run_timer_registration_admission_test_hook() {
6798 if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6799 hook();
6800 }
6801}
6802
6803#[cfg(all(test, not(target_family = "wasm")))]
6804thread_local! {
6805 static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6806 const { std::cell::RefCell::new(None) };
6807}
6808
6809#[cfg(all(test, not(target_family = "wasm")))]
6810fn run_owner_handoff_selected_test_hook() {
6811 if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6812 hook();
6813 }
6814}
6815
6816#[cfg(not(target_family = "wasm"))]
6819#[derive(Default)]
6820struct ParkedDrivers {
6821 parked: Mutex<Vec<ParkedDriver>>,
6822 count: AtomicUsize,
6825}
6826
6827#[cfg(not(target_family = "wasm"))]
6828struct ParkedDriver {
6829 parker: Arc<DriverParker>,
6830 can_run_blocking: bool,
6831 dependency: Option<Arc<TaskDependency>>,
6832}
6833
6834#[cfg(not(target_family = "wasm"))]
6835impl ParkedDrivers {
6836 #[cfg(test)]
6839 fn register(&self, parker: &Arc<DriverParker>) {
6840 self.register_with_role(parker, true, None);
6841 }
6842
6843 fn register_with_role(
6844 &self,
6845 parker: &Arc<DriverParker>,
6846 can_run_blocking: bool,
6847 dependency: Option<Arc<TaskDependency>>,
6848 ) {
6849 let mut parked = self
6850 .parked
6851 .lock()
6852 .unwrap_or_else(std::sync::PoisonError::into_inner);
6853 parked.push(ParkedDriver {
6854 parker: Arc::clone(parker),
6855 can_run_blocking,
6856 dependency,
6857 });
6858 self.count.store(parked.len(), Ordering::SeqCst);
6859 }
6860
6861 fn deregister(&self, parker: &Arc<DriverParker>) {
6865 let removed = {
6866 let mut parked = self
6867 .parked
6868 .lock()
6869 .unwrap_or_else(std::sync::PoisonError::into_inner);
6870 let removed = parked
6871 .iter()
6872 .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
6873 .map(|index| parked.swap_remove(index));
6874 self.count.store(parked.len(), Ordering::SeqCst);
6875 removed
6876 };
6877 drop(removed);
6878 }
6879
6880 fn wake_one(&self) -> bool {
6903 if self.count.load(Ordering::SeqCst) == 0 {
6904 return false;
6908 }
6909 let driver = {
6910 let mut parked = self
6911 .parked
6912 .lock()
6913 .unwrap_or_else(std::sync::PoisonError::into_inner);
6914 let driver = parked.pop();
6915 self.count.store(parked.len(), Ordering::SeqCst);
6916 driver
6917 };
6918 match driver {
6919 Some(driver) => {
6920 driver.parker.unpark();
6921 true
6922 }
6923 None => false,
6924 }
6925 }
6926
6927 fn wake_one_blocking(&self) -> bool {
6930 if self.count.load(Ordering::SeqCst) == 0 {
6931 return false;
6932 }
6933 let driver = {
6934 let mut parked = self
6935 .parked
6936 .lock()
6937 .unwrap_or_else(std::sync::PoisonError::into_inner);
6938 let driver = parked
6939 .iter()
6940 .rposition(|driver| driver.can_run_blocking)
6941 .map(|index| parked.swap_remove(index));
6942 self.count.store(parked.len(), Ordering::SeqCst);
6943 driver
6944 };
6945 match driver {
6946 Some(driver) => {
6947 driver.parker.unpark();
6948 true
6949 }
6950 None => false,
6951 }
6952 }
6953
6954 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
6959 loop {
6960 if self.count.load(Ordering::SeqCst) == 0 {
6961 return false;
6962 }
6963 let candidates = {
6964 let parked = self
6965 .parked
6966 .lock()
6967 .unwrap_or_else(std::sync::PoisonError::into_inner);
6968 parked
6969 .iter()
6970 .rev()
6971 .filter_map(|driver| {
6972 if !driver.can_run_blocking {
6973 return None;
6974 }
6975 driver
6976 .dependency
6977 .as_ref()
6978 .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
6979 })
6980 .collect::<Vec<_>>()
6981 };
6982 let selected = candidates.into_iter().find(|(_, dependency)| {
6983 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
6984 });
6985 let Some((selected_parker, selected_dependency)) = selected else {
6986 return false;
6987 };
6988 let driver = {
6989 let mut parked = self
6990 .parked
6991 .lock()
6992 .unwrap_or_else(std::sync::PoisonError::into_inner);
6993 let driver = parked
6994 .iter()
6995 .rposition(|driver| {
6996 Arc::ptr_eq(&driver.parker, &selected_parker)
6997 && driver
6998 .dependency
6999 .as_ref()
7000 .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7001 })
7002 .map(|index| parked.swap_remove(index));
7003 if let Some(driver) = &driver {
7008 driver.parker.grant_owner_handoff(owner);
7009 }
7010 self.count.store(parked.len(), Ordering::SeqCst);
7011 driver
7012 };
7013 if let Some(driver) = driver {
7014 #[cfg(test)]
7015 run_owner_handoff_selected_test_hook();
7016 driver.parker.unpark();
7017 return true;
7018 }
7019 }
7020 }
7021
7022 fn wake_all(&self) {
7025 if self.count.load(Ordering::SeqCst) == 0 {
7026 return;
7027 }
7028 let drivers = {
7029 let mut parked = self
7030 .parked
7031 .lock()
7032 .unwrap_or_else(std::sync::PoisonError::into_inner);
7033 let drivers = parked.drain(..).collect::<Vec<_>>();
7034 self.count.store(0, Ordering::SeqCst);
7035 drivers
7036 };
7037 for driver in drivers {
7038 driver.parker.unpark();
7039 }
7040 }
7041}
7042
7043pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7088 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7101
7102 fn is_live(&self) -> bool {
7103 true
7104 }
7105
7106 fn on_swept(&self) {}
7109}
7110
7111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7112pub struct CurrentThreadTaskDriverId(u64);
7113
7114type CurrentThreadTaskDriverSnapshot =
7115 ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7116
7117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7118pub struct CurrentThreadTaskDelivery {
7119 driver: CurrentThreadTaskDriverId,
7120 capability: u64,
7121 dispatch: u64,
7122}
7123
7124impl CurrentThreadTaskDelivery {
7125 pub fn capability(self) -> u64 {
7127 self.capability
7128 }
7129}
7130
7131struct CurrentThreadTaskDeliveryState {
7132 delivery: CurrentThreadTaskDelivery,
7133 claimed: bool,
7134 accepted: bool,
7135}
7136
7137struct CurrentThreadTaskDriverEntry {
7138 driver: Arc<dyn CurrentThreadTaskDriver>,
7139 delivery: Option<CurrentThreadTaskDeliveryState>,
7140 pending: Option<u64>,
7141 previous: Option<CurrentThreadTaskDriverId>,
7142 next: Option<CurrentThreadTaskDriverId>,
7143}
7144
7145#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7146enum CurrentThreadDispatchPhase {
7147 #[default]
7148 Pending,
7149 Serviced,
7150 Cancelling,
7151}
7152
7153#[derive(Default)]
7154struct CurrentThreadDispatchState {
7155 phase: CurrentThreadDispatchPhase,
7156 epoch: u64,
7157 publications: usize,
7158 references: usize,
7159 accepted: bool,
7160 accepted_failure: bool,
7161}
7162
7163impl CurrentThreadDispatchState {
7164 fn is_serviced(&self) -> bool {
7165 self.phase == CurrentThreadDispatchPhase::Serviced
7166 }
7167
7168 fn is_cancelling(&self) -> bool {
7169 self.phase == CurrentThreadDispatchPhase::Cancelling
7170 }
7171
7172 fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7173 CurrentThreadTaskDispatchFailure {
7174 dispatch,
7175 epoch: self.epoch,
7176 }
7177 }
7178}
7179
7180#[derive(Default)]
7181struct CurrentThreadTaskDriverRegistryState {
7182 entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7183 oldest: Option<CurrentThreadTaskDriverId>,
7184 newest: Option<CurrentThreadTaskDriverId>,
7185 delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7186 dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7187}
7188
7189impl CurrentThreadTaskDriverRegistryState {
7190 fn insert_driver(
7191 &mut self,
7192 id: CurrentThreadTaskDriverId,
7193 driver: Arc<dyn CurrentThreadTaskDriver>,
7194 ) {
7195 let previous = self.newest;
7196 let replaced = self.entries.insert(
7197 id,
7198 CurrentThreadTaskDriverEntry {
7199 driver,
7200 delivery: None,
7201 pending: None,
7202 previous,
7203 next: None,
7204 },
7205 );
7206 assert!(
7207 replaced.is_none(),
7208 "CurrentThread task driver id must be unique"
7209 );
7210 if let Some(previous) = previous {
7211 self
7212 .entries
7213 .get_mut(&previous)
7214 .expect("the newest CurrentThread task driver must remain registered")
7215 .next = Some(id);
7216 } else {
7217 self.oldest = Some(id);
7218 }
7219 self.newest = Some(id);
7220 }
7221
7222 fn remove_driver(
7223 &mut self,
7224 id: CurrentThreadTaskDriverId,
7225 ) -> Option<CurrentThreadTaskDriverEntry> {
7226 let entry = self.entries.remove(&id)?;
7227 if let Some(previous) = entry.previous {
7228 self
7229 .entries
7230 .get_mut(&previous)
7231 .expect("the previous CurrentThread task driver must remain registered")
7232 .next = entry.next;
7233 } else {
7234 self.oldest = entry.next;
7235 }
7236 if let Some(next) = entry.next {
7237 self
7238 .entries
7239 .get_mut(&next)
7240 .expect("the next CurrentThread task driver must remain registered")
7241 .previous = entry.previous;
7242 } else {
7243 self.newest = entry.previous;
7244 }
7245 if let Some(delivery) = &entry.delivery {
7246 let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
7247 debug_assert_eq!(
7248 indexed,
7249 Some(id),
7250 "delivery capability index must match its driver"
7251 );
7252 }
7253 Some(entry)
7254 }
7255
7256 fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7257 let mut snapshot = Vec::with_capacity(self.entries.len());
7258 let mut current = self.oldest;
7259 while let Some(id) = current {
7260 let entry = self
7261 .entries
7262 .get(&id)
7263 .expect("CurrentThread task driver order must reference a live entry");
7264 snapshot.push((id, Arc::clone(&entry.driver)));
7265 current = entry.next;
7266 }
7267 debug_assert_eq!(
7268 snapshot.len(),
7269 self.entries.len(),
7270 "CurrentThread task driver order must contain every entry exactly once"
7271 );
7272 snapshot
7273 }
7274
7275 fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
7276 if self.entries.len() != expected.len() {
7277 return false;
7278 }
7279 let mut current = self.oldest;
7280 for expected in expected {
7281 if current != Some(*expected) {
7282 return false;
7283 }
7284 current = self
7285 .entries
7286 .get(expected)
7287 .expect("CurrentThread task driver order must reference a live entry")
7288 .next;
7289 }
7290 current.is_none()
7291 }
7292}
7293
7294struct CurrentThreadTaskDriverSelection {
7295 live: CurrentThreadTaskDriverSnapshot,
7296 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7297}
7298
7299#[derive(Default)]
7300struct CurrentThreadTaskDeliveryCompletion {
7301 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7302 redispatch: bool,
7303}
7304
7305struct CurrentThreadTaskDispatchCompletion {
7306 result: CurrentThreadHostDispatchResult,
7307 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7308}
7309
7310struct CurrentThreadTaskDispatchPublicationGuard<'a> {
7311 registry: &'a CurrentThreadTaskDriverRegistry,
7312 dispatch: Option<u64>,
7313}
7314
7315impl CurrentThreadTaskDispatchPublicationGuard<'_> {
7316 fn finish(mut self) -> CurrentThreadHostDispatchResult {
7317 let dispatch = self
7318 .dispatch
7319 .expect("the CurrentThread task dispatch publication must remain armed");
7320 let result = self.registry.finish_publication(dispatch);
7321 self.dispatch = None;
7322 result
7323 }
7324}
7325
7326impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
7327 fn drop(&mut self) {
7328 if let Some(dispatch) = self.dispatch.take() {
7329 self.registry.abort_publication(dispatch);
7330 }
7331 }
7332}
7333
7334#[derive(Default)]
7335struct CurrentThreadTaskDriverRegistry {
7336 state: Mutex<CurrentThreadTaskDriverRegistryState>,
7337 next_id: AtomicU64,
7338 next_delivery: AtomicU64,
7339}
7340
7341impl CurrentThreadTaskDriverRegistry {
7342 fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
7343 let id = next_unique_id(
7344 &self.next_id,
7345 "CurrentThread task driver id space exhausted",
7346 );
7347 let id = CurrentThreadTaskDriverId(id);
7348 self
7349 .state
7350 .lock()
7351 .unwrap_or_else(std::sync::PoisonError::into_inner)
7352 .insert_driver(id, driver);
7353 id
7354 }
7355
7356 fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
7357 let (removed, failed_dispatches) = {
7358 let mut state = self
7359 .state
7360 .lock()
7361 .unwrap_or_else(std::sync::PoisonError::into_inner);
7362 let removed = state.remove_driver(id);
7363 let mut failed_dispatches = Vec::new();
7364 if let Some(entry) = &removed {
7365 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
7366 }
7367 (removed, failed_dispatches)
7368 };
7369 let _ = catch_unwind_contained(|| drop(removed));
7370 CurrentThreadTaskDeliveryCompletion {
7371 failed_dispatches,
7372 redispatch: false,
7373 }
7374 }
7375
7376 fn live(&self) -> CurrentThreadTaskDriverSelection {
7377 let mut failed_dispatches = Vec::new();
7378 loop {
7379 let snapshot = CurrentThreadTaskDriverSnapshot::new({
7380 let state = self
7381 .state
7382 .lock()
7383 .unwrap_or_else(std::sync::PoisonError::into_inner);
7384 state.ordered_snapshot()
7385 });
7386 let mut dead_ids = FxHashSet::default();
7387 let mut live_ids = Vec::new();
7388 for (id, driver) in snapshot.as_slice() {
7389 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
7390 live_ids.push(*id);
7391 } else {
7392 dead_ids.insert(*id);
7393 }
7394 }
7395
7396 let (live, swept, retry) = {
7397 let mut state = self
7398 .state
7399 .lock()
7400 .unwrap_or_else(std::sync::PoisonError::into_inner);
7401 let mut swept = Vec::new();
7402 let mut current = state.oldest;
7403 while let Some(id) = current {
7404 let next = state
7405 .entries
7406 .get(&id)
7407 .expect("CurrentThread task driver order must reference a live entry")
7408 .next;
7409 if dead_ids.contains(&id)
7410 && let Some(entry) = state.remove_driver(id)
7411 {
7412 swept.push(entry);
7413 }
7414 current = next;
7415 }
7416 for entry in &swept {
7417 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
7418 }
7419 let retry = !state.ordered_ids_match(&live_ids);
7420 let live = if retry {
7421 Vec::new()
7422 } else {
7423 state.ordered_snapshot()
7424 };
7425 (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
7426 };
7427
7428 drop(snapshot);
7431 for entry in swept {
7432 let _ = catch_unwind_contained(|| entry.driver.on_swept());
7433 let _ = catch_unwind_contained(|| drop(entry));
7434 }
7435 if !retry {
7436 return CurrentThreadTaskDriverSelection {
7437 live,
7438 failed_dispatches,
7439 };
7440 }
7441 }
7442 }
7443
7444 #[cfg(test)]
7445 fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7446 let mut live = self.live().live;
7447 live.pop().map(|(id, driver)| (id, driver.into_inner()))
7448 }
7449
7450 fn next_delivery(
7451 &self,
7452 driver: CurrentThreadTaskDriverId,
7453 dispatch: u64,
7454 ) -> CurrentThreadTaskDelivery {
7455 let capability = next_unique_id(
7456 &self.next_delivery,
7457 "CurrentThread task delivery id space exhausted",
7458 )
7459 .checked_add(1)
7460 .expect("CurrentThread task delivery id space exhausted");
7461 CurrentThreadTaskDelivery {
7462 driver,
7463 capability,
7464 dispatch,
7465 }
7466 }
7467
7468 fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
7469 let dispatch_state = state
7470 .dispatches
7471 .get_mut(&dispatch)
7472 .expect("a referenced CurrentThread dispatch must remain registered");
7473 dispatch_state.references = dispatch_state
7474 .references
7475 .checked_add(1)
7476 .expect("CurrentThread dispatch reference count exhausted");
7477 }
7478
7479 fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
7480 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7481 return;
7482 };
7483 dispatch_state.references = dispatch_state
7484 .references
7485 .checked_sub(1)
7486 .expect("CurrentThread dispatch reference count underflow");
7487 }
7488
7489 fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
7490 let publication = {
7491 let mut state = self
7492 .state
7493 .lock()
7494 .unwrap_or_else(std::sync::PoisonError::into_inner);
7495 let dispatch_state = state.dispatches.entry(dispatch).or_default();
7496 if dispatch_state.is_serviced() {
7497 return CurrentThreadTaskDispatchCompletion {
7498 result: CurrentThreadHostDispatchResult::Accepted,
7499 failed_dispatches: Vec::new(),
7500 };
7501 }
7502 if dispatch_state.is_cancelling() {
7503 return CurrentThreadTaskDispatchCompletion {
7504 result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
7505 failed_dispatches: Vec::new(),
7506 };
7507 }
7508 dispatch_state.publications = dispatch_state
7509 .publications
7510 .checked_add(1)
7511 .expect("CurrentThread dispatch publication count exhausted");
7512 CurrentThreadTaskDispatchPublicationGuard {
7513 registry: self,
7514 dispatch: Some(dispatch),
7515 }
7516 };
7517
7518 let CurrentThreadTaskDriverSelection {
7519 mut live,
7520 mut failed_dispatches,
7521 } = self.live();
7522 let planned_deliveries = {
7523 let mut state = self
7524 .state
7525 .lock()
7526 .unwrap_or_else(std::sync::PoisonError::into_inner);
7527 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
7528 return CurrentThreadTaskDispatchCompletion {
7529 result: CurrentThreadHostDispatchResult::Unavailable,
7530 failed_dispatches,
7531 };
7532 };
7533 if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
7534 Vec::new()
7535 } else {
7536 let mut reserved_deliveries = Vec::with_capacity(live.len());
7540 for (id, _) in live.as_slice().iter().rev() {
7541 let Some(entry) = state.entries.get(id) else {
7542 continue;
7543 };
7544 if entry.delivery.is_none() {
7545 reserved_deliveries.push(self.next_delivery(*id, dispatch));
7546 }
7547 }
7548 state.delivery_drivers.reserve(reserved_deliveries.len());
7549 let mut reserved_deliveries = reserved_deliveries.into_iter();
7550 let mut planned_deliveries = Vec::with_capacity(live.len());
7551 let mut retired = Vec::with_capacity(live.len());
7552 let mut accepted_reservation = false;
7553 for (id, _) in live.as_slice().iter().rev() {
7554 let Some(entry) = state.entries.get(id) else {
7555 continue;
7556 };
7557 if let Some(delivery) = &entry.delivery
7558 && delivery.delivery.dispatch == dispatch
7559 {
7560 accepted_reservation |= delivery.accepted;
7561 continue;
7562 }
7563 if entry.delivery.is_some() {
7564 let previous = state
7565 .entries
7566 .get_mut(id)
7567 .expect("the CurrentThread task driver must remain registered")
7568 .pending
7569 .replace(dispatch);
7570 if previous != Some(dispatch) {
7571 if let Some(previous) = previous {
7572 Self::remove_dispatch_reference(&mut state, previous);
7573 }
7574 Self::add_dispatch_reference(&mut state, dispatch);
7575 }
7576 retired.push(previous);
7577 accepted_reservation = true;
7578 continue;
7579 }
7580 let delivery = reserved_deliveries
7581 .next()
7582 .expect("every available CurrentThread host must have a reserved delivery");
7583 let pending = state
7584 .entries
7585 .get_mut(id)
7586 .expect("the CurrentThread task driver must remain registered")
7587 .pending
7588 .take();
7589 let accepted = pending == Some(dispatch);
7590 if !accepted && pending.is_some() {
7591 retired.push(pending);
7592 }
7593 match pending {
7594 Some(pending) if pending == dispatch => {}
7595 Some(pending) => {
7596 Self::remove_dispatch_reference(&mut state, pending);
7597 Self::add_dispatch_reference(&mut state, dispatch);
7598 }
7599 None => Self::add_dispatch_reference(&mut state, dispatch),
7600 }
7601 let indexed = state.delivery_drivers.insert(delivery.capability, *id);
7602 assert!(
7603 indexed.is_none(),
7604 "CurrentThread delivery capability must be unique"
7605 );
7606 state
7607 .entries
7608 .get_mut(id)
7609 .expect("the CurrentThread task driver must remain registered")
7610 .delivery = Some(CurrentThreadTaskDeliveryState {
7611 delivery,
7612 claimed: false,
7613 accepted,
7614 });
7615 accepted_reservation |= accepted;
7616 planned_deliveries.push((*id, delivery));
7617 }
7618 let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
7619 debug_assert!(
7620 all_reserved_deliveries_installed,
7621 "every reserved CurrentThread delivery must be installed"
7622 );
7623 if accepted_reservation {
7624 state
7625 .dispatches
7626 .get_mut(&dispatch)
7627 .expect("the publishing dispatch state must remain registered")
7628 .accepted = true;
7629 }
7630 for dispatch in retired.into_iter().flatten() {
7631 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7632 }
7633 planned_deliveries
7634 }
7635 };
7636
7637 let mut deliveries = Vec::with_capacity(planned_deliveries.len());
7642 let mut planned_deliveries = planned_deliveries.into_iter();
7643 let mut next_delivery = planned_deliveries.next();
7644 while let Some((id, driver)) = live.pop() {
7645 let Some((planned_id, delivery)) = next_delivery else {
7646 break;
7647 };
7648 if planned_id == id {
7649 deliveries.push((id, driver, delivery));
7650 next_delivery = planned_deliveries.next();
7651 } else {
7652 next_delivery = Some((planned_id, delivery));
7653 }
7654 }
7655 debug_assert!(
7656 next_delivery.is_none() && planned_deliveries.len() == 0,
7657 "every planned CurrentThread delivery must retain its selected driver"
7658 );
7659
7660 for (id, driver, delivery) in deliveries {
7661 if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
7662 self.accept_delivery(id, delivery);
7663 } else {
7664 let (entry, failures) = self.reject_delivery(id, delivery);
7665 failed_dispatches.extend(failures);
7666 if let Some(entry) = entry {
7667 let _ = catch_unwind_contained(|| entry.driver.on_swept());
7668 let _ = catch_unwind_contained(|| drop(entry));
7669 }
7670 }
7671 }
7672
7673 let result = publication.finish();
7674 debug_assert!(
7675 !failed_dispatches
7676 .iter()
7677 .any(|failure| failure.dispatch == dispatch),
7678 "the active publication must report its own terminal outcome directly"
7679 );
7680 CurrentThreadTaskDispatchCompletion {
7681 result,
7682 failed_dispatches,
7683 }
7684 }
7685
7686 #[cfg(test)]
7687 fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7688 self.dispatch_with_completion(dispatch).result
7689 }
7690
7691 fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
7692 let mut state = self
7693 .state
7694 .lock()
7695 .unwrap_or_else(std::sync::PoisonError::into_inner);
7696 let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
7697 entry
7698 .delivery
7699 .as_ref()
7700 .is_some_and(|current| current.delivery == delivery)
7701 }) {
7702 state
7703 .entries
7704 .get_mut(&id)
7705 .expect("the matching CurrentThread task driver must remain registered")
7706 .delivery
7707 .as_mut()
7708 .expect("the matching delivery must remain installed")
7709 .accepted = true;
7710 true
7711 } else {
7712 false
7717 };
7718 if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
7719 dispatch_state.accepted = true;
7720 dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
7721 }
7722 }
7723
7724 fn reject_delivery(
7725 &self,
7726 id: CurrentThreadTaskDriverId,
7727 delivery: CurrentThreadTaskDelivery,
7728 ) -> (
7729 Option<CurrentThreadTaskDriverEntry>,
7730 Vec<CurrentThreadTaskDispatchFailure>,
7731 ) {
7732 let mut state = self
7733 .state
7734 .lock()
7735 .unwrap_or_else(std::sync::PoisonError::into_inner);
7736 if !state.entries.get(&id).is_some_and(|entry| {
7737 entry
7738 .delivery
7739 .as_ref()
7740 .is_some_and(|current| current.delivery == delivery)
7741 }) {
7742 return (None, Vec::new());
7743 }
7744 let entry = state
7745 .remove_driver(id)
7746 .expect("the matching CurrentThread task driver must remain registered");
7747 let mut failures = Vec::new();
7748 Self::finish_entry_references(&mut state, &entry, &mut failures);
7749 (Some(entry), failures)
7750 }
7751
7752 fn finish_delivery(
7753 &self,
7754 delivery: CurrentThreadTaskDelivery,
7755 failed: bool,
7756 ) -> CurrentThreadTaskDeliveryCompletion {
7757 let mut state = self
7758 .state
7759 .lock()
7760 .unwrap_or_else(std::sync::PoisonError::into_inner);
7761 if !state.entries.get(&delivery.driver).is_some_and(|entry| {
7762 entry
7763 .delivery
7764 .as_ref()
7765 .is_some_and(|current| current.delivery == delivery)
7766 }) {
7767 return CurrentThreadTaskDeliveryCompletion::default();
7768 }
7769 let current = state
7770 .entries
7771 .get_mut(&delivery.driver)
7772 .expect("the matching CurrentThread task driver must remain registered")
7773 .delivery
7774 .take()
7775 .expect("the matching delivery must remain installed");
7776 let indexed = state.delivery_drivers.remove(&delivery.capability);
7777 debug_assert_eq!(
7778 indexed,
7779 Some(delivery.driver),
7780 "delivery capability index must match its driver"
7781 );
7782 let redispatch = state
7783 .entries
7784 .get(&delivery.driver)
7785 .expect("the matching CurrentThread task driver must remain registered")
7786 .pending
7787 .is_some_and(|dispatch| {
7788 state
7789 .dispatches
7790 .get(&dispatch)
7791 .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
7792 });
7793 let mut failed_dispatches = Vec::new();
7794 Self::finish_dispatch_reference(
7799 &mut state,
7800 delivery.dispatch,
7801 true,
7802 failed || !current.claimed,
7803 &mut failed_dispatches,
7804 );
7805 CurrentThreadTaskDeliveryCompletion {
7806 failed_dispatches,
7807 redispatch,
7808 }
7809 }
7810
7811 fn claim_delivery(&self, capability: u64) -> Option<u64> {
7812 let mut state = self
7813 .state
7814 .lock()
7815 .unwrap_or_else(std::sync::PoisonError::into_inner);
7816 let driver = *state.delivery_drivers.get(&capability)?;
7817 let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
7818 if delivery.capability != capability {
7819 return None;
7820 }
7821 let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
7822 if dispatch_state.is_serviced()
7823 || dispatch_state.is_cancelling()
7824 || state.entries.get(&driver)?.delivery.as_ref()?.claimed
7825 {
7826 return None;
7827 }
7828 let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
7829 current.claimed = true;
7830 current.accepted = true;
7831 state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
7832 Some(delivery.dispatch)
7833 }
7834
7835 fn mark_serviced(&self, dispatch: u64) {
7836 let mut state = self
7837 .state
7838 .lock()
7839 .unwrap_or_else(std::sync::PoisonError::into_inner);
7840 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7841 return;
7842 };
7843 dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
7844 let mut removed_references = 0usize;
7845 for entry in state.entries.values_mut() {
7846 if entry.pending == Some(dispatch) {
7847 entry.pending = None;
7848 removed_references = removed_references
7849 .checked_add(1)
7850 .expect("CurrentThread dispatch reference count exhausted");
7851 }
7852 }
7853 let dispatch_state = state
7854 .dispatches
7855 .get_mut(&dispatch)
7856 .expect("the serviced CurrentThread dispatch must remain registered");
7857 dispatch_state.references = dispatch_state
7858 .references
7859 .checked_sub(removed_references)
7860 .expect("CurrentThread dispatch reference count underflow");
7861 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7862 }
7863
7864 fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
7865 let delivery = {
7866 let state = self
7867 .state
7868 .lock()
7869 .unwrap_or_else(std::sync::PoisonError::into_inner);
7870 state
7871 .delivery_drivers
7872 .get(&capability)
7873 .and_then(|driver| state.entries.get(driver))
7874 .and_then(|entry| entry.delivery.as_ref())
7875 .map(|delivery| delivery.delivery)
7876 .filter(|delivery| delivery.capability == capability)
7877 };
7878 delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
7879 self.finish_delivery(delivery, true)
7880 })
7881 }
7882
7883 fn finish_dispatch_reference(
7884 state: &mut CurrentThreadTaskDriverRegistryState,
7885 dispatch: u64,
7886 accepted: bool,
7887 failed: bool,
7888 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7889 ) {
7890 let (publications, references) = {
7891 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7892 return;
7893 };
7894 dispatch_state.references = dispatch_state
7895 .references
7896 .checked_sub(1)
7897 .expect("CurrentThread dispatch reference count underflow");
7898 dispatch_state.accepted |= accepted;
7899 dispatch_state.accepted_failure |= accepted && failed;
7900 (dispatch_state.publications, dispatch_state.references)
7901 };
7902 if references != 0 || publications != 0 {
7903 return;
7904 }
7905 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7906 return;
7907 };
7908 if dispatch_state.accepted_failure
7909 && !dispatch_state.is_serviced()
7910 && !dispatch_state.is_cancelling()
7911 {
7912 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7913 failures.push(dispatch_state.failure(dispatch));
7914 } else if !dispatch_state.is_cancelling() {
7915 state.dispatches.remove(&dispatch);
7916 }
7917 }
7918
7919 fn finish_entry_references(
7920 state: &mut CurrentThreadTaskDriverRegistryState,
7921 entry: &CurrentThreadTaskDriverEntry,
7922 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7923 ) {
7924 if let Some(delivery) = &entry.delivery {
7925 Self::finish_dispatch_reference(
7926 state,
7927 delivery.delivery.dispatch,
7928 delivery.accepted,
7929 delivery.accepted,
7930 failures,
7931 );
7932 }
7933 if let Some(pending) = entry.pending {
7934 Self::finish_dispatch_reference(state, pending, true, true, failures);
7935 }
7936 }
7937
7938 fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7939 let mut state = self
7940 .state
7941 .lock()
7942 .unwrap_or_else(std::sync::PoisonError::into_inner);
7943 let publications = {
7944 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7945 return CurrentThreadHostDispatchResult::Unavailable;
7946 };
7947 dispatch_state.publications = dispatch_state
7948 .publications
7949 .checked_sub(1)
7950 .expect("CurrentThread dispatch publication count underflow");
7951 dispatch_state.publications
7952 };
7953 if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
7954 return CurrentThreadHostDispatchResult::Accepted;
7955 }
7956 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7957 return CurrentThreadHostDispatchResult::Unavailable;
7958 };
7959 if dispatch_state.is_cancelling() {
7960 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7961 }
7962 if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
7963 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7964 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7965 }
7966 let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
7967 state.dispatches.remove(&dispatch);
7968 if accepted {
7969 CurrentThreadHostDispatchResult::Accepted
7970 } else {
7971 CurrentThreadHostDispatchResult::Unavailable
7972 }
7973 }
7974
7975 fn abort_publication(&self, dispatch: u64) {
7976 let mut state = self
7977 .state
7978 .lock()
7979 .unwrap_or_else(std::sync::PoisonError::into_inner);
7980 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7981 return;
7982 };
7983 let Some(publications) = dispatch_state.publications.checked_sub(1) else {
7984 return;
7985 };
7986 dispatch_state.publications = publications;
7987 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7988 }
7989
7990 fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
7991 let mut state = self
7992 .state
7993 .lock()
7994 .unwrap_or_else(std::sync::PoisonError::into_inner);
7995 let can_rearm = state
7996 .dispatches
7997 .get(&failure.dispatch)
7998 .is_some_and(|dispatch_state| {
7999 dispatch_state.is_cancelling()
8000 && dispatch_state.epoch == failure.epoch
8001 && dispatch_state.publications == 0
8002 })
8003 && !Self::has_dispatch_reference(&state, failure.dispatch);
8004 if !can_rearm {
8005 return false;
8006 }
8007 let dispatch_state = state
8008 .dispatches
8009 .get_mut(&failure.dispatch)
8010 .expect("the exact failed CurrentThread dispatch must remain registered");
8011 let epoch = dispatch_state
8012 .epoch
8013 .checked_add(1)
8014 .expect("CurrentThread dispatch failure epoch space exhausted");
8015 *dispatch_state = CurrentThreadDispatchState {
8016 epoch,
8017 ..CurrentThreadDispatchState::default()
8018 };
8019 true
8020 }
8021
8022 fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8023 self
8024 .state
8025 .lock()
8026 .unwrap_or_else(std::sync::PoisonError::into_inner)
8027 .dispatches
8028 .get(&failure.dispatch)
8029 .is_some_and(|dispatch_state| {
8030 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8031 })
8032 }
8033
8034 fn retire_unreferenced_dispatch(
8035 state: &mut CurrentThreadTaskDriverRegistryState,
8036 dispatch: Option<u64>,
8037 ) {
8038 let Some(dispatch) = dispatch else {
8039 return;
8040 };
8041 if Self::has_dispatch_reference(state, dispatch) {
8042 return;
8043 }
8044 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8045 return;
8046 };
8047 if dispatch_state.publications == 0
8048 && !dispatch_state.is_cancelling()
8049 && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8050 {
8051 state.dispatches.remove(&dispatch);
8052 }
8053 }
8054
8055 fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8056 state
8057 .dispatches
8058 .get(&dispatch)
8059 .is_some_and(|dispatch_state| dispatch_state.references != 0)
8060 }
8061
8062 fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8063 let mut state = self
8064 .state
8065 .lock()
8066 .unwrap_or_else(std::sync::PoisonError::into_inner);
8067 if state
8068 .dispatches
8069 .get(&failure.dispatch)
8070 .is_some_and(|dispatch_state| {
8071 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8072 })
8073 {
8074 state.dispatches.remove(&failure.dispatch);
8075 }
8076 }
8077
8078 fn retire_for_shutdown(&self) {
8079 let mut state = self
8080 .state
8081 .lock()
8082 .unwrap_or_else(std::sync::PoisonError::into_inner);
8083 state.dispatches.clear();
8084 for entry in state.entries.values_mut() {
8085 entry.pending = None;
8086 }
8087 }
8088}
8089
8090static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8091 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8092
8093pub fn register_current_thread_task_driver(
8094 driver: Arc<dyn CurrentThreadTaskDriver>,
8095) -> CurrentThreadTaskDriverId {
8096 CURRENT_THREAD_TASK_DRIVERS.register(driver)
8097}
8098
8099pub fn request_current_thread_task_drain() {
8104 RUNTIME.request_current_thread_drain();
8105}
8106
8107pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8108 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8109}
8110
8111fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8112 let CurrentThreadTaskDispatchCompletion {
8113 result,
8114 failed_dispatches,
8115 } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8116 complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8117 failed_dispatches,
8118 redispatch: false,
8119 });
8120 result
8121}
8122
8123pub type TimerId = u64;
8124
8125static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8127
8128pub trait TimerDriver: Send + Sync + 'static {
8134 fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8138 fn cancel(&self, id: TimerId);
8141 fn is_live(&self) -> bool {
8146 true
8147 }
8148 fn on_swept(&self) {}
8158}
8159
8160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8164pub struct TimerDriverId(u64);
8165
8166type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8167
8168struct TimerDriverEntry {
8169 driver: Arc<dyn TimerDriver>,
8170 previous: Option<TimerDriverId>,
8171 next: Option<TimerDriverId>,
8172}
8173
8174#[derive(Default)]
8175struct TimerDriverRegistryState {
8176 entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8177 oldest: Option<TimerDriverId>,
8178 newest: Option<TimerDriverId>,
8179}
8180
8181impl TimerDriverRegistryState {
8182 fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8183 let previous = self.newest;
8184 let replaced = self.entries.insert(
8185 id,
8186 TimerDriverEntry {
8187 driver,
8188 previous,
8189 next: None,
8190 },
8191 );
8192 assert!(replaced.is_none(), "timer driver id must be unique");
8193 if let Some(previous) = previous {
8194 self
8195 .entries
8196 .get_mut(&previous)
8197 .expect("the newest timer driver must remain registered")
8198 .next = Some(id);
8199 } else {
8200 self.oldest = Some(id);
8201 }
8202 self.newest = Some(id);
8203 }
8204
8205 fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
8206 let entry = self.entries.remove(&id)?;
8207 if let Some(previous) = entry.previous {
8208 self
8209 .entries
8210 .get_mut(&previous)
8211 .expect("the previous timer driver must remain registered")
8212 .next = entry.next;
8213 } else {
8214 self.oldest = entry.next;
8215 }
8216 if let Some(next) = entry.next {
8217 self
8218 .entries
8219 .get_mut(&next)
8220 .expect("the next timer driver must remain registered")
8221 .previous = entry.previous;
8222 } else {
8223 self.newest = entry.previous;
8224 }
8225 Some(entry)
8226 }
8227
8228 fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
8229 let mut snapshot = Vec::with_capacity(self.entries.len());
8230 let mut current = self.oldest;
8231 while let Some(id) = current {
8232 let entry = self
8233 .entries
8234 .get(&id)
8235 .expect("timer driver order must reference a live entry");
8236 snapshot.push((id, Arc::clone(&entry.driver)));
8237 current = entry.next;
8238 }
8239 debug_assert_eq!(
8240 snapshot.len(),
8241 self.entries.len(),
8242 "timer driver order must contain every entry exactly once"
8243 );
8244 snapshot
8245 }
8246
8247 fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
8248 if self.entries.len() != expected.len() {
8249 return false;
8250 }
8251 let mut current = self.oldest;
8252 for expected in expected {
8253 if current != Some(*expected) {
8254 return false;
8255 }
8256 current = self
8257 .entries
8258 .get(expected)
8259 .expect("timer driver order must reference a live entry")
8260 .next;
8261 }
8262 current.is_none()
8263 }
8264}
8265
8266#[derive(Default)]
8284pub struct TimerDriverRegistry {
8285 state: Mutex<TimerDriverRegistryState>,
8289 pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
8294 next_id: AtomicU64,
8295}
8296
8297impl Drop for TimerDriverRegistry {
8298 fn drop(&mut self) {
8299 let state = self
8300 .state
8301 .get_mut()
8302 .unwrap_or_else(std::sync::PoisonError::into_inner);
8303 state.oldest = None;
8304 state.newest = None;
8305 let entries = std::mem::take(&mut state.entries);
8306 for (_, entry) in entries {
8307 let _ = catch_unwind_contained(|| drop(entry));
8308 }
8309 }
8310}
8311
8312impl TimerDriverRegistry {
8313 pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8315 let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
8316 let id = TimerDriverId(id);
8317 self
8318 .state
8319 .lock()
8320 .unwrap_or_else(std::sync::PoisonError::into_inner)
8321 .insert_driver(id, driver);
8322 let pending = {
8327 let mut entries = self
8328 .pending
8329 .lock()
8330 .unwrap_or_else(std::sync::PoisonError::into_inner);
8331 let mut pending = Vec::with_capacity(entries.len());
8332 entries.retain(|_, repoll| {
8333 if let Some(repoll) = repoll.upgrade() {
8334 pending.push(repoll);
8335 true
8336 } else {
8337 false
8338 }
8339 });
8340 pending
8341 };
8342 for repoll in pending {
8343 repoll.wake();
8344 }
8345 id
8346 }
8347
8348 pub fn unregister(&self, id: TimerDriverId) {
8350 let removed = self
8351 .state
8352 .lock()
8353 .unwrap_or_else(std::sync::PoisonError::into_inner)
8354 .remove_driver(id);
8355 let _ = catch_unwind_contained(|| drop(removed));
8358 }
8359
8360 fn live(&self) -> HostTimerDriverSnapshot {
8371 loop {
8372 let snapshot = HostTimerDriverSnapshot::new({
8373 let state = self
8374 .state
8375 .lock()
8376 .unwrap_or_else(std::sync::PoisonError::into_inner);
8377 state.ordered_snapshot()
8378 });
8379 let mut dead_ids = FxHashSet::default();
8380 let mut live_ids = Vec::new();
8381 for (id, driver) in snapshot.as_slice() {
8382 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8383 live_ids.push(*id);
8384 } else {
8385 dead_ids.insert(*id);
8386 }
8387 }
8388
8389 let (live, swept, retry) = {
8390 let mut state = self
8391 .state
8392 .lock()
8393 .unwrap_or_else(std::sync::PoisonError::into_inner);
8394 let mut swept = Vec::new();
8395 let mut current = state.oldest;
8396 while let Some(id) = current {
8397 let next = state
8398 .entries
8399 .get(&id)
8400 .expect("timer driver order must reference a live entry")
8401 .next;
8402 if dead_ids.contains(&id)
8403 && let Some(entry) = state.remove_driver(id)
8404 {
8405 swept.push(entry);
8406 }
8407 current = next;
8408 }
8409 let retry = !state.ordered_ids_match(&live_ids);
8410 let live = if retry {
8411 Vec::new()
8412 } else {
8413 state.ordered_snapshot()
8414 };
8415 (HostTimerDriverSnapshot::new(live), swept, retry)
8416 };
8417
8418 drop(snapshot);
8421 for entry in swept {
8422 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8423 let _ = catch_unwind_contained(|| drop(entry));
8424 }
8425 if !retry {
8426 return live;
8427 }
8428 }
8429 }
8430
8431 #[cfg(test)]
8432 fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
8433 let mut live = self.live();
8434 live.pop().map(|(id, driver)| (id, driver.into_inner()))
8435 }
8436
8437 pub fn has_live_driver(&self) -> bool {
8439 !self.live().is_empty()
8440 }
8441
8442 fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
8443 let replaced = self
8444 .pending
8445 .lock()
8446 .unwrap_or_else(std::sync::PoisonError::into_inner)
8447 .insert(id, Arc::downgrade(repoll));
8448 assert!(
8449 replaced.is_none(),
8450 "host timer {id} registered its repoll signal twice"
8451 );
8452 if repoll.is_closed() {
8455 self.remove_pending(id);
8456 }
8457 }
8458
8459 fn remove_pending(&self, id: TimerId) {
8460 let removed = self
8461 .pending
8462 .lock()
8463 .unwrap_or_else(std::sync::PoisonError::into_inner)
8464 .remove(&id);
8465 let _ = catch_unwind_contained(|| drop(removed));
8466 }
8467
8468 #[cfg(test)]
8469 fn pending_len(&self) -> usize {
8470 self
8471 .pending
8472 .lock()
8473 .unwrap_or_else(std::sync::PoisonError::into_inner)
8474 .len()
8475 }
8476}
8477
8478static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
8481 LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
8482
8483pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8489 TIMER_DRIVERS.register(driver)
8490}
8491
8492pub fn unregister_timer_driver(id: TimerDriverId) {
8496 TIMER_DRIVERS.unregister(id);
8497}
8498
8499pub fn has_live_timer_driver() -> bool {
8506 TIMER_DRIVERS.has_live_driver()
8507}
8508
8509#[derive(Default)]
8513struct HostTimerRepoll {
8514 state: Mutex<HostTimerRepollState>,
8515}
8516
8517#[derive(Default)]
8518struct HostTimerRepollState {
8519 closed: bool,
8520 notified: bool,
8521 waker: Option<Waker>,
8522}
8523
8524impl HostTimerRepoll {
8525 fn register(&self, waker: Waker) {
8526 let (replaced, wake) = {
8527 let mut state = self
8528 .state
8529 .lock()
8530 .unwrap_or_else(std::sync::PoisonError::into_inner);
8531 if state.closed {
8532 (Some(waker), None)
8533 } else if state.notified {
8534 state.notified = false;
8535 (state.waker.take(), Some(waker))
8536 } else {
8537 (state.waker.replace(waker), None)
8538 }
8539 };
8540 let _ = catch_unwind_contained(|| drop(replaced));
8541 if let Some(waker) = wake {
8542 wake_waker_contained(waker);
8543 }
8544 }
8545
8546 fn wake(&self) {
8547 let waker = {
8548 let mut state = self
8549 .state
8550 .lock()
8551 .unwrap_or_else(std::sync::PoisonError::into_inner);
8552 if state.closed {
8553 return;
8554 }
8555 if let Some(waker) = state.waker.take() {
8556 Some(waker)
8557 } else {
8558 state.notified = true;
8559 None
8560 }
8561 };
8562 if let Some(waker) = waker {
8563 wake_waker_contained(waker);
8564 }
8565 }
8566
8567 fn close(&self) {
8568 let waker = {
8569 let mut state = self
8570 .state
8571 .lock()
8572 .unwrap_or_else(std::sync::PoisonError::into_inner);
8573 state.closed = true;
8574 state.notified = false;
8575 state.waker.take()
8576 };
8577 let _ = catch_unwind_contained(|| drop(waker));
8578 }
8579
8580 fn is_closed(&self) -> bool {
8581 self
8582 .state
8583 .lock()
8584 .unwrap_or_else(std::sync::PoisonError::into_inner)
8585 .closed
8586 }
8587}
8588
8589#[derive(Default)]
8596struct HostTimerRegistry {
8597 state: Mutex<HostTimerRegistryState>,
8598}
8599
8600#[derive(Default)]
8601struct HostTimerRegistryState {
8602 closed: bool,
8603 timers: FxHashMap<TimerId, HostTimerEntry>,
8604}
8605
8606struct HostTimerEntry {
8607 deadline: Instant,
8608 waker: Waker,
8609 fired: Arc<AtomicBool>,
8610 drivers: Weak<TimerDriverRegistry>,
8611 repoll: Arc<HostTimerRepoll>,
8612}
8613
8614impl HostTimerRegistry {
8615 fn register(
8616 &self,
8617 id: TimerId,
8618 deadline: Instant,
8619 waker: Waker,
8620 fired: &Arc<AtomicBool>,
8621 drivers: &Arc<TimerDriverRegistry>,
8622 repoll: &Arc<HostTimerRepoll>,
8623 ) -> bool {
8624 let replaced = {
8625 let mut state = self
8626 .state
8627 .lock()
8628 .unwrap_or_else(std::sync::PoisonError::into_inner);
8629 if state.closed || fired.load(Ordering::SeqCst) {
8630 fired.store(true, Ordering::SeqCst);
8631 Err(waker)
8632 } else {
8633 Ok(state.timers.insert(
8634 id,
8635 HostTimerEntry {
8636 deadline,
8637 waker,
8638 fired: Arc::clone(fired),
8639 drivers: Arc::downgrade(drivers),
8640 repoll: Arc::clone(repoll),
8641 },
8642 ))
8643 }
8644 };
8645 match replaced {
8646 Ok(Some(entry)) => {
8647 let _ = catch_unwind_contained(|| drop(entry));
8648 true
8649 }
8650 Ok(None) => true,
8651 Err(waker) => {
8652 let _ = catch_unwind_contained(|| drop(waker));
8653 false
8654 }
8655 }
8656 }
8657
8658 fn remove(&self, id: TimerId) {
8659 let removed = self
8660 .state
8661 .lock()
8662 .unwrap_or_else(std::sync::PoisonError::into_inner)
8663 .timers
8664 .remove(&id);
8665 if let Some(entry) = removed {
8666 let _ = catch_unwind_contained(|| drop(entry));
8667 }
8668 }
8669
8670 fn shutdown(&self) {
8671 let timers = {
8672 let mut state = self
8673 .state
8674 .lock()
8675 .unwrap_or_else(std::sync::PoisonError::into_inner);
8676 state.closed = true;
8677 state
8678 .timers
8679 .drain()
8680 .map(|(id, entry)| {
8681 entry.fired.store(true, Ordering::SeqCst);
8682 (id, entry)
8683 })
8684 .collect::<Vec<_>>()
8685 };
8686 for (id, entry) in timers {
8687 entry.repoll.close();
8688 if let Some(drivers) = entry.drivers.upgrade() {
8689 drivers.remove_pending(id);
8690 }
8691 wake_waker_contained(entry.waker);
8692 }
8693 }
8694
8695 fn has_pending(&self) -> bool {
8700 !self
8701 .state
8702 .lock()
8703 .unwrap_or_else(std::sync::PoisonError::into_inner)
8704 .timers
8705 .is_empty()
8706 }
8707
8708 fn has_live_wait(&self, grace: Duration) -> bool {
8717 let now = Instant::now();
8718 self
8719 .state
8720 .lock()
8721 .unwrap_or_else(std::sync::PoisonError::into_inner)
8722 .timers
8723 .values()
8724 .any(|entry| {
8725 entry
8726 .deadline
8727 .checked_add(grace)
8728 .is_none_or(|deadline| deadline > now)
8729 })
8730 }
8731}
8732
8733struct HostTimerPendingGuard {
8737 registry: Arc<HostTimerRegistry>,
8738 drivers: Arc<TimerDriverRegistry>,
8739 repoll: Arc<HostTimerRepoll>,
8740 id: TimerId,
8741}
8742
8743impl Drop for HostTimerPendingGuard {
8744 fn drop(&mut self) {
8745 self.repoll.close();
8746 self.registry.remove(self.id);
8747 self.drivers.remove_pending(self.id);
8748 }
8749}
8750
8751#[cfg(not(target_family = "wasm"))]
8757struct TimerHeap {
8758 inner: Arc<Mutex<TimerHeapInner>>,
8759 parker: Arc<DriverParker>,
8760 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
8761 deadlock_state: Arc<MultiThreadDeadlockState>,
8762 #[cfg(test)]
8763 fail_next_start: AtomicBool,
8764 generation: u64,
8765 executor_id: u64,
8766 thread_name: String,
8767}
8768
8769#[cfg(not(target_family = "wasm"))]
8770#[derive(Default)]
8771struct TimerHeapInner {
8772 queue: TimerDeadlineQueue,
8776 entries: FxHashMap<TimerId, HeapTimerEntry>,
8777 timekeeper_parker: Option<Arc<DriverParker>>,
8781 closed: bool,
8785}
8786
8787#[cfg(not(target_family = "wasm"))]
8788struct HeapTimerEntry {
8789 waker: Waker,
8790 fired: Arc<AtomicBool>,
8795}
8796
8797#[cfg(not(target_family = "wasm"))]
8798#[derive(Default)]
8799struct TimerDeadlineQueue {
8800 nodes: Vec<(Instant, TimerId)>,
8801 positions: FxHashMap<TimerId, usize>,
8802}
8803
8804#[cfg(not(target_family = "wasm"))]
8805impl TimerDeadlineQueue {
8806 #[cfg(test)]
8807 fn len(&self) -> usize {
8808 self.nodes.len()
8809 }
8810
8811 #[cfg(test)]
8812 fn is_empty(&self) -> bool {
8813 self.nodes.is_empty()
8814 }
8815
8816 fn clear(&mut self) {
8817 self.nodes.clear();
8818 self.positions.clear();
8819 }
8820
8821 fn peek(&self) -> Option<(Instant, TimerId)> {
8822 self.nodes.first().copied()
8823 }
8824
8825 fn push(&mut self, deadline: Instant, id: TimerId) {
8826 let index = self.nodes.len();
8827 assert!(
8828 self.positions.insert(id, index).is_none(),
8829 "timer deadline queue received duplicate id {id}"
8830 );
8831 self.nodes.push((deadline, id));
8832 self.sift_up(index);
8833 }
8834
8835 fn pop(&mut self) -> Option<(Instant, TimerId)> {
8836 let first = self.nodes.first().copied()?;
8837 self.positions.remove(&first.1);
8838 let last = self
8839 .nodes
8840 .pop()
8841 .expect("the non-empty timer heap must have a final node");
8842 if !self.nodes.is_empty() {
8843 self.nodes[0] = last;
8844 self.positions.insert(last.1, 0);
8845 self.sift_down(0);
8846 }
8847 Some(first)
8848 }
8849
8850 fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
8851 let index = self.positions.remove(&id)?;
8852 let removed = self.nodes[index];
8853 let last = self.nodes.pop().expect("an indexed timer node must exist");
8854 if index < self.nodes.len() {
8855 self.nodes[index] = last;
8856 self.positions.insert(last.1, index);
8857 if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
8858 self.sift_up(index);
8859 } else {
8860 self.sift_down(index);
8861 }
8862 }
8863 Some(removed)
8864 }
8865
8866 fn sift_up(&mut self, mut index: usize) {
8867 while index > 0 {
8868 let parent = (index - 1) / 2;
8869 if self.nodes[parent] <= self.nodes[index] {
8870 break;
8871 }
8872 self.swap(parent, index);
8873 index = parent;
8874 }
8875 }
8876
8877 fn sift_down(&mut self, mut index: usize) {
8878 loop {
8879 let left = index * 2 + 1;
8880 if left >= self.nodes.len() {
8881 return;
8882 }
8883 let right = left + 1;
8884 let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
8885 right
8886 } else {
8887 left
8888 };
8889 if self.nodes[index] <= self.nodes[child] {
8890 return;
8891 }
8892 self.swap(index, child);
8893 index = child;
8894 }
8895 }
8896
8897 fn swap(&mut self, left: usize, right: usize) {
8898 self.nodes.swap(left, right);
8899 self.positions.insert(self.nodes[left].1, left);
8900 self.positions.insert(self.nodes[right].1, right);
8901 }
8902}
8903
8904#[cfg(not(target_family = "wasm"))]
8905impl TimerHeapInner {
8906 fn next_deadline(&self) -> Option<Instant> {
8907 self.queue.peek().map(|(deadline, _)| deadline)
8908 }
8909}
8910
8911#[cfg(not(target_family = "wasm"))]
8912impl TimerHeap {
8913 fn new(
8914 generation: u64,
8915 executor_id: u64,
8916 thread_name: String,
8917 deadlock_state: Arc<MultiThreadDeadlockState>,
8918 ) -> Self {
8919 Self {
8920 inner: Arc::new(Mutex::new(TimerHeapInner::default())),
8921 parker: Arc::new(DriverParker::default()),
8922 thread: Mutex::new(None),
8923 deadlock_state,
8924 #[cfg(test)]
8925 fail_next_start: AtomicBool::new(false),
8926 generation,
8927 executor_id,
8928 thread_name,
8929 }
8930 }
8931
8932 fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
8933 if self
8934 .inner
8935 .lock()
8936 .unwrap_or_else(std::sync::PoisonError::into_inner)
8937 .closed
8938 {
8939 return Ok(());
8940 }
8941 let current_thread = std::thread::current().id();
8942 loop {
8943 let finished = {
8944 let mut thread = self
8945 .thread
8946 .lock()
8947 .unwrap_or_else(std::sync::PoisonError::into_inner);
8948 match thread.as_ref() {
8949 Some(handle) if handle.thread().id() == current_thread => return Ok(()),
8950 Some(handle) if !handle.is_finished() => return Ok(()),
8951 Some(_) => thread.take(),
8952 None => {
8953 if self
8956 .inner
8957 .lock()
8958 .unwrap_or_else(std::sync::PoisonError::into_inner)
8959 .closed
8960 {
8961 return Ok(());
8962 }
8963 #[cfg(test)]
8964 if self.fail_next_start.swap(false, Ordering::SeqCst) {
8965 return Err(RuntimeConfigError(
8966 "injected runtime timer thread creation failure".to_string(),
8967 ));
8968 }
8969
8970 let inner = Arc::clone(&self.inner);
8971 let parker = Arc::clone(&self.parker);
8972 let deadlock_state = Arc::clone(&self.deadlock_state);
8973 let generation = self.generation;
8974 let executor_id = self.executor_id;
8975 let handle = std::thread::Builder::new()
8976 .name(self.thread_name.clone())
8977 .spawn(move || {
8978 ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
8981 let _generation = RuntimeGenerationGuard::enter(generation);
8982 timer_timekeeper_main(&inner, &parker, &deadlock_state);
8983 })
8984 .map_err(|error| {
8985 RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
8986 })?;
8987 *thread = Some(handle);
8988 return Ok(());
8989 }
8990 }
8991 };
8992
8993 if let Some(finished) = finished
8997 && finished.thread().id() != current_thread
8998 && let Err(payload) = finished.join()
8999 {
9000 discard_panic_payload(payload);
9001 }
9002 if self
9003 .inner
9004 .lock()
9005 .unwrap_or_else(std::sync::PoisonError::into_inner)
9006 .closed
9007 {
9008 return Ok(());
9009 }
9010 }
9011 }
9012
9013 fn close(&self) -> Vec<Waker> {
9014 let mut inner = self
9015 .inner
9016 .lock()
9017 .unwrap_or_else(std::sync::PoisonError::into_inner);
9018 inner.closed = true;
9019 inner.queue.clear();
9020 inner.timekeeper_parker = None;
9021 inner
9022 .entries
9023 .drain()
9024 .map(|(_, entry)| {
9025 entry.fired.store(true, Ordering::SeqCst);
9026 entry.waker
9027 })
9028 .collect()
9029 }
9030
9031 fn join(&self) {
9032 let handle = self
9033 .thread
9034 .lock()
9035 .unwrap_or_else(std::sync::PoisonError::into_inner)
9036 .take();
9037 if let Some(handle) = handle {
9038 if handle.thread().id() == std::thread::current().id() {
9039 drop(handle);
9043 return;
9044 }
9045 if let Err(payload) = handle.join() {
9046 discard_panic_payload(payload);
9047 }
9048 }
9049 }
9050}
9051
9052#[cfg(not(target_family = "wasm"))]
9053impl Drop for TimerHeap {
9054 fn drop(&mut self) {
9055 let mut admission = self.deadlock_state.begin_admission();
9056 let wakers = self.close();
9057 if !wakers.is_empty()
9058 && let Some(admission) = admission.as_mut()
9059 {
9060 admission.mark_published();
9061 }
9062 self.parker.unpark();
9063 for waker in wakers {
9064 wake_waker_contained(waker);
9065 }
9066 drop(admission);
9067 self.join();
9068 }
9069}
9070
9071#[cfg(not(target_family = "wasm"))]
9072fn timer_timekeeper_main(
9073 inner: &Mutex<TimerHeapInner>,
9074 parker: &Arc<DriverParker>,
9075 deadlock_state: &Arc<MultiThreadDeadlockState>,
9076) {
9077 loop {
9078 let mut admission = deadlock_state.begin_admission();
9082 let (due, deadline) = {
9083 let mut inner = inner
9084 .lock()
9085 .unwrap_or_else(std::sync::PoisonError::into_inner);
9086 if inner.closed {
9087 inner.timekeeper_parker = None;
9088 return;
9089 }
9090 let now = Instant::now();
9091 let mut due = Vec::new();
9092 while let Some((deadline, id)) = inner.queue.peek() {
9093 if deadline > now {
9094 break;
9095 }
9096 inner.queue.pop();
9097 if let Some(entry) = inner.entries.remove(&id) {
9098 entry.fired.store(true, Ordering::SeqCst);
9099 due.push(entry.waker);
9100 }
9101 }
9102 let deadline = inner.next_deadline();
9103 inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9104 (due, deadline)
9105 };
9106
9107 if !due.is_empty() {
9108 if let Some(admission) = admission.as_mut() {
9109 admission.mark_published();
9110 }
9111 for waker in due {
9112 wake_waker_contained(waker);
9113 }
9114 continue;
9115 }
9116 drop(admission);
9119
9120 match deadline {
9121 Some(deadline) => {
9122 let now = Instant::now();
9123 if deadline > now {
9124 parker.park_timeout(deadline - now);
9125 }
9126 }
9127 None => parker.park(),
9128 }
9129 }
9130}
9131
9132#[cfg(not(target_family = "wasm"))]
9133impl MultiThreadExecutor {
9134 fn register_timer(
9140 self: &Arc<Self>,
9141 id: TimerId,
9142 deadline: Instant,
9143 waker: Waker,
9144 fired: &Arc<AtomicBool>,
9145 ) {
9146 let mut admission = self.begin_deadlock_admission();
9147 #[cfg(test)]
9148 run_timer_registration_admission_test_hook();
9149 if let Err(error) = self.timers.ensure_started() {
9153 let _ = catch_unwind_contained(|| drop(waker));
9154 panic!("{error}");
9155 }
9156 let (became_earliest, published, discarded_waker) = {
9157 let mut inner = self
9158 .timers
9159 .inner
9160 .lock()
9161 .unwrap_or_else(std::sync::PoisonError::into_inner);
9162 if inner.closed || fired.load(Ordering::SeqCst) {
9163 fired.store(true, Ordering::SeqCst);
9164 (false, false, Some(waker))
9165 } else if let Some(entry) = inner.entries.get_mut(&id) {
9166 (
9168 false,
9169 false,
9170 Some(std::mem::replace(&mut entry.waker, waker)),
9171 )
9172 } else {
9173 let previous = inner.next_deadline();
9174 inner.entries.insert(
9175 id,
9176 HeapTimerEntry {
9177 waker,
9178 fired: Arc::clone(fired),
9179 },
9180 );
9181 inner.queue.push(deadline, id);
9182 (
9183 previous.is_none_or(|previous| deadline < previous),
9184 true,
9185 None,
9186 )
9187 }
9188 };
9189 if published && let Some(admission) = admission.as_mut() {
9190 admission.mark_published();
9191 }
9192 if let Some(waker) = discarded_waker {
9193 let _ = catch_unwind_contained(|| drop(waker));
9196 }
9197 if became_earliest {
9198 self.note_earlier_timer();
9200 }
9201 }
9202
9203 fn cancel_timer(&self, id: TimerId) {
9207 let (removed, earliest_changed) = {
9208 let mut inner = self
9209 .timers
9210 .inner
9211 .lock()
9212 .unwrap_or_else(std::sync::PoisonError::into_inner);
9213 let previous = inner.next_deadline();
9214 let removed = inner.entries.remove(&id);
9215 if removed.is_some() {
9216 let queued = inner.queue.remove(id);
9217 debug_assert!(
9218 queued.is_some(),
9219 "a live timer entry must have one indexed deadline node"
9220 );
9221 }
9222 let current = inner.next_deadline();
9223 (removed, previous != current)
9224 };
9225 if let Some(entry) = removed {
9226 let _ = catch_unwind_contained(|| drop(entry));
9229 }
9230 if earliest_changed {
9231 self.timers.parker.unpark();
9232 self.parked_drivers.wake_all();
9233 }
9234 }
9235
9236 fn next_timer_deadline(&self) -> Option<Instant> {
9238 self
9239 .timers
9240 .inner
9241 .lock()
9242 .unwrap_or_else(std::sync::PoisonError::into_inner)
9243 .next_deadline()
9244 }
9245
9246 fn fire_due_timers(&self) {
9251 let mut admission = self.begin_deadlock_admission();
9252 let due: Vec<Waker> = {
9253 let mut inner = self
9254 .timers
9255 .inner
9256 .lock()
9257 .unwrap_or_else(std::sync::PoisonError::into_inner);
9258 let now = Instant::now();
9259 let mut due = Vec::new();
9260 while let Some((deadline, id)) = inner.queue.peek() {
9261 if deadline > now {
9262 break;
9263 }
9264 inner.queue.pop();
9265 if let Some(entry) = inner.entries.remove(&id) {
9266 entry.fired.store(true, Ordering::SeqCst);
9267 due.push(entry.waker);
9268 }
9269 }
9270 due
9271 };
9272 if !due.is_empty()
9273 && let Some(admission) = admission.as_mut()
9274 {
9275 admission.mark_published();
9276 }
9277 for waker in due {
9278 wake_waker_contained(waker);
9281 }
9282 }
9283
9284 fn note_earlier_timer(self: &Arc<Self>) {
9288 self.timers.parker.unpark();
9289 self.parked_drivers.wake_one();
9290 }
9291
9292 fn shutdown_timers(&self) {
9298 let mut admission = self.begin_deadlock_admission();
9299 let wakers = self.timers.close();
9300 if !wakers.is_empty()
9301 && let Some(admission) = admission.as_mut()
9302 {
9303 admission.mark_published();
9304 }
9305 for waker in wakers {
9306 wake_waker_contained(waker);
9310 }
9311 self.timers.parker.unpark();
9312 }
9313
9314 fn begin_shutdown(&self) {
9315 let _generation = RuntimeGenerationGuard::enter(self.generation);
9316 self.stop.begin_shutdown();
9321 let queued = {
9322 let mut queue = self
9323 .blocking_queue
9324 .lock()
9325 .unwrap_or_else(std::sync::PoisonError::into_inner);
9326 queue.closed = true;
9329 queue.take_all()
9330 };
9331 for queued in queued {
9336 let _ = catch_unwind_contained(|| drop(queued));
9337 }
9338 self.shutdown_timers();
9339 }
9340
9341 fn wait_until_scheduler_idle(&self) {
9342 self.timers.join();
9343 let mut idle = self
9344 .scheduler_idle_lock
9345 .lock()
9346 .unwrap_or_else(std::sync::PoisonError::into_inner);
9347 *idle += 1;
9353 while self.active_drainers.load(Ordering::Acquire) != 0 {
9354 idle = self
9355 .scheduler_idle
9356 .wait(idle)
9357 .unwrap_or_else(std::sync::PoisonError::into_inner);
9358 }
9359 *idle -= 1;
9360 }
9361}
9362
9363pub struct Sleep {
9368 deadline: Instant,
9369 inner: SleepInner,
9370}
9371
9372enum SleepInner {
9373 #[cfg(not(target_family = "wasm"))]
9374 Heap(HeapSleep),
9375 Host(HostSleep),
9376}
9377
9378#[cfg(not(target_family = "wasm"))]
9379struct HeapSleep {
9380 id: TimerId,
9381 executor: Weak<MultiThreadExecutor>,
9382 fired: Arc<AtomicBool>,
9383 registered: bool,
9384}
9385
9386struct HostSleep {
9387 id: TimerId,
9388 drivers: Arc<TimerDriverRegistry>,
9392 armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
9396 registry: Arc<HostTimerRegistry>,
9399 fired: Arc<AtomicBool>,
9400 repoll: Arc<HostTimerRepoll>,
9404 pending: Option<HostTimerPendingGuard>,
9409}
9410
9411fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
9412 let _ = catch_unwind_contained(|| driver.cancel(id));
9413 let _ = catch_unwind_contained(|| drop(driver));
9414}
9415
9416fn cancel_host_timer_drivers(
9417 id: TimerId,
9418 armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
9419) {
9420 for (_, driver) in armed.drain() {
9421 cancel_host_timer_driver(id, driver);
9422 }
9423}
9424
9425impl Future for Sleep {
9426 type Output = ();
9427
9428 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
9429 let deadline = self.deadline;
9430 let this = self.get_mut();
9431 match &mut this.inner {
9432 #[cfg(not(target_family = "wasm"))]
9433 SleepInner::Heap(heap) => {
9434 if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
9435 if heap.registered {
9436 if let Some(executor) = heap.executor.upgrade() {
9439 executor.cancel_timer(heap.id);
9440 }
9441 heap.registered = false;
9442 }
9443 return Poll::Ready(());
9444 }
9445 let Some(executor) = heap.executor.upgrade() else {
9446 heap.fired.store(true, Ordering::SeqCst);
9447 heap.registered = false;
9448 return Poll::Ready(());
9449 };
9450 executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
9451 heap.registered = true;
9452 if heap.fired.load(Ordering::SeqCst) {
9456 heap.registered = false;
9457 return Poll::Ready(());
9458 }
9459 Poll::Pending
9460 }
9461 SleepInner::Host(host) => {
9462 if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
9463 cancel_host_timer_drivers(host.id, &mut host.armed);
9464 host.pending.take();
9465 return Poll::Ready(());
9466 }
9467 let repoll_waker = cx.waker().clone();
9472 let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
9473 Ok(waker) => waker,
9474 Err(payload) => {
9475 let _ = catch_unwind_contained(|| drop(repoll_waker));
9476 resume_unwind(payload);
9477 }
9478 };
9479 host.repoll.register(repoll_waker);
9480 if !host.registry.register(
9481 host.id,
9482 deadline,
9483 registry_waker,
9484 &host.fired,
9485 &host.drivers,
9486 &host.repoll,
9487 ) {
9488 cancel_host_timer_drivers(host.id, &mut host.armed);
9489 if host.pending.take().is_none() {
9490 host.repoll.close();
9491 }
9492 return Poll::Ready(());
9493 }
9494 if host.pending.is_none() {
9495 host.drivers.register_pending(host.id, &host.repoll);
9503 host.pending = Some(HostTimerPendingGuard {
9504 registry: Arc::clone(&host.registry),
9505 drivers: Arc::clone(&host.drivers),
9506 repoll: Arc::clone(&host.repoll),
9507 id: host.id,
9508 });
9509 }
9510 let current = host.drivers.live();
9514 assert!(
9515 !current.as_slice().is_empty(),
9516 "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
9517 that registered timer drivers (via \
9518 `napi_async_runtime::register_timer_driver`; the napi adapter registers \
9519 one per importing env through `registerTimerHost`) have all been torn down, so this \
9520 `sleep_until` can never be woken."
9521 );
9522 let missing = current
9528 .as_slice()
9529 .iter()
9530 .filter(|(current_id, _)| !host.armed.contains_key(current_id))
9531 .count();
9532 host.armed.reserve(missing);
9533 for (current_id, current_driver) in current.as_slice() {
9534 host
9535 .armed
9536 .entry(*current_id)
9537 .or_insert_with(|| Arc::clone(current_driver));
9538 }
9539 for (current_id, _) in current.as_slice() {
9540 let armed_driver = host
9541 .armed
9542 .get(current_id)
9543 .expect("every live timer driver must have cancellation ownership before callbacks");
9544 armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
9545 }
9546 let current_ids = current
9547 .as_slice()
9548 .iter()
9549 .map(|(current_id, _)| *current_id)
9550 .collect::<FxHashSet<_>>();
9551 let retired = host
9552 .armed
9553 .keys()
9554 .filter(|armed_id| !current_ids.contains(armed_id))
9555 .copied()
9556 .collect::<Vec<_>>();
9557 for retired in retired {
9558 let old_driver = host
9559 .armed
9560 .remove(&retired)
9561 .expect("the retired timer driver must remain armed");
9562 cancel_host_timer_driver(host.id, old_driver);
9563 }
9564 Poll::Pending
9565 }
9566 }
9567 }
9568}
9569
9570impl Drop for Sleep {
9571 fn drop(&mut self) {
9572 match &mut self.inner {
9573 #[cfg(not(target_family = "wasm"))]
9574 SleepInner::Heap(heap) => {
9575 if heap.registered
9576 && let Some(executor) = heap.executor.upgrade()
9577 {
9578 executor.cancel_timer(heap.id);
9579 }
9580 }
9581 SleepInner::Host(host) => {
9582 host.repoll.close();
9585 cancel_host_timer_drivers(host.id, &mut host.armed);
9586 }
9588 }
9589 }
9590}
9591
9592pub fn sleep_until(deadline: Instant) -> Sleep {
9597 make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
9598}
9599
9600fn make_sleep(
9604 backend: &RuntimeBackend,
9605 host_drivers: &Arc<TimerDriverRegistry>,
9606 deadline: Instant,
9607) -> Sleep {
9608 let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
9609 match &backend.executor {
9610 #[cfg(not(target_family = "wasm"))]
9611 RuntimeExecutor::MultiThread(executor) => Sleep {
9612 deadline,
9613 inner: SleepInner::Heap(HeapSleep {
9614 id,
9615 executor: Arc::downgrade(executor),
9616 fired: Arc::new(AtomicBool::new(false)),
9617 registered: false,
9618 }),
9619 },
9620 RuntimeExecutor::CurrentThread(executor) => {
9621 assert!(
9629 host_drivers.has_live_driver(),
9630 "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
9631 single-thread flavor delegates timers to the host event loop. Install one via \
9632 `napi_async_runtime::register_timer_driver` (the napi adapter registers \
9633 a setTimeout-based driver per importing env through `registerTimerHost` at import)."
9634 );
9635 Sleep {
9642 deadline,
9643 inner: SleepInner::Host(HostSleep {
9644 id,
9645 drivers: Arc::clone(host_drivers),
9646 armed: FxHashMap::default(),
9647 registry: Arc::clone(&executor.host_timers),
9648 fired: Arc::new(AtomicBool::new(false)),
9649 repoll: Arc::new(HostTimerRepoll::default()),
9650 pending: None,
9651 }),
9652 }
9653 }
9654 }
9655}
9656
9657#[derive(Clone)]
9658enum RuntimeExecutor {
9659 CurrentThread(Arc<CurrentThreadExecutor>),
9660 #[cfg(not(target_family = "wasm"))]
9661 MultiThread(Arc<MultiThreadExecutor>),
9662}
9663
9664enum WeakRuntimeExecutor {
9665 CurrentThread(Weak<CurrentThreadExecutor>),
9666 #[cfg(not(target_family = "wasm"))]
9667 MultiThread(Weak<MultiThreadExecutor>),
9668}
9669
9670impl WeakRuntimeExecutor {
9671 fn schedule(&self, runnable: Runnable) {
9672 match self {
9673 Self::CurrentThread(executor) => {
9674 if let Some(executor) = executor.upgrade() {
9675 executor.schedule(runnable);
9676 }
9677 }
9678 #[cfg(not(target_family = "wasm"))]
9679 Self::MultiThread(executor) => {
9680 if let Some(executor) = executor.upgrade() {
9681 executor.schedule(runnable);
9682 }
9683 }
9684 }
9685 }
9686}
9687
9688#[derive(Clone)]
9689struct RuntimeBackend {
9690 work: Arc<GenerationWork>,
9691 executor: RuntimeExecutor,
9692}
9693
9694impl RuntimeBackend {
9695 fn new(
9696 options: &RuntimeOptions,
9697 metrics: Arc<RuntimeMetrics>,
9698 ) -> Result<Self, RuntimeConfigError> {
9699 #[cfg(test)]
9700 if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
9701 return Err(RuntimeConfigError(
9702 "injected async runtime backend creation failure".to_string(),
9703 ));
9704 }
9705
9706 let work = GenerationWork::new();
9707 let stop = Arc::new(GenerationStop::default());
9708 let executor = match options.flavor {
9709 RuntimeFlavor::CurrentThread => {
9710 RuntimeExecutor::CurrentThread(Arc::new(CurrentThreadExecutor::with_generation(
9711 metrics,
9712 THREADLESS_BUILD,
9713 options.park_deadline,
9714 work.id,
9715 Arc::clone(&stop),
9716 )))
9717 }
9718 RuntimeFlavor::MultiThread => {
9719 #[cfg(not(target_family = "wasm"))]
9720 {
9721 RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
9722 options, metrics, work.id, stop,
9723 )?))
9724 }
9725 #[cfg(target_family = "wasm")]
9726 {
9727 let _ = metrics;
9728 return Err(RuntimeConfigError(
9729 "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
9730 ));
9731 }
9732 }
9733 };
9734 Ok(Self { work, executor })
9735 }
9736
9737 #[cfg(test)]
9738 fn from_executor(executor: RuntimeExecutor) -> Self {
9739 Self {
9740 work: GenerationWork::new(),
9741 executor,
9742 }
9743 }
9744
9745 fn generation(&self) -> u64 {
9746 self.work.id
9747 }
9748
9749 fn downgrade_executor(&self) -> WeakRuntimeExecutor {
9750 match &self.executor {
9751 RuntimeExecutor::CurrentThread(executor) => {
9752 WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
9753 }
9754 #[cfg(not(target_family = "wasm"))]
9755 RuntimeExecutor::MultiThread(executor) => {
9756 WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
9757 }
9758 }
9759 }
9760
9761 fn schedule(&self, runnable: Runnable) {
9762 match &self.executor {
9763 RuntimeExecutor::CurrentThread(executor) => executor.schedule(runnable),
9764 #[cfg(not(target_family = "wasm"))]
9765 RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
9766 }
9767 }
9768
9769 fn spawn_registered_blocking<F, T>(
9770 &self,
9771 function: F,
9772 registration: GenerationWorkGuard,
9773 metrics: &Arc<RuntimeMetrics>,
9774 ) -> JoinHandle<T>
9775 where
9776 F: FnOnce() -> T + Send + 'static,
9777 T: Send + 'static,
9778 {
9779 match &self.executor {
9780 RuntimeExecutor::CurrentThread(executor) => {
9781 let _ = metrics;
9782 executor.schedule_blocking(function, registration)
9783 }
9784 #[cfg(not(target_family = "wasm"))]
9785 RuntimeExecutor::MultiThread(executor) => {
9786 let generation = self.generation();
9787 let _ = metrics;
9788 let function = RegisteredBlockingFunction::new(function, registration, generation);
9789 executor.schedule_blocking_result(move || function.run())
9790 }
9791 }
9792 }
9793
9794 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
9795 match &self.executor {
9796 RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
9797 #[cfg(not(target_family = "wasm"))]
9798 RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
9799 }
9800 }
9801
9802 fn generation_stop(&self) -> Arc<GenerationStop> {
9803 match &self.executor {
9804 RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
9805 #[cfg(not(target_family = "wasm"))]
9806 RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
9807 }
9808 }
9809
9810 fn begin_shutdown(&self) {
9811 let _generation = RuntimeGenerationGuard::enter(self.generation());
9812 self.work.close_and_abort();
9813 match &self.executor {
9814 RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
9815 #[cfg(not(target_family = "wasm"))]
9816 RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
9817 }
9818 }
9819
9820 fn wait_until_idle(&self) {
9821 self.work.wait_until_idle();
9822 match &self.executor {
9823 RuntimeExecutor::CurrentThread(executor) => executor.wait_until_scheduler_idle(),
9824 #[cfg(not(target_family = "wasm"))]
9825 RuntimeExecutor::MultiThread(executor) => executor.wait_until_scheduler_idle(),
9826 }
9827 }
9828
9829 #[cfg(not(target_family = "wasm"))]
9830 fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
9831 match &self.executor {
9832 RuntimeExecutor::CurrentThread(_) => None,
9833 RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
9834 }
9835 }
9836
9837 fn stop_identity(&self) -> RuntimeStopIdentity {
9838 RuntimeStopIdentity {
9839 generation: self.generation(),
9840 #[cfg(not(target_family = "wasm"))]
9841 executor_id: match &self.executor {
9842 RuntimeExecutor::CurrentThread(_) => None,
9843 RuntimeExecutor::MultiThread(executor) => Some(executor.id),
9844 },
9845 }
9846 }
9847
9848 fn is_current(&self) -> bool {
9849 self.stop_identity().is_current()
9850 }
9851}
9852
9853#[derive(Clone, Copy)]
9854struct RuntimeStopIdentity {
9855 generation: u64,
9856 #[cfg(not(target_family = "wasm"))]
9857 executor_id: Option<u64>,
9858}
9859
9860impl RuntimeStopIdentity {
9861 fn is_current(self) -> bool {
9862 if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
9863 return true;
9864 }
9865 #[cfg(not(target_family = "wasm"))]
9866 if let Some(executor_id) = self.executor_id
9867 && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
9868 || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
9869 {
9870 return true;
9871 }
9872 false
9873 }
9874}
9875
9876enum RuntimeLifecycle {
9877 Initial,
9878 StoppedBeforeFirstUse,
9879 Running(RuntimeBackend),
9880 Stopping(RuntimeStopIdentity),
9881 StoppingWithoutBackend(ZeroBackendShutdownTarget),
9882 Stopped,
9883}
9884
9885#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9886enum ZeroBackendShutdownTarget {
9887 StoppedBeforeFirstUse,
9888 Stopped,
9889}
9890
9891impl ZeroBackendShutdownTarget {
9892 fn into_lifecycle(self) -> RuntimeLifecycle {
9893 match self {
9894 Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
9895 Self::Stopped => RuntimeLifecycle::Stopped,
9896 }
9897 }
9898}
9899
9900struct RuntimeState {
9901 options: RuntimeOptions,
9902 lifecycle: RuntimeLifecycle,
9903 rejected_drops: usize,
9904}
9905
9906#[cfg(test)]
9907thread_local! {
9908 static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
9909 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9910 static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
9911 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9912 static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
9913 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9914 static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
9915 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9916 static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
9917 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9918 static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
9919 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9920 static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
9921 std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
9922}
9923
9924#[cfg(test)]
9925fn run_before_runtime_submission_lock_test_hook() {
9926 if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9927 {
9928 hook();
9929 }
9930}
9931
9932#[cfg(test)]
9933fn run_after_async_work_registration_test_hook() {
9934 if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9935 {
9936 hook();
9937 }
9938}
9939
9940#[cfg(test)]
9941fn run_after_blocking_work_registration_test_hook() {
9942 if let Some(hook) =
9943 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9944 {
9945 hook();
9946 }
9947}
9948
9949#[cfg(test)]
9950fn run_after_current_thread_host_admission_test_hook() {
9951 if let Some(hook) =
9952 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9953 {
9954 hook();
9955 }
9956}
9957
9958#[cfg(test)]
9959fn run_before_initial_start_wait_test_hook() {
9960 if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
9961 hook();
9962 }
9963}
9964
9965#[cfg(test)]
9966fn run_after_generation_stop_publication_test_hook() {
9967 if let Some(hook) =
9968 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9969 {
9970 hook();
9971 }
9972}
9973
9974thread_local! {
9975 static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
9976 const { std::cell::Cell::new(0) };
9977}
9978
9979struct RejectedSubmissionDropContext;
9980
9981impl RejectedSubmissionDropContext {
9982 fn enter() -> Self {
9983 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
9984 Self
9985 }
9986
9987 fn is_current() -> bool {
9988 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
9989 }
9990}
9991
9992impl Drop for RejectedSubmissionDropContext {
9993 fn drop(&mut self) {
9994 IN_REJECTED_SUBMISSION_DROP.with(|depth| {
9995 depth.set(
9996 depth
9997 .get()
9998 .checked_sub(1)
9999 .expect("rejected submission drop depth underflow"),
10000 );
10001 });
10002 }
10003}
10004
10005struct RuntimeController {
10006 state: Mutex<RuntimeState>,
10007 lifecycle_changed: Condvar,
10008 metrics: Arc<RuntimeMetrics>,
10009 current_backend: ArcSwapOption<RuntimeBackend>,
10015}
10016
10017struct RejectedSubmissionGuard<'a> {
10018 controller: &'a RuntimeController,
10019}
10020
10021impl Drop for RejectedSubmissionGuard<'_> {
10022 fn drop(&mut self) {
10023 let mut state = self
10024 .controller
10025 .state
10026 .lock()
10027 .unwrap_or_else(std::sync::PoisonError::into_inner);
10028 state.rejected_drops = state
10029 .rejected_drops
10030 .checked_sub(1)
10031 .expect("rejected submission drop count underflow");
10032 self.controller.lifecycle_changed.notify_all();
10033 }
10034}
10035
10036impl RuntimeController {
10037 fn new() -> Self {
10038 let options = RuntimeOptions::default()
10039 .validate()
10040 .expect("default async runtime options must be valid");
10041 Self {
10042 state: Mutex::new(RuntimeState {
10043 options,
10044 lifecycle: RuntimeLifecycle::Initial,
10045 rejected_drops: 0,
10046 }),
10047 lifecycle_changed: Condvar::new(),
10048 metrics: Arc::new(RuntimeMetrics::default()),
10049 current_backend: ArcSwapOption::empty(),
10050 }
10051 }
10052
10053 fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10054 let options = options.validate()?;
10055 let mut state = self
10056 .state
10057 .lock()
10058 .unwrap_or_else(std::sync::PoisonError::into_inner);
10059 Self::ensure_configuration_mutable(&state)?;
10060 state.options = options;
10061 Ok(())
10062 }
10063
10064 fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10065 self.configure_partial_inner(patch, |_| {})
10066 }
10067
10068 fn configure_partial_inner(
10069 &self,
10070 patch: RuntimeOptionsPatch,
10071 after_merge: impl FnOnce(&RuntimeOptions),
10072 ) -> Result<(), RuntimeConfigError> {
10073 let mut state = self
10074 .state
10075 .lock()
10076 .unwrap_or_else(std::sync::PoisonError::into_inner);
10077 Self::ensure_configuration_mutable(&state)?;
10078
10079 let mut options = state.options.clone();
10080 patch.apply_to(&mut options);
10081 after_merge(&options);
10082 let options = options.validate()?;
10083 state.options = options;
10084 Ok(())
10085 }
10086
10087 fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10088 if matches!(
10089 &state.lifecycle,
10090 RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
10091 ) {
10092 return Ok(());
10093 }
10094 Err(RuntimeConfigError(
10095 "the async runtime configuration is frozen; configure it before the first async call"
10096 .to_string(),
10097 ))
10098 }
10099
10100 fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10101 let Some(active_generation) = active_runtime_generation() else {
10102 return Ok(());
10103 };
10104 let lifecycle_generation = match &state.lifecycle {
10105 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10106 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10107 RuntimeLifecycle::Initial
10108 | RuntimeLifecycle::StoppedBeforeFirstUse
10109 | RuntimeLifecycle::StoppingWithoutBackend(_)
10110 | RuntimeLifecycle::Stopped => None,
10111 };
10112 if lifecycle_generation == Some(active_generation) {
10113 return Ok(());
10114 }
10115 Err(RuntimeConfigError(
10116 "cannot access or control the async runtime from work owned by a retired generation"
10117 .to_string(),
10118 ))
10119 }
10120
10121 fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
10122 Self::ensure_active_generation_current(state)?;
10123 match &state.lifecycle {
10124 RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
10125 RuntimeLifecycle::StoppedBeforeFirstUse
10126 | RuntimeLifecycle::Stopping(_)
10127 | RuntimeLifecycle::StoppingWithoutBackend(_)
10128 | RuntimeLifecycle::Stopped => {
10129 return Err(RuntimeConfigError(
10130 "the async runtime is stopped; call start before submitting work".to_string(),
10131 ));
10132 }
10133 RuntimeLifecycle::Initial => {}
10134 }
10135 if state.rejected_drops != 0 {
10136 return Err(RuntimeConfigError(
10137 "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
10138 ));
10139 }
10140 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10141 state.lifecycle = RuntimeLifecycle::Running(backend.clone());
10142 self.current_backend.store(Some(Arc::new(backend.clone())));
10143 Ok(backend)
10144 }
10145
10146 fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
10147 let mut state = self
10148 .state
10149 .lock()
10150 .unwrap_or_else(std::sync::PoisonError::into_inner);
10151 self.backend_locked(&mut state)
10152 }
10153
10154 fn backend(&self) -> RuntimeBackend {
10155 self.try_backend().unwrap_or_else(|error| panic!("{error}"))
10156 }
10157
10158 fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
10159 let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
10160 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10161 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10162 RuntimeLifecycle::Initial
10163 | RuntimeLifecycle::StoppedBeforeFirstUse
10164 | RuntimeLifecycle::StoppingWithoutBackend(_)
10165 | RuntimeLifecycle::Stopped => None,
10166 });
10167 state.rejected_drops += 1;
10168 generation
10169 }
10170
10171 #[inline]
10179 fn fast_register<R>(
10180 &self,
10181 register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
10182 ) -> Option<(Arc<RuntimeBackend>, R)> {
10183 let guard = self.current_backend.load();
10184 let backend = guard.as_ref()?; if let Some(active) = active_runtime_generation() {
10186 if active != backend.generation() {
10187 return None; }
10189 }
10190 let registration = register(&backend.work)?; Some((Arc::clone(backend), registration))
10194 }
10195
10196 fn try_spawn_tracked<F, T>(
10197 &self,
10198 future: F,
10199 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10200 where
10201 F: Future<Output = T> + Send + 'static,
10202 T: Send + 'static,
10203 {
10204 #[cfg(test)]
10205 run_before_runtime_submission_lock_test_hook();
10206
10207 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
10208 #[cfg(test)]
10209 run_after_async_work_registration_test_hook();
10210 return Ok(spawn_registered(
10211 &backend,
10212 Arc::clone(&self.metrics),
10213 future,
10214 registration,
10215 ));
10216 }
10217
10218 let (backend, registration) = {
10219 let mut state = self
10220 .state
10221 .lock()
10222 .unwrap_or_else(std::sync::PoisonError::into_inner);
10223 let backend = match self.backend_locked(&mut state) {
10224 Ok(backend) => backend,
10225 Err(error) => {
10226 let generation = Self::begin_rejected_drop(&mut state);
10227 return Err((error, future, generation));
10228 }
10229 };
10230 let Some(registration) = backend.work.try_register_async() else {
10231 let generation = Self::begin_rejected_drop(&mut state);
10232 return Err((
10233 RuntimeConfigError(
10234 "the async runtime is stopped; call start before submitting work".to_string(),
10235 ),
10236 future,
10237 generation,
10238 ));
10239 };
10240 (backend, registration)
10241 };
10242 #[cfg(test)]
10243 run_after_async_work_registration_test_hook();
10244 Ok(spawn_registered(
10245 &backend,
10246 Arc::clone(&self.metrics),
10247 future,
10248 registration,
10249 ))
10250 }
10251
10252 fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10253 where
10254 F: Future<Output = T> + Send + 'static,
10255 T: Send + 'static,
10256 {
10257 match self.try_spawn_tracked(future) {
10258 Ok(handle) => Ok(handle),
10259 Err((error, future, _generation)) => {
10260 drop(RejectedSubmissionGuard { controller: self });
10261 Err((error, future))
10262 }
10263 }
10264 }
10265
10266 fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
10267 where
10268 F: Future<Output = T> + Send + 'static,
10269 T: Send + 'static,
10270 {
10271 match self.try_spawn_tracked(future) {
10272 Ok(handle) => handle,
10273 Err((error, future, generation)) => {
10274 let _rejection = RejectedSubmissionGuard { controller: self };
10275 let _drop_context = RejectedSubmissionDropContext::enter();
10276 let _generation = generation.map(RuntimeGenerationGuard::enter);
10277 let _ = catch_unwind_contained(|| drop(future));
10278 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10279 message: error.to_string(),
10280 }))))
10281 }
10282 }
10283 }
10284
10285 fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
10286 where
10287 F: Future<Output = ()> + Send + 'static,
10288 {
10289 match self.try_spawn(future) {
10290 Ok(handle) => {
10291 handle.detach();
10292 Ok(())
10293 }
10294 Err((_, future)) => Err(future),
10295 }
10296 }
10297
10298 fn try_spawn_blocking_tracked<F, T>(
10299 &self,
10300 function: F,
10301 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10302 where
10303 F: FnOnce() -> T + Send + 'static,
10304 T: Send + 'static,
10305 {
10306 #[cfg(test)]
10307 run_before_runtime_submission_lock_test_hook();
10308
10309 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
10310 #[cfg(test)]
10311 run_after_blocking_work_registration_test_hook();
10312 return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
10313 }
10314
10315 let (backend, registration) = {
10316 let mut state = self
10317 .state
10318 .lock()
10319 .unwrap_or_else(std::sync::PoisonError::into_inner);
10320 let backend = match self.backend_locked(&mut state) {
10321 Ok(backend) => backend,
10322 Err(error) => {
10323 let generation = Self::begin_rejected_drop(&mut state);
10324 return Err((error, function, generation));
10325 }
10326 };
10327 let Some(registration) = backend.work.try_register_work() else {
10328 let generation = Self::begin_rejected_drop(&mut state);
10329 return Err((
10330 RuntimeConfigError(
10331 "the async runtime is stopped; call start before submitting work".to_string(),
10332 ),
10333 function,
10334 generation,
10335 ));
10336 };
10337 (backend, registration)
10338 };
10339 #[cfg(test)]
10340 run_after_blocking_work_registration_test_hook();
10341 Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
10342 }
10343
10344 fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10345 where
10346 F: FnOnce() -> T + Send + 'static,
10347 T: Send + 'static,
10348 {
10349 match self.try_spawn_blocking_tracked(function) {
10350 Ok(handle) => Ok(handle),
10351 Err((error, function, _generation)) => {
10352 drop(RejectedSubmissionGuard { controller: self });
10353 Err((error, function))
10354 }
10355 }
10356 }
10357
10358 fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
10359 where
10360 F: FnOnce() -> T + Send + 'static,
10361 T: Send + 'static,
10362 {
10363 match self.try_spawn_blocking_tracked(function) {
10364 Ok(handle) => handle,
10365 Err((error, function, generation)) => {
10366 let _rejection = RejectedSubmissionGuard { controller: self };
10367 let _drop_context = RejectedSubmissionDropContext::enter();
10368 let _generation = generation.map(RuntimeGenerationGuard::enter);
10369 let _ = catch_unwind_contained(|| drop(function));
10370 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10371 message: error.to_string(),
10372 }))))
10373 }
10374 }
10375 }
10376
10377 #[cfg(test)]
10378 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10379 self
10380 .try_block_on(future)
10381 .unwrap_or_else(|error| panic!("{error}"))
10382 }
10383
10384 fn try_block_on_scope(
10385 &self,
10386 ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
10387 let mut state = self
10388 .state
10389 .lock()
10390 .unwrap_or_else(std::sync::PoisonError::into_inner);
10391 let backend = match self.backend_locked(&mut state) {
10392 Ok(backend) => backend,
10393 Err(error) => {
10394 let generation = Self::begin_rejected_drop(&mut state);
10395 return Err((error, generation));
10396 }
10397 };
10398 let Some(registration) = backend.work.try_register_work() else {
10399 let generation = Self::begin_rejected_drop(&mut state);
10400 return Err((
10401 RuntimeConfigError(
10402 "the async runtime is stopped; call start before submitting work".to_string(),
10403 ),
10404 generation,
10405 ));
10406 };
10407 Ok((backend, registration))
10408 }
10409
10410 fn try_block_on(
10411 &self,
10412 future: Pin<&mut dyn Future<Output = ()>>,
10413 ) -> Result<BlockOnOutcome, RuntimeConfigError> {
10414 let (backend, registration) = match self.try_block_on_scope() {
10415 Ok(scope) => scope,
10416 Err((error, _generation)) => {
10417 drop(RejectedSubmissionGuard { controller: self });
10418 return Err(error);
10419 }
10420 };
10421 let _registration = registration;
10422 let _generation = RuntimeGenerationGuard::enter(backend.generation());
10423 Ok(backend.block_on(future))
10424 }
10425
10426 fn start(&self) -> Result<(), RuntimeConfigError> {
10427 let mut state = self
10428 .state
10429 .lock()
10430 .unwrap_or_else(std::sync::PoisonError::into_inner);
10431 loop {
10432 Self::ensure_active_generation_current(&state)?;
10433 match &state.lifecycle {
10434 RuntimeLifecycle::Initial => {
10438 if state.rejected_drops == 0 {
10439 return Ok(());
10440 }
10441 if RejectedSubmissionDropContext::is_current() {
10442 return Err(RuntimeConfigError(
10443 "cannot start the async runtime while a rejected submission is being destroyed"
10444 .to_string(),
10445 ));
10446 }
10447 #[cfg(test)]
10448 run_before_initial_start_wait_test_hook();
10449 state = self
10450 .lifecycle_changed
10451 .wait(state)
10452 .unwrap_or_else(std::sync::PoisonError::into_inner);
10453 }
10454 RuntimeLifecycle::Running(_) => return Ok(()),
10455 RuntimeLifecycle::Stopping(identity) => {
10456 if identity.is_current() {
10457 return Err(RuntimeConfigError(
10458 "cannot restart the async runtime from work in the generation being stopped"
10459 .to_string(),
10460 ));
10461 }
10462 state = self
10463 .lifecycle_changed
10464 .wait(state)
10465 .unwrap_or_else(std::sync::PoisonError::into_inner);
10466 }
10467 RuntimeLifecycle::StoppingWithoutBackend(_) => {
10468 if RejectedSubmissionDropContext::is_current() {
10469 return Err(RuntimeConfigError(
10470 "cannot restart the async runtime while a rejected submission is being destroyed"
10471 .to_string(),
10472 ));
10473 }
10474 state = self
10475 .lifecycle_changed
10476 .wait(state)
10477 .unwrap_or_else(std::sync::PoisonError::into_inner);
10478 }
10479 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10480 if state.rejected_drops == 0 {
10481 if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10482 state.lifecycle = RuntimeLifecycle::Initial;
10483 return Ok(());
10484 }
10485 break;
10486 }
10487 if RejectedSubmissionDropContext::is_current() {
10488 return Err(RuntimeConfigError(
10489 "cannot restart the async runtime while a rejected submission is being destroyed"
10490 .to_string(),
10491 ));
10492 }
10493 state = self
10494 .lifecycle_changed
10495 .wait(state)
10496 .unwrap_or_else(std::sync::PoisonError::into_inner);
10497 }
10498 }
10499 }
10500 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10501 self.current_backend.store(Some(Arc::new(backend.clone())));
10502 state.lifecycle = RuntimeLifecycle::Running(backend);
10503 Ok(())
10504 }
10505
10506 fn options(&self) -> RuntimeOptions {
10507 self
10508 .state
10509 .lock()
10510 .unwrap_or_else(std::sync::PoisonError::into_inner)
10511 .options
10512 .clone()
10513 }
10514
10515 fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
10516 let state = self
10517 .state
10518 .lock()
10519 .unwrap_or_else(std::sync::PoisonError::into_inner);
10520 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10521 return None;
10522 };
10523 match &backend.executor {
10524 RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
10525 #[cfg(not(target_family = "wasm"))]
10526 RuntimeExecutor::MultiThread(_) => None,
10527 }
10528 }
10529
10530 fn request_current_thread_drain(&self) {
10531 if let Some(executor) = self.running_current_thread_executor() {
10532 executor.request_drain_if_queued();
10533 }
10534 }
10535
10536 fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
10537 let executor = {
10538 let state = self
10539 .state
10540 .lock()
10541 .unwrap_or_else(std::sync::PoisonError::into_inner);
10542 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10543 return;
10544 };
10545 match &backend.executor {
10546 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
10547 #[cfg(not(target_family = "wasm"))]
10548 RuntimeExecutor::MultiThread(_) => return,
10549 }
10550 };
10551 executor.cancel_host_dispatch(failure);
10552 }
10553
10554 #[cfg(test)]
10555 fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
10556 let executor = {
10557 let state = self
10558 .state
10559 .lock()
10560 .unwrap_or_else(std::sync::PoisonError::into_inner);
10561 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10562 return;
10563 };
10564 match &backend.executor {
10565 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
10566 #[cfg(not(target_family = "wasm"))]
10567 RuntimeExecutor::MultiThread(_) => return,
10568 }
10569 };
10570 executor.cancel_host_dispatch_for_test(dispatch);
10571 }
10572
10573 fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
10574 let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
10575 self.drive_current_thread_dispatch_with_lease(dispatch, true)
10576 }
10577
10578 fn drive_current_thread_dispatch_with_lease(
10579 &self,
10580 dispatch: u64,
10581 mark_serviced: bool,
10582 ) -> Option<CurrentThreadTaskCallbackLease> {
10583 let mut host_turn = {
10584 let state = self
10585 .state
10586 .lock()
10587 .unwrap_or_else(std::sync::PoisonError::into_inner);
10588 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10589 return None;
10590 };
10591 let executor = match &backend.executor {
10592 RuntimeExecutor::CurrentThread(executor) => executor,
10593 #[cfg(not(target_family = "wasm"))]
10594 RuntimeExecutor::MultiThread(_) => return None,
10595 };
10596 let host_turn = executor.try_admit_host_turn(dispatch)?;
10597 if mark_serviced {
10598 CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
10599 }
10600 host_turn
10601 };
10602 #[cfg(test)]
10603 run_after_current_thread_host_admission_test_hook();
10604 host_turn.drive();
10605 Some(CurrentThreadTaskCallbackLease::new(host_turn))
10606 }
10607
10608 #[cfg(test)]
10609 fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
10610 self
10611 .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
10612 .is_some()
10613 }
10614
10615 fn shutdown(&self) -> Result<(), RuntimeConfigError> {
10616 let backend = {
10617 let mut state = self
10618 .state
10619 .lock()
10620 .unwrap_or_else(std::sync::PoisonError::into_inner);
10621 loop {
10622 Self::ensure_active_generation_current(&state)?;
10623 match &state.lifecycle {
10624 RuntimeLifecycle::Initial => {
10625 if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
10626 return Err(RuntimeConfigError(
10627 "cannot shut down the async runtime while a rejected submission is being destroyed"
10628 .to_string(),
10629 ));
10630 }
10631 let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
10632 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10633 self.lifecycle_changed.notify_all();
10634 while state.rejected_drops != 0 {
10635 state = self
10636 .lifecycle_changed
10637 .wait(state)
10638 .unwrap_or_else(std::sync::PoisonError::into_inner);
10639 }
10640 debug_assert!(matches!(
10641 state.lifecycle,
10642 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10643 ));
10644 state.lifecycle = target.into_lifecycle();
10645 self.lifecycle_changed.notify_all();
10646 return Ok(());
10647 }
10648 RuntimeLifecycle::Running(backend) => {
10649 if backend.is_current() {
10650 return Err(RuntimeConfigError(
10651 "cannot shut down the async runtime from work running on that runtime".to_string(),
10652 ));
10653 }
10654 let identity = backend.stop_identity();
10655 let stop = backend.generation_stop();
10656 let stop_publication = stop.publication_guard();
10661 stop_publication.publish_stopping();
10662 let RuntimeLifecycle::Running(backend) =
10663 std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
10664 else {
10665 unreachable!();
10666 };
10667 self.current_backend.store(None);
10668 drop(stop_publication);
10672 break backend;
10673 }
10674 RuntimeLifecycle::Stopping(identity) => {
10675 if identity.is_current() {
10676 return Err(RuntimeConfigError(
10677 "cannot wait for async runtime shutdown from work in the generation being stopped"
10678 .to_string(),
10679 ));
10680 }
10681 state = self
10682 .lifecycle_changed
10683 .wait(state)
10684 .unwrap_or_else(std::sync::PoisonError::into_inner);
10685 }
10686 RuntimeLifecycle::StoppingWithoutBackend(_) => {
10687 if RejectedSubmissionDropContext::is_current() {
10688 return Err(RuntimeConfigError(
10689 "cannot shut down the async runtime while a rejected submission is being destroyed"
10690 .to_string(),
10691 ));
10692 }
10693 state = self
10694 .lifecycle_changed
10695 .wait(state)
10696 .unwrap_or_else(std::sync::PoisonError::into_inner);
10697 }
10698 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10699 if state.rejected_drops == 0 {
10700 return Ok(());
10701 }
10702 if RejectedSubmissionDropContext::is_current() {
10703 return Err(RuntimeConfigError(
10704 "cannot shut down the async runtime while a rejected submission is being destroyed"
10705 .to_string(),
10706 ));
10707 }
10708 let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10709 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
10710 } else {
10711 ZeroBackendShutdownTarget::Stopped
10712 };
10713 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10714 self.lifecycle_changed.notify_all();
10715 while state.rejected_drops != 0 {
10716 state = self
10717 .lifecycle_changed
10718 .wait(state)
10719 .unwrap_or_else(std::sync::PoisonError::into_inner);
10720 }
10721 debug_assert!(matches!(
10722 state.lifecycle,
10723 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10724 ));
10725 state.lifecycle = target.into_lifecycle();
10726 self.lifecycle_changed.notify_all();
10727 return Ok(());
10728 }
10729 }
10730 }
10731 };
10732
10733 #[cfg(test)]
10734 run_after_generation_stop_publication_test_hook();
10735 backend.begin_shutdown();
10736 backend.wait_until_idle();
10737
10738 #[cfg(not(target_family = "wasm"))]
10739 let worker_lifecycle = backend.worker_lifecycle();
10740 drop(backend);
10741
10742 #[cfg(not(target_family = "wasm"))]
10743 if let Some(worker_lifecycle) = worker_lifecycle {
10744 worker_lifecycle.wait_for_all_workers();
10745 }
10746
10747 let mut state = self
10748 .state
10749 .lock()
10750 .unwrap_or_else(std::sync::PoisonError::into_inner);
10751 while state.rejected_drops != 0 {
10752 state = self
10753 .lifecycle_changed
10754 .wait(state)
10755 .unwrap_or_else(std::sync::PoisonError::into_inner);
10756 }
10757 debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
10758 state.lifecycle = RuntimeLifecycle::Stopped;
10759 self.lifecycle_changed.notify_all();
10760 Ok(())
10761 }
10762}
10763
10764static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
10765
10766pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10767 RUNTIME.configure(options)
10768}
10769
10770pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10771 RUNTIME.configure_partial(patch)
10772}
10773
10774pub fn configured_options() -> RuntimeOptions {
10775 RUNTIME.options()
10776}
10777
10778pub fn is_multi_threaded() -> bool {
10779 RUNTIME.options().flavor == RuntimeFlavor::MultiThread
10780}
10781
10782fn spawn_registered<F, T>(
10783 backend: &RuntimeBackend,
10784 metrics: Arc<RuntimeMetrics>,
10785 future: F,
10786 registration: (AbortRegistration, GenerationWorkGuard),
10787) -> JoinHandle<T>
10788where
10789 F: Future<Output = T> + Send + 'static,
10790 T: Send + 'static,
10791{
10792 metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
10793 let generation = backend.generation();
10794 let (abort_registration, work_registration) = registration;
10795 #[cfg(not(target_family = "wasm"))]
10796 let dependency = Arc::new(TaskDependency::new_task(generation));
10797 #[cfg(not(target_family = "wasm"))]
10798 let task_dependency = Arc::clone(&dependency);
10799 let wrapped = async move {
10800 let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
10801 let mut abortable = std::pin::pin!(abortable);
10802 let polled = futures::future::poll_fn(|cx| {
10803 #[cfg(not(target_family = "wasm"))]
10804 task_dependency.begin_poll();
10805 #[cfg(not(target_family = "wasm"))]
10806 let _dependency = TaskDependencyGuard::enter(&task_dependency);
10807 abortable.as_mut().poll(cx)
10808 });
10809 match AssertUnwindSafe(polled).catch_unwind().await {
10810 Ok(Ok(output)) => {
10811 metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
10812 Ok(ContainedTaskOutput::new(output, generation))
10813 }
10814 Ok(Err(_)) => Err(JoinError {
10815 message: "async runtime stopped before the task completed".to_string(),
10816 }),
10817 Err(panic) => {
10818 metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
10819 Err(panic_payload_to_join_error(panic))
10820 }
10821 }
10822 };
10823 let scheduler = backend.downgrade_executor();
10824 let (runnable, task) = async_task::spawn(
10828 RegisteredTaskFuture::new(wrapped, work_registration, generation),
10829 move |runnable| {
10830 scheduler.schedule(runnable);
10831 },
10832 );
10833 backend.schedule(runnable);
10834 JoinHandle(JoinHandleInner::Task {
10835 task: task.fallible(),
10836 awaiter: None,
10837 #[cfg(not(target_family = "wasm"))]
10838 dependency,
10839 })
10840}
10841
10842pub fn spawn<F, T>(future: F) -> JoinHandle<T>
10843where
10844 F: Future<Output = T> + Send + 'static,
10845 T: Send + 'static,
10846{
10847 RUNTIME.spawn(future)
10848}
10849
10850pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10855where
10856 F: Future<Output = T> + Send + 'static,
10857 T: Send + 'static,
10858{
10859 RUNTIME.try_spawn(future)
10860}
10861
10862pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
10863where
10864 F: Future<Output = ()> + Send + 'static,
10865{
10866 RUNTIME.try_spawn_detached(future)
10867}
10868
10869pub fn spawn_detached<F>(future: F)
10870where
10871 F: Future<Output = ()> + Send + 'static,
10872{
10873 spawn(future).detach();
10874}
10875
10876pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
10877where
10878 F: FnOnce() -> T + Send + 'static,
10879 T: Send + 'static,
10880{
10881 RUNTIME.spawn_blocking(function)
10882}
10883
10884pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10889where
10890 F: FnOnce() -> T + Send + 'static,
10891 T: Send + 'static,
10892{
10893 RUNTIME.try_spawn_blocking(function)
10894}
10895
10896fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
10897 let (backend, registration) = match controller.try_block_on_scope() {
10898 Ok(scope) => scope,
10899 Err((error, generation)) => {
10900 let _rejection = RejectedSubmissionGuard { controller };
10901 let _drop_context = RejectedSubmissionDropContext::enter();
10902 let _generation = generation.map(RuntimeGenerationGuard::enter);
10903 let _ = catch_unwind_contained(|| drop(future));
10904 panic!("{error}");
10905 }
10906 };
10907 let generation = backend.generation();
10908 let _registration = registration;
10909 let _generation = RuntimeGenerationGuard::enter(generation);
10910 let mut output = None;
10911 {
10912 let future = ContainedFuture::new(future, generation);
10913 let mut erased = std::pin::pin!(async {
10914 output = Some(future.await);
10915 });
10916 let _ = backend.block_on(erased.as_mut());
10917 }
10922 output.expect("async runtime returned before the future completed")
10923}
10924
10925pub fn block_on<F: Future>(future: F) -> F::Output {
10926 block_on_with_controller(&RUNTIME, future)
10927}
10928
10929pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
10930 try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
10931}
10932
10933pub fn try_block_on_dyn(
10935 future: Pin<&mut dyn Future<Output = ()>>,
10936) -> Result<(), RuntimeConfigError> {
10937 match RUNTIME.try_block_on(future)? {
10938 BlockOnOutcome::Completed => Ok(()),
10939 BlockOnOutcome::Stopped => Err(RuntimeConfigError(
10940 "the async runtime stopped before block_on completed its future".to_string(),
10941 )),
10942 }
10943}
10944
10945pub fn start() -> Result<(), RuntimeConfigError> {
10946 RUNTIME.start()
10947}
10948
10949pub fn shutdown() -> Result<(), RuntimeConfigError> {
10950 RUNTIME.shutdown()
10951}
10952
10953fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
10954 for failure in completion.failed_dispatches {
10955 RUNTIME.cancel_current_thread_dispatch(failure);
10956 }
10957 if completion.redispatch {
10958 RUNTIME.request_current_thread_drain();
10959 }
10960}
10961
10962pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10971 complete_current_thread_task_delivery(
10972 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
10973 );
10974}
10975
10976pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10982 complete_current_thread_task_delivery(
10983 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
10984 );
10985}
10986
10987pub fn cancel_current_thread_task_dispatch(delivery: u64) {
10994 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
10995}
10996
10997pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11006 RUNTIME.drive_current_thread_tasks(delivery)
11007}
11008
11009pub fn reset_metrics() {
11010 RUNTIME.metrics.reset();
11011}
11012
11013pub fn metrics() -> RuntimeMetricsSnapshot {
11014 let options = RUNTIME.options();
11015 RUNTIME.metrics.snapshot(&options)
11016}
11017
11018#[cfg(test)]
11019mod tests {
11020 use super::*;
11021
11022 static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11023 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11024 LazyLock::new(Mutex::default);
11025 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11026 static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11027 static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11028 static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11029 LazyLock::new(Mutex::default);
11030 static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11031 static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11032 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11033 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11034 LazyLock::new(Mutex::default);
11035 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
11036 static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
11037 CurrentThreadTaskDriverRegistry,
11038 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11039 static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11040 static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11041 static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11042 LazyLock::new(Mutex::default);
11043 static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11044 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11045 static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11046 LazyLock::new(Mutex::default);
11047 static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11048 static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11049 LazyLock::new(Mutex::default);
11050 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11051 LazyLock::new(Mutex::default);
11052 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
11053 CurrentThreadTaskDriverRegistry,
11054 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11055 static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11056 LazyLock::new(Mutex::default);
11057 static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11058 static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11059 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11060 static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11061
11062 thread_local! {
11063 static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
11064 const { std::cell::Cell::new(0) };
11065 static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
11066 const { std::cell::Cell::new(0) };
11067 }
11068
11069 #[cfg(not(target_family = "wasm"))]
11070 struct StopPollProbe {
11071 polls: Arc<AtomicUsize>,
11072 first_poll: Option<std::sync::mpsc::Sender<()>>,
11073 }
11074
11075 #[cfg(not(target_family = "wasm"))]
11076 impl Future for StopPollProbe {
11077 type Output = ();
11078
11079 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
11080 self.polls.fetch_add(1, Ordering::SeqCst);
11081 if let Some(first_poll) = self.first_poll.take() {
11082 first_poll.send(()).unwrap();
11083 }
11084 Poll::Pending
11085 }
11086 }
11087
11088 #[cfg(not(target_family = "wasm"))]
11089 struct PanickingJoinAwaiter {
11090 wakes: Arc<AtomicUsize>,
11091 drops: Arc<AtomicUsize>,
11092 }
11093
11094 #[cfg(not(target_family = "wasm"))]
11095 impl std::task::Wake for PanickingJoinAwaiter {
11096 fn wake(self: Arc<Self>) {
11097 self.wakes.fetch_add(1, Ordering::SeqCst);
11098 panic!("intentional JoinHandle awaiter wake panic");
11099 }
11100
11101 fn wake_by_ref(self: &Arc<Self>) {
11102 self.wakes.fetch_add(1, Ordering::SeqCst);
11103 panic!("intentional JoinHandle awaiter wake-by-ref panic");
11104 }
11105 }
11106
11107 #[cfg(not(target_family = "wasm"))]
11108 impl Drop for PanickingJoinAwaiter {
11109 fn drop(&mut self) {
11110 self.drops.fetch_add(1, Ordering::SeqCst);
11111 panic!("intentional JoinHandle awaiter destructor panic");
11112 }
11113 }
11114
11115 #[cfg(not(target_family = "wasm"))]
11116 fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
11117 Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
11118 }
11119
11120 #[cfg(not(target_family = "wasm"))]
11121 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11122 enum JoinAwaiterWakerEventKind {
11123 Clone,
11124 Wake,
11125 WakeByRef,
11126 Drop,
11127 }
11128
11129 #[cfg(not(target_family = "wasm"))]
11130 struct JoinAwaiterWakerState {
11131 events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
11132 }
11133
11134 #[cfg(not(target_family = "wasm"))]
11135 impl JoinAwaiterWakerState {
11136 fn record(&self, kind: JoinAwaiterWakerEventKind) {
11137 self
11138 .events
11139 .lock()
11140 .unwrap_or_else(std::sync::PoisonError::into_inner)
11141 .push((kind, active_runtime_generation()));
11142 }
11143
11144 fn clear(&self) {
11145 self
11146 .events
11147 .lock()
11148 .unwrap_or_else(std::sync::PoisonError::into_inner)
11149 .clear();
11150 }
11151
11152 fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
11153 self
11154 .events
11155 .lock()
11156 .unwrap_or_else(std::sync::PoisonError::into_inner)
11157 .clone()
11158 }
11159 }
11160
11161 #[cfg(not(target_family = "wasm"))]
11162 struct JoinAwaiterWakerToken {
11163 state: Arc<JoinAwaiterWakerState>,
11164 }
11165
11166 #[cfg(not(target_family = "wasm"))]
11167 unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
11168 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11169 token.state.record(JoinAwaiterWakerEventKind::Clone);
11170 let clone = Box::new(JoinAwaiterWakerToken {
11171 state: Arc::clone(&token.state),
11172 });
11173 std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
11174 }
11175
11176 #[cfg(not(target_family = "wasm"))]
11177 unsafe fn wake_join_awaiter_waker(data: *const ()) {
11178 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11179 token.state.record(JoinAwaiterWakerEventKind::Wake);
11180 }
11181
11182 #[cfg(not(target_family = "wasm"))]
11183 unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
11184 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11185 token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
11186 }
11187
11188 #[cfg(not(target_family = "wasm"))]
11189 unsafe fn drop_join_awaiter_waker(data: *const ()) {
11190 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11191 token.state.record(JoinAwaiterWakerEventKind::Drop);
11192 }
11193
11194 #[cfg(not(target_family = "wasm"))]
11195 static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
11196 clone_join_awaiter_waker,
11197 wake_join_awaiter_waker,
11198 wake_join_awaiter_waker_by_ref,
11199 drop_join_awaiter_waker,
11200 );
11201
11202 #[cfg(not(target_family = "wasm"))]
11203 fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
11204 let token = Box::new(JoinAwaiterWakerToken { state });
11205 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
11206 unsafe { Waker::from_raw(raw) }
11207 }
11208
11209 #[cfg(not(target_family = "wasm"))]
11210 fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
11211 wait_until("the block_on driver to sleep", || {
11212 stop
11213 .parkers
11214 .lock()
11215 .unwrap_or_else(std::sync::PoisonError::into_inner)
11216 .iter()
11217 .filter_map(Weak::upgrade)
11218 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11219 });
11220 }
11221
11222 #[test]
11223 fn unique_ids_fail_before_wraparound() {
11224 let counter = AtomicU64::new(u64::MAX - 1);
11225 assert_eq!(
11226 next_unique_id(&counter, "test id space exhausted"),
11227 u64::MAX - 1
11228 );
11229
11230 let exhausted = catch_unwind(AssertUnwindSafe(|| {
11231 next_unique_id(&counter, "test id space exhausted");
11232 }));
11233 assert!(exhausted.is_err());
11234 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
11235 }
11236
11237 #[test]
11238 fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
11239 let work = GenerationWork::new();
11240 {
11241 let mut state = work.state.lock().unwrap();
11242 state.next_task_id = u64::MAX;
11243 }
11244
11245 let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
11246 assert!(exhausted.is_err());
11247
11248 let state = work.state.lock().unwrap();
11249 assert_eq!(state.next_task_id, u64::MAX);
11250 assert_eq!(state.active, 0);
11251 assert!(state.abort_handles.is_empty());
11252 }
11253
11254 #[test]
11255 fn caught_panic_payload_destructor_runs() {
11256 struct DropSignal(Arc<AtomicBool>);
11257
11258 impl Drop for DropSignal {
11259 fn drop(&mut self) {
11260 self.0.store(true, Ordering::SeqCst);
11261 }
11262 }
11263
11264 let dropped = Arc::new(AtomicBool::new(false));
11265 let payload = DropSignal(Arc::clone(&dropped));
11266
11267 assert!(
11268 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
11269 "the original panic must remain contained"
11270 );
11271 assert!(
11272 dropped.load(Ordering::SeqCst),
11273 "an ordinary caught panic payload must be destroyed"
11274 );
11275 }
11276
11277 #[test]
11278 fn hostile_caught_panic_payload_destructor_remains_contained() {
11279 struct PanicOnDrop(Arc<AtomicBool>);
11280
11281 impl Drop for PanicOnDrop {
11282 fn drop(&mut self) {
11283 self.0.store(true, Ordering::SeqCst);
11284 panic!("caught panic payload destructor escaped");
11285 }
11286 }
11287
11288 let dropped = Arc::new(AtomicBool::new(false));
11289 let payload = PanicOnDrop(Arc::clone(&dropped));
11290 let result = catch_unwind(AssertUnwindSafe(|| {
11291 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
11292 }));
11293
11294 assert!(
11295 result.is_ok(),
11296 "the payload destructor's nested panic must remain contained"
11297 );
11298 assert!(
11299 result.unwrap().is_none(),
11300 "the original panic must remain contained"
11301 );
11302 assert!(
11303 dropped.load(Ordering::SeqCst),
11304 "the hostile payload destructor must be attempted"
11305 );
11306 }
11307
11308 fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11309 CurrentThreadHostDispatchResult::Accepted
11310 }
11311
11312 fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11313 CurrentThreadHostDispatchResult::Unavailable
11314 }
11315
11316 fn count_current_thread_recovery_host_dispatch(
11317 _dispatch: u64,
11318 ) -> CurrentThreadHostDispatchResult {
11319 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
11320 CurrentThreadHostDispatchResult::Accepted
11321 }
11322
11323 fn reset_current_thread_recovery_host_dispatches() {
11324 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
11325 }
11326
11327 fn current_thread_recovery_host_dispatches() -> usize {
11328 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
11329 }
11330
11331 fn accept_only_first_current_thread_host_dispatch(
11332 _dispatch: u64,
11333 ) -> CurrentThreadHostDispatchResult {
11334 if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
11335 let previous = dispatches.get();
11336 dispatches.set(previous + 1);
11337 previous == 0
11338 }) {
11339 CurrentThreadHostDispatchResult::Accepted
11340 } else {
11341 CurrentThreadHostDispatchResult::Unavailable
11342 }
11343 }
11344
11345 fn reset_current_thread_one_shot_host_dispatches() {
11346 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
11347 }
11348
11349 fn current_thread_one_shot_host_dispatches() -> usize {
11350 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
11351 }
11352
11353 fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11354 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11355 CurrentThreadHostDispatchResult::Accepted
11356 }
11357
11358 fn count_current_thread_blocking_budget_host_dispatch(
11359 _dispatch: u64,
11360 ) -> CurrentThreadHostDispatchResult {
11361 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11362 CurrentThreadHostDispatchResult::Accepted
11363 }
11364
11365 fn count_current_thread_admission_host_dispatch(
11366 _dispatch: u64,
11367 ) -> CurrentThreadHostDispatchResult {
11368 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11369 CurrentThreadHostDispatchResult::Accepted
11370 }
11371
11372 fn count_current_thread_interleaved_recovery_host_dispatch(
11373 _dispatch: u64,
11374 ) -> CurrentThreadHostDispatchResult {
11375 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11376 CurrentThreadHostDispatchResult::Accepted
11377 }
11378
11379 fn dispatch_late_registration_current_thread_tasks(
11380 dispatch: u64,
11381 ) -> CurrentThreadHostDispatchResult {
11382 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
11383 }
11384
11385 fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
11386 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
11387 .lock()
11388 .unwrap()
11389 .push(dispatch);
11390 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
11391 }
11392
11393 fn dispatch_authoritative_panic_current_thread_tasks(
11394 dispatch: u64,
11395 ) -> CurrentThreadHostDispatchResult {
11396 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
11397 .lock()
11398 .unwrap()
11399 .push(dispatch);
11400 match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
11401 0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
11402 2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
11403 _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
11404 }
11405 }
11406
11407 fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
11408 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
11409 }
11410
11411 fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
11412 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
11413 .lock()
11414 .unwrap()
11415 .push(dispatch);
11416 assert!(
11417 !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
11418 "injected CurrentThread host dispatch panic"
11419 );
11420 CurrentThreadHostDispatchResult::Accepted
11421 }
11422
11423 fn panic_once_current_thread_armed_republish_host_dispatch(
11424 dispatch: u64,
11425 ) -> CurrentThreadHostDispatchResult {
11426 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
11427 .lock()
11428 .unwrap()
11429 .push(dispatch);
11430 assert!(
11431 !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
11432 "injected armed CurrentThread host dispatch panic"
11433 );
11434 CurrentThreadHostDispatchResult::Accepted
11435 }
11436
11437 fn record_current_thread_republish_rearm_dispatch(
11438 dispatch: u64,
11439 ) -> CurrentThreadHostDispatchResult {
11440 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
11441 .lock()
11442 .unwrap()
11443 .push(dispatch);
11444 CurrentThreadHostDispatchResult::Accepted
11445 }
11446
11447 fn reject_initial_current_thread_publication_once(
11448 dispatch: u64,
11449 ) -> CurrentThreadHostDispatchResult {
11450 let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
11451 .lock()
11452 .unwrap();
11453 dispatches.push(dispatch);
11454 if dispatches.len() == 1 {
11455 CurrentThreadHostDispatchResult::Unavailable
11456 } else {
11457 CurrentThreadHostDispatchResult::Accepted
11458 }
11459 }
11460
11461 fn reject_current_thread_replacement_publication_once(
11462 dispatch: u64,
11463 ) -> CurrentThreadHostDispatchResult {
11464 let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
11465 .lock()
11466 .unwrap();
11467 dispatches.push(dispatch);
11468 if dispatches.len() == 2 {
11469 CurrentThreadHostDispatchResult::Unavailable
11470 } else {
11471 CurrentThreadHostDispatchResult::Accepted
11472 }
11473 }
11474
11475 fn fail_current_thread_replacement_publication_once(
11476 dispatch: u64,
11477 ) -> CurrentThreadHostDispatchResult {
11478 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
11479 .lock()
11480 .unwrap()
11481 .push(dispatch);
11482 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
11483 }
11484
11485 fn count_current_thread_shutdown_republish_host_dispatch(
11486 _dispatch: u64,
11487 ) -> CurrentThreadHostDispatchResult {
11488 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11489 CurrentThreadHostDispatchResult::Accepted
11490 }
11491
11492 fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11493 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11494 CurrentThreadHostDispatchResult::Accepted
11495 }
11496
11497 fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
11498 let pending = executor.dispatch_pending.load(Ordering::Acquire);
11499 if pending != 0 {
11500 return pending;
11501 }
11502 let dispatch = next_current_thread_dispatch_id();
11503 match executor.dispatch_pending.compare_exchange(
11504 0,
11505 dispatch,
11506 Ordering::AcqRel,
11507 Ordering::Acquire,
11508 ) {
11509 Ok(_) => dispatch,
11510 Err(pending) => pending,
11511 }
11512 }
11513
11514 fn current_thread_dispatch_failure(
11515 dispatch: u64,
11516 epoch: u64,
11517 ) -> CurrentThreadTaskDispatchFailure {
11518 CurrentThreadTaskDispatchFailure { dispatch, epoch }
11519 }
11520
11521 fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
11522 let executor = controller
11523 .running_current_thread_executor()
11524 .expect("the test controller must have a running CurrentThread executor");
11525 current_thread_dispatch(&executor)
11526 }
11527
11528 #[test]
11529 fn current_thread_executor_drives_spawned_tasks() {
11530 let metrics = Arc::new(RuntimeMetrics::default());
11531 let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
11532 let scheduler = Arc::clone(&executor);
11533 let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
11534 scheduler.schedule(runnable);
11535 });
11536
11537 executor.schedule(runnable);
11538
11539 let mut output = None;
11540 {
11541 let mut future = std::pin::pin!(async {
11542 output = Some(task.await);
11543 });
11544 executor.block_on(future.as_mut());
11545 }
11546 assert_eq!(output, Some(42));
11547 assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
11548 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
11549 }
11550
11551 #[cfg(not(target_family = "wasm"))]
11552 #[test]
11553 fn current_thread_shutdown_wake_does_not_repoll_user_future() {
11554 use std::sync::mpsc;
11555
11556 let executor = Arc::new(CurrentThreadExecutor::with_detection(
11557 Arc::new(RuntimeMetrics::default()),
11558 false,
11559 None,
11560 ));
11561 let polls = Arc::new(AtomicUsize::new(0));
11562 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11563 let (outcome_tx, outcome_rx) = mpsc::channel();
11564 let driver_executor = Arc::clone(&executor);
11565 let driver_polls = Arc::clone(&polls);
11566 let driver = std::thread::spawn(move || {
11567 let mut future = std::pin::pin!(StopPollProbe {
11568 polls: driver_polls,
11569 first_poll: Some(first_poll_tx)
11570 });
11571 outcome_tx
11572 .send(driver_executor.block_on(future.as_mut()))
11573 .unwrap();
11574 });
11575
11576 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11577 wait_for_generation_parker_to_sleep(&executor.stop);
11578 executor.stop.begin_shutdown();
11579
11580 assert_eq!(
11581 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11582 BlockOnOutcome::Stopped
11583 );
11584 driver.join().unwrap();
11585 assert_eq!(
11586 polls.load(Ordering::SeqCst),
11587 1,
11588 "a shutdown wake must be checked before the next CurrentThread user poll"
11589 );
11590 }
11591
11592 #[cfg(not(target_family = "wasm"))]
11593 #[test]
11594 fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
11595 use std::sync::mpsc;
11596
11597 let metrics = Arc::new(RuntimeMetrics::default());
11598 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11599 Arc::clone(&metrics),
11600 accept_current_thread_host_dispatch,
11601 ));
11602 let polls = Arc::new(AtomicUsize::new(0));
11603 let task_polls = Arc::clone(&polls);
11604 let (runnable, task) = async_task::spawn(
11605 async move {
11606 task_polls.fetch_add(1, Ordering::SeqCst);
11607 },
11608 |_| {},
11609 );
11610 executor.schedule(runnable);
11611 let dispatch = current_thread_dispatch(&executor);
11612
11613 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
11614 let (release_claim_tx, release_claim_rx) = mpsc::channel();
11615 let driver_executor = Arc::clone(&executor);
11616 let driver = std::thread::spawn(move || {
11617 CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
11618 *slot.borrow_mut() = Some(Box::new(move || {
11619 claim_locked_tx.send(()).unwrap();
11620 release_claim_rx.recv().unwrap();
11621 }));
11622 });
11623 driver_executor.drive_host_turn_for_dispatch(dispatch);
11624 });
11625 claim_locked_rx
11626 .recv_timeout(Duration::from_secs(2))
11627 .expect("the admitted host turn must pause before its runnable claim");
11628
11629 executor.stop.publish_stopping();
11630 release_claim_tx.send(()).unwrap();
11631 driver.join().unwrap();
11632
11633 assert_eq!(
11634 polls.load(Ordering::SeqCst),
11635 0,
11636 "stop publication must outrank a later runnable claim by an already-admitted host turn"
11637 );
11638 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11639
11640 executor.begin_shutdown();
11641 assert!(
11642 futures::executor::block_on(task.fallible()).is_none(),
11643 "shutdown must cancel the runnable rejected by the stopped claim"
11644 );
11645 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11646 }
11647
11648 #[cfg(not(target_family = "wasm"))]
11649 #[test]
11650 fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
11651 use std::sync::mpsc;
11652
11653 let metrics = Arc::new(RuntimeMetrics::default());
11654 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11655 Arc::clone(&metrics),
11656 accept_current_thread_host_dispatch,
11657 ));
11658 let polls = Arc::new(AtomicUsize::new(0));
11659 let task_polls = Arc::clone(&polls);
11660 let (runnable, task) = async_task::spawn(
11661 async move {
11662 task_polls.fetch_add(1, Ordering::SeqCst);
11663 },
11664 |_| {},
11665 );
11666 executor.schedule(runnable);
11667
11668 let queue = executor
11669 .queue
11670 .lock()
11671 .unwrap_or_else(std::sync::PoisonError::into_inner);
11672 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
11673 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
11674 let cancelling_executor = Arc::clone(&executor);
11675 let cancellation = std::thread::spawn(move || {
11676 let scheduler = cancelling_executor
11677 .scheduler_idle_lock
11678 .lock()
11679 .unwrap_or_else(std::sync::PoisonError::into_inner);
11680 let cancelling = cancelling_executor
11681 .begin_failed_dispatch_cancellation()
11682 .expect("the test must publish terminal dispatch cancellation");
11683 drop(scheduler);
11684 cancellation_published_tx.send(()).unwrap();
11685 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
11686 cancellation_done_tx.send(()).unwrap();
11687 });
11688 cancellation_published_rx
11689 .recv_timeout(Duration::from_secs(2))
11690 .expect("terminal dispatch cancellation must publish before queue draining");
11691
11692 let claimant_executor = Arc::clone(&executor);
11693 let claimant = std::thread::spawn(move || claimant_executor.drain_one());
11694 assert!(
11695 !claimant.join().unwrap(),
11696 "a runnable claim after terminal cancellation publication must be rejected"
11697 );
11698 assert_eq!(polls.load(Ordering::SeqCst), 0);
11699 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11700 assert!(
11701 matches!(
11702 cancellation_done_rx.try_recv(),
11703 Err(mpsc::TryRecvError::Empty)
11704 ),
11705 "terminal cancellation must still be waiting to drain the queued runnable"
11706 );
11707
11708 drop(queue);
11709 cancellation_done_rx
11710 .recv_timeout(Duration::from_secs(2))
11711 .expect("terminal cancellation must drain the rejected runnable");
11712 cancellation.join().unwrap();
11713 assert!(
11714 futures::executor::block_on(task.fallible()).is_none(),
11715 "terminal cancellation must cancel the runnable it prevented from starting"
11716 );
11717 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11718 }
11719
11720 #[cfg(not(target_family = "wasm"))]
11721 #[test]
11722 fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
11723 use std::sync::mpsc;
11724
11725 let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
11726 let polls = Arc::new(AtomicUsize::new(0));
11727 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11728 let (outcome_tx, outcome_rx) = mpsc::channel();
11729 let driver_executor = Arc::clone(&executor);
11730 let driver_polls = Arc::clone(&polls);
11731 let driver = std::thread::spawn(move || {
11732 let mut future = std::pin::pin!(StopPollProbe {
11733 polls: driver_polls,
11734 first_poll: Some(first_poll_tx)
11735 });
11736 outcome_tx
11737 .send(driver_executor.block_on(future.as_mut()))
11738 .unwrap();
11739 });
11740
11741 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11742 wait_for_generation_parker_to_sleep(&executor.stop);
11743 executor.stop.begin_shutdown();
11744
11745 assert_eq!(
11746 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11747 BlockOnOutcome::Stopped
11748 );
11749 driver.join().unwrap();
11750 assert_eq!(
11751 polls.load(Ordering::SeqCst),
11752 1,
11753 "a shutdown wake must be checked before the next parking block_on user poll"
11754 );
11755 }
11756
11757 #[cfg(not(target_family = "wasm"))]
11758 #[test]
11759 fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
11760 use std::sync::mpsc;
11761
11762 let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
11763 let polls = Arc::new(AtomicUsize::new(0));
11764 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11765 let (outcome_tx, outcome_rx) = mpsc::channel();
11766 let driver_executor = Arc::clone(&executor);
11767 let driver_polls = Arc::clone(&polls);
11768 let driver = std::thread::spawn(move || {
11769 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
11770 let mut future = std::pin::pin!(StopPollProbe {
11771 polls: driver_polls,
11772 first_poll: Some(first_poll_tx)
11773 });
11774 outcome_tx
11775 .send(driver_executor.block_on(future.as_mut()))
11776 .unwrap();
11777 });
11778
11779 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11780 wait_for_generation_parker_to_sleep(&executor.stop);
11781 executor.stop.begin_shutdown();
11782
11783 assert_eq!(
11784 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11785 BlockOnOutcome::Stopped
11786 );
11787 driver.join().unwrap();
11788 assert_eq!(
11789 polls.load(Ordering::SeqCst),
11790 1,
11791 "a shutdown wake must be checked before the next cooperative user poll"
11792 );
11793 }
11794
11795 #[test]
11796 fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
11797 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11798 Arc::new(RuntimeMetrics::default()),
11799 accept_current_thread_host_dispatch,
11800 ));
11801 let pending = u64::MAX - 1;
11802 executor.dispatch_pending.store(pending, Ordering::Release);
11803 let mut allocations = 0;
11804
11805 executor.request_drain_with(|| {
11806 allocations += 1;
11807 u64::MAX
11808 });
11809
11810 assert_eq!(allocations, 0);
11811 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
11812 }
11813
11814 #[cfg(not(target_family = "wasm"))]
11815 #[test]
11816 fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
11817 use std::sync::mpsc;
11818
11819 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11820 Arc::new(RuntimeMetrics::default()),
11821 accept_current_thread_host_dispatch,
11822 ));
11823 let dispatch = current_thread_dispatch(&executor);
11824 let scheduler = executor
11825 .scheduler_idle_lock
11826 .lock()
11827 .unwrap_or_else(std::sync::PoisonError::into_inner);
11828 let (claiming_tx, claiming_rx) = mpsc::channel();
11829 let (claimed_tx, claimed_rx) = mpsc::channel();
11830 let claiming_executor = Arc::clone(&executor);
11831 let claimant = std::thread::spawn(move || {
11832 claiming_tx.send(()).unwrap();
11833 claimed_tx
11834 .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
11835 .unwrap();
11836 });
11837 claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11838
11839 executor.stop.begin_shutdown();
11840 drop(scheduler);
11841
11842 assert!(
11843 !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11844 "an old-generation dispatch must not claim a host turn after stop becomes visible"
11845 );
11846 claimant.join().unwrap();
11847 assert_eq!(
11848 executor.dispatch_pending.load(Ordering::Acquire),
11849 dispatch,
11850 "rejecting the stopped claim must not consume its stale capability"
11851 );
11852 }
11853
11854 #[cfg(not(target_family = "wasm"))]
11855 #[test]
11856 fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
11857 use std::sync::mpsc;
11858
11859 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11860 let metrics = Arc::new(RuntimeMetrics::default());
11861 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11862 Arc::clone(&metrics),
11863 count_current_thread_stopped_host_dispatch,
11864 ));
11865 let scheduler = executor
11866 .scheduler_idle_lock
11867 .lock()
11868 .unwrap_or_else(std::sync::PoisonError::into_inner);
11869 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
11870 let scheduling_executor = Arc::clone(&executor);
11871 let scheduling = std::thread::spawn(move || {
11872 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
11873 *slot.borrow_mut() = Some(Box::new(move || {
11874 publication_blocked_tx.send(()).unwrap();
11875 }));
11876 });
11877 let scheduler = Arc::clone(&scheduling_executor);
11878 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
11879 scheduler.schedule(runnable);
11880 });
11881 task.detach();
11882 scheduling_executor.schedule(runnable);
11883 });
11884
11885 publication_blocked_rx
11886 .recv_timeout(Duration::from_secs(2))
11887 .expect("dispatch publication must block behind the scheduler-idle mutex");
11888 executor.begin_shutdown();
11889 drop(scheduler);
11890 scheduling.join().unwrap();
11891 executor.wait_until_scheduler_idle();
11892
11893 assert_eq!(
11894 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
11895 0,
11896 "a publication that reaches the scheduler-idle barrier after stop must not call the host"
11897 );
11898 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
11899 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11900 }
11901
11902 #[test]
11903 fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
11904 use std::{sync::mpsc, time::Duration};
11905
11906 let metrics = Arc::new(RuntimeMetrics::default());
11907 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11908 Arc::clone(&metrics),
11909 accept_current_thread_host_dispatch,
11910 ));
11911 let scheduler = Arc::clone(&executor);
11912 let completed = Arc::new(AtomicBool::new(false));
11913 let completed_task = Arc::clone(&completed);
11914 let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
11915 let shared = async move {
11916 wake_rx.await.expect("wake sender dropped");
11917 }
11918 .boxed()
11919 .shared();
11920 let (runnable, task) = async_task::spawn(
11921 async move {
11922 shared.await;
11923 completed_task.store(true, Ordering::SeqCst);
11924 },
11925 move |runnable| scheduler.schedule(runnable),
11926 );
11927
11928 executor.schedule(runnable);
11929 assert!(
11930 !completed.load(Ordering::SeqCst),
11931 "accepted host dispatch must defer the first poll"
11932 );
11933 executor.drive_host_turn();
11934
11935 let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
11936 std::thread::spawn(move || {
11937 wake_tx.send(()).expect("shared wake receiver dropped");
11938 send_returned_tx.send(()).unwrap();
11939 });
11940 send_returned_rx
11941 .recv_timeout(Duration::from_secs(1))
11942 .expect("Shared wake re-entered its own waker mutex through inline polling");
11943 assert!(
11944 !completed.load(Ordering::SeqCst),
11945 "the wake must remain queued until a fresh host turn"
11946 );
11947
11948 executor.drive_host_turn();
11949 futures::executor::block_on(task);
11950 assert!(completed.load(Ordering::SeqCst));
11951 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11952 }
11953
11954 #[test]
11955 fn current_thread_hostless_wake_is_enqueue_only() {
11956 use std::{sync::mpsc, time::Duration};
11957
11958 struct LockOnSecondPoll {
11959 gate: Arc<Mutex<()>>,
11960 first: bool,
11961 waker: Option<mpsc::Sender<Waker>>,
11962 completed: Arc<AtomicBool>,
11963 }
11964
11965 impl Future for LockOnSecondPoll {
11966 type Output = ();
11967
11968 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
11969 if self.first {
11970 self.first = false;
11971 self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
11972 return Poll::Pending;
11973 }
11974 drop(self.gate.lock().unwrap());
11975 self.completed.store(true, Ordering::SeqCst);
11976 Poll::Ready(())
11977 }
11978 }
11979
11980 let metrics = Arc::new(RuntimeMetrics::default());
11981 let executor = Arc::new(CurrentThreadExecutor::new(metrics));
11982 let gate = Arc::new(Mutex::new(()));
11983 let completed = Arc::new(AtomicBool::new(false));
11984 let (waker_tx, waker_rx) = mpsc::channel();
11985 let scheduler = Arc::clone(&executor);
11986 let (runnable, task) = async_task::spawn(
11987 LockOnSecondPoll {
11988 gate: Arc::clone(&gate),
11989 first: true,
11990 waker: Some(waker_tx),
11991 completed: Arc::clone(&completed),
11992 },
11993 move |runnable| scheduler.schedule(runnable),
11994 );
11995 executor.schedule(runnable);
11996 executor.drive_host_turn();
11997 let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
11998
11999 let (returned_tx, returned_rx) = mpsc::channel();
12000 let wake_gate = Arc::clone(&gate);
12001 let waking = std::thread::spawn(move || {
12002 let _held = wake_gate.lock().unwrap();
12003 waker.wake();
12004 returned_tx.send(()).unwrap();
12005 });
12006 returned_rx
12007 .recv_timeout(Duration::from_secs(1))
12008 .expect("hostless wake must enqueue without polling under the caller's lock");
12009 waking.join().unwrap();
12010 assert!(!completed.load(Ordering::SeqCst));
12011
12012 executor.drive_host_turn();
12013 futures::executor::block_on(task);
12014 assert!(completed.load(Ordering::SeqCst));
12015 }
12016
12017 #[cfg(not(target_family = "wasm"))]
12018 #[test]
12019 fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
12020 use std::sync::mpsc;
12021
12022 let controller = Arc::new(current_thread_controller(
12023 "current-thread-hostless-late-work",
12024 ));
12025 let backend = controller.backend();
12026 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
12027 panic!("configured CurrentThread backend must create a CurrentThread executor");
12028 };
12029 let executor = Arc::clone(executor);
12030 drop(backend);
12031
12032 let (value_tx, value_rx) = oneshot::channel();
12033 let (done_tx, done_rx) = mpsc::channel();
12034 let block_on_controller = Arc::clone(&controller);
12035 let runner = std::thread::spawn(move || {
12036 let mut output = None;
12037 let outcome = {
12038 let mut future = std::pin::pin!(async {
12039 output = Some(value_rx.await.expect("scheduled sender must deliver"));
12040 });
12041 block_on_controller.block_on(future.as_mut())
12042 };
12043 done_tx.send((outcome, output)).unwrap();
12044 });
12045
12046 wait_until(
12047 "the hostless CurrentThread block_on driver to sleep",
12048 || {
12049 executor
12050 .stop
12051 .parkers
12052 .lock()
12053 .unwrap_or_else(std::sync::PoisonError::into_inner)
12054 .iter()
12055 .filter_map(Weak::upgrade)
12056 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12057 },
12058 );
12059
12060 controller
12061 .try_spawn_detached(async move {
12062 value_tx
12063 .send(37usize)
12064 .expect("block_on receiver must remain alive");
12065 })
12066 .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
12067
12068 let (outcome, output) = done_rx
12069 .recv_timeout(Duration::from_secs(2))
12070 .expect("queue publication must wake the sleeping explicit driver");
12071 assert_eq!(outcome, BlockOnOutcome::Completed);
12072 assert_eq!(output, Some(37));
12073 runner.join().unwrap();
12074 controller.shutdown().unwrap();
12075 }
12076
12077 #[cfg(not(target_family = "wasm"))]
12078 #[test]
12079 fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
12080 use std::sync::mpsc;
12081
12082 struct BlockingPoll {
12083 entered: Option<mpsc::Sender<()>>,
12084 release: mpsc::Receiver<()>,
12085 }
12086
12087 impl Future for BlockingPoll {
12088 type Output = ();
12089
12090 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12091 self.entered.take().unwrap().send(()).unwrap();
12092 self.release.recv().unwrap();
12093 Poll::Ready(())
12094 }
12095 }
12096
12097 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12098 Arc::new(RuntimeMetrics::default()),
12099 reject_current_thread_host_dispatch,
12100 ));
12101 let (value_tx, value_rx) = oneshot::channel();
12102 let (older_done_tx, older_done_rx) = mpsc::channel();
12103 let older_executor = Arc::clone(&executor);
12104 let older = std::thread::spawn(move || {
12105 let mut value = None;
12106 {
12107 let mut future = std::pin::pin!(async {
12108 value = Some(value_rx.await.unwrap());
12109 });
12110 assert_eq!(
12111 older_executor.block_on(future.as_mut()),
12112 BlockOnOutcome::Completed
12113 );
12114 }
12115 older_done_tx.send(value.unwrap()).unwrap();
12116 });
12117 wait_until("the older CurrentThread driver to sleep", || {
12118 executor
12119 .stop
12120 .parkers
12121 .lock()
12122 .unwrap_or_else(std::sync::PoisonError::into_inner)
12123 .iter()
12124 .filter_map(Weak::upgrade)
12125 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12126 });
12127
12128 let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
12129 let (release_newer_tx, release_newer_rx) = mpsc::channel();
12130 let newer_executor = Arc::clone(&executor);
12131 let newer = std::thread::spawn(move || {
12132 let mut future = std::pin::pin!(BlockingPoll {
12133 entered: Some(newer_entered_tx),
12134 release: release_newer_rx
12135 });
12136 assert_eq!(
12137 newer_executor.block_on(future.as_mut()),
12138 BlockOnOutcome::Completed
12139 );
12140 });
12141 newer_entered_rx
12142 .recv_timeout(Duration::from_secs(1))
12143 .expect("the newer driver must be blocked inside its poll");
12144
12145 let scheduler = Arc::clone(&executor);
12146 let (runnable, task) = async_task::spawn(
12147 async move {
12148 value_tx.send(41usize).unwrap();
12149 },
12150 move |runnable| scheduler.schedule(runnable),
12151 );
12152 task.detach();
12153 executor.schedule(runnable);
12154
12155 assert_eq!(
12156 older_done_rx
12157 .recv_timeout(Duration::from_secs(2))
12158 .expect("queue publication must also wake the older sleeping driver"),
12159 41
12160 );
12161 release_newer_tx.send(()).unwrap();
12162 older.join().unwrap();
12163 newer.join().unwrap();
12164 }
12165
12166 #[test]
12167 fn current_thread_shutdown_cancels_host_queued_task() {
12168 use std::sync::mpsc;
12169
12170 struct DropSignal(mpsc::Sender<()>);
12171
12172 impl Drop for DropSignal {
12173 fn drop(&mut self) {
12174 self.0.send(()).unwrap();
12175 }
12176 }
12177
12178 let metrics = Arc::new(RuntimeMetrics::default());
12179 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12180 Arc::clone(&metrics),
12181 accept_current_thread_host_dispatch,
12182 ));
12183 let scheduler = Arc::clone(&executor);
12184 let (dropped_tx, dropped_rx) = mpsc::channel();
12185 let signal = DropSignal(dropped_tx);
12186 let (runnable, task) = async_task::spawn(
12187 async move {
12188 let _signal = signal;
12189 futures::future::pending::<()>().await;
12190 },
12191 move |runnable| scheduler.schedule(runnable),
12192 );
12193
12194 executor.schedule(runnable);
12195 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12196 executor.begin_shutdown();
12197
12198 dropped_rx
12199 .recv_timeout(Duration::from_secs(1))
12200 .expect("shutdown must drop a task queued behind a host turn");
12201 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12202 assert!(
12203 futures::executor::block_on(task.fallible()).is_none(),
12204 "the queued task handle must resolve as cancelled"
12205 );
12206 }
12207
12208 #[test]
12209 fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
12210 reset_current_thread_recovery_host_dispatches();
12211 let metrics = Arc::new(RuntimeMetrics::default());
12212 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12213 Arc::clone(&metrics),
12214 count_current_thread_recovery_host_dispatch,
12215 ));
12216 let scheduler = Arc::clone(&executor);
12217 let completed = Arc::new(AtomicBool::new(false));
12218 let completed_task = Arc::clone(&completed);
12219 let (runnable, task) = async_task::spawn(
12220 async move {
12221 completed_task.store(true, Ordering::SeqCst);
12222 },
12223 move |runnable| scheduler.schedule(runnable),
12224 );
12225
12226 executor.schedule(runnable);
12227 assert_eq!(current_thread_recovery_host_dispatches(), 1);
12228 let lost_dispatch = current_thread_dispatch(&executor);
12229
12230 executor.request_drain_if_queued();
12234 assert_eq!(
12235 current_thread_recovery_host_dispatches(),
12236 2,
12237 "the newly registered host must receive the pending executor dispatch"
12238 );
12239 assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
12240 executor.drive_host_turn_for_dispatch(lost_dispatch);
12241 futures::executor::block_on(task);
12242 assert!(completed.load(Ordering::SeqCst));
12243 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12244
12245 let second_completed = Arc::new(AtomicBool::new(false));
12246 let second_completed_task = Arc::clone(&second_completed);
12247 let second_scheduler = Arc::clone(&executor);
12248 let (second_runnable, second_task) = async_task::spawn(
12249 async move {
12250 second_completed_task.store(true, Ordering::SeqCst);
12251 },
12252 move |runnable| second_scheduler.schedule(runnable),
12253 );
12254 executor.schedule(second_runnable);
12255 assert_eq!(
12256 current_thread_recovery_host_dispatches(),
12257 3,
12258 "work queued after the stale callback must receive a new dispatch"
12259 );
12260 let third_dispatch = current_thread_dispatch(&executor);
12261
12262 executor.drive_host_turn_for_dispatch(lost_dispatch);
12265 assert!(!second_completed.load(Ordering::SeqCst));
12266 executor.drive_host_turn_for_dispatch(third_dispatch);
12267 futures::executor::block_on(second_task);
12268 assert!(second_completed.load(Ordering::SeqCst));
12269
12270 let third_scheduler = Arc::clone(&executor);
12271 let (third_runnable, third_task) =
12272 async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
12273 executor.schedule(third_runnable);
12274 assert_eq!(
12275 current_thread_recovery_host_dispatches(),
12276 4,
12277 "a stale callback must not leave the dispatch latch stuck"
12278 );
12279 executor.drive_host_turn();
12280 futures::executor::block_on(third_task);
12281 }
12282
12283 #[test]
12284 fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
12285 let metrics = Arc::new(RuntimeMetrics::default());
12286 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12287 Arc::clone(&metrics),
12288 accept_current_thread_host_dispatch,
12289 ));
12290 let scheduler = Arc::clone(&executor);
12291 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12292 scheduler.schedule(runnable)
12293 });
12294
12295 executor.schedule(runnable);
12296 let failed_dispatch = current_thread_dispatch(&executor);
12297 executor.cancel_host_dispatch_for_test(failed_dispatch);
12298 let recovery_dispatch = current_thread_dispatch(&executor);
12299 assert_eq!(
12300 executor.recovery_dispatch.load(Ordering::Acquire),
12301 recovery_dispatch
12302 );
12303
12304 executor.request_drain_if_queued();
12305 let new_host_dispatch = current_thread_dispatch(&executor);
12306 assert_eq!(new_host_dispatch, recovery_dispatch);
12307 assert_eq!(
12308 executor.recovery_dispatch.load(Ordering::Acquire),
12309 recovery_dispatch
12310 );
12311 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12312
12313 executor.drive_host_turn_for_dispatch(new_host_dispatch);
12314 assert_eq!(futures::executor::block_on(task), 17);
12315 }
12316
12317 #[cfg(not(target_family = "wasm"))]
12318 #[test]
12319 fn current_thread_host_admission_serializes_dispatch_publication() {
12320 use std::sync::mpsc;
12321
12322 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12323 let metrics = Arc::new(RuntimeMetrics::default());
12324 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12325 Arc::clone(&metrics),
12326 count_current_thread_admission_host_dispatch,
12327 ));
12328 let first_scheduler = Arc::clone(&executor);
12329 let (first_runnable, first_task) =
12330 async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
12331 first_task.detach();
12332 executor.schedule(first_runnable);
12333 let dispatch = current_thread_dispatch(&executor);
12334 assert_eq!(
12335 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
12336 1
12337 );
12338
12339 let (claimed_tx, claimed_rx) = mpsc::channel();
12340 let (release_tx, release_rx) = mpsc::channel();
12341 let driver_executor = Arc::clone(&executor);
12342 let driver = std::thread::spawn(move || {
12343 CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
12344 *slot.borrow_mut() = Some(Box::new(move || {
12345 claimed_tx.send(()).unwrap();
12346 release_rx.recv().unwrap();
12347 }));
12348 });
12349 driver_executor.drive_host_turn_for_dispatch(dispatch);
12350 });
12351 claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12352
12353 let second_ran = Arc::new(AtomicBool::new(false));
12354 let second_ran_task = Arc::clone(&second_ran);
12355 let scheduling_executor = Arc::clone(&executor);
12356 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
12357 let (scheduled_tx, scheduled_rx) = mpsc::channel();
12358 let scheduling = std::thread::spawn(move || {
12359 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
12360 *slot.borrow_mut() = Some(Box::new(move || {
12361 publication_blocked_tx.send(()).unwrap();
12362 }));
12363 });
12364 let scheduler = Arc::clone(&scheduling_executor);
12365 let (runnable, task) = async_task::spawn(
12366 async move {
12367 second_ran_task.store(true, Ordering::SeqCst);
12368 },
12369 move |runnable| scheduler.schedule(runnable),
12370 );
12371 task.detach();
12372 scheduling_executor.schedule(runnable);
12373 scheduled_tx.send(()).unwrap();
12374 });
12375
12376 publication_blocked_rx
12377 .recv_timeout(Duration::from_secs(2))
12378 .unwrap();
12379 assert!(
12380 matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
12381 "dispatch publication must wait until host admission publishes its draining role"
12382 );
12383 assert_eq!(
12384 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
12385 1,
12386 "the admission gap must not publish a duplicate host dispatch"
12387 );
12388
12389 release_tx.send(()).unwrap();
12390 scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12391 scheduling.join().unwrap();
12392 driver.join().unwrap();
12393 if !second_ran.load(Ordering::SeqCst) {
12394 executor.drive_host_turn();
12395 }
12396 assert!(second_ran.load(Ordering::SeqCst));
12397 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12398 }
12399
12400 #[test]
12401 fn current_thread_host_turn_yields_after_runnable_budget() {
12402 struct SelfWakingFuture {
12403 remaining: usize,
12404 }
12405
12406 impl Future for SelfWakingFuture {
12407 type Output = ();
12408
12409 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12410 if self.remaining == 0 {
12411 return Poll::Ready(());
12412 }
12413 self.remaining -= 1;
12414 cx.waker().wake_by_ref();
12415 Poll::Pending
12416 }
12417 }
12418
12419 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12420 let metrics = Arc::new(RuntimeMetrics::default());
12421 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12422 Arc::clone(&metrics),
12423 count_current_thread_budget_host_dispatch,
12424 ));
12425 let scheduler = Arc::clone(&executor);
12426 let (runnable, task) = async_task::spawn(
12427 SelfWakingFuture {
12428 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12429 },
12430 move |runnable| scheduler.schedule(runnable),
12431 );
12432
12433 executor.schedule(runnable);
12434 executor.drive_host_turn();
12435
12436 assert_eq!(
12437 metrics.runnable_polls.load(Ordering::Relaxed),
12438 CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
12439 );
12440 assert_eq!(
12441 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
12442 2,
12443 "remaining hot work must continue through a fresh host turn"
12444 );
12445
12446 executor.drive_host_turn();
12447 futures::executor::block_on(task);
12448 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12449 }
12450
12451 #[cfg(not(target_family = "wasm"))]
12452 #[test]
12453 fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
12454 const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
12455
12456 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12457 let metrics = Arc::new(RuntimeMetrics::default());
12458 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12459 Arc::clone(&metrics),
12460 count_current_thread_blocking_budget_host_dispatch,
12461 ));
12462 let work = GenerationWork::new();
12463 let observed = Arc::new(Mutex::new(Vec::new()));
12464
12465 let task_executor = Arc::clone(&executor);
12466 let wake_executor = Arc::clone(&executor);
12467 let producer_executor = Arc::clone(&executor);
12468 let producer_work = Arc::clone(&work);
12469 let producer_observed = Arc::clone(&observed);
12470 let outer_registration = work
12471 .try_register_work()
12472 .expect("the outer blocking job must be accepted");
12473 let (runnable, task) = async_task::spawn(
12474 async move {
12475 let queued = task_executor
12476 .schedule_blocking(
12477 move || {
12478 std::thread::spawn(move || {
12479 (0..QUEUED_BLOCKING_JOBS)
12480 .map(|index| {
12481 let registration = producer_work
12482 .try_register_work()
12483 .expect("queued blocking work must be accepted");
12484 let observed = Arc::clone(&producer_observed);
12485 producer_executor.schedule_blocking(
12486 move || {
12487 observed
12488 .lock()
12489 .unwrap_or_else(std::sync::PoisonError::into_inner)
12490 .push(index);
12491 },
12492 registration,
12493 )
12494 })
12495 .collect::<Vec<_>>()
12496 })
12497 .join()
12498 .expect("the blocking producer must finish")
12499 },
12500 outer_registration,
12501 )
12502 .await
12503 .expect("the inline blocking job must finish");
12504 for handle in queued {
12505 handle.await.expect("queued blocking work must finish");
12506 }
12507 },
12508 move |runnable| wake_executor.schedule(runnable),
12509 );
12510
12511 executor.schedule(runnable);
12512 executor.drive_host_turn();
12513
12514 let first_turn = observed
12515 .lock()
12516 .unwrap_or_else(std::sync::PoisonError::into_inner)
12517 .clone();
12518 assert!(
12519 !first_turn.is_empty(),
12520 "the first host turn must service queued blocking work"
12521 );
12522 assert!(
12523 first_turn.len() < QUEUED_BLOCKING_JOBS,
12524 "inline lane release must not drain the entire blocking FIFO in one host turn"
12525 );
12526 assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
12527 assert_eq!(
12528 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
12529 2,
12530 "remaining blocking work must continue through a fresh host turn"
12531 );
12532
12533 let mut continuation_turns = 0;
12534 while observed
12535 .lock()
12536 .unwrap_or_else(std::sync::PoisonError::into_inner)
12537 .len()
12538 < QUEUED_BLOCKING_JOBS
12539 {
12540 continuation_turns += 1;
12541 assert!(
12542 continuation_turns <= QUEUED_BLOCKING_JOBS,
12543 "queued blocking work failed to make bounded host-turn progress"
12544 );
12545 executor.drive_host_turn();
12546 }
12547 executor.drive_host_turn();
12548 futures::executor::block_on(task);
12549
12550 assert_eq!(
12551 *observed
12552 .lock()
12553 .unwrap_or_else(std::sync::PoisonError::into_inner),
12554 (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
12555 "blocking jobs must preserve FIFO order across host turns"
12556 );
12557 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12558 }
12559
12560 #[cfg(not(target_family = "wasm"))]
12561 #[test]
12562 fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
12563 use std::sync::mpsc;
12564
12565 struct SelfWakingFuture {
12566 remaining: usize,
12567 }
12568
12569 impl Future for SelfWakingFuture {
12570 type Output = ();
12571
12572 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12573 if self.remaining == 0 {
12574 return Poll::Ready(());
12575 }
12576 self.remaining -= 1;
12577 cx.waker().wake_by_ref();
12578 Poll::Pending
12579 }
12580 }
12581
12582 let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
12583 let old_generation = controller.backend().generation();
12584 controller
12585 .try_spawn_detached(SelfWakingFuture {
12586 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12587 })
12588 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
12589 let dispatch = controller_current_thread_dispatch(&controller);
12590
12591 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12592 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12593 let driver_controller = Arc::clone(&controller);
12594 let driver = std::thread::spawn(move || {
12595 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12596 *slot.borrow_mut() = Some(Box::new(move || {
12597 dispatch_entered_tx.send(()).unwrap();
12598 release_dispatch_rx.recv().unwrap();
12599 }));
12600 });
12601 driver_controller.drive_current_thread_dispatch(dispatch, false);
12602 });
12603 dispatch_entered_rx
12604 .recv_timeout(Duration::from_secs(2))
12605 .expect("the bounded host turn must begin publishing its continuation");
12606
12607 let (shutdown_tx, shutdown_rx) = mpsc::channel();
12608 let shutdown_controller = Arc::clone(&controller);
12609 let shutdown = std::thread::spawn(move || {
12610 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12611 });
12612 wait_until("CurrentThread shutdown to leave Running", || {
12613 let state = controller
12614 .state
12615 .lock()
12616 .unwrap_or_else(std::sync::PoisonError::into_inner);
12617 !matches!(
12618 &state.lifecycle,
12619 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12620 )
12621 });
12622
12623 let (restart_tx, restart_rx) = mpsc::channel();
12624 let restart_controller = Arc::clone(&controller);
12625 let restart = std::thread::spawn(move || {
12626 let result = restart_controller
12627 .start()
12628 .map(|()| restart_controller.backend().generation());
12629 restart_tx.send(result).unwrap();
12630 });
12631
12632 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12633 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12634 let shutdown_overlapped = early_shutdown.is_some();
12635 let restart_overlapped = early_restart.is_some();
12636 release_dispatch_tx.send(()).unwrap();
12637 driver.join().unwrap();
12638
12639 let shutdown_result = early_shutdown.unwrap_or_else(|| {
12640 shutdown_rx
12641 .recv_timeout(Duration::from_secs(2))
12642 .expect("shutdown must finish after continuation publication retires")
12643 });
12644 shutdown_result.unwrap();
12645 let new_generation = early_restart.unwrap_or_else(|| {
12646 restart_rx
12647 .recv_timeout(Duration::from_secs(2))
12648 .expect("restart must follow continuation publication and shutdown")
12649 });
12650 let new_generation = new_generation.unwrap();
12651 shutdown.join().unwrap();
12652 restart.join().unwrap();
12653
12654 assert!(
12655 !shutdown_overlapped,
12656 "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
12657 );
12658 assert!(
12659 !restart_overlapped,
12660 "restart must not overlap an old-generation continuation dispatch"
12661 );
12662 assert_ne!(new_generation, old_generation);
12663 controller.shutdown().unwrap();
12664 }
12665
12666 #[cfg(not(target_family = "wasm"))]
12667 #[test]
12668 fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
12669 use std::sync::mpsc;
12670
12671 let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
12672 let old_generation = controller.backend().generation();
12673 let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
12674 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12675 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12676 let submit_controller = Arc::clone(&controller);
12677 let reentrant_controller = Arc::clone(&controller);
12678 let submitter = std::thread::spawn(move || {
12679 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12680 *slot.borrow_mut() = Some(Box::new(move || {
12681 reentrant_shutdown_tx
12682 .send(reentrant_controller.shutdown())
12683 .unwrap();
12684 dispatch_entered_tx.send(()).unwrap();
12685 release_dispatch_rx.recv().unwrap();
12686 }));
12687 });
12688 submit_controller
12689 .try_spawn_detached(std::future::pending::<()>())
12690 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
12691 });
12692
12693 let reentrant_shutdown = reentrant_shutdown_rx
12694 .recv_timeout(Duration::from_secs(2))
12695 .expect("dispatch publication must not self-deadlock on reentrant shutdown");
12696 dispatch_entered_rx
12697 .recv_timeout(Duration::from_secs(2))
12698 .expect("the initial host dispatch publication must reach the injected pause");
12699
12700 let (shutdown_tx, shutdown_rx) = mpsc::channel();
12701 let shutdown_controller = Arc::clone(&controller);
12702 let shutdown = std::thread::spawn(move || {
12703 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12704 });
12705 wait_until("CurrentThread shutdown to leave Running", || {
12706 let state = controller
12707 .state
12708 .lock()
12709 .unwrap_or_else(std::sync::PoisonError::into_inner);
12710 !matches!(
12711 &state.lifecycle,
12712 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12713 )
12714 });
12715
12716 let (restart_tx, restart_rx) = mpsc::channel();
12717 let restart_controller = Arc::clone(&controller);
12718 let restart = std::thread::spawn(move || {
12719 let result = restart_controller
12720 .start()
12721 .map(|()| restart_controller.backend().generation());
12722 restart_tx.send(result).unwrap();
12723 });
12724
12725 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12726 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12727 let shutdown_overlapped = early_shutdown.is_some();
12728 let restart_overlapped = early_restart.is_some();
12729 release_dispatch_tx.send(()).unwrap();
12730 submitter.join().unwrap();
12731
12732 let shutdown_result = early_shutdown.unwrap_or_else(|| {
12733 shutdown_rx
12734 .recv_timeout(Duration::from_secs(2))
12735 .expect("shutdown must finish after initial dispatch publication retires")
12736 });
12737 shutdown_result.unwrap();
12738 let new_generation = early_restart.unwrap_or_else(|| {
12739 restart_rx
12740 .recv_timeout(Duration::from_secs(2))
12741 .expect("restart must follow initial dispatch publication and shutdown")
12742 });
12743 let new_generation = new_generation.unwrap();
12744 shutdown.join().unwrap();
12745 restart.join().unwrap();
12746
12747 let reentrant_error = reentrant_shutdown
12748 .expect_err("dispatch publication must be generation-scoped scheduler work");
12749 assert!(
12750 reentrant_error
12751 .to_string()
12752 .contains("work running on that runtime")
12753 );
12754 assert!(
12755 !shutdown_overlapped,
12756 "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
12757 );
12758 assert!(
12759 !restart_overlapped,
12760 "restart must not overlap an old-generation host dispatch call"
12761 );
12762 assert_ne!(new_generation, old_generation);
12763 controller.shutdown().unwrap();
12764 }
12765
12766 #[test]
12767 fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
12768 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12769 .lock()
12770 .unwrap()
12771 .clear();
12772 CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12773 let metrics = Arc::new(RuntimeMetrics::default());
12774 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12775 Arc::clone(&metrics),
12776 panic_once_current_thread_host_dispatch,
12777 ));
12778 let scheduler = Arc::clone(&executor);
12779 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12780 scheduler.schedule(runnable)
12781 });
12782
12783 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
12784 assert!(
12785 publication.is_err(),
12786 "the injected host dispatch panic must unwind"
12787 );
12788 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12789 {
12790 let scheduler = executor
12791 .scheduler_idle_lock
12792 .lock()
12793 .unwrap_or_else(std::sync::PoisonError::into_inner);
12794 assert_eq!(scheduler.active_dispatch_calls, 0);
12795 assert!(scheduler.dispatch_publications.is_empty());
12796 }
12797
12798 executor.request_drain_if_queued();
12799
12800 assert_ne!(dispatch, 0);
12801 assert_eq!(
12802 *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12803 .lock()
12804 .unwrap(),
12805 vec![dispatch, dispatch],
12806 "a later host registration must retry the same pending capability"
12807 );
12808 {
12809 let scheduler = executor
12810 .scheduler_idle_lock
12811 .lock()
12812 .unwrap_or_else(std::sync::PoisonError::into_inner);
12813 assert_eq!(scheduler.active_dispatch_calls, 0);
12814 assert!(scheduler.dispatch_publications.is_empty());
12815 }
12816 executor.drive_host_turn_for_dispatch(dispatch);
12817 assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
12818 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12819 }
12820
12821 #[cfg(not(target_family = "wasm"))]
12822 #[test]
12823 fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
12824 use std::sync::mpsc;
12825
12826 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12827 .lock()
12828 .unwrap()
12829 .clear();
12830 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12831 let metrics = Arc::new(RuntimeMetrics::default());
12832 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12833 Arc::clone(&metrics),
12834 panic_once_current_thread_armed_republish_host_dispatch,
12835 ));
12836 let scheduler = Arc::clone(&executor);
12837 let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
12838 scheduler.schedule(runnable)
12839 });
12840
12841 let (called_tx, called_rx) = mpsc::channel();
12842 let (release_tx, release_rx) = mpsc::channel();
12843 let publishing_executor = Arc::clone(&executor);
12844 let publication = std::thread::spawn(move || {
12845 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12846 *slot.borrow_mut() = Some(Box::new(move || {
12847 called_tx.send(()).unwrap();
12848 release_rx.recv().unwrap();
12849 }));
12850 });
12851 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12852 });
12853 called_rx
12854 .recv_timeout(Duration::from_secs(2))
12855 .expect("the first publication must pause before calling the host dispatcher");
12856
12857 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12858 executor.request_drain_if_queued();
12859 let publication_state = {
12860 let scheduler = executor
12861 .scheduler_idle_lock
12862 .lock()
12863 .unwrap_or_else(std::sync::PoisonError::into_inner);
12864 (
12865 scheduler.active_dispatch_calls,
12866 scheduler.dispatch_publications.len(),
12867 scheduler.dispatch_publications[0].dispatch,
12868 scheduler.dispatch_publications[0].republish_requested,
12869 )
12870 };
12871 release_tx.send(()).unwrap();
12872
12873 let publication = publication.join().unwrap();
12874 assert!(
12875 publication.is_err(),
12876 "the original host-dispatch panic must resume"
12877 );
12878 assert_ne!(dispatch, 0);
12879 assert_eq!(publication_state, (1, 1, dispatch, true));
12880 assert_eq!(
12881 *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12882 .lock()
12883 .unwrap(),
12884 vec![dispatch, dispatch],
12885 "the armed request must retry automatically before the original panic resumes"
12886 );
12887 {
12888 let scheduler = executor
12889 .scheduler_idle_lock
12890 .lock()
12891 .unwrap_or_else(std::sync::PoisonError::into_inner);
12892 assert_eq!(scheduler.active_dispatch_calls, 0);
12893 assert!(scheduler.dispatch_publications.is_empty());
12894 }
12895 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
12896 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12897
12898 executor.drive_host_turn_for_dispatch(dispatch);
12899 assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
12900 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12901 }
12902
12903 #[cfg(not(target_family = "wasm"))]
12904 #[test]
12905 fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
12906 use std::sync::mpsc;
12907
12908 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12909 .lock()
12910 .unwrap()
12911 .clear();
12912 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
12913 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12914 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12915 {
12916 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12917 .state
12918 .lock()
12919 .unwrap_or_else(std::sync::PoisonError::into_inner);
12920 assert!(state.entries.is_empty());
12921 assert!(state.dispatches.is_empty());
12922 }
12923 let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12924 .register(Arc::new(FailDuringAuthoritativePanicPublication));
12925 let metrics = Arc::new(RuntimeMetrics::default());
12926 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12927 Arc::clone(&metrics),
12928 dispatch_authoritative_panic_current_thread_tasks,
12929 &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
12930 ));
12931 let scheduler = Arc::clone(&executor);
12932 let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
12933 scheduler.schedule(runnable)
12934 });
12935
12936 let (called_tx, called_rx) = mpsc::channel();
12937 let (release_tx, release_rx) = mpsc::channel();
12938 let publishing_executor = Arc::clone(&executor);
12939 let publication = std::thread::spawn(move || {
12940 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12941 *slot.borrow_mut() = Some(Box::new(move || {
12942 called_tx.send(()).unwrap();
12943 release_rx.recv().unwrap();
12944 }));
12945 });
12946 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12947 });
12948 called_rx
12949 .recv_timeout(Duration::from_secs(2))
12950 .expect("the first publication must pause before raising its panic");
12951
12952 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12953 executor.request_drain_if_queued();
12954 release_tx.send(()).unwrap();
12955
12956 let payload = publication
12957 .join()
12958 .unwrap()
12959 .expect_err("the first host-dispatch panic must resume");
12960 assert!(
12961 payload.is::<FirstHostileCurrentThreadPanic>(),
12962 "the first panic must remain authoritative over nested recovery"
12963 );
12964 assert_eq!(
12965 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12966 1,
12967 "the later hostile payload must be discarded under containment"
12968 );
12969 assert_eq!(
12970 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12971 0,
12972 "the authoritative payload must remain owned by the resumed unwind"
12973 );
12974 discard_panic_payload(payload);
12975 assert_eq!(
12976 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12977 1
12978 );
12979
12980 let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12981 .lock()
12982 .unwrap()
12983 .clone();
12984 assert_eq!(dispatches.len(), 3);
12985 let replacement_dispatch = dispatches[2];
12986 assert_eq!(dispatches[0], failed_dispatch);
12987 assert_eq!(dispatches[1], failed_dispatch);
12988 assert_ne!(replacement_dispatch, failed_dispatch);
12989 assert_eq!(
12990 executor.dispatch_pending.load(Ordering::Acquire),
12991 replacement_dispatch
12992 );
12993 assert_eq!(
12994 executor.recovery_dispatch.load(Ordering::Acquire),
12995 replacement_dispatch
12996 );
12997 {
12998 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12999 .state
13000 .lock()
13001 .unwrap_or_else(std::sync::PoisonError::into_inner);
13002 assert!(
13003 !state.dispatches.contains_key(&failed_dispatch),
13004 "nested recovery unwind must retire the exact failed epoch"
13005 );
13006 assert!(!state.dispatches.contains_key(&replacement_dispatch));
13007 }
13008
13009 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
13010 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13011 let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13012 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13013 executor.request_drain_if_queued();
13014
13015 assert_eq!(
13016 *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13017 .lock()
13018 .unwrap(),
13019 vec![
13020 failed_dispatch,
13021 failed_dispatch,
13022 replacement_dispatch,
13023 replacement_dispatch
13024 ],
13025 "retry must retain the replacement reserved before the nested panic"
13026 );
13027 let delivery = healthy.deliveries()[0];
13028 assert_eq!(
13029 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
13030 Some(replacement_dispatch)
13031 );
13032 let mut host_turn = executor
13033 .try_admit_host_turn(replacement_dispatch)
13034 .expect("the retried replacement delivery must be admitted");
13035 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13036 host_turn.drive();
13037 assert!(
13038 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13039 .finish_delivery(delivery, false)
13040 .failed_dispatches
13041 .is_empty()
13042 );
13043 assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
13044 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13045
13046 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
13047 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13048 .state
13049 .lock()
13050 .unwrap_or_else(std::sync::PoisonError::into_inner);
13051 assert!(state.entries.is_empty());
13052 assert!(state.dispatches.is_empty());
13053 }
13054
13055 #[cfg(not(target_family = "wasm"))]
13056 #[test]
13057 fn current_thread_shutdown_suppresses_queued_republish() {
13058 use std::sync::mpsc;
13059
13060 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13061 let metrics = Arc::new(RuntimeMetrics::default());
13062 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13063 Arc::clone(&metrics),
13064 count_current_thread_shutdown_republish_host_dispatch,
13065 ));
13066 let scheduler = Arc::clone(&executor);
13067 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13068 scheduler.schedule(runnable)
13069 });
13070
13071 let (returned_tx, returned_rx) = mpsc::channel();
13072 let (release_tx, release_rx) = mpsc::channel();
13073 let publishing_executor = Arc::clone(&executor);
13074 let publication = std::thread::spawn(move || {
13075 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13076 *slot.borrow_mut() = Some(Box::new(move || {
13077 returned_tx.send(()).unwrap();
13078 release_rx.recv().unwrap();
13079 }));
13080 });
13081 publishing_executor.schedule(runnable);
13082 });
13083 returned_rx
13084 .recv_timeout(Duration::from_secs(2))
13085 .expect("the initial publication must reach its completion barrier");
13086
13087 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13088 executor.request_drain_if_queued();
13089 let publication_state = {
13090 let scheduler = executor
13091 .scheduler_idle_lock
13092 .lock()
13093 .unwrap_or_else(std::sync::PoisonError::into_inner);
13094 (
13095 scheduler.active_dispatch_calls,
13096 scheduler.dispatch_publications.len(),
13097 scheduler.dispatch_publications[0].dispatch,
13098 scheduler.dispatch_publications[0].republish_requested,
13099 )
13100 };
13101 executor.begin_shutdown();
13102 release_tx.send(()).unwrap();
13103 publication.join().unwrap();
13104 executor.wait_until_scheduler_idle();
13105
13106 assert_ne!(dispatch, 0);
13107 assert_eq!(publication_state, (1, 1, dispatch, true));
13108 assert_eq!(
13109 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
13110 1
13111 );
13112 {
13113 let scheduler = executor
13114 .scheduler_idle_lock
13115 .lock()
13116 .unwrap_or_else(std::sync::PoisonError::into_inner);
13117 assert_eq!(scheduler.active_dispatch_calls, 0);
13118 assert!(scheduler.dispatch_publications.is_empty());
13119 }
13120 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13121 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13122 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13123 assert_eq!(futures::executor::block_on(task.fallible()), None);
13124 }
13125
13126 #[cfg(not(target_family = "wasm"))]
13127 #[test]
13128 fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
13129 use std::sync::mpsc;
13130
13131 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13132 .lock()
13133 .unwrap()
13134 .clear();
13135 let metrics = Arc::new(RuntimeMetrics::default());
13136 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13137 Arc::clone(&metrics),
13138 record_current_thread_republish_rearm_dispatch,
13139 ));
13140 let scheduler = Arc::clone(&executor);
13141 let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
13142 scheduler.schedule(runnable)
13143 });
13144
13145 let (first_returned_tx, first_returned_rx) = mpsc::channel();
13146 let (release_first_tx, release_first_rx) = mpsc::channel();
13147 let (second_returned_tx, second_returned_rx) = mpsc::channel();
13148 let (release_second_tx, release_second_rx) = mpsc::channel();
13149 let publishing_executor = Arc::clone(&executor);
13150 let publication = std::thread::spawn(move || {
13151 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13152 *slot.borrow_mut() = Some(Box::new(move || {
13153 first_returned_tx.send(()).unwrap();
13154 release_first_rx.recv().unwrap();
13155 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13156 *slot.borrow_mut() = Some(Box::new(move || {
13157 second_returned_tx.send(()).unwrap();
13158 release_second_rx.recv().unwrap();
13159 }));
13160 });
13161 }));
13162 });
13163 publishing_executor.schedule(runnable);
13164 });
13165 first_returned_rx
13166 .recv_timeout(Duration::from_secs(2))
13167 .expect("the first publication must reach its completion barrier");
13168
13169 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13170 executor.request_drain_if_queued();
13171 executor.request_drain_if_queued();
13172 let first_publication_state = {
13173 let scheduler = executor
13174 .scheduler_idle_lock
13175 .lock()
13176 .unwrap_or_else(std::sync::PoisonError::into_inner);
13177 (
13178 scheduler.active_dispatch_calls,
13179 scheduler.dispatch_publications.len(),
13180 scheduler.dispatch_publications[0].dispatch,
13181 scheduler.dispatch_publications[0].republish_requested,
13182 )
13183 };
13184 release_first_tx.send(()).unwrap();
13185 second_returned_rx
13186 .recv_timeout(Duration::from_secs(2))
13187 .expect("the second publication must reach its completion barrier");
13188
13189 executor.request_drain_if_queued();
13190 executor.request_drain_if_queued();
13191 let second_publication_state = {
13192 let scheduler = executor
13193 .scheduler_idle_lock
13194 .lock()
13195 .unwrap_or_else(std::sync::PoisonError::into_inner);
13196 (
13197 scheduler.active_dispatch_calls,
13198 scheduler.dispatch_publications.len(),
13199 scheduler.dispatch_publications[0].dispatch,
13200 scheduler.dispatch_publications[0].republish_requested,
13201 )
13202 };
13203 release_second_tx.send(()).unwrap();
13204 publication.join().unwrap();
13205
13206 assert_ne!(dispatch, 0);
13207 assert_eq!(first_publication_state, (1, 1, dispatch, true));
13208 assert_eq!(second_publication_state, (1, 1, dispatch, true));
13209 assert_eq!(
13210 *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
13211 vec![dispatch, dispatch, dispatch],
13212 "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
13213 );
13214 {
13215 let scheduler = executor
13216 .scheduler_idle_lock
13217 .lock()
13218 .unwrap_or_else(std::sync::PoisonError::into_inner);
13219 assert_eq!(scheduler.active_dispatch_calls, 0);
13220 assert!(scheduler.dispatch_publications.is_empty());
13221 }
13222 executor.drive_host_turn_for_dispatch(dispatch);
13223 assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
13224 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13225 }
13226
13227 #[cfg(not(target_family = "wasm"))]
13228 #[test]
13229 fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
13230 use std::sync::mpsc;
13231
13232 CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13233 .lock()
13234 .unwrap()
13235 .clear();
13236 let metrics = Arc::new(RuntimeMetrics::default());
13237 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13238 Arc::clone(&metrics),
13239 reject_initial_current_thread_publication_once,
13240 ));
13241 let scheduler = Arc::clone(&executor);
13242 let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
13243 scheduler.schedule(runnable)
13244 });
13245
13246 let (returned_tx, returned_rx) = mpsc::channel();
13247 let (release_tx, release_rx) = mpsc::channel();
13248 let publishing_executor = Arc::clone(&executor);
13249 let publication = std::thread::spawn(move || {
13250 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13251 *slot.borrow_mut() = Some(Box::new(move || {
13252 returned_tx.send(()).unwrap();
13253 release_rx.recv().unwrap();
13254 }));
13255 });
13256 publishing_executor.schedule(runnable);
13257 });
13258 returned_rx
13259 .recv_timeout(Duration::from_secs(2))
13260 .expect("the unavailable initial publication must reach its completion barrier");
13261
13262 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13263 executor.request_drain_if_queued();
13264 let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13265 .lock()
13266 .unwrap()
13267 .len();
13268
13269 release_tx.send(()).unwrap();
13270 publication.join().unwrap();
13271
13272 let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13273 .lock()
13274 .unwrap()
13275 .clone();
13276 assert_ne!(dispatch, 0);
13277 assert_eq!(
13278 attempts_while_unavailable_is_paused, 1,
13279 "a racing host request must join the in-flight publication instead of retiring it independently"
13280 );
13281 assert_eq!(
13282 dispatches,
13283 vec![dispatch, dispatch],
13284 "the publication owner must retry the same internal capability after the new host joins"
13285 );
13286 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
13287 executor.drive_host_turn_for_dispatch(dispatch);
13288 assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
13289 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13290 }
13291
13292 #[cfg(not(target_family = "wasm"))]
13293 #[test]
13294 fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
13295 use std::sync::mpsc;
13296
13297 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13298 .lock()
13299 .unwrap()
13300 .clear();
13301 let metrics = Arc::new(RuntimeMetrics::default());
13302 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13303 Arc::clone(&metrics),
13304 reject_current_thread_replacement_publication_once,
13305 ));
13306 let scheduler = Arc::clone(&executor);
13307 let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
13308 scheduler.schedule(runnable)
13309 });
13310 executor.schedule(runnable);
13311 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13312 assert_ne!(failed_dispatch, 0);
13313
13314 let (returned_tx, returned_rx) = mpsc::channel();
13315 let (release_tx, release_rx) = mpsc::channel();
13316 let cancelling_executor = Arc::clone(&executor);
13317 let cancellation = std::thread::spawn(move || {
13318 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13319 *slot.borrow_mut() = Some(Box::new(move || {
13320 returned_tx.send(()).unwrap();
13321 release_rx.recv().unwrap();
13322 }));
13323 });
13324 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
13325 });
13326 returned_rx
13327 .recv_timeout(Duration::from_secs(2))
13328 .expect("the unavailable replacement publication must reach its completion barrier");
13329
13330 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13331 executor.request_drain_if_queued();
13332 let attempts_while_unavailable_is_paused =
13333 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13334 .lock()
13335 .unwrap()
13336 .len();
13337
13338 release_tx.send(()).unwrap();
13339 cancellation.join().unwrap();
13340
13341 let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13342 .lock()
13343 .unwrap()
13344 .clone();
13345 assert_ne!(replacement_dispatch, 0);
13346 assert_ne!(replacement_dispatch, failed_dispatch);
13347 assert_eq!(
13348 attempts_while_unavailable_is_paused, 2,
13349 "a racing host request must join the in-flight replacement publication"
13350 );
13351 assert_eq!(
13352 dispatches,
13353 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13354 "republication must reuse the sole replacement capability instead of allocating another"
13355 );
13356 assert_eq!(
13357 executor.dispatch_pending.load(Ordering::Acquire),
13358 replacement_dispatch
13359 );
13360 assert_eq!(
13361 executor.recovery_dispatch.load(Ordering::Acquire),
13362 replacement_dispatch
13363 );
13364 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13365
13366 executor.drive_host_turn_for_dispatch(replacement_dispatch);
13367 assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
13368 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13369 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13370 }
13371
13372 #[cfg(not(target_family = "wasm"))]
13373 #[test]
13374 fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
13375 use std::sync::mpsc;
13376
13377 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13378 .lock()
13379 .unwrap()
13380 .clear();
13381 {
13382 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13383 .state
13384 .lock()
13385 .unwrap_or_else(std::sync::PoisonError::into_inner);
13386 assert!(state.entries.is_empty());
13387 assert!(state.dispatches.is_empty());
13388 }
13389 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
13390 let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13391 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
13392 let metrics = Arc::new(RuntimeMetrics::default());
13393 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13394 Arc::clone(&metrics),
13395 fail_current_thread_replacement_publication_once,
13396 &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
13397 ));
13398 let scheduler = Arc::clone(&executor);
13399 let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
13400 scheduler.schedule(runnable)
13401 });
13402 executor.schedule(runnable);
13403 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13404 assert_ne!(failed_dispatch, 0);
13405 let initial_delivery = initial.deliveries()[0];
13406 let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13407 .finish_delivery(initial_delivery, true);
13408 let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
13409 panic!("the initial accepted delivery must report one exact failure");
13410 };
13411 assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
13412 let failed_dispatch_failure = *failed_dispatch_failure;
13413 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
13414 let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13415 .register(Arc::new(FailDuringReplacementRepublishPublication));
13416
13417 let (returned_tx, returned_rx) = mpsc::channel();
13418 let (release_tx, release_rx) = mpsc::channel();
13419 let cancelling_executor = Arc::clone(&executor);
13420 let cancellation = std::thread::spawn(move || {
13421 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13422 *slot.borrow_mut() = Some(Box::new(move || {
13423 returned_tx.send(()).unwrap();
13424 release_rx.recv().unwrap();
13425 }));
13426 });
13427 cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
13428 });
13429 returned_rx
13430 .recv_timeout(Duration::from_secs(2))
13431 .expect("the failed replacement publication must reach its completion barrier");
13432
13433 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13434 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13435 let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13436 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13437 executor.request_drain_if_queued();
13438 let publication_state = {
13439 let scheduler = executor
13440 .scheduler_idle_lock
13441 .lock()
13442 .unwrap_or_else(std::sync::PoisonError::into_inner);
13443 (
13444 scheduler.active_dispatch_calls,
13445 scheduler.dispatch_publications.len(),
13446 scheduler.dispatch_publications[0].dispatch,
13447 scheduler.dispatch_publications[0].republish_requested,
13448 )
13449 };
13450 release_tx.send(()).unwrap();
13451 cancellation.join().unwrap();
13452
13453 assert_ne!(replacement_dispatch, 0);
13454 assert_ne!(replacement_dispatch, failed_dispatch);
13455 assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
13456 assert_eq!(
13457 *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13458 .lock()
13459 .unwrap(),
13460 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13461 "the newly armed host request must receive the exact replacement before cancellation"
13462 );
13463 assert_eq!(
13464 executor.dispatch_pending.load(Ordering::Acquire),
13465 replacement_dispatch
13466 );
13467 assert_eq!(
13468 executor.recovery_dispatch.load(Ordering::Acquire),
13469 replacement_dispatch
13470 );
13471 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13472
13473 let delivery = healthy.deliveries()[0];
13474 assert_eq!(delivery.dispatch, replacement_dispatch);
13475 assert_eq!(
13476 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13477 .claim_delivery(delivery.capability()),
13478 Some(replacement_dispatch)
13479 );
13480 let mut host_turn = executor
13481 .try_admit_host_turn(replacement_dispatch)
13482 .expect("the rearmed replacement delivery must be admitted");
13483 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13484 host_turn.drive();
13485 assert!(
13486 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13487 .finish_delivery(delivery, false)
13488 .failed_dispatches
13489 .is_empty()
13490 );
13491 assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
13492 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13493 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13494
13495 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
13496 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
13497 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13498 .state
13499 .lock()
13500 .unwrap_or_else(std::sync::PoisonError::into_inner);
13501 assert!(state.entries.is_empty());
13502 assert!(state.dispatches.is_empty());
13503 }
13504
13505 #[cfg(not(target_family = "wasm"))]
13506 #[test]
13507 fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
13508 use std::sync::mpsc;
13509
13510 {
13511 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13512 .state
13513 .lock()
13514 .unwrap_or_else(std::sync::PoisonError::into_inner);
13515 assert!(state.entries.is_empty());
13516 assert!(state.dispatches.is_empty());
13517 }
13518 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
13519 let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13520 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
13521 let metrics = Arc::new(RuntimeMetrics::default());
13522 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13523 Arc::clone(&metrics),
13524 dispatch_stale_failure_current_thread_tasks,
13525 &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
13526 ));
13527 let scheduler = Arc::clone(&executor);
13528 let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
13529 scheduler.schedule(runnable)
13530 });
13531 executor.schedule(runnable);
13532
13533 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13534 let initial_delivery = initial.deliveries()[0];
13535 let delayed_completion =
13536 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
13537 let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
13538 panic!("the initial accepted delivery must report one exact failure");
13539 };
13540 assert_eq!(
13541 *delayed_failure,
13542 current_thread_dispatch_failure(dispatch, 0)
13543 );
13544 let delayed_failure = *delayed_failure;
13545
13546 let (returned_tx, returned_rx) = mpsc::channel();
13547 let (release_tx, release_rx) = mpsc::channel();
13548 let publishing_executor = Arc::clone(&executor);
13549 let publication = std::thread::spawn(move || {
13550 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13551 *slot.borrow_mut() = Some(Box::new(move || {
13552 returned_tx.send(()).unwrap();
13553 release_rx.recv().unwrap();
13554 }));
13555 });
13556 publishing_executor.request_drain_if_queued();
13557 });
13558 returned_rx
13559 .recv_timeout(Duration::from_secs(2))
13560 .expect("the old failed epoch must pause before executor recovery");
13561
13562 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13563 let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13564 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13565 executor.request_drain_if_queued();
13566 release_tx.send(()).unwrap();
13567 publication.join().unwrap();
13568
13569 let healthy_delivery = healthy.deliveries()[0];
13570 let delayed_initial_delivery = initial.deliveries()[1];
13571 {
13572 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13573 .state
13574 .lock()
13575 .unwrap_or_else(std::sync::PoisonError::into_inner);
13576 let dispatch_state = state
13577 .dispatches
13578 .get(&dispatch)
13579 .expect("the rearmed dispatch must remain registered");
13580 assert_eq!(dispatch_state.epoch, 1);
13581 assert!(!dispatch_state.is_cancelling());
13582 }
13583
13584 executor.cancel_host_dispatch(delayed_failure);
13585
13586 assert_eq!(
13587 executor.dispatch_pending.load(Ordering::Acquire),
13588 dispatch,
13589 "the delayed old-epoch failure must not consume the rearmed executor capability"
13590 );
13591 assert_eq!(
13592 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13593 .state
13594 .lock()
13595 .unwrap_or_else(std::sync::PoisonError::into_inner)
13596 .dispatches
13597 .get(&dispatch)
13598 .map(|state| (state.epoch, state.phase)),
13599 Some((1, CurrentThreadDispatchPhase::Pending)),
13600 "the delayed old-epoch cancellation must not remove the accepted rearm"
13601 );
13602
13603 assert_eq!(
13604 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
13605 Some(dispatch)
13606 );
13607 let mut host_turn = executor
13608 .try_admit_host_turn(dispatch)
13609 .expect("the current-epoch delivery must be admitted");
13610 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
13611 host_turn.drive();
13612 assert!(
13613 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13614 .finish_delivery(healthy_delivery, false)
13615 .failed_dispatches
13616 .is_empty()
13617 );
13618 assert!(
13619 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13620 .finish_delivery(delayed_initial_delivery, false)
13621 .failed_dispatches
13622 .is_empty()
13623 );
13624 assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
13625 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13626
13627 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
13628 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
13629 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13630 .state
13631 .lock()
13632 .unwrap_or_else(std::sync::PoisonError::into_inner);
13633 assert!(state.entries.is_empty());
13634 assert!(state.dispatches.is_empty());
13635 }
13636
13637 #[cfg(not(target_family = "wasm"))]
13638 #[test]
13639 fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
13640 use std::sync::mpsc;
13641
13642 {
13643 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13644 .state
13645 .lock()
13646 .unwrap_or_else(std::sync::PoisonError::into_inner);
13647 assert!(state.entries.is_empty());
13648 assert!(state.dispatches.is_empty());
13649 }
13650 let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13651 .register(Arc::new(FailDuringLateRegistrationPublication));
13652
13653 let metrics = Arc::new(RuntimeMetrics::default());
13654 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13655 Arc::clone(&metrics),
13656 dispatch_late_registration_current_thread_tasks,
13657 &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
13658 ));
13659 let scheduler = Arc::clone(&executor);
13660 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13661 scheduler.schedule(runnable)
13662 });
13663 metrics.runnable_scheduled();
13664 executor
13665 .queue
13666 .lock()
13667 .unwrap_or_else(std::sync::PoisonError::into_inner)
13668 .runnables
13669 .push_back(runnable);
13670 let replacement_dispatch = next_current_thread_dispatch_id();
13671 executor
13672 .recovery_dispatch
13673 .store(replacement_dispatch, Ordering::Release);
13674 executor
13675 .dispatch_pending
13676 .store(replacement_dispatch, Ordering::Release);
13677
13678 let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
13679 let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
13680 let publishing_executor = Arc::clone(&executor);
13681 let publication = std::thread::spawn(move || {
13682 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13683 *slot.borrow_mut() = Some(Box::new(move || {
13684 owner_retired_tx.send(()).unwrap();
13685 request_blocked_rx
13686 .recv_timeout(Duration::from_secs(2))
13687 .expect("the binding-like drain request must wait for the failure transition");
13688 }));
13689 });
13690 publishing_executor.request_drain_if_queued();
13691 });
13692 owner_retired_rx
13693 .recv_timeout(Duration::from_secs(2))
13694 .expect("the failed replacement publication must retire its owner");
13695
13696 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13699 let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13700 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13701 let requesting_executor = Arc::clone(&executor);
13702 let request = std::thread::spawn(move || {
13703 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13704 *slot.borrow_mut() = Some(Box::new(move || {
13705 request_blocked_tx.send(()).unwrap();
13706 }));
13707 });
13708 requesting_executor.request_drain_if_queued();
13709 });
13710
13711 publication.join().unwrap();
13712 request.join().unwrap();
13713
13714 assert!(
13715 healthy.deliveries().is_empty(),
13716 "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
13717 );
13718 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13719 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13720 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13721 assert_eq!(futures::executor::block_on(task.fallible()), None);
13722 {
13723 let scheduler = executor
13724 .scheduler_idle_lock
13725 .lock()
13726 .unwrap_or_else(std::sync::PoisonError::into_inner);
13727 assert_eq!(scheduler.active_dispatch_calls, 0);
13728 assert!(scheduler.dispatch_publications.is_empty());
13729 }
13730
13731 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
13732
13733 let scheduler = Arc::clone(&executor);
13734 let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
13735 scheduler.schedule(runnable)
13736 });
13737 executor.schedule(later_runnable);
13738 let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13739 let later_delivery = healthy.deliveries()[0];
13740 assert_eq!(
13741 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
13742 Some(later_dispatch)
13743 );
13744 let mut host_turn = executor
13745 .try_admit_host_turn(later_dispatch)
13746 .expect("the later exact delivery must be admitted");
13747 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
13748 host_turn.drive();
13749 assert!(
13750 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13751 .finish_delivery(later_delivery, false)
13752 .failed_dispatches
13753 .is_empty()
13754 );
13755 assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
13756 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13757
13758 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
13759 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13760 .state
13761 .lock()
13762 .unwrap_or_else(std::sync::PoisonError::into_inner);
13763 assert!(state.entries.is_empty());
13764 assert!(state.dispatches.is_empty());
13765 }
13766
13767 #[cfg(not(target_family = "wasm"))]
13768 #[test]
13769 fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
13770 {
13771 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13772 .lock()
13773 .unwrap()
13774 .clear();
13775 {
13776 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13777 .state
13778 .lock()
13779 .unwrap_or_else(std::sync::PoisonError::into_inner);
13780 assert!(state.entries.is_empty());
13781 assert!(state.dispatches.is_empty());
13782 }
13783 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13784 .next_delivery
13785 .store(0, Ordering::Relaxed);
13786 let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13787 .register(Arc::new(FailDuringNestedUnwindPublication));
13788
13789 let metrics = Arc::new(RuntimeMetrics::default());
13790 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13791 Arc::clone(&metrics),
13792 dispatch_nested_unwind_current_thread_tasks,
13793 &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
13794 ));
13795 let scheduler = Arc::clone(&executor);
13796 let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
13797 scheduler.schedule(runnable)
13798 });
13799
13800 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13801 *slot.borrow_mut() = Some(Box::new(|| {
13802 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13803 .next_delivery
13804 .store(u64::MAX, Ordering::Relaxed);
13805 }));
13806 });
13807 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
13808 assert!(
13809 publication.is_err(),
13810 "replacement delivery-id exhaustion must unwind through the outer failed publication"
13811 );
13812
13813 let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13814 .lock()
13815 .unwrap()
13816 .clone();
13817 assert_eq!(dispatches.len(), 2);
13818 let failed_dispatch = dispatches[0];
13819 let replacement_dispatch = dispatches[1];
13820 assert_ne!(failed_dispatch, replacement_dispatch);
13821 assert_eq!(
13822 executor.dispatch_pending.load(Ordering::Acquire),
13823 replacement_dispatch
13824 );
13825 assert_eq!(
13826 executor.recovery_dispatch.load(Ordering::Acquire),
13827 replacement_dispatch
13828 );
13829 {
13830 let scheduler = executor
13831 .scheduler_idle_lock
13832 .lock()
13833 .unwrap_or_else(std::sync::PoisonError::into_inner);
13834 assert_eq!(scheduler.active_dispatch_calls, 0);
13835 assert!(scheduler.dispatch_publications.is_empty());
13836 }
13837 {
13838 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13839 .state
13840 .lock()
13841 .unwrap_or_else(std::sync::PoisonError::into_inner);
13842 assert!(
13843 !state.dispatches.contains_key(&failed_dispatch),
13844 "outer unwind cleanup must retire the exact old cancelling dispatch"
13845 );
13846 assert!(
13847 !state.dispatches.contains_key(&replacement_dispatch),
13848 "the replacement publication guard must release its aborted registry state"
13849 );
13850 }
13851
13852 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13853 .next_delivery
13854 .store(1, Ordering::Relaxed);
13855 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
13856 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13857 let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13858 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13859
13860 executor.request_drain_if_queued();
13861
13862 assert_eq!(
13863 *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
13864 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13865 "retry must reuse the replacement reserved before the nested unwind"
13866 );
13867 let delivery = healthy.deliveries()[0];
13868 assert_eq!(delivery.dispatch, replacement_dispatch);
13869 assert_eq!(
13870 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
13871 Some(replacement_dispatch)
13872 );
13873 let mut host_turn = executor
13874 .try_admit_host_turn(replacement_dispatch)
13875 .expect("the retried replacement delivery must be admitted");
13876 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13877 host_turn.drive();
13878 assert!(
13879 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13880 .finish_delivery(delivery, false)
13881 .failed_dispatches
13882 .is_empty()
13883 );
13884 assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
13885 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13886
13887 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
13888 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13889 .state
13890 .lock()
13891 .unwrap_or_else(std::sync::PoisonError::into_inner);
13892 assert!(state.entries.is_empty());
13893 assert!(state.dispatches.is_empty());
13894 }
13895
13896 #[test]
13897 fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
13898 reset_current_thread_recovery_host_dispatches();
13899 let metrics = Arc::new(RuntimeMetrics::default());
13900 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13901 Arc::clone(&metrics),
13902 count_current_thread_recovery_host_dispatch,
13903 ));
13904
13905 let scheduler = Arc::clone(&executor);
13906 let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
13907 scheduler.schedule(runnable)
13908 });
13909 executor.schedule(runnable);
13910 assert_eq!(current_thread_recovery_host_dispatches(), 1);
13911 let failed_dispatch = current_thread_dispatch(&executor);
13912
13913 executor.cancel_host_dispatch_for_test(failed_dispatch);
13916 assert_eq!(
13917 current_thread_recovery_host_dispatches(),
13918 2,
13919 "cancellation must request one replacement without another submission"
13920 );
13921 let replacement_dispatch = current_thread_dispatch(&executor);
13922 assert_ne!(replacement_dispatch, failed_dispatch);
13923 assert_eq!(
13924 executor.recovery_dispatch.load(Ordering::Acquire),
13925 replacement_dispatch
13926 );
13927
13928 executor.drive_host_turn_for_dispatch(replacement_dispatch);
13929 assert_eq!(futures::executor::block_on(task), 1);
13930 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13931 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13932 }
13933
13934 #[test]
13935 fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
13936 reset_current_thread_recovery_host_dispatches();
13937 let metrics = Arc::new(RuntimeMetrics::default());
13938 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13939 Arc::clone(&metrics),
13940 count_current_thread_recovery_host_dispatch,
13941 ));
13942 let backend =
13943 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13944
13945 let registration = backend
13946 .work
13947 .try_register_async()
13948 .expect("the backend must accept work");
13949 let task = spawn_registered(
13950 &backend,
13951 Arc::clone(&metrics),
13952 std::future::pending::<()>(),
13953 registration,
13954 );
13955 let failed_dispatch = current_thread_dispatch(&executor);
13956 executor.cancel_host_dispatch_for_test(failed_dispatch);
13957 let replacement_dispatch = current_thread_dispatch(&executor);
13958
13959 executor.cancel_host_dispatch_for_test(replacement_dispatch);
13960
13961 assert_eq!(
13962 current_thread_recovery_host_dispatches(),
13963 2,
13964 "a second scheduler failure must not create an unbounded retry loop"
13965 );
13966 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13967 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13968 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13969 assert_eq!(
13970 futures::executor::block_on(task).unwrap_err().to_string(),
13971 "async runtime stopped before the task completed",
13972 "terminal host failure must settle the original operation as cancelled"
13973 );
13974 assert_eq!(backend.work.state.lock().unwrap().active, 0);
13975
13976 let later_registration = backend
13977 .work
13978 .try_register_async()
13979 .expect("terminal cancellation must reopen acceptance");
13980 let later_task = spawn_registered(
13981 &backend,
13982 Arc::clone(&metrics),
13983 async { 2usize },
13984 later_registration,
13985 );
13986 assert_eq!(
13987 current_thread_recovery_host_dispatches(),
13988 3,
13989 "terminal cancellation must not permanently close the executor"
13990 );
13991 executor.drive_host_turn();
13992 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
13993 }
13994
13995 #[cfg(not(target_family = "wasm"))]
13996 #[test]
13997 fn current_thread_recovery_reservation_blocks_new_host_publication() {
13998 use std::sync::mpsc;
13999
14000 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14001 let metrics = Arc::new(RuntimeMetrics::default());
14002 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14003 Arc::clone(&metrics),
14004 count_current_thread_interleaved_recovery_host_dispatch,
14005 ));
14006 let backend =
14007 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14008
14009 let registration = backend
14010 .work
14011 .try_register_async()
14012 .expect("the backend must accept work");
14013 let task = spawn_registered(
14014 &backend,
14015 Arc::clone(&metrics),
14016 std::future::pending::<()>(),
14017 registration,
14018 );
14019 let failed_dispatch = current_thread_dispatch(&executor);
14020 assert_eq!(
14021 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14022 1
14023 );
14024
14025 let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
14026 let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
14027 let cancelling_executor = Arc::clone(&executor);
14028 let cancellation = std::thread::spawn(move || {
14029 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
14030 *slot.borrow_mut() = Some(Box::new(move || {
14031 recovery_cleared_tx.send(()).unwrap();
14032 release_recovery_rx.recv().unwrap();
14033 }));
14034 });
14035 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14036 });
14037 recovery_cleared_rx
14038 .recv_timeout(Duration::from_secs(2))
14039 .expect("failed dispatch cancellation must pause after consuming the old capability");
14040
14041 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
14042 let (publication_done_tx, publication_done_rx) = mpsc::channel();
14043 let publishing_executor = Arc::clone(&executor);
14044 let publication = std::thread::spawn(move || {
14045 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14046 *slot.borrow_mut() = Some(Box::new(move || {
14047 publication_blocked_tx.send(()).unwrap();
14048 }));
14049 });
14050 publishing_executor.request_drain_if_queued();
14051 publication_done_tx.send(()).unwrap();
14052 });
14053
14054 if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
14055 release_recovery_tx.send(()).unwrap();
14056 cancellation.join().unwrap();
14057 publication.join().unwrap();
14058 panic!("a newly registered host must wait for recovery reservation: {error}");
14059 }
14060 assert!(
14061 matches!(
14062 publication_done_rx.try_recv(),
14063 Err(mpsc::TryRecvError::Empty)
14064 ),
14065 "the new host must not publish in the consumed-to-recovery gap"
14066 );
14067 release_recovery_tx.send(()).unwrap();
14068
14069 cancellation.join().unwrap();
14070 publication_done_rx
14071 .recv_timeout(Duration::from_secs(2))
14072 .unwrap();
14073 publication.join().unwrap();
14074
14075 let replacement_dispatch = current_thread_dispatch(&executor);
14076 assert_ne!(replacement_dispatch, failed_dispatch);
14077 assert_eq!(
14078 executor.recovery_dispatch.load(Ordering::Acquire),
14079 replacement_dispatch
14080 );
14081 assert_eq!(
14082 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14083 3,
14084 "the original publication and two host attempts for its one replacement must run"
14085 );
14086
14087 executor.cancel_host_dispatch_for_test(replacement_dispatch);
14088
14089 assert_eq!(
14090 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14091 3,
14092 "failure of the tagged replacement must cancel work without a second replacement"
14093 );
14094 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14095 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14096 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14097 assert_eq!(
14098 futures::executor::block_on(task).unwrap_err().to_string(),
14099 "async runtime stopped before the task completed"
14100 );
14101
14102 let later_registration = backend
14103 .work
14104 .try_register_async()
14105 .expect("terminal cancellation must reopen acceptance");
14106 let later_task = spawn_registered(
14107 &backend,
14108 Arc::clone(&metrics),
14109 async { 2usize },
14110 later_registration,
14111 );
14112 assert_eq!(
14113 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14114 4
14115 );
14116 executor.drive_host_turn();
14117 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
14118 }
14119
14120 #[cfg(not(target_family = "wasm"))]
14121 #[test]
14122 fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
14123 use std::sync::mpsc;
14124
14125 struct BlockingDrop {
14126 entered: mpsc::Sender<()>,
14127 release: mpsc::Receiver<()>,
14128 }
14129
14130 impl Drop for BlockingDrop {
14131 fn drop(&mut self) {
14132 self.entered.send(()).unwrap();
14133 self.release.recv().unwrap();
14134 }
14135 }
14136
14137 reset_current_thread_recovery_host_dispatches();
14138 let metrics = Arc::new(RuntimeMetrics::default());
14139 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14140 Arc::clone(&metrics),
14141 count_current_thread_recovery_host_dispatch,
14142 ));
14143 let backend =
14144 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14145
14146 let holder_registration = backend
14147 .work
14148 .try_register_work()
14149 .expect("the backend must accept the blocking holder");
14150 let (holder_started_tx, holder_started_rx) = mpsc::channel();
14151 let (release_holder_tx, release_holder_rx) = mpsc::channel();
14152 let holder_executor = Arc::clone(&executor);
14153 let holder = std::thread::spawn(move || {
14154 let handle = holder_executor.schedule_blocking(
14155 move || {
14156 holder_started_tx.send(()).unwrap();
14157 release_holder_rx.recv().unwrap();
14158 },
14159 holder_registration,
14160 );
14161 futures::executor::block_on(handle).unwrap();
14162 });
14163 holder_started_rx
14164 .recv_timeout(Duration::from_secs(2))
14165 .expect("the first blocking call must hold the CurrentThread lane");
14166
14167 let cancelled_registration = backend
14168 .work
14169 .try_register_work()
14170 .expect("the backend must accept queued blocking work");
14171 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
14172 let (release_drop_tx, release_drop_rx) = mpsc::channel();
14173 let blocking_drop = BlockingDrop {
14174 entered: drop_entered_tx,
14175 release: release_drop_rx,
14176 };
14177 let cancelled = executor.schedule_blocking(
14178 move || {
14179 let _blocking_drop = blocking_drop;
14180 },
14181 cancelled_registration,
14182 );
14183
14184 let async_registration = backend
14185 .work
14186 .try_register_async()
14187 .expect("the backend must accept queued async work");
14188 let pending = spawn_registered(
14189 &backend,
14190 Arc::clone(&metrics),
14191 std::future::pending::<()>(),
14192 async_registration,
14193 );
14194 let failed_dispatch = current_thread_dispatch(&executor);
14195 executor.cancel_host_dispatch_for_test(failed_dispatch);
14196 let replacement_dispatch = current_thread_dispatch(&executor);
14197
14198 let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
14199 let cancelling_executor = Arc::clone(&executor);
14200 let cancellation = std::thread::spawn(move || {
14201 cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
14202 cancel_done_tx.send(()).unwrap();
14203 });
14204 drop_entered_rx
14205 .recv_timeout(Duration::from_secs(2))
14206 .expect("terminal dispatch cancellation must destroy the queued blocking capture");
14207 let active_during_drop = backend.work.state.lock().unwrap().active;
14208 let cancellation_blocked = cancel_done_rx.try_recv().is_err();
14209
14210 release_drop_tx.send(()).unwrap();
14211 cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14212 cancellation.join().unwrap();
14213
14214 assert_eq!(
14215 active_during_drop, 2,
14216 "the blocking capture's work registration must remain active until its destructor returns"
14217 );
14218 assert!(
14219 cancellation_blocked,
14220 "terminal cancellation must wait for capture destruction"
14221 );
14222 assert!(futures::executor::block_on(cancelled).is_err());
14223 assert!(futures::executor::block_on(pending).is_err());
14224 assert_eq!(backend.work.state.lock().unwrap().active, 1);
14225
14226 release_holder_tx.send(()).unwrap();
14227 holder.join().unwrap();
14228 assert_eq!(backend.work.state.lock().unwrap().active, 0);
14229 }
14230
14231 #[test]
14232 fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
14233 reset_current_thread_one_shot_host_dispatches();
14234 let metrics = Arc::new(RuntimeMetrics::default());
14235 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14236 Arc::clone(&metrics),
14237 accept_only_first_current_thread_host_dispatch,
14238 ));
14239
14240 let scheduler = Arc::clone(&executor);
14241 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14242 scheduler.schedule(runnable)
14243 });
14244 executor.schedule(runnable);
14245 let failed_dispatch = current_thread_dispatch(&executor);
14246
14247 executor.cancel_host_dispatch_for_test(failed_dispatch);
14248
14249 assert_eq!(
14250 current_thread_one_shot_host_dispatches(),
14251 2,
14252 "the original and its one bounded replacement must be attempted"
14253 );
14254 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14255 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14256 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14257 assert!(
14258 futures::executor::block_on(task.fallible()).is_none(),
14259 "an unavailable replacement host must settle the original task as cancelled"
14260 );
14261 }
14262
14263 enum TestCurrentThreadTaskDriverBehavior {
14264 Healthy,
14265 PanicInLivenessAndSweep,
14266 PanicInDispatchAndSweep,
14267 }
14268
14269 struct TestCurrentThreadTaskDriver {
14270 behavior: TestCurrentThreadTaskDriverBehavior,
14271 dispatched: AtomicBool,
14272 }
14273
14274 #[derive(Default)]
14275 struct ManualCurrentThreadTaskDriver {
14276 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
14277 }
14278
14279 struct FailDuringAuthoritativePanicPublication;
14280 struct FailDuringLateRegistrationPublication;
14281 struct FailDuringNestedUnwindPublication;
14282 struct FailDuringReplacementRepublishPublication;
14283 struct FirstHostileCurrentThreadPanic;
14284 struct LaterHostileCurrentThreadPanic;
14285
14286 impl Drop for FirstHostileCurrentThreadPanic {
14287 fn drop(&mut self) {
14288 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
14289 panic!("the first retained CurrentThread panic payload was dropped");
14290 }
14291 }
14292
14293 impl Drop for LaterHostileCurrentThreadPanic {
14294 fn drop(&mut self) {
14295 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
14296 panic!("a later CurrentThread panic payload escaped containment");
14297 }
14298 }
14299
14300 impl ManualCurrentThreadTaskDriver {
14301 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
14302 self.deliveries.lock().unwrap().clone()
14303 }
14304 }
14305
14306 impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
14307 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14308 self.deliveries.lock().unwrap().push(delivery);
14309 true
14310 }
14311 }
14312
14313 impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
14314 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14315 let completion =
14316 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
14317 assert!(
14318 completion.failed_dispatches.is_empty(),
14319 "the failed delivery must remain provisional until its publication retires"
14320 );
14321 true
14322 }
14323 }
14324
14325 impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
14326 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14327 let completion =
14328 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
14329 assert!(
14330 completion.failed_dispatches.is_empty(),
14331 "the failed delivery must remain provisional until its publication retires"
14332 );
14333 true
14334 }
14335 }
14336
14337 impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
14338 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14339 let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
14340 assert!(
14341 completion.failed_dispatches.is_empty(),
14342 "the failed delivery must remain provisional until its publication retires"
14343 );
14344 true
14345 }
14346 }
14347
14348 impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
14349 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14350 let completion =
14351 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
14352 assert!(
14353 completion.failed_dispatches.is_empty(),
14354 "the failed delivery must remain provisional until its publication retires"
14355 );
14356 true
14357 }
14358 }
14359
14360 struct ReentrantCurrentThreadTaskDriver {
14361 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14362 registration: Mutex<Option<CurrentThreadTaskDriverId>>,
14363 }
14364
14365 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
14366 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14367 true
14368 }
14369
14370 fn is_live(&self) -> bool {
14371 let registration = *self.registration.lock().unwrap();
14372 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
14373 registry.unregister(id);
14374 }
14375 false
14376 }
14377 }
14378
14379 struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
14380 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14381 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
14382 }
14383
14384 impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
14385 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14386 true
14387 }
14388
14389 fn is_live(&self) -> bool {
14390 if let Some(registry) = self.registry.upgrade() {
14391 for registration in self.registrations.lock().unwrap().iter().copied() {
14392 let completion = registry.unregister(registration);
14393 assert!(completion.failed_dispatches.is_empty());
14394 assert!(!completion.redispatch);
14395 }
14396 }
14397 false
14398 }
14399 }
14400
14401 struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14402 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14403 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
14404 drops: Arc<AtomicUsize>,
14405 }
14406
14407 impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14408 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14409 if let Some(registry) = self.registry.upgrade() {
14410 for registration in self.registrations.lock().unwrap().iter().copied() {
14411 let completion = registry.unregister(registration);
14412 assert!(completion.failed_dispatches.is_empty());
14413 assert!(!completion.redispatch);
14414 }
14415 }
14416 true
14417 }
14418 }
14419
14420 impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14421 fn drop(&mut self) {
14422 self.drops.fetch_add(1, Ordering::SeqCst);
14423 panic!("intentional reentrant CurrentThread task-driver destructor panic");
14424 }
14425 }
14426
14427 struct PanickingDropCurrentThreadTaskDriver {
14428 drops: Arc<AtomicUsize>,
14429 }
14430
14431 impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
14432 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14433 true
14434 }
14435 }
14436
14437 impl Drop for PanickingDropCurrentThreadTaskDriver {
14438 fn drop(&mut self) {
14439 self.drops.fetch_add(1, Ordering::SeqCst);
14440 panic!("intentional CurrentThread task-driver destructor panic");
14441 }
14442 }
14443
14444 struct ReentrantCurrentThreadTaskDriverDrop {
14445 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14446 fallback: CurrentThreadTaskDriverId,
14447 }
14448
14449 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
14450 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14451 true
14452 }
14453 }
14454
14455 impl Drop for ReentrantCurrentThreadTaskDriverDrop {
14456 fn drop(&mut self) {
14457 if let Some(registry) = self.registry.upgrade() {
14458 registry.unregister(self.fallback);
14459 }
14460 }
14461 }
14462
14463 impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
14464 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14465 assert!(
14466 !matches!(
14467 self.behavior,
14468 TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
14469 ),
14470 "intentional task-driver dispatch panic"
14471 );
14472 self.dispatched.store(true, Ordering::SeqCst);
14473 true
14474 }
14475
14476 fn is_live(&self) -> bool {
14477 assert!(
14478 !matches!(
14479 self.behavior,
14480 TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
14481 ),
14482 "intentional task-driver liveness panic"
14483 );
14484 true
14485 }
14486
14487 fn on_swept(&self) {
14488 assert!(
14489 matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
14490 "intentional task-driver sweep panic"
14491 );
14492 }
14493 }
14494
14495 #[test]
14496 fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
14497 let registry = CurrentThreadTaskDriverRegistry::default();
14498 let fallback = Arc::new(TestCurrentThreadTaskDriver {
14499 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
14500 dispatched: AtomicBool::new(false),
14501 });
14502 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
14503 registry.register(Arc::new(TestCurrentThreadTaskDriver {
14504 behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
14505 dispatched: AtomicBool::new(false),
14506 }));
14507 registry.register(Arc::new(TestCurrentThreadTaskDriver {
14508 behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
14509 dispatched: AtomicBool::new(false),
14510 }));
14511
14512 assert_eq!(
14513 registry.dispatch(37),
14514 CurrentThreadHostDispatchResult::Accepted,
14515 "a panicking newest host must fall back to a live host"
14516 );
14517 assert!(fallback.dispatched.load(Ordering::SeqCst));
14518 }
14519
14520 #[test]
14521 fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
14522 let registry = CurrentThreadTaskDriverRegistry::default();
14523 let first = Arc::new(ManualCurrentThreadTaskDriver::default());
14524 let second = Arc::new(ManualCurrentThreadTaskDriver::default());
14525 registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
14526 registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
14527 registry
14528 .next_delivery
14529 .store(u64::MAX - 1, Ordering::Relaxed);
14530
14531 let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
14532 assert!(
14533 publication.is_err(),
14534 "delivery-id exhaustion on the second host must unwind the publication"
14535 );
14536 {
14537 let state = registry
14538 .state
14539 .lock()
14540 .unwrap_or_else(std::sync::PoisonError::into_inner);
14541 assert!(
14542 !state.dispatches.contains_key(&38),
14543 "unwind cleanup must release the exact registry publication barrier"
14544 );
14545 assert!(
14546 state
14547 .entries
14548 .values()
14549 .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
14550 "failed transactional reservation must leave every physical host slot clean"
14551 );
14552 }
14553 assert!(first.deliveries().is_empty());
14554 assert!(second.deliveries().is_empty());
14555
14556 registry.next_delivery.store(0, Ordering::Relaxed);
14557 assert_eq!(
14558 registry.dispatch(38),
14559 CurrentThreadHostDispatchResult::Accepted
14560 );
14561 let first_delivery = first.deliveries()[0];
14562 let second_delivery = second.deliveries()[0];
14563 assert_eq!(
14564 registry.claim_delivery(first_delivery.capability()),
14565 Some(38)
14566 );
14567 assert_eq!(
14568 registry.claim_delivery(second_delivery.capability()),
14569 Some(38)
14570 );
14571 registry.mark_serviced(38);
14572 assert!(
14573 registry
14574 .finish_delivery(first_delivery, false)
14575 .failed_dispatches
14576 .is_empty()
14577 );
14578 assert!(
14579 registry
14580 .finish_delivery(second_delivery, false)
14581 .failed_dispatches
14582 .is_empty()
14583 );
14584
14585 assert_eq!(
14586 registry.dispatch(39),
14587 CurrentThreadHostDispatchResult::Accepted
14588 );
14589 let first_later = first.deliveries()[1];
14590 let second_later = second.deliveries()[1];
14591 assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
14592 assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
14593 registry.mark_serviced(39);
14594 assert!(
14595 registry
14596 .finish_delivery(first_later, false)
14597 .failed_dispatches
14598 .is_empty()
14599 );
14600 assert!(
14601 registry
14602 .finish_delivery(second_later, false)
14603 .failed_dispatches
14604 .is_empty()
14605 );
14606 assert!(
14607 registry
14608 .state
14609 .lock()
14610 .unwrap_or_else(std::sync::PoisonError::into_inner)
14611 .dispatches
14612 .is_empty()
14613 );
14614 let state = registry
14615 .state
14616 .lock()
14617 .unwrap_or_else(std::sync::PoisonError::into_inner);
14618 assert!(
14619 state
14620 .entries
14621 .values()
14622 .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
14623 );
14624 }
14625
14626 #[test]
14627 fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
14628 let registry = CurrentThreadTaskDriverRegistry::default();
14629 let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
14630 let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
14631 let dispatch = 40;
14632
14633 assert_eq!(
14634 registry.dispatch(dispatch),
14635 CurrentThreadHostDispatchResult::Accepted
14636 );
14637 let failed_delivery = failed.deliveries()[0];
14638 let failure = current_thread_dispatch_failure(dispatch, 0);
14639 assert_eq!(
14640 registry
14641 .finish_delivery(failed_delivery, true)
14642 .failed_dispatches,
14643 vec![failure]
14644 );
14645 registry.unregister(failed_id);
14646
14647 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
14648 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
14649 assert!(
14650 registry.rearm_failed_publication(failure),
14651 "a closed unreferenced dispatch must reopen for its armed publication owner"
14652 );
14653 assert_eq!(
14654 registry.dispatch(dispatch),
14655 CurrentThreadHostDispatchResult::Accepted
14656 );
14657 let replacement_delivery = replacement.deliveries()[0];
14658 assert_eq!(replacement_delivery.dispatch, dispatch);
14659 assert_eq!(
14660 registry.claim_delivery(replacement_delivery.capability()),
14661 Some(dispatch)
14662 );
14663 registry.mark_serviced(dispatch);
14664 assert!(
14665 registry
14666 .finish_delivery(replacement_delivery, false)
14667 .failed_dispatches
14668 .is_empty()
14669 );
14670 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14671 }
14672
14673 #[test]
14674 fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
14675 struct RacingTaskDriver {
14676 executor: Arc<CurrentThreadExecutor>,
14677 drives: bool,
14678 dispatches: Mutex<Vec<u64>>,
14679 }
14680
14681 impl CurrentThreadTaskDriver for RacingTaskDriver {
14682 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14683 self.dispatches.lock().unwrap().push(delivery.dispatch);
14684 if self.drives {
14685 self
14686 .executor
14687 .drive_host_turn_for_dispatch(delivery.dispatch);
14688 }
14689 true
14690 }
14691 }
14692
14693 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14694 Arc::new(RuntimeMetrics::default()),
14695 accept_current_thread_host_dispatch,
14696 ));
14697 let polls = Arc::new(AtomicUsize::new(0));
14698 let task_polls = Arc::clone(&polls);
14699 let scheduler = Arc::clone(&executor);
14700 let (runnable, task) = async_task::spawn(
14701 async move {
14702 task_polls.fetch_add(1, Ordering::SeqCst);
14703 },
14704 move |runnable| scheduler.schedule(runnable),
14705 );
14706 executor.schedule(runnable);
14707 let dispatch = current_thread_dispatch(&executor);
14708
14709 let registry = CurrentThreadTaskDriverRegistry::default();
14710 let responsive = Arc::new(RacingTaskDriver {
14711 executor: Arc::clone(&executor),
14712 drives: true,
14713 dispatches: Mutex::new(Vec::new()),
14714 });
14715 let starved = Arc::new(RacingTaskDriver {
14716 executor: Arc::clone(&executor),
14717 drives: false,
14718 dispatches: Mutex::new(Vec::new()),
14719 });
14720 registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
14721 registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
14723
14724 assert_eq!(
14725 registry.dispatch(dispatch),
14726 CurrentThreadHostDispatchResult::Accepted
14727 );
14728 assert_eq!(
14729 responsive.dispatches.lock().unwrap().as_slice(),
14730 &[dispatch]
14731 );
14732 assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
14733 assert_eq!(
14734 polls.load(Ordering::SeqCst),
14735 1,
14736 "the responsive older host must win even when the newest host is starved"
14737 );
14738
14739 executor.drive_host_turn_for_dispatch(dispatch);
14742 assert_eq!(polls.load(Ordering::SeqCst), 1);
14743 futures::executor::block_on(task);
14744 }
14745
14746 #[test]
14747 fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
14748 const HOSTS: usize = 1_024;
14749
14750 let registry = CurrentThreadTaskDriverRegistry::default();
14751 let mut ids = Vec::with_capacity(HOSTS);
14752 let mut drivers = Vec::with_capacity(HOSTS);
14753 for _ in 0..HOSTS {
14754 let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
14755 ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
14756 drivers.push(driver);
14757 }
14758
14759 let dispatch = u64::MAX - 300;
14760 assert_eq!(
14761 registry.dispatch(dispatch),
14762 CurrentThreadHostDispatchResult::Accepted
14763 );
14764 let deliveries = drivers
14765 .iter()
14766 .map(|driver| driver.deliveries()[0])
14767 .collect::<Vec<_>>();
14768 {
14769 let state = registry
14770 .state
14771 .lock()
14772 .unwrap_or_else(std::sync::PoisonError::into_inner);
14773 assert_eq!(state.delivery_drivers.len(), HOSTS);
14774 assert_eq!(
14775 state
14776 .dispatches
14777 .get(&dispatch)
14778 .map(|state| state.references),
14779 Some(HOSTS)
14780 );
14781 assert_eq!(
14782 state
14783 .ordered_snapshot()
14784 .iter()
14785 .map(|(id, _)| *id)
14786 .collect::<Vec<_>>(),
14787 ids,
14788 "keyed driver lookup must retain registration order"
14789 );
14790 }
14791
14792 for delivery in &deliveries {
14793 assert_eq!(
14794 registry.claim_delivery(delivery.capability()),
14795 Some(dispatch)
14796 );
14797 }
14798 registry.mark_serviced(dispatch);
14799 for delivery in deliveries {
14800 assert!(
14801 registry
14802 .finish_delivery(delivery, false)
14803 .failed_dispatches
14804 .is_empty()
14805 );
14806 }
14807 {
14808 let state = registry
14809 .state
14810 .lock()
14811 .unwrap_or_else(std::sync::PoisonError::into_inner);
14812 assert!(state.delivery_drivers.is_empty());
14813 assert!(state.dispatches.is_empty());
14814 }
14815
14816 let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
14817 for id in &removed {
14818 let completion = registry.unregister(*id);
14819 assert!(completion.failed_dispatches.is_empty());
14820 assert!(!completion.redispatch);
14821 }
14822 let expected = ids
14823 .iter()
14824 .copied()
14825 .filter(|id| !removed.contains(id))
14826 .collect::<Vec<_>>();
14827 let live = registry.live().live;
14828 assert_eq!(
14829 live
14830 .as_slice()
14831 .iter()
14832 .map(|(id, _)| *id)
14833 .collect::<Vec<_>>(),
14834 expected,
14835 "O(1) removals must preserve the relative order of surviving host registrations"
14836 );
14837 drop(live);
14838
14839 for id in expected {
14840 registry.unregister(id);
14841 }
14842 let state = registry
14843 .state
14844 .lock()
14845 .unwrap_or_else(std::sync::PoisonError::into_inner);
14846 assert!(state.entries.is_empty());
14847 assert!(state.oldest.is_none());
14848 assert!(state.newest.is_none());
14849 }
14850
14851 #[test]
14852 fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
14853 let registry = CurrentThreadTaskDriverRegistry::default();
14854 let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
14855 let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
14856 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14857 registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
14858 registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
14859 registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14860
14861 let dispatch = 41;
14862 assert_eq!(
14863 registry.dispatch(dispatch),
14864 CurrentThreadHostDispatchResult::Accepted
14865 );
14866 let failing_delivery = failing.deliveries()[0];
14867 let delayed_delivery = delayed.deliveries()[0];
14868 let healthy_delivery = healthy.deliveries()[0];
14869 assert_eq!(failing_delivery.dispatch, dispatch);
14870 assert_eq!(delayed_delivery.dispatch, dispatch);
14871 assert_eq!(healthy_delivery.dispatch, dispatch);
14872
14873 assert!(
14874 registry
14875 .finish_delivery(failing_delivery, true)
14876 .failed_dispatches
14877 .is_empty(),
14878 "one host failure must not recover while other accepted attempts remain"
14879 );
14880 assert_eq!(
14881 registry.claim_delivery(healthy_delivery.capability()),
14882 Some(dispatch)
14883 );
14884 registry.mark_serviced(dispatch);
14885 assert!(
14886 registry
14887 .finish_delivery(healthy_delivery, false)
14888 .failed_dispatches
14889 .is_empty()
14890 );
14891 assert_eq!(
14892 registry.claim_delivery(delayed_delivery.capability()),
14893 None,
14894 "a delayed losing delivery must become stale after another host services the dispatch"
14895 );
14896 assert!(
14897 registry
14898 .finish_delivery(delayed_delivery, true)
14899 .failed_dispatches
14900 .is_empty(),
14901 "a stale losing failure must not recover or cancel a serviced dispatch"
14902 );
14903 assert!(
14904 registry.state.lock().unwrap().dispatches.is_empty(),
14905 "all per-dispatch acknowledgement state must retire"
14906 );
14907 }
14908
14909 #[test]
14910 fn current_thread_unclaimed_callback_success_is_delivery_failure() {
14911 let registry = CurrentThreadTaskDriverRegistry::default();
14912 let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
14913 registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
14914
14915 let dispatch = 56;
14916 assert_eq!(
14917 registry.dispatch(dispatch),
14918 CurrentThreadHostDispatchResult::Accepted
14919 );
14920 let delivery = no_op.deliveries()[0];
14921 let completion = registry.finish_delivery(delivery, false);
14922 let failure = current_thread_dispatch_failure(dispatch, 0);
14923 assert_eq!(
14924 completion.failed_dispatches,
14925 vec![failure],
14926 "a callback return without an exact capability claim must enter recovery"
14927 );
14928 assert!(
14929 registry
14930 .state
14931 .lock()
14932 .unwrap()
14933 .dispatches
14934 .get(&dispatch)
14935 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14936 );
14937 registry.finish_cancellation(failure);
14938 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14939 }
14940
14941 #[test]
14942 fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
14943 struct FailDuringPublication {
14944 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14945 }
14946
14947 impl CurrentThreadTaskDriver for FailDuringPublication {
14948 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14949 let completion = self
14950 .registry
14951 .upgrade()
14952 .expect("the registry must outlive publication")
14953 .finish_delivery(delivery, true);
14954 assert!(
14955 completion.failed_dispatches.is_empty(),
14956 "failure must remain provisional until the broadcast closes"
14957 );
14958 true
14959 }
14960 }
14961
14962 struct RejectProvisional;
14963
14964 impl CurrentThreadTaskDriver for RejectProvisional {
14965 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14966 false
14967 }
14968 }
14969
14970 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14971 registry.register(Arc::new(RejectProvisional));
14972 registry.register(Arc::new(FailDuringPublication {
14975 registry: Arc::downgrade(®istry),
14976 }));
14977
14978 let failure = current_thread_dispatch_failure(57, 0);
14979 assert_eq!(
14980 registry.dispatch(57),
14981 CurrentThreadHostDispatchResult::Failed(failure),
14982 "the final provisional rejection must expose the earlier accepted failure"
14983 );
14984 assert!(
14985 registry
14986 .state
14987 .lock()
14988 .unwrap()
14989 .dispatches
14990 .get(&57)
14991 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14992 );
14993 assert_eq!(
14994 registry.dispatch(57),
14995 CurrentThreadHostDispatchResult::Failed(failure),
14996 "a racing replacement publication must preserve terminal failure until cancellation runs"
14997 );
14998 registry.finish_cancellation(failure);
14999 }
15000
15001 #[test]
15002 fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
15003 struct RejectProvisional;
15004
15005 impl CurrentThreadTaskDriver for RejectProvisional {
15006 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
15007 false
15008 }
15009 }
15010
15011 let registry = CurrentThreadTaskDriverRegistry::default();
15012 registry.register(Arc::new(RejectProvisional));
15013 registry.register(Arc::new(RejectProvisional));
15014
15015 assert_eq!(
15016 registry.dispatch(58),
15017 CurrentThreadHostDispatchResult::Unavailable
15018 );
15019 assert!(
15020 registry.state.lock().unwrap().dispatches.is_empty(),
15021 "a broadcast with no accepted attempt must not enter failure recovery"
15022 );
15023 }
15024
15025 #[test]
15026 fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
15027 let registry = CurrentThreadTaskDriverRegistry::default();
15028 let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
15029 let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
15030
15031 assert_eq!(
15032 registry.dispatch(1),
15033 CurrentThreadHostDispatchResult::Accepted
15034 );
15035 let first = blocked.deliveries()[0];
15036 for dispatch in 2..=256 {
15037 assert_eq!(
15038 registry.dispatch(dispatch),
15039 CurrentThreadHostDispatchResult::Accepted
15040 );
15041 assert_eq!(
15042 blocked.deliveries().len(),
15043 1,
15044 "a blocked host must never receive a second queued or in-flight callback"
15045 );
15046 }
15047 {
15048 let state = registry.state.lock().unwrap();
15049 let entry = state
15050 .entries
15051 .get(&blocked_id)
15052 .expect("the blocked host must remain registered");
15053 assert_eq!(entry.pending, Some(256));
15054 assert_eq!(
15055 state.dispatches.len(),
15056 2,
15057 "only the in-flight and latest pending dispatches may retain registry state"
15058 );
15059 }
15060
15061 assert_eq!(registry.claim_delivery(first.capability()), Some(1));
15062 registry.mark_serviced(1);
15063 let completion = registry.finish_delivery(first, false);
15064 assert!(completion.failed_dispatches.is_empty());
15065 assert!(completion.redispatch);
15066 assert_eq!(
15067 registry.dispatch(256),
15068 CurrentThreadHostDispatchResult::Accepted
15069 );
15070 let deliveries = blocked.deliveries();
15071 assert_eq!(deliveries.len(), 2);
15072 assert_eq!(
15073 deliveries[1].dispatch, 256,
15074 "completion must schedule exactly the latest coalesced capability"
15075 );
15076 assert_eq!(registry.claim_delivery(first.capability()), None);
15077 assert_eq!(
15078 registry.claim_delivery(deliveries[1].capability()),
15079 Some(256)
15080 );
15081 registry.mark_serviced(256);
15082 assert!(
15083 registry
15084 .finish_delivery(deliveries[1], false)
15085 .failed_dispatches
15086 .is_empty()
15087 );
15088 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15089 }
15090
15091 #[test]
15092 fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
15093 struct RejectAfterFirst {
15094 calls: AtomicUsize,
15095 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
15096 }
15097
15098 impl CurrentThreadTaskDriver for RejectAfterFirst {
15099 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
15100 self.deliveries.lock().unwrap().push(delivery);
15101 self.calls.fetch_add(1, Ordering::SeqCst) == 0
15102 }
15103 }
15104
15105 let registry = CurrentThreadTaskDriverRegistry::default();
15106 let host = Arc::new(RejectAfterFirst {
15107 calls: AtomicUsize::new(0),
15108 deliveries: Mutex::new(Vec::new()),
15109 });
15110 registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
15111 assert_eq!(
15112 registry.dispatch(11),
15113 CurrentThreadHostDispatchResult::Accepted
15114 );
15115 let first = host.deliveries.lock().unwrap()[0];
15116 assert_eq!(
15117 registry.dispatch(12),
15118 CurrentThreadHostDispatchResult::Accepted
15119 );
15120
15121 assert_eq!(registry.claim_delivery(first.capability()), Some(11));
15122 registry.mark_serviced(11);
15123 let completion = registry.finish_delivery(first, false);
15124 assert!(completion.redispatch);
15125 let failure = current_thread_dispatch_failure(12, 0);
15126 assert_eq!(
15127 registry.dispatch(12),
15128 CurrentThreadHostDispatchResult::Failed(failure),
15129 "an accepted coalesced capability must report failure if its eventual host queue rejects it"
15130 );
15131 assert!(
15132 registry
15133 .state
15134 .lock()
15135 .unwrap()
15136 .dispatches
15137 .get(&12)
15138 .is_some_and(CurrentThreadDispatchState::is_cancelling)
15139 );
15140 registry.finish_cancellation(failure);
15141 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15142 }
15143
15144 #[test]
15145 fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
15146 let registry = CurrentThreadTaskDriverRegistry::default();
15147 let old = Arc::new(ManualCurrentThreadTaskDriver::default());
15148 let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
15149 assert_eq!(
15150 registry.dispatch(73),
15151 CurrentThreadHostDispatchResult::Accepted
15152 );
15153 let old_delivery = old.deliveries()[0];
15154
15155 let completion = registry.unregister(old_id);
15156 let failure = current_thread_dispatch_failure(73, 0);
15157 assert_eq!(
15158 completion.failed_dispatches,
15159 vec![failure],
15160 "unregistering the final accepting host must retire its exact dispatch"
15161 );
15162 registry.finish_cancellation(failure);
15163 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
15164 let replacement_id =
15165 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
15166 assert_ne!(replacement_id, old_id);
15167 assert_eq!(
15168 registry.dispatch(74),
15169 CurrentThreadHostDispatchResult::Accepted
15170 );
15171 let replacement_delivery = replacement.deliveries()[0];
15172 assert_ne!(replacement_delivery.capability(), old_delivery.capability());
15173
15174 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
15175 assert!(
15176 registry
15177 .finish_delivery(old_delivery, true)
15178 .failed_dispatches
15179 .is_empty()
15180 );
15181 assert!(
15182 registry
15183 .fail_delivery(old_delivery.capability())
15184 .failed_dispatches
15185 .is_empty()
15186 );
15187 assert_eq!(
15188 registry.claim_delivery(replacement_delivery.capability()),
15189 Some(74)
15190 );
15191 registry.mark_serviced(74);
15192 assert!(
15193 registry
15194 .finish_delivery(replacement_delivery, false)
15195 .failed_dispatches
15196 .is_empty()
15197 );
15198 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15199 }
15200
15201 #[test]
15202 fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
15203 struct QueuePendingThenReject {
15204 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
15205 }
15206
15207 impl CurrentThreadTaskDriver for QueuePendingThenReject {
15208 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
15209 assert_eq!(delivery.dispatch, 81);
15210 let completion = self
15211 .registry
15212 .upgrade()
15213 .expect("the registry must outlive publication")
15214 .dispatch_with_completion(82);
15215 assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
15216 assert!(completion.failed_dispatches.is_empty());
15217 false
15218 }
15219 }
15220
15221 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15222 registry.register(Arc::new(QueuePendingThenReject {
15223 registry: Arc::downgrade(®istry),
15224 }));
15225
15226 let completion = registry.dispatch_with_completion(81);
15227 assert_eq!(
15228 completion.result,
15229 CurrentThreadHostDispatchResult::Unavailable
15230 );
15231 let failure = current_thread_dispatch_failure(82, 0);
15232 assert_eq!(
15233 completion.failed_dispatches,
15234 vec![failure],
15235 "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
15236 );
15237 assert!(
15238 registry
15239 .state
15240 .lock()
15241 .unwrap()
15242 .dispatches
15243 .get(&82)
15244 .is_some_and(CurrentThreadDispatchState::is_cancelling)
15245 );
15246 registry.finish_cancellation(failure);
15247 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15248 }
15249
15250 #[test]
15251 fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
15252 let registry = CurrentThreadTaskDriverRegistry::default();
15253 let host = Arc::new(ManualCurrentThreadTaskDriver::default());
15254 let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
15255 assert_eq!(
15256 registry.dispatch(91),
15257 CurrentThreadHostDispatchResult::Accepted
15258 );
15259 let old_delivery = host.deliveries()[0];
15260 assert_eq!(
15261 registry.dispatch(92),
15262 CurrentThreadHostDispatchResult::Accepted
15263 );
15264
15265 registry.retire_for_shutdown();
15266 {
15267 let state = registry.state.lock().unwrap();
15268 assert!(state.dispatches.is_empty());
15269 let entry = state
15270 .entries
15271 .get(&host_id)
15272 .expect("the host must remain registered");
15273 assert_eq!(entry.pending, None);
15274 assert!(
15275 entry.delivery.is_some(),
15276 "the physical callback remains single-flight until it actually retires"
15277 );
15278 }
15279 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
15280
15281 assert_eq!(
15282 registry.dispatch(93),
15283 CurrentThreadHostDispatchResult::Accepted
15284 );
15285 assert_eq!(host.deliveries().len(), 1);
15286 let completion = registry.finish_delivery(old_delivery, true);
15287 assert!(
15288 completion.failed_dispatches.is_empty(),
15289 "a retired-generation failure must not cancel restarted work"
15290 );
15291 assert!(completion.redispatch);
15292 assert_eq!(
15293 registry.dispatch(93),
15294 CurrentThreadHostDispatchResult::Accepted
15295 );
15296 let restarted_delivery = host.deliveries()[1];
15297 assert_eq!(restarted_delivery.dispatch, 93);
15298 assert_eq!(
15299 registry.claim_delivery(restarted_delivery.capability()),
15300 Some(93)
15301 );
15302 registry.mark_serviced(93);
15303 assert!(
15304 registry
15305 .finish_delivery(restarted_delivery, false)
15306 .failed_dispatches
15307 .is_empty()
15308 );
15309
15310 registry.unregister(host_id);
15311 let state = registry.state.lock().unwrap();
15312 assert!(state.entries.is_empty());
15313 assert!(state.dispatches.is_empty());
15314 }
15315
15316 #[test]
15317 fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
15318 use std::{sync::mpsc, time::Duration};
15319
15320 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15321 let fallback = Arc::new(TestCurrentThreadTaskDriver {
15322 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
15323 dispatched: AtomicBool::new(false),
15324 });
15325 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
15326 let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
15327 registry: Arc::downgrade(®istry),
15328 registration: Mutex::new(None),
15329 });
15330 let reentrant_id =
15331 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15332 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
15333
15334 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
15335 let selection_registry = Arc::clone(®istry);
15336 let selection_thread = std::thread::spawn(move || {
15337 selection_tx
15338 .send(selection_registry.current().map(|(id, _)| id))
15339 .unwrap();
15340 });
15341 assert_eq!(
15342 selection_rx
15343 .recv_timeout(Duration::from_secs(10))
15344 .expect("is_live re-entry deadlocked"),
15345 Some(fallback_id)
15346 );
15347 selection_thread.join().unwrap();
15348
15349 let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
15350 Arc::new(ReentrantCurrentThreadTaskDriverDrop {
15351 registry: Arc::downgrade(®istry),
15352 fallback: fallback_id,
15353 });
15354 let drop_id = registry.register(Arc::clone(&drop_driver));
15355 drop(drop_driver);
15356
15357 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
15358 let drop_registry = Arc::clone(®istry);
15359 let drop_thread = std::thread::spawn(move || {
15360 drop_registry.unregister(drop_id);
15361 drop_tx.send(()).unwrap();
15362 });
15363 drop_rx
15364 .recv_timeout(Duration::from_secs(10))
15365 .expect("driver destructor re-entry deadlocked");
15366 drop_thread.join().unwrap();
15367 assert!(
15368 registry.current().is_none(),
15369 "the destructor must unregister the fallback"
15370 );
15371 }
15372
15373 #[test]
15374 fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
15375 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
15376
15377 if std::env::var_os(CHILD_ENV).is_some() {
15378 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15379 let fallback = Arc::new(TestCurrentThreadTaskDriver {
15380 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
15381 dispatched: AtomicBool::new(false),
15382 });
15383 let fallback_id =
15384 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
15385 let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
15386 registry: Arc::downgrade(®istry),
15387 registrations: Mutex::new(Vec::new()),
15388 });
15389 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15390
15391 let first_drops = Arc::new(AtomicUsize::new(0));
15392 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
15393 drops: Arc::clone(&first_drops),
15394 });
15395 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
15396 drop(first);
15397
15398 let second_drops = Arc::new(AtomicUsize::new(0));
15399 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
15400 drops: Arc::clone(&second_drops),
15401 });
15402 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
15403 drop(second);
15404 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
15405
15406 assert_eq!(
15407 registry.current().map(|(id, _)| id),
15408 Some(fallback_id),
15409 "selection must retry after reentrant removals and preserve the healthy fallback"
15410 );
15411 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
15412 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
15413 let state = registry
15414 .state
15415 .lock()
15416 .unwrap_or_else(std::sync::PoisonError::into_inner);
15417 assert_eq!(state.entries.len(), 1);
15418 assert!(state.entries.contains_key(&fallback_id));
15419 assert!(state.dispatches.is_empty());
15420 return;
15421 }
15422
15423 let output = std::process::Command::new(std::env::current_exe().unwrap())
15424 .arg("--exact")
15425 .arg(
15426 "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
15427 )
15428 .arg("--nocapture")
15429 .env(CHILD_ENV, "1")
15430 .output()
15431 .expect("the hostile task-driver snapshot subprocess test must start");
15432 assert!(
15433 output.status.success(),
15434 "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
15435 output.status.code(),
15436 String::from_utf8_lossy(&output.stdout),
15437 String::from_utf8_lossy(&output.stderr)
15438 );
15439 }
15440
15441 #[test]
15442 fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
15443 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
15444
15445 if std::env::var_os(CHILD_ENV).is_some() {
15446 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15447
15448 let first_drops = Arc::new(AtomicUsize::new(0));
15449 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
15450 drops: Arc::clone(&first_drops),
15451 });
15452 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
15453 drop(first);
15454
15455 let second_drops = Arc::new(AtomicUsize::new(0));
15456 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
15457 drops: Arc::clone(&second_drops),
15458 });
15459 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
15460 drop(second);
15461
15462 let reentrant_drops = Arc::new(AtomicUsize::new(0));
15463 let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
15464 registry: Arc::downgrade(®istry),
15465 registrations: Mutex::new(Vec::new()),
15466 drops: Arc::clone(&reentrant_drops),
15467 });
15468 let reentrant_id =
15469 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15470 *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
15471 drop(reentrant);
15472
15473 let dispatch = 59;
15474 let failure = current_thread_dispatch_failure(dispatch, 0);
15475 assert_eq!(
15476 registry.dispatch(dispatch),
15477 CurrentThreadHostDispatchResult::Failed(failure),
15478 "reentrant removal of every provisional host must report accepted delivery failure"
15479 );
15480 assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
15481 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
15482 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
15483 registry.finish_cancellation(failure);
15484 let state = registry
15485 .state
15486 .lock()
15487 .unwrap_or_else(std::sync::PoisonError::into_inner);
15488 assert!(state.entries.is_empty());
15489 assert!(state.dispatches.is_empty());
15490 return;
15491 }
15492
15493 let output = std::process::Command::new(std::env::current_exe().unwrap())
15494 .arg("--exact")
15495 .arg(
15496 "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
15497 )
15498 .arg("--nocapture")
15499 .env(CHILD_ENV, "1")
15500 .output()
15501 .expect("the hostile task-driver delivery subprocess test must start");
15502 assert!(
15503 output.status.success(),
15504 "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
15505 output.status.code(),
15506 String::from_utf8_lossy(&output.stdout),
15507 String::from_utf8_lossy(&output.stderr)
15508 );
15509 }
15510
15511 #[test]
15512 fn dropping_join_handle_detaches_task_like_tokio() {
15513 let completed = Arc::new(AtomicBool::new(false));
15514 let completed_task = Arc::clone(&completed);
15515 let (runnable, task) = async_task::spawn(
15516 async move {
15517 completed_task.store(true, Ordering::SeqCst);
15518 Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
15519 },
15520 |_| {},
15521 );
15522 let handle = JoinHandle(JoinHandleInner::Task {
15523 task: task.fallible(),
15524 awaiter: None,
15525 dependency: Arc::new(TaskDependency::default()),
15526 });
15527
15528 drop(handle);
15529 runnable.run();
15530 assert!(completed.load(Ordering::SeqCst));
15531 }
15532
15533 #[cfg(not(target_family = "wasm"))]
15534 #[test]
15535 fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
15536 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
15537
15538 if std::env::var_os(CHILD_ENV).is_some() {
15539 let wakes = Arc::new(AtomicUsize::new(0));
15540 let drops = Arc::new(AtomicUsize::new(0));
15541 let (runnable, task) = async_task::spawn(
15542 async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
15543 |_| {},
15544 );
15545 let mut handle = JoinHandle(JoinHandleInner::Task {
15546 task: task.fallible(),
15547 awaiter: None,
15548 dependency: Arc::new(TaskDependency::default()),
15549 });
15550 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15551 {
15552 let mut cx = Context::from_waker(&waker);
15553 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15554 }
15555 drop(waker);
15556
15557 runnable.run();
15558
15559 let waker = futures::task::noop_waker();
15560 let mut cx = Context::from_waker(&waker);
15561 assert!(matches!(
15562 Pin::new(&mut handle).poll(&mut cx),
15563 Poll::Ready(Ok(()))
15564 ));
15565 assert_eq!(
15566 drops.load(Ordering::SeqCst),
15567 1,
15568 "a completed handle must not retain the caller's awaiter"
15569 );
15570 drop(handle);
15571 assert!(wakes.load(Ordering::SeqCst) >= 1);
15572 assert_eq!(drops.load(Ordering::SeqCst), 1);
15573 return;
15574 }
15575
15576 let output = std::process::Command::new(std::env::current_exe().unwrap())
15577 .arg("--exact")
15578 .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
15579 .arg("--nocapture")
15580 .env(CHILD_ENV, "1")
15581 .output()
15582 .expect("the JoinHandle completion awaiter subprocess must start");
15583 assert!(
15584 output.status.success(),
15585 "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15586 output.status.code(),
15587 String::from_utf8_lossy(&output.stdout),
15588 String::from_utf8_lossy(&output.stderr)
15589 );
15590 }
15591
15592 #[cfg(not(target_family = "wasm"))]
15593 #[test]
15594 fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
15595 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
15596
15597 if std::env::var_os(CHILD_ENV).is_some() {
15598 let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
15599 let wakes = Arc::new(AtomicUsize::new(0));
15600 let drops = Arc::new(AtomicUsize::new(0));
15601 let mut handle = controller.spawn(std::future::pending::<()>());
15602 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15603 {
15604 let mut cx = Context::from_waker(&waker);
15605 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15606 }
15607 drop(waker);
15608
15609 controller
15610 .shutdown()
15611 .expect("shutdown must survive the hostile awaiter");
15612 drop(handle);
15613 assert!(wakes.load(Ordering::SeqCst) >= 1);
15614 assert_eq!(drops.load(Ordering::SeqCst), 1);
15615 return;
15616 }
15617
15618 let output = std::process::Command::new(std::env::current_exe().unwrap())
15619 .arg("--exact")
15620 .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
15621 .arg("--nocapture")
15622 .env(CHILD_ENV, "1")
15623 .output()
15624 .expect("the JoinHandle shutdown awaiter subprocess must start");
15625 assert!(
15626 output.status.success(),
15627 "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15628 output.status.code(),
15629 String::from_utf8_lossy(&output.stdout),
15630 String::from_utf8_lossy(&output.stderr)
15631 );
15632 }
15633
15634 #[cfg(not(target_family = "wasm"))]
15635 #[test]
15636 fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
15637 let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
15638 let mut handle = controller.spawn(std::future::pending::<()>());
15639 let old_generation = controller.backend().generation();
15640 let state = Arc::new(JoinAwaiterWakerState {
15641 events: Mutex::new(Vec::new()),
15642 });
15643 let waker = {
15644 let _generation = RuntimeGenerationGuard::enter(old_generation);
15645 join_awaiter_waker(Arc::clone(&state))
15646 };
15647 {
15648 let _generation = RuntimeGenerationGuard::enter(old_generation);
15649 let mut cx = Context::from_waker(&waker);
15650 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15651 }
15652 {
15653 let _generation = RuntimeGenerationGuard::enter(old_generation);
15654 drop(waker);
15655 }
15656
15657 controller
15658 .shutdown()
15659 .expect("the first generation must stop");
15660 controller.start().expect("the runtime must restart");
15661 let new_generation = controller.backend().generation();
15662 assert_ne!(new_generation, old_generation);
15663 state.clear();
15664
15665 let replacement = futures::task::noop_waker();
15666 {
15667 let _generation = RuntimeGenerationGuard::enter(new_generation);
15668 let mut cx = Context::from_waker(&replacement);
15669 assert!(matches!(
15670 Pin::new(&mut handle).poll(&mut cx),
15671 Poll::Ready(Err(_))
15672 ));
15673 }
15674
15675 let events = state.snapshot();
15676 assert!(
15677 !events.is_empty(),
15678 "completion must retire the registered awaiter waker"
15679 );
15680 assert!(
15681 events.iter().all(|(kind, generation)| {
15682 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15683 }),
15684 "old-generation awaiter retirement escaped its generation: {events:?}"
15685 );
15686 controller
15687 .shutdown()
15688 .expect("the replacement generation must stop");
15689 }
15690
15691 #[cfg(not(target_family = "wasm"))]
15692 #[test]
15693 fn dropping_join_handle_retains_awaiter_generation_after_restart() {
15694 let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
15695 let mut handle = controller.spawn(std::future::pending::<()>());
15696 let old_generation = controller.backend().generation();
15697 let state = Arc::new(JoinAwaiterWakerState {
15698 events: Mutex::new(Vec::new()),
15699 });
15700 let waker = {
15701 let _generation = RuntimeGenerationGuard::enter(old_generation);
15702 join_awaiter_waker(Arc::clone(&state))
15703 };
15704 {
15705 let _generation = RuntimeGenerationGuard::enter(old_generation);
15706 let mut cx = Context::from_waker(&waker);
15707 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15708 }
15709 {
15710 let _generation = RuntimeGenerationGuard::enter(old_generation);
15711 drop(waker);
15712 }
15713
15714 controller
15715 .shutdown()
15716 .expect("the first generation must stop");
15717 controller.start().expect("the runtime must restart");
15718 let new_generation = controller.backend().generation();
15719 assert_ne!(new_generation, old_generation);
15720 state.clear();
15721
15722 {
15723 let _generation = RuntimeGenerationGuard::enter(new_generation);
15724 drop(handle);
15725 }
15726
15727 let events = state.snapshot();
15728 assert!(
15729 !events.is_empty(),
15730 "handle drop must retire the registered awaiter waker"
15731 );
15732 assert!(
15733 events.iter().all(|(kind, generation)| {
15734 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15735 }),
15736 "old-generation awaiter destruction escaped its generation: {events:?}"
15737 );
15738 controller
15739 .shutdown()
15740 .expect("the replacement generation must stop");
15741 }
15742
15743 #[cfg(not(target_family = "wasm"))]
15744 #[test]
15745 fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
15746 use std::{sync::mpsc, time::Duration};
15747
15748 let options = RuntimeOptions {
15749 flavor: RuntimeFlavor::MultiThread,
15750 worker_threads: 2,
15751 max_blocking_tasks: 1,
15752 thread_name_prefix: "worker-classification".to_string(),
15753 park_deadline: None,
15754 drain_linger: DEFAULT_DRAIN_LINGER,
15755 };
15756 let executor =
15757 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
15758 let id = executor.id;
15759 let (tx, rx) = mpsc::sync_channel(1);
15760
15761 executor.pool.spawn(move || {
15762 tx.send((
15763 ON_POOL_WORKER.with(std::cell::Cell::get),
15764 IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
15765 ))
15766 .unwrap();
15767 });
15768
15769 let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
15770 assert_eq!(
15771 worker,
15772 Some(id),
15773 "nested Rayon work must use cooperative block_on"
15774 );
15775 assert_eq!(
15776 driver, None,
15777 "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
15778 );
15779 }
15780
15781 #[cfg(not(target_family = "wasm"))]
15782 #[test]
15783 fn multi_thread_block_on_does_not_park_pool_worker() {
15784 use std::sync::{Barrier, mpsc};
15789 use std::time::Duration;
15790
15791 let (done_tx, done_rx) = mpsc::channel();
15792 let runner = std::thread::spawn(move || {
15793 let metrics = Arc::new(RuntimeMetrics::default());
15794 let options = RuntimeOptions {
15795 flavor: RuntimeFlavor::MultiThread,
15796 worker_threads: 2,
15797 max_blocking_tasks: 2,
15798 thread_name_prefix: "rd1-test".to_string(),
15799 park_deadline: None,
15800 drain_linger: DEFAULT_DRAIN_LINGER,
15801 };
15802 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15803
15804 let task_count = 2usize;
15805 let barrier = Arc::new(Barrier::new(task_count));
15808
15809 let mut tasks = Vec::new();
15810 for _ in 0..task_count {
15811 let exec = Arc::clone(&executor);
15812 let barrier = Arc::clone(&barrier);
15813 let body = async move {
15814 barrier.wait();
15815 let inner_exec = Arc::clone(&exec);
15816 let mut inner = std::pin::pin!(async move {
15817 let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
15818 assert_eq!(value, 7);
15819 });
15820 exec.block_on(inner.as_mut());
15822 };
15823 let scheduler = Arc::clone(&executor);
15824 let (runnable, task) =
15825 async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
15826 executor.schedule(runnable);
15827 tasks.push(task);
15828 }
15829
15830 for task in tasks {
15831 futures::executor::block_on(task);
15832 }
15833 let _ = done_tx.send(());
15834 });
15835
15836 match done_rx.recv_timeout(Duration::from_secs(10)) {
15837 Ok(()) => runner.join().unwrap(),
15838 Err(error) => panic!(
15839 "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
15840 ),
15841 }
15842 }
15843
15844 #[cfg(not(target_family = "wasm"))]
15845 #[test]
15846 fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
15847 use std::sync::mpsc;
15866
15867 for round in 0..3 {
15868 let metrics = Arc::new(RuntimeMetrics::default());
15869 let options = RuntimeOptions {
15870 flavor: RuntimeFlavor::MultiThread,
15871 worker_threads: 2,
15872 max_blocking_tasks: 1,
15873 thread_name_prefix: format!("linger-starve-{round}"),
15874 park_deadline: None,
15875 drain_linger: DEFAULT_DRAIN_LINGER,
15876 };
15877 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15878
15879 let r_done = Arc::new(AtomicBool::new(false));
15880 let give_up = Arc::new(AtomicBool::new(false));
15881 let (done_tx, done_rx) = mpsc::channel::<Duration>();
15882
15883 let t1_executor = Arc::clone(&executor);
15889 let t1_started = Arc::new(AtomicBool::new(false));
15890 let t1_started_setter = Arc::clone(&t1_started);
15891 let t1_body = async move {
15892 t1_started_setter.store(true, Ordering::SeqCst);
15893 let deadline = Instant::now() + Duration::from_secs(10);
15894 while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
15895 if Instant::now() >= deadline {
15896 return;
15898 }
15899 std::hint::spin_loop();
15900 }
15901 };
15902 let t1_scheduler = Arc::clone(&executor);
15903 let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
15904 executor.schedule(t1_runnable);
15905 wait_until("the worker-pinning task to start", || {
15906 t1_started.load(Ordering::SeqCst)
15907 });
15908
15909 let blocking_executor = Arc::clone(&executor);
15911 let r_done_setter = Arc::clone(&r_done);
15912 let started = Instant::now();
15913 let handle = executor.schedule_blocking(move || {
15914 let (r_tx, r_rx) = oneshot::channel::<()>();
15915 blocking_executor.pool.spawn(move || {
15916 r_done_setter.store(true, Ordering::SeqCst);
15917 let _ = r_tx.send(());
15918 });
15919 let mut wait_for_r = std::pin::pin!(async move {
15920 let _ = r_rx.await;
15921 });
15922 blocking_executor.block_on(wait_for_r.as_mut());
15923 let _ = done_tx.send(started.elapsed());
15924 });
15925
15926 let arm_deadline = Instant::now() + Duration::from_secs(15);
15930 while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
15931 && !r_done.load(Ordering::SeqCst)
15932 {
15933 assert!(
15934 Instant::now() < arm_deadline,
15935 "timed out arming the linger-starvation interleaving"
15936 );
15937 std::thread::yield_now();
15938 }
15939
15940 let producer_executor = Arc::clone(&executor);
15943 let producer_r_done = Arc::clone(&r_done);
15944 let producer_give_up = Arc::clone(&give_up);
15945 let producer = std::thread::spawn(move || {
15946 let cadence = Duration::from_micros(200);
15947 let mut next_tick = Instant::now();
15948 while !producer_r_done.load(Ordering::SeqCst)
15949 && !producer_give_up.load(Ordering::SeqCst)
15950 {
15951 let scheduler = Arc::clone(&producer_executor);
15952 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
15953 producer_executor.schedule(runnable);
15954 task.detach();
15955 next_tick += cadence;
15956 while Instant::now() < next_tick {
15957 std::hint::spin_loop();
15958 }
15959 }
15960 });
15961
15962 let outcome = done_rx.recv_timeout(Duration::from_secs(5));
15963 give_up.store(true, Ordering::SeqCst);
15964 producer.join().unwrap();
15965 match outcome {
15966 Ok(elapsed) => {
15967 futures::executor::block_on(handle).expect("blocking closure join");
15968 drop(t1_task);
15969 eprintln!("round {round}: completed in {elapsed:?}");
15970 }
15971 Err(error) => panic!(
15972 "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
15973 sub-budget periodic work must not keep a drain frame resident forever"
15974 ),
15975 }
15976 }
15977 }
15978
15979 #[cfg(not(target_family = "wasm"))]
15980 #[test]
15981 #[ignore = "microbenchmark: run explicitly in release mode"]
15982 fn parked_drivers_registry_cycle_microbench() {
15983 for &n in &[8usize, 64, 256] {
15990 let registry = ParkedDrivers::default();
15991 let parkers: Vec<Arc<DriverParker>> =
15992 (0..n).map(|_| Arc::new(DriverParker::default())).collect();
15993 for parker in &parkers {
15994 registry.register_with_role(parker, true, None);
15995 }
15996 let hot = &parkers[n - 1];
15997
15998 let iters = 2_000_000u32;
15999 let start = Instant::now();
16000 for _ in 0..iters {
16001 assert!(registry.wake_one());
16002 registry.deregister(hot);
16003 assert!(hot.consume_permit());
16004 registry.register_with_role(hot, true, None);
16005 }
16006 let wake_cycle = start.elapsed() / iters;
16007
16008 let start = Instant::now();
16009 for _ in 0..iters {
16010 registry.deregister(hot);
16011 registry.register_with_role(hot, true, None);
16012 }
16013 let timeout_cycle = start.elapsed() / iters;
16014
16015 let owner = BlockingOwnerToken {
16016 executor_id: u64::MAX,
16017 frame: u64::MAX,
16018 };
16019 let start = Instant::now();
16020 for _ in 0..iters {
16021 assert!(!registry.wake_one_owner_dependency(owner));
16022 }
16023 let owner_sweep = start.elapsed() / iters;
16024
16025 eprintln!(
16026 "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
16027 timeout-deregister+re-register {timeout_cycle:?}, \
16028 owner-dependency sweep {owner_sweep:?}"
16029 );
16030 }
16031 }
16032
16033 #[cfg(not(target_family = "wasm"))]
16034 #[test]
16035 fn multi_thread_block_on_from_caller_thread_still_parks() {
16036 let metrics = Arc::new(RuntimeMetrics::default());
16039 let options = RuntimeOptions {
16040 flavor: RuntimeFlavor::MultiThread,
16041 worker_threads: 3,
16042 max_blocking_tasks: 2,
16043 thread_name_prefix: "rd1-caller".to_string(),
16044 park_deadline: None,
16045 drain_linger: DEFAULT_DRAIN_LINGER,
16046 };
16047 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16048
16049 let mut output = None;
16050 let mut future = std::pin::pin!(async {
16051 output = Some(41usize + 1);
16052 });
16053 executor.block_on(future.as_mut());
16054 assert_eq!(output, Some(42));
16055 }
16056
16057 #[cfg(not(target_family = "wasm"))]
16058 #[test]
16059 fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
16060 use std::sync::mpsc;
16083 use std::time::Duration;
16084
16085 struct CallerProbe {
16090 task: Task<usize>,
16091 parked_tx: Option<mpsc::Sender<()>>,
16092 }
16093 impl Future for CallerProbe {
16094 type Output = usize;
16095 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
16096 let poll = Pin::new(&mut self.task).poll(cx);
16098 if poll.is_pending()
16099 && let Some(tx) = self.parked_tx.take()
16100 {
16101 let _ = tx.send(());
16103 }
16104 poll
16105 }
16106 }
16107
16108 let (done_tx, done_rx) = mpsc::channel();
16109 let runner = std::thread::spawn(move || {
16110 let metrics = Arc::new(RuntimeMetrics::default());
16111 let options = RuntimeOptions {
16112 flavor: RuntimeFlavor::MultiThread,
16113 worker_threads: 2,
16114 max_blocking_tasks: 2,
16115 thread_name_prefix: "rd10-caller".to_string(),
16116 park_deadline: None,
16117 drain_linger: DEFAULT_DRAIN_LINGER,
16118 };
16119 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16120
16121 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
16126 let scheduler = Arc::clone(&executor);
16127 let (runnable, task) = async_task::spawn(
16128 async move { gate_rx.await.expect("gate sender dropped") },
16129 move |r| {
16130 scheduler.schedule(r);
16131 },
16132 );
16133 executor.schedule(runnable);
16134
16135 let (parked_tx, parked_rx) = mpsc::channel::<()>();
16139 let releaser = std::thread::spawn(move || {
16140 if parked_rx.recv().is_ok() {
16141 let _ = gate_tx.send(42usize);
16142 }
16143 });
16144
16145 let mut output = None;
16148 {
16149 let mut future = std::pin::pin!(async {
16150 output = Some(
16151 CallerProbe {
16152 task,
16153 parked_tx: Some(parked_tx),
16154 }
16155 .await,
16156 );
16157 });
16158 executor.block_on(future.as_mut());
16159 }
16160
16161 releaser.join().unwrap();
16162 assert_eq!(
16163 output,
16164 Some(42usize),
16165 "block_on must return the pool-computed value"
16166 );
16167 let _ = done_tx.send(());
16168 });
16169
16170 match done_rx.recv_timeout(Duration::from_secs(10)) {
16171 Ok(()) => runner.join().unwrap(),
16172 Err(error) => panic!(
16173 "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
16174 ),
16175 }
16176 }
16177
16178 #[cfg(not(target_family = "wasm"))]
16179 #[test]
16180 fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
16181 use std::sync::{Barrier, mpsc};
16187 use std::time::Duration;
16188
16189 let (done_tx, done_rx) = mpsc::channel();
16190 let runner = std::thread::spawn(move || {
16191 let metrics = Arc::new(RuntimeMetrics::default());
16192 let options = RuntimeOptions {
16193 flavor: RuntimeFlavor::MultiThread,
16194 worker_threads: 2,
16195 max_blocking_tasks: 2,
16196 thread_name_prefix: "rd1-park".to_string(),
16197 park_deadline: None,
16198 drain_linger: DEFAULT_DRAIN_LINGER,
16199 };
16200 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16201
16202 let task_count = 2usize;
16203 let barrier = Arc::new(Barrier::new(task_count));
16204
16205 let mut senders = Vec::new();
16206 let mut tasks = Vec::new();
16207 for _ in 0..task_count {
16208 let (tx, rx) = oneshot::channel::<usize>();
16209 senders.push(tx);
16210 let exec = Arc::clone(&executor);
16211 let barrier = Arc::clone(&barrier);
16212 let body = async move {
16213 barrier.wait();
16215 let child_exec = Arc::clone(&exec);
16218 let (child_runnable, child_task) =
16219 async_task::spawn(async move { rx.await.unwrap() }, move |r| {
16220 child_exec.schedule(r)
16221 });
16222 exec.schedule(child_runnable);
16223 let mut inner = std::pin::pin!(async move {
16224 assert_eq!(child_task.await, 9usize);
16225 });
16226 exec.block_on(inner.as_mut());
16227 };
16228 let scheduler = Arc::clone(&executor);
16229 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
16230 executor.schedule(runnable);
16231 tasks.push(task);
16232 }
16233
16234 std::thread::sleep(Duration::from_millis(200));
16236 for tx in senders {
16237 let _ = tx.send(9usize);
16238 }
16239
16240 for task in tasks {
16241 futures::executor::block_on(task);
16242 }
16243 let _ = done_tx.send(());
16244 });
16245
16246 match done_rx.recv_timeout(Duration::from_secs(10)) {
16247 Ok(()) => runner.join().unwrap(),
16248 Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
16249 }
16250 }
16251
16252 #[cfg(not(target_family = "wasm"))]
16258 fn wait_until(what: &str, condition: impl Fn() -> bool) {
16259 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
16260 while !condition() {
16261 assert!(
16262 std::time::Instant::now() < deadline,
16263 "timed out waiting for: {what}"
16264 );
16265 std::thread::yield_now();
16266 }
16267 }
16268
16269 #[cfg(not(target_family = "wasm"))]
16278 fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
16279 let deadline = std::time::Instant::now() + timeout;
16280 while !handle.is_finished() {
16281 assert!(
16282 std::time::Instant::now() < deadline,
16283 "thread wedged past its {timeout:?} bound while: {what}"
16284 );
16285 std::thread::sleep(Duration::from_millis(10));
16286 }
16287 handle.join().unwrap();
16288 }
16289
16290 #[cfg(not(target_family = "wasm"))]
16291 fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
16292 executor: &Arc<MultiThreadExecutor>,
16293 start_counter: &AtomicU64,
16294 claim: impl FnOnce() -> T + Send + 'static,
16295 ) -> T {
16296 use std::sync::mpsc;
16297
16298 let (claimed_tx, claimed_rx) = mpsc::channel();
16299 let (release_claim_tx, release_claim_rx) = mpsc::channel();
16300 let (started_tx, started_rx) = mpsc::channel();
16301 let (release_start_tx, release_start_rx) = mpsc::channel();
16302 let starts_before = start_counter.load(Ordering::SeqCst);
16303 let worker = std::thread::spawn(move || {
16304 MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
16305 *slot.borrow_mut() = Some(Box::new(move || {
16306 claimed_tx.send(()).unwrap();
16307 release_claim_rx.recv().unwrap();
16308 }));
16309 });
16310 MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
16311 *slot.borrow_mut() = Some(Box::new(move || {
16312 started_tx.send(()).unwrap();
16313 release_start_rx.recv().unwrap();
16314 }));
16315 });
16316 claim()
16317 });
16318 claimed_rx
16319 .recv_timeout(Duration::from_secs(2))
16320 .expect("the scheduler claim must pause after removing work");
16321
16322 let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16323 let verdict = executor.try_begin_deadlock_verdict();
16324 let claim_verdict_rejected = verdict.is_none();
16325 drop(verdict);
16326 release_claim_tx.send(()).unwrap();
16327
16328 started_rx
16329 .recv_timeout(Duration::from_secs(2))
16330 .expect("the scheduler claim must pause after publishing its start");
16331 let admission_active_after_start =
16332 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16333 let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
16334 let verdict = executor.try_begin_deadlock_verdict();
16335 let start_verdict_rejected = verdict.is_none();
16336 drop(verdict);
16337 release_start_tx.send(()).unwrap();
16338 let output = worker.join().unwrap();
16339
16340 assert!(
16341 admission_active,
16342 "removing scheduler work must retain a detector admission until start accounting"
16343 );
16344 assert!(
16345 claim_verdict_rejected,
16346 "a deadline verdict must not close while removed work has not published its start"
16347 );
16348 assert!(
16349 start_published,
16350 "work start accounting must publish before admission retires"
16351 );
16352 assert!(
16353 admission_active_after_start,
16354 "the detector admission must remain active until after start accounting"
16355 );
16356 assert!(
16357 start_verdict_rejected,
16358 "a deadline verdict must not close between start accounting and admission retirement"
16359 );
16360 output
16361 }
16362
16363 #[cfg(not(target_family = "wasm"))]
16364 #[test]
16365 fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
16366 let executor =
16367 multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
16368
16369 let fifo_ran = Arc::new(AtomicBool::new(false));
16370 let fifo_task_ran = Arc::clone(&fifo_ran);
16371 let (fifo, fifo_task) = async_task::spawn(
16372 async move {
16373 fifo_task_ran.store(true, Ordering::SeqCst);
16374 },
16375 |_| {},
16376 );
16377 executor.metrics.runnable_scheduled();
16378 executor.push_fifo_raw(fifo);
16379 let fifo_executor = Arc::clone(&executor);
16380 assert!(run_claim_with_deadlock_gate_assertion(
16381 &executor,
16382 &executor.metrics.runnable_polls,
16383 move || {
16384 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16385 fifo_executor.run_one_fifo_runnable()
16386 }
16387 ));
16388 futures::executor::block_on(fifo_task);
16389 assert!(fifo_ran.load(Ordering::SeqCst));
16390
16391 let fifo_order = Arc::new(Mutex::new(Vec::new()));
16392 let mut fifo_tasks = Vec::new();
16393 for name in ["first", "second"] {
16394 let task_order = Arc::clone(&fifo_order);
16395 let (runnable, task) = async_task::spawn(
16396 async move {
16397 task_order.lock().unwrap().push(name);
16398 },
16399 |_| {},
16400 );
16401 executor.metrics.runnable_scheduled();
16402 executor.push_fifo_raw(runnable);
16403 fifo_tasks.push(task);
16404 }
16405 for _ in 0..2 {
16406 let fifo_executor = Arc::clone(&executor);
16407 assert!(run_claim_with_deadlock_gate_assertion(
16408 &executor,
16409 &executor.metrics.runnable_polls,
16410 move || {
16411 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16412 fifo_executor.run_one_runnable()
16413 }
16414 ));
16415 }
16416 for task in fifo_tasks {
16417 futures::executor::block_on(task);
16418 }
16419 assert_eq!(
16420 *fifo_order.lock().unwrap(),
16421 ["first", "second"],
16422 "the detector-enabled normal fallback must preserve FIFO order"
16423 );
16424
16425 let lifo_ran = Arc::new(AtomicBool::new(false));
16426 let lifo_task_ran = Arc::clone(&lifo_ran);
16427 let (lifo, lifo_task) = async_task::spawn(
16428 async move {
16429 lifo_task_ran.store(true, Ordering::SeqCst);
16430 },
16431 |_| {},
16432 );
16433 executor.metrics.runnable_scheduled();
16434 let lifo_executor = Arc::clone(&executor);
16435 assert!(run_claim_with_deadlock_gate_assertion(
16436 &executor,
16437 &executor.metrics.runnable_polls,
16438 move || {
16439 let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
16440 LIFO_SLOT.with(|slot| {
16441 assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
16442 });
16443 lifo_executor.run_one_runnable()
16444 }
16445 ));
16446 futures::executor::block_on(lifo_task);
16447 assert!(lifo_ran.load(Ordering::SeqCst));
16448
16449 executor.begin_shutdown();
16450 executor.wait_until_scheduler_idle();
16451 }
16452
16453 #[cfg(not(target_family = "wasm"))]
16454 #[test]
16455 fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
16456 let executor =
16457 multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
16458
16459 let fifo_ran = Arc::new(AtomicBool::new(false));
16460 let fifo_job_ran = Arc::clone(&fifo_ran);
16461 executor.metrics.blocking_scheduled();
16462 executor
16463 .blocking_queue
16464 .lock()
16465 .unwrap()
16466 .push(QueuedBlocking {
16467 id: BlockingJobId(u64::MAX - 90),
16468 run: Box::new(move || {
16469 fifo_job_ran.store(true, Ordering::SeqCst);
16470 }),
16471 });
16472 let fifo_executor = Arc::clone(&executor);
16473 assert!(run_claim_with_deadlock_gate_assertion(
16474 &executor,
16475 &executor.metrics.blocking_tasks_started,
16476 move || {
16477 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16478 fifo_executor.run_one_blocking()
16479 }
16480 ));
16481 assert!(fifo_ran.load(Ordering::SeqCst));
16482
16483 let owner = executor.fresh_owner_token();
16484 let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
16485 let exact_job = BlockingJobId(u64::MAX - 91);
16486 let exact_ran = Arc::new(AtomicBool::new(false));
16487 let exact_job_ran = Arc::clone(&exact_ran);
16488 executor.metrics.blocking_scheduled();
16489 executor
16490 .blocking_queue
16491 .lock()
16492 .unwrap()
16493 .push(QueuedBlocking {
16494 id: exact_job,
16495 run: Box::new(move || {
16496 exact_job_ran.store(true, Ordering::SeqCst);
16497 }),
16498 });
16499 let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
16500 dependency.set_owned(BlockingDependency {
16501 job: exact_job,
16502 owner: Some(owner),
16503 });
16504 let exact_executor = Arc::clone(&executor);
16505 assert!(run_claim_with_deadlock_gate_assertion(
16506 &executor,
16507 &executor.metrics.blocking_tasks_started,
16508 move || {
16509 let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
16510 let _owner = BlockingOwnerGuard::enter(Some(owner));
16511 exact_executor.try_owned_blocking_over_cap(&dependency)
16512 }
16513 ));
16514 assert!(exact_ran.load(Ordering::SeqCst));
16515
16516 drop(active_owner);
16517 executor.begin_shutdown();
16518 executor.wait_until_scheduler_idle();
16519 }
16520
16521 #[cfg(not(target_family = "wasm"))]
16522 #[test]
16523 fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
16524 use std::sync::mpsc;
16532 use std::time::Duration;
16533
16534 let registry = Arc::new(ParkedDrivers::default());
16535
16536 let guard = registry.parked.lock().unwrap();
16538 let (done_tx, done_rx) = mpsc::channel();
16539 let waker_thread = {
16540 let registry = Arc::clone(®istry);
16541 std::thread::spawn(move || {
16542 done_tx.send(registry.wake_one()).unwrap();
16543 })
16544 };
16545 let woke = done_rx
16546 .recv_timeout(Duration::from_secs(5))
16547 .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
16548 assert!(!woke, "an empty registry has nobody to wake");
16549 waker_thread.join().unwrap();
16550 drop(guard);
16551
16552 let parker = Arc::new(DriverParker::default());
16556 registry.register(&parker);
16557 let guard = registry.parked.lock().unwrap();
16558 let (done_tx, done_rx) = mpsc::channel();
16559 let waker_thread = {
16560 let registry = Arc::clone(®istry);
16561 std::thread::spawn(move || {
16562 done_tx.send(registry.wake_one()).unwrap();
16563 })
16564 };
16565 assert!(
16566 done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16567 "wake_one with a registered parker must take the slow path (block on the held lock)"
16568 );
16569 drop(guard);
16570 let woke = done_rx
16571 .recv_timeout(Duration::from_secs(5))
16572 .expect("slow-path wake_one must complete once the lock is released");
16573 assert!(
16574 woke,
16575 "the slow path must pop and wake the registered parker"
16576 );
16577 waker_thread.join().unwrap();
16578 parker.park();
16581 assert_eq!(
16582 registry.count.load(Ordering::SeqCst),
16583 0,
16584 "wake_one deregisters what it pops"
16585 );
16586 }
16587
16588 #[cfg(not(target_family = "wasm"))]
16589 #[test]
16590 fn driver_parker_permit_granted_while_running_is_not_lost() {
16591 use std::sync::mpsc;
16595 use std::time::Duration;
16596
16597 let parker = Arc::new(DriverParker::default());
16598 parker.unpark();
16600 parker.unpark();
16601
16602 let (tx, rx) = mpsc::channel();
16603 let driver = {
16604 let parker = Arc::clone(&parker);
16605 std::thread::spawn(move || {
16606 parker.park(); tx.send(1).unwrap();
16608 parker.park(); tx.send(2).unwrap();
16610 })
16611 };
16612
16613 assert_eq!(
16614 rx.recv_timeout(Duration::from_secs(5))
16615 .expect("a stored permit must not be lost"),
16616 1
16617 );
16618 assert!(
16619 rx.recv_timeout(Duration::from_millis(300)).is_err(),
16620 "two pre-park wakes must coalesce into one permit (second park must sleep)"
16621 );
16622 parker.unpark();
16623 assert_eq!(
16624 rx.recv_timeout(Duration::from_secs(5))
16625 .expect("unpark must wake a sleeping driver"),
16626 2
16627 );
16628 driver.join().unwrap();
16629 }
16630
16631 #[cfg(not(target_family = "wasm"))]
16632 #[test]
16633 fn driver_parker_unrepresentable_timeout_remains_wakeable() {
16634 use std::sync::mpsc;
16635
16636 let parker = Arc::new(DriverParker::default());
16637 assert!(
16638 Instant::now().checked_add(Duration::MAX).is_none(),
16639 "Duration::MAX must exercise the unrepresentable-deadline path"
16640 );
16641 let (done_tx, done_rx) = mpsc::channel();
16642 let driver = {
16643 let parker = Arc::clone(&parker);
16644 std::thread::spawn(move || {
16645 done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
16646 })
16647 };
16648 wait_until("the unbounded timeout driver to sleep", || {
16649 parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
16650 });
16651 assert!(
16652 done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
16653 "an unrepresentable timeout must not expire immediately"
16654 );
16655
16656 let unparker = {
16657 let parker = Arc::clone(&parker);
16658 std::thread::spawn(move || parker.unpark())
16659 };
16660 assert!(
16661 done_rx
16662 .recv_timeout(Duration::from_secs(1))
16663 .expect("an unrepresentable timeout must remain wakeable"),
16664 "a permit must be reported as a wake"
16665 );
16666 unparker.join().unwrap();
16667 driver.join().unwrap();
16668 }
16669
16670 #[cfg(not(target_family = "wasm"))]
16671 #[test]
16672 fn parked_drivers_wake_one_wakes_exactly_one() {
16673 use std::sync::mpsc;
16677 use std::time::Duration;
16678
16679 let registry = Arc::new(ParkedDrivers::default());
16680 let (woke_tx, woke_rx) = mpsc::channel();
16681 let mut drivers = Vec::new();
16682 for index in 0..2 {
16683 let registry = Arc::clone(®istry);
16684 let woke_tx = woke_tx.clone();
16685 drivers.push(std::thread::spawn(move || {
16686 let parker = Arc::new(DriverParker::default());
16687 registry.register(&parker);
16688 parker.park();
16689 registry.deregister(&parker);
16690 woke_tx.send(index).unwrap();
16691 }));
16692 }
16693 wait_until("both drivers registered as parked", || {
16694 registry.count.load(Ordering::SeqCst) == 2
16695 });
16696
16697 assert!(registry.wake_one(), "wake_one must wake a parked driver");
16698 woke_rx
16699 .recv_timeout(Duration::from_secs(5))
16700 .expect("exactly one driver must wake");
16701 assert!(
16702 woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16703 "the second driver must stay parked after a single wake_one"
16704 );
16705 assert_eq!(
16706 registry.count.load(Ordering::SeqCst),
16707 1,
16708 "one driver must remain registered"
16709 );
16710
16711 assert!(
16712 registry.wake_one(),
16713 "a second wake_one must wake the remaining driver"
16714 );
16715 woke_rx
16716 .recv_timeout(Duration::from_secs(5))
16717 .expect("the second driver must wake");
16718 for driver in drivers {
16719 driver.join().unwrap();
16720 }
16721 }
16722
16723 #[cfg(not(target_family = "wasm"))]
16724 #[test]
16725 fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
16726 use std::sync::mpsc;
16727
16728 struct ReenterRegistryOnDrop {
16729 registry: Arc<ParkedDrivers>,
16730 dropped: mpsc::Sender<()>,
16731 }
16732
16733 #[expect(
16734 clippy::manual_noop_waker,
16735 reason = "the test observes the waker payload's Drop"
16736 )]
16737 impl std::task::Wake for ReenterRegistryOnDrop {
16738 fn wake(self: Arc<Self>) {}
16739 }
16740
16741 impl Drop for ReenterRegistryOnDrop {
16742 fn drop(&mut self) {
16743 let parker = Arc::new(DriverParker::default());
16744 self.registry.register(&parker);
16745 self.registry.deregister(&parker);
16746 self.dropped.send(()).unwrap();
16747 }
16748 }
16749
16750 let registry = Arc::new(ParkedDrivers::default());
16751 let dependency = Arc::new(TaskDependency::default());
16752 let (dropped_tx, dropped_rx) = mpsc::channel();
16753 let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
16754 registry: Arc::clone(®istry),
16755 dropped: dropped_tx,
16756 }));
16757 dependency.register_waiter(&waiter);
16758 drop(waiter);
16759
16760 let parker = Arc::new(DriverParker::default());
16761 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16762 drop(dependency);
16763
16764 let retiring_registry = Arc::clone(®istry);
16765 let retiring_parker = Arc::clone(&parker);
16766 let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
16767 dropped_rx
16768 .recv_timeout(Duration::from_secs(1))
16769 .expect("retained dependency waiters must be destroyed after releasing the registry lock");
16770 retiring.join().unwrap();
16771 }
16772
16773 #[cfg(not(target_family = "wasm"))]
16774 #[test]
16775 fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
16776 use std::sync::mpsc;
16777
16778 let registry = Arc::new(ParkedDrivers::default());
16779 let owner = BlockingOwnerToken {
16780 executor_id: u64::MAX - 70,
16781 frame: u64::MAX - 71,
16782 };
16783 let dependency = Arc::new(TaskDependency::default());
16784 dependency.set_owned(BlockingDependency {
16785 job: BlockingJobId(u64::MAX - 72),
16786 owner: Some(owner),
16787 });
16788 let parker = Arc::new(DriverParker::default());
16789 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16790
16791 let (predicate_tx, predicate_rx) = mpsc::channel();
16792 let (release_tx, release_rx) = mpsc::channel();
16793 let selecting_registry = Arc::clone(®istry);
16794 let selector = std::thread::spawn(move || {
16795 DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
16796 *slot.borrow_mut() = Some(Box::new(move || {
16797 predicate_tx.send(()).unwrap();
16798 release_rx.recv().unwrap();
16799 }));
16800 });
16801 selecting_registry.wake_one_owner_dependency(owner)
16802 });
16803
16804 predicate_rx
16805 .recv_timeout(Duration::from_secs(2))
16806 .expect("owner selection must reach the dependency predicate");
16807 let registry_was_unlocked = registry.parked.try_lock().is_ok();
16808 release_tx.send(()).unwrap();
16809 assert!(
16810 selector.join().unwrap(),
16811 "the exact-owner parker must still be selected"
16812 );
16813
16814 assert!(
16815 registry_was_unlocked,
16816 "owner dependency predicates must not run while the parked-driver registry is locked"
16817 );
16818 assert!(parker.consume_permit());
16819 assert_eq!(parker.take_owner_handoff(), Some(owner));
16820 }
16821
16822 #[cfg(not(target_family = "wasm"))]
16823 #[test]
16824 fn owner_handoff_skips_newer_untagged_dependency() {
16825 let registry = ParkedDrivers::default();
16826 let owner = BlockingOwnerToken {
16827 executor_id: u64::MAX - 1,
16828 frame: u64::MAX - 2,
16829 };
16830 let owned_dependency = Arc::new(TaskDependency::default());
16831 owned_dependency.set_owned(BlockingDependency {
16832 job: BlockingJobId(u64::MAX - 3),
16833 owner: Some(owner),
16834 });
16835 let untagged_dependency = Arc::new(TaskDependency::default());
16836 untagged_dependency.set_owned(BlockingDependency {
16837 job: BlockingJobId(u64::MAX - 4),
16838 owner: None,
16839 });
16840 let owned = Arc::new(DriverParker::default());
16841 let untagged = Arc::new(DriverParker::default());
16842
16843 registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
16844 registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
16847
16848 assert!(registry.wake_one_owner_dependency(owner));
16849 assert!(
16850 owned.consume_permit(),
16851 "the exact owner dependency must receive the retry"
16852 );
16853 assert!(
16854 !untagged.consume_permit(),
16855 "an untagged dependency must not consume an exact owner handoff"
16856 );
16857 registry.deregister(&untagged);
16858 }
16859
16860 #[cfg(not(target_family = "wasm"))]
16861 #[test]
16862 fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
16863 let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
16864 let owner = executor.fresh_owner_token();
16865 let valid_dependency = Arc::new(TaskDependency::default());
16866 valid_dependency.set_owned(BlockingDependency {
16867 job: BlockingJobId(u64::MAX - 45),
16868 owner: Some(owner),
16869 });
16870 let withdrawn_dependency = Arc::new(TaskDependency::default());
16871 withdrawn_dependency.set_owned(BlockingDependency {
16872 job: BlockingJobId(u64::MAX - 46),
16873 owner: Some(owner),
16874 });
16875 let valid = Arc::new(DriverParker::default());
16876 let withdrawn = Arc::new(DriverParker::default());
16877
16878 executor
16879 .parked_drivers
16880 .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
16881 executor.parked_drivers.register_with_role(
16882 &withdrawn,
16883 true,
16884 Some(Arc::clone(&withdrawn_dependency)),
16885 );
16886 assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
16887 withdrawn_dependency.clear();
16888
16889 assert_eq!(
16890 executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
16891 Some(false),
16892 "a selected dependency withdrawn before retry must forward the owner handoff"
16893 );
16894 assert!(
16895 valid.consume_permit(),
16896 "the next live same-owner dependency must receive the forwarded wake"
16897 );
16898 assert_eq!(
16899 valid.take_owner_handoff(),
16900 Some(owner),
16901 "the forwarded wake must retain the exact owner identity"
16902 );
16903 }
16904
16905 #[cfg(not(target_family = "wasm"))]
16906 #[test]
16907 fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
16908 use std::sync::mpsc;
16909
16910 let executor = multi_thread_executor(2, None, "owner-handoff-publication");
16911 let owner = executor.fresh_owner_token();
16912 let fallback_dependency = Arc::new(TaskDependency::default());
16913 fallback_dependency.set_owned(BlockingDependency {
16914 job: BlockingJobId(u64::MAX - 47),
16915 owner: Some(owner),
16916 });
16917 let exiting_dependency = Arc::new(TaskDependency::default());
16918 exiting_dependency.set_owned(BlockingDependency {
16919 job: BlockingJobId(u64::MAX - 48),
16920 owner: Some(owner),
16921 });
16922 let fallback = Arc::new(DriverParker::default());
16923 let exiting = Arc::new(DriverParker::default());
16924
16925 executor.parked_drivers.register_with_role(
16926 &fallback,
16927 true,
16928 Some(Arc::clone(&fallback_dependency)),
16929 );
16930 executor.parked_drivers.register_with_role(
16931 &exiting,
16932 true,
16933 Some(Arc::clone(&exiting_dependency)),
16934 );
16935
16936 let (selected_tx, selected_rx) = mpsc::channel();
16937 let (release_selection_tx, release_selection_rx) = mpsc::channel();
16938 let selector_executor = Arc::clone(&executor);
16939 let selector = std::thread::spawn(move || {
16940 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
16941 *slot.borrow_mut() = Some(Box::new(move || {
16942 selected_tx.send(()).unwrap();
16943 release_selection_rx.recv().unwrap();
16944 }));
16945 });
16946 assert!(
16947 selector_executor
16948 .parked_drivers
16949 .wake_one_owner_dependency(owner)
16950 );
16951 });
16952 selected_rx
16953 .recv_timeout(Duration::from_secs(2))
16954 .expect("selection must pause after removing the exact-owner parker");
16955
16956 let (exited_tx, exited_rx) = mpsc::channel();
16957 let exiting_executor = Arc::clone(&executor);
16958 let exiting_driver = Arc::clone(&exiting);
16959 let driver = std::thread::spawn(move || {
16960 exiting_driver.park();
16961 exiting_executor.parked_drivers.deregister(&exiting_driver);
16962 exiting_executor.forward_owner_handoff(&exiting_driver);
16963 exited_tx.send(()).unwrap();
16964 });
16965 exiting.unpark();
16966 exited_rx
16967 .recv_timeout(Duration::from_secs(2))
16968 .expect("the direct future wake must let the selected driver exit");
16969
16970 release_selection_tx.send(()).unwrap();
16971 selector.join().unwrap();
16972 driver.join().unwrap();
16973
16974 assert!(
16975 fallback.consume_permit(),
16976 "an exiting selected driver must forward the owner handoff to the next dependency"
16977 );
16978 assert_eq!(
16979 fallback.take_owner_handoff(),
16980 Some(owner),
16981 "the forwarded handoff must retain the exact owner identity"
16982 );
16983 }
16984
16985 #[cfg(not(target_family = "wasm"))]
16986 #[test]
16987 fn cooperative_panic_forwards_pending_owner_handoff() {
16988 use std::sync::mpsc;
16989
16990 struct PanicAfterWake {
16991 dependency: BlockingDependency,
16992 first_poll: Option<mpsc::Sender<()>>,
16993 }
16994
16995 impl Future for PanicAfterWake {
16996 type Output = ();
16997
16998 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16999 if let Some(first_poll) = self.first_poll.take() {
17000 let mut published = PublishedBlockingDependencies::default();
17003 record_blocking_dependency(&mut published, self.dependency);
17004 first_poll.send(()).unwrap();
17005 return Poll::Pending;
17006 }
17007 panic!("intentional cooperative driver panic after owner handoff");
17008 }
17009 }
17010
17011 let executor = multi_thread_executor(2, None, "panic-owner-handoff");
17012 let owner = executor.fresh_owner_token();
17013 let fallback_dependency = Arc::new(TaskDependency::default());
17014 fallback_dependency.set_owned(BlockingDependency {
17015 job: BlockingJobId(u64::MAX - 49),
17016 owner: Some(owner),
17017 });
17018 let fallback = Arc::new(DriverParker::default());
17019 executor.parked_drivers.register_with_role(
17020 &fallback,
17021 true,
17022 Some(Arc::clone(&fallback_dependency)),
17023 );
17024
17025 let (first_poll_tx, first_poll_rx) = mpsc::channel();
17026 let (panicked_tx, panicked_rx) = mpsc::channel();
17027 let driver_executor = Arc::clone(&executor);
17028 let driver = std::thread::spawn(move || {
17029 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17030 let mut future = std::pin::pin!(PanicAfterWake {
17031 dependency: BlockingDependency {
17032 job: BlockingJobId(u64::MAX - 50),
17033 owner: Some(owner)
17034 },
17035 first_poll: Some(first_poll_tx),
17036 });
17037 let panicked = catch_unwind(AssertUnwindSafe(|| {
17038 driver_executor.block_on(future.as_mut());
17039 }))
17040 .is_err();
17041 panicked_tx.send(panicked).unwrap();
17042 });
17043
17044 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17045 wait_until("the panicking cooperative driver to park", || {
17046 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17047 });
17048 assert!(
17049 executor.parked_drivers.wake_one_owner_dependency(owner),
17050 "the exact owner handoff must select the cooperative driver"
17051 );
17052 assert!(
17053 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17054 "the test future must unwind from its second poll"
17055 );
17056 driver.join().unwrap();
17057
17058 assert!(
17059 fallback.consume_permit(),
17060 "panic cleanup must forward the unconsumed owner handoff"
17061 );
17062 assert_eq!(
17063 fallback.take_owner_handoff(),
17064 Some(owner),
17065 "panic cleanup must preserve the exact owner identity"
17066 );
17067 }
17068
17069 #[cfg(not(target_family = "wasm"))]
17070 #[test]
17071 fn cooperative_panic_flushes_lifo_slot() {
17072 struct ScheduleThenPanic {
17073 executor: Arc<MultiThreadExecutor>,
17074 ran: Arc<AtomicBool>,
17075 }
17076
17077 impl Future for ScheduleThenPanic {
17078 type Output = ();
17079
17080 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17081 let ran = Arc::clone(&self.ran);
17082 let scheduler = Arc::clone(&self.executor);
17083 let (runnable, task) = async_task::spawn(
17084 async move {
17085 ran.store(true, Ordering::SeqCst);
17086 },
17087 move |runnable| scheduler.schedule(runnable),
17088 );
17089 self.executor.schedule(runnable);
17090 task.detach();
17091 panic!("intentional cooperative driver panic with a populated LIFO slot");
17092 }
17093 }
17094
17095 let executor = multi_thread_executor(2, None, "panic-lifo-flush");
17096 executor
17097 .active_drainers
17098 .store(executor.max_drainers, Ordering::SeqCst);
17099 let ran = Arc::new(AtomicBool::new(false));
17100 let panicked = {
17101 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17102 let mut future = std::pin::pin!(ScheduleThenPanic {
17103 executor: Arc::clone(&executor),
17104 ran: Arc::clone(&ran),
17105 });
17106 catch_unwind(AssertUnwindSafe(|| {
17107 executor.block_on(future.as_mut());
17108 }))
17109 .is_err()
17110 };
17111
17112 assert!(panicked, "the test future must unwind from its first poll");
17113 assert!(
17114 executor.pop_lifo_slot().is_none(),
17115 "panic cleanup must empty the LIFO slot"
17116 );
17117 assert_eq!(
17118 executor.injector.len(),
17119 1,
17120 "panic cleanup must move the slot runnable to the shared FIFO"
17121 );
17122 assert!(
17123 executor.run_one(),
17124 "the flushed runnable must remain executable"
17125 );
17126 assert!(ran.load(Ordering::SeqCst));
17127 executor.active_drainers.store(0, Ordering::SeqCst);
17128 }
17129
17130 #[cfg(not(target_family = "wasm"))]
17131 #[test]
17132 fn cooperative_panic_compensates_absorbed_queue_wake() {
17133 use std::sync::mpsc;
17134
17135 struct PanicAfterWake {
17136 first_poll: Option<mpsc::Sender<()>>,
17137 }
17138
17139 impl Future for PanicAfterWake {
17140 type Output = ();
17141
17142 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17143 if let Some(first_poll) = self.first_poll.take() {
17144 first_poll.send(()).unwrap();
17145 return Poll::Pending;
17146 }
17147 panic!("intentional cooperative driver panic after a queue wake");
17148 }
17149 }
17150
17151 let executor = multi_thread_executor(2, None, "panic-wake-compensation");
17152 executor
17153 .active_drainers
17154 .store(executor.max_drainers, Ordering::SeqCst);
17155 let fallback = Arc::new(DriverParker::default());
17156 executor.parked_drivers.register(&fallback);
17157 let (first_poll_tx, first_poll_rx) = mpsc::channel();
17158 let (panicked_tx, panicked_rx) = mpsc::channel();
17159 let driver_executor = Arc::clone(&executor);
17160 let driver = std::thread::spawn(move || {
17161 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17162 let mut future = std::pin::pin!(PanicAfterWake {
17163 first_poll: Some(first_poll_tx)
17164 });
17165 let panicked = catch_unwind(AssertUnwindSafe(|| {
17166 driver_executor.block_on(future.as_mut());
17167 }))
17168 .is_err();
17169 panicked_tx.send(panicked).unwrap();
17170 });
17171
17172 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17173 wait_until(
17174 "the panicking cooperative driver to park after the fallback",
17175 || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
17176 );
17177
17178 let ran = Arc::new(AtomicBool::new(false));
17179 let ran_task = Arc::clone(&ran);
17180 let scheduler = Arc::clone(&executor);
17181 let (runnable, task) = async_task::spawn(
17182 async move {
17183 ran_task.store(true, Ordering::SeqCst);
17184 },
17185 move |runnable| scheduler.schedule(runnable),
17186 );
17187 task.detach();
17188 executor.metrics.runnable_scheduled();
17189 executor.push_fifo_raw(runnable);
17190 assert!(
17191 executor.parked_drivers.wake_one(),
17192 "the queued runnable's wake must be absorbed by the cooperative driver"
17193 );
17194
17195 assert!(
17196 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17197 "the test future must unwind after absorbing the queue wake"
17198 );
17199 driver.join().unwrap();
17200 assert!(
17201 fallback.consume_permit(),
17202 "panic cleanup must compensate the absorbed queue wake to the fallback driver"
17203 );
17204 assert!(
17205 executor.run_one(),
17206 "the queued runnable must remain executable"
17207 );
17208 assert!(ran.load(Ordering::SeqCst));
17209 executor.active_drainers.store(0, Ordering::SeqCst);
17210 }
17211
17212 #[cfg(not(target_family = "wasm"))]
17213 #[test]
17214 fn multi_thread_future_wake_targets_its_own_parked_driver() {
17215 use std::sync::mpsc;
17223 use std::time::Duration;
17224
17225 let (done_tx, done_rx) = mpsc::channel();
17226 let runner = std::thread::spawn(move || {
17227 let metrics = Arc::new(RuntimeMetrics::default());
17228 let options = RuntimeOptions {
17229 flavor: RuntimeFlavor::MultiThread,
17230 worker_threads: 2,
17231 max_blocking_tasks: 2,
17232 thread_name_prefix: "wake-target".to_string(),
17233 park_deadline: None,
17234 drain_linger: DEFAULT_DRAIN_LINGER,
17235 };
17236 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17237
17238 let (tx1, rx1) = oneshot::channel::<usize>();
17239 let (tx2, rx2) = oneshot::channel::<usize>();
17240 let t1_done = Arc::new(AtomicBool::new(false));
17241 let t2_done = Arc::new(AtomicBool::new(false));
17242
17243 let t1_exec = Arc::clone(&executor);
17245 let t1_flag = Arc::clone(&t1_done);
17246 let t1_body = async move {
17247 let mut inner = std::pin::pin!(async move {
17248 assert_eq!(rx1.await.unwrap(), 7usize);
17249 });
17250 t1_exec.block_on(inner.as_mut());
17251 t1_flag.store(true, Ordering::SeqCst);
17252 };
17253 let sched1 = Arc::clone(&executor);
17254 let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
17255 executor.schedule(runnable1);
17256 wait_until("driver 1 parked", || {
17257 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17258 });
17259
17260 let t2_exec = Arc::clone(&executor);
17264 let t2_flag = Arc::clone(&t2_done);
17265 let t2_body = async move {
17266 let mut inner = std::pin::pin!(async move {
17267 assert_eq!(rx2.await.unwrap(), 9usize);
17268 });
17269 t2_exec.block_on(inner.as_mut());
17270 t2_flag.store(true, Ordering::SeqCst);
17271 };
17272 let sched2 = Arc::clone(&executor);
17273 let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
17274 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(runnable2);
17276 executor.ensure_drainer();
17277 wait_until("driver 2 parked", || {
17278 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17279 });
17280
17281 tx1.send(7usize).unwrap();
17284 wait_until("T1 completed via its own driver's wake", || {
17285 t1_done.load(Ordering::SeqCst)
17286 });
17287 futures::executor::block_on(task1);
17288 assert!(
17289 !t2_done.load(Ordering::SeqCst),
17290 "D2 must still be parked: its future was never completed"
17291 );
17292 wait_until(
17309 "the retiring drain frame registered its linger parker",
17310 || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
17311 );
17312 wait_until("the lingering drainer's idle budget expired", || {
17313 executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
17314 });
17315 assert_eq!(
17316 executor.parked_drivers.count.load(Ordering::SeqCst),
17317 1,
17318 "exactly the untargeted driver must remain parked after the linger expired"
17319 );
17320 assert!(
17321 !t2_done.load(Ordering::SeqCst),
17322 "D2 must still be parked after the lingering drainer expired"
17323 );
17324
17325 tx2.send(9usize).unwrap();
17327 futures::executor::block_on(task2);
17328 assert!(t2_done.load(Ordering::SeqCst));
17329 let _ = done_tx.send(());
17330 });
17331
17332 match done_rx.recv_timeout(Duration::from_secs(10)) {
17333 Ok(()) => runner.join().unwrap(),
17334 Err(error) => panic!(
17335 "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
17336 ),
17337 }
17338 }
17339
17340 #[cfg(not(target_family = "wasm"))]
17341 #[test]
17342 fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
17343 use std::sync::mpsc;
17351 use std::time::Duration;
17352
17353 let (done_tx, done_rx) = mpsc::channel();
17354 let runner = std::thread::spawn(move || {
17355 let metrics = Arc::new(RuntimeMetrics::default());
17356 let options = RuntimeOptions {
17357 flavor: RuntimeFlavor::MultiThread,
17358 worker_threads: 1,
17359 max_blocking_tasks: 1,
17360 thread_name_prefix: "wake-compensate".to_string(),
17361 park_deadline: None,
17362 drain_linger: DEFAULT_DRAIN_LINGER,
17363 };
17364 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17365
17366 let (gate_tx, gate_rx) = oneshot::channel::<()>();
17368 let td_exec = Arc::clone(&executor);
17369 let td_body = async move {
17370 let mut inner = std::pin::pin!(async move {
17371 gate_rx.await.unwrap();
17372 });
17373 td_exec.block_on(inner.as_mut());
17374 };
17375 let sched = Arc::clone(&executor);
17376 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
17377 executor.schedule(td_runnable);
17378 wait_until("the sole driver parked", || {
17379 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17380 });
17381
17382 let (ran_tx, ran_rx) = mpsc::channel::<()>();
17385 let sched_stranded = Arc::clone(&executor);
17386 let (stranded_runnable, stranded_task) = async_task::spawn(
17387 async move {
17388 ran_tx.send(()).unwrap();
17389 },
17390 move |r| sched_stranded.schedule(r),
17391 );
17392 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(stranded_runnable);
17394 stranded_task.detach();
17395
17396 {
17401 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17402 let mut ready = std::pin::pin!(async {});
17403 executor.block_on(ready.as_mut());
17404 }
17405
17406 ran_rx.recv_timeout(Duration::from_secs(5)).expect(
17407 "exit compensation must wake the parked driver to run the stranded queued runnable",
17408 );
17409
17410 gate_tx.send(()).unwrap();
17412 futures::executor::block_on(td_task);
17413 let _ = done_tx.send(());
17414 });
17415
17416 match done_rx.recv_timeout(Duration::from_secs(10)) {
17417 Ok(()) => runner.join().unwrap(),
17418 Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
17419 }
17420 }
17421
17422 #[cfg(not(target_family = "wasm"))]
17423 #[test]
17424 fn cooperative_exit_compensates_blocking_only_residue() {
17425 use std::sync::mpsc;
17426
17427 struct Controlled {
17428 ready: Arc<AtomicBool>,
17429 entered: Option<mpsc::Sender<()>>,
17430 }
17431
17432 impl Future for Controlled {
17433 type Output = ();
17434
17435 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17436 if let Some(entered) = self.entered.take() {
17437 entered.send(()).unwrap();
17438 }
17439 if self.ready.load(Ordering::SeqCst) {
17440 Poll::Ready(())
17441 } else {
17442 Poll::Pending
17443 }
17444 }
17445 }
17446
17447 let options = RuntimeOptions {
17448 flavor: RuntimeFlavor::MultiThread,
17449 worker_threads: 2,
17450 max_blocking_tasks: 1,
17451 thread_name_prefix: "blocking-exit-compensation".to_string(),
17452 park_deadline: None,
17453 drain_linger: DEFAULT_DRAIN_LINGER,
17454 };
17455 let executor =
17456 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17457 let ready = Arc::new(AtomicBool::new(false));
17458 let (entered_tx, entered_rx) = mpsc::channel();
17459 let (returned_tx, returned_rx) = mpsc::channel();
17460 let (release_tx, release_rx) = mpsc::channel();
17461 let task_exec = Arc::clone(&executor);
17462 let task_ready = Arc::clone(&ready);
17463 let scheduler = Arc::clone(&executor);
17464 let (runnable, task) = async_task::spawn(
17465 async move {
17466 let mut future = std::pin::pin!(Controlled {
17467 ready: task_ready,
17468 entered: Some(entered_tx)
17469 });
17470 task_exec.block_on(future.as_mut());
17471 returned_tx.send(()).unwrap();
17472 release_rx.recv().unwrap();
17473 },
17474 move |runnable| scheduler.schedule(runnable),
17475 );
17476 executor.schedule(runnable);
17477 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17478
17479 let timer_exec = Arc::clone(&executor);
17480 let timer_scheduler = Arc::clone(&executor);
17481 let (timer_runnable, timer_task) = async_task::spawn(
17482 async move {
17483 heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
17484 },
17485 move |runnable| timer_scheduler.schedule(runnable),
17486 );
17487 executor.schedule(timer_runnable);
17488 wait_until("the dedicated timekeeper parked", || {
17489 executor
17490 .timers
17491 .inner
17492 .lock()
17493 .unwrap()
17494 .timekeeper_parker
17495 .is_some()
17496 });
17497
17498 ready.store(true, Ordering::SeqCst);
17499 let (blocking_tx, blocking_rx) = mpsc::channel();
17500 let blocking = executor.schedule_blocking(move || {
17501 blocking_tx.send(()).unwrap();
17502 });
17503 blocking_rx
17504 .recv_timeout(Duration::from_secs(2))
17505 .expect("blocking-only residue must be serviced by a Rayon worker");
17506 returned_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17507 release_tx.send(()).unwrap();
17508 futures::executor::block_on(task);
17509 futures::executor::block_on(blocking).unwrap();
17510 executor.shutdown_timers();
17511 futures::executor::block_on(timer_task);
17512 }
17513
17514 #[cfg(not(target_family = "wasm"))]
17515 #[test]
17516 fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
17517 let options = RuntimeOptions {
17518 flavor: RuntimeFlavor::MultiThread,
17519 worker_threads: 2,
17520 max_blocking_tasks: 1,
17521 thread_name_prefix: "stopped-exit-compensation".to_string(),
17522 park_deadline: None,
17523 drain_linger: DEFAULT_DRAIN_LINGER,
17524 };
17525 let executor =
17526 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17527 let ran = Arc::new(AtomicBool::new(false));
17528 let blocking_ran = Arc::clone(&ran);
17529
17530 executor
17534 .active_drainers
17535 .store(executor.max_drainers, Ordering::SeqCst);
17536 let blocking = executor.schedule_blocking(move || {
17537 blocking_ran.store(true, Ordering::SeqCst);
17538 });
17539 assert!(!executor.blocking_queue.lock().unwrap().is_empty());
17540
17541 executor.stop.begin_shutdown();
17542 executor.compensate_queued_work_wake(true);
17543 let ran_after_stop = ran.load(Ordering::SeqCst);
17544
17545 executor.active_drainers.store(0, Ordering::SeqCst);
17546 executor.begin_shutdown();
17547 let blocking_result = futures::executor::block_on(blocking);
17548
17549 assert!(
17550 !ran_after_stop,
17551 "exit compensation started queued blocking work after stop"
17552 );
17553 assert!(
17554 blocking_result.is_err(),
17555 "shutdown must cancel the queued blocking job"
17556 );
17557 }
17558
17559 #[cfg(not(target_family = "wasm"))]
17560 #[test]
17561 fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
17562 use std::sync::mpsc;
17571 use std::time::Duration;
17572
17573 const ROUNDS: usize = 20_000;
17574
17575 let (done_tx, done_rx) = mpsc::channel();
17576 let registry = Arc::new(ParkedDrivers::default());
17577 let work = Arc::new(AtomicBool::new(false));
17578
17579 let consumer = {
17580 let registry = Arc::clone(®istry);
17581 let work = Arc::clone(&work);
17582 std::thread::spawn(move || {
17583 let parker = Arc::new(DriverParker::default());
17584 for _ in 0..ROUNDS {
17585 loop {
17586 if work.swap(false, Ordering::SeqCst) {
17587 break;
17588 }
17589 registry.register(&parker);
17592 if work.load(Ordering::SeqCst) {
17593 registry.deregister(&parker);
17594 continue;
17595 }
17596 parker.park();
17597 registry.deregister(&parker);
17598 }
17599 }
17600 done_tx.send(()).unwrap();
17601 })
17602 };
17603
17604 for _ in 0..ROUNDS {
17605 work.store(true, Ordering::SeqCst);
17606 let _ = registry.wake_one();
17610 while work.load(Ordering::SeqCst) {
17611 std::hint::spin_loop();
17612 }
17613 }
17614
17615 done_rx
17616 .recv_timeout(Duration::from_secs(30))
17617 .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
17618 consumer.join().unwrap();
17619 }
17620
17621 #[cfg(not(target_family = "wasm"))]
17622 #[test]
17623 fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
17624 let options = RuntimeOptions {
17628 flavor: RuntimeFlavor::MultiThread,
17629 worker_threads: 2,
17630 max_blocking_tasks: 2,
17631 thread_name_prefix: "lifo-order".to_string(),
17632 park_deadline: None,
17633 drain_linger: DEFAULT_DRAIN_LINGER,
17634 };
17635 let executor =
17636 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17637 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17638
17639 let old_order = Arc::clone(&order);
17642 let sched = Arc::clone(&executor);
17643 let (old_runnable, old_task) = async_task::spawn(
17644 async move {
17645 old_order.lock().unwrap().push("old");
17646 },
17647 move |r| sched.schedule(r),
17648 );
17649 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(old_runnable);
17651 old_task.detach();
17652
17653 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17656 let new_order = Arc::clone(&order);
17657 let sched = Arc::clone(&executor);
17658 let (new_runnable, new_task) = async_task::spawn(
17659 async move {
17660 new_order.lock().unwrap().push("new");
17661 },
17662 move |r| sched.schedule(r),
17663 );
17664 executor.schedule(new_runnable);
17665 new_task.detach();
17666 assert_eq!(
17667 executor.injector.len(),
17668 1,
17669 "the slot-path schedule must not push to the shared FIFO"
17670 );
17671
17672 assert!(executor.run_one(), "run_one must pop the slot first");
17673 assert!(executor.run_one(), "run_one must then pop the FIFO");
17674 assert!(!executor.run_one(), "no work must remain");
17675 assert_eq!(
17676 *order.lock().unwrap(),
17677 vec!["new", "old"],
17678 "the slot runnable (newest) must run before older FIFO work"
17679 );
17680 }
17681
17682 #[cfg(not(target_family = "wasm"))]
17683 #[test]
17684 fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
17685 let options = RuntimeOptions {
17686 flavor: RuntimeFlavor::MultiThread,
17687 worker_threads: 2,
17688 max_blocking_tasks: 1,
17689 thread_name_prefix: "fifo-owner".to_string(),
17690 park_deadline: None,
17691 drain_linger: DEFAULT_DRAIN_LINGER,
17692 };
17693 let executor =
17694 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17695 let observed_owner = Arc::new(Mutex::new(None));
17696 let observed = Arc::clone(&observed_owner);
17697 let (runnable, task) = async_task::spawn(
17698 async move {
17699 *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
17700 },
17701 |_| {},
17702 );
17703 task.detach();
17704 executor.metrics.runnable_scheduled();
17705 executor.push_fifo_raw(runnable);
17706 executor.active_drainers.store(1, Ordering::Release);
17707
17708 let owner = executor.fresh_owner_token();
17709 let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
17710 Arc::clone(&executor).drain();
17711
17712 assert_eq!(
17713 *observed_owner.lock().unwrap(),
17714 Some(None),
17715 "FIFO runnables must not borrow a blocking closure's over-cap privilege"
17716 );
17717 assert_eq!(
17718 BLOCKING_OWNER.with(std::cell::Cell::get),
17719 Some(owner),
17720 "the ambient blocking owner must be restored after the runnable"
17721 );
17722 }
17723
17724 #[cfg(not(target_family = "wasm"))]
17725 #[test]
17726 fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
17727 use std::sync::mpsc;
17731 use std::time::Duration;
17732
17733 let (done_tx, done_rx) = mpsc::channel();
17734 let runner = std::thread::spawn(move || {
17735 let options = RuntimeOptions {
17736 flavor: RuntimeFlavor::MultiThread,
17737 worker_threads: 1,
17738 max_blocking_tasks: 1,
17739 thread_name_prefix: "lifo-drain".to_string(),
17740 park_deadline: None,
17741 drain_linger: DEFAULT_DRAIN_LINGER,
17742 };
17743 let executor =
17744 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17745 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17746
17747 let p_exec = Arc::clone(&executor);
17748 let p_order = Arc::clone(&order);
17749 let p_body = async move {
17750 p_order.lock().unwrap().push("P");
17751 let c_order = Arc::clone(&p_order);
17753 let sched = Arc::clone(&p_exec);
17754 let (c_runnable, c_task) = async_task::spawn(
17755 async move {
17756 c_order.lock().unwrap().push("C");
17757 },
17758 move |r| sched.schedule(r),
17759 );
17760 p_exec.schedule(c_runnable);
17761 c_task.detach();
17762 };
17763
17764 let mut tasks = Vec::new();
17768 let sched = Arc::clone(&executor);
17769 let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
17770 executor.metrics.runnable_scheduled();
17771 executor.push_fifo_raw(p_runnable);
17772 tasks.push(p_task);
17773 for name in ["T1", "T2"] {
17774 let t_order = Arc::clone(&order);
17775 let sched = Arc::clone(&executor);
17776 let (t_runnable, t_task) = async_task::spawn(
17777 async move {
17778 t_order.lock().unwrap().push(name);
17779 },
17780 move |r| sched.schedule(r),
17781 );
17782 executor.metrics.runnable_scheduled();
17783 executor.push_fifo_raw(t_runnable);
17784 tasks.push(t_task);
17785 }
17786 executor.ensure_drainer();
17787
17788 for task in tasks {
17789 futures::executor::block_on(task);
17790 }
17791 wait_until("the detached child C ran", || {
17792 order.lock().unwrap().len() == 4
17793 });
17794 assert_eq!(
17795 *order.lock().unwrap(),
17796 vec!["P", "C", "T1", "T2"],
17797 "the slot child C must run right after its scheduler P, ahead of older FIFO work"
17798 );
17799 let _ = done_tx.send(());
17800 });
17801
17802 match done_rx.recv_timeout(Duration::from_secs(10)) {
17803 Ok(()) => runner.join().unwrap(),
17804 Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
17805 }
17806 }
17807
17808 #[cfg(not(target_family = "wasm"))]
17809 #[test]
17810 fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
17811 let options = RuntimeOptions {
17817 flavor: RuntimeFlavor::MultiThread,
17818 worker_threads: 2,
17819 max_blocking_tasks: 2,
17820 thread_name_prefix: "lifo-displace".to_string(),
17821 park_deadline: None,
17822 drain_linger: DEFAULT_DRAIN_LINGER,
17823 };
17824 let executor =
17825 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17826 executor
17827 .active_drainers
17828 .store(executor.max_drainers, Ordering::SeqCst);
17829 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17830
17831 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17832 for name in ["a", "b"] {
17833 let n_order = Arc::clone(&order);
17834 let sched = Arc::clone(&executor);
17835 let (runnable, task) = async_task::spawn(
17836 async move {
17837 n_order.lock().unwrap().push(name);
17838 },
17839 move |r| sched.schedule(r),
17840 );
17841 executor.schedule(runnable);
17842 task.detach();
17843 }
17844 assert_eq!(
17845 executor.injector.len(),
17846 1,
17847 "the displaced occupant (a) must have been pushed to the shared FIFO"
17848 );
17849
17850 assert!(executor.run_one(), "run_one must pop the slot (b) first");
17851 assert!(
17852 executor.run_one(),
17853 "run_one must then pop the displaced (a) from the FIFO"
17854 );
17855 assert!(!executor.run_one(), "no work must remain");
17856 assert_eq!(
17857 *order.lock().unwrap(),
17858 vec!["b", "a"],
17859 "the newest schedule must win the slot; the displaced one must survive via the FIFO"
17860 );
17861 executor.active_drainers.store(0, Ordering::SeqCst);
17862 }
17863
17864 #[cfg(not(target_family = "wasm"))]
17865 #[test]
17866 fn lifo_slot_is_executor_scoped() {
17867 let options = RuntimeOptions {
17872 flavor: RuntimeFlavor::MultiThread,
17873 worker_threads: 2,
17874 max_blocking_tasks: 2,
17875 thread_name_prefix: "lifo-scope".to_string(),
17876 park_deadline: None,
17877 drain_linger: DEFAULT_DRAIN_LINGER,
17878 };
17879 let exec1 =
17880 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17881 let exec2 =
17882 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17883 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
17884 exec1
17885 .active_drainers
17886 .store(exec1.max_drainers, Ordering::SeqCst);
17887 exec2
17888 .active_drainers
17889 .store(exec2.max_drainers, Ordering::SeqCst);
17890 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17891
17892 let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
17894 let r1_order = Arc::clone(&order);
17895 let sched = Arc::clone(&exec1);
17896 let (r1, t1) = async_task::spawn(
17897 async move {
17898 r1_order.lock().unwrap().push("one");
17899 },
17900 move |r| sched.schedule(r),
17901 );
17902 exec1.schedule(r1);
17903 t1.detach();
17904 assert!(
17905 exec1.injector.is_empty(),
17906 "r1 must sit in the slot, not exec1's FIFO"
17907 );
17908
17909 {
17910 let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
17913 let r2_order = Arc::clone(&order);
17914 let sched = Arc::clone(&exec2);
17915 let (r2, t2) = async_task::spawn(
17916 async move {
17917 r2_order.lock().unwrap().push("two");
17918 },
17919 move |r| sched.schedule(r),
17920 );
17921 exec2.schedule(r2);
17922 t2.detach();
17923 assert_eq!(
17924 exec2.injector.len(),
17925 1,
17926 "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
17927 );
17928 assert!(
17929 exec2.pop_lifo_slot().is_none(),
17930 "exec2 must not pop exec1's slot entry (executor-scoped pop)"
17931 );
17932 assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
17933 }
17934
17935 assert!(
17937 exec1.run_one(),
17938 "exec1's slot entry must have survived the foreign activity"
17939 );
17940 assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
17941 exec1.active_drainers.store(0, Ordering::SeqCst);
17942 exec2.active_drainers.store(0, Ordering::SeqCst);
17943 }
17944
17945 #[cfg(not(target_family = "wasm"))]
17946 #[test]
17947 fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
17948 use std::sync::mpsc;
17955 use std::time::Duration;
17956
17957 const CHAIN: u64 = 200; fn spawn_link(
17960 executor: &Arc<MultiThreadExecutor>,
17961 remaining: u64,
17962 counter: Arc<AtomicU64>,
17963 done: mpsc::Sender<()>,
17964 ) {
17965 let body_exec = Arc::clone(executor);
17966 let body = async move {
17967 counter.fetch_add(1, Ordering::SeqCst);
17968 if remaining > 0 {
17969 spawn_link(&body_exec, remaining - 1, counter, done);
17970 } else {
17971 done.send(()).unwrap();
17972 }
17973 };
17974 let sched = Arc::clone(executor);
17975 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17976 executor.schedule(runnable);
17977 task.detach();
17978 }
17979
17980 let (done_tx, done_rx) = mpsc::channel();
17981 let runner = std::thread::spawn(move || {
17982 let metrics = Arc::new(RuntimeMetrics::default());
17983 let options = RuntimeOptions {
17984 flavor: RuntimeFlavor::MultiThread,
17985 worker_threads: 1,
17986 max_blocking_tasks: 1,
17987 thread_name_prefix: "lifo-budget".to_string(),
17988 park_deadline: None,
17989 drain_linger: DEFAULT_DRAIN_LINGER,
17990 };
17991 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17992
17993 let counter = Arc::new(AtomicU64::new(0));
17994 let (chain_tx, chain_rx) = mpsc::channel();
17995 spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
17996
17997 chain_rx
17998 .recv_timeout(Duration::from_secs(10))
17999 .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
18000 assert_eq!(
18001 counter.load(Ordering::SeqCst),
18002 CHAIN,
18003 "every chain link must have run"
18004 );
18005 wait_until("runnable counters settle to zero (no slot leak)", || {
18006 metrics.queued_runnables.load(Ordering::Relaxed) == 0
18007 && metrics.active_runnables.load(Ordering::Relaxed) == 0
18008 });
18009 let _ = done_tx.send(());
18010 });
18011
18012 match done_rx.recv_timeout(Duration::from_secs(15)) {
18013 Ok(()) => runner.join().unwrap(),
18014 Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
18015 }
18016 }
18017
18018 #[cfg(not(target_family = "wasm"))]
18019 #[test]
18020 fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
18021 let options = RuntimeOptions {
18028 flavor: RuntimeFlavor::MultiThread,
18029 worker_threads: 2,
18030 max_blocking_tasks: 2,
18031 thread_name_prefix: "lifo-flush".to_string(),
18032 park_deadline: None,
18033 drain_linger: DEFAULT_DRAIN_LINGER,
18034 };
18035 let executor =
18036 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18037 executor
18038 .active_drainers
18039 .store(executor.max_drainers, Ordering::SeqCst);
18040 let ran = Arc::new(AtomicBool::new(false));
18041
18042 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18043 let ran_flag = Arc::clone(&ran);
18044 let sched = Arc::clone(&executor);
18045 let (runnable, task) = async_task::spawn(
18046 async move {
18047 ran_flag.store(true, Ordering::SeqCst);
18048 },
18049 move |r| sched.schedule(r),
18050 );
18051 executor.schedule(runnable);
18052 task.detach();
18053 assert!(
18054 executor.injector.is_empty(),
18055 "the runnable must start in the slot"
18056 );
18057
18058 executor.flush_lifo_slot();
18059 assert!(
18060 executor.pop_lifo_slot().is_none(),
18061 "the slot must be empty after a flush"
18062 );
18063 assert_eq!(
18064 executor.injector.len(),
18065 1,
18066 "the flushed runnable must be on the shared FIFO"
18067 );
18068 assert!(executor.run_one(), "the flushed runnable must still run");
18069 assert!(
18070 ran.load(Ordering::SeqCst),
18071 "the flushed task must complete, not be dropped"
18072 );
18073 executor.active_drainers.store(0, Ordering::SeqCst);
18074 }
18075
18076 #[cfg(not(target_family = "wasm"))]
18077 #[test]
18078 fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
18079 let options = RuntimeOptions {
18084 flavor: RuntimeFlavor::MultiThread,
18085 worker_threads: 2,
18086 max_blocking_tasks: 2,
18087 thread_name_prefix: "lifo-owner".to_string(),
18088 park_deadline: None,
18089 drain_linger: DEFAULT_DRAIN_LINGER,
18090 };
18091 let executor =
18092 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18093 let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
18094
18095 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18096
18097 let observed_slot = Arc::clone(&observed);
18101 let sched = Arc::clone(&executor);
18102 let (runnable, task) = async_task::spawn(
18103 async move {
18104 *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
18105 },
18106 move |r| sched.schedule(r),
18107 );
18108 executor.schedule(runnable);
18109 task.detach();
18110 assert!(
18111 executor.injector.is_empty(),
18112 "the runnable must sit in the slot"
18113 );
18114
18115 let token = executor.fresh_owner_token();
18119 let _owner = BlockingOwnerGuard::enter(Some(token));
18120 assert!(executor.run_one(), "the slot runnable must run");
18121 assert_eq!(
18122 *observed.lock().unwrap(),
18123 Some(None),
18124 "a slot runnable must observe a CLEARED blocking owner"
18125 );
18126 assert_eq!(
18127 BLOCKING_OWNER.with(std::cell::Cell::get),
18128 Some(token),
18129 "the ambient owner frame must be restored after the slot runnable"
18130 );
18131 }
18132
18133 #[cfg(not(target_family = "wasm"))]
18139 struct PingPongShared {
18140 count: AtomicU64,
18141 stop: AtomicBool,
18142 stop_after: u64,
18143 wakers: Mutex<[Option<std::task::Waker>; 2]>,
18144 }
18145
18146 #[cfg(not(target_family = "wasm"))]
18147 struct PingPong {
18148 shared: Arc<PingPongShared>,
18149 me: usize,
18150 }
18151
18152 #[cfg(not(target_family = "wasm"))]
18153 impl Future for PingPong {
18154 type Output = ();
18155 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18156 let shared = &self.shared;
18157 let polls = shared.count.fetch_add(1, Ordering::SeqCst);
18158 if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
18159 let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
18160 if let Some(partner) = partner {
18161 partner.wake();
18162 }
18163 return Poll::Ready(());
18164 }
18165 let mut wakers = shared.wakers.lock().unwrap();
18166 wakers[self.me] = Some(cx.waker().clone());
18167 if let Some(partner) = wakers[1 - self.me].take() {
18168 drop(wakers);
18169 partner.wake();
18170 }
18171 Poll::Pending
18172 }
18173 }
18174
18175 #[cfg(not(target_family = "wasm"))]
18176 fn spawn_ping_pong_pair(
18177 executor: &Arc<MultiThreadExecutor>,
18178 shared: &Arc<PingPongShared>,
18179 ) -> Vec<Task<()>> {
18180 let mut tasks = Vec::new();
18181 for me in 0..2 {
18182 let sched = Arc::clone(executor);
18183 let (runnable, task) = async_task::spawn(
18184 PingPong {
18185 shared: Arc::clone(shared),
18186 me,
18187 },
18188 move |r| sched.schedule(r),
18189 );
18190 executor.schedule(runnable);
18191 tasks.push(task);
18192 }
18193 tasks
18194 }
18195
18196 #[cfg(not(target_family = "wasm"))]
18197 #[test]
18198 fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
18199 use std::sync::mpsc;
18206 use std::time::Duration;
18207
18208 let (done_tx, done_rx) = mpsc::channel();
18209 let runner = std::thread::spawn(move || {
18210 let options = RuntimeOptions {
18211 flavor: RuntimeFlavor::MultiThread,
18212 worker_threads: 1,
18213 max_blocking_tasks: 0,
18214 thread_name_prefix: "lifo-streak".to_string(),
18215 park_deadline: None,
18216 drain_linger: DEFAULT_DRAIN_LINGER,
18217 };
18218 let executor =
18219 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18220
18221 let shared = Arc::new(PingPongShared {
18222 count: AtomicU64::new(0),
18223 stop: AtomicBool::new(false),
18224 stop_after: u64::MAX,
18225 wakers: Mutex::new([None, None]),
18226 });
18227 let pair = spawn_ping_pong_pair(&executor, &shared);
18228
18229 let (t3_tx, t3_rx) = mpsc::channel::<()>();
18231 let sched = Arc::clone(&executor);
18232 let (t3_runnable, t3_task) = async_task::spawn(
18233 async move {
18234 t3_tx.send(()).unwrap();
18235 },
18236 move |r| sched.schedule(r),
18237 );
18238 executor.schedule(t3_runnable);
18239
18240 t3_rx
18241 .recv_timeout(Duration::from_secs(10))
18242 .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
18243 assert!(
18244 shared.count.load(Ordering::SeqCst) >= 32,
18245 "the pair must have been bouncing through the slot before T3 got its turn"
18246 );
18247
18248 shared.stop.store(true, Ordering::SeqCst);
18250 let stored: Vec<_> = shared
18251 .wakers
18252 .lock()
18253 .unwrap()
18254 .iter_mut()
18255 .filter_map(Option::take)
18256 .collect();
18257 for waker in stored {
18258 waker.wake();
18259 }
18260 for task in pair {
18261 futures::executor::block_on(task);
18262 }
18263 futures::executor::block_on(t3_task);
18264 let _ = done_tx.send(());
18265 });
18266
18267 match done_rx.recv_timeout(Duration::from_secs(15)) {
18268 Ok(()) => runner.join().unwrap(),
18269 Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
18270 }
18271 }
18272
18273 #[cfg(not(target_family = "wasm"))]
18274 #[test]
18275 fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
18276 use std::sync::mpsc;
18283 use std::time::Duration;
18284
18285 const BOUNCES: u64 = 2_000; let (done_tx, done_rx) = mpsc::channel();
18288 let runner = std::thread::spawn(move || {
18289 let options = RuntimeOptions {
18290 flavor: RuntimeFlavor::MultiThread,
18291 worker_threads: 1,
18292 max_blocking_tasks: 1,
18293 thread_name_prefix: "lifo-blocking".to_string(),
18294 park_deadline: None,
18295 drain_linger: DEFAULT_DRAIN_LINGER,
18296 };
18297 let executor =
18298 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18299
18300 let shared = Arc::new(PingPongShared {
18301 count: AtomicU64::new(0),
18302 stop: AtomicBool::new(false),
18303 stop_after: BOUNCES,
18304 wakers: Mutex::new([None, None]),
18305 });
18306 let pair = spawn_ping_pong_pair(&executor, &shared);
18307
18308 let blocking = executor.schedule_blocking(|| 11usize);
18310
18311 assert_eq!(
18312 futures::executor::block_on(blocking).unwrap(),
18313 11,
18314 "the queued blocking job must complete despite the hot LIFO pair"
18315 );
18316 for task in pair {
18317 futures::executor::block_on(task);
18318 }
18319 assert!(
18320 shared.count.load(Ordering::SeqCst) >= BOUNCES,
18321 "the pair must have actually run its full bounce quota"
18322 );
18323 let _ = done_tx.send(());
18324 });
18325
18326 match done_rx.recv_timeout(Duration::from_secs(15)) {
18327 Ok(()) => runner.join().unwrap(),
18328 Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
18329 }
18330 }
18331
18332 #[cfg(not(target_family = "wasm"))]
18333 #[test]
18334 fn infinite_hot_runnable_yields_to_blocking_fifo() {
18335 use std::sync::mpsc;
18336
18337 struct HotFuture {
18338 stop: Arc<AtomicBool>,
18339 }
18340
18341 impl Future for HotFuture {
18342 type Output = ();
18343
18344 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18345 if self.stop.load(Ordering::SeqCst) {
18346 Poll::Ready(())
18347 } else {
18348 cx.waker().wake_by_ref();
18349 Poll::Pending
18350 }
18351 }
18352 }
18353
18354 let options = RuntimeOptions {
18355 flavor: RuntimeFlavor::MultiThread,
18356 worker_threads: 2,
18357 max_blocking_tasks: 1,
18358 thread_name_prefix: "blocking-fairness".to_string(),
18359 park_deadline: None,
18360 drain_linger: DEFAULT_DRAIN_LINGER,
18361 };
18362 let executor =
18363 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18364 let stop = Arc::new(AtomicBool::new(false));
18365 let scheduler = Arc::clone(&executor);
18366 let (runnable, task) = async_task::spawn(
18367 HotFuture {
18368 stop: Arc::clone(&stop),
18369 },
18370 move |r| scheduler.schedule(r),
18371 );
18372 task.detach();
18373 let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
18374 executor
18375 .blocking_queue
18376 .lock()
18377 .unwrap()
18378 .push(QueuedBlocking {
18379 id: BlockingJobId(u64::MAX),
18380 run: Box::new(move || blocking_tx.send(()).unwrap()),
18381 });
18382
18383 let _driver = OnPoolWorkerGuard::enter(executor.id);
18384 executor.schedule(runnable);
18385 let mut runnable_streak = 0;
18386 for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
18387 assert!(executor.run_one_fair(&mut runnable_streak));
18388 }
18389 blocking_rx
18390 .try_recv()
18391 .expect("an infinite self-waker must yield a bounded quantum to blocking work");
18392
18393 stop.store(true, Ordering::SeqCst);
18394 assert!(executor.run_one_fair(&mut runnable_streak));
18395 }
18396
18397 #[cfg(not(target_family = "wasm"))]
18398 #[test]
18399 fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
18400 use std::sync::mpsc;
18401
18402 struct HotFuture {
18403 stop: Arc<AtomicBool>,
18404 }
18405
18406 impl Future for HotFuture {
18407 type Output = ();
18408
18409 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18410 if self.stop.load(Ordering::SeqCst) {
18411 Poll::Ready(())
18412 } else {
18413 cx.waker().wake_by_ref();
18414 Poll::Pending
18415 }
18416 }
18417 }
18418
18419 let options = RuntimeOptions {
18420 flavor: RuntimeFlavor::MultiThread,
18421 worker_threads: 1,
18422 max_blocking_tasks: 1,
18423 thread_name_prefix: "exact-dependency-fairness".to_string(),
18424 park_deadline: None,
18425 drain_linger: DEFAULT_DRAIN_LINGER,
18426 };
18427 let executor =
18428 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18429 let stop = Arc::new(AtomicBool::new(false));
18430 let (queued_tx, queued_rx) = mpsc::channel();
18431 let (exact_tx, exact_rx) = mpsc::channel();
18432 let owner_executor = Arc::clone(&executor);
18433 let owner_stop = Arc::clone(&stop);
18434 let owner = executor.schedule_blocking(move || {
18435 let scheduler = Arc::clone(&owner_executor);
18436 let hot_stop = Arc::clone(&owner_stop);
18437 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
18438 scheduler.schedule(runnable);
18439 });
18440 owner_executor.schedule(runnable);
18441
18442 let exact_stop = Arc::clone(&owner_stop);
18443 let exact = owner_executor.schedule_blocking(move || {
18444 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
18445 41usize
18446 });
18447 queued_tx.send(()).unwrap();
18448
18449 let mut value = None;
18450 {
18451 let mut future = std::pin::pin!(async {
18452 value = Some(exact.await.unwrap());
18453 hot_task.await;
18454 });
18455 assert_eq!(
18456 owner_executor.block_on(future.as_mut()),
18457 BlockOnOutcome::Completed
18458 );
18459 }
18460 value.unwrap()
18461 });
18462
18463 queued_rx
18464 .recv_timeout(Duration::from_secs(2))
18465 .expect("the exact dependency must be queued behind the saturated owner");
18466 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
18467 stop.store(true, Ordering::SeqCst);
18468 assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
18469 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
18470 assert_eq!(
18471 exact_before_stop,
18472 Some(true),
18473 "a hot local runnable must yield to the exact dependency before it is stopped"
18474 );
18475 }
18476
18477 #[cfg(not(target_family = "wasm"))]
18478 #[test]
18479 fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
18480 use std::sync::mpsc;
18481
18482 struct BlockingPoll {
18483 started: Option<mpsc::Sender<()>>,
18484 released: Arc<(Mutex<bool>, Condvar)>,
18485 }
18486
18487 impl Future for BlockingPoll {
18488 type Output = ();
18489
18490 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18491 if let Some(started) = self.started.take() {
18492 started.send(()).unwrap();
18493 }
18494 let (released, changed) = &*self.released;
18495 let mut released = released
18496 .lock()
18497 .unwrap_or_else(std::sync::PoisonError::into_inner);
18498 while !*released {
18499 released = changed
18500 .wait(released)
18501 .unwrap_or_else(std::sync::PoisonError::into_inner);
18502 }
18503 Poll::Ready(())
18504 }
18505 }
18506
18507 let options = RuntimeOptions {
18508 flavor: RuntimeFlavor::MultiThread,
18509 worker_threads: 3,
18510 max_blocking_tasks: 2,
18511 thread_name_prefix: "exact-before-last-slot".to_string(),
18512 park_deadline: None,
18513 drain_linger: DEFAULT_DRAIN_LINGER,
18514 };
18515 let executor =
18516 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18517
18518 let released = Arc::new((Mutex::new(false), Condvar::new()));
18519 let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
18520 let mut blockers = Vec::new();
18521 for _ in 0..2 {
18522 let scheduler = Arc::clone(&executor);
18523 let blocker_released = Arc::clone(&released);
18524 let started = blocker_started_tx.clone();
18525 let (runnable, task) = async_task::spawn(
18526 BlockingPoll {
18527 started: Some(started),
18528 released: blocker_released,
18529 },
18530 move |runnable| scheduler.schedule(runnable),
18531 );
18532 executor.schedule(runnable);
18533 blockers.push(task);
18534 }
18535 drop(blocker_started_tx);
18536 for _ in 0..2 {
18537 blocker_started_rx
18538 .recv_timeout(Duration::from_secs(2))
18539 .expect("two workers must be gated before the owner starts");
18540 }
18541
18542 let dependency_started = Arc::new(AtomicBool::new(false));
18543 let owner_executor = Arc::clone(&executor);
18544 let owner_dependency_started = Arc::clone(&dependency_started);
18545 let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
18546 let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
18547 let owner_signal = Arc::clone(&dependency_signal);
18548 let owner = executor.schedule_blocking(move || {
18549 let unrelated_signal = Arc::clone(&owner_signal);
18550 let unrelated_started = Arc::clone(&owner_dependency_started);
18551 let unrelated = owner_executor.schedule_blocking(move || {
18552 let observed = unrelated_started.load(Ordering::SeqCst);
18553 unrelated_observed_tx.send(observed).unwrap();
18554 let (ready, changed) = &*unrelated_signal;
18555 let mut ready = ready
18556 .lock()
18557 .unwrap_or_else(std::sync::PoisonError::into_inner);
18558 while !*ready {
18559 ready = changed
18560 .wait(ready)
18561 .unwrap_or_else(std::sync::PoisonError::into_inner);
18562 }
18563 });
18564
18565 let exact_signal = Arc::clone(&owner_signal);
18566 let exact_started = Arc::clone(&owner_dependency_started);
18567 let exact = owner_executor.schedule_blocking(move || {
18568 exact_started.store(true, Ordering::SeqCst);
18569 let (ready, changed) = &*exact_signal;
18570 *ready
18571 .lock()
18572 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18573 changed.notify_all();
18574 41usize
18575 });
18576
18577 let mut output = None;
18578 {
18579 let mut future = std::pin::pin!(async {
18580 output = Some(exact.await.unwrap());
18581 unrelated.await.unwrap();
18582 });
18583 assert_eq!(
18584 owner_executor.block_on(future.as_mut()),
18585 BlockOnOutcome::Completed
18586 );
18587 }
18588 output.unwrap()
18589 });
18590
18591 let (owner_done_tx, owner_done_rx) = mpsc::channel();
18592 let owner_waiter = std::thread::spawn(move || {
18593 owner_done_tx
18594 .send(futures::executor::block_on(owner))
18595 .unwrap();
18596 });
18597 let (completed_while_gated, owner_result) =
18598 match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
18599 Ok(result) => (true, result),
18600 Err(_) => {
18601 let (released, changed) = &*released;
18604 *released
18605 .lock()
18606 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18607 changed.notify_all();
18608 (
18609 false,
18610 owner_done_rx
18611 .recv_timeout(Duration::from_secs(2))
18612 .expect("releasing the other workers must let the broken ordering recover"),
18613 )
18614 }
18615 };
18616 let unrelated_observed = unrelated_observed_rx
18617 .recv_timeout(Duration::from_secs(2))
18618 .expect("the unrelated FIFO job must eventually run");
18619
18620 {
18621 let (released, changed) = &*released;
18622 *released
18623 .lock()
18624 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18625 changed.notify_all();
18626 }
18627 owner_waiter.join().unwrap();
18628 for blocker in blockers {
18629 futures::executor::block_on(blocker);
18630 }
18631 executor.begin_shutdown();
18632 executor.wait_until_scheduler_idle();
18633
18634 assert!(
18635 completed_while_gated,
18636 "the exact dependency must complete while unrelated workers remain gated"
18637 );
18638 assert_eq!(owner_result.unwrap(), 41);
18639 assert!(
18640 unrelated_observed,
18641 "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
18642 );
18643 }
18644
18645 #[cfg(not(target_family = "wasm"))]
18646 #[test]
18647 fn blocking_saturation_preserves_a_runnable_execution_lane() {
18648 use std::{sync::mpsc, time::Duration};
18649
18650 let options = RuntimeOptions {
18651 flavor: RuntimeFlavor::MultiThread,
18652 worker_threads: 2,
18653 max_blocking_tasks: 2,
18654 thread_name_prefix: "blocking-reserve".to_string(),
18655 park_deadline: None,
18656 drain_linger: DEFAULT_DRAIN_LINGER,
18657 }
18658 .validate()
18659 .expect("production options must reserve a runnable lane");
18660 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
18661 let executor =
18662 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18663 let (release_tx, release_rx) = mpsc::channel();
18664 let (started_tx, started_rx) = mpsc::channel();
18665 let first = executor.schedule_blocking(move || {
18666 started_tx.send(()).unwrap();
18667 release_rx.recv().unwrap();
18668 });
18669 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
18670
18671 let (queued_tx, queued_rx) = mpsc::channel();
18672 let second = executor.schedule_blocking(move || {
18673 queued_tx.send(()).unwrap();
18674 });
18675 assert!(
18676 queued_rx.try_recv().is_err(),
18677 "the second blocking job must remain queued while the sole blocking slot is occupied"
18678 );
18679
18680 let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
18681 let scheduler = Arc::clone(&executor);
18682 let (runnable, task) = async_task::spawn(
18683 async move {
18684 runnable_tx.send(()).unwrap();
18685 },
18686 move |r| scheduler.schedule(r),
18687 );
18688 task.detach();
18689 executor.schedule(runnable);
18690 runnable_rx
18691 .recv_timeout(Duration::from_secs(1))
18692 .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
18693
18694 release_tx.send(()).unwrap();
18695 futures::executor::block_on(first).unwrap();
18696 futures::executor::block_on(second).unwrap();
18697 queued_rx
18698 .recv_timeout(Duration::from_secs(1))
18699 .expect("queued blocking work must run after the occupied slot is released");
18700 }
18701
18702 #[cfg(not(target_family = "wasm"))]
18703 #[test]
18704 fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
18705 use std::sync::mpsc;
18713 use std::time::Duration;
18714
18715 struct SpawnOnReady {
18716 executor: Arc<MultiThreadExecutor>,
18717 child_tx: Option<mpsc::Sender<u64>>,
18718 }
18719 impl Future for SpawnOnReady {
18720 type Output = ();
18721 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18722 let child_tx = self.child_tx.take().expect("polled once");
18723 let sched = Arc::clone(&self.executor);
18724 let (runnable, task) = async_task::spawn(
18725 async move {
18726 child_tx.send(21).unwrap();
18727 },
18728 move |r| sched.schedule(r),
18729 );
18730 self.executor.schedule(runnable);
18732 task.detach();
18733 Poll::Ready(())
18734 }
18735 }
18736
18737 let (done_tx, done_rx) = mpsc::channel();
18738 let runner = std::thread::spawn(move || {
18739 let options = RuntimeOptions {
18740 flavor: RuntimeFlavor::MultiThread,
18741 worker_threads: 2,
18742 max_blocking_tasks: 2,
18743 thread_name_prefix: "exit-flush".to_string(),
18744 park_deadline: None,
18745 drain_linger: DEFAULT_DRAIN_LINGER,
18746 };
18747 let executor =
18748 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18749
18750 let (child_tx, child_rx) = mpsc::channel::<u64>();
18751 {
18752 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18755 let mut future = std::pin::pin!(SpawnOnReady {
18756 executor: Arc::clone(&executor),
18757 child_tx: Some(child_tx)
18758 });
18759 executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
18760 }
18761
18762 let value = child_rx
18766 .recv_timeout(Duration::from_secs(5))
18767 .expect("the child scheduled by the final poll was stranded in the LIFO slot");
18768 assert_eq!(value, 21);
18769 let _ = done_tx.send(());
18770 });
18771
18772 match done_rx.recv_timeout(Duration::from_secs(10)) {
18773 Ok(()) => runner.join().unwrap(),
18774 Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
18775 }
18776 }
18777
18778 #[cfg(not(target_family = "wasm"))]
18782 fn spawn_hot_chain(
18783 executor: &Arc<MultiThreadExecutor>,
18784 stop: Arc<AtomicBool>,
18785 hops: Arc<AtomicU64>,
18786 ) {
18787 if stop.load(Ordering::SeqCst) {
18788 return;
18789 }
18790 let link_exec = Arc::clone(executor);
18791 let body = async move {
18792 hops.fetch_add(1, Ordering::SeqCst);
18793 spawn_hot_chain(&link_exec, stop, hops);
18794 };
18795 let sched = Arc::clone(executor);
18796 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
18797 executor.schedule(runnable);
18798 task.detach();
18799 }
18800
18801 #[cfg(not(target_family = "wasm"))]
18802 #[test]
18803 fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
18804 use std::sync::mpsc;
18813 use std::time::Duration;
18814
18815 let (done_tx, done_rx) = mpsc::channel();
18816 let runner = std::thread::spawn(move || {
18817 let options = RuntimeOptions {
18818 flavor: RuntimeFlavor::MultiThread,
18819 worker_threads: 1,
18820 max_blocking_tasks: 0,
18823 thread_name_prefix: "coop-budget".to_string(),
18824 park_deadline: None,
18825 drain_linger: DEFAULT_DRAIN_LINGER,
18826 };
18827 let executor =
18828 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18829
18830 let stop = Arc::new(AtomicBool::new(false));
18831 let hops = Arc::new(AtomicU64::new(0));
18832
18833 let m_exec = Arc::clone(&executor);
18837 let m_stop = Arc::clone(&stop);
18838 let m_hops = Arc::clone(&hops);
18839 let m_body = async move {
18840 let (tx, rx) = oneshot::channel::<u64>();
18841 let sched_f = Arc::clone(&m_exec);
18843 let (f_runnable, f_task) = async_task::spawn(
18844 async move {
18845 let _ = tx.send(7);
18846 },
18847 move |r| sched_f.schedule(r),
18848 );
18849 m_exec.schedule(f_runnable);
18850 f_task.detach();
18851 spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
18854
18855 let mut inner = std::pin::pin!(async move {
18856 assert_eq!(
18857 rx.await.unwrap(),
18858 7,
18859 "F must run and complete the awaited future"
18860 );
18861 });
18862 m_exec.block_on(inner.as_mut());
18863 m_stop.store(true, Ordering::SeqCst);
18864 };
18865 let sched_m = Arc::clone(&executor);
18866 let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
18867 executor.schedule(m_runnable);
18868
18869 futures::executor::block_on(m_task);
18870 assert!(
18871 hops.load(Ordering::SeqCst) >= 64,
18872 "the chain must have been genuinely hot (>= one budget window) before F ran"
18873 );
18874 let _ = done_tx.send(());
18875 });
18876
18877 match done_rx.recv_timeout(Duration::from_secs(10)) {
18878 Ok(()) => runner.join().unwrap(),
18879 Err(error) => panic!(
18880 "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
18881 ),
18882 }
18883 }
18884
18885 #[cfg(not(target_family = "wasm"))]
18886 #[test]
18887 fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
18888 use std::sync::mpsc;
18889
18890 struct RefillLifo {
18891 executor: Arc<MultiThreadExecutor>,
18892 fifo_ran: Arc<AtomicBool>,
18893 }
18894
18895 impl Future for RefillLifo {
18896 type Output = ();
18897
18898 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18899 if self.fifo_ran.load(Ordering::SeqCst) {
18900 return Poll::Ready(());
18901 }
18902 let scheduler = Arc::clone(&self.executor);
18903 let (runnable, task) =
18904 async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
18905 self.executor.schedule(runnable);
18906 task.detach();
18907 Poll::Pending
18908 }
18909 }
18910
18911 let (done_tx, done_rx) = mpsc::channel();
18912 let runner = std::thread::spawn(move || {
18913 let options = RuntimeOptions {
18914 flavor: RuntimeFlavor::MultiThread,
18915 worker_threads: 1,
18916 max_blocking_tasks: 0,
18917 thread_name_prefix: "forced-fifo".to_string(),
18918 park_deadline: None,
18919 drain_linger: DEFAULT_DRAIN_LINGER,
18920 };
18921 let executor =
18922 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18923 executor
18924 .active_drainers
18925 .store(executor.max_drainers, Ordering::SeqCst);
18926
18927 let fifo_ran = Arc::new(AtomicBool::new(false));
18928 let fifo_flag = Arc::clone(&fifo_ran);
18929 let scheduler = Arc::clone(&executor);
18930 let (fifo, task) = async_task::spawn(
18931 async move {
18932 fifo_flag.store(true, Ordering::SeqCst);
18933 },
18934 move |runnable| scheduler.schedule(runnable),
18935 );
18936 task.detach();
18937 executor.metrics.runnable_scheduled();
18938 executor.push_fifo_raw(fifo);
18939
18940 {
18941 let _driver = OnPoolWorkerGuard::enter(executor.id);
18942 let mut future = std::pin::pin!(RefillLifo {
18943 executor: Arc::clone(&executor),
18944 fifo_ran: Arc::clone(&fifo_ran)
18945 });
18946 executor.block_on(future.as_mut());
18947 while executor.run_one() {}
18948 }
18949 executor.active_drainers.store(0, Ordering::SeqCst);
18950 assert!(fifo_ran.load(Ordering::SeqCst));
18951 done_tx.send(()).unwrap();
18952 });
18953 done_rx
18954 .recv_timeout(Duration::from_secs(3))
18955 .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
18956 runner.join().unwrap();
18957 }
18958
18959 #[cfg(not(target_family = "wasm"))]
18960 #[test]
18961 fn blocking_closure_spawn_is_not_stranded_in_slot() {
18962 use std::sync::mpsc;
18969 use std::time::Duration;
18970
18971 let (done_tx, done_rx) = mpsc::channel();
18972 let runner = std::thread::spawn(move || {
18973 let options = RuntimeOptions {
18974 flavor: RuntimeFlavor::MultiThread,
18975 worker_threads: 2,
18976 max_blocking_tasks: 2,
18977 thread_name_prefix: "blk-spawn-wait".to_string(),
18978 park_deadline: None,
18979 drain_linger: DEFAULT_DRAIN_LINGER,
18980 };
18981 let executor =
18982 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18983
18984 let blk_exec = Arc::clone(&executor);
18987 let handle = executor.schedule_blocking(move || {
18988 let (tx, rx) = mpsc::channel::<u64>();
18989 let sched = Arc::clone(&blk_exec);
18990 let (runnable, task) = async_task::spawn(
18991 async move {
18992 tx.send(21).unwrap();
18993 },
18994 move |r| sched.schedule(r),
18995 );
18996 blk_exec.schedule(runnable);
18997 task.detach();
18998 rx.recv_timeout(Duration::from_secs(5))
18999 .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
19000 });
19001 assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
19002
19003 let (v_tx, v_rx) = mpsc::channel::<u64>();
19006 let v_exec = Arc::clone(&executor);
19007 let v_handle = executor.schedule_blocking(move || {
19008 let sched = Arc::clone(&v_exec);
19009 let (runnable, task) = async_task::spawn(
19010 async move {
19011 v_tx.send(42).unwrap();
19012 },
19013 move |r| sched.schedule(r),
19014 );
19015 v_exec.schedule(runnable);
19016 task.detach();
19017 1u64
19018 });
19019 assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
19020 assert_eq!(
19021 v_rx
19022 .recv_timeout(Duration::from_secs(5))
19023 .expect("fire-and-forget child must complete"),
19024 42
19025 );
19026 let _ = done_tx.send(());
19027 });
19028
19029 match done_rx.recv_timeout(Duration::from_secs(15)) {
19030 Ok(()) => runner.join().unwrap(),
19031 Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
19032 }
19033 }
19034
19035 #[cfg(not(target_family = "wasm"))]
19036 #[test]
19037 fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
19038 use std::sync::{Barrier, mpsc};
19044 use std::time::Duration;
19045
19046 let (done_tx, done_rx) = mpsc::channel();
19047 let runner = std::thread::spawn(move || {
19048 let metrics = Arc::new(RuntimeMetrics::default());
19049 let options = RuntimeOptions {
19050 flavor: RuntimeFlavor::MultiThread,
19051 worker_threads: 2,
19052 max_blocking_tasks: 2,
19053 thread_name_prefix: "rd1-nested-blk".to_string(),
19054 park_deadline: None,
19055 drain_linger: DEFAULT_DRAIN_LINGER,
19056 };
19057 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19058
19059 let outer_count = 2usize; let barrier = Arc::new(Barrier::new(outer_count));
19061
19062 let mut handles = Vec::new();
19063 for _ in 0..outer_count {
19064 let exec = Arc::clone(&executor);
19065 let barrier = Arc::clone(&barrier);
19066 let handle = executor.schedule_blocking(move || {
19067 barrier.wait();
19069 let inner_exec = Arc::clone(&exec);
19070 let mut value = 0usize;
19071 {
19072 let mut inner = std::pin::pin!(async {
19073 value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19074 });
19075 exec.block_on(inner.as_mut());
19076 }
19077 value
19078 });
19079 handles.push(handle);
19080 }
19081
19082 for handle in handles {
19083 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
19084 }
19085 let _ = done_tx.send(());
19086 });
19087
19088 match done_rx.recv_timeout(Duration::from_secs(10)) {
19089 Ok(()) => runner.join().unwrap(),
19090 Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
19091 }
19092 }
19093
19094 #[cfg(not(target_family = "wasm"))]
19095 #[test]
19096 fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
19097 let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
19098 let owner = executor.fresh_owner_token();
19099 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19100 let job = BlockingJobId(u64::MAX - 5);
19101 let dependency = TaskDependency::default();
19102 dependency.set_owned(BlockingDependency { job, owner: None });
19103 let ran = Arc::new(AtomicBool::new(false));
19104 let job_ran = Arc::clone(&ran);
19105 executor
19106 .blocking_queue
19107 .lock()
19108 .unwrap()
19109 .push(QueuedBlocking {
19110 id: job,
19111 run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
19112 });
19113
19114 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19115 assert!(
19116 !executor.try_owned_blocking_over_cap(&dependency),
19117 "an untagged worker must not infer ancestry from a sole active owner"
19118 );
19119 assert!(!ran.load(Ordering::SeqCst));
19120 assert!(
19121 executor
19122 .blocking_queue
19123 .lock()
19124 .unwrap()
19125 .jobs
19126 .contains_key(&job),
19127 "rejected owner inference must leave the exact job queued"
19128 );
19129 }
19130
19131 #[cfg(not(target_family = "wasm"))]
19132 #[test]
19133 fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
19134 use std::sync::{Barrier, mpsc};
19135
19136 let options = RuntimeOptions {
19137 flavor: RuntimeFlavor::MultiThread,
19138 worker_threads: 2,
19139 max_blocking_tasks: 2,
19140 thread_name_prefix: "exact-owner-frame".to_string(),
19141 park_deadline: None,
19142 drain_linger: DEFAULT_DRAIN_LINGER,
19143 };
19144 let executor =
19145 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19146 let owners_saturated = Arc::new(Barrier::new(3));
19147 let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
19148 let begin_lending = Arc::new(Barrier::new(3));
19149 let release_dependencies = Arc::new(Barrier::new(3));
19150 let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
19151 let mut owners = Vec::new();
19152
19153 for owner_index in 0..2 {
19154 let owner_executor = Arc::clone(&executor);
19155 let owners_saturated = Arc::clone(&owners_saturated);
19156 let dependency_queued_tx = dependency_queued_tx.clone();
19157 let begin_lending = Arc::clone(&begin_lending);
19158 let release_dependencies = Arc::clone(&release_dependencies);
19159 let dependency_started_tx = dependency_started_tx.clone();
19160 owners.push(executor.schedule_blocking(move || {
19161 let owner_thread = std::thread::current().id();
19162 owners_saturated.wait();
19163 let child_started = dependency_started_tx.clone();
19164 let child_release = Arc::clone(&release_dependencies);
19165 let child = owner_executor.schedule_blocking(move || {
19166 assert_eq!(
19167 std::thread::current().id(),
19168 owner_thread,
19169 "an owner frame must execute only its own exact dependency on its lent lane"
19170 );
19171 child_started.send(owner_index).unwrap();
19172 child_release.wait();
19173 owner_index
19174 });
19175 dependency_queued_tx.send(()).unwrap();
19176 begin_lending.wait();
19177
19178 let mut result = None;
19179 {
19180 let mut future = std::pin::pin!(async {
19181 result = Some(child.await.unwrap());
19182 });
19183 assert_eq!(
19184 owner_executor.block_on(future.as_mut()),
19185 BlockOnOutcome::Completed
19186 );
19187 }
19188 result.unwrap()
19189 }));
19190 }
19191 drop(dependency_queued_tx);
19192 drop(dependency_started_tx);
19193 owners_saturated.wait();
19194
19195 for _ in 0..2 {
19196 dependency_queued_rx
19197 .recv_timeout(Duration::from_secs(2))
19198 .expect("both owner-scoped dependencies must be queued while the cap is saturated");
19199 }
19200 let (unrelated_tx, unrelated_rx) = mpsc::channel();
19201 let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
19202 begin_lending.wait();
19203
19204 let mut started = Vec::new();
19205 for _ in 0..2 {
19206 started.push(
19207 dependency_started_rx
19208 .recv_timeout(Duration::from_secs(2))
19209 .expect("each owner must service its own exact dependency"),
19210 );
19211 }
19212 started.sort_unstable();
19213 assert_eq!(started, vec![0, 1]);
19214 assert!(
19215 unrelated_rx.try_recv().is_err(),
19216 "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
19217 );
19218
19219 release_dependencies.wait();
19220 for (owner_index, owner) in owners.into_iter().enumerate() {
19221 assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
19222 }
19223 unrelated_rx
19224 .recv_timeout(Duration::from_secs(2))
19225 .expect("the unrelated job must run after an owner releases a counted lane");
19226 futures::executor::block_on(unrelated).unwrap();
19227 }
19228
19229 #[cfg(not(target_family = "wasm"))]
19230 #[test]
19231 fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
19232 use std::sync::{Mutex, mpsc};
19233
19234 const DEPENDENCY_COUNT: usize = 4;
19235
19236 let options = RuntimeOptions {
19237 flavor: RuntimeFlavor::MultiThread,
19238 worker_threads: 3,
19239 max_blocking_tasks: 1,
19240 thread_name_prefix: "parked-unrelated-worker".to_string(),
19241 park_deadline: None,
19242 drain_linger: DEFAULT_DRAIN_LINGER,
19243 };
19244 let executor =
19245 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19246 let (parked_tx, parked_rx) = mpsc::channel();
19247 let (release_parked_tx, release_parked_rx) = mpsc::channel();
19248 executor.pool.spawn(move || {
19249 parked_tx.send(()).unwrap();
19250 release_parked_rx.recv().unwrap();
19251 });
19252 parked_rx
19253 .recv_timeout(Duration::from_secs(2))
19254 .expect("the unrelated Rayon worker must remain occupied");
19255
19256 let owner_executor = Arc::clone(&executor);
19257 let owner = executor.schedule_blocking(move || {
19258 let owner = BLOCKING_OWNER
19259 .with(std::cell::Cell::get)
19260 .expect("the blocking closure must carry its exact owner frame");
19261 let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
19262 let results = Arc::new(Mutex::new(Vec::new()));
19263 let descendant_results = Arc::clone(&results);
19264 let descendant_executor = Arc::clone(&owner_executor);
19265 rayon::scope(move |scope| {
19266 scope.spawn(move |_| {
19267 assert_eq!(
19268 BLOCKING_OWNER.with(std::cell::Cell::get),
19269 None,
19270 "the parked owner must force its descendant onto the remaining worker"
19271 );
19272 let _owner = BlockingOwnerGuard::enter(Some(owner));
19273 descendant_started_tx.send(()).unwrap();
19274 for value in 0..DEPENDENCY_COUNT {
19275 let handle = descendant_executor.schedule_blocking(move || value);
19276 let mut result = None;
19277 {
19278 let mut future = std::pin::pin!(async {
19279 result = Some(handle.await.unwrap());
19280 });
19281 assert_eq!(
19282 descendant_executor.block_on(future.as_mut()),
19283 BlockOnOutcome::Completed
19284 );
19285 }
19286 descendant_results.lock().unwrap().push(result.unwrap());
19287 }
19288 });
19289 descendant_started_rx
19290 .recv_timeout(Duration::from_secs(2))
19291 .expect("the nested descendant must start on the only unoccupied worker");
19292 });
19293 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19294 });
19295
19296 assert_eq!(
19297 futures::executor::block_on(owner).unwrap(),
19298 (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
19299 );
19300
19301 let (idle_tx, idle_rx) = mpsc::channel();
19302 let idle_executor = Arc::clone(&executor);
19303 let idle_waiter = std::thread::spawn(move || {
19304 idle_executor.begin_shutdown();
19305 idle_executor.wait_until_scheduler_idle();
19306 idle_tx.send(()).unwrap();
19307 });
19308 idle_rx
19309 .recv_timeout(Duration::from_secs(2))
19310 .expect("an unrelated parked worker must not retain owner-probe idle accounting");
19311
19312 release_parked_tx.send(()).unwrap();
19313 idle_waiter.join().unwrap();
19314 }
19315
19316 #[cfg(not(target_family = "wasm"))]
19317 #[test]
19318 fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
19319 use std::sync::{Condvar, Mutex, mpsc};
19320
19321 let options = RuntimeOptions {
19322 flavor: RuntimeFlavor::MultiThread,
19323 worker_threads: 3,
19324 max_blocking_tasks: 1,
19325 thread_name_prefix: "same-owner-rearm".to_string(),
19326 park_deadline: None,
19327 drain_linger: DEFAULT_DRAIN_LINGER,
19328 };
19329 let executor =
19330 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19331 let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
19332 let (job_started_tx, job_started_rx) = mpsc::channel();
19333 let owner_executor = Arc::clone(&executor);
19334 let owner_releases = Arc::clone(&releases);
19335 let owner = executor.schedule_blocking(move || {
19336 let owner = BLOCKING_OWNER
19337 .with(std::cell::Cell::get)
19338 .expect("the blocking closure must carry its exact owner frame");
19339 let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
19340 let results = Arc::new(Mutex::new(Vec::new()));
19341 let scope_results = Arc::clone(&results);
19342 rayon::scope(move |scope| {
19343 for index in 0..2 {
19344 let descendant_ready_tx = descendant_ready_tx.clone();
19345 let descendant_executor = Arc::clone(&owner_executor);
19346 let descendant_results = Arc::clone(&scope_results);
19347 let job_started_tx = job_started_tx.clone();
19348 let releases = Arc::clone(&owner_releases);
19349 scope.spawn(move |_| {
19350 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19351 let _owner = BlockingOwnerGuard::enter(Some(owner));
19352 descendant_ready_tx.send(()).unwrap();
19353 let handle = descendant_executor.schedule_blocking(move || {
19354 job_started_tx.send(index).unwrap();
19355 let (released, changed) = &*releases;
19356 let mut state = released.lock().unwrap();
19357 while !state[index] {
19358 state = changed.wait(state).unwrap();
19359 }
19360 index
19361 });
19362 let mut result = None;
19363 {
19364 let mut future = std::pin::pin!(async {
19365 result = Some(handle.await.unwrap());
19366 });
19367 assert_eq!(
19368 descendant_executor.block_on(future.as_mut()),
19369 BlockOnOutcome::Completed
19370 );
19371 }
19372 descendant_results.lock().unwrap().push(result.unwrap());
19373 });
19374 }
19375 drop(descendant_ready_tx);
19376 for _ in 0..2 {
19377 descendant_ready_rx
19378 .recv_timeout(Duration::from_secs(2))
19379 .expect("both descendants must be stolen before the owner leaves its Rayon scope");
19380 }
19381 });
19382 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19383 });
19384
19385 let first = job_started_rx
19386 .recv_timeout(Duration::from_secs(2))
19387 .expect("one exact dependency must borrow the owner lane");
19388 assert!(
19389 job_started_rx
19390 .recv_timeout(Duration::from_millis(100))
19391 .is_err(),
19392 "one owner frame must not service two dependencies concurrently"
19393 );
19394 let unrelated_parker = Arc::new(DriverParker::default());
19395 executor
19396 .parked_drivers
19397 .register_with_role(&unrelated_parker, true, None);
19398 {
19399 let (released, changed) = &*releases;
19400 released.lock().unwrap()[first] = true;
19401 changed.notify_all();
19402 }
19403
19404 let second = job_started_rx
19405 .recv_timeout(Duration::from_secs(2))
19406 .expect("completion must rearm one parked same-owner dependency");
19407 assert_ne!(second, first);
19408 assert!(
19409 !unrelated_parker.consume_permit(),
19410 "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
19411 );
19412 executor.parked_drivers.deregister(&unrelated_parker);
19413 {
19414 let (released, changed) = &*releases;
19415 released.lock().unwrap()[second] = true;
19416 changed.notify_all();
19417 }
19418
19419 let mut results = futures::executor::block_on(owner).unwrap();
19420 results.sort_unstable();
19421 assert_eq!(results, vec![0, 1]);
19422 }
19423
19424 #[cfg(not(target_family = "wasm"))]
19425 #[test]
19426 fn failed_exact_claim_rearms_parked_same_owner_dependency() {
19427 let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
19428 let owner = executor.fresh_owner_token();
19429 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19430 let failed_dependency = Arc::new(TaskDependency::default());
19431 failed_dependency.set_owned(BlockingDependency {
19432 job: BlockingJobId(u64::MAX - 21),
19433 owner: Some(owner),
19434 });
19435 let parked_dependency = Arc::new(TaskDependency::default());
19436 parked_dependency.set_owned(BlockingDependency {
19437 job: BlockingJobId(u64::MAX - 22),
19438 owner: Some(owner),
19439 });
19440 let parker = Arc::new(DriverParker::default());
19441
19442 let queue = executor.blocking_queue.lock().unwrap();
19443 let claimant_executor = Arc::clone(&executor);
19444 let claimant_dependency = Arc::clone(&failed_dependency);
19445 let claimant = std::thread::spawn(move || {
19446 let _owner = BlockingOwnerGuard::enter(Some(owner));
19447 claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
19448 });
19449 wait_until("the failed claimant to reserve the owner lane", || {
19450 !executor.active_blocking_owners.is_available(owner)
19451 });
19452 executor
19453 .parked_drivers
19454 .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
19455 failed_dependency.clear();
19456 drop(queue);
19457
19458 assert!(
19459 !claimant.join().unwrap(),
19460 "the cancelled dependency must not execute"
19461 );
19462 assert!(
19463 parker.consume_permit(),
19464 "releasing a failed exact claim must hand the owner lane to another parked dependency"
19465 );
19466 }
19467
19468 #[cfg(not(target_family = "wasm"))]
19469 #[test]
19470 fn exact_owner_lending_does_not_start_blocking_after_stop() {
19471 use std::sync::mpsc;
19472
19473 let options = RuntimeOptions {
19474 flavor: RuntimeFlavor::MultiThread,
19475 worker_threads: 2,
19476 max_blocking_tasks: 1,
19477 thread_name_prefix: "stopped-exact-owner".to_string(),
19478 park_deadline: None,
19479 drain_linger: DEFAULT_DRAIN_LINGER,
19480 };
19481 let executor =
19482 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19483 let ran = Arc::new(AtomicBool::new(false));
19484 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
19485 let (release_claim_tx, release_claim_rx) = mpsc::channel();
19486 let owner_executor = Arc::clone(&executor);
19487 let child_ran = Arc::clone(&ran);
19488 let owner = executor.schedule_blocking(move || {
19489 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
19490 *slot.borrow_mut() = Some(Box::new(move || {
19491 claim_entered_tx.send(()).unwrap();
19492 release_claim_rx.recv().unwrap();
19493 }));
19494 });
19495 let child = owner_executor.schedule_blocking(move || {
19496 child_ran.store(true, Ordering::SeqCst);
19497 });
19498 let mut future = std::pin::pin!(async move {
19499 let _ = child.await;
19500 });
19501 owner_executor.block_on(future.as_mut())
19502 });
19503
19504 claim_entered_rx
19505 .recv_timeout(Duration::from_secs(2))
19506 .expect("the cooperative driver must pause before the exact-owner queue claim");
19507 assert_eq!(
19508 executor.blocking_queue.lock().unwrap().jobs.len(),
19509 1,
19510 "the exact dependency must remain queued before stop publication"
19511 );
19512
19513 executor.stop.begin_shutdown();
19516 release_claim_tx.send(()).unwrap();
19517
19518 assert_eq!(
19519 futures::executor::block_on(owner).unwrap(),
19520 BlockOnOutcome::Stopped,
19521 "the cooperative driver must stop instead of executing its queued dependency"
19522 );
19523 assert!(
19524 !ran.load(Ordering::SeqCst),
19525 "exact-owner lending started blocking work after stop"
19526 );
19527 assert_eq!(
19528 executor.blocking_queue.lock().unwrap().jobs.len(),
19529 1,
19530 "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
19531 );
19532
19533 executor.begin_shutdown();
19534 executor.wait_until_scheduler_idle();
19535 assert!(executor.blocking_queue.lock().unwrap().is_empty());
19536 assert!(!ran.load(Ordering::SeqCst));
19537 }
19538
19539 #[cfg(not(target_family = "wasm"))]
19540 #[test]
19541 fn exact_owner_lending_attempts_scale_linearly_with_topology() {
19542 const DEPENDENCY_COUNT: usize = 1024;
19543
19544 let options = RuntimeOptions {
19545 flavor: RuntimeFlavor::MultiThread,
19546 worker_threads: 2,
19547 max_blocking_tasks: 1,
19548 thread_name_prefix: "linear-owner-lending".to_string(),
19549 park_deadline: None,
19550 drain_linger: DEFAULT_DRAIN_LINGER,
19551 };
19552 let executor =
19553 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19554 let completed = Arc::new(AtomicUsize::new(0));
19555
19556 for index in 0..DEPENDENCY_COUNT {
19557 let owner = executor.fresh_owner_token();
19558 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19559 let dependency = TaskDependency::new(u64::MAX, Some(owner));
19560 let blocking = BlockingDependency {
19561 job: BlockingJobId(u64::MAX - index as u64),
19562 owner: Some(owner),
19563 };
19564 dependency.set_owned(blocking);
19565 let completed = Arc::clone(&completed);
19566 executor
19567 .blocking_queue
19568 .lock()
19569 .unwrap()
19570 .push(QueuedBlocking {
19571 id: blocking.job,
19572 run: Box::new(move || {
19573 completed.fetch_add(1, Ordering::SeqCst);
19574 }),
19575 });
19576
19577 let _owner = BlockingOwnerGuard::enter(Some(owner));
19578 assert!(executor.try_owned_blocking_over_cap(&dependency));
19579 }
19580
19581 assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
19582 assert_eq!(
19583 executor.owner_lending_attempts.load(Ordering::Relaxed),
19584 DEPENDENCY_COUNT,
19585 "targeted lending must perform one O(1) claim attempt per exact dependency"
19586 );
19587 let queue = executor.blocking_queue.lock().unwrap();
19588 assert!(queue.is_empty());
19589 assert_eq!(queue.head, None);
19590 assert_eq!(queue.tail, None);
19591 }
19592
19593 #[cfg(not(target_family = "wasm"))]
19594 #[test]
19595 fn one_dependency_interspersed_owner_publications_stays_linear() {
19596 const PUBLICATION_COUNT: usize = 1024;
19597
19598 let executor = multi_thread_executor(2, None, "linear-publication-lending");
19599 let owner = executor.fresh_owner_token();
19600 let wrong_owner = executor.fresh_owner_token();
19601 let dependency = TaskDependency::new(u64::MAX, Some(owner));
19602 let completed = Arc::new(AtomicUsize::new(0));
19603 for index in 0..PUBLICATION_COUNT {
19604 let cancelled_source = TaskDependency::default();
19605 cancelled_source.set_owned(BlockingDependency {
19606 job: BlockingJobId(u64::MAX - 100_000 - index as u64),
19607 owner: Some(owner),
19608 });
19609 dependency.set_borrowed(
19610 &cancelled_source
19611 .get()
19612 .expect("the source publication must initially be live"),
19613 );
19614 cancelled_source.clear();
19615
19616 dependency.set_owned(BlockingDependency {
19617 job: BlockingJobId(u64::MAX - 200_000 - index as u64),
19618 owner: Some(wrong_owner),
19619 });
19620 let blocking = BlockingDependency {
19621 job: BlockingJobId(u64::MAX - 1_000 - index as u64),
19622 owner: Some(owner),
19623 };
19624 dependency.set_owned(blocking);
19625 let completed = Arc::clone(&completed);
19626 executor
19627 .blocking_queue
19628 .lock()
19629 .unwrap()
19630 .push(QueuedBlocking {
19631 id: blocking.job,
19632 run: Box::new(move || {
19633 completed.fetch_add(1, Ordering::SeqCst);
19634 }),
19635 });
19636 }
19637 dependency.reset_entry_visits();
19638
19639 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19640 let _owner = BlockingOwnerGuard::enter(Some(owner));
19641 for _ in 0..PUBLICATION_COUNT {
19642 assert!(
19643 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19644 "the owner-handoff predicate must find the next exact publication"
19645 );
19646 assert!(
19647 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19648 "the park-availability predicate must find the next serviceable publication"
19649 );
19650 assert!(executor.try_owned_blocking_over_cap(&dependency));
19651 }
19652
19653 assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
19654 assert!(
19655 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19656 "wrong-owner publications must not satisfy an exact handoff"
19657 );
19658 assert!(
19659 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19660 "wrong-owner publications must not make the owner's lane appear serviceable"
19661 );
19662 assert!(
19663 dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
19664 "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
19665 dependency.entry_visits()
19666 );
19667 assert!(executor.blocking_queue.lock().unwrap().is_empty());
19668 }
19669
19670 #[cfg(not(target_family = "wasm"))]
19671 #[test]
19672 fn binding_unowned_dependency_updates_exact_owner_predicate() {
19673 let owner = BlockingOwnerToken {
19674 executor_id: u64::MAX - 200,
19675 frame: u64::MAX - 201,
19676 };
19677 let dependency = TaskDependency::default();
19678 let blocking = BlockingDependency {
19679 job: BlockingJobId(u64::MAX - 202),
19680 owner: None,
19681 };
19682 dependency.set_owned(blocking);
19683
19684 assert!(
19685 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19686 "an unowned publication must not satisfy an exact owner handoff"
19687 );
19688 assert!(
19689 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19690 "an unowned publication is available to the ambient owner"
19691 );
19692
19693 let selected = dependency
19694 .select_for_owner(owner)
19695 .expect("the owner must bind the unowned publication");
19696 assert_eq!(selected.publication.dependency.owner, Some(owner));
19697 assert!(
19698 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19699 "binding must immediately publish the entry to exact-owner predicates"
19700 );
19701 assert!(dependency.try_claim(&selected));
19702 assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
19703 }
19704
19705 #[cfg(not(target_family = "wasm"))]
19706 #[test]
19707 fn owner_selection_preserves_unowned_and_exact_publication_order() {
19708 let owner = BlockingOwnerToken {
19709 executor_id: u64::MAX - 210,
19710 frame: u64::MAX - 211,
19711 };
19712 let dependency = TaskDependency::default();
19713 let first = BlockingDependency {
19714 job: BlockingJobId(u64::MAX - 212),
19715 owner: None,
19716 };
19717 let second = BlockingDependency {
19718 job: BlockingJobId(u64::MAX - 213),
19719 owner: Some(owner),
19720 };
19721 dependency.set_owned(first);
19722 dependency.set_owned(second);
19723
19724 let selected = dependency
19725 .select_for_owner(owner)
19726 .expect("the first publication must be found");
19727 assert_eq!(
19728 selected.publication.dependency,
19729 BlockingDependency {
19730 owner: Some(owner),
19731 ..first
19732 }
19733 );
19734 assert!(dependency.try_claim(&selected));
19735
19736 let selected = dependency
19737 .select_for_owner(owner)
19738 .expect("the second publication must remain eligible");
19739 assert_eq!(selected.publication.dependency, second);
19740 }
19741
19742 #[cfg(not(target_family = "wasm"))]
19743 #[test]
19744 fn blocking_queue_exact_removal_reclaims_fifo_links() {
19745 const JOB_COUNT: u64 = 4096;
19746
19747 let mut queue = BlockingQueue {
19748 closed: false,
19749 head: None,
19750 tail: None,
19751 jobs: FxHashMap::default(),
19752 };
19753 for id in 1..=JOB_COUNT {
19754 queue.push(QueuedBlocking {
19755 id: BlockingJobId(id),
19756 run: Box::new(|| {}),
19757 });
19758 }
19759 for id in (2..=JOB_COUNT).step_by(2) {
19760 drop(
19761 queue
19762 .remove(BlockingJobId(id))
19763 .expect("even job must remain indexed"),
19764 );
19765 }
19766 for id in (1..=JOB_COUNT).step_by(2) {
19767 drop(
19768 queue
19769 .remove(BlockingJobId(id))
19770 .expect("odd job must remain indexed"),
19771 );
19772 }
19773 assert!(queue.jobs.is_empty());
19774 assert_eq!(queue.head, None);
19775 assert_eq!(queue.tail, None);
19776
19777 let order = Arc::new(Mutex::new(Vec::new()));
19778 for id in 1..=3 {
19779 let order = Arc::clone(&order);
19780 queue.push(QueuedBlocking {
19781 id: BlockingJobId(id),
19782 run: Box::new(move || order.lock().unwrap().push(id)),
19783 });
19784 }
19785 drop(
19786 queue
19787 .remove(BlockingJobId(2))
19788 .expect("middle job must be removable"),
19789 );
19790 queue.pop_front().expect("FIFO head must remain linked")();
19791 queue.pop_front().expect("FIFO tail must remain linked")();
19792 assert!(queue.pop_front().is_none());
19793 assert_eq!(*order.lock().unwrap(), vec![1, 3]);
19794 assert_eq!(queue.head, None);
19795 assert_eq!(queue.tail, None);
19796 }
19797
19798 #[cfg(not(target_family = "wasm"))]
19799 #[test]
19800 fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
19801 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
19802
19803 if std::env::var_os(CHILD_ENV).is_some() {
19804 configure(RuntimeOptions {
19805 flavor: RuntimeFlavor::MultiThread,
19806 worker_threads: 2,
19807 max_blocking_tasks: 1,
19808 thread_name_prefix: "dependency-lending-metrics".to_string(),
19809 park_deadline: None,
19810 drain_linger: DEFAULT_DRAIN_LINGER,
19811 })
19812 .unwrap();
19813
19814 let outer = spawn_blocking(|| {
19815 let inner = spawn_blocking(|| 42usize);
19816 block_on(inner).unwrap()
19817 });
19818 assert_eq!(block_on(outer).unwrap(), 42);
19819
19820 shutdown().unwrap();
19825 let snapshot = metrics();
19826 assert_eq!(snapshot.blocking_tasks_started, 2);
19827 assert_eq!(snapshot.blocking_tasks_completed, 2);
19828 assert_eq!(snapshot.active_blocking_tasks, 0);
19829 assert_eq!(
19830 snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
19831 "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
19832 );
19833 return;
19834 }
19835
19836 let output = std::process::Command::new(std::env::current_exe().unwrap())
19837 .arg("--exact")
19838 .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
19839 .arg("--nocapture")
19840 .env(CHILD_ENV, "1")
19841 .output()
19842 .expect("the dependency-lending metrics subprocess must start");
19843 assert!(
19844 output.status.success(),
19845 "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
19846 output.status.code(),
19847 String::from_utf8_lossy(&output.stdout),
19848 String::from_utf8_lossy(&output.stderr)
19849 );
19850 }
19851
19852 #[cfg(not(target_family = "wasm"))]
19853 #[test]
19854 fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
19855 use std::sync::mpsc;
19856
19857 let (done_tx, done_rx) = mpsc::channel();
19858 let runner = std::thread::spawn(move || {
19859 let options = RuntimeOptions {
19860 flavor: RuntimeFlavor::MultiThread,
19861 worker_threads: 2,
19862 max_blocking_tasks: 1,
19863 thread_name_prefix: "exact-blocking-job".to_string(),
19864 park_deadline: None,
19865 drain_linger: DEFAULT_DRAIN_LINGER,
19866 };
19867 let executor =
19868 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19869 let outer_exec = Arc::clone(&executor);
19870 let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
19871 let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
19872 let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
19873 let outer = executor.schedule_blocking(move || {
19874 let sibling = outer_exec.schedule_blocking(move || {
19875 sibling_started_tx.send(()).unwrap();
19876 release_sibling_rx.recv().unwrap();
19877 });
19878 let awaited = outer_exec.schedule_blocking(move || {
19879 awaited_ran_tx.send(()).unwrap();
19880 17usize
19881 });
19882 let mut value = None;
19883 {
19884 let mut future = std::pin::pin!(async {
19885 value = Some(awaited.await.unwrap());
19886 });
19887 outer_exec.block_on(future.as_mut());
19888 }
19889 (sibling, value.unwrap())
19890 });
19891
19892 awaited_ran_rx
19893 .recv_timeout(Duration::from_secs(2))
19894 .expect("the exact awaited job must bypass the earlier detached sibling");
19895 let (sibling, value) = futures::executor::block_on(outer).unwrap();
19896 assert_eq!(value, 17);
19897 sibling_started_rx
19898 .recv_timeout(Duration::from_secs(2))
19899 .expect("the sibling must run after the owner releases its counted slot");
19900 release_sibling_tx.send(()).unwrap();
19901 futures::executor::block_on(sibling).unwrap();
19902 done_tx.send(()).unwrap();
19903 });
19904 done_rx
19905 .recv_timeout(Duration::from_secs(5))
19906 .expect("same-owner sibling selection deadlocked the exact awaited job");
19907 runner.join().unwrap();
19908 }
19909
19910 #[cfg(not(target_family = "wasm"))]
19911 #[test]
19912 fn blocking_dependency_propagates_through_async_join_lineage() {
19913 use std::sync::mpsc;
19914
19915 let (done_tx, done_rx) = mpsc::channel();
19916 let runner = std::thread::spawn(move || {
19917 let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
19918 let owner_controller = Arc::clone(&controller);
19919 let (child_tx, child_rx) = mpsc::channel();
19920 let outer = controller
19921 .try_spawn_blocking(move || {
19922 let async_controller = Arc::clone(&owner_controller);
19923 let async_child = owner_controller
19924 .try_spawn(async move {
19925 async_controller
19926 .try_spawn_blocking(move || {
19927 child_tx.send(()).unwrap();
19928 23usize
19929 })
19930 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
19931 .await
19932 .unwrap()
19933 })
19934 .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
19935 let mut value = None;
19936 {
19937 let mut future = std::pin::pin!(async {
19938 value = Some(async_child.await.unwrap());
19939 });
19940 owner_controller.block_on(future.as_mut());
19941 }
19942 value.unwrap()
19943 })
19944 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19945
19946 child_rx
19947 .recv_timeout(Duration::from_secs(2))
19948 .expect("the async descendant's exact blocking dependency must inherit the owner lane");
19949 assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
19950 controller.shutdown().unwrap();
19951 done_tx.send(()).unwrap();
19952 });
19953 done_rx
19954 .recv_timeout(Duration::from_secs(5))
19955 .expect("blocking dependency lineage deadlocked");
19956 runner.join().unwrap();
19957 }
19958
19959 #[cfg(not(target_family = "wasm"))]
19960 #[test]
19961 fn blocking_dependency_does_not_escape_a_serviced_runnable() {
19962 use std::sync::mpsc;
19963
19964 let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
19965 let outer_controller = Arc::clone(&controller);
19966 let (child_queued_tx, child_queued_rx) = mpsc::channel();
19967 let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
19968 let (release_outer_tx, release_outer_rx) = oneshot::channel();
19969 let outer = controller
19970 .try_spawn_blocking(move || {
19971 let child_controller = Arc::clone(&outer_controller);
19972 let child = outer_controller
19973 .try_spawn(async move {
19974 let blocking = child_controller
19975 .try_spawn_blocking(move || {
19976 unrelated_ran_tx.send(()).unwrap();
19977 31usize
19978 })
19979 .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
19980 child_queued_tx.send(()).unwrap();
19981 blocking.await.unwrap()
19982 })
19983 .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
19984 let mut released = false;
19985 {
19986 let mut future = std::pin::pin!(async {
19987 release_outer_rx.await.unwrap();
19988 released = true;
19989 });
19990 outer_controller.block_on(future.as_mut());
19991 }
19992 assert!(released);
19993 child
19994 })
19995 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19996
19997 child_queued_rx
19998 .recv_timeout(Duration::from_secs(2))
19999 .expect("the cooperative driver must service the unrelated async runnable");
20000 assert!(
20001 unrelated_ran_rx
20002 .recv_timeout(Duration::from_millis(200))
20003 .is_err(),
20004 "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
20005 );
20006
20007 release_outer_tx.send(()).unwrap();
20008 let child = futures::executor::block_on(outer).unwrap();
20009 unrelated_ran_rx
20010 .recv_timeout(Duration::from_secs(2))
20011 .expect("the unrelated blocking job must run after the owner releases its counted slot");
20012 assert_eq!(futures::executor::block_on(child).unwrap(), 31);
20013 controller.shutdown().unwrap();
20014 }
20015
20016 #[cfg(not(target_family = "wasm"))]
20017 #[test]
20018 fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
20019 let parent = Arc::new(TaskDependency::default());
20020 let _context = TaskDependencyGuard::enter(&parent);
20021 let dependency = BlockingDependency {
20022 job: BlockingJobId(u64::MAX),
20023 owner: None,
20024 };
20025 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20026 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20027 receiver,
20028 dependency,
20029 published: PublishedBlockingDependencies::default(),
20030 });
20031 let waker = futures::task::noop_waker();
20032 let mut cx = Context::from_waker(&waker);
20033
20034 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20035 assert_eq!(parent.get_dependency(), Some(dependency));
20036 drop(handle);
20037 assert_eq!(
20038 parent.get_dependency(),
20039 None,
20040 "an abandoned JoinHandle must clear stale dependency lineage"
20041 );
20042 drop(sender);
20043 }
20044
20045 #[cfg(not(target_family = "wasm"))]
20046 #[test]
20047 fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
20048 let owner = BlockingOwnerToken {
20049 executor_id: u64::MAX - 40,
20050 frame: u64::MAX - 41,
20051 };
20052 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20053 let _context = TaskDependencyGuard::enter(&parent);
20054 let dependency = BlockingDependency {
20055 job: BlockingJobId(u64::MAX - 42),
20056 owner: None,
20057 };
20058 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20059 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20060 receiver,
20061 dependency,
20062 published: PublishedBlockingDependencies::default(),
20063 });
20064 let waker = futures::task::noop_waker();
20065 let mut cx = Context::from_waker(&waker);
20066
20067 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20068 assert_eq!(
20069 parent.get_dependency(),
20070 Some(BlockingDependency {
20071 owner: Some(owner),
20072 ..dependency
20073 }),
20074 "the ambient task owner must enrich an untagged direct blocking dependency"
20075 );
20076 drop(handle);
20077 assert_eq!(
20078 parent.get_dependency(),
20079 None,
20080 "detach cleanup must match the stable job id after owner enrichment"
20081 );
20082 drop(sender);
20083 }
20084
20085 #[cfg(not(target_family = "wasm"))]
20086 #[test]
20087 fn one_poll_preserves_every_observed_blocking_dependency() {
20088 let parent = Arc::new(TaskDependency::default());
20089 let _context = TaskDependencyGuard::enter(&parent);
20090 let first = BlockingDependency {
20091 job: BlockingJobId(u64::MAX - 60),
20092 owner: None,
20093 };
20094 let second = BlockingDependency {
20095 job: BlockingJobId(u64::MAX - 61),
20096 owner: None,
20097 };
20098 let (first_sender, first_receiver) =
20099 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20100 let (second_sender, second_receiver) =
20101 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20102 let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
20103 receiver: first_receiver,
20104 dependency: first,
20105 published: PublishedBlockingDependencies::default(),
20106 });
20107 let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
20108 receiver: second_receiver,
20109 dependency: second,
20110 published: PublishedBlockingDependencies::default(),
20111 });
20112 let waker = futures::task::noop_waker();
20113 let mut cx = Context::from_waker(&waker);
20114
20115 assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
20116 assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
20117 assert_eq!(
20118 parent.get_dependencies(),
20119 vec![first, second],
20120 "publishing a later dependency in one user poll must not replace an earlier dependency"
20121 );
20122
20123 drop(first_handle);
20124 drop(second_handle);
20125 drop(first_sender);
20126 drop(second_sender);
20127 }
20128
20129 #[cfg(not(target_family = "wasm"))]
20130 #[test]
20131 fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
20132 let owner = BlockingOwnerToken {
20133 executor_id: u64::MAX - 900,
20134 frame: u64::MAX - 901,
20135 };
20136 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20137 let _context = TaskDependencyGuard::enter(&parent);
20138 let waker = futures::task::noop_waker();
20139 let mut cx = Context::from_waker(&waker);
20140
20141 let j1 = BlockingDependency {
20142 job: BlockingJobId(u64::MAX - 910),
20143 owner: Some(owner),
20144 };
20145 let j2 = BlockingDependency {
20146 job: BlockingJobId(u64::MAX - 911),
20147 owner: Some(owner),
20148 };
20149
20150 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20151 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20152
20153 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20154 receiver: r1,
20155 dependency: j1,
20156 published: PublishedBlockingDependencies::default(),
20157 });
20158 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20161 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20162 let entries_after_double_poll = parent
20163 .state
20164 .lock()
20165 .unwrap_or_else(std::sync::PoisonError::into_inner)
20166 .current
20167 .len();
20168 drop(h1);
20169
20170 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20171 receiver: r2,
20172 dependency: j2,
20173 published: PublishedBlockingDependencies::default(),
20174 });
20175 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20176
20177 assert_eq!(
20178 entries_after_double_poll, 1,
20179 "a repeat Pending poll must supersede the handle's own previous publication"
20180 );
20181 let selected = parent
20182 .select_for_owner(owner)
20183 .expect("a live dependency must be selectable");
20184 assert_eq!(
20185 selected.publication.dependency.job, j2.job,
20186 "a DETACHED job must not be selected for owner lending ahead of the live one"
20187 );
20188 drop((s1, s2));
20189 }
20190
20191 #[cfg(not(target_family = "wasm"))]
20192 #[test]
20193 fn handle_polled_under_a_nested_frame_retires_both_publications() {
20194 let owner = BlockingOwnerToken {
20195 executor_id: u64::MAX - 920,
20196 frame: u64::MAX - 921,
20197 };
20198 let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20199 let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20200 let waker = futures::task::noop_waker();
20201 let mut cx = Context::from_waker(&waker);
20202
20203 let j1 = BlockingDependency {
20204 job: BlockingJobId(u64::MAX - 930),
20205 owner: Some(owner),
20206 };
20207 let j2 = BlockingDependency {
20208 job: BlockingJobId(u64::MAX - 931),
20209 owner: Some(owner),
20210 };
20211 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20212 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20213 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20214 receiver: r1,
20215 dependency: j1,
20216 published: Default::default(),
20217 });
20218
20219 {
20220 let _outer_ctx = TaskDependencyGuard::enter(&outer);
20221 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20225 {
20226 let _inner_ctx = TaskDependencyGuard::enter(&inner);
20227 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20228 }
20229 drop(h1);
20230 assert!(
20231 outer.get_dependencies().is_empty(),
20232 "detaching must retire the publication made in the outer frame too"
20233 );
20234 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20235 receiver: r2,
20236 dependency: j2,
20237 published: Default::default(),
20238 });
20239 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20240 let selected = outer.select_for_owner(owner).expect("selectable");
20241 assert_eq!(
20242 selected.publication.dependency.job, j2.job,
20243 "a detached job published in an outer frame must not outrank a live one"
20244 );
20245 drop(h2);
20246 }
20247 drop((s1, s2));
20248 }
20249
20250 #[cfg(not(target_family = "wasm"))]
20251 #[test]
20252 fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
20253 const FRAME_COUNT: usize = 64;
20254 let waker = futures::task::noop_waker();
20255 let mut cx = Context::from_waker(&waker);
20256 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20257 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20258 receiver,
20259 dependency: BlockingDependency {
20260 job: BlockingJobId(u64::MAX - 940),
20261 owner: None,
20262 },
20263 published: PublishedBlockingDependencies::default(),
20264 });
20265 let mut frames = Vec::new();
20266 for _ in 0..FRAME_COUNT {
20267 let frame = Arc::new(TaskDependency::default());
20268 frames.push(Arc::downgrade(&frame));
20269 let _context = TaskDependencyGuard::enter(&frame);
20270 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20271 }
20272 let retained = frames
20277 .iter()
20278 .filter(|frame| frame.strong_count() > 0)
20279 .count();
20280 assert_eq!(
20281 retained, 0,
20282 "a completed frame must not be kept alive by a pending handle's publication history"
20283 );
20284 drop(handle);
20285 drop(sender);
20286 }
20287
20288 #[cfg(not(target_family = "wasm"))]
20289 #[test]
20290 fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
20291 let owner = BlockingOwnerToken {
20296 executor_id: u64::MAX - 950,
20297 frame: u64::MAX - 951,
20298 };
20299 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20300 let _context = TaskDependencyGuard::enter(&parent);
20301 let waker = futures::task::noop_waker();
20302 let mut cx = Context::from_waker(&waker);
20303
20304 let j1 = BlockingDependency {
20305 job: BlockingJobId(u64::MAX - 960),
20306 owner: Some(owner),
20307 };
20308 let j2 = BlockingDependency {
20309 job: BlockingJobId(u64::MAX - 961),
20310 owner: Some(owner),
20311 };
20312 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20313 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20314 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20315 receiver: r1,
20316 dependency: j1,
20317 published: PublishedBlockingDependencies::default(),
20318 });
20319 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20320 receiver: r2,
20321 dependency: j2,
20322 published: PublishedBlockingDependencies::default(),
20323 });
20324
20325 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20326 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20327 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20328
20329 let selected = parent
20330 .select_for_owner(owner)
20331 .expect("a live dependency must be selectable");
20332 assert_eq!(
20333 selected.publication.dependency.job, j1.job,
20334 "re-polling a handle must not push its dependency behind one published later"
20335 );
20336 drop((h1, h2, s1, s2));
20337 }
20338
20339 #[cfg(not(target_family = "wasm"))]
20340 #[test]
20341 fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
20342 const FRAME_COUNT: usize = 256;
20349 let waker = futures::task::noop_waker();
20350 let mut cx = Context::from_waker(&waker);
20351 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20352 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20353 receiver,
20354 dependency: BlockingDependency {
20355 job: BlockingJobId(u64::MAX - 970),
20356 owner: None,
20357 },
20358 published: PublishedBlockingDependencies::default(),
20359 });
20360 let mut retained = Vec::new();
20361 for _ in 0..FRAME_COUNT {
20362 let frame = Arc::new(TaskDependency::default());
20363 retained.push(Arc::clone(&frame));
20364 let _context = TaskDependencyGuard::enter(&frame);
20365 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20366 frame.begin_poll();
20368 }
20369 let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
20370 panic!("the handle must still be a blocking handle");
20371 };
20372 let remembered = usize::from(published.first.is_some()) + published.rest.len();
20373 assert!(
20374 remembered <= 2,
20375 "a drained frame must not stay in the publication history, remembered={remembered}"
20376 );
20377 drop(handle);
20378 drop(sender);
20379 }
20380
20381 #[cfg(not(target_family = "wasm"))]
20382 #[test]
20383 fn mass_detach_of_pending_blocking_handles_stays_linear() {
20384 const HANDLE_COUNT: usize = 512;
20395
20396 let parent = Arc::new(TaskDependency::default());
20397 let _context = TaskDependencyGuard::enter(&parent);
20398 let waker = futures::task::noop_waker();
20399 let mut cx = Context::from_waker(&waker);
20400
20401 let mut senders = Vec::with_capacity(HANDLE_COUNT);
20402 let mut handles = Vec::with_capacity(HANDLE_COUNT);
20403 for index in 0..HANDLE_COUNT {
20404 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20405 senders.push(sender);
20406 handles.push(JoinHandle(JoinHandleInner::Blocking {
20407 receiver,
20408 dependency: BlockingDependency {
20409 job: BlockingJobId(u64::MAX - 300_000 - index as u64),
20410 owner: None,
20411 },
20412 published: PublishedBlockingDependencies::default(),
20413 }));
20414 }
20415 for handle in &mut handles {
20416 assert!(Pin::new(handle).poll(&mut cx).is_pending());
20417 }
20418 assert_eq!(
20419 parent
20420 .state
20421 .lock()
20422 .unwrap_or_else(std::sync::PoisonError::into_inner)
20423 .current
20424 .len(),
20425 HANDLE_COUNT,
20426 "every handle must be registered and still pending before the detach"
20427 );
20428
20429 parent.reset_entry_visits();
20430 drop(handles);
20431
20432 assert!(
20433 parent.entry_visits() <= HANDLE_COUNT * 4,
20434 "mass-detach must visit O(n) entries across n handles, visits={}",
20435 parent.entry_visits()
20436 );
20437 assert_eq!(
20438 parent.get_dependency(),
20439 None,
20440 "every detached handle must have retired its own publication"
20441 );
20442 drop(senders);
20443 }
20444
20445 #[cfg(not(target_family = "wasm"))]
20446 #[test]
20447 fn dependency_tls_borrow_is_released_before_reentrant_wake() {
20448 use std::sync::mpsc;
20449
20450 struct ReenterDependencyContext {
20451 dependency: Arc<TaskDependency>,
20452 woke: mpsc::Sender<()>,
20453 }
20454
20455 impl std::task::Wake for ReenterDependencyContext {
20456 fn wake(self: Arc<Self>) {
20457 let _context = TaskDependencyGuard::enter(&self.dependency);
20458 self.woke.send(()).unwrap();
20459 }
20460 }
20461
20462 let parent = Arc::new(TaskDependency::default());
20463 let nested = Arc::new(TaskDependency::default());
20464 let (woke_tx, woke_rx) = mpsc::channel();
20465 let waiter = Waker::from(Arc::new(ReenterDependencyContext {
20466 dependency: Arc::clone(&nested),
20467 woke: woke_tx,
20468 }));
20469 parent.register_waiter(&waiter);
20470 let blocking = BlockingDependency {
20471 job: BlockingJobId(u64::MAX - 62),
20472 owner: None,
20473 };
20474
20475 let _context = TaskDependencyGuard::enter(&parent);
20476 let mut published = PublishedBlockingDependencies::default();
20477 record_blocking_dependency(&mut published, blocking);
20478 woke_rx
20479 .recv_timeout(Duration::from_secs(1))
20480 .expect("dependency notification must permit reentrant TLS context access");
20481
20482 parent.register_waiter(&waiter);
20483 clear_blocking_dependency(published);
20484 woke_rx
20485 .recv_timeout(Duration::from_secs(1))
20486 .expect("dependency clearing must permit reentrant TLS context access");
20487 }
20488
20489 #[cfg(not(target_family = "wasm"))]
20490 #[test]
20491 fn dependency_free_begin_poll_does_not_lock_state() {
20492 use std::sync::mpsc;
20493
20494 let dependency = Arc::new(TaskDependency::default());
20495 let state = dependency.state.lock().unwrap();
20496 let (completed_tx, completed_rx) = mpsc::channel();
20497 let polling_dependency = Arc::clone(&dependency);
20498 let polling = std::thread::spawn(move || {
20499 polling_dependency.begin_poll();
20500 completed_tx.send(()).unwrap();
20501 });
20502
20503 let completed_without_lock = completed_rx
20504 .recv_timeout(Duration::from_millis(200))
20505 .is_ok();
20506 drop(state);
20507 polling.join().unwrap();
20508 assert!(
20509 completed_without_lock,
20510 "a task with no published dependency must skip the dependency-state mutex"
20511 );
20512 }
20513
20514 #[cfg(not(target_family = "wasm"))]
20515 #[test]
20516 fn deep_dependency_liveness_chain_is_iterative() {
20517 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
20518
20519 if std::env::var_os(CHILD_ENV).is_some() {
20520 const DEPTH: usize = 100_000;
20521
20522 let mut links = Vec::with_capacity(DEPTH + 1);
20523 links.push(DependencyLink::root());
20524 for _ in 0..DEPTH {
20525 links.push(DependencyLink::derived(links.last().unwrap()));
20526 }
20527 assert!(links.last().unwrap().is_live());
20528 links[0].cancel();
20529 assert!(!links.last().unwrap().is_live());
20530 return;
20531 }
20532
20533 let output = std::process::Command::new(std::env::current_exe().unwrap())
20534 .arg("--exact")
20535 .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
20536 .arg("--nocapture")
20537 .env(CHILD_ENV, "1")
20538 .output()
20539 .expect("the subprocess test must start");
20540 assert!(
20541 output.status.success(),
20542 "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
20543 output.status.code(),
20544 String::from_utf8_lossy(&output.stdout),
20545 String::from_utf8_lossy(&output.stderr)
20546 );
20547 }
20548
20549 #[cfg(not(target_family = "wasm"))]
20550 #[test]
20551 fn task_dependency_set_contains_panicking_waiter() {
20552 struct PanicWake;
20553 impl std::task::Wake for PanicWake {
20554 fn wake(self: Arc<Self>) {
20555 panic!("intentional TaskDependency::set waiter panic");
20556 }
20557 }
20558
20559 let dependency = TaskDependency::default();
20560 let blocking = BlockingDependency {
20561 job: BlockingJobId(u64::MAX),
20562 owner: None,
20563 };
20564 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20565
20566 let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
20567 assert!(
20568 result.is_ok(),
20569 "set notification must not unwind its caller"
20570 );
20571 assert_eq!(
20572 dependency.get_dependency(),
20573 Some(blocking),
20574 "set must commit before notifying its waiter"
20575 );
20576 }
20577
20578 #[cfg(not(target_family = "wasm"))]
20579 #[test]
20580 fn task_dependency_clear_contains_panicking_waiter() {
20581 struct PanicWake;
20582 impl std::task::Wake for PanicWake {
20583 fn wake(self: Arc<Self>) {
20584 panic!("intentional TaskDependency::clear waiter panic");
20585 }
20586 }
20587
20588 let dependency = TaskDependency::default();
20589 dependency.set_owned(BlockingDependency {
20590 job: BlockingJobId(u64::MAX),
20591 owner: None,
20592 });
20593 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20594
20595 let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
20596 assert!(
20597 result.is_ok(),
20598 "clear notification must not unwind its caller"
20599 );
20600 assert_eq!(
20601 dependency.get_dependency(),
20602 None,
20603 "clear must commit before notifying its waiter"
20604 );
20605 }
20606
20607 #[cfg(not(target_family = "wasm"))]
20608 #[test]
20609 fn task_dependency_clear_if_contains_panicking_waiter() {
20610 struct PanicWake;
20611 impl std::task::Wake for PanicWake {
20612 fn wake(self: Arc<Self>) {
20613 panic!("intentional TaskDependency::clear_if waiter panic");
20614 }
20615 }
20616
20617 let dependency = TaskDependency::default();
20618 let blocking = BlockingDependency {
20619 job: BlockingJobId(u64::MAX),
20620 owner: None,
20621 };
20622 let sequence = dependency.set_owned(blocking);
20623 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20624
20625 let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
20626 assert!(
20627 result.is_ok(),
20628 "retire notification must not unwind its caller"
20629 );
20630 assert_eq!(
20631 dependency.get_dependency(),
20632 None,
20633 "retire must commit before notifying its waiter"
20634 );
20635 }
20636
20637 #[cfg(not(target_family = "wasm"))]
20638 #[test]
20639 fn task_dependency_waiter_replacement_contains_panicking_drop() {
20640 use std::sync::mpsc;
20641
20642 const GENERATION: u64 = u64::MAX - 20;
20643
20644 struct PanicOnDropWake {
20645 dropped: mpsc::Sender<Option<u64>>,
20646 }
20647
20648 #[expect(
20649 clippy::manual_noop_waker,
20650 reason = "the test observes the waker payload's Drop"
20651 )]
20652 impl std::task::Wake for PanicOnDropWake {
20653 fn wake(self: Arc<Self>) {}
20654 }
20655
20656 impl Drop for PanicOnDropWake {
20657 fn drop(&mut self) {
20658 self
20659 .dropped
20660 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20661 .unwrap();
20662 panic!("intentional replaced dependency waiter destructor panic");
20663 }
20664 }
20665
20666 let dependency = TaskDependency::new(GENERATION, None);
20667 let (dropped_tx, dropped_rx) = mpsc::channel();
20668 let waiter = Waker::from(Arc::new(PanicOnDropWake {
20669 dropped: dropped_tx,
20670 }));
20671 dependency.register_waiter(&waiter);
20672 drop(waiter);
20673
20674 let replacement = futures::task::noop_waker();
20675 let result = catch_unwind(AssertUnwindSafe(|| {
20676 dependency.register_waiter(&replacement)
20677 }));
20678 assert!(
20679 result.is_ok(),
20680 "replacing a hostile waiter must not unwind registration"
20681 );
20682 assert_eq!(
20683 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20684 Some(GENERATION),
20685 "the replaced waiter must be destroyed under its runtime generation"
20686 );
20687 }
20688
20689 #[cfg(not(target_family = "wasm"))]
20690 #[test]
20691 fn task_dependency_retirement_contains_stored_waiter_drop() {
20692 use std::sync::mpsc;
20693
20694 const GENERATION: u64 = u64::MAX - 21;
20695
20696 struct PanicOnDropWake {
20697 dropped: mpsc::Sender<Option<u64>>,
20698 }
20699
20700 #[expect(
20701 clippy::manual_noop_waker,
20702 reason = "the test observes the waker payload's Drop"
20703 )]
20704 impl std::task::Wake for PanicOnDropWake {
20705 fn wake(self: Arc<Self>) {}
20706 }
20707
20708 impl Drop for PanicOnDropWake {
20709 fn drop(&mut self) {
20710 self
20711 .dropped
20712 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20713 .unwrap();
20714 panic!("intentional stored dependency waiter destructor panic");
20715 }
20716 }
20717
20718 let dependency = TaskDependency::new(GENERATION, None);
20719 let (dropped_tx, dropped_rx) = mpsc::channel();
20720 let waiter = Waker::from(Arc::new(PanicOnDropWake {
20721 dropped: dropped_tx,
20722 }));
20723 dependency.register_waiter(&waiter);
20724 drop(waiter);
20725
20726 let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
20727 assert!(
20728 result.is_ok(),
20729 "retiring a hostile stored waiter must not unwind"
20730 );
20731 assert_eq!(
20732 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20733 Some(GENERATION),
20734 "the retained waiter must be destroyed under its runtime generation"
20735 );
20736 }
20737
20738 #[cfg(not(target_family = "wasm"))]
20739 #[test]
20740 fn detaching_task_clears_dependency_waiter_parent_retention() {
20741 struct RetainingWake {
20742 retained_parent: Arc<()>,
20743 }
20744
20745 impl std::task::Wake for RetainingWake {
20746 fn wake(self: Arc<Self>) {
20747 let _ = &self.retained_parent;
20748 }
20749 }
20750
20751 let dependency = Arc::new(TaskDependency::default());
20752 let retained_parent = Arc::new(());
20753 let retained_parent_weak = Arc::downgrade(&retained_parent);
20754 let waiter = Waker::from(Arc::new(RetainingWake {
20755 retained_parent: Arc::clone(&retained_parent),
20756 }));
20757 dependency.register_waiter(&waiter);
20758 drop(waiter);
20759 drop(retained_parent);
20760 assert!(
20761 retained_parent_weak.upgrade().is_some(),
20762 "the stored dependency waiter must retain its parent before detachment"
20763 );
20764
20765 let (runnable, task) = async_task::spawn(
20766 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20767 |_| {},
20768 );
20769 drop(JoinHandle(JoinHandleInner::Task {
20770 task: task.fallible(),
20771 awaiter: None,
20772 dependency: Arc::clone(&dependency),
20773 }));
20774 assert!(
20775 retained_parent_weak.upgrade().is_none(),
20776 "detachment must clear the dependency waiter instead of retaining its parent task"
20777 );
20778 drop(runnable);
20779 }
20780
20781 #[cfg(not(target_family = "wasm"))]
20782 #[test]
20783 fn detaching_task_clears_many_parent_publications_linearly() {
20784 const PUBLICATION_COUNT: usize = 1024;
20785
20786 struct CountWake(Arc<AtomicUsize>);
20787
20788 impl std::task::Wake for CountWake {
20789 fn wake(self: Arc<Self>) {
20790 self.0.fetch_add(1, Ordering::SeqCst);
20791 }
20792 }
20793
20794 fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
20795 dependency
20796 .state
20797 .lock()
20798 .unwrap_or_else(std::sync::PoisonError::into_inner)
20799 .current
20800 .back()
20801 .expect("the sentinel publication must be appended")
20802 .publication
20803 .clone()
20804 }
20805
20806 let owner = BlockingOwnerToken {
20807 executor_id: u64::MAX - 300,
20808 frame: u64::MAX - 301,
20809 };
20810 let other_owner = BlockingOwnerToken {
20811 executor_id: u64::MAX - 302,
20812 frame: u64::MAX - 303,
20813 };
20814 let child = Arc::new(TaskDependency::default());
20815 for index in 0..PUBLICATION_COUNT {
20816 child.set_owned(BlockingDependency {
20817 job: BlockingJobId(1_000_000 + index as u64),
20818 owner: (index % 2 == 0).then_some(owner),
20819 });
20820 }
20821 let child_publications = child.snapshot_all_live();
20822 let source = child_publications[0].clone();
20823
20824 let parent = Arc::new(TaskDependency::default());
20825 let _context = TaskDependencyGuard::enter(&parent);
20826 let (runnable, task) = async_task::spawn(
20827 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20828 |_| {},
20829 );
20830 let mut handle = JoinHandle(JoinHandleInner::Task {
20831 task: task.fallible(),
20832 awaiter: None,
20833 dependency: Arc::clone(&child),
20834 });
20835 let waker = futures::task::noop_waker();
20836 let mut cx = Context::from_waker(&waker);
20837 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20838 assert_eq!(
20839 parent.get_dependencies().len(),
20840 PUBLICATION_COUNT,
20841 "polling the child handle must propagate every current publication"
20842 );
20843
20844 let mut wrong_claim_source = source.clone();
20845 wrong_claim_source.claim = Arc::new(DependencyClaim::new());
20846 parent.set_borrowed(&wrong_claim_source);
20847 let wrong_claim_publication = latest_publication(&parent);
20848
20849 let mut wrong_job_source = source.clone();
20850 wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
20851 parent.set_borrowed(&wrong_job_source);
20852 let wrong_job_publication = latest_publication(&parent);
20853
20854 let wrong_immediate_source = source.derived(None);
20855 parent.set_borrowed(&wrong_immediate_source);
20856 let wrong_immediate_publication = latest_publication(&parent);
20857
20858 let wakes = Arc::new(AtomicUsize::new(0));
20859 parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
20860 parent.reset_entry_visits();
20861 drop(handle);
20862
20863 assert_eq!(
20864 wakes.load(Ordering::SeqCst),
20865 1,
20866 "one batched parent cleanup must emit one waiter notification"
20867 );
20868 assert!(
20869 parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
20870 "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
20871 parent.entry_visits()
20872 );
20873 assert!(
20874 wrong_claim_publication.is_live(),
20875 "claim identity must remain part of the match"
20876 );
20877 assert!(
20878 wrong_job_publication.is_live(),
20879 "job identity must remain part of the match"
20880 );
20881 assert!(
20882 wrong_immediate_publication.is_live(),
20883 "only an exact immediate-parent link may be retired"
20884 );
20885
20886 {
20887 let state = parent
20888 .state
20889 .lock()
20890 .unwrap_or_else(std::sync::PoisonError::into_inner);
20891 assert_eq!(state.current.len(), 3);
20892 assert!(
20899 state
20900 .current
20901 .iter()
20902 .all(|entry| entry.publication.is_live()),
20903 "only the three non-matching sentinels may remain, all still live"
20904 );
20905 assert!(state.unowned_sequences.is_empty());
20906 assert_eq!(state.owner_sequences.len(), 1);
20907 assert_eq!(
20908 state.owner_sequences.get(&owner).map(VecDeque::len),
20909 Some(3)
20910 );
20911 }
20912 assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
20913 assert!(
20914 !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
20915 "cancelled unowned publications must not survive in availability predicates"
20916 );
20917 drop(runnable);
20918 }
20919
20920 #[cfg(not(target_family = "wasm"))]
20921 #[test]
20922 fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
20923 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
20924
20925 if std::env::var_os(CHILD_ENV).is_some() {
20926 struct PanicWake;
20927 impl std::task::Wake for PanicWake {
20928 fn wake(self: Arc<Self>) {
20929 panic!("intentional dependency waker panic");
20930 }
20931 }
20932
20933 struct PanicOnDrop;
20934 impl Drop for PanicOnDrop {
20935 fn drop(&mut self) {
20936 panic!("intentional buffered blocking result destructor panic");
20937 }
20938 }
20939
20940 let parent = Arc::new(TaskDependency::default());
20941 let dependency = BlockingDependency {
20942 job: BlockingJobId(u64::MAX - 1),
20943 owner: None,
20944 };
20945 let sequence = parent.set_owned(dependency);
20949 parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
20950 let _context = TaskDependencyGuard::enter(&parent);
20951 let (sender, receiver) = oneshot::channel();
20952 let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
20953 ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
20954 ));
20955 drop(JoinHandle(JoinHandleInner::Blocking {
20956 receiver,
20957 dependency,
20958 published: PublishedBlockingDependencies {
20959 first: Some(PublishedBlockingDependency {
20960 state: Arc::downgrade(&parent),
20961 sequence,
20962 }),
20963 rest: Vec::new(),
20964 },
20965 }));
20966 assert_eq!(parent.get_dependency(), None);
20967 return;
20968 }
20969
20970 let output = std::process::Command::new(std::env::current_exe().unwrap())
20971 .arg("--exact")
20972 .arg(
20973 "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
20974 )
20975 .arg("--nocapture")
20976 .env(CHILD_ENV, "1")
20977 .output()
20978 .expect("the subprocess test must start");
20979 assert!(
20980 output.status.success(),
20981 "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
20982 output.status.code(),
20983 String::from_utf8_lossy(&output.stdout),
20984 String::from_utf8_lossy(&output.stderr)
20985 );
20986 }
20987
20988 #[cfg(not(target_family = "wasm"))]
20989 #[test]
20990 fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
20991 use std::sync::{Barrier, mpsc};
21000 use std::time::Duration;
21001
21002 let (done_tx, done_rx) = mpsc::channel();
21003 let peak_out = Arc::new(Mutex::new(None));
21004 let peak_slot = Arc::clone(&peak_out);
21005 let runner = std::thread::spawn(move || {
21006 let metrics = Arc::new(RuntimeMetrics::default());
21007 let options = RuntimeOptions {
21008 flavor: RuntimeFlavor::MultiThread,
21009 worker_threads: 4,
21010 max_blocking_tasks: 2,
21011 thread_name_prefix: "rd1-cap".to_string(),
21012 park_deadline: None,
21013 drain_linger: DEFAULT_DRAIN_LINGER,
21014 };
21015 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21016
21017 let holder_count = 2usize; let driver_count = 2usize;
21019
21020 let (held_tx, held_rx) = mpsc::channel();
21022 let release = Arc::new(Barrier::new(holder_count + 1));
21023
21024 let mut holder_handles = Vec::new();
21025 for _ in 0..holder_count {
21026 let held_tx = held_tx.clone();
21027 let release = Arc::clone(&release);
21028 let handle = executor.schedule_blocking(move || {
21029 held_tx.send(()).unwrap();
21030 release.wait();
21031 0usize
21032 });
21033 holder_handles.push(handle);
21034 }
21035 for _ in 0..holder_count {
21037 held_rx.recv().unwrap();
21038 }
21039
21040 let mut driver_tasks = Vec::new();
21043 for _ in 0..driver_count {
21044 let exec = Arc::clone(&executor);
21045 let body = async move {
21046 let inner_exec = Arc::clone(&exec);
21047 let mut inner = std::pin::pin!(async move {
21048 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21049 assert_eq!(v, 5);
21050 });
21051 exec.block_on(inner.as_mut());
21052 };
21053 let scheduler = Arc::clone(&executor);
21054 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
21055 executor.schedule(runnable);
21056 driver_tasks.push(task);
21057 }
21058
21059 std::thread::sleep(Duration::from_millis(300));
21062
21063 release.wait();
21065
21066 for task in driver_tasks {
21067 futures::executor::block_on(task);
21068 }
21069 for handle in holder_handles {
21070 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21071 }
21072
21073 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21074 let _ = done_tx.send(());
21075 });
21076
21077 match done_rx.recv_timeout(Duration::from_secs(10)) {
21078 Ok(()) => runner.join().unwrap(),
21079 Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
21080 }
21081 let peak = peak_out.lock().unwrap().expect("peak captured");
21082 assert!(
21083 peak <= 2,
21084 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21085 );
21086 }
21087
21088 #[cfg(not(target_family = "wasm"))]
21089 #[test]
21090 fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
21091 use std::sync::{Barrier, mpsc};
21107 use std::time::Duration;
21108
21109 let (done_tx, done_rx) = mpsc::channel();
21110 let peak_out = Arc::new(Mutex::new(None));
21111 let peak_slot = Arc::clone(&peak_out);
21112 let runner = std::thread::spawn(move || {
21113 let metrics = Arc::new(RuntimeMetrics::default());
21114 let options = RuntimeOptions {
21115 flavor: RuntimeFlavor::MultiThread,
21116 worker_threads: 2,
21117 max_blocking_tasks: 2,
21118 thread_name_prefix: "rd1-owner-runnable".to_string(),
21119 park_deadline: None,
21120 drain_linger: DEFAULT_DRAIN_LINGER,
21121 };
21122 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21123
21124 let saturated = Arc::new(Barrier::new(2));
21125 let release = Arc::new(Barrier::new(2));
21126
21127 let h2_saturated = Arc::clone(&saturated);
21129 let h2_release = Arc::clone(&release);
21130 let h2 = executor.schedule_blocking(move || {
21131 h2_saturated.wait();
21132 h2_release.wait();
21133 0usize
21134 });
21135
21136 let o_exec = Arc::clone(&executor);
21139 let o_saturated = Arc::clone(&saturated);
21140 let o = executor.schedule_blocking(move || {
21141 o_saturated.wait(); let r_exec = Arc::clone(&o_exec);
21144 let body = async move {
21145 let inner_exec = Arc::clone(&r_exec);
21146 let mut inner = std::pin::pin!(async move {
21147 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21148 assert_eq!(v, 5);
21149 });
21150 r_exec.block_on(inner.as_mut());
21151 };
21152 let sched = Arc::clone(&o_exec);
21153 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21154 o_exec.schedule(runnable);
21155
21156 let mut wait_r = std::pin::pin!(async move {
21157 task.await;
21158 });
21159 o_exec.block_on(wait_r.as_mut());
21160 1usize
21161 });
21162
21163 std::thread::sleep(Duration::from_millis(300));
21166 release.wait();
21168
21169 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21170 assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
21171
21172 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21173 let _ = done_tx.send(());
21174 });
21175
21176 match done_rx.recv_timeout(Duration::from_secs(10)) {
21177 Ok(()) => runner.join().unwrap(),
21178 Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
21179 }
21180 let peak = peak_out.lock().unwrap().expect("peak captured");
21181 assert!(
21182 peak <= 2,
21183 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21184 );
21185 }
21186
21187 #[cfg(not(target_family = "wasm"))]
21188 #[test]
21189 fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
21190 use std::sync::{Barrier, mpsc};
21199
21200 let (done_tx, done_rx) = mpsc::channel();
21201 let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21202 let order_read = Arc::clone(&order_out);
21203 let runner = std::thread::spawn(move || {
21204 let metrics = Arc::new(RuntimeMetrics::default());
21205 let options = RuntimeOptions {
21206 flavor: RuntimeFlavor::MultiThread,
21207 worker_threads: 2,
21208 max_blocking_tasks: 2,
21209 thread_name_prefix: "rd1-unrelated".to_string(),
21210 park_deadline: None,
21211 drain_linger: DEFAULT_DRAIN_LINGER,
21212 };
21213 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21214
21215 let (held_tx, held_rx) = mpsc::channel();
21218 let u_ready = Arc::new(Barrier::new(2)); let h_release = Arc::new(Barrier::new(2)); let h_held = held_tx.clone();
21223 let h_rel = Arc::clone(&h_release);
21224 let h = executor.schedule_blocking(move || {
21225 h_held.send(()).unwrap();
21226 h_rel.wait();
21227 0usize
21228 });
21229
21230 let o_exec = Arc::clone(&executor);
21233 let o_ready = Arc::clone(&u_ready);
21234 let o_order = Arc::clone(&order_out);
21235 let o = executor.schedule_blocking(move || {
21236 held_tx.send(()).unwrap();
21237 o_ready.wait(); let inner_exec = Arc::clone(&o_exec);
21239 let d_order = Arc::clone(&o_order);
21240 let mut inner = std::pin::pin!(async move {
21241 let v = inner_exec
21242 .schedule_blocking(move || {
21243 d_order.lock().unwrap().push("D");
21244 5usize
21245 })
21246 .await
21247 .unwrap();
21248 assert_eq!(v, 5);
21249 });
21250 o_exec.block_on(inner.as_mut());
21251 1usize
21252 });
21253
21254 held_rx.recv().unwrap();
21256 held_rx.recv().unwrap();
21257
21258 let u_order = Arc::clone(&order_out);
21260 let u = executor.schedule_blocking(move || {
21261 u_order.lock().unwrap().push("U");
21262 2usize
21263 });
21264
21265 u_ready.wait();
21267
21268 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21270 assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
21271
21272 h_release.wait();
21274 assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
21275
21276 let _ = done_tx.send(());
21277 });
21278
21279 match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
21280 Ok(()) => runner.join().unwrap(),
21281 Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
21282 }
21283 let order = order_read.lock().unwrap().clone();
21284 assert_eq!(
21285 order,
21286 vec!["D", "U"],
21287 "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
21288 );
21289 }
21290
21291 #[cfg(not(target_family = "wasm"))]
21292 #[test]
21293 fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
21294 use std::sync::{Barrier, mpsc};
21295
21296 let opts = RuntimeOptions {
21297 flavor: RuntimeFlavor::MultiThread,
21298 worker_threads: 2,
21299 max_blocking_tasks: 2,
21300 thread_name_prefix: "rd1-scope".to_string(),
21301 park_deadline: None,
21302 drain_linger: DEFAULT_DRAIN_LINGER,
21303 };
21304 let exec1 =
21305 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21306 let exec2 =
21307 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21308 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21309
21310 let holder_count = opts.max_blocking_tasks;
21311 let (held_tx, held_rx) = mpsc::channel();
21312 let release = Arc::new(Barrier::new(holder_count + 1));
21313 let mut holder_handles = Vec::new();
21314 for _ in 0..holder_count {
21315 let held_tx = held_tx.clone();
21316 let release = Arc::clone(&release);
21317 holder_handles.push(exec2.schedule_blocking(move || {
21318 held_tx.send(()).unwrap();
21319 release.wait();
21320 0usize
21321 }));
21322 }
21323 for _ in 0..holder_count {
21324 held_rx.recv().unwrap();
21325 }
21326 assert_eq!(
21327 exec2.active_blocking.load(Ordering::Acquire),
21328 opts.max_blocking_tasks
21329 );
21330
21331 let exact = BlockingJobId(u64::MAX);
21332 let ran = Arc::new(AtomicBool::new(false));
21333 let ran_job = Arc::clone(&ran);
21334 exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
21335 id: exact,
21336 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21337 });
21338 let dependency = TaskDependency::default();
21339
21340 let token1 = exec1.fresh_owner_token();
21341 {
21342 let _owner = BlockingOwnerGuard::enter(Some(token1));
21343 dependency.set_owned(BlockingDependency {
21344 job: exact,
21345 owner: Some(token1),
21346 });
21347 assert!(
21348 !exec2.try_owned_blocking_over_cap(&dependency),
21349 "a foreign-executor ambient token must fail the executor gate"
21350 );
21351 }
21352
21353 let token2 = exec2.fresh_owner_token();
21354 {
21355 let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
21356 let _owner = BlockingOwnerGuard::enter(Some(token2));
21357 dependency.set_owned(BlockingDependency {
21358 job: BlockingJobId(u64::MAX - 1),
21359 owner: Some(token2),
21360 });
21361 assert!(
21362 !exec2.try_owned_blocking_over_cap(&dependency),
21363 "a foreign or unrelated dependency id must fail the exact-job gate"
21364 );
21365 dependency.set_owned(BlockingDependency {
21366 job: BlockingJobId(exact.0 - 2),
21367 owner: Some(token2),
21368 });
21369 assert!(
21370 !exec2.try_owned_blocking_over_cap(&dependency),
21371 "a different job id must not consume the queued dependency"
21372 );
21373 dependency.set_owned(BlockingDependency {
21374 job: exact,
21375 owner: Some(token2),
21376 });
21377 assert!(
21378 exec2.try_owned_blocking_over_cap(&dependency),
21379 "the exact awaited job must run over cap"
21380 );
21381 }
21382 assert!(
21383 ran.load(Ordering::SeqCst),
21384 "the exact dependency must have run"
21385 );
21386
21387 release.wait();
21388 for handle in holder_handles {
21389 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21390 }
21391 }
21392
21393 #[cfg(not(target_family = "wasm"))]
21394 #[test]
21395 fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
21396 let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
21397 let first_owner = executor.fresh_owner_token();
21398 let second_owner = executor.fresh_owner_token();
21399 let _first_owner =
21400 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
21401 let _second_owner =
21402 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
21403 let exact = BlockingJobId(u64::MAX - 30);
21404 let dependency = TaskDependency::default();
21405 dependency.set_owned(BlockingDependency {
21406 job: exact,
21407 owner: None,
21408 });
21409 let ran = Arc::new(AtomicBool::new(false));
21410 let ran_job = Arc::clone(&ran);
21411 executor
21412 .blocking_queue
21413 .lock()
21414 .unwrap()
21415 .push(QueuedBlocking {
21416 id: exact,
21417 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21418 });
21419
21420 assert!(
21421 !executor.try_owned_blocking_over_cap(&dependency),
21422 "an untagged driver must not choose between multiple active owner frames"
21423 );
21424 assert!(!ran.load(Ordering::SeqCst));
21425 let queued = executor.blocking_queue.lock().unwrap().remove(exact);
21426 assert!(
21427 queued.is_some(),
21428 "ambiguous ownership must leave the exact job queued"
21429 );
21430 }
21431
21432 #[cfg(not(target_family = "wasm"))]
21433 #[test]
21434 fn owner_lending_claims_live_dependency_not_stale_snapshot() {
21435 let executor = multi_thread_executor(2, None, "live-parked-dependency");
21436 let owner = executor.fresh_owner_token();
21437 let dependency = TaskDependency::new(u64::MAX, Some(owner));
21438 let stale = BlockingJobId(u64::MAX - 10);
21439 let live = BlockingJobId(u64::MAX - 9);
21440 dependency.set_owned(BlockingDependency {
21441 job: stale,
21442 owner: Some(owner),
21443 });
21444 dependency.begin_poll();
21445 dependency.set_owned(BlockingDependency {
21446 job: live,
21447 owner: Some(owner),
21448 });
21449
21450 let stale_ran = Arc::new(AtomicBool::new(false));
21451 let stale_job_ran = Arc::clone(&stale_ran);
21452 let live_ran = Arc::new(AtomicBool::new(false));
21453 let live_job_ran = Arc::clone(&live_ran);
21454 {
21455 let mut queue = executor
21456 .blocking_queue
21457 .lock()
21458 .unwrap_or_else(std::sync::PoisonError::into_inner);
21459 queue.push(QueuedBlocking {
21460 id: stale,
21461 run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
21462 });
21463 queue.push(QueuedBlocking {
21464 id: live,
21465 run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
21466 });
21467 }
21468
21469 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21470 let _owner = BlockingOwnerGuard::enter(Some(owner));
21471 assert!(
21472 executor.try_owned_blocking_over_cap(&dependency),
21473 "the owner lane must claim the currently published dependency"
21474 );
21475 assert!(
21476 live_ran.load(Ordering::SeqCst),
21477 "the live dependency must run"
21478 );
21479 assert!(
21480 !stale_ran.load(Ordering::SeqCst),
21481 "the stale registered id must remain queued"
21482 );
21483 let remaining = executor
21484 .blocking_queue
21485 .lock()
21486 .unwrap_or_else(std::sync::PoisonError::into_inner)
21487 .jobs
21488 .keys()
21489 .copied()
21490 .collect::<Vec<_>>();
21491 assert_eq!(remaining, vec![stale]);
21492 }
21493
21494 #[cfg(not(target_family = "wasm"))]
21495 #[test]
21496 fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
21497 let executor = multi_thread_executor(2, None, "cancelled-child-publication");
21498 let owner = executor.fresh_owner_token();
21499 let child = TaskDependency::new_task(u64::MAX);
21500 let parent = TaskDependency::new(u64::MAX, Some(owner));
21501 let exact = BlockingDependency {
21502 job: BlockingJobId(u64::MAX - 31),
21503 owner: Some(owner),
21504 };
21505
21506 child.set_owned(exact);
21507 let stale = child
21508 .get()
21509 .expect("the child must publish its direct dependency");
21510 child.begin_poll();
21511 parent.set_borrowed(&stale);
21512
21513 let ran = Arc::new(AtomicBool::new(false));
21514 let ran_job = Arc::clone(&ran);
21515 executor
21516 .blocking_queue
21517 .lock()
21518 .unwrap()
21519 .push(QueuedBlocking {
21520 id: exact.job,
21521 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21522 });
21523 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21524 let _owner = BlockingOwnerGuard::enter(Some(owner));
21525 assert!(
21526 !executor.try_owned_blocking_over_cap(&parent),
21527 "a publication copied before child cancellation must remain cancelled after parent install"
21528 );
21529 assert!(!ran.load(Ordering::SeqCst));
21530
21531 child.set_owned(exact);
21532 parent.set_borrowed(
21533 &child
21534 .get()
21535 .expect("the replacement publication must be live"),
21536 );
21537 assert!(
21538 executor.try_owned_blocking_over_cap(&parent),
21539 "a fresh child publication must still authorize its exact queued job"
21540 );
21541 assert!(ran.load(Ordering::SeqCst));
21542 }
21543
21544 #[cfg(not(target_family = "wasm"))]
21545 #[test]
21546 fn intermediate_repoll_does_not_cancel_child_source_claim() {
21547 let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
21548 let owner = executor.fresh_owner_token();
21549 let child = TaskDependency::new_task(u64::MAX);
21550 let intermediate = TaskDependency::new_task(u64::MAX);
21551 let parent = TaskDependency::new(u64::MAX, Some(owner));
21552 let exact = BlockingDependency {
21553 job: BlockingJobId(u64::MAX - 43),
21554 owner: Some(owner),
21555 };
21556
21557 child.set_owned(exact);
21558 intermediate.set_borrowed(&child.get().expect("the child source must be live"));
21559 parent.set_borrowed(
21560 &intermediate
21561 .get()
21562 .expect("the intermediate publication must be live"),
21563 );
21564
21565 intermediate.begin_poll();
21566 assert!(
21567 child.get().is_some(),
21568 "withdrawing an intermediate hop must not cancel the child's source claim"
21569 );
21570 assert!(
21571 parent.get().is_none(),
21572 "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
21573 );
21574
21575 intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
21576 parent.set_borrowed(
21577 &intermediate
21578 .get()
21579 .expect("the intermediate must republish the still-live source"),
21580 );
21581 let ran = Arc::new(AtomicBool::new(false));
21582 let ran_job = Arc::clone(&ran);
21583 executor
21584 .blocking_queue
21585 .lock()
21586 .unwrap()
21587 .push(QueuedBlocking {
21588 id: exact.job,
21589 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21590 });
21591
21592 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21593 let _owner = BlockingOwnerGuard::enter(Some(owner));
21594 assert!(
21595 executor.try_owned_blocking_over_cap(&parent),
21596 "a fresh chain through the repolled intermediate must retain the exact source claim"
21597 );
21598 assert!(ran.load(Ordering::SeqCst));
21599 }
21600
21601 #[cfg(not(target_family = "wasm"))]
21602 #[test]
21603 fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
21604 use std::sync::mpsc;
21605
21606 let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
21607 let owner = executor.fresh_owner_token();
21608 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21609 let child = Arc::new(TaskDependency::new_task(u64::MAX));
21610 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21611 let exact = BlockingDependency {
21612 job: BlockingJobId(u64::MAX - 44),
21613 owner: Some(owner),
21614 };
21615 child.set_owned(exact);
21616 parent.set_borrowed(&child.get().expect("the child source must be live"));
21617
21618 let ran = Arc::new(AtomicBool::new(false));
21619 let ran_job = Arc::clone(&ran);
21620 executor
21621 .blocking_queue
21622 .lock()
21623 .unwrap()
21624 .push(QueuedBlocking {
21625 id: exact.job,
21626 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21627 });
21628
21629 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
21630 let (release_claim_tx, release_claim_rx) = mpsc::channel();
21631 let claimant_executor = Arc::clone(&executor);
21632 let claimant_parent = Arc::clone(&parent);
21633 let claimant = std::thread::spawn(move || {
21634 DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
21635 *slot.borrow_mut() = Some(Box::new(move || {
21636 claim_entered_tx.send(()).unwrap();
21637 release_claim_rx.recv().unwrap();
21638 }));
21639 });
21640 let _owner = BlockingOwnerGuard::enter(Some(owner));
21641 claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
21642 });
21643 claim_entered_rx
21644 .recv_timeout(Duration::from_secs(2))
21645 .expect("the exact claimant must hold the shared transition lock");
21646
21647 let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
21648 let withdrawing_child = Arc::clone(&child);
21649 let withdrawal = std::thread::spawn(move || {
21650 withdrawing_child.begin_poll();
21651 withdrawn_tx.send(()).unwrap();
21652 });
21653 let withdrew_before_claim = withdrawn_rx
21654 .recv_timeout(Duration::from_millis(200))
21655 .is_ok();
21656 release_claim_tx.send(()).unwrap();
21657
21658 assert!(
21659 claimant.join().unwrap(),
21660 "the claim that linearized first must execute the exact job"
21661 );
21662 withdrawal.join().unwrap();
21663 assert!(
21664 !withdrew_before_claim,
21665 "source withdrawal must not interleave after claim validation but before claim commit"
21666 );
21667 assert!(ran.load(Ordering::SeqCst));
21668 }
21669
21670 #[cfg(not(target_family = "wasm"))]
21671 #[test]
21672 fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
21673 struct PendThenReady {
21694 polls: usize,
21695 }
21696 impl Future for PendThenReady {
21697 type Output = ();
21698 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21699 self.polls += 1;
21700 if self.polls >= 2 {
21701 Poll::Ready(())
21702 } else {
21703 cx.waker().wake_by_ref();
21707 Poll::Pending
21708 }
21709 }
21710 }
21711
21712 fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
21715 let scheduler = Arc::clone(target);
21716 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
21717 target.push_fifo_raw(runnable);
21718 task.detach();
21719 }
21720
21721 let opts = RuntimeOptions {
21722 flavor: RuntimeFlavor::MultiThread,
21723 worker_threads: 2,
21724 max_blocking_tasks: 2,
21725 thread_name_prefix: "rd-onpool-scope".to_string(),
21726 park_deadline: None,
21727 drain_linger: DEFAULT_DRAIN_LINGER,
21728 };
21729 let exec1 =
21730 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21731 let exec2 =
21732 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21733 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21734
21735 let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
21737
21738 enqueue_runnable(&exec2);
21742 {
21743 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21744 exec2.block_on(probe.as_mut());
21745 }
21746 assert_eq!(
21747 exec2.metrics.runnable_polls.load(Ordering::Relaxed),
21748 0,
21749 "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
21750 );
21751 assert_eq!(
21752 exec2.injector.len(),
21753 1,
21754 "the foreign call must leave exec2's runnable untouched on its queue"
21755 );
21756
21757 enqueue_runnable(&exec1);
21761 {
21762 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21763 exec1.block_on(probe.as_mut());
21764 }
21765 assert_eq!(
21766 exec1.metrics.runnable_polls.load(Ordering::Relaxed),
21767 1,
21768 "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
21769 );
21770 assert!(
21771 exec1.injector.is_empty(),
21772 "the cooperative branch must have drained exec1's queued runnable"
21773 );
21774
21775 while exec2.steal_one().is_some() {}
21778 }
21779
21780 #[cfg(not(target_family = "wasm"))]
21781 #[test]
21782 fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
21783 use std::sync::mpsc;
21787 use std::time::Duration;
21788
21789 const PRODUCER_COUNT: usize = 8;
21790
21791 fn schedule_pool_workload(
21792 executor: &Arc<MultiThreadExecutor>,
21793 tx: &mpsc::Sender<Option<usize>>,
21794 count: usize,
21795 ) {
21796 for i in 0..count {
21797 let tx = tx.clone();
21798 let scheduler = Arc::clone(executor);
21799 let (runnable, task) =
21800 async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
21801 scheduler.schedule(r);
21802 });
21803 executor.schedule(runnable);
21804 task.detach();
21805 }
21806 let tx = tx.clone();
21807 let scheduler = Arc::clone(executor);
21808 let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
21809 scheduler.schedule(r)
21810 });
21811 executor.schedule(runnable);
21812 task.detach();
21813 }
21814
21815 let expected_sum: usize = (0..PRODUCER_COUNT).sum();
21816 let (done_tx, done_rx) = mpsc::sync_channel(1);
21817 let runner = std::thread::spawn(move || {
21818 let options = RuntimeOptions {
21819 flavor: RuntimeFlavor::MultiThread,
21820 worker_threads: 1,
21821 max_blocking_tasks: 1,
21822 thread_name_prefix: "rd2-pre-fix".to_string(),
21823 park_deadline: None,
21824 drain_linger: DEFAULT_DRAIN_LINGER,
21825 }
21826 .validate()
21827 .expect("production MultiThread options must provide a reserve lane");
21828 let executor =
21829 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21830
21831 let (tx, rx) = mpsc::channel::<Option<usize>>();
21832 let (started_tx, started_rx) = mpsc::sync_channel(1);
21833 let handle = executor.schedule_blocking(move || {
21834 started_tx.send(()).unwrap();
21835 let mut sum = 0usize;
21836 while let Ok(msg) = rx.recv() {
21837 match msg {
21838 Some(value) => sum += value,
21839 None => break,
21840 }
21841 }
21842 sum
21843 });
21844 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
21845 schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
21846 let sum = futures::executor::block_on(handle).unwrap();
21847 done_tx.send(sum).unwrap();
21848 });
21849
21850 match done_rx.recv_timeout(Duration::from_secs(10)) {
21851 Ok(sum) => {
21852 runner.join().unwrap();
21853 assert_eq!(sum, expected_sum);
21854 }
21855 Err(error) => {
21856 panic!("a saturated blocking lane starved the async producers that feed it ({error})")
21857 }
21858 }
21859 }
21860
21861 #[test]
21862 fn current_thread_blocking_work_completes_inline() {
21863 let metrics = Arc::new(RuntimeMetrics::default());
21864 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
21865 CurrentThreadExecutor::new(Arc::clone(&metrics)),
21866 )));
21867 let registration = backend.work.try_register_work().unwrap();
21868
21869 let value =
21870 futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
21871 .expect("blocking task should complete");
21872
21873 assert_eq!(value, 7);
21874 assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
21875 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
21876 }
21877
21878 #[cfg(not(target_family = "wasm"))]
21879 #[test]
21880 fn current_thread_blocking_work_obeys_single_lane_across_callers() {
21881 use std::sync::mpsc;
21882
21883 let controller = Arc::new(current_thread_controller(
21884 "current-thread-blocking-admission",
21885 ));
21886 let backend = controller.backend();
21887 let (first_started_tx, first_started_rx) = mpsc::channel();
21888 let (release_first_tx, release_first_rx) = mpsc::channel();
21889 let first_controller = Arc::clone(&controller);
21890 let first = std::thread::spawn(move || {
21891 let handle = first_controller
21892 .try_spawn_blocking(move || {
21893 first_started_tx.send(()).unwrap();
21894 release_first_rx.recv().unwrap();
21895 1usize
21896 })
21897 .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
21898 futures::executor::block_on(handle).unwrap()
21899 });
21900 first_started_rx
21901 .recv_timeout(Duration::from_secs(1))
21902 .unwrap();
21903
21904 let (second_started_tx, second_started_rx) = mpsc::channel();
21905 let second_controller = Arc::clone(&controller);
21906 let second = std::thread::spawn(move || {
21907 let handle = second_controller
21908 .try_spawn_blocking(move || {
21909 second_started_tx.send(()).unwrap();
21910 2usize
21911 })
21912 .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
21913 futures::executor::block_on(handle).unwrap()
21914 });
21915
21916 wait_until("both CurrentThread blocking calls to be accepted", || {
21917 backend
21918 .work
21919 .state
21920 .lock()
21921 .unwrap_or_else(std::sync::PoisonError::into_inner)
21922 .active
21923 == 2
21924 });
21925 assert!(
21926 second_started_rx
21927 .recv_timeout(Duration::from_millis(100))
21928 .is_err(),
21929 "CurrentThread must not execute two blocking closures concurrently"
21930 );
21931
21932 release_first_tx.send(()).unwrap();
21933 second_started_rx
21934 .recv_timeout(Duration::from_secs(1))
21935 .expect("the waiting blocking call must acquire the released lane");
21936 assert_eq!(first.join().unwrap(), 1);
21937 assert_eq!(second.join().unwrap(), 2);
21938 assert_eq!(
21939 controller
21940 .metrics
21941 .blocking_tasks_started
21942 .load(Ordering::Relaxed),
21943 2
21944 );
21945 assert_eq!(
21946 controller
21947 .metrics
21948 .blocking_tasks_completed
21949 .load(Ordering::Relaxed),
21950 2
21951 );
21952 assert_eq!(
21953 controller
21954 .metrics
21955 .active_blocking_tasks
21956 .load(Ordering::Relaxed),
21957 0
21958 );
21959 assert_eq!(
21960 controller
21961 .metrics
21962 .max_active_blocking_tasks
21963 .load(Ordering::Relaxed),
21964 1,
21965 "the public active-lane metric must remain within CurrentThread's reported capacity"
21966 );
21967 controller.shutdown().unwrap();
21968 }
21969
21970 #[cfg(not(target_family = "wasm"))]
21971 #[test]
21972 fn current_thread_nested_blocking_reuses_the_active_lane() {
21973 use std::sync::mpsc;
21974
21975 let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
21976 let (done_tx, done_rx) = mpsc::channel();
21977 let runner_controller = Arc::clone(&controller);
21978 let runner = std::thread::spawn(move || {
21979 let nested_controller = Arc::clone(&runner_controller);
21980 let outer = runner_controller
21981 .try_spawn_blocking(move || {
21982 let inner = nested_controller
21983 .try_spawn_blocking(|| 41usize)
21984 .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
21985 futures::executor::block_on(inner).unwrap() + 1
21986 })
21987 .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
21988 done_tx
21989 .send(futures::executor::block_on(outer).unwrap())
21990 .unwrap();
21991 });
21992
21993 assert_eq!(
21994 done_rx
21995 .recv_timeout(Duration::from_secs(2))
21996 .expect("a nested CurrentThread blocking call must not wait on its own lane"),
21997 42
21998 );
21999 runner.join().unwrap();
22000 assert_eq!(
22001 controller
22002 .metrics
22003 .blocking_tasks_started
22004 .load(Ordering::Relaxed),
22005 2
22006 );
22007 assert_eq!(
22008 controller
22009 .metrics
22010 .blocking_tasks_completed
22011 .load(Ordering::Relaxed),
22012 2
22013 );
22014 assert_eq!(
22015 controller
22016 .metrics
22017 .active_blocking_tasks
22018 .load(Ordering::Relaxed),
22019 0
22020 );
22021 assert_eq!(
22022 controller
22023 .metrics
22024 .max_active_blocking_tasks
22025 .load(Ordering::Relaxed),
22026 1,
22027 "nested work borrows the outer lane instead of reporting a second physical lane"
22028 );
22029 controller.shutdown().unwrap();
22030 }
22031
22032 #[cfg(not(target_family = "wasm"))]
22033 #[test]
22034 fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22035 use std::sync::mpsc;
22036
22037 struct HotFuture {
22038 stop: Arc<AtomicBool>,
22039 }
22040
22041 impl Future for HotFuture {
22042 type Output = ();
22043
22044 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22045 if self.stop.load(Ordering::SeqCst) {
22046 Poll::Ready(())
22047 } else {
22048 cx.waker().wake_by_ref();
22049 Poll::Pending
22050 }
22051 }
22052 }
22053
22054 let stop = Arc::new(AtomicBool::new(false));
22055 let (ready_tx, ready_rx) = mpsc::channel();
22056 let (exact_tx, exact_rx) = mpsc::channel();
22057 let (done_tx, done_rx) = mpsc::channel();
22058 let runner_stop = Arc::clone(&stop);
22059 let runner = std::thread::spawn(move || {
22060 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22061 RuntimeMetrics::default(),
22062 )));
22063 let owner = executor.fresh_owner_token();
22064 let job = BlockingJobId(u64::MAX - 70);
22065 let (result_tx, result_rx) = oneshot::channel();
22066 {
22067 let mut state = executor
22068 .blocking_admission
22069 .state
22070 .lock()
22071 .unwrap_or_else(std::sync::PoisonError::into_inner);
22072 state.owner = Some(owner);
22073 let exact_stop = Arc::clone(&runner_stop);
22074 state.queue.push(QueuedBlocking {
22075 id: job,
22076 run: Box::new(move || {
22077 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22078 let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
22079 }),
22080 });
22081 }
22082 let exact = JoinHandle(JoinHandleInner::Blocking {
22083 receiver: result_rx,
22084 dependency: BlockingDependency {
22085 job,
22086 owner: Some(owner),
22087 },
22088 published: PublishedBlockingDependencies::default(),
22089 });
22090 let _owner = executor.enter_blocking_owner(owner, false);
22091
22092 let scheduler = Arc::clone(&executor);
22093 let hot_stop = Arc::clone(&runner_stop);
22094 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22095 scheduler.schedule(runnable);
22096 });
22097 executor.schedule(runnable);
22098 ready_tx.send(()).unwrap();
22099
22100 let mut value = None;
22101 {
22102 let mut future = std::pin::pin!(async {
22103 value = Some(exact.await.unwrap());
22104 hot_task.await;
22105 });
22106 assert_eq!(
22107 executor.block_on(future.as_mut()),
22108 BlockOnOutcome::Completed
22109 );
22110 }
22111 executor
22112 .blocking_admission
22113 .state
22114 .lock()
22115 .unwrap_or_else(std::sync::PoisonError::into_inner)
22116 .owner = None;
22117 done_tx.send(value.unwrap()).unwrap();
22118 });
22119
22120 ready_rx
22121 .recv_timeout(Duration::from_secs(2))
22122 .expect("the fairness driver must start");
22123 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22124 stop.store(true, Ordering::SeqCst);
22125 assert_eq!(
22126 done_rx
22127 .recv_timeout(Duration::from_secs(2))
22128 .expect("the CurrentThread driver must retire"),
22129 41
22130 );
22131 runner.join().unwrap();
22132 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22133 assert_eq!(
22134 exact_before_stop,
22135 Some(true),
22136 "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
22137 );
22138 }
22139
22140 #[cfg(not(target_family = "wasm"))]
22141 #[test]
22142 fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
22143 use std::sync::mpsc;
22144
22145 let controller = Arc::new(current_thread_controller(
22146 "current-thread-cross-driver-blocking",
22147 ));
22148 let backend = controller.backend();
22149 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22150 panic!("configured CurrentThread backend must create a CurrentThread executor");
22151 };
22152 let executor = Arc::clone(executor);
22153 drop(backend);
22154
22155 let (release_driver_tx, release_driver_rx) = oneshot::channel();
22156 let (driver_started_tx, driver_started_rx) = mpsc::channel();
22157 let driver_controller = Arc::clone(&controller);
22158 let driver = std::thread::spawn(move || {
22159 driver_started_tx.send(std::thread::current().id()).unwrap();
22160 block_on_with_controller(&driver_controller, async {
22161 release_driver_rx
22162 .await
22163 .expect("driver release sender must remain alive");
22164 });
22165 });
22166 let driver_thread = driver_started_rx
22167 .recv_timeout(Duration::from_secs(1))
22168 .unwrap();
22169 wait_until("the secondary CurrentThread driver to park", || {
22170 executor
22171 .stop
22172 .parkers
22173 .lock()
22174 .unwrap_or_else(std::sync::PoisonError::into_inner)
22175 .iter()
22176 .filter_map(Weak::upgrade)
22177 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22178 });
22179
22180 let (child_polled_tx, child_polled_rx) = mpsc::channel();
22181 let (release_child_tx, release_child_rx) = mpsc::channel();
22182 let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
22183 let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
22184 let (owner_done_tx, owner_done_rx) = mpsc::channel();
22185 let owner_controller = Arc::clone(&controller);
22186 let owner = std::thread::spawn(move || {
22187 let owner_thread = std::thread::current().id();
22188 let await_controller = Arc::clone(&owner_controller);
22189 let child_controller = Arc::clone(&owner_controller);
22190 let outer = owner_controller
22191 .try_spawn_blocking(move || {
22192 let spawn_controller = Arc::clone(&child_controller);
22193 let child = spawn_controller
22194 .try_spawn(async move {
22195 child_polled_tx.send(std::thread::current().id()).unwrap();
22196 release_child_rx.recv().unwrap();
22197 child_controller
22198 .try_spawn_blocking(move || {
22199 blocking_ran_tx.send(std::thread::current().id()).unwrap();
22200 41usize
22201 })
22202 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
22203 .await
22204 .unwrap()
22205 + 1
22206 })
22207 .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
22208
22209 assert_eq!(
22210 child_polled_rx
22211 .recv_timeout(Duration::from_secs(1))
22212 .unwrap(),
22213 driver_thread,
22214 "the secondary driver must own the child poll that reaches blocking admission"
22215 );
22216 owner_awaiting_tx.send(owner_thread).unwrap();
22217 block_on_with_controller(&await_controller, child).unwrap()
22218 })
22219 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22220 owner_done_tx
22221 .send(futures::executor::block_on(outer))
22222 .unwrap();
22223 });
22224
22225 let owner_thread = owner_awaiting_rx
22226 .recv_timeout(Duration::from_secs(1))
22227 .unwrap();
22228 wait_until(
22229 "the blocking owner to park while awaiting the cross-driver child",
22230 || {
22231 executor
22232 .stop
22233 .parkers
22234 .lock()
22235 .unwrap_or_else(std::sync::PoisonError::into_inner)
22236 .iter()
22237 .filter_map(Weak::upgrade)
22238 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22239 },
22240 );
22241 release_child_tx.send(()).unwrap();
22242
22243 assert_eq!(
22244 blocking_ran_rx
22245 .recv_timeout(Duration::from_secs(2))
22246 .unwrap(),
22247 owner_thread,
22248 "the exact descendant must be serviced by the lane-owning driver"
22249 );
22250 assert_eq!(
22251 owner_done_rx
22252 .recv_timeout(Duration::from_secs(2))
22253 .expect("cross-driver blocking dependency must not deadlock")
22254 .unwrap(),
22255 42
22256 );
22257 release_driver_tx.send(()).unwrap();
22258 owner.join().unwrap();
22259 driver.join().unwrap();
22260 controller.shutdown().unwrap();
22261 assert_eq!(
22262 controller
22263 .metrics
22264 .blocking_tasks_started
22265 .load(Ordering::Relaxed),
22266 2
22267 );
22268 assert_eq!(
22269 controller
22270 .metrics
22271 .blocking_tasks_completed
22272 .load(Ordering::Relaxed),
22273 2
22274 );
22275 assert_eq!(
22276 controller
22277 .metrics
22278 .active_blocking_tasks
22279 .load(Ordering::Relaxed),
22280 0
22281 );
22282 assert_eq!(
22283 controller
22284 .metrics
22285 .max_active_blocking_tasks
22286 .load(Ordering::Relaxed),
22287 1,
22288 "cross-driver dependency service must reuse the owner's physical lane"
22289 );
22290 }
22291
22292 #[cfg(not(target_family = "wasm"))]
22293 #[test]
22294 fn current_thread_blocking_admission_rechecks_stop_under_lock() {
22295 use std::sync::mpsc;
22296
22297 let controller = Arc::new(current_thread_controller(
22298 "current-thread-blocking-stop-race",
22299 ));
22300 let backend = controller.backend();
22301 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22302 panic!("configured CurrentThread backend must create a CurrentThread executor");
22303 };
22304 let executor = Arc::clone(executor);
22305 drop(backend);
22306
22307 let ran = Arc::new(AtomicBool::new(false));
22308 let blocking_ran = Arc::clone(&ran);
22309 let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
22310 let (release_admission_tx, release_admission_rx) = mpsc::channel();
22311 let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
22312 let submit_controller = Arc::clone(&controller);
22313 let submitter = std::thread::spawn(move || {
22314 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22315 *slot.borrow_mut() = Some(Box::new(move || {
22316 admission_locked_tx.send(()).unwrap();
22317 release_admission_rx.recv().unwrap();
22318 }));
22319 });
22320 let handle = submit_controller
22321 .try_spawn_blocking(move || {
22322 blocking_ran.store(true, Ordering::SeqCst);
22323 })
22324 .unwrap_or_else(|_| {
22325 panic!("the submission linearized before shutdown and must be accepted")
22326 });
22327 blocking_result_tx
22328 .send(futures::executor::block_on(handle))
22329 .unwrap();
22330 });
22331 admission_locked_rx
22332 .recv_timeout(Duration::from_secs(2))
22333 .expect("the blocking submitter must pause while holding the admission lock");
22334
22335 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
22336 let shutdown_controller = Arc::clone(&controller);
22337 let shutdown = std::thread::spawn(move || {
22338 shutdown_result_tx
22339 .send(shutdown_controller.shutdown())
22340 .unwrap();
22341 });
22342 wait_until("CurrentThread shutdown to publish generation stop", || {
22343 executor.stop.is_stopping()
22344 });
22345 release_admission_tx.send(()).unwrap();
22346
22347 let blocking_error = blocking_result_rx
22348 .recv_timeout(Duration::from_secs(2))
22349 .expect("the admitted blocking handle must resolve")
22350 .expect_err("stop publication must reject the blocking lane claim");
22351 assert_eq!(
22352 blocking_error.to_string(),
22353 "the async runtime stopped before the blocking task could start"
22354 );
22355 assert!(
22356 !ran.load(Ordering::SeqCst),
22357 "blocking work must not start after stop publication"
22358 );
22359 shutdown_result_rx
22360 .recv_timeout(Duration::from_secs(2))
22361 .expect("shutdown must finish after the rejected admission retires")
22362 .unwrap();
22363 submitter.join().unwrap();
22364 shutdown.join().unwrap();
22365 }
22366
22367 #[cfg(not(target_family = "wasm"))]
22368 #[test]
22369 fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
22370 use std::sync::mpsc;
22371
22372 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22373 RuntimeMetrics::default(),
22374 )));
22375 let ran = Arc::new(AtomicBool::new(false));
22376 let job_ran = Arc::clone(&ran);
22377 {
22378 let mut state = executor
22379 .blocking_admission
22380 .state
22381 .lock()
22382 .unwrap_or_else(std::sync::PoisonError::into_inner);
22383 state.queue.push(QueuedBlocking {
22384 id: BlockingJobId(u64::MAX - 71),
22385 run: Box::new(move || {
22386 job_ran.store(true, Ordering::SeqCst);
22387 }),
22388 });
22389 }
22390
22391 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22392 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22393 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22394 let claim_executor = Arc::clone(&executor);
22395 let claimant = std::thread::spawn(move || {
22396 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22397 *slot.borrow_mut() = Some(Box::new(move || {
22398 claim_locked_tx.send(()).unwrap();
22399 release_claim_rx.recv().unwrap();
22400 }));
22401 });
22402 claim_result_tx
22403 .send(claim_executor.run_one_blocking())
22404 .unwrap();
22405 });
22406 claim_locked_rx
22407 .recv_timeout(Duration::from_secs(2))
22408 .expect("the FIFO claimant must pause while holding the admission lock");
22409
22410 executor.stop.begin_shutdown();
22411 release_claim_tx.send(()).unwrap();
22412 assert!(
22413 !claim_result_rx
22414 .recv_timeout(Duration::from_secs(2))
22415 .expect("the stopped FIFO claim must finish"),
22416 "stop publication must reject the queued FIFO claim"
22417 );
22418 claimant.join().unwrap();
22419 assert!(
22420 !ran.load(Ordering::SeqCst),
22421 "queued FIFO work must not start after stop publication"
22422 );
22423 assert!(
22424 !executor
22425 .blocking_admission
22426 .state
22427 .lock()
22428 .unwrap_or_else(std::sync::PoisonError::into_inner)
22429 .queue
22430 .is_empty(),
22431 "the rejected FIFO job must remain queued for shutdown cancellation"
22432 );
22433
22434 executor.close_blocking_admission();
22435 assert!(
22436 executor
22437 .blocking_admission
22438 .state
22439 .lock()
22440 .unwrap_or_else(std::sync::PoisonError::into_inner)
22441 .queue
22442 .is_empty()
22443 );
22444 }
22445
22446 #[cfg(not(target_family = "wasm"))]
22447 #[test]
22448 fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22449 use std::sync::mpsc;
22450
22451 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22452 RuntimeMetrics::default(),
22453 )));
22454 let ran = Arc::new(AtomicBool::new(false));
22455 let job_ran = Arc::clone(&ran);
22456 {
22457 let mut state = executor
22458 .blocking_admission
22459 .state
22460 .lock()
22461 .unwrap_or_else(std::sync::PoisonError::into_inner);
22462 state.queue.push(QueuedBlocking {
22463 id: BlockingJobId(u64::MAX - 73),
22464 run: Box::new(move || {
22465 job_ran.store(true, Ordering::SeqCst);
22466 }),
22467 });
22468 }
22469
22470 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22471 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22472 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22473 let claim_executor = Arc::clone(&executor);
22474 let claimant = std::thread::spawn(move || {
22475 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22476 *slot.borrow_mut() = Some(Box::new(move || {
22477 claim_locked_tx.send(()).unwrap();
22478 release_claim_rx.recv().unwrap();
22479 }));
22480 });
22481 claim_result_tx
22482 .send(claim_executor.run_one_blocking())
22483 .unwrap();
22484 });
22485 claim_locked_rx
22486 .recv_timeout(Duration::from_secs(2))
22487 .expect("the FIFO claimant must pause while holding the admission lock");
22488
22489 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22490 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22491 let cancelling_executor = Arc::clone(&executor);
22492 let cancellation = std::thread::spawn(move || {
22493 let cancelling = cancelling_executor
22494 .begin_failed_dispatch_cancellation()
22495 .expect("the test must publish terminal dispatch cancellation");
22496 cancellation_published_tx.send(()).unwrap();
22497 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22498 cancellation_done_tx.send(()).unwrap();
22499 });
22500 cancellation_published_rx
22501 .recv_timeout(Duration::from_secs(2))
22502 .expect("terminal dispatch cancellation must publish before queue cancellation");
22503 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22504 assert!(
22505 matches!(
22506 cancellation_done_rx.try_recv(),
22507 Err(mpsc::TryRecvError::Empty)
22508 ),
22509 "terminal cancellation must wait for the blocking-admission lock"
22510 );
22511
22512 release_claim_tx.send(()).unwrap();
22513 assert!(
22514 !claim_result_rx
22515 .recv_timeout(Duration::from_secs(2))
22516 .expect("the cancelled FIFO claim must finish"),
22517 "terminal cancellation publication must reject the queued FIFO claim"
22518 );
22519 cancellation_done_rx
22520 .recv_timeout(Duration::from_secs(2))
22521 .expect("terminal cancellation must drain the rejected FIFO job");
22522 claimant.join().unwrap();
22523 cancellation.join().unwrap();
22524
22525 assert!(
22526 !ran.load(Ordering::SeqCst),
22527 "terminally cancelled FIFO work must not start"
22528 );
22529 assert!(
22530 executor
22531 .blocking_admission
22532 .state
22533 .lock()
22534 .unwrap_or_else(std::sync::PoisonError::into_inner)
22535 .queue
22536 .is_empty(),
22537 "terminal cancellation must drain the rejected FIFO job"
22538 );
22539 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22540 }
22541
22542 #[cfg(not(target_family = "wasm"))]
22543 #[test]
22544 fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
22545 use std::sync::mpsc;
22546
22547 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22548 RuntimeMetrics::default(),
22549 )));
22550 let owner = executor.fresh_owner_token();
22551 let job = BlockingJobId(u64::MAX - 72);
22552 let ran = Arc::new(AtomicBool::new(false));
22553 let job_ran = Arc::clone(&ran);
22554 {
22555 let mut state = executor
22556 .blocking_admission
22557 .state
22558 .lock()
22559 .unwrap_or_else(std::sync::PoisonError::into_inner);
22560 state.owner = Some(owner);
22561 state.queue.push(QueuedBlocking {
22562 id: job,
22563 run: Box::new(move || {
22564 job_ran.store(true, Ordering::SeqCst);
22565 }),
22566 });
22567 }
22568 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22569 dependency.set_owned(BlockingDependency {
22570 job,
22571 owner: Some(owner),
22572 });
22573
22574 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22575 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22576 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22577 let claim_executor = Arc::clone(&executor);
22578 let claim_dependency = Arc::clone(&dependency);
22579 let claimant = std::thread::spawn(move || {
22580 let _owner = claim_executor.enter_blocking_owner(owner, false);
22581 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22582 *slot.borrow_mut() = Some(Box::new(move || {
22583 claim_locked_tx.send(()).unwrap();
22584 release_claim_rx.recv().unwrap();
22585 }));
22586 });
22587 claim_result_tx
22588 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22589 .unwrap();
22590 });
22591 claim_locked_rx
22592 .recv_timeout(Duration::from_secs(2))
22593 .expect("the owner claimant must pause while holding the admission lock");
22594
22595 executor.stop.begin_shutdown();
22596 release_claim_tx.send(()).unwrap();
22597 assert!(
22598 !claim_result_rx
22599 .recv_timeout(Duration::from_secs(2))
22600 .expect("the stopped owner claim must finish"),
22601 "stop publication must reject the exact-owner claim"
22602 );
22603 claimant.join().unwrap();
22604 assert!(
22605 !ran.load(Ordering::SeqCst),
22606 "queued exact-owner work must not start after stop publication"
22607 );
22608 assert!(
22609 !executor
22610 .blocking_admission
22611 .state
22612 .lock()
22613 .unwrap_or_else(std::sync::PoisonError::into_inner)
22614 .queue
22615 .is_empty(),
22616 "the rejected exact-owner job must remain queued for shutdown cancellation"
22617 );
22618
22619 executor
22620 .blocking_admission
22621 .state
22622 .lock()
22623 .unwrap_or_else(std::sync::PoisonError::into_inner)
22624 .owner = None;
22625 executor.close_blocking_admission();
22626 assert!(
22627 executor
22628 .blocking_admission
22629 .state
22630 .lock()
22631 .unwrap_or_else(std::sync::PoisonError::into_inner)
22632 .queue
22633 .is_empty()
22634 );
22635 }
22636
22637 #[cfg(not(target_family = "wasm"))]
22638 #[test]
22639 fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22640 use std::sync::mpsc;
22641
22642 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22643 RuntimeMetrics::default(),
22644 )));
22645 let owner = executor.fresh_owner_token();
22646 let job = BlockingJobId(u64::MAX - 74);
22647 let ran = Arc::new(AtomicBool::new(false));
22648 let job_ran = Arc::clone(&ran);
22649 {
22650 let mut state = executor
22651 .blocking_admission
22652 .state
22653 .lock()
22654 .unwrap_or_else(std::sync::PoisonError::into_inner);
22655 state.owner = Some(owner);
22656 state.queue.push(QueuedBlocking {
22657 id: job,
22658 run: Box::new(move || {
22659 job_ran.store(true, Ordering::SeqCst);
22660 }),
22661 });
22662 }
22663 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22664 dependency.set_owned(BlockingDependency {
22665 job,
22666 owner: Some(owner),
22667 });
22668
22669 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22670 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22671 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22672 let claim_executor = Arc::clone(&executor);
22673 let claim_dependency = Arc::clone(&dependency);
22674 let claimant = std::thread::spawn(move || {
22675 let _owner = claim_executor.enter_blocking_owner(owner, false);
22676 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22677 *slot.borrow_mut() = Some(Box::new(move || {
22678 claim_locked_tx.send(()).unwrap();
22679 release_claim_rx.recv().unwrap();
22680 }));
22681 });
22682 claim_result_tx
22683 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22684 .unwrap();
22685 });
22686 claim_locked_rx
22687 .recv_timeout(Duration::from_secs(2))
22688 .expect("the owner claimant must pause while holding the admission lock");
22689
22690 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22691 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22692 let cancelling_executor = Arc::clone(&executor);
22693 let cancellation = std::thread::spawn(move || {
22694 let cancelling = cancelling_executor
22695 .begin_failed_dispatch_cancellation()
22696 .expect("the test must publish terminal dispatch cancellation");
22697 cancellation_published_tx.send(()).unwrap();
22698 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22699 cancellation_done_tx.send(()).unwrap();
22700 });
22701 cancellation_published_rx
22702 .recv_timeout(Duration::from_secs(2))
22703 .expect("terminal dispatch cancellation must publish before queue cancellation");
22704 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22705 assert!(
22706 matches!(
22707 cancellation_done_rx.try_recv(),
22708 Err(mpsc::TryRecvError::Empty)
22709 ),
22710 "terminal cancellation must wait for the blocking-admission lock"
22711 );
22712
22713 release_claim_tx.send(()).unwrap();
22714 assert!(
22715 !claim_result_rx
22716 .recv_timeout(Duration::from_secs(2))
22717 .expect("the cancelled owner claim must finish"),
22718 "terminal cancellation publication must reject the exact-owner claim"
22719 );
22720 cancellation_done_rx
22721 .recv_timeout(Duration::from_secs(2))
22722 .expect("terminal cancellation must drain the rejected exact-owner job");
22723 claimant.join().unwrap();
22724 cancellation.join().unwrap();
22725
22726 assert!(
22727 !ran.load(Ordering::SeqCst),
22728 "terminally cancelled exact-owner work must not start"
22729 );
22730 assert!(
22731 executor
22732 .blocking_admission
22733 .state
22734 .lock()
22735 .unwrap_or_else(std::sync::PoisonError::into_inner)
22736 .queue
22737 .is_empty(),
22738 "terminal cancellation must drain the rejected exact-owner job"
22739 );
22740 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22741 }
22742
22743 #[cfg(not(target_family = "wasm"))]
22744 #[test]
22745 fn current_thread_shutdown_cancels_waiting_blocking_work() {
22746 use std::sync::mpsc;
22747
22748 struct DropSignal(mpsc::Sender<()>);
22749
22750 impl Drop for DropSignal {
22751 fn drop(&mut self) {
22752 let _ = self.0.send(());
22753 }
22754 }
22755
22756 let controller = Arc::new(current_thread_controller(
22757 "current-thread-blocking-shutdown",
22758 ));
22759 let backend = controller.backend();
22760 let (first_started_tx, first_started_rx) = mpsc::channel();
22761 let (release_first_tx, release_first_rx) = mpsc::channel();
22762 let first_controller = Arc::clone(&controller);
22763 let first = std::thread::spawn(move || {
22764 let handle = first_controller
22765 .try_spawn_blocking(move || {
22766 first_started_tx.send(()).unwrap();
22767 release_first_rx.recv().unwrap();
22768 })
22769 .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
22770 futures::executor::block_on(handle)
22771 });
22772 first_started_rx
22773 .recv_timeout(Duration::from_secs(1))
22774 .unwrap();
22775
22776 let waiting_ran = Arc::new(AtomicBool::new(false));
22777 let waiting_ran_task = Arc::clone(&waiting_ran);
22778 let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
22779 let waiting_drop = DropSignal(waiting_dropped_tx);
22780 let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
22781 let waiting_controller = Arc::clone(&controller);
22782 let waiting = std::thread::spawn(move || {
22783 let handle = waiting_controller
22784 .try_spawn_blocking(move || {
22785 let _waiting_drop = waiting_drop;
22786 waiting_ran_task.store(true, Ordering::SeqCst);
22787 })
22788 .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
22789 waiting_result_tx
22790 .send(futures::executor::block_on(handle))
22791 .unwrap();
22792 });
22793 wait_until("both CurrentThread blocking calls to be accepted", || {
22794 backend
22795 .work
22796 .state
22797 .lock()
22798 .unwrap_or_else(std::sync::PoisonError::into_inner)
22799 .active
22800 == 2
22801 });
22802
22803 let (shutdown_tx, shutdown_rx) = mpsc::channel();
22804 let shutdown_controller = Arc::clone(&controller);
22805 let shutdown = std::thread::spawn(move || {
22806 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22807 });
22808 waiting_dropped_rx
22809 .recv_timeout(Duration::from_secs(1))
22810 .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
22811 assert!(
22812 !waiting_ran.load(Ordering::SeqCst),
22813 "waiting blocking work must not start"
22814 );
22815 assert!(
22816 waiting_result_rx
22817 .recv_timeout(Duration::from_secs(1))
22818 .unwrap()
22819 .is_err(),
22820 "the waiting blocking handle must resolve as cancelled"
22821 );
22822 assert!(
22823 shutdown_rx
22824 .recv_timeout(Duration::from_millis(100))
22825 .is_err(),
22826 "shutdown must still wait for the blocking closure that already owns the lane"
22827 );
22828
22829 release_first_tx.send(()).unwrap();
22830 first.join().unwrap().unwrap();
22831 waiting.join().unwrap();
22832 shutdown_rx
22833 .recv_timeout(Duration::from_secs(1))
22834 .unwrap()
22835 .unwrap();
22836 shutdown.join().unwrap();
22837 assert_eq!(
22838 controller
22839 .metrics
22840 .blocking_tasks_started
22841 .load(Ordering::Relaxed),
22842 1
22843 );
22844 assert_eq!(
22845 controller
22846 .metrics
22847 .blocking_tasks_completed
22848 .load(Ordering::Relaxed),
22849 1
22850 );
22851 assert_eq!(
22852 controller
22853 .metrics
22854 .active_blocking_tasks
22855 .load(Ordering::Relaxed),
22856 0
22857 );
22858 }
22859
22860 #[cfg(not(target_family = "wasm"))]
22861 #[test]
22862 fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
22863 use std::{panic::panic_any, sync::mpsc};
22864
22865 struct ReentrantPayload {
22866 controller: Arc<RuntimeController>,
22867 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
22868 }
22869
22870 impl Drop for ReentrantPayload {
22871 fn drop(&mut self) {
22872 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
22873 let shutdown = self.controller.shutdown();
22874 self.result.send((generation, shutdown)).unwrap();
22875 }
22876 }
22877
22878 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
22879
22880 impl Drop for PanicPayloadOnDrop {
22881 fn drop(&mut self) {
22882 panic_any(self.0.take().expect("the result must panic only once"));
22883 }
22884 }
22885
22886 let controller = Arc::new(current_thread_controller(
22887 "current-thread-panic-payload-generation",
22888 ));
22889 let backend = controller.backend();
22890 let generation = backend.generation();
22891 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22892 panic!("configured CurrentThread backend must create a CurrentThread executor");
22893 };
22894 let executor = Arc::clone(executor);
22895 drop(backend);
22896
22897 let owner = executor.fresh_owner_token();
22900 executor
22901 .blocking_admission
22902 .state
22903 .lock()
22904 .unwrap_or_else(std::sync::PoisonError::into_inner)
22905 .owner = Some(owner);
22906 let (result_tx, result_rx) = mpsc::channel();
22907 let payload_controller = Arc::clone(&controller);
22908 let handle = controller
22909 .try_spawn_blocking(move || {
22910 PanicPayloadOnDrop(Some(ReentrantPayload {
22911 controller: payload_controller,
22912 result: result_tx,
22913 }))
22914 })
22915 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22916 drop(handle);
22917 executor
22918 .blocking_admission
22919 .state
22920 .lock()
22921 .unwrap_or_else(std::sync::PoisonError::into_inner)
22922 .owner = None;
22923 let dispatch = current_thread_dispatch(&executor);
22924 controller.drive_current_thread_dispatch(dispatch, false);
22925
22926 let (observed_generation, shutdown) = result_rx
22927 .recv_timeout(Duration::from_secs(1))
22928 .expect("the caught result panic payload must be destroyed in the scheduler turn");
22929 assert_eq!(observed_generation, Some(generation));
22930 let error = shutdown.expect_err("payload destruction is still work in the active generation");
22931 assert!(error.to_string().contains("work running on that runtime"));
22932 controller.shutdown().unwrap();
22933 }
22934
22935 #[test]
22936 fn current_thread_detached_blocking_result_drop_is_contained() {
22937 use std::sync::mpsc;
22938
22939 struct PanicOnDrop(mpsc::Sender<()>);
22940
22941 impl Drop for PanicOnDrop {
22942 fn drop(&mut self) {
22943 self.0.send(()).unwrap();
22944 panic!("intentional CurrentThread blocking result destructor panic");
22945 }
22946 }
22947
22948 let controller = current_thread_controller("current-thread-blocking-result-drop");
22949 let (dropped_tx, dropped_rx) = mpsc::channel();
22950 let handle = controller
22951 .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
22952 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22953
22954 let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
22955 dropped_rx
22956 .recv_timeout(Duration::from_secs(1))
22957 .expect("dropping the detached handle must reclaim its blocking result");
22958 assert!(
22959 result.is_ok(),
22960 "the blocking result destructor panic must be contained"
22961 );
22962 controller.shutdown().unwrap();
22963 }
22964
22965 #[test]
22970 fn validate_rejects_zero_worker_threads() {
22971 let error = RuntimeOptions {
22972 flavor: RuntimeFlavor::MultiThread,
22973 worker_threads: 0,
22974 max_blocking_tasks: 1,
22975 thread_name_prefix: "rd8".to_string(),
22976 park_deadline: None,
22977 drain_linger: DEFAULT_DRAIN_LINGER,
22978 }
22979 .validate()
22980 .expect_err("worker_threads == 0 must be rejected");
22981 assert_eq!(
22982 error.to_string(),
22983 "worker_threads must be greater than zero"
22984 );
22985 }
22986
22987 #[test]
22988 fn validate_rejects_zero_max_blocking_tasks() {
22989 let error = RuntimeOptions {
22990 flavor: RuntimeFlavor::MultiThread,
22991 worker_threads: 2,
22992 max_blocking_tasks: 0,
22993 thread_name_prefix: "rd8".to_string(),
22994 park_deadline: None,
22995 drain_linger: DEFAULT_DRAIN_LINGER,
22996 }
22997 .validate()
22998 .expect_err("max_blocking_tasks == 0 must be rejected");
22999 assert_eq!(
23000 error.to_string(),
23001 "max_blocking_tasks must be greater than zero"
23002 );
23003 }
23004
23005 #[test]
23006 fn validate_current_thread_coerces_worker_threads_to_one() {
23007 let validated = RuntimeOptions {
23010 flavor: RuntimeFlavor::CurrentThread,
23011 worker_threads: 8,
23012 max_blocking_tasks: 8,
23013 thread_name_prefix: "rd8".to_string(),
23014 park_deadline: None,
23015 drain_linger: DEFAULT_DRAIN_LINGER,
23016 }
23017 .validate()
23018 .expect("CurrentThread options must validate");
23019 assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
23020 assert_eq!(validated.worker_threads, 1);
23021 assert_eq!(validated.max_blocking_tasks, 1);
23022 }
23023
23024 #[cfg(not(target_family = "wasm"))]
23027 #[test]
23028 fn validate_reserves_one_worker_from_blocking_admission() {
23029 let validated = RuntimeOptions {
23030 flavor: RuntimeFlavor::MultiThread,
23031 worker_threads: 2,
23032 max_blocking_tasks: 8,
23033 thread_name_prefix: "rd8".to_string(),
23034 park_deadline: None,
23035 drain_linger: DEFAULT_DRAIN_LINGER,
23036 }
23037 .validate()
23038 .expect("MultiThread options must validate");
23039 assert_eq!(validated.worker_threads, 2);
23040 assert_eq!(validated.max_blocking_tasks, 1);
23041 }
23042
23043 #[cfg(not(target_family = "wasm"))]
23044 #[test]
23045 fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
23046 let validated = RuntimeOptions {
23047 flavor: RuntimeFlavor::MultiThread,
23048 worker_threads: 1,
23049 max_blocking_tasks: 8,
23050 thread_name_prefix: "rd8-minimum".to_string(),
23051 park_deadline: None,
23052 drain_linger: DEFAULT_DRAIN_LINGER,
23053 }
23054 .validate()
23055 .expect("MultiThread options must validate");
23056 assert_eq!(validated.worker_threads, 2);
23057 assert_eq!(validated.max_blocking_tasks, 1);
23058 }
23059
23060 #[cfg(not(target_family = "wasm"))]
23061 #[test]
23062 fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
23063 let simulated_32_bit = RuntimeOptions {
23064 flavor: RuntimeFlavor::MultiThread,
23065 worker_threads: 256,
23066 max_blocking_tasks: 256,
23067 thread_name_prefix: "rd8-rayon-cap-32".to_string(),
23068 park_deadline: None,
23069 drain_linger: DEFAULT_DRAIN_LINGER,
23070 }
23071 .validate_with_rayon_max_threads(Some(255))
23072 .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
23073 assert_eq!(simulated_32_bit.worker_threads, 255);
23074 assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
23075
23076 let rayon_max_threads = max_num_threads();
23077 let requested_threads = rayon_max_threads
23078 .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
23079 .checked_add(1)
23080 .expect("supported worker counts must leave room for an oversized request");
23081 let controller = RuntimeController::new();
23082 controller
23083 .configure(RuntimeOptions {
23084 flavor: RuntimeFlavor::MultiThread,
23085 worker_threads: requested_threads,
23086 max_blocking_tasks: requested_threads,
23087 thread_name_prefix: "rd8-rayon-cap".to_string(),
23088 park_deadline: None,
23089 drain_linger: DEFAULT_DRAIN_LINGER,
23090 })
23091 .expect("an oversized request must clamp to the production worker ceiling");
23092
23093 let reported = controller.options();
23094 let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
23095 assert_eq!(reported.worker_threads, expected_workers);
23096 assert_eq!(
23097 reported.max_blocking_tasks,
23098 expected_workers - 1,
23099 "the blocking cap must reserve a lane from the physically realizable worker count"
23100 );
23101 }
23102
23103 #[cfg(not(target_family = "wasm"))]
23104 #[test]
23105 fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
23106 let extreme = Duration::from_micros(u64::MAX);
23113 let validated = RuntimeOptions {
23114 flavor: RuntimeFlavor::MultiThread,
23115 worker_threads: 2,
23116 max_blocking_tasks: 1,
23117 thread_name_prefix: "linger-clamp".to_string(),
23118 park_deadline: None,
23119 drain_linger: extreme,
23120 }
23121 .validate_with_rayon_max_threads(Some(2))
23122 .expect("an extreme linger budget must clamp, not fail validation");
23123 assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
23124
23125 let controller = RuntimeController::new();
23126 controller
23127 .configure_partial(RuntimeOptionsPatch {
23128 drain_linger: Some(extreme),
23129 ..RuntimeOptionsPatch::default()
23130 })
23131 .expect("an extreme linger patch must clamp, not fail validation");
23132 assert_eq!(
23133 controller.options().drain_linger,
23134 MAX_DRAIN_LINGER,
23135 "introspection must report the clamped budget, not the requested one"
23136 );
23137 controller
23138 .configure_partial(RuntimeOptionsPatch {
23139 drain_linger: Some(Duration::ZERO),
23140 ..RuntimeOptionsPatch::default()
23141 })
23142 .expect("ZERO (lingering disabled) must remain a valid budget");
23143 assert_eq!(controller.options().drain_linger, Duration::ZERO);
23144
23145 let metrics = Arc::new(RuntimeMetrics::default());
23146 let unvalidated = RuntimeOptions {
23147 flavor: RuntimeFlavor::MultiThread,
23148 worker_threads: 2,
23149 max_blocking_tasks: 2,
23150 thread_name_prefix: "linger-clamp-exec".to_string(),
23151 park_deadline: None,
23152 drain_linger: extreme,
23153 };
23154 let executor = MultiThreadExecutor::new(&unvalidated, metrics)
23155 .expect("the executor must construct with an extreme unvalidated budget");
23156 assert_eq!(
23157 executor.drain_linger,
23158 Some(MAX_DRAIN_LINGER),
23159 "the constructor's defensive clamp must bound unvalidated budgets"
23160 );
23161
23162 let metrics = Arc::new(RuntimeMetrics::default());
23163 let disabled = RuntimeOptions {
23164 flavor: RuntimeFlavor::MultiThread,
23165 worker_threads: 2,
23166 max_blocking_tasks: 2,
23167 thread_name_prefix: "linger-clamp-off".to_string(),
23168 park_deadline: None,
23169 drain_linger: Duration::ZERO,
23170 };
23171 let executor = MultiThreadExecutor::new(&disabled, metrics)
23172 .expect("the executor must construct with lingering disabled");
23173 assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
23174 }
23175
23176 #[cfg(not(target_family = "wasm"))]
23177 #[test]
23178 fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
23179 let controller = RuntimeController::new();
23180 let options = controller.options();
23181 assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
23182 assert_eq!(
23183 options.max_blocking_tasks,
23184 options.worker_threads.saturating_sub(1).max(1),
23185 "the unconfigured Rust API must use the same validated reserve as the binding"
23186 );
23187 }
23188
23189 #[cfg(not(target_family = "wasm"))]
23190 #[test]
23191 fn concurrent_partial_configuration_merges_from_latest_committed_options() {
23192 use std::{sync::mpsc, time::Duration};
23193
23194 let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
23195 let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
23196 let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
23197 let first_controller = Arc::clone(&controller);
23198 let first = std::thread::spawn(move || {
23199 first_controller
23200 .configure_partial_inner(
23201 RuntimeOptionsPatch {
23202 max_blocking_tasks: Some(3),
23203 ..RuntimeOptionsPatch::default()
23204 },
23205 |candidate| {
23206 assert_eq!(
23207 (candidate.worker_threads, candidate.max_blocking_tasks),
23208 (8, 3)
23209 );
23210 first_merged_tx
23211 .send(())
23212 .expect("the race coordinator must still be listening");
23213 release_first_rx
23214 .recv_timeout(Duration::from_secs(5))
23215 .expect("the first partial configuration must be released");
23216 },
23217 )
23218 .expect("the first partial configuration must commit");
23219 });
23220 first_merged_rx
23221 .recv_timeout(Duration::from_secs(5))
23222 .expect("the first partial configuration must hold the controller lock after merging");
23223
23224 let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
23225 let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
23226 let second_controller = Arc::clone(&controller);
23227 let second = std::thread::spawn(move || {
23228 second_started_tx
23229 .send(())
23230 .expect("the race coordinator must still be listening");
23231 second_controller
23232 .configure_partial_inner(
23233 RuntimeOptionsPatch {
23234 worker_threads: Some(6),
23235 ..RuntimeOptionsPatch::default()
23236 },
23237 |candidate| {
23238 second_candidate_tx
23239 .send((candidate.worker_threads, candidate.max_blocking_tasks))
23240 .expect("the race coordinator must still be listening");
23241 },
23242 )
23243 .expect("the second partial configuration must commit");
23244 });
23245 second_started_rx
23246 .recv_timeout(Duration::from_secs(5))
23247 .expect("the second partial configuration thread must start");
23248 release_first_tx
23249 .send(())
23250 .expect("the first partial configuration must still be waiting");
23251
23252 first
23253 .join()
23254 .expect("the first configuration thread must not panic");
23255 second
23256 .join()
23257 .expect("the second configuration thread must not panic");
23258 assert_eq!(
23259 second_candidate_rx
23260 .recv_timeout(Duration::from_secs(5))
23261 .expect("the second partial configuration must expose its merged candidate"),
23262 (6, 3),
23263 "the second caller must merge against the first caller's committed options"
23264 );
23265
23266 let options = controller.options();
23267 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
23268 assert_eq!(options.thread_name_prefix, "partial-config-race");
23269 }
23270
23271 #[cfg(not(target_family = "wasm"))]
23272 #[test]
23273 fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
23274 use std::{sync::mpsc, time::Duration};
23275
23276 let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
23277 let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
23278 let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
23279 let invalid_controller = Arc::clone(&controller);
23280 let invalid = std::thread::spawn(move || {
23281 invalid_controller
23282 .configure_partial_inner(
23283 RuntimeOptionsPatch {
23284 worker_threads: Some(0),
23285 ..RuntimeOptionsPatch::default()
23286 },
23287 |candidate| {
23288 assert_eq!(
23289 (candidate.worker_threads, candidate.max_blocking_tasks),
23290 (0, 2)
23291 );
23292 invalid_merged_tx
23293 .send(())
23294 .expect("the race coordinator must still be listening");
23295 release_invalid_rx
23296 .recv_timeout(Duration::from_secs(5))
23297 .expect("the invalid partial configuration must be released");
23298 },
23299 )
23300 .expect_err("the invalid partial configuration must be rejected")
23301 .to_string()
23302 });
23303 invalid_merged_rx
23304 .recv_timeout(Duration::from_secs(5))
23305 .expect("the invalid partial configuration must hold the lock after merging");
23306
23307 let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
23308 let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
23309 let valid_controller = Arc::clone(&controller);
23310 let valid = std::thread::spawn(move || {
23311 valid_started_tx
23312 .send(())
23313 .expect("the race coordinator must still be listening");
23314 valid_controller
23315 .configure_partial_inner(
23316 RuntimeOptionsPatch {
23317 max_blocking_tasks: Some(4),
23318 ..RuntimeOptionsPatch::default()
23319 },
23320 |candidate| {
23321 valid_candidate_tx
23322 .send((candidate.worker_threads, candidate.max_blocking_tasks))
23323 .expect("the race coordinator must still be listening");
23324 },
23325 )
23326 .expect("the valid partial configuration must commit");
23327 });
23328 valid_started_rx
23329 .recv_timeout(Duration::from_secs(5))
23330 .expect("the valid partial configuration thread must start");
23331 release_invalid_tx
23332 .send(())
23333 .expect("the invalid partial configuration must still be waiting");
23334
23335 assert_eq!(
23336 invalid
23337 .join()
23338 .expect("the invalid configuration thread must not panic"),
23339 "worker_threads must be greater than zero"
23340 );
23341 valid
23342 .join()
23343 .expect("the valid configuration thread must not panic");
23344 assert_eq!(
23345 valid_candidate_rx
23346 .recv_timeout(Duration::from_secs(5))
23347 .expect("the valid partial configuration must expose its merged candidate"),
23348 (6, 4),
23349 "the valid caller must merge against the last committed options, not the rejected candidate"
23350 );
23351
23352 let options = controller.options();
23353 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
23354 assert_eq!(options.thread_name_prefix, "partial-config-rollback");
23355 }
23356
23357 #[test]
23358 fn configure_after_backend_started_is_rejected() {
23359 let controller = RuntimeController::new();
23362 controller
23363 .configure(RuntimeOptions {
23364 flavor: RuntimeFlavor::CurrentThread,
23365 worker_threads: 1,
23366 max_blocking_tasks: 1,
23367 thread_name_prefix: "rd8".to_string(),
23368 park_deadline: None,
23369 drain_linger: DEFAULT_DRAIN_LINGER,
23370 })
23371 .expect("first configure before the backend exists must succeed");
23372
23373 let _backend = controller.backend();
23375
23376 let error = controller
23377 .configure(RuntimeOptions::default())
23378 .expect_err("configure after the backend started must be rejected");
23379 assert_eq!(
23380 error.to_string(),
23381 "the async runtime configuration is frozen; configure it before the first async call"
23382 );
23383 }
23384
23385 #[test]
23386 fn partial_configuration_after_backend_started_is_rejected() {
23387 let controller = current_thread_controller("partial-config-frozen");
23388 let _backend = controller.backend();
23389
23390 let error = controller
23391 .configure_partial(RuntimeOptionsPatch {
23392 worker_threads: Some(2),
23393 ..RuntimeOptionsPatch::default()
23394 })
23395 .expect_err("partial configuration after the backend started must be rejected");
23396 assert_eq!(
23397 error.to_string(),
23398 "the async runtime configuration is frozen; configure it before the first async call"
23399 );
23400 }
23401
23402 fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
23403 let controller = RuntimeController::new();
23404 controller
23405 .configure(RuntimeOptions {
23406 flavor: RuntimeFlavor::CurrentThread,
23407 worker_threads: 1,
23408 max_blocking_tasks: 1,
23409 thread_name_prefix: thread_name_prefix.to_string(),
23410 park_deadline: None,
23411 drain_linger: DEFAULT_DRAIN_LINGER,
23412 })
23413 .expect("CurrentThread test configuration must be accepted");
23414 controller
23415 }
23416
23417 #[cfg(not(target_family = "wasm"))]
23418 fn multi_thread_controller(
23419 thread_name_prefix: &str,
23420 worker_threads: usize,
23421 max_blocking_tasks: usize,
23422 ) -> RuntimeController {
23423 let controller = RuntimeController::new();
23424 controller
23425 .configure(RuntimeOptions {
23426 flavor: RuntimeFlavor::MultiThread,
23427 worker_threads,
23428 max_blocking_tasks,
23429 thread_name_prefix: thread_name_prefix.to_string(),
23430 park_deadline: None,
23431 drain_linger: DEFAULT_DRAIN_LINGER,
23432 })
23433 .expect("MultiThread test configuration must be accepted");
23434 controller
23435 }
23436
23437 #[test]
23438 fn blocking_backend_creation_failure_preserves_diagnostic() {
23439 let controller = current_thread_controller("blocking-backend-creation-failure");
23440 let ran = Arc::new(AtomicBool::new(false));
23441 let work_ran = Arc::clone(&ran);
23442
23443 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23444 let Err((error, work)) = controller.try_spawn_blocking(move || {
23445 work_ran.store(true, Ordering::SeqCst);
23446 17usize
23447 }) else {
23448 panic!("the injected backend creation failure must reject blocking work");
23449 };
23450 assert_eq!(
23451 error.to_string(),
23452 "injected async runtime backend creation failure"
23453 );
23454 assert!(
23455 !ran.load(Ordering::SeqCst),
23456 "rejected blocking work must not run"
23457 );
23458 assert_eq!(
23459 work(),
23460 17,
23461 "the rejected blocking closure must remain usable"
23462 );
23463 assert!(ran.load(Ordering::SeqCst));
23464
23465 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23466 let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
23467 .expect_err("infallible blocking submission must return a failed join handle");
23468 assert_eq!(
23469 error.to_string(),
23470 "injected async runtime backend creation failure"
23471 );
23472
23473 let handle = controller
23474 .try_spawn_blocking(|| 31usize)
23475 .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
23476 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
23477 controller.shutdown().unwrap();
23478 }
23479
23480 #[cfg(not(target_family = "wasm"))]
23481 #[test]
23482 fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
23483 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
23484
23485 if std::env::var_os(CHILD_ENV).is_some() {
23486 struct PollProbe {
23487 polls: Arc<AtomicUsize>,
23488 }
23489
23490 impl Future for PollProbe {
23491 type Output = ();
23492
23493 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23494 self.polls.fetch_add(1, Ordering::SeqCst);
23495 Poll::Ready(())
23496 }
23497 }
23498
23499 shutdown().expect("the isolated runtime must stop before block_on admission");
23500 let polls = Arc::new(AtomicUsize::new(0));
23501 let mut future = std::pin::pin!(PollProbe {
23502 polls: Arc::clone(&polls)
23503 });
23504 let error = try_block_on_dyn(future.as_mut())
23505 .expect_err("the stopped runtime must reject the borrowed future");
23506 assert_eq!(
23507 error.to_string(),
23508 "the async runtime is stopped; call start before submitting work"
23509 );
23510 assert_eq!(
23511 polls.load(Ordering::SeqCst),
23512 0,
23513 "rejected block_on must not poll its future"
23514 );
23515
23516 start().expect("the isolated runtime must restart");
23517 try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
23518 assert_eq!(polls.load(Ordering::SeqCst), 1);
23519 shutdown().expect("the restarted isolated runtime must stop");
23520 return;
23521 }
23522
23523 let output = std::process::Command::new(std::env::current_exe().unwrap())
23524 .arg("--exact")
23525 .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
23526 .arg("--nocapture")
23527 .env(CHILD_ENV, "1")
23528 .output()
23529 .expect("the stopped block_on subprocess must start");
23530 assert!(
23531 output.status.success(),
23532 "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
23533 output.status.code(),
23534 String::from_utf8_lossy(&output.stdout),
23535 String::from_utf8_lossy(&output.stderr)
23536 );
23537 }
23538
23539 #[cfg(not(target_family = "wasm"))]
23540 #[test]
23541 fn try_block_on_dyn_reports_concurrent_shutdown() {
23542 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
23543
23544 if std::env::var_os(CHILD_ENV).is_some() {
23545 use std::sync::mpsc;
23546
23547 struct PendingProbe {
23548 entered: Option<mpsc::Sender<()>>,
23549 complete: Arc<AtomicBool>,
23550 }
23551
23552 impl Future for PendingProbe {
23553 type Output = ();
23554
23555 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23556 if let Some(entered) = self.entered.take() {
23557 entered.send(()).unwrap();
23558 }
23559 if self.complete.load(Ordering::SeqCst) {
23560 Poll::Ready(())
23561 } else {
23562 Poll::Pending
23563 }
23564 }
23565 }
23566
23567 configure(RuntimeOptions {
23568 flavor: RuntimeFlavor::CurrentThread,
23569 worker_threads: 1,
23570 max_blocking_tasks: 1,
23571 thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
23572 park_deadline: None,
23573 drain_linger: DEFAULT_DRAIN_LINGER,
23574 })
23575 .expect("the isolated runtime must accept CurrentThread configuration");
23576 start().expect("the isolated runtime must start");
23577
23578 let (entered_tx, entered_rx) = mpsc::channel();
23579 let (error_tx, error_rx) = mpsc::channel();
23580 let (retry_tx, retry_rx) = mpsc::channel();
23581 let complete = Arc::new(AtomicBool::new(false));
23582 let future_complete = Arc::clone(&complete);
23583 let runner = std::thread::spawn(move || {
23584 let mut future = std::pin::pin!(PendingProbe {
23585 entered: Some(entered_tx),
23586 complete: future_complete
23587 });
23588 let error = try_block_on_dyn(future.as_mut())
23589 .expect_err("concurrent shutdown must interrupt the borrowed future")
23590 .to_string();
23591 error_tx.send(error).unwrap();
23592 retry_rx.recv().unwrap();
23593 try_block_on_dyn(future.as_mut())
23594 .expect("restart must accept the same interrupted borrowed future");
23595 });
23596 entered_rx
23597 .recv_timeout(Duration::from_secs(2))
23598 .expect("the borrowed future must be polled before shutdown");
23599 shutdown().expect("shutdown must wait for the active block_on scope to retire");
23600 assert_eq!(
23601 error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
23602 "the async runtime stopped before block_on completed its future"
23603 );
23604 start().expect("the isolated runtime must restart after the interrupted drive");
23605 complete.store(true, Ordering::SeqCst);
23606 retry_tx.send(()).unwrap();
23607 runner.join().unwrap();
23608 shutdown().expect("the retried isolated runtime must stop");
23609 return;
23610 }
23611
23612 let output = std::process::Command::new(std::env::current_exe().unwrap())
23613 .arg("--exact")
23614 .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
23615 .arg("--nocapture")
23616 .env(CHILD_ENV, "1")
23617 .output()
23618 .expect("the concurrent block_on shutdown subprocess must start");
23619 assert!(
23620 output.status.success(),
23621 "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
23622 output.status.code(),
23623 String::from_utf8_lossy(&output.stdout),
23624 String::from_utf8_lossy(&output.stderr)
23625 );
23626 }
23627
23628 #[test]
23629 fn initial_lifecycle_start_preserves_lazy_configuration() {
23630 let controller = RuntimeController::new();
23631 controller
23632 .start()
23633 .expect("initial lifecycle start must succeed");
23634 controller
23635 .configure(RuntimeOptions {
23636 flavor: RuntimeFlavor::CurrentThread,
23637 worker_threads: 1,
23638 max_blocking_tasks: 1,
23639 thread_name_prefix: "lifecycle-initial-start".to_string(),
23640 park_deadline: None,
23641 drain_linger: DEFAULT_DRAIN_LINGER,
23642 })
23643 .expect("module-registration start must not consume the configuration window");
23644
23645 let state = controller
23646 .state
23647 .lock()
23648 .unwrap_or_else(std::sync::PoisonError::into_inner);
23649 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23650 }
23651
23652 #[test]
23653 fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
23654 let controller = RuntimeController::new();
23655
23656 for _ in 0..3 {
23657 controller
23658 .start()
23659 .expect("zero-work lifecycle start must succeed");
23660 assert!(matches!(
23661 &controller
23662 .state
23663 .lock()
23664 .unwrap_or_else(std::sync::PoisonError::into_inner)
23665 .lifecycle,
23666 RuntimeLifecycle::Initial
23667 ));
23668
23669 controller
23670 .shutdown()
23671 .expect("zero-work lifecycle shutdown must succeed");
23672 assert!(matches!(
23673 &controller
23674 .state
23675 .lock()
23676 .unwrap_or_else(std::sync::PoisonError::into_inner)
23677 .lifecycle,
23678 RuntimeLifecycle::StoppedBeforeFirstUse
23679 ));
23680 }
23681
23682 controller
23683 .start()
23684 .expect("a later zero-work restart must remain lazy");
23685 controller
23686 .configure(RuntimeOptions {
23687 flavor: RuntimeFlavor::CurrentThread,
23688 worker_threads: 1,
23689 max_blocking_tasks: 1,
23690 thread_name_prefix: "lifecycle-zero-work-config".to_string(),
23691 park_deadline: None,
23692 drain_linger: DEFAULT_DRAIN_LINGER,
23693 })
23694 .expect("zero-work lifecycle cycles must not consume the configuration window");
23695
23696 let state = controller
23697 .state
23698 .lock()
23699 .unwrap_or_else(std::sync::PoisonError::into_inner);
23700 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23701 }
23702
23703 #[cfg(not(target_family = "wasm"))]
23704 #[test]
23705 fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
23706 use std::sync::mpsc;
23707
23708 struct BlockingDropFuture {
23709 entered: mpsc::Sender<()>,
23710 release: mpsc::Receiver<()>,
23711 }
23712
23713 impl Future for BlockingDropFuture {
23714 type Output = ();
23715
23716 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23717 Poll::Pending
23718 }
23719 }
23720
23721 impl Drop for BlockingDropFuture {
23722 fn drop(&mut self) {
23723 self.entered.send(()).unwrap();
23724 self.release.recv().unwrap();
23725 }
23726 }
23727
23728 let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
23729 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23730 let (release_drop_tx, release_drop_rx) = mpsc::channel();
23731 let submit_controller = Arc::clone(&controller);
23732 let submitter = std::thread::spawn(move || {
23733 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23734 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23735 entered: drop_entered_tx,
23736 release: release_drop_rx,
23737 }));
23738 assert!(
23739 result.is_err(),
23740 "the injected backend creation failure must reject the submission"
23741 );
23742 });
23743 drop_entered_rx
23744 .recv_timeout(Duration::from_secs(2))
23745 .expect("the rejected future destructor must start");
23746
23747 let retry_ran = Arc::new(AtomicBool::new(false));
23748 let retry_ran_task = Arc::clone(&retry_ran);
23749 let Err(retry) = controller.try_spawn_detached(async move {
23750 retry_ran_task.store(true, Ordering::SeqCst);
23751 }) else {
23752 panic!("backend creation must remain closed during rejected destruction");
23753 };
23754 drop(retry);
23755 assert!(!retry_ran.load(Ordering::SeqCst));
23756
23757 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23758 let shutdown_controller = Arc::clone(&controller);
23759 let shutdown = std::thread::spawn(move || {
23760 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23761 });
23762 wait_until("initial shutdown to close runtime admission", || {
23763 matches!(
23764 controller
23765 .state
23766 .lock()
23767 .unwrap_or_else(std::sync::PoisonError::into_inner)
23768 .lifecycle,
23769 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
23770 )
23771 });
23772 assert!(
23773 shutdown_rx
23774 .recv_timeout(Duration::from_millis(200))
23775 .is_err(),
23776 "initial shutdown must wait for rejected user state destruction"
23777 );
23778 assert!(
23779 controller.try_spawn_detached(async {}).is_err(),
23780 "shutdown must keep first-generation admission closed while it waits"
23781 );
23782
23783 let (start_tx, start_rx) = mpsc::channel();
23784 let start_controller = Arc::clone(&controller);
23785 let starter = std::thread::spawn(move || {
23786 start_tx.send(start_controller.start()).unwrap();
23787 });
23788 assert!(
23789 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23790 "start must wait for the zero-backend shutdown transition to finish"
23791 );
23792
23793 release_drop_tx.send(()).unwrap();
23794 submitter.join().unwrap();
23795 shutdown_rx
23796 .recv_timeout(Duration::from_secs(2))
23797 .unwrap()
23798 .unwrap();
23799 shutdown.join().unwrap();
23800 start_rx
23801 .recv_timeout(Duration::from_secs(2))
23802 .unwrap()
23803 .unwrap();
23804 starter.join().unwrap();
23805
23806 let ran = Arc::new(AtomicBool::new(false));
23807 let ran_task = Arc::clone(&ran);
23808 controller
23809 .try_spawn_detached(async move {
23810 ran_task.store(true, Ordering::SeqCst);
23811 })
23812 .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
23813 let dispatch = controller_current_thread_dispatch(&controller);
23814 controller.drive_current_thread_dispatch(dispatch, false);
23815 assert!(ran.load(Ordering::SeqCst));
23816 controller.shutdown().unwrap();
23817 }
23818
23819 #[cfg(not(target_family = "wasm"))]
23820 #[test]
23821 fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
23822 use std::sync::mpsc;
23823
23824 struct BlockingDropFuture {
23825 entered: mpsc::Sender<()>,
23826 release: mpsc::Receiver<()>,
23827 }
23828
23829 impl Future for BlockingDropFuture {
23830 type Output = ();
23831
23832 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23833 Poll::Pending
23834 }
23835 }
23836
23837 impl Drop for BlockingDropFuture {
23838 fn drop(&mut self) {
23839 self.entered.send(()).unwrap();
23840 self.release.recv().unwrap();
23841 }
23842 }
23843
23844 let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
23845 let old_generation = controller.backend().generation();
23846 controller.shutdown().unwrap();
23847
23848 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23849 let (release_drop_tx, release_drop_rx) = mpsc::channel();
23850 let submit_controller = Arc::clone(&controller);
23851 let submitter = std::thread::spawn(move || {
23852 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23853 entered: drop_entered_tx,
23854 release: release_drop_rx,
23855 }));
23856 assert!(
23857 result.is_err(),
23858 "the stopped runtime must reject the submission"
23859 );
23860 });
23861 drop_entered_rx
23862 .recv_timeout(Duration::from_secs(2))
23863 .unwrap();
23864
23865 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23866 let shutdown_controller = Arc::clone(&controller);
23867 let shutdown = std::thread::spawn(move || {
23868 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23869 });
23870 wait_until(
23871 "stopped shutdown to enter its zero-backend transition",
23872 || {
23873 matches!(
23874 controller
23875 .state
23876 .lock()
23877 .unwrap_or_else(std::sync::PoisonError::into_inner)
23878 .lifecycle,
23879 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
23880 )
23881 },
23882 );
23883
23884 let (start_tx, start_rx) = mpsc::channel();
23885 let start_controller = Arc::clone(&controller);
23886 let starter = std::thread::spawn(move || {
23887 start_tx.send(start_controller.start()).unwrap();
23888 });
23889 assert!(
23890 shutdown_rx
23891 .recv_timeout(Duration::from_millis(200))
23892 .is_err(),
23893 "stopped shutdown must wait for rejected destruction"
23894 );
23895 assert!(
23896 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23897 "restart must wait for the stopped zero-backend transition"
23898 );
23899
23900 release_drop_tx.send(()).unwrap();
23901 submitter.join().unwrap();
23902 shutdown_rx
23903 .recv_timeout(Duration::from_secs(2))
23904 .unwrap()
23905 .unwrap();
23906 shutdown.join().unwrap();
23907 start_rx
23908 .recv_timeout(Duration::from_secs(2))
23909 .unwrap()
23910 .unwrap();
23911 starter.join().unwrap();
23912
23913 let new_generation = controller.backend().generation();
23914 assert_ne!(new_generation, old_generation);
23915 controller.shutdown().unwrap();
23916 }
23917
23918 #[test]
23919 fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
23920 use std::sync::mpsc;
23921
23922 struct ShutdownOnDrop {
23923 controller: Arc<RuntimeController>,
23924 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
23925 }
23926
23927 impl Future for ShutdownOnDrop {
23928 type Output = ();
23929
23930 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23931 Poll::Pending
23932 }
23933 }
23934
23935 impl Drop for ShutdownOnDrop {
23936 fn drop(&mut self) {
23937 self.result.send(self.controller.shutdown()).unwrap();
23938 }
23939 }
23940
23941 let controller = Arc::new(current_thread_controller(
23942 "failed-initial-reentrant-shutdown",
23943 ));
23944 let (result_tx, result_rx) = mpsc::channel();
23945 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23946 let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
23947 controller: Arc::clone(&controller),
23948 result: result_tx,
23949 }));
23950 assert!(rejected.is_err());
23951
23952 let error = result_rx
23953 .recv_timeout(Duration::from_secs(1))
23954 .expect("the rejected destructor must not self-deadlock")
23955 .expect_err("rejected destruction cannot wait for its own shutdown");
23956 assert!(error.to_string().contains("rejected submission"));
23957 {
23958 let state = controller
23959 .state
23960 .lock()
23961 .unwrap_or_else(std::sync::PoisonError::into_inner);
23962 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23963 assert_eq!(state.rejected_drops, 0);
23964 }
23965
23966 let handle = controller
23967 .try_spawn(async { 23usize })
23968 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
23969 let dispatch = controller_current_thread_dispatch(&controller);
23970 controller.drive_current_thread_dispatch(dispatch, false);
23971 assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
23972 controller.shutdown().unwrap();
23973 }
23974
23975 #[test]
23976 fn failed_initial_submission_destructor_cannot_reenter_start() {
23977 use std::sync::mpsc;
23978
23979 struct StartOnDrop {
23980 controller: Arc<RuntimeController>,
23981 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
23982 release: mpsc::Receiver<()>,
23983 }
23984
23985 impl Future for StartOnDrop {
23986 type Output = ();
23987
23988 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23989 Poll::Pending
23990 }
23991 }
23992
23993 impl Drop for StartOnDrop {
23994 fn drop(&mut self) {
23995 self.result.send(self.controller.start()).unwrap();
23996 self.release.recv().unwrap();
23997 }
23998 }
23999
24000 let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
24001 let (result_tx, result_rx) = mpsc::channel();
24002 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24003 let (submission_tx, submission_rx) = mpsc::channel();
24004 let submit_controller = Arc::clone(&controller);
24005 let submitter = std::thread::spawn(move || {
24006 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24007 let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
24008 controller: Arc::clone(&submit_controller),
24009 result: result_tx,
24010 release: release_drop_rx,
24011 }));
24012 submission_tx.send(rejected).unwrap();
24013 });
24014
24015 let error = result_rx
24016 .recv_timeout(Duration::from_secs(1))
24017 .expect("the rejected destructor must not self-deadlock")
24018 .expect_err("rejected destruction must not report a successful runtime start");
24019 assert!(error.to_string().contains("rejected submission"));
24020 {
24021 let state = controller
24022 .state
24023 .lock()
24024 .unwrap_or_else(std::sync::PoisonError::into_inner);
24025 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24026 assert_eq!(state.rejected_drops, 1);
24027 }
24028
24029 let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
24030 let (external_start_tx, external_start_rx) = mpsc::channel();
24031 let start_controller = Arc::clone(&controller);
24032 let starter = std::thread::spawn(move || {
24033 BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
24034 *slot.borrow_mut() = Some(Box::new(move || {
24035 wait_entered_tx.send(()).unwrap();
24036 }));
24037 });
24038 external_start_tx.send(start_controller.start()).unwrap();
24039 });
24040 wait_entered_rx
24041 .recv_timeout(Duration::from_secs(1))
24042 .expect("external start must reach the Initial rejected-destruction wait");
24043 assert!(
24044 external_start_rx.try_recv().is_err(),
24045 "external start must not return while rejected destruction remains active"
24046 );
24047
24048 release_drop_tx.send(()).unwrap();
24049 assert!(
24050 submission_rx
24051 .recv_timeout(Duration::from_secs(2))
24052 .unwrap()
24053 .is_err()
24054 );
24055 submitter.join().unwrap();
24056 external_start_rx
24057 .recv_timeout(Duration::from_secs(2))
24058 .unwrap()
24059 .unwrap();
24060 starter.join().unwrap();
24061
24062 {
24063 let state = controller
24064 .state
24065 .lock()
24066 .unwrap_or_else(std::sync::PoisonError::into_inner);
24067 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24068 assert_eq!(state.rejected_drops, 0);
24069 }
24070
24071 let handle = controller
24072 .try_spawn(async { 31usize })
24073 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
24074 let dispatch = controller_current_thread_dispatch(&controller);
24075 controller.drive_current_thread_dispatch(dispatch, false);
24076 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
24077 controller.shutdown().unwrap();
24078 }
24079
24080 #[test]
24081 fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
24082 use std::sync::mpsc;
24083
24084 struct BlockingDropFuture {
24085 controller: Arc<RuntimeController>,
24086 entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24087 release: mpsc::Receiver<()>,
24088 }
24089
24090 impl Future for BlockingDropFuture {
24091 type Output = ();
24092
24093 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24094 Poll::Pending
24095 }
24096 }
24097
24098 impl Drop for BlockingDropFuture {
24099 fn drop(&mut self) {
24100 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
24101 self
24102 .entered
24103 .send((generation, self.controller.shutdown()))
24104 .unwrap();
24105 self.release.recv().unwrap();
24106 }
24107 }
24108
24109 let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
24110 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24111 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24112 let (runner_done_tx, runner_done_rx) = mpsc::channel();
24113 let runner_controller = Arc::clone(&controller);
24114 let runner = std::thread::spawn(move || {
24115 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24116 let result = catch_unwind(AssertUnwindSafe(|| {
24117 block_on_with_controller(
24118 &runner_controller,
24119 BlockingDropFuture {
24120 controller: Arc::clone(&runner_controller),
24121 entered: drop_entered_tx,
24122 release: release_drop_rx,
24123 },
24124 );
24125 }));
24126 runner_done_tx.send(result.is_err()).unwrap();
24127 });
24128
24129 let (generation, reentrant_shutdown) = drop_entered_rx
24130 .recv_timeout(Duration::from_secs(2))
24131 .expect("failed block_on acquisition must destroy its owned future");
24132 assert_eq!(generation, None, "no runtime generation was created");
24133 let error = reentrant_shutdown
24134 .expect_err("rejected block_on destruction cannot wait for its own shutdown");
24135 assert!(error.to_string().contains("rejected submission"));
24136
24137 let Err(retry_error) = controller.try_backend() else {
24138 panic!("backend retry must remain closed during rejected block_on destruction");
24139 };
24140 assert!(retry_error.to_string().contains("rejected submission"));
24141
24142 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24143 let shutdown_controller = Arc::clone(&controller);
24144 let shutdown = std::thread::spawn(move || {
24145 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24146 });
24147 assert!(
24148 shutdown_rx
24149 .recv_timeout(Duration::from_millis(200))
24150 .is_err(),
24151 "initial shutdown must wait for rejected block_on destruction"
24152 );
24153
24154 release_drop_tx.send(()).unwrap();
24155 assert!(
24156 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24157 "block_on must preserve its infallible panic contract after contained cleanup"
24158 );
24159 runner.join().unwrap();
24160 shutdown_rx
24161 .recv_timeout(Duration::from_secs(2))
24162 .unwrap()
24163 .unwrap();
24164 shutdown.join().unwrap();
24165
24166 controller
24167 .start()
24168 .expect("zero-generation shutdown must remain restartable");
24169 let ran = Arc::new(AtomicBool::new(false));
24170 let ran_task = Arc::clone(&ran);
24171 controller
24172 .try_spawn_detached(async move {
24173 ran_task.store(true, Ordering::SeqCst);
24174 })
24175 .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
24176 let dispatch = controller_current_thread_dispatch(&controller);
24177 controller.drive_current_thread_dispatch(dispatch, false);
24178 assert!(ran.load(Ordering::SeqCst));
24179 controller.shutdown().unwrap();
24180 }
24181
24182 #[test]
24183 fn public_block_on_contains_poll_and_drop_double_panic() {
24184 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
24185
24186 if std::env::var_os(CHILD_ENV).is_some() {
24187 struct PanicInPollAndDrop;
24188
24189 impl Future for PanicInPollAndDrop {
24190 type Output = ();
24191
24192 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24193 panic!("intentional block_on poll panic");
24194 }
24195 }
24196
24197 impl Drop for PanicInPollAndDrop {
24198 fn drop(&mut self) {
24199 panic!("intentional block_on future destructor panic");
24200 }
24201 }
24202
24203 let controller = current_thread_controller("block-on-double-panic");
24204 assert!(
24205 catch_unwind(AssertUnwindSafe(|| {
24206 block_on_with_controller(&controller, PanicInPollAndDrop);
24207 }))
24208 .is_err(),
24209 "the original poll panic must still reach the caller"
24210 );
24211 controller
24212 .shutdown()
24213 .expect("contained future destruction must not strand the runtime");
24214 return;
24215 }
24216
24217 let output = std::process::Command::new(std::env::current_exe().unwrap())
24218 .arg("--exact")
24219 .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
24220 .arg("--nocapture")
24221 .env(CHILD_ENV, "1")
24222 .output()
24223 .expect("the subprocess test must start");
24224 assert!(
24225 output.status.success(),
24226 "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
24227 output.status.code(),
24228 String::from_utf8_lossy(&output.stdout),
24229 String::from_utf8_lossy(&output.stderr)
24230 );
24231 }
24232
24233 #[test]
24234 fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
24235 let controller = RuntimeController::new();
24236 controller
24237 .start()
24238 .expect("initial lifecycle start must succeed");
24239 controller
24240 .shutdown()
24241 .expect("zero-work lifecycle shutdown must succeed");
24242 controller
24243 .start()
24244 .expect("zero-work lifecycle restart must remain lazy");
24245 controller
24246 .configure(RuntimeOptions {
24247 flavor: RuntimeFlavor::CurrentThread,
24248 worker_threads: 1,
24249 max_blocking_tasks: 1,
24250 thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
24251 park_deadline: None,
24252 drain_linger: DEFAULT_DRAIN_LINGER,
24253 })
24254 .expect("configuration before first real async use must succeed");
24255
24256 let first_generation = controller.backend().generation();
24257 controller
24258 .shutdown()
24259 .expect("the first real generation must stop");
24260 assert!(matches!(
24261 &controller
24262 .state
24263 .lock()
24264 .unwrap_or_else(std::sync::PoisonError::into_inner)
24265 .lifecycle,
24266 RuntimeLifecycle::Stopped
24267 ));
24268 let error = controller
24269 .configure(RuntimeOptions::default())
24270 .expect_err("a real generation must permanently freeze configuration");
24271 assert_eq!(
24272 error.to_string(),
24273 "the async runtime configuration is frozen; configure it before the first async call"
24274 );
24275
24276 controller
24277 .start()
24278 .expect("the stopped real generation must restart");
24279 let restarted_generation = {
24280 let state = controller
24281 .state
24282 .lock()
24283 .unwrap_or_else(std::sync::PoisonError::into_inner);
24284 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
24285 panic!("a real generation restart must eagerly create its replacement backend");
24286 };
24287 backend.generation()
24288 };
24289 assert_ne!(
24290 restarted_generation, first_generation,
24291 "a real generation restart must not reuse the retired backend"
24292 );
24293 controller
24294 .shutdown()
24295 .expect("the replacement generation must stop");
24296 }
24297
24298 #[test]
24299 fn runtime_controller_rejects_submissions_after_shutdown() {
24300 let controller = current_thread_controller("lifecycle-reject");
24301 controller.start().expect("runtime must start");
24302 drop(controller.backend());
24303 controller.shutdown().expect("runtime must stop");
24304
24305 let async_ran = Arc::new(AtomicBool::new(false));
24306 let async_ran_task = Arc::clone(&async_ran);
24307 let Err(rejected_future) = controller.try_spawn_detached(async move {
24308 async_ran_task.store(true, Ordering::SeqCst);
24309 }) else {
24310 panic!("shutdown must reject async work");
24311 };
24312 assert!(
24313 !async_ran.load(Ordering::SeqCst),
24314 "a rejected future must not be polled"
24315 );
24316 futures::executor::block_on(rejected_future);
24317 assert!(
24318 async_ran.load(Ordering::SeqCst),
24319 "the original future must remain usable"
24320 );
24321
24322 let blocking_ran = Arc::new(AtomicBool::new(false));
24323 let blocking_ran_task = Arc::clone(&blocking_ran);
24324 let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
24325 blocking_ran_task.store(true, Ordering::SeqCst);
24326 7
24327 }) else {
24328 panic!("shutdown must reject blocking work");
24329 };
24330 assert_eq!(
24331 blocking_error.to_string(),
24332 "the async runtime is stopped; call start before submitting work"
24333 );
24334 assert!(
24335 !blocking_ran.load(Ordering::SeqCst),
24336 "rejected blocking work must not run"
24337 );
24338 assert_eq!(
24339 rejected_work(),
24340 7,
24341 "the original closure must remain usable"
24342 );
24343
24344 let config_error = controller
24345 .configure(RuntimeOptions::default())
24346 .expect_err("shutdown must not make runtime configuration mutable again");
24347 assert_eq!(
24348 config_error.to_string(),
24349 "the async runtime configuration is frozen; configure it before the first async call"
24350 );
24351
24352 let state = controller
24353 .state
24354 .lock()
24355 .unwrap_or_else(std::sync::PoisonError::into_inner);
24356 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
24357 }
24358
24359 #[test]
24360 fn rejected_future_can_be_resubmitted_after_restart() {
24361 let controller = current_thread_controller("lifecycle-retry-future");
24362 controller.start().expect("runtime must start");
24363 drop(controller.backend());
24364 controller.shutdown().expect("runtime must stop");
24365
24366 let runs = Arc::new(AtomicUsize::new(0));
24367 let runs_task = Arc::clone(&runs);
24368 let future = async move {
24369 runs_task.fetch_add(1, Ordering::SeqCst);
24370 23
24371 };
24372 let Err((error, future)) = controller.try_spawn(future) else {
24373 panic!("stopped runtime must reject the future");
24374 };
24375 assert!(error.to_string().contains("runtime is stopped"));
24376 assert_eq!(runs.load(Ordering::SeqCst), 0);
24377
24378 controller.start().expect("runtime must restart");
24379 let handle = controller
24380 .try_spawn(future)
24381 .unwrap_or_else(|_| panic!("restart must accept retained future"));
24382 let dispatch = controller_current_thread_dispatch(&controller);
24383 controller.drive_current_thread_dispatch(dispatch, false);
24384 assert_eq!(
24385 futures::executor::block_on(handle).expect("retained future must complete"),
24386 23
24387 );
24388 assert_eq!(runs.load(Ordering::SeqCst), 1);
24389 controller
24390 .shutdown()
24391 .expect("replacement runtime must stop");
24392 }
24393
24394 #[test]
24395 fn runtime_controller_start_after_shutdown_accepts_work() {
24396 let controller = current_thread_controller("lifecycle-restart");
24397 controller.start().expect("runtime must start");
24398 drop(controller.backend());
24399 controller.shutdown().expect("runtime must stop");
24400 controller.start().expect("runtime must restart");
24401
24402 let async_ran = Arc::new(AtomicBool::new(false));
24403 let async_ran_task = Arc::clone(&async_ran);
24404 controller
24405 .try_spawn_detached(async move {
24406 async_ran_task.store(true, Ordering::SeqCst);
24407 })
24408 .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
24409 assert!(
24410 !async_ran.load(Ordering::SeqCst),
24411 "hostless CurrentThread submission must remain enqueue-only"
24412 );
24413 let dispatch = controller_current_thread_dispatch(&controller);
24414 controller.drive_current_thread_dispatch(dispatch, false);
24415 assert!(
24416 async_ran.load(Ordering::SeqCst),
24417 "restarted runtime must poll submitted work"
24418 );
24419
24420 let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
24421 panic!("restarted runtime must accept blocking work");
24422 };
24423 assert_eq!(
24424 futures::executor::block_on(handle).expect("blocking work must complete after restart"),
24425 11
24426 );
24427 }
24428
24429 #[test]
24430 fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
24431 let controller = current_thread_controller("stale-host-generation");
24432 let old_generation = controller.backend().generation();
24433 let old_dispatch = controller_current_thread_dispatch(&controller);
24434 controller.shutdown().unwrap();
24435 controller.start().unwrap();
24436 let new_generation = controller.backend().generation();
24437 assert_ne!(new_generation, old_generation);
24438
24439 let ran = Arc::new(AtomicBool::new(false));
24440 let ran_task = Arc::clone(&ran);
24441 controller
24442 .try_spawn_detached(async move {
24443 ran_task.store(true, Ordering::SeqCst);
24444 })
24445 .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
24446 let new_dispatch = controller_current_thread_dispatch(&controller);
24447 assert_ne!(new_dispatch, old_dispatch);
24448
24449 controller.drive_current_thread_dispatch(old_dispatch, false);
24450 assert!(
24451 !ran.load(Ordering::SeqCst),
24452 "a callback from the retired generation must not touch the restarted executor"
24453 );
24454 controller.drive_current_thread_dispatch(new_dispatch, false);
24455 assert!(ran.load(Ordering::SeqCst));
24456 controller.shutdown().unwrap();
24457 }
24458
24459 #[test]
24460 fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
24461 let controller = current_thread_controller("stale-host-cancellation");
24462 let old_generation = controller.backend().generation();
24463 let old_dispatch = controller_current_thread_dispatch(&controller);
24464 controller.shutdown().unwrap();
24465 controller.start().unwrap();
24466 let backend = controller.backend();
24467 let new_generation = backend.generation();
24468 assert_ne!(new_generation, old_generation);
24469 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24470 panic!("configured CurrentThread backend must create a CurrentThread executor");
24471 };
24472 let new_dispatch = current_thread_dispatch(executor);
24473 assert_ne!(new_dispatch, old_dispatch);
24474
24475 controller.cancel_current_thread_dispatch_for_test(old_dispatch);
24476 assert_eq!(
24477 executor.dispatch_pending.load(Ordering::Acquire),
24478 new_dispatch,
24479 "a delayed failure from the retired generation must not release the replacement latch"
24480 );
24481
24482 controller.cancel_current_thread_dispatch_for_test(new_dispatch);
24483 assert_eq!(
24484 executor.dispatch_pending.load(Ordering::Acquire),
24485 0,
24486 "the live generation must be able to cancel its accepted dispatch"
24487 );
24488 drop(backend);
24489 controller.shutdown().unwrap();
24490 }
24491
24492 #[cfg(not(target_family = "wasm"))]
24493 #[test]
24494 fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
24495 use std::sync::mpsc;
24496
24497 type ProbeResult = (
24498 &'static str,
24499 Option<u64>,
24500 Result<(), String>,
24501 Result<(), String>,
24502 Result<(), String>,
24503 );
24504
24505 struct StaleGenerationProbe {
24506 label: &'static str,
24507 controller: Arc<RuntimeController>,
24508 result: mpsc::Sender<ProbeResult>,
24509 }
24510
24511 impl Drop for StaleGenerationProbe {
24512 fn drop(&mut self) {
24513 let generation = active_runtime_generation();
24514 let submission = match self.controller.try_spawn(async {}) {
24515 Ok(handle) => {
24516 drop(handle);
24517 Ok(())
24518 }
24519 Err((error, future)) => {
24520 drop(future);
24521 Err(error.to_string())
24522 }
24523 };
24524 let start = self.controller.start().map_err(|error| error.to_string());
24525 let shutdown = self
24526 .controller
24527 .shutdown()
24528 .map_err(|error| error.to_string());
24529 self
24530 .result
24531 .send((self.label, generation, submission, start, shutdown))
24532 .unwrap();
24533 }
24534 }
24535
24536 let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
24537 let old_generation = controller.backend().generation();
24538 let (result_tx, result_rx) = mpsc::channel();
24539
24540 let async_controller = Arc::clone(&controller);
24541 let async_result = result_tx.clone();
24542 let async_handle = controller
24543 .try_spawn(async move {
24544 StaleGenerationProbe {
24545 label: "async",
24546 controller: async_controller,
24547 result: async_result,
24548 }
24549 })
24550 .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
24551 let old_dispatch = controller_current_thread_dispatch(&controller);
24552 controller.drive_current_thread_dispatch(old_dispatch, false);
24553
24554 let ready_controller = Arc::clone(&controller);
24555 let ready_handle = controller
24556 .try_spawn_blocking(move || StaleGenerationProbe {
24557 label: "ready",
24558 controller: ready_controller,
24559 result: result_tx,
24560 })
24561 .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
24562
24563 controller.shutdown().unwrap();
24564 controller.start().unwrap();
24565 let new_generation = controller.backend().generation();
24566 assert_ne!(new_generation, old_generation);
24567
24568 drop(async_handle);
24569 let async_result = result_rx
24570 .recv_timeout(Duration::from_secs(2))
24571 .expect("the retained async output must be destroyed without waiting on the replacement");
24572 drop(ready_handle);
24573 let ready_result = result_rx
24574 .recv_timeout(Duration::from_secs(2))
24575 .expect("the retained immediate output must be destroyed without touching the replacement");
24576
24577 for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
24578 let (label, generation, submission, start, shutdown) = result;
24579 assert_eq!(label, expected_label);
24580 assert_eq!(generation, Some(old_generation));
24581 for error in [submission, start, shutdown] {
24582 assert!(
24583 error
24584 .expect_err("retired-generation output destruction must be rejected")
24585 .contains("retired generation")
24586 );
24587 }
24588 }
24589
24590 let survivor = controller
24591 .try_spawn(async { 29usize })
24592 .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
24593 let new_dispatch = controller_current_thread_dispatch(&controller);
24594 controller.drive_current_thread_dispatch(new_dispatch, false);
24595 assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
24596 controller.shutdown().unwrap();
24597 }
24598
24599 #[cfg(not(target_family = "wasm"))]
24600 #[test]
24601 fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
24602 use std::sync::mpsc;
24603
24604 struct ShutdownOnDrop {
24605 controller: Arc<RuntimeController>,
24606 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24607 }
24608
24609 impl Drop for ShutdownOnDrop {
24610 fn drop(&mut self) {
24611 self
24612 .result
24613 .send((active_runtime_generation(), self.controller.shutdown()))
24614 .unwrap();
24615 }
24616 }
24617
24618 let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
24619 let old_generation = controller.backend().generation();
24620 let (result_tx, result_rx) = mpsc::channel();
24621 let old_handle = controller
24622 .try_spawn_blocking({
24623 let controller = Arc::clone(&controller);
24624 move || ShutdownOnDrop {
24625 controller,
24626 result: result_tx,
24627 }
24628 })
24629 .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
24630 wait_until(
24631 "the first-generation blocking result to be buffered",
24632 || {
24633 controller
24634 .metrics
24635 .blocking_tasks_completed
24636 .load(Ordering::Acquire)
24637 == 1
24638 },
24639 );
24640 controller.shutdown().unwrap();
24641 controller.start().unwrap();
24642 let new_generation = controller.backend().generation();
24643 assert_ne!(new_generation, old_generation);
24644
24645 let (holder_started_tx, holder_started_rx) = mpsc::channel();
24646 let (release_holder_tx, release_holder_rx) = mpsc::channel();
24647 let holder = controller
24648 .try_spawn_blocking(move || {
24649 holder_started_tx.send(()).unwrap();
24650 release_holder_rx.recv().unwrap();
24651 })
24652 .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
24653 holder_started_rx
24654 .recv_timeout(Duration::from_secs(2))
24655 .unwrap();
24656
24657 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24658 let shutdown_controller = Arc::clone(&controller);
24659 let shutdown = std::thread::spawn(move || {
24660 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24661 });
24662 wait_until("the replacement generation to enter Stopping", || {
24663 matches!(
24664 controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
24665 RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
24666 )
24667 });
24668
24669 let dropper = std::thread::spawn(move || drop(old_handle));
24670 let (observed_generation, reentrant_shutdown) =
24671 match result_rx.recv_timeout(Duration::from_secs(2)) {
24672 Ok(result) => result,
24673 Err(error) => {
24674 release_holder_tx.send(()).unwrap();
24675 let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
24676 let _ = dropper.join();
24677 let _ = shutdown.join();
24678 panic!("retained first-generation output waited on replacement shutdown: {error}");
24679 }
24680 };
24681 assert_eq!(observed_generation, Some(old_generation));
24682 let error = reentrant_shutdown
24683 .expect_err("a retained first-generation output must not control replacement shutdown");
24684 assert!(error.to_string().contains("retired generation"));
24685
24686 release_holder_tx.send(()).unwrap();
24687 shutdown_rx
24688 .recv_timeout(Duration::from_secs(2))
24689 .unwrap()
24690 .unwrap();
24691 dropper.join().unwrap();
24692 shutdown.join().unwrap();
24693 futures::executor::block_on(holder).unwrap();
24694 }
24695
24696 #[cfg(not(target_family = "wasm"))]
24697 #[test]
24698 fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
24699 use std::sync::mpsc;
24700
24701 let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
24702 let generation = controller.backend().generation();
24703 let dispatch = controller_current_thread_dispatch(&controller);
24704 let (admitted_tx, admitted_rx) = mpsc::channel();
24705 let (release_tx, release_rx) = mpsc::channel();
24706 let driver_controller = Arc::clone(&controller);
24707 let driver = std::thread::spawn(move || {
24708 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
24709 *slot.borrow_mut() = Some(Box::new(move || {
24710 admitted_tx.send(()).unwrap();
24711 release_rx.recv().unwrap();
24712 }));
24713 });
24714 driver_controller.drive_current_thread_dispatch(dispatch, false);
24715 });
24716 admitted_rx
24717 .recv_timeout(Duration::from_secs(2))
24718 .expect("the host turn must claim its scheduler role before execution");
24719
24720 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24721 let shutdown_controller = Arc::clone(&controller);
24722 let shutdown = std::thread::spawn(move || {
24723 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24724 });
24725 wait_until(
24726 "shutdown to close the admitted CurrentThread generation",
24727 || {
24728 let state = controller
24729 .state
24730 .lock()
24731 .unwrap_or_else(std::sync::PoisonError::into_inner);
24732 matches!(
24733 &state.lifecycle,
24734 RuntimeLifecycle::Stopping(identity) if identity.generation == generation
24735 )
24736 },
24737 );
24738 assert!(
24739 shutdown_rx
24740 .recv_timeout(Duration::from_millis(200))
24741 .is_err(),
24742 "shutdown must wait for an admitted host callback even before it starts draining"
24743 );
24744
24745 release_tx.send(()).unwrap();
24746 driver.join().unwrap();
24747 shutdown_rx
24748 .recv_timeout(Duration::from_secs(2))
24749 .unwrap()
24750 .unwrap();
24751 shutdown.join().unwrap();
24752 }
24753
24754 #[test]
24755 fn spawn_racing_shutdown_cannot_recreate_backend() {
24756 use std::sync::mpsc;
24757
24758 let controller = Arc::new(current_thread_controller("lifecycle-race"));
24759 controller.start().expect("runtime must start");
24760 drop(controller.backend());
24761
24762 let (at_hook_tx, at_hook_rx) = mpsc::channel();
24763 let (release_tx, release_rx) = mpsc::channel();
24764 let ran = Arc::new(AtomicBool::new(false));
24765 let ran_task = Arc::clone(&ran);
24766 let worker_controller = Arc::clone(&controller);
24767 let worker = std::thread::spawn(move || {
24768 BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
24769 *slot.borrow_mut() = Some(Box::new(move || {
24770 at_hook_tx
24771 .send(())
24772 .expect("test coordinator must still be listening");
24773 release_rx
24774 .recv()
24775 .expect("test coordinator must release the submission");
24776 }));
24777 });
24778
24779 let future = async move {
24780 ran_task.store(true, Ordering::SeqCst);
24781 };
24782 match worker_controller.try_spawn_detached(future) {
24783 Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
24784 Err(future) => drop(future),
24785 }
24786 });
24787
24788 at_hook_rx
24789 .recv_timeout(Duration::from_secs(5))
24790 .expect("submitting thread must stop before taking the controller lock");
24791 controller.shutdown().expect("runtime must stop");
24792 release_tx
24793 .send(())
24794 .expect("submitting thread must still be waiting");
24795 worker.join().expect("submitting thread must not panic");
24796
24797 assert!(
24798 !ran.load(Ordering::SeqCst),
24799 "the rejected task must never run"
24800 );
24801 let state = controller
24802 .state
24803 .lock()
24804 .unwrap_or_else(std::sync::PoisonError::into_inner);
24805 assert!(
24806 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
24807 "the rejected submission must not lazily recreate a backend"
24808 );
24809 }
24810
24811 #[cfg(not(target_family = "wasm"))]
24812 #[test]
24813 fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
24814 use std::sync::mpsc;
24815
24816 struct BlockingDropFuture {
24817 work: Arc<GenerationWork>,
24818 entered: mpsc::Sender<(Option<u64>, usize)>,
24819 release: mpsc::Receiver<()>,
24820 }
24821
24822 impl Future for BlockingDropFuture {
24823 type Output = ();
24824
24825 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24826 Poll::Pending
24827 }
24828 }
24829
24830 impl Drop for BlockingDropFuture {
24831 fn drop(&mut self) {
24832 let active = self
24833 .work
24834 .state
24835 .lock()
24836 .unwrap_or_else(std::sync::PoisonError::into_inner)
24837 .active;
24838 self
24839 .entered
24840 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24841 .unwrap();
24842 self.release.recv().unwrap();
24843 }
24844 }
24845
24846 let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
24847 let backend = controller.backend();
24848 let old_generation = backend.generation();
24849 let work = Arc::clone(&backend.work);
24850 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24851 panic!("configured CurrentThread backend must create a CurrentThread executor");
24852 };
24853 let executor = Arc::clone(executor);
24854 drop(backend);
24855
24856 let (registered_tx, registered_rx) = mpsc::channel();
24857 let (release_submission_tx, release_submission_rx) = mpsc::channel();
24858 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24859 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24860 let submit_controller = Arc::clone(&controller);
24861 let submit_work = Arc::clone(&work);
24862 let submitter = std::thread::spawn(move || {
24863 AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
24864 *slot.borrow_mut() = Some(Box::new(move || {
24865 registered_tx.send(()).unwrap();
24866 release_submission_rx.recv().unwrap();
24867 }));
24868 });
24869 submit_controller
24870 .try_spawn_detached(BlockingDropFuture {
24871 work: submit_work,
24872 entered: drop_entered_tx,
24873 release: release_drop_rx,
24874 })
24875 .unwrap_or_else(|_| {
24876 panic!("the submission linearized before shutdown and must be accepted")
24877 });
24878 });
24879 registered_rx
24880 .recv_timeout(Duration::from_secs(2))
24881 .expect("the submitting thread must register generation work before shutdown");
24882
24883 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24884 let shutdown_controller = Arc::clone(&controller);
24885 let shutdown = std::thread::spawn(move || {
24886 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24887 });
24888 wait_until("CurrentThread shutdown to close the runnable queue", || {
24889 executor
24890 .queue
24891 .lock()
24892 .unwrap_or_else(std::sync::PoisonError::into_inner)
24893 .closed
24894 });
24895 release_submission_tx.send(()).unwrap();
24896
24897 let (drop_generation, active_during_drop) = drop_entered_rx
24898 .recv_timeout(Duration::from_secs(2))
24899 .expect("the closed queue must destroy the accepted unpolled future");
24900 assert_eq!(drop_generation, Some(old_generation));
24901 assert_eq!(
24902 active_during_drop, 1,
24903 "accepted generation work must remain registered through user future destruction"
24904 );
24905
24906 let (start_tx, start_rx) = mpsc::channel();
24907 let start_controller = Arc::clone(&controller);
24908 let starter = std::thread::spawn(move || {
24909 start_tx.send(start_controller.start()).unwrap();
24910 });
24911 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
24912 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
24913 let shutdown_overlapped = early_shutdown.is_some();
24914 let restart_overlapped = early_start.is_some();
24915
24916 release_drop_tx.send(()).unwrap();
24917 submitter.join().unwrap();
24918 let shutdown_result = early_shutdown.unwrap_or_else(|| {
24919 shutdown_rx
24920 .recv_timeout(Duration::from_secs(2))
24921 .expect("shutdown must finish after drop")
24922 });
24923 shutdown_result.unwrap();
24924 shutdown.join().unwrap();
24925 let start_result = early_start.unwrap_or_else(|| {
24926 start_rx
24927 .recv_timeout(Duration::from_secs(2))
24928 .expect("restart must follow shutdown")
24929 });
24930 start_result.unwrap();
24931 starter.join().unwrap();
24932
24933 assert!(
24934 !shutdown_overlapped,
24935 "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
24936 );
24937 assert!(
24938 !restart_overlapped,
24939 "restart must not create a new generation while an accepted old-generation future is being destroyed"
24940 );
24941 assert_ne!(controller.backend().generation(), old_generation);
24942 controller.shutdown().unwrap();
24943 }
24944
24945 #[cfg(not(target_family = "wasm"))]
24946 #[test]
24947 fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
24948 use std::sync::mpsc;
24949
24950 struct BlockingDrop {
24951 work: Arc<GenerationWork>,
24952 entered: mpsc::Sender<(Option<u64>, usize)>,
24953 release: mpsc::Receiver<()>,
24954 }
24955
24956 impl Drop for BlockingDrop {
24957 fn drop(&mut self) {
24958 let active = self
24959 .work
24960 .state
24961 .lock()
24962 .unwrap_or_else(std::sync::PoisonError::into_inner)
24963 .active;
24964 self
24965 .entered
24966 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24967 .unwrap();
24968 self.release.recv().unwrap();
24969 }
24970 }
24971
24972 let controller = Arc::new(multi_thread_controller(
24973 "accepted-blocking-submission-drop",
24974 2,
24975 1,
24976 ));
24977 let backend = controller.backend();
24978 let old_generation = backend.generation();
24979 let work = Arc::clone(&backend.work);
24980 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
24981 panic!("configured MultiThread backend must create a MultiThread executor");
24982 };
24983 let executor = Arc::downgrade(executor);
24984 drop(backend);
24985
24986 let (registered_tx, registered_rx) = mpsc::channel();
24987 let (release_submission_tx, release_submission_rx) = mpsc::channel();
24988 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24989 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24990 let submit_controller = Arc::clone(&controller);
24991 let submitter = std::thread::spawn(move || {
24992 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
24993 *slot.borrow_mut() = Some(Box::new(move || {
24994 registered_tx.send(()).unwrap();
24995 release_submission_rx.recv().unwrap();
24996 }));
24997 });
24998 let drop_signal = BlockingDrop {
24999 work,
25000 entered: drop_entered_tx,
25001 release: release_drop_rx,
25002 };
25003 let handle = submit_controller
25004 .try_spawn_blocking(move || {
25005 let _drop_signal = drop_signal;
25006 })
25007 .unwrap_or_else(|_| {
25008 panic!("the submission linearized before shutdown and must be accepted")
25009 });
25010 assert!(
25011 futures::executor::block_on(handle).is_err(),
25012 "the executor must cancel work that reaches its queue after shutdown closed it"
25013 );
25014 });
25015 registered_rx
25016 .recv_timeout(Duration::from_secs(2))
25017 .expect("the submitting thread must register generation work before shutdown");
25018
25019 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25020 let shutdown_controller = Arc::clone(&controller);
25021 let shutdown = std::thread::spawn(move || {
25022 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25023 });
25024 wait_until("MultiThread shutdown to close the blocking queue", || {
25025 executor
25026 .upgrade()
25027 .expect("the retiring executor must remain alive until shutdown completes")
25028 .blocking_queue
25029 .lock()
25030 .unwrap_or_else(std::sync::PoisonError::into_inner)
25031 .closed
25032 });
25033 release_submission_tx.send(()).unwrap();
25034
25035 let (drop_generation, active_during_drop) = drop_entered_rx
25036 .recv_timeout(Duration::from_secs(2))
25037 .expect("the closed queue must destroy the accepted blocking closure");
25038 assert_eq!(drop_generation, Some(old_generation));
25039 assert_eq!(
25040 active_during_drop, 1,
25041 "accepted generation work must remain registered through blocking closure destruction"
25042 );
25043
25044 let (start_tx, start_rx) = mpsc::channel();
25045 let start_controller = Arc::clone(&controller);
25046 let starter = std::thread::spawn(move || {
25047 start_tx.send(start_controller.start()).unwrap();
25048 });
25049 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
25050 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
25051 let shutdown_overlapped = early_shutdown.is_some();
25052 let restart_overlapped = early_start.is_some();
25053
25054 release_drop_tx.send(()).unwrap();
25055 submitter.join().unwrap();
25056 let shutdown_result = early_shutdown.unwrap_or_else(|| {
25057 shutdown_rx
25058 .recv_timeout(Duration::from_secs(2))
25059 .expect("shutdown must finish after drop")
25060 });
25061 shutdown_result.unwrap();
25062 shutdown.join().unwrap();
25063 let start_result = early_start.unwrap_or_else(|| {
25064 start_rx
25065 .recv_timeout(Duration::from_secs(2))
25066 .expect("restart must follow shutdown")
25067 });
25068 start_result.unwrap();
25069 starter.join().unwrap();
25070
25071 assert!(
25072 !shutdown_overlapped,
25073 "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
25074 );
25075 assert!(
25076 !restart_overlapped,
25077 "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
25078 );
25079 assert_ne!(controller.backend().generation(), old_generation);
25080 controller.shutdown().unwrap();
25081 }
25082
25083 #[cfg(not(target_family = "wasm"))]
25084 #[test]
25085 fn multi_thread_controller_reports_its_exact_physical_worker_count() {
25086 let controller = multi_thread_controller("lifecycle-physical", 1, 8);
25087 let options = controller.options();
25088 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
25089
25090 let backend = controller.backend();
25091 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25092 panic!("configured MultiThread backend must create a Rayon executor");
25093 };
25094 assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
25095 let lifecycle = Arc::clone(&executor.worker_lifecycle);
25096 drop(backend);
25097
25098 controller.shutdown().expect("runtime must stop");
25099 assert_eq!(
25100 lifecycle.remaining(),
25101 0,
25102 "shutdown must observe every physical worker exit"
25103 );
25104 }
25105
25106 #[cfg(not(target_family = "wasm"))]
25107 #[test]
25108 fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
25109 use std::sync::mpsc;
25126
25127 let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
25128 let backend = controller.backend();
25129 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25130 panic!("configured MultiThread backend must create a Rayon executor");
25131 };
25132 let worker_count = executor.pool.current_num_threads();
25133 let completed = Arc::new(AtomicUsize::new(0));
25134 let probe_completed = Arc::clone(&completed);
25135 executor.pool.broadcast(move |_| {
25136 WORKER_TLS_DROP_PROBE.with(|slot| {
25137 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25138 action: Some(WorkerTlsDropAction::Record {
25139 completed: Arc::clone(&probe_completed),
25140 }),
25141 });
25142 assert!(
25143 previous.is_none(),
25144 "each worker must install exactly one TLS drop probe"
25145 );
25146 });
25147 });
25148 drop(backend);
25149
25150 assert_eq!(
25151 completed.load(Ordering::SeqCst),
25152 0,
25153 "no worker TLS destructor may run before shutdown retires the workers"
25154 );
25155
25156 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25157 let shutdown_controller = Arc::clone(&controller);
25158 let sample_completed = Arc::clone(&completed);
25159 let shutdown = std::thread::spawn(move || {
25160 let result = shutdown_controller.shutdown();
25161 let seen_at_return = sample_completed.load(Ordering::SeqCst);
25165 shutdown_tx.send((result, seen_at_return)).unwrap();
25166 });
25167 let (shutdown_result, seen_at_return) = shutdown_rx
25168 .recv_timeout(Duration::from_secs(10))
25169 .expect("shutdown must finish once worker TLS destructors have run");
25170 shutdown_result.unwrap();
25171 assert_eq!(
25172 seen_at_return, worker_count,
25173 "every worker TLS destructor must have completed before shutdown() returned: \
25174 the worker-join barrier must include TLS-destructor completion"
25175 );
25176 join_within(
25177 "the shutdown thread exits after teardown",
25178 shutdown,
25179 Duration::from_secs(30),
25180 );
25181 }
25182
25183 #[cfg(not(target_family = "wasm"))]
25184 #[test]
25185 fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
25186 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
25187
25188 if std::env::var_os(CHILD_ENV).is_some() {
25189 use std::sync::mpsc;
25190
25191 let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
25192 let backend = controller.backend();
25193 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25194 panic!("configured MultiThread backend must create a Rayon executor");
25195 };
25196 let installed = Arc::new(AtomicBool::new(false));
25197 let (completed_tx, completed_rx) = mpsc::channel();
25198 let probe_controller = Arc::clone(&controller);
25199 let probe_installed = Arc::clone(&installed);
25200 executor.pool.broadcast(move |_| {
25201 if probe_installed.swap(true, Ordering::SeqCst) {
25202 return;
25203 }
25204 WORKER_TLS_DROP_PROBE.with(|slot| {
25205 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25206 action: Some(WorkerTlsDropAction::ReenterLifecycle {
25207 controller: Arc::clone(&probe_controller),
25208 completed: completed_tx.clone(),
25209 }),
25210 });
25211 assert!(
25212 previous.is_none(),
25213 "the selected worker must have no prior TLS probe"
25214 );
25215 });
25216 });
25217 assert!(
25218 installed.load(Ordering::SeqCst),
25219 "one worker must install the lifecycle probe"
25220 );
25221 drop(backend);
25222
25223 controller
25224 .shutdown()
25225 .expect("external shutdown must join the retiring workers");
25226 let (start, shutdown) = completed_rx
25227 .recv_timeout(Duration::from_secs(2))
25228 .expect("the TLS destructor must complete both lifecycle reentry attempts");
25229 assert_eq!(
25230 start.unwrap_err(),
25231 "cannot restart the async runtime from work in the generation being stopped"
25232 );
25233 assert_eq!(
25234 shutdown.unwrap_err(),
25235 "cannot wait for async runtime shutdown from work in the generation being stopped"
25236 );
25237 return;
25238 }
25239
25240 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
25241 .arg("--exact")
25242 .arg(
25243 "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
25244 )
25245 .arg("--nocapture")
25246 .env(CHILD_ENV, "1")
25247 .stdout(std::process::Stdio::piped())
25248 .stderr(std::process::Stdio::piped())
25249 .spawn()
25250 .expect("the TLS lifecycle-reentry subprocess must start");
25251 let deadline = Instant::now() + Duration::from_secs(5);
25252 let status = loop {
25253 if let Some(status) = child
25254 .try_wait()
25255 .expect("the subprocess status must be readable")
25256 {
25257 break status;
25258 }
25259 if Instant::now() >= deadline {
25260 let _ = child.kill();
25261 let output = child
25262 .wait_with_output()
25263 .expect("the timed-out subprocess must be reaped");
25264 panic!(
25265 "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
25266 String::from_utf8_lossy(&output.stdout),
25267 String::from_utf8_lossy(&output.stderr)
25268 );
25269 }
25270 std::thread::sleep(Duration::from_millis(10));
25271 };
25272 let output = child
25273 .wait_with_output()
25274 .expect("the completed subprocess output must be read");
25275 assert!(
25276 status.success(),
25277 "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
25278 status.code(),
25279 String::from_utf8_lossy(&output.stdout),
25280 String::from_utf8_lossy(&output.stderr)
25281 );
25282 }
25283
25284 #[cfg(not(target_family = "wasm"))]
25285 #[test]
25286 fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
25287 use std::sync::mpsc;
25288
25289 struct DropSignal(mpsc::Sender<()>);
25290 impl Drop for DropSignal {
25291 fn drop(&mut self) {
25292 let _ = self.0.send(());
25293 }
25294 }
25295
25296 let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
25297 let (polled_tx, polled_rx) = mpsc::channel();
25298 let (dropped_tx, dropped_rx) = mpsc::channel();
25299 controller
25300 .try_spawn_detached(async move {
25301 let _drop_signal = DropSignal(dropped_tx);
25302 polled_tx.send(()).unwrap();
25303 std::future::pending::<()>().await;
25304 })
25305 .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
25306 polled_rx
25307 .recv_timeout(Duration::from_secs(5))
25308 .expect("the pending task must be polled before shutdown");
25309
25310 controller
25311 .shutdown()
25312 .expect("shutdown must cancel accepted async work");
25313 dropped_rx
25314 .recv_timeout(Duration::from_secs(5))
25315 .expect("shutdown must drop the accepted task future before returning");
25316 }
25317
25318 #[cfg(not(target_family = "wasm"))]
25319 #[test]
25320 fn current_thread_shutdown_outranks_perpetual_self_wake() {
25321 use std::sync::mpsc;
25322
25323 struct SelfWakingPending {
25324 entered: Option<mpsc::Sender<()>>,
25325 }
25326
25327 impl Future for SelfWakingPending {
25328 type Output = ();
25329
25330 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25331 if let Some(entered) = self.entered.take() {
25332 entered.send(()).unwrap();
25333 }
25334 cx.waker().wake_by_ref();
25335 Poll::Pending
25336 }
25337 }
25338
25339 let controller = Arc::new(current_thread_controller(
25340 "current-thread-self-wake-shutdown",
25341 ));
25342 let (entered_tx, entered_rx) = mpsc::channel();
25343 let (outcome_tx, outcome_rx) = mpsc::channel();
25344 let block_on_controller = Arc::clone(&controller);
25345 let runner = std::thread::spawn(move || {
25346 let mut future = std::pin::pin!(SelfWakingPending {
25347 entered: Some(entered_tx)
25348 });
25349 outcome_tx
25350 .send(block_on_controller.block_on(future.as_mut()))
25351 .unwrap();
25352 });
25353 entered_rx
25354 .recv_timeout(Duration::from_secs(2))
25355 .expect("the self-waking future must be polled before shutdown");
25356
25357 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25358 let shutdown_controller = Arc::clone(&controller);
25359 let shutdown = std::thread::spawn(move || {
25360 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25361 });
25362 shutdown_rx
25363 .recv_timeout(Duration::from_secs(2))
25364 .expect("shutdown must outrank a stored self-wake permit")
25365 .unwrap();
25366 assert_eq!(
25367 outcome_rx
25368 .recv_timeout(Duration::from_secs(2))
25369 .expect("the raw block_on driver must report shutdown"),
25370 BlockOnOutcome::Stopped
25371 );
25372 runner.join().unwrap();
25373 shutdown.join().unwrap();
25374 }
25375
25376 #[cfg(not(target_family = "wasm"))]
25377 #[test]
25378 fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
25379 use std::sync::mpsc;
25380
25381 let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
25382 let backend = controller.backend();
25383 let executor = match &backend.executor {
25384 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25385 RuntimeExecutor::CurrentThread(_) => unreachable!(),
25386 };
25387 let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
25388 let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
25389 for _ in 0..2 {
25390 let worker_ready_tx = worker_ready_tx.clone();
25391 let release_workers = Arc::clone(&release_workers);
25392 executor.pool.spawn(move || {
25393 worker_ready_tx.send(()).unwrap();
25394 let (released, changed) = &*release_workers;
25395 let mut released = released
25396 .lock()
25397 .unwrap_or_else(std::sync::PoisonError::into_inner);
25398 while !*released {
25399 released = changed
25400 .wait(released)
25401 .unwrap_or_else(std::sync::PoisonError::into_inner);
25402 }
25403 });
25404 }
25405 drop(worker_ready_tx);
25406 for _ in 0..2 {
25407 worker_ready_rx
25408 .recv_timeout(Duration::from_secs(5))
25409 .expect("both workers must be occupied before the FIFO runnable is submitted");
25410 }
25411 drop(executor);
25412 drop(backend);
25413
25414 let polls = Arc::new(AtomicUsize::new(0));
25415 let handle = controller.spawn(StopPollProbe {
25416 polls: Arc::clone(&polls),
25417 first_poll: None,
25418 });
25419 wait_until("the FIFO runnable to be queued", || {
25420 controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
25421 });
25422
25423 let observed_queue = Arc::new(Mutex::new(None));
25424 let hook_observed_queue = Arc::clone(&observed_queue);
25425 let hook_metrics = Arc::clone(&controller.metrics);
25426 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25427 *slot.borrow_mut() = Some(Box::new(move || {
25428 let (released, changed) = &*release_workers;
25429 *released
25430 .lock()
25431 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
25432 changed.notify_all();
25433 wait_until("the stopped FIFO runnable claim", || {
25434 hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
25435 });
25436 *hook_observed_queue
25437 .lock()
25438 .unwrap_or_else(std::sync::PoisonError::into_inner) =
25439 Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
25440 }));
25441 });
25442
25443 controller
25444 .shutdown()
25445 .expect("shutdown must cancel the stopped FIFO claim");
25446 assert_eq!(polls.load(Ordering::SeqCst), 0);
25447 assert_eq!(
25448 *observed_queue
25449 .lock()
25450 .unwrap_or_else(std::sync::PoisonError::into_inner),
25451 Some(0)
25452 );
25453 assert_eq!(
25454 controller.metrics.queued_runnables.load(Ordering::SeqCst),
25455 0
25456 );
25457 drop(handle);
25458 }
25459
25460 #[cfg(not(target_family = "wasm"))]
25461 #[test]
25462 fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
25463 use std::sync::mpsc;
25464
25465 let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
25466 let backend = controller.backend();
25467 let executor = match &backend.executor {
25468 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25469 RuntimeExecutor::CurrentThread(_) => unreachable!(),
25470 };
25471 let polls = Arc::new(AtomicUsize::new(0));
25472 let scheduler = Arc::downgrade(&executor);
25473 let (runnable, task) = async_task::spawn(
25474 StopPollProbe {
25475 polls: Arc::clone(&polls),
25476 first_poll: None,
25477 },
25478 move |runnable| {
25479 if let Some(executor) = scheduler.upgrade() {
25480 executor.schedule(runnable);
25481 }
25482 },
25483 );
25484 let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
25485 let (release_slot_tx, release_slot_rx) = mpsc::channel();
25486 let (claim_done_tx, claim_done_rx) = mpsc::channel();
25487 let worker_executor = Arc::clone(&executor);
25488 let worker_metrics = Arc::clone(&controller.metrics);
25489 executor.pool.spawn(move || {
25490 let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
25491 worker_executor.schedule(runnable);
25492 slot_ready_tx.send(()).unwrap();
25493 release_slot_rx.recv().unwrap();
25494 let claimed = worker_executor.run_one_runnable();
25495 claim_done_tx
25496 .send((
25497 claimed,
25498 worker_metrics.queued_runnables.load(Ordering::SeqCst),
25499 ))
25500 .unwrap();
25501 });
25502 slot_ready_rx
25503 .recv_timeout(Duration::from_secs(5))
25504 .expect("the worker must publish the LIFO runnable before shutdown");
25505 assert_eq!(
25506 controller.metrics.queued_runnables.load(Ordering::SeqCst),
25507 1
25508 );
25509 drop(executor);
25510 drop(backend);
25511
25512 let observed_claim = Arc::new(Mutex::new(None));
25513 let hook_observed_claim = Arc::clone(&observed_claim);
25514 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25515 *slot.borrow_mut() = Some(Box::new(move || {
25516 release_slot_tx.send(()).unwrap();
25517 let result = claim_done_rx
25518 .recv_timeout(Duration::from_secs(5))
25519 .expect("the stopped LIFO runnable claim must complete before abort");
25520 *hook_observed_claim
25521 .lock()
25522 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
25523 }));
25524 });
25525
25526 controller
25527 .shutdown()
25528 .expect("shutdown must cancel the stopped LIFO claim");
25529 assert_eq!(polls.load(Ordering::SeqCst), 0);
25530 assert_eq!(
25531 *observed_claim
25532 .lock()
25533 .unwrap_or_else(std::sync::PoisonError::into_inner),
25534 Some((true, 0))
25535 );
25536 assert_eq!(
25537 controller.metrics.queued_runnables.load(Ordering::SeqCst),
25538 0
25539 );
25540 drop(task);
25541 }
25542
25543 #[cfg(not(target_family = "wasm"))]
25544 #[test]
25545 fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
25546 use std::sync::mpsc;
25547
25548 struct BlockingDrop {
25549 entered: mpsc::Sender<()>,
25550 release: mpsc::Receiver<()>,
25551 }
25552
25553 impl Drop for BlockingDrop {
25554 fn drop(&mut self) {
25555 self.entered.send(()).unwrap();
25556 self.release.recv().unwrap();
25557 }
25558 }
25559
25560 struct TwoPollProbe {
25561 first_poll: Option<mpsc::Sender<()>>,
25562 release_first_poll: mpsc::Receiver<()>,
25563 second_poll: mpsc::Sender<()>,
25564 }
25565
25566 impl Future for TwoPollProbe {
25567 type Output = ();
25568
25569 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25570 if let Some(first_poll) = self.first_poll.take() {
25571 first_poll.send(()).unwrap();
25572 self.release_first_poll.recv().unwrap();
25573 } else {
25574 self.second_poll.send(()).unwrap();
25575 }
25576 cx.waker().wake_by_ref();
25577 Poll::Pending
25578 }
25579 }
25580
25581 let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
25582 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
25583 let (release_drop_tx, release_drop_rx) = mpsc::channel();
25584 let blocking_drop = BlockingDrop {
25585 entered: drop_entered_tx,
25586 release: release_drop_rx,
25587 };
25588 controller
25589 .try_spawn_detached(async move {
25590 let _drop = blocking_drop;
25591 std::future::pending::<()>().await;
25592 })
25593 .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
25594
25595 let (first_poll_tx, first_poll_rx) = mpsc::channel();
25596 let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
25597 let (second_poll_tx, second_poll_rx) = mpsc::channel();
25598 let (outcome_tx, outcome_rx) = mpsc::channel();
25599 let runner_controller = Arc::clone(&controller);
25600 let runner = std::thread::spawn(move || {
25601 let mut future = std::pin::pin!(TwoPollProbe {
25602 first_poll: Some(first_poll_tx),
25603 release_first_poll: release_first_poll_rx,
25604 second_poll: second_poll_tx,
25605 });
25606 outcome_tx
25607 .send(runner_controller.block_on(future.as_mut()))
25608 .unwrap();
25609 });
25610 first_poll_rx
25611 .recv_timeout(Duration::from_secs(2))
25612 .expect("the active block_on poll must enter before shutdown");
25613
25614 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25615 let shutdown_controller = Arc::clone(&controller);
25616 let shutdown = std::thread::spawn(move || {
25617 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25618 });
25619 drop_entered_rx
25620 .recv_timeout(Duration::from_secs(2))
25621 .expect("shutdown must begin destroying queued work");
25622
25623 release_first_poll_tx.send(()).unwrap();
25624 let polled_again = second_poll_rx
25625 .recv_timeout(Duration::from_millis(200))
25626 .is_ok();
25627 release_drop_tx.send(()).unwrap();
25628
25629 assert!(
25630 !polled_again,
25631 "an active block_on driver must observe generation stop before queued user cleanup blocks"
25632 );
25633 assert_eq!(
25634 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25635 BlockOnOutcome::Stopped
25636 );
25637 shutdown_rx
25638 .recv_timeout(Duration::from_secs(2))
25639 .unwrap()
25640 .unwrap();
25641 runner.join().unwrap();
25642 shutdown.join().unwrap();
25643 }
25644
25645 #[cfg(not(target_family = "wasm"))]
25646 #[test]
25647 fn shutdown_cancels_nested_block_on_within_deadline() {
25648 use std::sync::mpsc;
25649
25650 let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
25651 let task_controller = Arc::clone(&controller);
25652 let (entered_tx, entered_rx) = mpsc::channel();
25653 let task = controller
25654 .try_spawn(async move {
25655 entered_tx.send(()).unwrap();
25656 block_on_with_controller(&task_controller, std::future::pending::<()>());
25657 })
25658 .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
25659 entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25660
25661 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25662 let shutdown_controller = Arc::clone(&controller);
25663 let shutdown = std::thread::spawn(move || {
25664 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25665 });
25666 shutdown_rx
25667 .recv_timeout(Duration::from_secs(2))
25668 .expect("shutdown must wake and cancel an active nested block_on")
25669 .unwrap();
25670 shutdown.join().unwrap();
25671 assert!(
25672 futures::executor::block_on(task).is_err(),
25673 "the cancelled nested block_on task must resolve as stopped"
25674 );
25675 }
25676
25677 #[cfg(not(target_family = "wasm"))]
25678 #[test]
25679 fn stopped_public_block_on_drops_future_before_generation_retirement() {
25680 use std::sync::mpsc;
25681
25682 struct PendingDrop {
25683 entered: Option<mpsc::Sender<()>>,
25684 dropped: mpsc::Sender<Option<u64>>,
25685 release: mpsc::Receiver<()>,
25686 }
25687
25688 impl Future for PendingDrop {
25689 type Output = ();
25690
25691 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25692 if let Some(entered) = self.entered.take() {
25693 entered.send(()).unwrap();
25694 }
25695 Poll::Pending
25696 }
25697 }
25698
25699 impl Drop for PendingDrop {
25700 fn drop(&mut self) {
25701 self
25702 .dropped
25703 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25704 .unwrap();
25705 self.release.recv().unwrap();
25706 }
25707 }
25708
25709 let controller = Arc::new(current_thread_controller(
25710 "public-block-on-generation-destruction",
25711 ));
25712 let generation = controller.backend().generation();
25713 let (entered_tx, entered_rx) = mpsc::channel();
25714 let (dropped_tx, dropped_rx) = mpsc::channel();
25715 let (release_tx, release_rx) = mpsc::channel();
25716 let (runner_done_tx, runner_done_rx) = mpsc::channel();
25717 let block_controller = Arc::clone(&controller);
25718 let runner = std::thread::spawn(move || {
25719 let result = catch_unwind(AssertUnwindSafe(|| {
25720 block_on_with_controller(
25721 &block_controller,
25722 PendingDrop {
25723 entered: Some(entered_tx),
25724 dropped: dropped_tx,
25725 release: release_rx,
25726 },
25727 );
25728 }));
25729 runner_done_tx.send(result.is_err()).unwrap();
25730 });
25731 entered_rx
25732 .recv_timeout(Duration::from_secs(2))
25733 .expect("the public block_on future must be polled before shutdown");
25734
25735 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25736 let shutdown_controller = Arc::clone(&controller);
25737 let shutdown = std::thread::spawn(move || {
25738 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25739 });
25740
25741 assert_eq!(
25742 dropped_rx
25743 .recv_timeout(Duration::from_secs(2))
25744 .expect("shutdown must wake block_on and begin destroying its future"),
25745 Some(generation),
25746 "the stopped future must be destroyed under its retiring generation identity"
25747 );
25748 assert!(
25749 shutdown_rx
25750 .recv_timeout(Duration::from_millis(200))
25751 .is_err(),
25752 "shutdown must not publish Stopped while the public block_on future destructor is running"
25753 );
25754
25755 release_tx.send(()).unwrap();
25756 assert!(
25757 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25758 "the public helper must retain its existing early-stop panic behavior"
25759 );
25760 shutdown_rx
25761 .recv_timeout(Duration::from_secs(2))
25762 .unwrap()
25763 .unwrap();
25764 runner.join().unwrap();
25765 shutdown.join().unwrap();
25766 }
25767
25768 #[cfg(not(target_family = "wasm"))]
25769 #[test]
25770 fn shutdown_contains_panicking_nested_block_on_future_drop() {
25771 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
25772
25773 if std::env::var_os(CHILD_ENV).is_some() {
25774 use std::sync::mpsc;
25775
25776 struct PendingPanicOnDrop {
25777 entered: Option<mpsc::Sender<()>>,
25778 }
25779
25780 impl Future for PendingPanicOnDrop {
25781 type Output = ();
25782
25783 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25784 if let Some(entered) = self.entered.take() {
25785 entered.send(()).unwrap();
25786 }
25787 Poll::Pending
25788 }
25789 }
25790
25791 impl Drop for PendingPanicOnDrop {
25792 fn drop(&mut self) {
25793 panic!("intentional nested block_on future destructor panic");
25794 }
25795 }
25796
25797 let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
25798 let task_controller = Arc::clone(&controller);
25799 let (entered_tx, entered_rx) = mpsc::channel();
25800 let task = controller
25801 .try_spawn(async move {
25802 block_on_with_controller(
25803 &task_controller,
25804 PendingPanicOnDrop {
25805 entered: Some(entered_tx),
25806 },
25807 );
25808 })
25809 .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
25810 entered_rx
25811 .recv_timeout(Duration::from_secs(2))
25812 .expect("the nested block_on future must be polled before shutdown");
25813
25814 controller
25815 .shutdown()
25816 .expect("shutdown must contain nested future destruction");
25817 assert!(
25818 futures::executor::block_on(task).is_err(),
25819 "shutdown cancellation must still surface as a task error"
25820 );
25821 return;
25822 }
25823
25824 let output = std::process::Command::new(std::env::current_exe().unwrap())
25825 .arg("--exact")
25826 .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
25827 .arg("--nocapture")
25828 .env(CHILD_ENV, "1")
25829 .output()
25830 .expect("the subprocess test must start");
25831 assert!(
25832 output.status.success(),
25833 "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
25834 output.status.code(),
25835 String::from_utf8_lossy(&output.stdout),
25836 String::from_utf8_lossy(&output.stderr)
25837 );
25838 }
25839
25840 #[test]
25841 fn panicking_future_drop_is_contained_before_async_task_drop() {
25842 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
25843
25844 if std::env::var_os(CHILD_ENV).is_some() {
25845 struct PanicOnDrop;
25846 impl Drop for PanicOnDrop {
25847 fn drop(&mut self) {
25848 panic!("intentional future destructor panic");
25849 }
25850 }
25851
25852 let controller = current_thread_controller("future-drop-subprocess");
25853 let bomb = PanicOnDrop;
25854 controller
25855 .try_spawn_detached(async move {
25856 let _bomb = bomb;
25857 std::future::pending::<()>().await;
25858 })
25859 .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
25860 controller
25861 .shutdown()
25862 .expect("shutdown must contain the future destructor panic");
25863 return;
25864 }
25865
25866 let output = std::process::Command::new(std::env::current_exe().unwrap())
25867 .arg("--exact")
25868 .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
25869 .arg("--nocapture")
25870 .env(CHILD_ENV, "1")
25871 .output()
25872 .expect("the subprocess test must start");
25873 assert!(
25874 output.status.success(),
25875 "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25876 output.status.code(),
25877 String::from_utf8_lossy(&output.stdout),
25878 String::from_utf8_lossy(&output.stderr)
25879 );
25880 }
25881
25882 #[test]
25883 fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
25884 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
25885
25886 if std::env::var_os(CHILD_ENV).is_some() {
25887 struct PanicOnDrop;
25888 impl Drop for PanicOnDrop {
25889 fn drop(&mut self) {
25890 panic!("intentional detached output destructor panic");
25891 }
25892 }
25893
25894 let controller = current_thread_controller("output-drop-subprocess");
25895 let handle = controller
25896 .try_spawn(async { PanicOnDrop })
25897 .unwrap_or_else(|_| panic!("runtime must accept the task"));
25898 drop(handle);
25899 let dispatch = controller_current_thread_dispatch(&controller);
25900 controller.drive_current_thread_dispatch(dispatch, false);
25901 controller.shutdown().unwrap();
25902 return;
25903 }
25904
25905 let output = std::process::Command::new(std::env::current_exe().unwrap())
25906 .arg("--exact")
25907 .arg(
25908 "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
25909 )
25910 .arg("--nocapture")
25911 .env(CHILD_ENV, "1")
25912 .output()
25913 .expect("the subprocess test must start");
25914 assert!(
25915 output.status.success(),
25916 "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25917 output.status.code(),
25918 String::from_utf8_lossy(&output.stdout),
25919 String::from_utf8_lossy(&output.stderr)
25920 );
25921 }
25922
25923 #[cfg(not(target_family = "wasm"))]
25924 #[test]
25925 fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
25926 use std::sync::mpsc;
25927
25928 struct BlockingDrop {
25929 controller: Arc<RuntimeController>,
25930 entered: mpsc::Sender<Option<u64>>,
25931 release: mpsc::Receiver<()>,
25932 reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
25933 }
25934
25935 impl Drop for BlockingDrop {
25936 fn drop(&mut self) {
25937 self
25938 .entered
25939 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25940 .unwrap();
25941 self.release.recv().unwrap();
25942 self
25943 .reentrant_shutdown
25944 .send(self.controller.shutdown())
25945 .unwrap();
25946 }
25947 }
25948
25949 let controller = Arc::new(current_thread_controller(
25950 "current-thread-host-drain-output-retirement",
25951 ));
25952 let old_generation = controller.backend().generation();
25953 let (entered_tx, entered_rx) = mpsc::channel();
25954 let (release_tx, release_rx) = mpsc::channel();
25955 let (reentrant_tx, reentrant_rx) = mpsc::channel();
25956 let output_controller = Arc::clone(&controller);
25957 let handle = controller
25958 .try_spawn(async move {
25959 BlockingDrop {
25960 controller: output_controller,
25961 entered: entered_tx,
25962 release: release_rx,
25963 reentrant_shutdown: reentrant_tx,
25964 }
25965 })
25966 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
25967 drop(handle);
25968 let dispatch = controller_current_thread_dispatch(&controller);
25969
25970 let driver_controller = Arc::clone(&controller);
25971 let driver =
25972 std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
25973 assert_eq!(
25974 entered_rx
25975 .recv_timeout(Duration::from_secs(2))
25976 .expect("the host turn must begin destroying the detached output"),
25977 Some(old_generation),
25978 "detached output destruction must retain the generation that produced it"
25979 );
25980
25981 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25982 let shutdown_controller = Arc::clone(&controller);
25983 let shutdown = std::thread::spawn(move || {
25984 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25985 });
25986 wait_until(
25987 "CurrentThread shutdown to enter the old generation's stopping state",
25988 || {
25989 let state = controller
25990 .state
25991 .lock()
25992 .unwrap_or_else(std::sync::PoisonError::into_inner);
25993 matches!(
25994 &state.lifecycle,
25995 RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
25996 )
25997 },
25998 );
25999
26000 let (restart_tx, restart_rx) = mpsc::channel();
26001 let restart_controller = Arc::clone(&controller);
26002 let restart = std::thread::spawn(move || {
26003 restart_controller.start().unwrap();
26004 restart_tx
26005 .send(restart_controller.backend().generation())
26006 .unwrap();
26007 });
26008
26009 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
26010 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
26011 let shutdown_overlapped = early_shutdown.is_some();
26012 let restart_overlapped = early_restart.is_some();
26013 release_tx.send(()).unwrap();
26014
26015 let reentrant_error = reentrant_rx
26016 .recv_timeout(Duration::from_secs(2))
26017 .expect("the retiring output destructor must finish its lifecycle check")
26018 .expect_err("old-generation output destruction must not shut down a replacement runtime");
26019 assert!(
26020 reentrant_error
26021 .to_string()
26022 .contains("generation being stopped"),
26023 "the destructor must still observe its old generation as Stopping: {reentrant_error}"
26024 );
26025
26026 let shutdown_result = early_shutdown.unwrap_or_else(|| {
26027 shutdown_rx
26028 .recv_timeout(Duration::from_secs(2))
26029 .expect("shutdown must finish after detached output destruction retires")
26030 });
26031 shutdown_result.unwrap();
26032 let new_generation = early_restart.unwrap_or_else(|| {
26033 restart_rx
26034 .recv_timeout(Duration::from_secs(2))
26035 .expect("restart must finish after the old host turn retires")
26036 });
26037
26038 driver.join().unwrap();
26039 shutdown.join().unwrap();
26040 restart.join().unwrap();
26041 assert!(
26042 !shutdown_overlapped,
26043 "shutdown must not publish Stopped while a host turn is destroying detached output"
26044 );
26045 assert!(
26046 !restart_overlapped,
26047 "restart must not overlap a host turn from the retiring generation"
26048 );
26049 assert_ne!(new_generation, old_generation);
26050 controller.shutdown().unwrap();
26051 }
26052
26053 #[cfg(not(target_family = "wasm"))]
26054 #[test]
26055 fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
26056 use std::sync::mpsc;
26057
26058 struct ShutdownOnDrop {
26059 controller: Arc<RuntimeController>,
26060 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26061 }
26062
26063 impl Drop for ShutdownOnDrop {
26064 fn drop(&mut self) {
26065 let _ = self.result.send(self.controller.shutdown());
26066 }
26067 }
26068
26069 let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
26070 let (running_tx, running_rx) = mpsc::channel();
26071 let (release_tx, release_rx) = mpsc::channel();
26072 let running = controller
26073 .try_spawn_blocking(move || {
26074 running_tx.send(()).unwrap();
26075 release_rx.recv().unwrap();
26076 })
26077 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26078 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26079
26080 let (reentrant_tx, reentrant_rx) = mpsc::channel();
26081 let bomb = ShutdownOnDrop {
26082 controller: Arc::clone(&controller),
26083 result: reentrant_tx,
26084 };
26085 let queued = controller
26086 .try_spawn_blocking(move || {
26087 let _bomb = &bomb;
26088 })
26089 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26090
26091 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26092 let shutdown_controller = Arc::clone(&controller);
26093 let shutdown = std::thread::spawn(move || {
26094 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26095 });
26096 let error = reentrant_rx
26097 .recv_timeout(Duration::from_secs(2))
26098 .expect("queued destruction must not self-deadlock")
26099 .expect_err("the retiring generation must reject reentrant shutdown");
26100 assert!(error.to_string().contains("generation being stopped"));
26101 release_tx.send(()).unwrap();
26102 shutdown_rx
26103 .recv_timeout(Duration::from_secs(2))
26104 .unwrap()
26105 .unwrap();
26106 shutdown.join().unwrap();
26107 futures::executor::block_on(running).unwrap();
26108 assert!(futures::executor::block_on(queued).is_err());
26109 }
26110
26111 #[cfg(not(target_family = "wasm"))]
26112 #[test]
26113 fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
26114 use std::sync::mpsc;
26115
26116 struct ShutdownAfterRelease {
26117 controller: Arc<RuntimeController>,
26118 entered: mpsc::Sender<()>,
26119 release: mpsc::Receiver<()>,
26120 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26121 }
26122
26123 impl Drop for ShutdownAfterRelease {
26124 fn drop(&mut self) {
26125 self.entered.send(()).unwrap();
26126 self.release.recv().unwrap();
26127 self.result.send(self.controller.shutdown()).unwrap();
26128 }
26129 }
26130
26131 let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
26132 let (holder_started_tx, holder_started_rx) = mpsc::channel();
26133 let (release_holder_tx, release_holder_rx) = mpsc::channel();
26134 let holder = controller
26135 .try_spawn_blocking(move || {
26136 holder_started_tx.send(()).unwrap();
26137 release_holder_rx.recv().unwrap();
26138 })
26139 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26140 holder_started_rx
26141 .recv_timeout(Duration::from_secs(2))
26142 .unwrap();
26143
26144 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26145 let shutdown_controller = Arc::clone(&controller);
26146 let shutdown = std::thread::spawn(move || {
26147 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26148 });
26149 wait_until("the runtime entered Stopping", || {
26150 matches!(
26151 controller
26152 .state
26153 .lock()
26154 .unwrap_or_else(std::sync::PoisonError::into_inner)
26155 .lifecycle,
26156 RuntimeLifecycle::Stopping(_)
26157 )
26158 });
26159
26160 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26161 let (release_drop_tx, release_drop_rx) = mpsc::channel();
26162 let (reentrant_tx, reentrant_rx) = mpsc::channel();
26163 let rejected_controller = Arc::clone(&controller);
26164 let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
26165 let rejected = std::thread::spawn(move || {
26166 let bomb = ShutdownAfterRelease {
26167 controller: Arc::clone(&rejected_controller),
26168 entered: drop_entered_tx,
26169 release: release_drop_rx,
26170 result: reentrant_tx,
26171 };
26172 let handle = rejected_controller.spawn(async move {
26173 let _bomb = bomb;
26174 std::future::pending::<()>().await;
26175 });
26176 rejected_done_tx
26177 .send(futures::executor::block_on(handle))
26178 .unwrap();
26179 });
26180 drop_entered_rx
26181 .recv_timeout(Duration::from_secs(2))
26182 .unwrap();
26183
26184 release_holder_tx.send(()).unwrap();
26185 assert!(
26186 shutdown_rx
26187 .recv_timeout(Duration::from_millis(200))
26188 .is_err(),
26189 "shutdown crossed into Stopped before rejected destruction finished"
26190 );
26191
26192 let (start_tx, start_rx) = mpsc::channel();
26193 let start_controller = Arc::clone(&controller);
26194 let starter = std::thread::spawn(move || {
26195 start_tx.send(start_controller.start()).unwrap();
26196 });
26197 assert!(
26198 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26199 "restart crossed into a new generation before rejected destruction finished"
26200 );
26201
26202 release_drop_tx.send(()).unwrap();
26203 let error = reentrant_rx
26204 .recv_timeout(Duration::from_secs(2))
26205 .expect("the rejected destructor must complete")
26206 .expect_err("the retiring generation must reject the destructor's shutdown");
26207 assert!(error.to_string().contains("generation being stopped"));
26208 assert!(
26209 rejected_done_rx
26210 .recv_timeout(Duration::from_secs(2))
26211 .unwrap()
26212 .is_err()
26213 );
26214 rejected.join().unwrap();
26215
26216 shutdown_rx
26217 .recv_timeout(Duration::from_secs(2))
26218 .unwrap()
26219 .unwrap();
26220 shutdown.join().unwrap();
26221 start_rx
26222 .recv_timeout(Duration::from_secs(2))
26223 .unwrap()
26224 .unwrap();
26225 starter.join().unwrap();
26226 futures::executor::block_on(holder).unwrap();
26227 controller.shutdown().unwrap();
26228 }
26229
26230 #[cfg(not(target_family = "wasm"))]
26231 #[test]
26232 fn shutdown_contains_hostile_caught_panic_payload() {
26233 use std::{panic::panic_any, sync::mpsc};
26234
26235 struct PanicAgain;
26236 impl Drop for PanicAgain {
26237 fn drop(&mut self) {
26238 panic!("caught panic payload destructor escaped");
26239 }
26240 }
26241
26242 struct PanicPayloadOnDrop(mpsc::Sender<()>);
26243 impl Drop for PanicPayloadOnDrop {
26244 fn drop(&mut self) {
26245 let _ = self.0.send(());
26246 panic_any(PanicAgain);
26247 }
26248 }
26249
26250 let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
26251 let (running_tx, running_rx) = mpsc::channel();
26252 let (release_tx, release_rx) = mpsc::channel();
26253 let running = controller
26254 .try_spawn_blocking(move || {
26255 running_tx.send(()).unwrap();
26256 release_rx.recv().unwrap();
26257 })
26258 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26259 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26260
26261 let (drop_tx, drop_rx) = mpsc::channel();
26262 let payload = PanicPayloadOnDrop(drop_tx);
26263 let queued = controller
26264 .try_spawn_blocking(move || {
26265 let _payload = &payload;
26266 })
26267 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26268
26269 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26270 let shutdown_controller = Arc::clone(&controller);
26271 let shutdown = std::thread::spawn(move || {
26272 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26273 });
26274 drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
26275 release_tx.send(()).unwrap();
26276 shutdown_rx
26277 .recv_timeout(Duration::from_secs(2))
26278 .expect("hostile panic payload destruction must remain contained")
26279 .unwrap();
26280 shutdown
26281 .join()
26282 .expect("the caught payload must not panic during destruction");
26283 futures::executor::block_on(running).unwrap();
26284 assert!(futures::executor::block_on(queued).is_err());
26285 }
26286
26287 #[cfg(not(target_family = "wasm"))]
26288 #[test]
26289 fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
26290 use std::sync::mpsc;
26291
26292 struct DropSignal(mpsc::Sender<()>);
26293 impl Drop for DropSignal {
26294 fn drop(&mut self) {
26295 let _ = self.0.send(());
26296 }
26297 }
26298
26299 let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
26300 let (running_tx, running_rx) = mpsc::channel();
26301 let (release_tx, release_rx) = mpsc::channel();
26302 let running = controller
26303 .try_spawn_blocking(move || {
26304 running_tx.send(()).unwrap();
26305 release_rx.recv().unwrap();
26306 7usize
26307 })
26308 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26309 running_rx
26310 .recv_timeout(Duration::from_secs(5))
26311 .expect("the first blocking job must hold the only blocking slot");
26312
26313 let queued_ran = Arc::new(AtomicBool::new(false));
26314 let queued_ran_work = Arc::clone(&queued_ran);
26315 let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
26316 let queued_drop_signal = DropSignal(queued_dropped_tx);
26317 let queued = controller
26318 .try_spawn_blocking(move || {
26319 let _drop_signal = queued_drop_signal;
26320 queued_ran_work.store(true, Ordering::SeqCst);
26321 11usize
26322 })
26323 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26324
26325 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26326 let shutdown_controller = Arc::clone(&controller);
26327 let shutdown = std::thread::spawn(move || {
26328 shutdown_controller.shutdown().unwrap();
26329 shutdown_done_tx.send(()).unwrap();
26330 });
26331
26332 queued_dropped_rx
26333 .recv_timeout(Duration::from_secs(5))
26334 .expect("shutdown must drop queued blocking work");
26335 assert!(
26336 !queued_ran.load(Ordering::SeqCst),
26337 "queued blocking work must not execute"
26338 );
26339 assert!(
26340 shutdown_done_rx.try_recv().is_err(),
26341 "shutdown must wait while an accepted blocking closure is still running"
26342 );
26343
26344 release_tx.send(()).unwrap();
26345 shutdown_done_rx
26346 .recv_timeout(Duration::from_secs(5))
26347 .expect("shutdown must finish after running blocking work retires");
26348 shutdown.join().unwrap();
26349 assert_eq!(futures::executor::block_on(running).unwrap(), 7);
26350 assert!(
26351 futures::executor::block_on(queued).is_err(),
26352 "the queued blocking handle must resolve as cancelled"
26353 );
26354 }
26355
26356 #[cfg(not(target_family = "wasm"))]
26357 #[test]
26358 fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
26359 use std::sync::mpsc;
26360
26361 struct PanicOnDrop(mpsc::Sender<()>);
26362 impl Drop for PanicOnDrop {
26363 fn drop(&mut self) {
26364 let _ = self.0.send(());
26365 panic!("intentional queued blocking destructor panic");
26366 }
26367 }
26368
26369 let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
26370 let (running_tx, running_rx) = mpsc::channel();
26371 let (release_tx, release_rx) = mpsc::channel();
26372 let running = controller
26373 .try_spawn_blocking(move || {
26374 running_tx.send(()).unwrap();
26375 release_rx.recv().unwrap();
26376 })
26377 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26378 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26379
26380 let (drop_tx, drop_rx) = mpsc::channel();
26381 let panic_on_drop = PanicOnDrop(drop_tx);
26382 let queued = controller
26383 .try_spawn_blocking(move || {
26384 let _keep_captured_until_run = &panic_on_drop;
26385 })
26386 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26387
26388 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26389 let shutdown_controller = Arc::clone(&controller);
26390 let shutdown = std::thread::spawn(move || {
26391 shutdown_controller.shutdown().unwrap();
26392 shutdown_done_tx.send(()).unwrap();
26393 });
26394
26395 drop_rx
26396 .recv_timeout(Duration::from_secs(5))
26397 .expect("shutdown must attempt to drop the queued closure");
26398 release_tx.send(()).unwrap();
26399 shutdown_done_rx
26400 .recv_timeout(Duration::from_secs(5))
26401 .expect("a panicking queued destructor must not strand shutdown");
26402 shutdown
26403 .join()
26404 .expect("shutdown must contain the destructor panic");
26405 futures::executor::block_on(running).unwrap();
26406 assert!(futures::executor::block_on(queued).is_err());
26407
26408 controller
26409 .start()
26410 .expect("the stopped controller must restart");
26411 let restarted = controller
26412 .try_spawn_blocking(|| 17usize)
26413 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26414 assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
26415 controller.shutdown().unwrap();
26416 }
26417
26418 #[cfg(not(target_family = "wasm"))]
26419 #[test]
26420 fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
26421 use std::{panic::panic_any, sync::mpsc};
26422
26423 struct ReentrantPayload {
26424 controller: Arc<RuntimeController>,
26425 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26426 }
26427
26428 impl Drop for ReentrantPayload {
26429 fn drop(&mut self) {
26430 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26431 let shutdown = self.controller.shutdown();
26432 self.result.send((generation, shutdown)).unwrap();
26433 }
26434 }
26435
26436 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26437
26438 impl Drop for PanicPayloadOnDrop {
26439 fn drop(&mut self) {
26440 panic_any(self.0.take().expect("the result must panic only once"));
26441 }
26442 }
26443
26444 let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
26445 let generation = controller.backend().generation();
26446 let (started_tx, started_rx) = mpsc::channel();
26447 let (release_tx, release_rx) = mpsc::channel();
26448 let (result_tx, result_rx) = mpsc::channel();
26449 let payload_controller = Arc::clone(&controller);
26450 let handle = controller
26451 .try_spawn_blocking(move || {
26452 started_tx.send(()).unwrap();
26453 release_rx.recv().unwrap();
26454 PanicPayloadOnDrop(Some(ReentrantPayload {
26455 controller: payload_controller,
26456 result: result_tx,
26457 }))
26458 })
26459 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26460 started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26461
26462 drop(handle);
26465 release_tx.send(()).unwrap();
26466 let (observed_generation, reentrant_shutdown) = result_rx
26467 .recv_timeout(Duration::from_secs(5))
26468 .expect("the dropped result panic payload must not self-deadlock");
26469 assert_eq!(observed_generation, Some(generation));
26470 let error =
26471 reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
26472 assert!(error.to_string().contains("work running on that runtime"));
26473
26474 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26475 let shutdown_controller = Arc::clone(&controller);
26476 let shutdown = std::thread::spawn(move || {
26477 shutdown_controller.shutdown().unwrap();
26478 shutdown_done_tx.send(()).unwrap();
26479 });
26480 shutdown_done_rx
26481 .recv_timeout(Duration::from_secs(5))
26482 .expect("a dropped-result destructor panic must not strand shutdown");
26483 shutdown
26484 .join()
26485 .expect("shutdown must contain the destructor panic");
26486
26487 controller
26488 .start()
26489 .expect("the stopped controller must restart");
26490 let restarted = controller
26491 .try_spawn_blocking(|| 19usize)
26492 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26493 assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
26494 controller.shutdown().unwrap();
26495 }
26496
26497 #[cfg(not(target_family = "wasm"))]
26498 #[test]
26499 fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
26500 use std::sync::mpsc;
26501
26502 struct PanicOnDrop(mpsc::Sender<()>);
26503
26504 impl Drop for PanicOnDrop {
26505 fn drop(&mut self) {
26506 self.0.send(()).unwrap();
26507 panic!("intentional buffered blocking result destructor panic");
26508 }
26509 }
26510
26511 let options = RuntimeOptions {
26512 flavor: RuntimeFlavor::MultiThread,
26513 worker_threads: 2,
26514 max_blocking_tasks: 1,
26515 thread_name_prefix: "buffered-blocking-result-drop".to_string(),
26516 park_deadline: None,
26517 drain_linger: DEFAULT_DRAIN_LINGER,
26518 };
26519 let executor =
26520 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26521 let (started_tx, started_rx) = mpsc::channel();
26522 let (dropped_tx, dropped_rx) = mpsc::channel();
26523 let handle = executor.schedule_blocking(move || {
26524 started_tx.send(()).unwrap();
26525 PanicOnDrop(dropped_tx)
26526 });
26527 started_rx
26528 .recv_timeout(Duration::from_secs(2))
26529 .expect("the blocking closure must run");
26530 wait_until("the completed blocking result to be buffered", || {
26531 executor.active_blocking.load(Ordering::Acquire) == 0
26532 });
26533 assert!(
26534 dropped_rx.try_recv().is_err(),
26535 "the buffered result must remain owned by the live receiver"
26536 );
26537
26538 let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26539 assert!(
26540 dropped.is_ok(),
26541 "dropping the buffered result must contain its destructor panic"
26542 );
26543 dropped_rx
26544 .recv_timeout(Duration::from_secs(2))
26545 .expect("dropping the receiver must reclaim the buffered result");
26546 }
26547
26548 #[cfg(not(target_family = "wasm"))]
26549 #[test]
26550 fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
26551 use std::sync::mpsc;
26552
26553 struct PanicOnDrop(mpsc::Sender<()>);
26554 impl Drop for PanicOnDrop {
26555 fn drop(&mut self) {
26556 let _ = self.0.send(());
26557 panic!("intentional late blocking destructor panic");
26558 }
26559 }
26560
26561 let executor = multi_thread_executor(2, None, "closed-queue-drop");
26562 executor.begin_shutdown();
26563
26564 let (drop_tx, drop_rx) = mpsc::channel();
26565 let panic_on_drop = PanicOnDrop(drop_tx);
26566 let handle = executor.schedule_blocking(move || {
26567 let _keep_captured_until_run = &panic_on_drop;
26568 });
26569
26570 drop_rx
26571 .recv_timeout(Duration::from_secs(5))
26572 .expect("the closed queue must retire the rejected closure");
26573 assert!(
26574 futures::executor::block_on(handle).is_err(),
26575 "the rejected blocking handle must resolve as cancelled"
26576 );
26577 }
26578
26579 #[cfg(not(target_family = "wasm"))]
26580 #[test]
26581 fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
26582 struct PanicWake;
26583 impl std::task::Wake for PanicWake {
26584 fn wake(self: Arc<Self>) {
26585 panic!("intentional timer waker panic");
26586 }
26587 }
26588
26589 let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
26590 let backend = controller.backend();
26591 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26592 panic!("configured MultiThread backend must create a Rayon executor");
26593 };
26594 let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
26595 let waker = Waker::from(Arc::new(PanicWake));
26596 let mut cx = Context::from_waker(&waker);
26597 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
26598 drop(backend);
26599
26600 controller
26601 .shutdown()
26602 .expect("shutdown must contain the timer waker panic");
26603 controller
26604 .start()
26605 .expect("the stopped controller must restart");
26606 let restarted = controller
26607 .try_spawn_blocking(|| 23usize)
26608 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26609 assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
26610 controller.shutdown().unwrap();
26611 }
26612
26613 #[cfg(not(target_family = "wasm"))]
26614 #[test]
26615 fn multi_thread_restart_waits_for_old_generation_worker_exit() {
26616 use std::sync::mpsc;
26617
26618 let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
26619 let backend = controller.backend();
26620 let old_generation = backend.generation();
26621 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26622 panic!("configured MultiThread backend must create a Rayon executor");
26623 };
26624 let old_workers = Arc::clone(&executor.worker_lifecycle);
26625 drop(backend);
26626
26627 let (running_tx, running_rx) = mpsc::channel();
26628 let (release_tx, release_rx) = mpsc::channel();
26629 let running = controller
26630 .try_spawn_blocking(move || {
26631 running_tx.send(()).unwrap();
26632 release_rx.recv().unwrap();
26633 })
26634 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26635 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26636
26637 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26638 let shutdown_controller = Arc::clone(&controller);
26639 let shutdown = std::thread::spawn(move || {
26640 shutdown_controller.shutdown().unwrap();
26641 shutdown_done_tx.send(()).unwrap();
26642 });
26643 wait_until("controller entered Stopping", || {
26644 let state = controller
26645 .state
26646 .lock()
26647 .unwrap_or_else(std::sync::PoisonError::into_inner);
26648 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
26649 });
26650
26651 let (start_done_tx, start_done_rx) = mpsc::channel();
26652 let start_controller = Arc::clone(&controller);
26653 let starter = std::thread::spawn(move || {
26654 start_controller.start().unwrap();
26655 start_done_tx.send(()).unwrap();
26656 });
26657 assert!(
26658 start_done_rx.try_recv().is_err(),
26659 "start must wait while shutdown is quiescing"
26660 );
26661 assert!(
26662 old_workers.remaining() > 0,
26663 "the held old generation must still own workers"
26664 );
26665
26666 release_tx.send(()).unwrap();
26667 futures::executor::block_on(running).unwrap();
26668 shutdown_done_rx
26669 .recv_timeout(Duration::from_secs(5))
26670 .unwrap();
26671 start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26672 shutdown.join().unwrap();
26673 starter.join().unwrap();
26674 assert_eq!(
26675 old_workers.remaining(),
26676 0,
26677 "restart may begin only after every old worker exits"
26678 );
26679
26680 let backend = controller.backend();
26681 assert_ne!(backend.generation(), old_generation);
26682 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26683 panic!("restarted backend must remain MultiThread");
26684 };
26685 assert_eq!(
26686 executor.pool.current_num_threads(),
26687 controller.options().worker_threads
26688 );
26689 drop(backend);
26690 controller.shutdown().unwrap();
26691 }
26692
26693 #[cfg(not(target_family = "wasm"))]
26694 #[test]
26695 fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
26696 let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
26697 let task_controller = Arc::clone(&controller);
26698 let handle = controller
26699 .try_spawn(async move {
26700 task_controller
26701 .shutdown()
26702 .expect_err("runtime work must not wait for its own generation")
26703 .to_string()
26704 })
26705 .unwrap_or_else(|_| panic!("running runtime must accept the task"));
26706 let message = futures::executor::block_on(handle).unwrap();
26707 assert!(message.contains("cannot shut down"));
26708 controller.shutdown().unwrap();
26709 }
26710
26711 #[cfg(not(target_family = "wasm"))]
26718 #[test]
26719 fn multi_thread_blocking_active_never_exceeds_max_blocking() {
26720 use std::sync::{Barrier, mpsc};
26742 use std::time::Duration;
26743
26744 let (done_tx, done_rx) = mpsc::channel();
26745 let peak_out = Arc::new(Mutex::new(None));
26746 let peak_slot = Arc::clone(&peak_out);
26747 let runner = std::thread::spawn(move || {
26748 let metrics = Arc::new(RuntimeMetrics::default());
26749 let options = RuntimeOptions {
26750 flavor: RuntimeFlavor::MultiThread,
26751 worker_threads: 4,
26752 max_blocking_tasks: 2,
26753 thread_name_prefix: "rd9-blk-cap".to_string(),
26754 park_deadline: None,
26755 drain_linger: DEFAULT_DRAIN_LINGER,
26756 };
26757 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26758
26759 let holder_count = options.max_blocking_tasks; let extra_count = 4usize; let (held_tx, held_rx) = mpsc::channel();
26764 let release = Arc::new(Barrier::new(holder_count + 1));
26765 let mut holder_handles = Vec::new();
26766 for _ in 0..holder_count {
26767 let held_tx = held_tx.clone();
26768 let release = Arc::clone(&release);
26769 holder_handles.push(executor.schedule_blocking(move || {
26770 held_tx.send(()).unwrap();
26771 release.wait();
26772 0usize
26773 }));
26774 }
26775 for _ in 0..holder_count {
26777 held_rx.recv().unwrap();
26778 }
26779
26780 let (extra_started_tx, extra_started_rx) = mpsc::channel();
26782 let mut extra_handles = Vec::new();
26783 for i in 0..extra_count {
26784 let extra_started_tx = extra_started_tx.clone();
26785 extra_handles.push(executor.schedule_blocking(move || {
26786 extra_started_tx.send(i).unwrap();
26787 i * 2
26788 }));
26789 }
26790
26791 assert!(
26794 matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
26795 "a beyond-cap blocking job ran before the cap was released (cap check broken)"
26796 );
26797
26798 release.wait();
26800
26801 for handle in holder_handles {
26802 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
26803 }
26804 for (i, handle) in extra_handles.into_iter().enumerate() {
26807 assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
26808 }
26809
26810 assert_eq!(
26813 metrics.blocking_tasks_started.load(Ordering::Relaxed),
26814 (holder_count + extra_count) as u64
26815 );
26816
26817 *peak_slot
26818 .lock()
26819 .unwrap_or_else(std::sync::PoisonError::into_inner) =
26820 Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
26821 let _ = done_tx.send(());
26822 });
26823
26824 match done_rx.recv_timeout(Duration::from_secs(10)) {
26825 Ok(()) => runner.join().unwrap(),
26826 Err(error) => panic!("blocking-cap test did not complete ({error})"),
26827 }
26828 let peak = peak_out
26829 .lock()
26830 .unwrap_or_else(std::sync::PoisonError::into_inner)
26831 .expect("peak captured");
26832 assert!(
26833 peak >= 1,
26834 "at least one blocking job must have run (peak {peak})"
26835 );
26836 assert!(
26837 peak <= 2,
26838 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
26839 );
26840 }
26841
26842 #[cfg(not(target_family = "wasm"))]
26843 #[test]
26844 fn multi_thread_drain_budget_completes_large_runnable_batch() {
26845 use std::sync::mpsc;
26850 use std::time::{Duration, Instant};
26851
26852 let (done_tx, done_rx) = mpsc::channel();
26853 let runner = std::thread::spawn(move || {
26854 let metrics = Arc::new(RuntimeMetrics::default());
26855 let options = RuntimeOptions {
26856 flavor: RuntimeFlavor::MultiThread,
26857 worker_threads: 2,
26858 max_blocking_tasks: 2,
26859 thread_name_prefix: "rd9-budget".to_string(),
26860 park_deadline: None,
26861 drain_linger: DEFAULT_DRAIN_LINGER,
26862 };
26863 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26864
26865 let task_count = 200usize; let completed = Arc::new(AtomicU64::new(0));
26867 let mut tasks = Vec::new();
26868 for _ in 0..task_count {
26869 let completed = Arc::clone(&completed);
26870 let scheduler = Arc::clone(&executor);
26871 let (runnable, task) = async_task::spawn(
26872 async move {
26873 completed.fetch_add(1, Ordering::Relaxed);
26874 },
26875 move |r| scheduler.schedule(r),
26876 );
26877 executor.schedule(runnable);
26878 tasks.push(task);
26879 }
26880 for task in tasks {
26881 futures::executor::block_on(task);
26882 }
26883
26884 assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
26887 assert_eq!(
26888 metrics.runnable_polls.load(Ordering::Relaxed),
26889 task_count as u64
26890 );
26891
26892 let deadline = Instant::now() + Duration::from_secs(5);
26896 loop {
26897 let active = metrics.active_runnables.load(Ordering::Relaxed);
26898 let queued = metrics.queued_runnables.load(Ordering::Relaxed);
26899 if active == 0 && queued == 0 {
26900 break;
26901 }
26902 assert!(
26903 Instant::now() < deadline,
26904 "runnable counters did not settle to 0: active={active} queued={queued}"
26905 );
26906 std::thread::yield_now();
26907 }
26908 let _ = done_tx.send(());
26909 });
26910
26911 match done_rx.recv_timeout(Duration::from_secs(15)) {
26912 Ok(()) => runner.join().unwrap(),
26913 Err(error) => panic!("drain-budget test did not complete ({error})"),
26914 }
26915 }
26916
26917 #[cfg(not(target_family = "wasm"))]
26918 #[test]
26919 fn multi_thread_spawned_panic_surfaces_as_join_error() {
26920 use std::sync::mpsc;
26927 use std::time::Duration;
26928
26929 let (done_tx, done_rx) = mpsc::channel();
26930 let runner = std::thread::spawn(move || {
26931 let metrics = Arc::new(RuntimeMetrics::default());
26932 let options = RuntimeOptions {
26933 flavor: RuntimeFlavor::MultiThread,
26934 worker_threads: 2,
26935 max_blocking_tasks: 2,
26936 thread_name_prefix: "rd9-panic".to_string(),
26937 park_deadline: None,
26938 drain_linger: DEFAULT_DRAIN_LINGER,
26939 };
26940 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
26941
26942 let scheduler = Arc::clone(&executor);
26943 let wrapped = async {
26944 AssertUnwindSafe(async { panic!("x") })
26945 .catch_unwind()
26946 .await
26947 .map(|()| ContainedTaskOutput::new((), u64::MAX))
26948 .map_err(panic_payload_to_join_error)
26949 };
26950 let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
26951 executor.schedule(runnable);
26952
26953 let result: Result<(), JoinError> =
26954 futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
26955 task: task.fallible(),
26956 awaiter: None,
26957 dependency: Arc::new(TaskDependency::default()),
26958 }));
26959 let _ = done_tx.send(result);
26960 });
26961
26962 let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
26963 Ok(result) => {
26964 runner.join().unwrap();
26965 result
26966 }
26967 Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
26968 };
26969 let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
26970 assert_eq!(error.to_string(), "x");
26971 }
26972
26973 #[test]
26974 fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
26975 use std::sync::mpsc;
26976
26977 let metrics = Arc::new(RuntimeMetrics::default());
26978 metrics.tasks_spawned.store(1, Ordering::Relaxed);
26979 metrics.tasks_completed.store(2, Ordering::Relaxed);
26980 metrics.tasks_panicked.store(3, Ordering::Relaxed);
26981 metrics.runnable_schedules.store(4, Ordering::Relaxed);
26982 metrics.runnable_polls.store(5, Ordering::Relaxed);
26983 metrics
26984 .blocking_tasks_scheduled
26985 .store(14, Ordering::Relaxed);
26986 metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
26987 metrics
26988 .blocking_tasks_completed
26989 .store(11, Ordering::Relaxed);
26990 metrics.runnable_scheduled();
26991 let (guards_live_tx, guards_live_rx) = mpsc::channel();
26992 let (release_tx, release_rx) = mpsc::channel();
26993 let guard_metrics = Arc::clone(&metrics);
26994 let guard_thread = std::thread::spawn(move || {
26995 let runnable_guard = guard_metrics.runnable_started();
26996 let blocking_guard = guard_metrics.blocking_started(true);
26997 guards_live_tx.send(()).unwrap();
26998 release_rx.recv().unwrap();
26999 drop(runnable_guard);
27000 drop(blocking_guard);
27001 });
27002 guards_live_rx.recv().unwrap();
27003 let fingerprint_before_reset = metrics.progress_fingerprint();
27004
27005 metrics.reset();
27006
27007 assert!(
27008 metrics.progress_fingerprint() != fingerprint_before_reset,
27009 "a reset must advance the deadlock detector generation even if counters later repeat"
27010 );
27011 assert_eq!(
27012 metrics.tasks_spawned.load(Ordering::Relaxed),
27013 0,
27014 "tasks_spawned"
27015 );
27016 assert_eq!(
27017 metrics.tasks_completed.load(Ordering::Relaxed),
27018 0,
27019 "tasks_completed"
27020 );
27021 assert_eq!(
27022 metrics.tasks_panicked.load(Ordering::Relaxed),
27023 0,
27024 "tasks_panicked"
27025 );
27026 assert_eq!(
27027 metrics.runnable_schedules.load(Ordering::Relaxed),
27028 0,
27029 "runnable_schedules"
27030 );
27031 assert_eq!(
27032 metrics.runnable_polls.load(Ordering::Relaxed),
27033 0,
27034 "runnable_polls"
27035 );
27036 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
27037 assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
27038 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
27039 assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
27040 assert_eq!(
27041 metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
27042 0,
27043 "blocking_tasks_scheduled"
27044 );
27045 assert_eq!(
27046 metrics.blocking_tasks_started.load(Ordering::Relaxed),
27047 0,
27048 "blocking_tasks_started"
27049 );
27050 assert_eq!(
27051 metrics.blocking_tasks_completed.load(Ordering::Relaxed),
27052 0,
27053 "blocking_tasks_completed"
27054 );
27055 assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
27056 assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
27057
27058 release_tx.send(()).unwrap();
27059 guard_thread.join().unwrap();
27060 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
27061 assert_eq!(
27062 metrics.active_blocking_tasks.load(Ordering::Relaxed),
27063 0,
27064 "a live blocking guard completed after reset without unsigned underflow"
27065 );
27066 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
27067 }
27068
27069 #[test]
27070 fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
27071 use std::sync::mpsc;
27072
27073 fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
27074 RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
27075 *slot.borrow_mut() = Some(Box::new(move || {
27076 entered.send(()).unwrap();
27077 release.recv().unwrap();
27078 }));
27079 });
27080 }
27081
27082 let options = RuntimeOptions {
27083 flavor: RuntimeFlavor::CurrentThread,
27084 worker_threads: 1,
27085 max_blocking_tasks: 1,
27086 thread_name_prefix: "metrics-snapshot".to_string(),
27087 park_deadline: None,
27088 drain_linger: DEFAULT_DRAIN_LINGER,
27089 };
27090
27091 let queued_metrics = Arc::new(RuntimeMetrics::default());
27092 let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
27093 let (queued_release_tx, queued_release_rx) = mpsc::channel();
27094 let queued_writer_metrics = Arc::clone(&queued_metrics);
27095 let queued_writer = std::thread::spawn(move || {
27096 pause_next_high_water_update(queued_entered_tx, queued_release_rx);
27097 queued_writer_metrics.runnable_scheduled();
27098 });
27099 queued_entered_rx.recv().unwrap();
27100 assert_eq!(
27101 queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
27102 0
27103 );
27104 let snapshot = queued_metrics.snapshot(&options);
27105 assert_eq!(snapshot.queued_runnables, 1);
27106 assert_eq!(snapshot.max_queued_runnables, 1);
27107 queued_release_tx.send(()).unwrap();
27108 queued_writer.join().unwrap();
27109 queued_metrics.runnable_cancelled();
27110
27111 let active_metrics = Arc::new(RuntimeMetrics::default());
27112 active_metrics.queued_runnables.store(1, Ordering::Relaxed);
27113 let (active_entered_tx, active_entered_rx) = mpsc::channel();
27114 let (active_release_tx, active_release_rx) = mpsc::channel();
27115 let active_writer_metrics = Arc::clone(&active_metrics);
27116 let active_writer = std::thread::spawn(move || {
27117 pause_next_high_water_update(active_entered_tx, active_release_rx);
27118 let _active = active_writer_metrics.runnable_started();
27119 });
27120 active_entered_rx.recv().unwrap();
27121 assert_eq!(
27122 active_metrics.max_active_runnables.load(Ordering::Relaxed),
27123 0
27124 );
27125 let snapshot = active_metrics.snapshot(&options);
27126 assert_eq!(snapshot.active_runnables, 1);
27127 assert_eq!(snapshot.max_active_runnables, 1);
27128 active_release_tx.send(()).unwrap();
27129 active_writer.join().unwrap();
27130
27131 let blocking_metrics = Arc::new(RuntimeMetrics::default());
27132 let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
27133 let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
27134 let blocking_writer_metrics = Arc::clone(&blocking_metrics);
27135 let blocking_writer = std::thread::spawn(move || {
27136 pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
27137 let _active = blocking_writer_metrics.blocking_started(true);
27138 });
27139 blocking_entered_rx.recv().unwrap();
27140 assert_eq!(
27141 blocking_metrics
27142 .max_active_blocking_tasks
27143 .load(Ordering::Relaxed),
27144 0
27145 );
27146 let snapshot = blocking_metrics.snapshot(&options);
27147 assert_eq!(snapshot.active_blocking_tasks, 1);
27148 assert_eq!(snapshot.max_active_blocking_tasks, 1);
27149 blocking_release_tx.send(()).unwrap();
27150 blocking_writer.join().unwrap();
27151 }
27152
27153 #[test]
27154 fn runtime_metrics_reset_fails_before_generation_wraparound() {
27155 let metrics = RuntimeMetrics::default();
27156 metrics
27157 .reset_generation
27158 .store(u64::MAX - 1, Ordering::SeqCst);
27159 metrics.tasks_spawned.store(7, Ordering::Relaxed);
27160
27161 let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
27162 assert!(exhausted.is_err());
27163 assert!(!metrics.reset_lock.is_poisoned());
27164 assert_eq!(
27165 metrics.reset_generation.load(Ordering::SeqCst),
27166 u64::MAX - 1
27167 );
27168 assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
27169 }
27170
27171 struct NeverReady;
27183 impl Future for NeverReady {
27184 type Output = ();
27185 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27186 Poll::Pending
27187 }
27188 }
27189
27190 #[cfg(not(target_family = "wasm"))]
27191 struct ReadyAfterOwnerLanePublication {
27192 dependency: BlockingDependency,
27193 ready: Arc<AtomicBool>,
27194 }
27195
27196 #[cfg(not(target_family = "wasm"))]
27197 impl Future for ReadyAfterOwnerLanePublication {
27198 type Output = ();
27199
27200 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27201 if self.ready.load(Ordering::SeqCst) {
27202 Poll::Ready(())
27203 } else {
27204 let mut published = PublishedBlockingDependencies::default();
27207 record_blocking_dependency(&mut published, self.dependency);
27208 Poll::Pending
27209 }
27210 }
27211 }
27212
27213 #[test]
27214 fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
27215 use std::sync::mpsc;
27222
27223 let (done_tx, done_rx) = mpsc::channel();
27224 let runner = std::thread::spawn(move || {
27225 let metrics = Arc::new(RuntimeMetrics::default());
27226 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27229
27230 let payload = catch_unwind(AssertUnwindSafe(|| {
27231 let mut future = std::pin::pin!(NeverReady);
27232 executor.block_on(future.as_mut());
27233 }))
27234 .expect_err("a threadless park with an empty queue and no pending wake must panic");
27235 let diagnostic = payload
27236 .downcast_ref::<BlockOnDeadlock>()
27237 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
27238 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
27239 assert_eq!(
27240 diagnostic.park_deadline, None,
27241 "the certain case carries no deadline"
27242 );
27243 let message = diagnostic.to_string();
27244 assert!(
27245 message.contains("block_on") && message.contains("JS"),
27246 "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
27247 );
27248 let _ = done_tx.send(());
27249 });
27250
27251 match done_rx.recv_timeout(Duration::from_secs(10)) {
27252 Ok(()) => runner.join().unwrap(),
27253 Err(error) => panic!(
27254 "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
27255 ),
27256 }
27257 }
27258
27259 #[test]
27260 fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
27261 use std::sync::mpsc;
27267
27268 struct PendThenReady {
27269 polls: usize,
27270 }
27271 impl Future for PendThenReady {
27272 type Output = ();
27273 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
27274 self.polls += 1;
27275 if self.polls >= 2 {
27276 Poll::Ready(())
27277 } else {
27278 cx.waker().wake_by_ref();
27279 Poll::Pending
27280 }
27281 }
27282 }
27283
27284 let (done_tx, done_rx) = mpsc::channel();
27285 let runner = std::thread::spawn(move || {
27286 let metrics = Arc::new(RuntimeMetrics::default());
27287 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27288 let mut future = std::pin::pin!(PendThenReady { polls: 0 });
27289 executor.block_on(future.as_mut());
27290 assert_eq!(
27291 future.polls, 2,
27292 "the self-woken future must have been re-polled to completion"
27293 );
27294 let _ = done_tx.send(());
27295 });
27296
27297 match done_rx.recv_timeout(Duration::from_secs(10)) {
27298 Ok(()) => runner.join().unwrap(),
27299 Err(error) => panic!(
27300 "a self-waking Pending future must complete without panicking on a threadless build ({error})"
27301 ),
27302 }
27303 }
27304
27305 #[cfg(not(target_family = "wasm"))]
27306 #[test]
27307 fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
27308 use std::sync::mpsc;
27313
27314 let (done_tx, done_rx) = mpsc::channel();
27315 let runner = std::thread::spawn(move || {
27316 let metrics = Arc::new(RuntimeMetrics::default());
27317 let executor = Arc::new(CurrentThreadExecutor::with_detection(
27318 metrics,
27319 false,
27320 Some(Duration::from_millis(50)),
27321 ));
27322
27323 let payload = catch_unwind(AssertUnwindSafe(|| {
27324 let mut future = std::pin::pin!(NeverReady);
27325 executor.block_on(future.as_mut());
27326 }))
27327 .expect_err("a dead native CT park must fire the armed deadline");
27328 let diagnostic = payload
27329 .downcast_ref::<BlockOnDeadlock>()
27330 .expect("typed payload");
27331 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
27332 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27333 drop(executor);
27336 let _ = done_tx.send(());
27337 });
27338
27339 match done_rx.recv_timeout(Duration::from_secs(10)) {
27340 Ok(()) => join_within(
27341 "the CT deadline-fire runner exits after teardown",
27342 runner,
27343 Duration::from_secs(30),
27344 ),
27345 Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
27346 }
27347 }
27348
27349 #[cfg(not(target_family = "wasm"))]
27350 #[test]
27351 fn current_thread_native_park_deadline_resets_on_runtime_progress() {
27352 use std::sync::mpsc;
27369
27370 let deadline = Duration::from_millis(1000);
27371 let metrics = Arc::new(RuntimeMetrics::default());
27372 let executor = Arc::new(CurrentThreadExecutor::with_detection(
27373 Arc::clone(&metrics),
27374 false,
27375 Some(deadline),
27376 ));
27377
27378 let (value_tx, value_rx) = oneshot::channel::<usize>();
27379 let (done_tx, done_rx) = mpsc::channel();
27380 let runner = std::thread::spawn(move || {
27381 let mut output = None;
27382 {
27383 let mut future = std::pin::pin!(async {
27384 output = Some(value_rx.await.unwrap());
27385 });
27386 executor.block_on(future.as_mut());
27387 }
27388 assert_eq!(output, Some(9));
27389 drop(executor);
27391 let _ = done_tx.send(());
27392 });
27393
27394 let hold_until = Instant::now() + deadline * 5 / 2;
27400 while Instant::now() < hold_until {
27401 std::thread::sleep(Duration::from_millis(25));
27402 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27403 }
27404 value_tx.send(9).unwrap();
27405
27406 match done_rx.recv_timeout(Duration::from_secs(30)) {
27407 Ok(()) => join_within(
27408 "the CT deadline-reset runner exits after teardown",
27409 runner,
27410 Duration::from_secs(30),
27411 ),
27412 Err(error) => panic!(
27413 "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
27414 ),
27415 }
27416 }
27417
27418 #[cfg(not(target_family = "wasm"))]
27419 #[test]
27420 fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
27421 use std::sync::mpsc;
27426
27427 let (done_tx, done_rx) = mpsc::channel();
27428 let runner = std::thread::spawn(move || {
27429 let options = RuntimeOptions {
27430 flavor: RuntimeFlavor::MultiThread,
27431 worker_threads: 1,
27432 max_blocking_tasks: 1,
27433 thread_name_prefix: "deadline-fire".to_string(),
27434 park_deadline: Some(Duration::from_millis(50)),
27435 drain_linger: DEFAULT_DRAIN_LINGER,
27436 };
27437 let executor =
27438 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27439
27440 let payload = {
27441 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27442 catch_unwind(AssertUnwindSafe(|| {
27443 let mut future = std::pin::pin!(NeverReady);
27444 executor.block_on(future.as_mut());
27445 }))
27446 .expect_err("a dead cooperative park must fire the armed deadline")
27447 };
27448 let diagnostic = payload
27449 .downcast_ref::<BlockOnDeadlock>()
27450 .expect("typed payload");
27451 assert_eq!(
27452 diagnostic.kind,
27453 BlockOnDeadlockKind::MultiThreadCooperativeDeadline
27454 );
27455 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27456 let message = diagnostic.to_string();
27457 assert!(
27458 message.contains("block_on"),
27459 "diagnostic must name the hazard, got: {message}"
27460 );
27461 assert_eq!(
27462 executor.parked_drivers.count.load(Ordering::SeqCst),
27463 0,
27464 "the firing driver must deregister itself before panicking"
27465 );
27466 drop(executor);
27468 let _ = done_tx.send(());
27469 });
27470
27471 match done_rx.recv_timeout(Duration::from_secs(10)) {
27472 Ok(()) => join_within(
27473 "the MT deadline-fire runner exits after executor teardown",
27474 runner,
27475 Duration::from_secs(30),
27476 ),
27477 Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
27478 }
27479 }
27480
27481 #[cfg(not(target_family = "wasm"))]
27482 #[test]
27483 fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
27484 use std::sync::mpsc;
27500
27501 let (done_tx, done_rx) = mpsc::channel();
27502 let runner = std::thread::spawn(move || {
27503 let deadline = Duration::from_millis(1000);
27504 let metrics = Arc::new(RuntimeMetrics::default());
27505 let options = RuntimeOptions {
27506 flavor: RuntimeFlavor::MultiThread,
27507 worker_threads: 1,
27508 max_blocking_tasks: 1,
27509 thread_name_prefix: "deadline-reset".to_string(),
27510 park_deadline: Some(deadline),
27511 drain_linger: DEFAULT_DRAIN_LINGER,
27512 };
27513 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
27514
27515 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27518 let td_exec = Arc::clone(&executor);
27519 let completed = Arc::new(AtomicBool::new(false));
27520 let td_flag = Arc::clone(&completed);
27521 let td_body = async move {
27522 let mut inner = std::pin::pin!(async move {
27523 assert_eq!(gate_rx.await.unwrap(), 9);
27524 });
27525 td_exec.block_on(inner.as_mut());
27526 td_flag.store(true, Ordering::SeqCst);
27527 };
27528 let sched = Arc::clone(&executor);
27529 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
27530 executor.schedule(td_runnable);
27531 wait_until("the sole driver parked", || {
27532 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
27533 });
27534
27535 let hold_until = Instant::now() + deadline * 5 / 2;
27541 while Instant::now() < hold_until {
27542 std::thread::sleep(Duration::from_millis(25));
27543 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27544 }
27545 assert!(
27546 !completed.load(Ordering::SeqCst),
27547 "the driver must still be parked awaiting its late wake"
27548 );
27549 gate_tx.send(9).unwrap();
27550 futures::executor::block_on(td_task);
27551 assert!(
27552 completed.load(Ordering::SeqCst),
27553 "the late wake must complete the parked driver"
27554 );
27555 drop(executor);
27557 let _ = done_tx.send(());
27558 });
27559
27560 match done_rx.recv_timeout(Duration::from_secs(30)) {
27561 Ok(()) => join_within(
27562 "the MT deadline-reset runner exits after executor teardown",
27563 runner,
27564 Duration::from_secs(30),
27565 ),
27566 Err(error) => panic!(
27567 "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
27568 ),
27569 }
27570 }
27571
27572 #[cfg(not(target_family = "wasm"))]
27573 #[test]
27574 fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
27575 use std::sync::mpsc;
27580
27581 let (done_tx, done_rx) = mpsc::channel();
27582 let runner = std::thread::spawn(move || {
27583 let options = RuntimeOptions {
27584 flavor: RuntimeFlavor::MultiThread,
27585 worker_threads: 2,
27586 max_blocking_tasks: 2,
27587 thread_name_prefix: "foreign-exempt".to_string(),
27588 park_deadline: Some(Duration::from_millis(40)),
27589 drain_linger: DEFAULT_DRAIN_LINGER,
27590 };
27591 let executor =
27592 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27593
27594 let (value_tx, value_rx) = oneshot::channel::<usize>();
27595 let waker_thread = std::thread::spawn(move || {
27596 std::thread::sleep(Duration::from_millis(300));
27598 let _ = value_tx.send(7);
27599 });
27600
27601 let started = Instant::now();
27602 let mut output = None;
27603 {
27604 let mut future = std::pin::pin!(async {
27605 output = Some(value_rx.await.unwrap());
27606 });
27607 executor.block_on(future.as_mut());
27609 }
27610 assert_eq!(output, Some(7));
27611 assert!(
27612 started.elapsed() >= Duration::from_millis(250),
27613 "the park must genuinely have outlived the 40ms deadline many times over"
27614 );
27615 waker_thread.join().unwrap();
27616 drop(executor);
27621 let _ = done_tx.send(());
27622 });
27623
27624 match done_rx.recv_timeout(Duration::from_secs(10)) {
27625 Ok(()) => join_within(
27626 "the exempt foreign-park runner exits after executor teardown",
27627 runner,
27628 Duration::from_secs(30),
27629 ),
27630 Err(error) => {
27631 panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
27632 }
27633 }
27634 }
27635
27636 #[cfg(not(target_family = "wasm"))]
27637 #[test]
27638 fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
27639 use std::sync::mpsc;
27654
27655 let (done_tx, done_rx) = mpsc::channel();
27656 let runner = std::thread::spawn(move || {
27657 let options = RuntimeOptions {
27658 flavor: RuntimeFlavor::MultiThread,
27659 worker_threads: 1,
27660 max_blocking_tasks: 1,
27661 thread_name_prefix: "edge-wake".to_string(),
27662 park_deadline: Some(Duration::from_millis(40)),
27663 drain_linger: DEFAULT_DRAIN_LINGER,
27664 };
27665 let executor =
27666 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27667
27668 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27670 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
27672
27673 let hook_exec = Arc::clone(&executor);
27674 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
27675 *slot.borrow_mut() = Some(Box::new(move || {
27676 let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27677 let handle = hook_exec.schedule_blocking(move || {
27681 let _ = gate_tx.send(5usize);
27682 0usize
27683 });
27684 drop(handle); let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27686 hook_saw_tx.send((before, after)).unwrap();
27687 }));
27688 });
27689
27690 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27693 let mut output = None;
27694 {
27695 let mut future = std::pin::pin!(async {
27696 output = Some(gate_rx.await.unwrap());
27697 });
27698 executor.block_on(future.as_mut());
27699 }
27700 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
27705 assert_eq!(
27706 output,
27707 Some(5),
27708 "the edge-delivered wake's job must have run and completed the future"
27709 );
27710
27711 let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
27712 assert_eq!(
27713 before, 1,
27714 "the hook must observe the parker STILL registered (race window)"
27715 );
27716 assert_eq!(
27717 after, 0,
27718 "the racing wake_one must have popped the parker (wake DELIVERED)"
27719 );
27720 drop(executor);
27722 let _ = done_tx.send(());
27723 });
27724
27725 match done_rx.recv_timeout(Duration::from_secs(10)) {
27726 Ok(()) => join_within(
27727 "the expiry-edge-wake runner exits after executor teardown",
27728 runner,
27729 Duration::from_secs(30),
27730 ),
27731 Err(error) => panic!(
27732 "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
27733 ),
27734 }
27735 }
27736
27737 #[cfg(not(target_family = "wasm"))]
27738 #[test]
27739 fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
27740 use std::sync::mpsc;
27752
27753 let (done_tx, done_rx) = mpsc::channel();
27754 let runner = std::thread::spawn(move || {
27755 let options = RuntimeOptions {
27756 flavor: RuntimeFlavor::MultiThread,
27757 worker_threads: 1,
27758 max_blocking_tasks: 1,
27759 thread_name_prefix: "verdict-blocking".to_string(),
27760 park_deadline: Some(Duration::from_millis(40)),
27761 drain_linger: DEFAULT_DRAIN_LINGER,
27762 };
27763 let executor =
27764 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27765 executor
27766 .active_drainers
27767 .store(executor.max_drainers, Ordering::SeqCst);
27768
27769 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27771 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
27774
27775 let hook_exec = Arc::clone(&executor);
27776 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
27777 *slot.borrow_mut() = Some(Box::new(move || {
27778 let handle = hook_exec.schedule_blocking(move || {
27779 let _ = gate_tx.send(11usize);
27780 0usize
27781 });
27782 drop(handle); hook_saw_tx
27784 .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
27785 .unwrap();
27786 }));
27787 });
27788
27789 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27792 let mut output = None;
27793 {
27794 let mut future = std::pin::pin!(async {
27795 output = Some(gate_rx.await.unwrap());
27796 });
27797 executor.block_on(future.as_mut());
27798 }
27799 assert_eq!(
27800 output,
27801 Some(11),
27802 "the enqueued job must have been run by the surviving driver via run_one"
27803 );
27804
27805 let registered_at_hook = hook_saw_rx
27806 .recv()
27807 .expect("the verdict hook must have fired");
27808 assert_eq!(
27809 registered_at_hook, 0,
27810 "the hook fires post-deregister: no permit can be delivered, only the fingerprint's enqueue counter can catch this submission"
27811 );
27812 executor.active_drainers.store(0, Ordering::SeqCst);
27813 let _ = done_tx.send(());
27814 });
27815
27816 match done_rx.recv_timeout(Duration::from_secs(10)) {
27817 Ok(()) => runner.join().unwrap(),
27818 Err(error) => panic!(
27819 "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
27820 ),
27821 }
27822 }
27823
27824 #[cfg(not(target_family = "wasm"))]
27825 #[test]
27826 fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
27827 use std::sync::mpsc;
27833
27834 struct ReadyAfterPublication(Arc<AtomicBool>);
27835
27836 impl Future for ReadyAfterPublication {
27837 type Output = ();
27838
27839 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27840 if self.0.load(Ordering::SeqCst) {
27841 Poll::Ready(())
27842 } else {
27843 Poll::Pending
27844 }
27845 }
27846 }
27847
27848 let (done_tx, done_rx) = mpsc::channel();
27849 let runner = std::thread::spawn(move || {
27850 let options = RuntimeOptions {
27851 flavor: RuntimeFlavor::MultiThread,
27852 worker_threads: 1,
27853 max_blocking_tasks: 1,
27854 thread_name_prefix: "final-verdict-publication".to_string(),
27855 park_deadline: Some(Duration::from_millis(40)),
27856 drain_linger: DEFAULT_DRAIN_LINGER,
27857 };
27858 let executor =
27859 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27860
27861 let ready = Arc::new(AtomicBool::new(false));
27862 let baseline_fingerprint = executor.metrics.progress_fingerprint();
27863 let baseline_publications = executor.deadlock_state.publications();
27864 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
27865 let hook_exec = Arc::clone(&executor);
27866 let hook_ready = Arc::clone(&ready);
27867 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
27868 *slot.borrow_mut() = Some(Box::new(move || {
27869 let publication_executor = Arc::clone(&hook_exec);
27870 std::thread::spawn(move || {
27871 let mut admission = publication_executor
27872 .begin_deadlock_admission()
27873 .expect("the deadline detector must enable admission accounting");
27874 hook_ready.store(true, Ordering::SeqCst);
27875 admission.mark_published();
27876 })
27877 .join()
27878 .unwrap();
27879 hook_saw_tx
27880 .send((
27881 hook_exec.deadlock_state.publications(),
27882 !hook_exec.has_queued_runnable(),
27883 hook_exec
27884 .blocking_queue
27885 .lock()
27886 .unwrap_or_else(std::sync::PoisonError::into_inner)
27887 .is_empty(),
27888 hook_exec.next_timer_deadline().is_none(),
27889 hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
27890 hook_exec.parked_drivers.count.load(Ordering::SeqCst),
27891 ))
27892 .unwrap();
27893 }));
27894 });
27895
27896 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27897 let mut future = std::pin::pin!(ReadyAfterPublication(ready));
27898 assert_eq!(
27899 executor.block_on(future.as_mut()),
27900 BlockOnOutcome::Completed,
27901 "the publication epoch must veto the first verdict and permit the next poll"
27902 );
27903 let (
27904 publications,
27905 runnable_queue_empty,
27906 blocking_queue_empty,
27907 timer_heap_empty,
27908 fingerprint_unchanged,
27909 parked_drivers,
27910 ) = hook_saw_rx
27911 .recv()
27912 .expect("the final-verdict hook must have fired");
27913 assert_eq!(
27914 publications,
27915 baseline_publications
27916 .checked_add(1)
27917 .expect("test publication epoch must not overflow"),
27918 "the cross-thread admission must publish exactly one epoch"
27919 );
27920 assert!(
27921 runnable_queue_empty,
27922 "the publication test must not queue a runnable"
27923 );
27924 assert!(
27925 blocking_queue_empty,
27926 "the publication test must not queue blocking work"
27927 );
27928 assert!(
27929 timer_heap_empty,
27930 "the publication test must not register a timer"
27931 );
27932 assert!(
27933 fingerprint_unchanged,
27934 "ordinary metrics progress must not veto the publication-only verdict"
27935 );
27936 assert_eq!(
27937 parked_drivers, 0,
27938 "the publication must land after the cooperative parker was deregistered"
27939 );
27940 let _ = done_tx.send(());
27941 });
27942
27943 match done_rx.recv_timeout(Duration::from_secs(10)) {
27944 Ok(()) => runner.join().unwrap(),
27945 Err(error) => panic!(
27946 "a publication-only cross-thread admission was not observed by the final verdict ({error})"
27947 ),
27948 }
27949 }
27950
27951 #[cfg(not(target_family = "wasm"))]
27952 #[test]
27953 fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
27954 use std::sync::mpsc;
27962
27963 let executor = multi_thread_executor(
27964 1,
27965 Some(Duration::from_millis(40)),
27966 "deadline-selected-handoff",
27967 );
27968 let owner = executor.fresh_owner_token();
27969 executor.active_blocking_owners.register(owner);
27970 executor
27971 .active_blocking
27972 .store(executor.max_blocking, Ordering::SeqCst);
27973
27974 let ready = Arc::new(AtomicBool::new(false));
27975 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
27976 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
27977 let (selected_tx, selected_rx) = mpsc::channel();
27978 let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
27979 let (released_tx, released_rx) = mpsc::channel();
27980 let (renewed_tx, renewed_rx) = mpsc::channel();
27981 let release_executor = Arc::clone(&executor);
27982 let renewal_executor = Arc::clone(&executor);
27983 let release_ready = Arc::clone(&ready);
27984 let releaser = std::thread::spawn(move || {
27985 let reservation =
27986 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
27987 .expect("the synthetic exact-owner lane must start reserved");
27988 reservation_ready_tx.send(()).unwrap();
27989 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
27990 *slot.borrow_mut() = Some(Box::new(move || {
27991 selected_tx.send(()).unwrap();
27992 allow_unpark_rx.recv().unwrap();
27993 let renewed = renewal_executor
27994 .active_blocking_owners
27995 .reserve(owner)
27996 .expect("the released exact-owner lane must be reservable before unpark");
27997 release_ready.store(true, Ordering::SeqCst);
27998 renewed_tx.send(renewed).unwrap();
27999 }));
28000 });
28001 release_reservation_rx.recv().unwrap();
28002 released_tx
28003 .send(release_executor.release_owner_lane_and_wake(reservation))
28004 .unwrap();
28005 });
28006 reservation_ready_rx
28007 .recv_timeout(Duration::from_secs(2))
28008 .expect("the exact-owner reservation must be established before the park");
28009
28010 let baseline_fingerprint = executor.metrics.progress_fingerprint();
28011 let baseline_publications = executor.deadlock_state.publications();
28012 let expiry_executor = Arc::clone(&executor);
28013 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
28014 *slot.borrow_mut() = Some(Box::new(move || {
28015 release_reservation_tx.send(()).unwrap();
28016 selected_rx
28017 .recv_timeout(Duration::from_secs(2))
28018 .expect("reservation release must select the still-registered exact-owner driver");
28019 assert_eq!(
28020 expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
28021 0,
28022 "handoff selection must remove the driver before pausing ahead of unpark"
28023 );
28024 }));
28025 });
28026
28027 let verdict_executor = Arc::clone(&executor);
28028 let verdict_ready = Arc::clone(&ready);
28029 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28030 *slot.borrow_mut() = Some(Box::new(move || {
28031 assert!(
28032 verdict_executor.active_blocking_owners.is_available(owner),
28033 "reservation release must publish lane availability before handoff selection"
28034 );
28035 assert!(!verdict_executor.has_queued_runnable());
28036 assert!(
28037 verdict_executor
28038 .blocking_queue
28039 .lock()
28040 .unwrap_or_else(std::sync::PoisonError::into_inner)
28041 .is_empty()
28042 );
28043 assert!(verdict_executor.next_timer_deadline().is_none());
28044 assert!(
28045 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28046 "ordinary metrics progress must not save the selected-handoff verdict"
28047 );
28048 assert_eq!(
28049 verdict_executor.deadlock_state.publications(),
28050 baseline_publications
28051 );
28052 assert!(
28053 !verdict_ready.load(Ordering::SeqCst),
28054 "the selected handoff must still be paused before unpark at the gated verdict"
28055 );
28056 allow_unpark_tx.send(()).unwrap();
28057 }));
28058 });
28059 let exact_owner_executor = Arc::clone(&executor);
28060 DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
28061 *slot.borrow_mut() = Some(Box::new(move |parker| {
28062 assert!(
28063 parker.consume_permit(),
28064 "the selected handoff must unpark before exact-owner final predicates run"
28065 );
28066 assert!(
28067 parker.has_owner_handoff(),
28068 "consuming the ordinary permit must leave the exact-owner handoff pending"
28069 );
28070 assert!(
28071 !exact_owner_executor
28072 .active_blocking_owners
28073 .is_available(owner),
28074 "the renewed reservation must prevent lane availability from saving the verdict"
28075 );
28076 }));
28077 });
28078
28079 {
28080 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28081 let _owner = BlockingOwnerGuard::enter(Some(owner));
28082 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28083 dependency: BlockingDependency {
28084 job: BlockingJobId(u64::MAX - 80),
28085 owner: Some(owner)
28086 },
28087 ready: Arc::clone(&ready),
28088 });
28089 assert_eq!(
28090 executor.block_on(future.as_mut()),
28091 BlockOnOutcome::Completed
28092 );
28093 }
28094 assert!(
28095 released_rx
28096 .recv_timeout(Duration::from_secs(2))
28097 .expect("the selected handoff publication must finish"),
28098 "reservation release must have selected the cooperative driver"
28099 );
28100 let renewed = renewed_rx
28101 .recv_timeout(Duration::from_secs(2))
28102 .expect("the selected handoff must renew the owner reservation before unpark");
28103 releaser.join().unwrap();
28104
28105 executor.active_blocking_owners.release(owner, renewed);
28106 executor.active_blocking.store(0, Ordering::SeqCst);
28107 executor.active_blocking_owners.unregister(owner);
28108 executor.begin_shutdown();
28109 executor.wait_until_scheduler_idle();
28110 }
28111
28112 #[cfg(not(target_family = "wasm"))]
28113 #[test]
28114 fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
28115 use std::sync::mpsc;
28120
28121 let executor = multi_thread_executor(
28122 1,
28123 Some(Duration::from_millis(40)),
28124 "deadline-owner-no-parker",
28125 );
28126 let owner = executor.fresh_owner_token();
28127 executor.active_blocking_owners.register(owner);
28128 executor
28129 .active_blocking
28130 .store(executor.max_blocking, Ordering::SeqCst);
28131
28132 let ready = Arc::new(AtomicBool::new(false));
28133 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
28134 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
28135 let (released_tx, released_rx) = mpsc::channel();
28136 let release_executor = Arc::clone(&executor);
28137 let release_ready = Arc::clone(&ready);
28138 let releaser = std::thread::spawn(move || {
28139 let reservation =
28140 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
28141 .expect("the synthetic exact-owner lane must start reserved");
28142 reservation_ready_tx.send(()).unwrap();
28143 release_reservation_rx.recv().unwrap();
28144 let woke = release_executor.release_owner_lane_and_wake(reservation);
28145 release_ready.store(true, Ordering::SeqCst);
28146 released_tx.send(woke).unwrap();
28147 });
28148 reservation_ready_rx
28149 .recv_timeout(Duration::from_secs(2))
28150 .expect("the exact-owner reservation must be established before the park");
28151
28152 let baseline_fingerprint = executor.metrics.progress_fingerprint();
28153 let baseline_publications = executor.deadlock_state.publications();
28154 let verdict_executor = Arc::clone(&executor);
28155 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
28156 *slot.borrow_mut() = Some(Box::new(move || {
28157 assert_eq!(
28158 verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
28159 0,
28160 "the release must begin only after deadline deregistration"
28161 );
28162 release_reservation_tx.send(()).unwrap();
28163 assert!(
28164 !released_rx
28165 .recv_timeout(Duration::from_secs(2))
28166 .expect("the no-parker owner release must finish"),
28167 "no registered parker may receive the released owner lane"
28168 );
28169 assert!(verdict_executor.active_blocking_owners.is_available(owner));
28170 assert!(!verdict_executor.has_queued_runnable());
28171 assert!(
28172 verdict_executor
28173 .blocking_queue
28174 .lock()
28175 .unwrap_or_else(std::sync::PoisonError::into_inner)
28176 .is_empty()
28177 );
28178 assert!(verdict_executor.next_timer_deadline().is_none());
28179 assert!(
28180 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28181 "ordinary metrics progress must not save the no-parker release verdict"
28182 );
28183 assert_eq!(
28184 verdict_executor.deadlock_state.publications(),
28185 baseline_publications
28186 );
28187 }));
28188 });
28189
28190 {
28191 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28192 let _owner = BlockingOwnerGuard::enter(Some(owner));
28193 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28194 dependency: BlockingDependency {
28195 job: BlockingJobId(u64::MAX - 81),
28196 owner: Some(owner)
28197 },
28198 ready: Arc::clone(&ready),
28199 });
28200 assert_eq!(
28201 executor.block_on(future.as_mut()),
28202 BlockOnOutcome::Completed
28203 );
28204 }
28205 releaser.join().unwrap();
28206
28207 executor.active_blocking.store(0, Ordering::SeqCst);
28208 executor.active_blocking_owners.unregister(owner);
28209 executor.begin_shutdown();
28210 executor.wait_until_scheduler_idle();
28211 }
28212
28213 #[cfg(not(target_family = "wasm"))]
28214 #[test]
28215 fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
28216 use std::sync::mpsc;
28230
28231 let controller = Arc::new(RuntimeController::new());
28232 controller
28233 .configure(RuntimeOptions {
28234 flavor: RuntimeFlavor::MultiThread,
28235 worker_threads: 2,
28236 max_blocking_tasks: 1,
28237 thread_name_prefix: "shutdown-gated-verdict".to_string(),
28238 park_deadline: Some(Duration::from_millis(40)),
28239 drain_linger: DEFAULT_DRAIN_LINGER,
28240 })
28241 .expect("the shutdown race test configuration must be accepted");
28242
28243 let backend = controller.backend();
28244 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28245 panic!("the configured backend must be MultiThread");
28246 };
28247 let executor_id = executor.id;
28248 let weak_executor = Arc::downgrade(executor);
28249 drop(backend);
28250
28251 let (task_polled_tx, task_polled_rx) = mpsc::channel();
28252 let task = controller
28253 .try_spawn(async move {
28254 task_polled_tx.send(()).unwrap();
28255 std::future::pending::<()>().await;
28256 })
28257 .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
28258 task_polled_rx
28259 .recv_timeout(Duration::from_secs(2))
28260 .expect("the accepted task must publish an abortable pending waker");
28261
28262 let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
28263 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28264 let (driver_result_tx, driver_result_rx) = mpsc::channel();
28265 let driver_controller = Arc::clone(&controller);
28266 let driver = std::thread::spawn(move || {
28267 DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
28268 *slot.borrow_mut() = Some(Box::new(move || {
28269 gate_closed_tx.send(()).unwrap();
28270 release_verdict_rx.recv().unwrap();
28271 }));
28272 });
28273
28274 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28275 let mut pending = std::pin::pin!(NeverReady);
28276 let result = catch_unwind(AssertUnwindSafe(|| {
28277 driver_controller.block_on(pending.as_mut())
28278 }));
28279 let result = match result {
28280 Ok(outcome) => Ok(outcome),
28281 Err(payload) => {
28282 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28283 discard_panic_payload(payload);
28284 Err(was_deadlock)
28285 }
28286 };
28287 driver_result_tx.send(result).unwrap();
28288 });
28289
28290 gate_closed_rx
28291 .recv_timeout(Duration::from_secs(2))
28292 .expect("the cooperative driver must pause with the verdict gate closed");
28293
28294 let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
28295 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28296 let shutdown_controller = Arc::clone(&controller);
28297 let shutdown = std::thread::spawn(move || {
28298 DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
28299 *slot.borrow_mut() = Some(Box::new(move || {
28300 abort_blocked_tx.send(()).unwrap();
28301 }));
28302 });
28303 shutdown_result_tx
28304 .send(shutdown_controller.shutdown())
28305 .unwrap();
28306 });
28307
28308 abort_blocked_rx
28309 .recv_timeout(Duration::from_secs(2))
28310 .expect("the accepted task's abort wake must reach the closed verdict gate");
28311 let lifecycle_is_stopping = {
28312 let state = controller
28313 .state
28314 .lock()
28315 .unwrap_or_else(std::sync::PoisonError::into_inner);
28316 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
28317 };
28318 let stop_was_published = weak_executor
28319 .upgrade()
28320 .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
28321
28322 release_verdict_tx.send(()).unwrap();
28323
28324 let driver_result = driver_result_rx
28325 .recv_timeout(Duration::from_secs(2))
28326 .expect("the gated driver must resolve after observing shutdown");
28327 let shutdown_result = shutdown_result_rx
28328 .recv_timeout(Duration::from_secs(5))
28329 .expect("shutdown must finish after the verdict gate reopens");
28330 join_within(
28335 "the gated cooperative driver exits after teardown",
28336 driver,
28337 Duration::from_secs(30),
28338 );
28339 join_within(
28340 "the shutdown thread exits after teardown",
28341 shutdown,
28342 Duration::from_secs(30),
28343 );
28344
28345 assert!(
28346 lifecycle_is_stopping,
28347 "the abort wake must occur after Running -> Stopping"
28348 );
28349 assert!(
28350 stop_was_published,
28351 "generation stop must be visible before shutdown invokes accepted-task aborts"
28352 );
28353 assert_eq!(
28354 driver_result,
28355 Ok(BlockOnOutcome::Stopped),
28356 "shutdown must not be reported as a gated BlockOnDeadlock"
28357 );
28358 shutdown_result.unwrap();
28359 assert!(
28360 futures::executor::block_on(task).is_err(),
28361 "the accepted pending task must resolve as cancelled"
28362 );
28363 }
28364
28365 #[cfg(not(target_family = "wasm"))]
28366 #[test]
28367 fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
28368 use std::sync::mpsc;
28373
28374 let controller = Arc::new(RuntimeController::new());
28375 controller
28376 .configure(RuntimeOptions {
28377 flavor: RuntimeFlavor::MultiThread,
28378 worker_threads: 2,
28379 max_blocking_tasks: 1,
28380 thread_name_prefix: "verdict-before-shutdown".to_string(),
28381 park_deadline: Some(Duration::from_millis(40)),
28382 drain_linger: DEFAULT_DRAIN_LINGER,
28383 })
28384 .expect("the verdict-first test configuration must be accepted");
28385
28386 let backend = controller.backend();
28387 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28388 panic!("the configured backend must be MultiThread");
28389 };
28390 let executor_id = executor.id;
28391 drop(backend);
28392
28393 let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
28394 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28395 let (driver_result_tx, driver_result_rx) = mpsc::channel();
28396 let driver_controller = Arc::clone(&controller);
28397 let driver = std::thread::spawn(move || {
28398 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28399 *slot.borrow_mut() = Some(Box::new(move || {
28400 verdict_locked_tx.send(()).unwrap();
28401 release_verdict_rx.recv().unwrap();
28402 }));
28403 });
28404
28405 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28406 let mut pending = std::pin::pin!(NeverReady);
28407 let result = catch_unwind(AssertUnwindSafe(|| {
28408 driver_controller.block_on(pending.as_mut())
28409 }));
28410 let result = match result {
28411 Ok(outcome) => Ok(outcome),
28412 Err(payload) => {
28413 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28414 discard_panic_payload(payload);
28415 Err(was_deadlock)
28416 }
28417 };
28418 driver_result_tx.send(result).unwrap();
28419 });
28420
28421 verdict_locked_rx
28422 .recv_timeout(Duration::from_secs(2))
28423 .expect("the verdict must hold the synchronized stop-publication guard");
28424
28425 let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
28426 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28427 let shutdown_controller = Arc::clone(&controller);
28428 let shutdown = std::thread::spawn(move || {
28429 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
28430 *slot.borrow_mut() = Some(Box::new(move || {
28431 shutdown_waiting_tx.send(()).unwrap();
28432 }));
28433 });
28434 shutdown_result_tx
28435 .send(shutdown_controller.shutdown())
28436 .unwrap();
28437 });
28438
28439 shutdown_waiting_rx
28440 .recv_timeout(Duration::from_secs(2))
28441 .expect("shutdown must reach the lifecycle-to-publication lock edge");
28442 let early_shutdown = shutdown_result_rx
28443 .recv_timeout(Duration::from_millis(100))
28444 .ok();
28445 let shutdown_was_blocked = early_shutdown.is_none();
28446 release_verdict_tx.send(()).unwrap();
28447
28448 let driver_result = driver_result_rx
28449 .recv_timeout(Duration::from_secs(2))
28450 .expect("the verdict-first driver must finish after release");
28451 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28452 shutdown_result_rx
28453 .recv_timeout(Duration::from_secs(5))
28454 .expect("shutdown must finish after the verdict releases publication")
28455 });
28456 driver.join().unwrap();
28457 shutdown.join().unwrap();
28458
28459 assert!(
28460 shutdown_was_blocked,
28461 "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
28462 );
28463 assert_eq!(
28464 driver_result,
28465 Err(true),
28466 "a verdict that linearizes before shutdown must retain the typed deadlock"
28467 );
28468 shutdown_result.unwrap();
28469 }
28470
28471 #[cfg(not(target_family = "wasm"))]
28472 #[test]
28473 fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
28474 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
28475
28476 if std::env::var_os(CHILD_ENV).is_some() {
28477 let executor =
28478 multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
28479 executor
28482 .active_drainers
28483 .store(executor.max_drainers, Ordering::SeqCst);
28484
28485 let hook_returned = Arc::new(AtomicBool::new(false));
28486 let hook_flag = Arc::clone(&hook_returned);
28487 let hook_executor = Arc::clone(&executor);
28488 let previous_hook = std::panic::take_hook();
28489 std::panic::set_hook(Box::new(move |_| {
28490 let handle = hook_executor.schedule_blocking(|| ());
28491 drop(handle);
28492 hook_flag.store(true, Ordering::SeqCst);
28493 }));
28494
28495 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28496 let result = catch_unwind(AssertUnwindSafe(|| {
28497 let mut pending = std::pin::pin!(std::future::pending::<()>());
28498 executor.block_on(pending.as_mut());
28499 }));
28500 std::panic::set_hook(previous_hook);
28501
28502 let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
28503 assert!(
28504 payload.downcast_ref::<BlockOnDeadlock>().is_some(),
28505 "the deadline must retain its structured diagnostic"
28506 );
28507 assert!(
28508 hook_returned.load(Ordering::SeqCst),
28509 "the panic hook's scheduler submission must return before unwind"
28510 );
28511
28512 executor.active_drainers.store(0, Ordering::SeqCst);
28513 executor.begin_shutdown();
28514 executor.wait_until_scheduler_idle();
28515 return;
28516 }
28517
28518 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28519 .arg("--exact")
28520 .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
28521 .arg("--nocapture")
28522 .env(CHILD_ENV, "1")
28523 .stdout(std::process::Stdio::piped())
28524 .stderr(std::process::Stdio::piped())
28525 .spawn()
28526 .expect("the panic-hook subprocess must start");
28527 let deadline = Instant::now() + Duration::from_secs(5);
28528 let status = loop {
28529 if let Some(status) = child
28530 .try_wait()
28531 .expect("the subprocess status must be readable")
28532 {
28533 break status;
28534 }
28535 if Instant::now() >= deadline {
28536 let _ = child.kill();
28537 let output = child
28538 .wait_with_output()
28539 .expect("the timed-out subprocess must be reaped");
28540 panic!(
28541 "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
28542 String::from_utf8_lossy(&output.stdout),
28543 String::from_utf8_lossy(&output.stderr)
28544 );
28545 }
28546 std::thread::sleep(Duration::from_millis(10));
28547 };
28548 let output = child
28549 .wait_with_output()
28550 .expect("the completed subprocess output must be read");
28551 assert!(
28552 status.success(),
28553 "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
28554 status.code(),
28555 String::from_utf8_lossy(&output.stdout),
28556 String::from_utf8_lossy(&output.stderr)
28557 );
28558 }
28559
28560 #[test]
28571 fn contained_waker_separates_wake_and_drop_panics() {
28572 struct PanicWakeAndDrop;
28573
28574 impl std::task::Wake for PanicWakeAndDrop {
28575 fn wake(self: Arc<Self>) {
28576 panic!("intentional consuming wake panic");
28577 }
28578
28579 fn wake_by_ref(self: &Arc<Self>) {
28580 panic!("intentional borrowed wake panic");
28581 }
28582 }
28583
28584 impl Drop for PanicWakeAndDrop {
28585 fn drop(&mut self) {
28586 panic!("intentional RawWaker destructor panic");
28587 }
28588 }
28589
28590 wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
28591 }
28592
28593 struct TestHostileRawWakerState {
28594 clones: AtomicUsize,
28595 drop_panics: AtomicUsize,
28596 panic_on_clone: Option<usize>,
28597 }
28598
28599 struct TestHostileRawWakerToken {
28600 state: Arc<TestHostileRawWakerState>,
28601 panic_on_drop: bool,
28602 }
28603
28604 unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
28605 let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
28606 let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
28607 assert_ne!(
28608 token.state.panic_on_clone,
28609 Some(clone),
28610 "intentional RawWaker clone panic"
28611 );
28612 let clone = Box::new(TestHostileRawWakerToken {
28613 state: Arc::clone(&token.state),
28614 panic_on_drop: true,
28615 });
28616 std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
28617 }
28618
28619 unsafe fn wake_test_hostile_waker(data: *const ()) {
28620 unsafe { drop_test_hostile_waker(data) };
28621 }
28622
28623 unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
28624
28625 unsafe fn drop_test_hostile_waker(data: *const ()) {
28626 let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
28627 if token.panic_on_drop {
28628 token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
28629 panic!("intentional cloned RawWaker destructor panic");
28630 }
28631 }
28632
28633 static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
28634 clone_test_hostile_waker,
28635 wake_test_hostile_waker,
28636 wake_test_hostile_waker_by_ref,
28637 drop_test_hostile_waker,
28638 );
28639
28640 fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
28641 let token = Box::new(TestHostileRawWakerToken {
28642 state,
28643 panic_on_drop: false,
28644 });
28645 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
28646 unsafe { Waker::from_raw(raw) }
28647 }
28648
28649 struct CountingWake(AtomicUsize);
28651 impl std::task::Wake for CountingWake {
28652 fn wake(self: Arc<Self>) {
28653 self.0.fetch_add(1, Ordering::SeqCst);
28654 }
28655 }
28656
28657 type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
28659
28660 struct StubHostTimerDriver {
28664 timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
28665 }
28666
28667 impl StubHostTimerDriver {
28668 fn new() -> Self {
28669 Self {
28670 timers: Mutex::new(FxHashMap::default()),
28671 }
28672 }
28673 }
28674
28675 impl TimerDriver for StubHostTimerDriver {
28676 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28677 let mut timers = self.timers.lock().unwrap();
28678 if let Some(cell) = timers.get(&id) {
28679 *cell.0.lock().unwrap() = waker;
28680 return;
28681 }
28682 let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
28683 timers.insert(id, Arc::clone(&cell));
28684 std::thread::spawn(move || {
28685 let now = Instant::now();
28686 if deadline > now {
28687 std::thread::sleep(deadline - now);
28688 }
28689 if !cell.1.load(Ordering::SeqCst) {
28690 cell.0.lock().unwrap().wake_by_ref();
28691 }
28692 });
28693 }
28694
28695 fn cancel(&self, id: TimerId) {
28696 let removed = self.timers.lock().unwrap().remove(&id);
28697 if let Some(cell) = removed {
28698 cell.1.store(true, Ordering::SeqCst);
28699 }
28700 }
28701 }
28702
28703 struct ManualStubDriver {
28709 live: AtomicBool,
28710 registers: Mutex<Vec<(TimerId, Instant)>>,
28711 cancels: Mutex<Vec<TimerId>>,
28712 pending: Mutex<FxHashMap<TimerId, Waker>>,
28713 swept: AtomicBool,
28714 }
28715
28716 impl ManualStubDriver {
28717 fn new() -> Arc<Self> {
28718 Arc::new(Self {
28719 live: AtomicBool::new(true),
28720 registers: Mutex::new(Vec::new()),
28721 cancels: Mutex::new(Vec::new()),
28722 pending: Mutex::new(FxHashMap::default()),
28723 swept: AtomicBool::new(false),
28724 })
28725 }
28726
28727 fn kill(&self) {
28728 self.live.store(false, Ordering::SeqCst);
28729 }
28730 }
28731
28732 impl TimerDriver for ManualStubDriver {
28733 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28734 self.registers.lock().unwrap().push((id, deadline));
28735 self.pending.lock().unwrap().insert(id, waker);
28736 }
28737
28738 fn cancel(&self, id: TimerId) {
28739 self.cancels.lock().unwrap().push(id);
28740 self.pending.lock().unwrap().remove(&id);
28741 }
28742
28743 fn is_live(&self) -> bool {
28744 self.live.load(Ordering::SeqCst)
28745 }
28746
28747 fn on_swept(&self) {
28748 self.swept.store(true, Ordering::SeqCst);
28751 let wakers: Vec<Waker> = self
28752 .pending
28753 .lock()
28754 .unwrap()
28755 .drain()
28756 .map(|(_, w)| w)
28757 .collect();
28758 for waker in wakers {
28759 waker.wake();
28760 }
28761 }
28762 }
28763
28764 struct PanickingTimerDriver {
28765 swept: AtomicBool,
28766 }
28767
28768 impl TimerDriver for PanickingTimerDriver {
28769 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28770
28771 fn cancel(&self, _id: TimerId) {}
28772
28773 fn is_live(&self) -> bool {
28774 panic!("intentional timer-driver liveness panic");
28775 }
28776
28777 fn on_swept(&self) {
28778 self.swept.store(true, Ordering::SeqCst);
28779 panic!("intentional timer-driver sweep panic");
28780 }
28781 }
28782
28783 struct PanickingRegisterTimerDriver {
28784 registers: AtomicUsize,
28785 cancels: Mutex<Vec<TimerId>>,
28786 pending: Mutex<FxHashMap<TimerId, Waker>>,
28787 }
28788
28789 impl PanickingRegisterTimerDriver {
28790 fn new() -> Arc<Self> {
28791 Arc::new(Self {
28792 registers: AtomicUsize::new(0),
28793 cancels: Mutex::new(Vec::new()),
28794 pending: Mutex::new(FxHashMap::default()),
28795 })
28796 }
28797 }
28798
28799 impl TimerDriver for PanickingRegisterTimerDriver {
28800 fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
28801 self.registers.fetch_add(1, Ordering::SeqCst);
28802 self.pending.lock().unwrap().insert(id, waker);
28803 panic!("intentional timer-driver register panic after arming");
28804 }
28805
28806 fn cancel(&self, id: TimerId) {
28807 self.cancels.lock().unwrap().push(id);
28808 self.pending.lock().unwrap().remove(&id);
28809 }
28810 }
28811
28812 struct PanicBeforeRetainingTimerDriver {
28813 registers: AtomicUsize,
28814 }
28815
28816 impl TimerDriver for PanicBeforeRetainingTimerDriver {
28817 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28818 self.registers.fetch_add(1, Ordering::SeqCst);
28819 panic!("intentional timer-driver register panic before retaining its waker");
28820 }
28821
28822 fn cancel(&self, _id: TimerId) {}
28823 }
28824
28825 struct PanickingCancelDropTimerDriver {
28826 cancels: Arc<AtomicUsize>,
28827 drops: Arc<AtomicUsize>,
28828 }
28829
28830 impl TimerDriver for PanickingCancelDropTimerDriver {
28831 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28832
28833 fn cancel(&self, _id: TimerId) {
28834 self.cancels.fetch_add(1, Ordering::SeqCst);
28835 panic!("intentional timer-driver cancel panic");
28836 }
28837 }
28838
28839 impl Drop for PanickingCancelDropTimerDriver {
28840 fn drop(&mut self) {
28841 self.drops.fetch_add(1, Ordering::SeqCst);
28842 panic!("intentional timer-driver destructor panic");
28843 }
28844 }
28845
28846 struct UnregisterLaterThenPanicTimerDriver {
28847 registry: Weak<TimerDriverRegistry>,
28848 later: Mutex<Option<TimerDriverId>>,
28849 registers: AtomicUsize,
28850 }
28851
28852 impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
28853 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28854 self.registers.fetch_add(1, Ordering::SeqCst);
28855 let later = *self.later.lock().unwrap();
28856 if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
28857 registry.unregister(later);
28858 }
28859 panic!("intentional earlier timer-driver panic after unregistering a later driver");
28860 }
28861
28862 fn cancel(&self, _id: TimerId) {}
28863 }
28864
28865 struct ReentrantLivenessDriver {
28866 registry: std::sync::Weak<TimerDriverRegistry>,
28867 registration: Mutex<Option<TimerDriverId>>,
28868 }
28869
28870 impl TimerDriver for ReentrantLivenessDriver {
28871 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28872
28873 fn cancel(&self, _id: TimerId) {}
28874
28875 fn is_live(&self) -> bool {
28876 let registration = *self.registration.lock().unwrap();
28877 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
28878 registry.unregister(id);
28879 }
28880 false
28881 }
28882 }
28883
28884 struct ReentrantMultiUnregisterTimerDriver {
28885 registry: std::sync::Weak<TimerDriverRegistry>,
28886 registrations: Mutex<Vec<TimerDriverId>>,
28887 }
28888
28889 impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
28890 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28891
28892 fn cancel(&self, _id: TimerId) {}
28893
28894 fn is_live(&self) -> bool {
28895 if let Some(registry) = self.registry.upgrade() {
28896 for registration in self.registrations.lock().unwrap().iter().copied() {
28897 registry.unregister(registration);
28898 }
28899 }
28900 false
28901 }
28902 }
28903
28904 struct ReentrantSweepTimerDriver {
28905 registry: std::sync::Weak<TimerDriverRegistry>,
28906 registration: Mutex<Option<TimerDriverId>>,
28907 sweeps: AtomicUsize,
28908 removed_before_sweep: AtomicBool,
28909 }
28910
28911 impl TimerDriver for ReentrantSweepTimerDriver {
28912 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28913
28914 fn cancel(&self, _id: TimerId) {}
28915
28916 fn is_live(&self) -> bool {
28917 false
28918 }
28919
28920 fn on_swept(&self) {
28921 self.sweeps.fetch_add(1, Ordering::SeqCst);
28922 let registration = self.registration.lock().unwrap().take();
28923 if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
28924 let state = registry
28925 .state
28926 .try_lock()
28927 .expect("the sweep callback must run outside the timer-driver registry lock");
28928 assert!(
28929 !state.entries.contains_key(®istration),
28930 "the dead driver must be unlinked before its sweep callback runs"
28931 );
28932 self.removed_before_sweep.store(true, Ordering::SeqCst);
28933 drop(state);
28934 registry.unregister(registration);
28937 }
28938 }
28939 }
28940
28941 struct ReentrantDropDriver {
28942 registry: std::sync::Weak<TimerDriverRegistry>,
28943 fallback: TimerDriverId,
28944 }
28945
28946 impl TimerDriver for ReentrantDropDriver {
28947 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28948
28949 fn cancel(&self, _id: TimerId) {}
28950 }
28951
28952 impl Drop for ReentrantDropDriver {
28953 fn drop(&mut self) {
28954 if let Some(registry) = self.registry.upgrade() {
28955 registry.unregister(self.fallback);
28956 }
28957 }
28958 }
28959
28960 #[derive(Default)]
28963 struct WakeFlag(AtomicBool);
28964
28965 impl std::task::Wake for WakeFlag {
28966 fn wake(self: Arc<Self>) {
28967 self.0.store(true, Ordering::SeqCst);
28968 }
28969 }
28970
28971 #[cfg(not(target_family = "wasm"))]
28972 fn multi_thread_executor(
28973 workers: usize,
28974 park_deadline: Option<Duration>,
28975 prefix: &str,
28976 ) -> Arc<MultiThreadExecutor> {
28977 let options = RuntimeOptions {
28978 flavor: RuntimeFlavor::MultiThread,
28979 worker_threads: workers,
28980 max_blocking_tasks: workers,
28981 thread_name_prefix: prefix.to_string(),
28982 park_deadline,
28983 drain_linger: DEFAULT_DRAIN_LINGER,
28984 };
28985 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
28986 }
28987
28988 fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
28991 let registry = Arc::new(TimerDriverRegistry::default());
28992 registry.register(driver);
28993 registry
28994 }
28995
28996 #[cfg(not(target_family = "wasm"))]
28997 fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
28998 make_sleep(
29000 &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
29001 &Arc::new(TimerDriverRegistry::default()),
29002 deadline,
29003 )
29004 }
29005
29006 #[cfg(not(target_family = "wasm"))]
29007 #[test]
29008 fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
29009 use std::sync::mpsc;
29010
29011 let executor = multi_thread_executor(
29012 1,
29013 Some(Duration::from_secs(1)),
29014 "timer-registration-admission",
29015 );
29016 let (entered_tx, entered_rx) = mpsc::channel();
29017 let (release_tx, release_rx) = mpsc::channel();
29018 let registration_executor = Arc::clone(&executor);
29019 let registration = std::thread::spawn(move || {
29020 TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
29021 *slot.borrow_mut() = Some(Box::new(move || {
29022 entered_tx.send(()).unwrap();
29023 release_rx.recv().unwrap();
29024 }));
29025 });
29026 registration_executor.register_timer(
29027 1,
29028 Instant::now() + Duration::from_secs(60),
29029 futures::task::noop_waker(),
29030 &Arc::new(AtomicBool::new(false)),
29031 );
29032 });
29033
29034 entered_rx
29035 .recv_timeout(Duration::from_secs(2))
29036 .expect("timer registration must pause after entering verdict admission");
29037 let verdict = executor.try_begin_deadlock_verdict();
29038 let admission_blocked_verdict = verdict.is_none();
29039 drop(verdict);
29040 release_tx.send(()).unwrap();
29041 registration.join().unwrap();
29042
29043 assert!(
29044 admission_blocked_verdict,
29045 "a timer registration must remain announced until its heap entry is visible"
29046 );
29047 assert!(
29048 executor.next_timer_deadline().is_some(),
29049 "the admitted registration must publish its timer after release"
29050 );
29051 executor.begin_shutdown();
29052 executor.wait_until_scheduler_idle();
29053 }
29054
29055 #[cfg(not(target_family = "wasm"))]
29056 #[derive(Clone, Copy, Debug)]
29057 enum TimerFireAdmissionPath {
29058 Timekeeper,
29059 Cooperative,
29060 Shutdown,
29061 }
29062
29063 #[cfg(not(target_family = "wasm"))]
29064 struct ScheduleAfterTimerWakeRelease {
29065 executor: Arc<MultiThreadExecutor>,
29066 runnable: Mutex<Option<Runnable>>,
29067 entered: std::sync::mpsc::Sender<()>,
29068 release: Mutex<std::sync::mpsc::Receiver<()>>,
29069 scheduled: std::sync::mpsc::Sender<()>,
29070 }
29071
29072 #[cfg(not(target_family = "wasm"))]
29073 impl ScheduleAfterTimerWakeRelease {
29074 fn publish(&self) {
29075 self.entered.send(()).unwrap();
29076 self
29077 .release
29078 .lock()
29079 .unwrap_or_else(std::sync::PoisonError::into_inner)
29080 .recv()
29081 .unwrap();
29082 let runnable = self
29083 .runnable
29084 .lock()
29085 .unwrap_or_else(std::sync::PoisonError::into_inner)
29086 .take();
29087 if let Some(runnable) = runnable {
29088 self.executor.schedule(runnable);
29089 }
29090 self.scheduled.send(()).unwrap();
29091 }
29092 }
29093
29094 #[cfg(not(target_family = "wasm"))]
29095 impl std::task::Wake for ScheduleAfterTimerWakeRelease {
29096 fn wake(self: Arc<Self>) {
29097 self.publish();
29098 }
29099
29100 fn wake_by_ref(self: &Arc<Self>) {
29101 self.publish();
29102 }
29103 }
29104
29105 #[cfg(not(target_family = "wasm"))]
29106 fn insert_timer_for_fire_admission_test(
29107 executor: &MultiThreadExecutor,
29108 id: TimerId,
29109 deadline: Instant,
29110 waker: Waker,
29111 fired: Arc<AtomicBool>,
29112 ) {
29113 let mut inner = executor
29114 .timers
29115 .inner
29116 .lock()
29117 .unwrap_or_else(std::sync::PoisonError::into_inner);
29118 inner.entries.insert(id, HeapTimerEntry { waker, fired });
29119 inner.queue.push(deadline, id);
29120 }
29121
29122 #[cfg(not(target_family = "wasm"))]
29123 fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
29124 use std::sync::mpsc;
29125
29126 let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
29127 let (runnable, task) = async_task::spawn(async {}, |_| {});
29128 task.detach();
29129 let (entered_tx, entered_rx) = mpsc::channel();
29130 let (release_tx, release_rx) = mpsc::channel();
29131 let (scheduled_tx, scheduled_rx) = mpsc::channel();
29132 let fired = Arc::new(AtomicBool::new(false));
29133 let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
29134 executor: Arc::clone(&executor),
29135 runnable: Mutex::new(Some(runnable)),
29136 entered: entered_tx,
29137 release: Mutex::new(release_rx),
29138 scheduled: scheduled_tx,
29139 }));
29140
29141 let fire_thread = match path {
29142 TimerFireAdmissionPath::Timekeeper => {
29143 executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
29144 None
29145 }
29146 TimerFireAdmissionPath::Cooperative => {
29147 insert_timer_for_fire_admission_test(
29148 &executor,
29149 1,
29150 Instant::now(),
29151 waker,
29152 Arc::clone(&fired),
29153 );
29154 let fire_executor = Arc::clone(&executor);
29155 Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
29156 }
29157 TimerFireAdmissionPath::Shutdown => {
29158 insert_timer_for_fire_admission_test(
29159 &executor,
29160 1,
29161 Instant::now() + Duration::from_secs(60),
29162 waker,
29163 Arc::clone(&fired),
29164 );
29165 let fire_executor = Arc::clone(&executor);
29166 Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
29167 }
29168 };
29169
29170 entered_rx
29174 .recv_timeout(Duration::from_secs(10))
29175 .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
29176 assert!(
29177 executor.next_timer_deadline().is_none(),
29178 "{path:?} must exercise the remove-before-wake window"
29179 );
29180 assert!(
29181 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
29182 "{path:?} must retain admission while its waker is publishing"
29183 );
29184 let verdict = executor.try_begin_deadlock_verdict();
29185 let admission_blocked_verdict = verdict.is_none();
29186 drop(verdict);
29187
29188 release_tx.send(()).unwrap();
29189 scheduled_rx
29190 .recv_timeout(Duration::from_secs(10))
29191 .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
29192 if let Some(fire_thread) = fire_thread {
29193 fire_thread.join().unwrap();
29194 }
29195 assert!(
29196 admission_blocked_verdict,
29197 "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
29198 );
29199
29200 executor.begin_shutdown();
29201 executor.wait_until_scheduler_idle();
29202 }
29203
29204 #[cfg(not(target_family = "wasm"))]
29205 #[test]
29206 fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
29207 for path in [
29208 TimerFireAdmissionPath::Timekeeper,
29209 TimerFireAdmissionPath::Cooperative,
29210 TimerFireAdmissionPath::Shutdown,
29211 ] {
29212 assert_timer_fire_path_holds_deadlock_admission(path);
29213 }
29214 }
29215
29216 #[cfg(not(target_family = "wasm"))]
29217 #[test]
29218 fn multi_thread_sleep_fires_from_foreign_block_on() {
29219 use std::sync::mpsc;
29224
29225 let (done_tx, done_rx) = mpsc::channel();
29226 let runner = std::thread::spawn(move || {
29227 let executor = multi_thread_executor(2, None, "timer-foreign");
29228 let started = Instant::now();
29229 let mut future = std::pin::pin!(async {
29230 heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
29231 });
29232 executor.block_on(future.as_mut());
29233 assert!(
29234 started.elapsed() >= Duration::from_millis(80),
29235 "the sleep must not resolve before its deadline"
29236 );
29237 let _ = done_tx.send(());
29238 });
29239
29240 match done_rx.recv_timeout(Duration::from_secs(10)) {
29241 Ok(()) => runner.join().unwrap(),
29242 Err(error) => {
29243 panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
29244 }
29245 }
29246 }
29247
29248 #[cfg(not(target_family = "wasm"))]
29249 #[test]
29250 fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
29251 use std::sync::mpsc;
29254
29255 let (done_tx, done_rx) = mpsc::channel();
29256 let runner = std::thread::spawn(move || {
29257 let executor = multi_thread_executor(1, None, "timer-drainer");
29258
29259 let sleep_exec = Arc::clone(&executor);
29262 let sleep_done = Arc::new(AtomicBool::new(false));
29263 let sleep_flag = Arc::clone(&sleep_done);
29264 let sched = Arc::clone(&executor);
29265 let (sleep_runnable, sleep_task) = async_task::spawn(
29266 async move {
29267 heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
29268 sleep_flag.store(true, Ordering::SeqCst);
29269 },
29270 move |r| sched.schedule(r),
29271 );
29272 executor.schedule(sleep_runnable);
29273 wait_until("the timekeeper parked on the 60s deadline", || {
29274 executor
29275 .timers
29276 .inner
29277 .lock()
29278 .unwrap()
29279 .timekeeper_parker
29280 .is_some()
29281 });
29282
29283 let counter = Arc::new(AtomicUsize::new(0));
29285 let mut tasks = Vec::new();
29286 for _ in 0..32 {
29287 let counter = Arc::clone(&counter);
29288 let sched = Arc::clone(&executor);
29289 let (runnable, task) = async_task::spawn(
29290 async move {
29291 counter.fetch_add(1, Ordering::SeqCst);
29292 },
29293 move |r| sched.schedule(r),
29294 );
29295 executor.schedule(runnable);
29296 tasks.push(task);
29297 }
29298 for task in tasks {
29299 futures::executor::block_on(task);
29300 }
29301 assert_eq!(counter.load(Ordering::SeqCst), 32);
29302 assert!(
29303 !sleep_done.load(Ordering::SeqCst),
29304 "the 60s timer must still be pending: the work ran DURING the timer wait"
29305 );
29306 executor.shutdown_timers();
29308 futures::executor::block_on(sleep_task);
29309 let _ = done_tx.send(());
29310 });
29311
29312 match done_rx.recv_timeout(Duration::from_secs(15)) {
29313 Ok(()) => runner.join().unwrap(),
29314 Err(error) => {
29315 panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
29316 }
29317 }
29318 }
29319
29320 #[cfg(not(target_family = "wasm"))]
29321 #[test]
29322 fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
29323 let executor = multi_thread_executor(2, None, "timer-lazy-start");
29324 assert!(
29325 executor.timers.thread.lock().unwrap().is_none(),
29326 "constructing a runtime without timers must not create an extra OS thread"
29327 );
29328
29329 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29330 assert!(
29331 executor.timers.thread.lock().unwrap().is_none(),
29332 "an unpolled sleep has not registered a deadline"
29333 );
29334 let waker = futures::task::noop_waker();
29335 let mut cx = Context::from_waker(&waker);
29336 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29337 assert!(
29338 executor.timers.thread.lock().unwrap().is_some(),
29339 "the first timer registration must start the dedicated service"
29340 );
29341 }
29342
29343 #[cfg(not(target_family = "wasm"))]
29344 #[test]
29345 fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
29346 use std::sync::mpsc;
29347
29348 let executor = multi_thread_executor(1, None, "timer-closed-start");
29349 executor.shutdown_timers();
29350 let handle_guard = executor.timers.thread.lock().unwrap();
29351 let ensure_executor = Arc::clone(&executor);
29352 let (completed_tx, completed_rx) = mpsc::channel();
29353 let ensure = std::thread::spawn(move || {
29354 ensure_executor.timers.ensure_started().unwrap();
29355 completed_tx.send(()).unwrap();
29356 });
29357
29358 completed_rx.recv_timeout(Duration::from_secs(10)).expect(
29361 "ensure_started consulted or joined the timer handle before observing the closed heap",
29362 );
29363 drop(handle_guard);
29364 ensure.join().unwrap();
29365 }
29366
29367 #[cfg(not(target_family = "wasm"))]
29368 #[test]
29369 fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
29370 const TIMERS: u64 = 4096;
29371
29372 let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
29373 let base = Instant::now() + Duration::from_secs(3600);
29374 for id in 0..TIMERS {
29375 executor.register_timer(
29376 id,
29377 base + Duration::from_secs(id),
29378 futures::task::noop_waker(),
29379 &Arc::new(AtomicBool::new(false)),
29380 );
29381 }
29382 {
29383 let inner = executor.timers.inner.lock().unwrap();
29384 assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
29385 assert_eq!(inner.queue.len(), inner.entries.len());
29386 }
29387
29388 for id in 1..TIMERS {
29391 executor.cancel_timer(id);
29392 }
29393 {
29394 let inner = executor.timers.inner.lock().unwrap();
29395 assert_eq!(inner.entries.len(), 1);
29396 assert_eq!(inner.queue.len(), 1);
29397 assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
29398 }
29399
29400 executor.cancel_timer(0);
29401 {
29402 let inner = executor.timers.inner.lock().unwrap();
29403 assert!(inner.entries.is_empty());
29404 assert!(inner.queue.is_empty());
29405 }
29406 executor.shutdown_timers();
29407 executor.timers.join();
29408 }
29409
29410 #[cfg(not(target_family = "wasm"))]
29411 #[test]
29412 fn timer_registration_is_transactional_when_timekeeper_start_fails() {
29413 let executor = multi_thread_executor(2, None, "timer-start-failure");
29414 executor
29415 .timers
29416 .fail_next_start
29417 .store(true, Ordering::SeqCst);
29418
29419 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29420 let waker = Waker::from(Arc::clone(&wake_count));
29421 let baseline_waker_refs = Arc::strong_count(&wake_count);
29422 let mut cx = Context::from_waker(&waker);
29423 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29424
29425 let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
29426 assert!(
29427 failure.is_err(),
29428 "the injected timer-thread start failure must panic the poll"
29429 );
29430 {
29431 let inner = executor.timers.inner.lock().unwrap();
29432 assert!(
29433 inner.entries.is_empty(),
29434 "a failed start must not retain a timer entry"
29435 );
29436 assert!(
29437 inner.queue.is_empty(),
29438 "a failed start must not retain a stale heap node"
29439 );
29440 assert!(
29441 inner.timekeeper_parker.is_none(),
29442 "a failed start must not publish a timekeeper parker"
29443 );
29444 }
29445 assert!(
29446 executor.timers.thread.lock().unwrap().is_none(),
29447 "a failed start must not retain a thread handle"
29448 );
29449 assert_eq!(
29450 Arc::strong_count(&wake_count),
29451 baseline_waker_refs,
29452 "a failed start must release the cloned timer waker"
29453 );
29454
29455 assert!(
29456 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29457 "the same Sleep must be safely retryable after a transient start failure"
29458 );
29459 {
29460 let inner = executor.timers.inner.lock().unwrap();
29461 assert_eq!(
29462 inner.entries.len(),
29463 1,
29464 "the retry must publish exactly one timer entry"
29465 );
29466 assert_eq!(
29467 inner.queue.len(),
29468 1,
29469 "the retry must publish exactly one heap node"
29470 );
29471 }
29472 assert!(
29473 executor.timers.thread.lock().unwrap().is_some(),
29474 "the retry must start the dedicated timekeeper"
29475 );
29476
29477 drop(sleep);
29478 assert!(
29479 executor.timers.inner.lock().unwrap().entries.is_empty(),
29480 "dropping the retried Sleep must cancel its registration"
29481 );
29482 executor.shutdown_timers();
29483 executor.wait_until_scheduler_idle();
29484 assert!(
29485 executor.timers.thread.lock().unwrap().is_none(),
29486 "shutdown after a recovered start failure must join the timekeeper"
29487 );
29488 }
29489
29490 #[cfg(not(target_family = "wasm"))]
29491 #[test]
29492 fn pending_timer_preserves_every_configured_rayon_worker() {
29493 use std::sync::mpsc;
29494
29495 let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
29496 let waker = futures::task::noop_waker();
29497 let mut cx = Context::from_waker(&waker);
29498 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29499 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29500 wait_until("the dedicated timer thread to park", || {
29501 executor
29502 .timers
29503 .inner
29504 .lock()
29505 .unwrap()
29506 .timekeeper_parker
29507 .is_some()
29508 });
29509
29510 let released = Arc::new((Mutex::new(false), Condvar::new()));
29511 let (started_tx, started_rx) = mpsc::channel();
29512 let (completed_tx, completed_rx) = mpsc::channel();
29513 for _ in 0..2 {
29514 let released = Arc::clone(&released);
29515 let started_tx = started_tx.clone();
29516 let completed_tx = completed_tx.clone();
29517 executor.pool.spawn(move || {
29518 started_tx.send(()).unwrap();
29519 let (lock, changed) = &*released;
29520 let mut released = lock
29521 .lock()
29522 .unwrap_or_else(std::sync::PoisonError::into_inner);
29523 while !*released {
29524 released = changed
29525 .wait(released)
29526 .unwrap_or_else(std::sync::PoisonError::into_inner);
29527 }
29528 completed_tx.send(()).unwrap();
29529 });
29530 }
29531 drop(started_tx);
29532 drop(completed_tx);
29533
29534 let first = started_rx.recv_timeout(Duration::from_secs(2));
29535 let second = started_rx.recv_timeout(Duration::from_secs(2));
29536 {
29537 let (lock, changed) = &*released;
29538 *lock
29539 .lock()
29540 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29541 changed.notify_all();
29542 }
29543 drop(sleep);
29544 for _ in 0..2 {
29545 completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29546 }
29547
29548 assert!(
29549 first.is_ok(),
29550 "the first configured Rayon worker did not start"
29551 );
29552 assert!(
29553 second.is_ok(),
29554 "a pending timer consumed one of the two configured Rayon workers"
29555 );
29556 }
29557
29558 #[cfg(not(target_family = "wasm"))]
29559 #[test]
29560 fn timekeeper_never_enters_a_blocking_closure() {
29561 use std::{
29562 sync::{Barrier, mpsc},
29563 time::Duration,
29564 };
29565
29566 let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
29567 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29568 let waker = Waker::from(Arc::clone(&wake_count));
29569 let mut cx = Context::from_waker(&waker);
29570 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
29571 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29572 wait_until(
29573 "the timekeeper parked before blocking work was submitted",
29574 || {
29575 executor
29576 .timers
29577 .inner
29578 .lock()
29579 .unwrap()
29580 .timekeeper_parker
29581 .is_some()
29582 },
29583 );
29584
29585 let release = Arc::new(Barrier::new(2));
29586 let blocker_release = Arc::clone(&release);
29587 let (started_tx, started_rx) = mpsc::sync_channel(1);
29588 let blocking = executor.schedule_blocking(move || {
29589 started_tx.send(()).unwrap();
29590 blocker_release.wait();
29591 });
29592 started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
29595
29596 wait_until(
29597 "the timer waker to fire while the Rayon worker is blocked",
29598 || wake_count.0.load(Ordering::SeqCst) != 0,
29599 );
29600 release.wait();
29601 futures::executor::block_on(blocking).unwrap();
29602 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
29603 }
29604
29605 #[cfg(not(target_family = "wasm"))]
29606 #[test]
29607 fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
29608 use std::sync::mpsc;
29609
29610 struct PendingEntered(Option<mpsc::Sender<()>>);
29611
29612 impl Future for PendingEntered {
29613 type Output = ();
29614
29615 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29616 if let Some(entered) = self.0.take() {
29617 entered.send(()).unwrap();
29618 }
29619 Poll::Pending
29620 }
29621 }
29622
29623 let options = RuntimeOptions {
29624 flavor: RuntimeFlavor::MultiThread,
29625 worker_threads: 2,
29626 max_blocking_tasks: 1,
29627 thread_name_prefix: "nested-timer-wake".to_string(),
29628 park_deadline: None,
29629 drain_linger: DEFAULT_DRAIN_LINGER,
29630 };
29631 let executor =
29632 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
29633
29634 let timer_exec = Arc::clone(&executor);
29635 let (timer_tx, timer_rx) = mpsc::channel();
29636 let timer_scheduler = Arc::clone(&executor);
29637 let (timer_runnable, timer_task) = async_task::spawn(
29638 async move {
29639 heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
29640 timer_tx.send(()).unwrap();
29641 },
29642 move |runnable| timer_scheduler.schedule(runnable),
29643 );
29644 executor.schedule(timer_runnable);
29645 wait_until(
29646 "the dedicated timekeeper parked before nested block_on",
29647 || {
29648 executor
29649 .timers
29650 .inner
29651 .lock()
29652 .unwrap()
29653 .timekeeper_parker
29654 .is_some()
29655 },
29656 );
29657
29658 let (entered_tx, entered_rx) = mpsc::channel();
29659 let nested_exec = Arc::clone(&executor);
29660 let nested_scheduler = Arc::clone(&executor);
29661 let (nested_runnable, nested_task) = async_task::spawn(
29662 async move {
29663 let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
29664 let outcome = nested_exec.block_on(pending.as_mut());
29665 drop(pending);
29666 assert_eq!(
29667 outcome,
29668 BlockOnOutcome::Stopped,
29669 "shutdown must stop the raw nested block_on driver"
29670 );
29671 },
29672 move |runnable| nested_scheduler.schedule(runnable),
29673 );
29674 executor.schedule(nested_runnable);
29675 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29676
29677 let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
29678 let (release_tx, release_rx) = mpsc::channel();
29679 let blocking = executor.schedule_blocking(move || {
29680 blocking_started_tx.send(()).unwrap();
29681 release_rx.recv().unwrap();
29682 });
29683 blocking_started_rx
29684 .recv_timeout(Duration::from_secs(2))
29685 .expect("a general worker must service blocking work");
29686 timer_rx
29687 .recv_timeout(Duration::from_secs(2))
29688 .expect("the timer wake must reach the nested cooperative driver");
29689
29690 release_tx.send(()).unwrap();
29691 futures::executor::block_on(blocking).unwrap();
29692 executor.begin_shutdown();
29693 assert_eq!(
29694 futures::executor::block_on(nested_task.fallible()),
29695 Some(())
29696 );
29697 futures::executor::block_on(timer_task);
29698 }
29699
29700 #[cfg(not(target_family = "wasm"))]
29701 #[test]
29702 fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
29703 use std::sync::mpsc;
29707
29708 let (done_tx, done_rx) = mpsc::channel();
29709 let runner = std::thread::spawn(move || {
29710 let executor = multi_thread_executor(2, None, "timer-rearm");
29711
29712 let long_exec = Arc::clone(&executor);
29713 let long_done = Arc::new(AtomicBool::new(false));
29714 let long_flag = Arc::clone(&long_done);
29715 let sched = Arc::clone(&executor);
29716 let (long_runnable, long_task) = async_task::spawn(
29717 async move {
29718 heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
29719 long_flag.store(true, Ordering::SeqCst);
29720 },
29721 move |r| sched.schedule(r),
29722 );
29723 executor.schedule(long_runnable);
29724 wait_until("the timekeeper parked on the 60s sentinel", || {
29725 executor
29726 .timers
29727 .inner
29728 .lock()
29729 .unwrap()
29730 .timekeeper_parker
29731 .is_some()
29732 });
29733
29734 let started = Instant::now();
29735 let mut future = std::pin::pin!(async {
29736 heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
29737 });
29738 executor.block_on(future.as_mut());
29739 let elapsed = started.elapsed();
29740 assert!(
29741 elapsed >= Duration::from_millis(60),
29742 "fired before its deadline: {elapsed:?}"
29743 );
29744 assert!(
29745 !long_done.load(Ordering::SeqCst),
29746 "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
29747 timekeeper, not by the sentinel expiring"
29748 );
29749 executor.shutdown_timers();
29750 futures::executor::block_on(long_task);
29751 let _ = done_tx.send(());
29752 });
29753
29754 match done_rx.recv_timeout(Duration::from_secs(15)) {
29755 Ok(()) => runner.join().unwrap(),
29756 Err(error) => panic!(
29757 "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
29758 ),
29759 }
29760 }
29761
29762 #[cfg(not(target_family = "wasm"))]
29763 #[test]
29764 fn multi_thread_sleep_cancels_registration_on_drop() {
29765 let executor = multi_thread_executor(1, None, "timer-cancel");
29768 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29769 let waker = Waker::from(Arc::clone(&wake_count));
29770 let mut cx = Context::from_waker(&waker);
29771
29772 let deadline = Instant::now() + Duration::from_millis(250);
29776 let mut sleep = heap_sleep(&executor, deadline);
29777 assert!(
29778 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29779 "must register, not resolve"
29780 );
29781 assert!(
29782 executor.next_timer_deadline().is_some(),
29783 "the registration must be in the heap"
29784 );
29785 drop(sleep);
29786 assert!(
29787 executor.next_timer_deadline().is_none(),
29788 "cancel-on-drop must retire the heap registration"
29789 );
29790 while Instant::now() < deadline + Duration::from_millis(25) {
29795 std::thread::sleep(Duration::from_millis(10));
29796 }
29797 executor.fire_due_timers();
29798 assert_eq!(
29799 wake_count.0.load(Ordering::SeqCst),
29800 0,
29801 "a dropped Sleep's waker must never fire"
29802 );
29803 }
29804
29805 #[cfg(not(target_family = "wasm"))]
29806 #[test]
29807 fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
29808 let executor = multi_thread_executor(2, None, "timer-cancel-retire");
29809 let waker = futures::task::noop_waker();
29810 let mut cx = Context::from_waker(&waker);
29811 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29812
29813 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29814 wait_until(
29815 "the timekeeper parked on the timer that will be cancelled",
29816 || {
29817 executor
29818 .timers
29819 .inner
29820 .lock()
29821 .unwrap()
29822 .timekeeper_parker
29823 .is_some()
29824 },
29825 );
29826
29827 drop(sleep);
29828 wait_until(
29829 "the cancelled timer's timekeeper returned to its idle wait",
29830 || {
29831 executor.next_timer_deadline().is_none()
29832 && executor
29833 .timers
29834 .inner
29835 .lock()
29836 .unwrap()
29837 .timekeeper_parker
29838 .is_none()
29839 },
29840 );
29841 }
29842
29843 #[cfg(not(target_family = "wasm"))]
29844 #[test]
29845 fn timer_waker_replacement_drops_outside_heap_lock() {
29846 use std::sync::mpsc;
29847
29848 struct DropOtherTimer(Mutex<Option<Sleep>>);
29849
29850 #[expect(
29851 clippy::manual_noop_waker,
29852 reason = "the no-op wake type has a reentrant Drop used by this regression"
29853 )]
29854 impl std::task::Wake for DropOtherTimer {
29855 fn wake(self: Arc<Self>) {}
29856 }
29857
29858 impl Drop for DropOtherTimer {
29859 fn drop(&mut self) {
29860 drop(self.0.lock().unwrap().take());
29861 }
29862 }
29863
29864 let (done_tx, done_rx) = mpsc::channel();
29865 let runner = std::thread::spawn(move || {
29866 let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
29867 let deadline = Instant::now() + Duration::from_secs(120);
29868 let mut other = heap_sleep(&executor, deadline);
29869 let noop = futures::task::noop_waker();
29870 let mut noop_cx = Context::from_waker(&noop);
29871 assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
29872
29873 let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
29874 let mut primary = heap_sleep(&executor, deadline);
29875 let mut old_cx = Context::from_waker(&old);
29876 assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
29877 drop(old);
29878
29879 let replacement = futures::task::noop_waker();
29880 let mut replacement_cx = Context::from_waker(&replacement);
29881 assert!(
29882 Pin::new(&mut primary)
29883 .poll(&mut replacement_cx)
29884 .is_pending()
29885 );
29886 drop(primary);
29887 executor.shutdown_timers();
29888 done_tx.send(()).unwrap();
29889 });
29890 done_rx
29893 .recv_timeout(Duration::from_secs(10))
29894 .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
29895 runner.join().unwrap();
29896 }
29897
29898 #[cfg(not(target_family = "wasm"))]
29899 #[test]
29900 fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
29901 use std::sync::mpsc;
29902
29903 struct DropFinalExecutorOnWake {
29904 executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
29905 completed: mpsc::Sender<()>,
29906 }
29907
29908 impl std::task::Wake for DropFinalExecutorOnWake {
29909 fn wake(self: Arc<Self>) {
29910 let executor = self
29911 .executor
29912 .lock()
29913 .unwrap_or_else(std::sync::PoisonError::into_inner)
29914 .take()
29915 .expect("the timer waker must own the final executor reference");
29916 assert_eq!(
29917 ON_TIMER_THREAD.with(std::cell::Cell::get),
29918 Some(executor.id),
29919 "the dedicated timer thread must deliver the timer wake"
29920 );
29921 drop(executor);
29922 self
29923 .completed
29924 .send(())
29925 .expect("the test receiver must outlive the timer-thread callback");
29926 }
29927 }
29928
29929 let executor = multi_thread_executor(1, None, "timer-final-arc");
29930 let (completed_tx, completed_rx) = mpsc::channel();
29931 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
29932 {
29933 let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
29934 executor: Mutex::new(Some(Arc::clone(&executor))),
29935 completed: completed_tx,
29936 }));
29937 let mut cx = Context::from_waker(&waker);
29938 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29939 }
29940 wait_until(
29941 "the timer thread parked before owning the final executor Arc",
29942 || {
29943 executor
29944 .timers
29945 .inner
29946 .lock()
29947 .unwrap()
29948 .timekeeper_parker
29949 .is_some()
29950 },
29951 );
29952
29953 drop(executor);
29957 completed_rx
29960 .recv_timeout(Duration::from_secs(10))
29961 .expect("dropping the final executor Arc on the timer thread attempted to join itself");
29962 }
29963
29964 #[cfg(not(target_family = "wasm"))]
29965 #[test]
29966 fn multi_thread_shutdown_with_pending_timers_drain_fires() {
29967 use std::sync::mpsc;
29972
29973 let (done_tx, done_rx) = mpsc::channel();
29974 let runner = std::thread::spawn(move || {
29975 let controller = Arc::new(RuntimeController::new());
29976 let backend = controller.backend();
29978 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29979 panic!("default native backend must be MultiThread");
29980 };
29981 let executor = Arc::downgrade(executor);
29982 let mut late = heap_sleep(
29983 &executor
29984 .upgrade()
29985 .expect("the initial executor must be live"),
29986 Instant::now() + Duration::from_secs(120),
29987 );
29988 drop(backend);
29989
29990 let (parked_tx, parked_rx) = mpsc::channel();
29991 let block_controller = Arc::clone(&controller);
29992 let block_exec = Weak::clone(&executor);
29993 let blocker = std::thread::spawn(move || {
29994 let mut future = std::pin::pin!(async {
29995 let executor = block_exec
29996 .upgrade()
29997 .expect("the runtime must remain live while blocking");
29998 let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
29999 drop(executor);
30000 parked_tx.send(()).unwrap();
30001 sleep.await;
30002 });
30003 block_controller.block_on(future.as_mut());
30004 });
30005 parked_rx.recv().unwrap();
30006 wait_until("the 120s sleep registered in the heap", || {
30007 executor
30008 .upgrade()
30009 .is_some_and(|executor| executor.next_timer_deadline().is_some())
30010 });
30011
30012 controller
30014 .shutdown()
30015 .expect("runtime shutdown must succeed");
30016 blocker
30017 .join()
30018 .expect("the blocked sleep must be drain-fired by shutdown, not hang");
30019
30020 let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
30023 let mut cx = Context::from_waker(&waker);
30024 assert!(
30025 Pin::new(&mut late).poll(&mut cx).is_ready(),
30026 "a Sleep polled after shutdown must fire early, never park a closing runtime"
30027 );
30028 let _ = done_tx.send(());
30029 });
30030
30031 match done_rx.recv_timeout(Duration::from_secs(10)) {
30032 Ok(()) => runner.join().unwrap(),
30033 Err(error) => {
30034 panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
30035 }
30036 }
30037 }
30038
30039 #[cfg(not(target_family = "wasm"))]
30040 #[test]
30041 fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
30042 use std::sync::mpsc;
30051
30052 let (done_tx, done_rx) = mpsc::channel();
30053 let runner = std::thread::spawn(move || {
30054 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
30055
30056 let task_exec = Arc::clone(&executor);
30057 let body = async move {
30058 let started = Instant::now();
30059 let mut inner = std::pin::pin!(async {
30060 heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
30061 });
30062 task_exec.block_on(inner.as_mut());
30064 assert!(started.elapsed() >= Duration::from_millis(250));
30065 };
30066 let sched = Arc::clone(&executor);
30067 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
30068 executor.schedule(runnable);
30069
30070 futures::executor::block_on(task);
30074 drop(executor);
30080 let _ = done_tx.send(());
30081 });
30082
30083 match done_rx.recv_timeout(Duration::from_secs(10)) {
30084 Ok(()) => join_within(
30085 "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
30086 runner,
30087 Duration::from_secs(30),
30088 ),
30089 Err(error) => {
30090 panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
30091 }
30092 }
30093 }
30094
30095 #[cfg(not(target_family = "wasm"))]
30096 #[test]
30097 fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
30098 use std::sync::mpsc;
30105
30106 struct GateWake {
30109 gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
30110 }
30111 impl std::task::Wake for GateWake {
30112 fn wake(self: Arc<Self>) {
30113 let gate = self.gate.lock().unwrap().take();
30114 if let Some(gate) = gate {
30115 let _ = gate.send(23);
30116 }
30117 }
30118 }
30119
30120 let (done_tx, done_rx) = mpsc::channel();
30121 let runner = std::thread::spawn(move || {
30122 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
30123 executor
30127 .active_drainers
30128 .store(executor.max_drainers, Ordering::SeqCst);
30129
30130 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30133 let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
30134
30135 let hook_exec = Arc::clone(&executor);
30136 let hook_gate = Arc::clone(&gate_tx);
30137 let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
30140 let hook_sleep_slot = Arc::clone(&parked_sleep);
30141 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
30142 *slot.borrow_mut() = Some(Box::new(move || {
30143 let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
30144 let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
30145 let mut cx = Context::from_waker(&waker);
30146 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30147 *hook_sleep_slot.lock().unwrap() = Some(sleep);
30148 }));
30149 });
30150
30151 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30154 let mut output = None;
30155 {
30156 let mut future = std::pin::pin!(async {
30157 output = Some(gate_rx.await.unwrap());
30158 });
30159 executor.block_on(future.as_mut());
30160 }
30161 DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
30167 assert_eq!(
30168 output,
30169 Some(23),
30170 "the edge-registered timer must have fired and released the gate"
30171 );
30172 executor.active_drainers.store(0, Ordering::SeqCst);
30173 drop(executor);
30176 let _ = done_tx.send(());
30177 });
30178
30179 match done_rx.recv_timeout(Duration::from_secs(10)) {
30180 Ok(()) => join_within(
30181 "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
30182 runner,
30183 Duration::from_secs(30),
30184 ),
30185 Err(error) => panic!(
30186 "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
30187 ),
30188 }
30189 }
30190
30191 #[test]
30192 fn current_thread_sleep_without_host_driver_fails_loud() {
30193 let metrics = Arc::new(RuntimeMetrics::default());
30197 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30198 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
30199
30200 let payload = catch_unwind(AssertUnwindSafe(|| {
30201 let _sleep = make_sleep(
30202 &backend,
30203 &Arc::new(TimerDriverRegistry::default()),
30204 Instant::now() + Duration::from_millis(10),
30205 );
30206 }))
30207 .expect_err("sleep_until on CurrentThread without a driver must panic");
30208 let message = payload
30209 .downcast_ref::<String>()
30210 .cloned()
30211 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
30212 .expect("panic payload must be a message");
30213 assert!(
30214 message.contains("CurrentThread runtime has no live timer driver registered"),
30215 "the diagnostic must name the missing driver, got: {message}"
30216 );
30217 }
30218
30219 #[test]
30220 fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
30221 let registry = TimerDriverRegistry::default();
30225 let a = ManualStubDriver::new();
30226 let b = ManualStubDriver::new();
30227 let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
30228 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
30229 assert_ne!(a_id, b_id, "registration handles must be distinct");
30230 assert_eq!(
30231 registry.current().map(|(id, _)| id),
30232 Some(b_id),
30233 "newest live registrant serves"
30234 );
30235 assert!(registry.has_live_driver());
30236
30237 b.kill();
30240 assert_eq!(
30241 registry.current().map(|(id, _)| id),
30242 Some(a_id),
30243 "dead newest must be skipped"
30244 );
30245 b.live.store(true, Ordering::SeqCst);
30248 assert_eq!(
30249 registry.current().map(|(id, _)| id),
30250 Some(a_id),
30251 "swept entries stay gone"
30252 );
30253
30254 registry.unregister(a_id);
30257 assert!(registry.current().is_none());
30258 assert!(
30259 !registry.has_live_driver(),
30260 "an emptied registry must not report a live driver"
30261 );
30262 }
30263
30264 #[test]
30265 fn timer_driver_registry_contains_callback_panics_and_falls_back() {
30266 let registry = TimerDriverRegistry::default();
30267 let fallback = ManualStubDriver::new();
30268 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30269 let panicking = Arc::new(PanickingTimerDriver {
30270 swept: AtomicBool::new(false),
30271 });
30272 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30273
30274 assert_eq!(
30275 registry.current().map(|(id, _)| id),
30276 Some(fallback_id),
30277 "a panicking liveness callback must be treated as a dead driver"
30278 );
30279 assert!(
30280 panicking.swept.load(Ordering::SeqCst),
30281 "a panicking liveness callback must still run its contained sweep hook"
30282 );
30283 assert!(
30284 registry.has_live_driver(),
30285 "the healthy fallback must remain selectable"
30286 );
30287 }
30288
30289 #[test]
30290 fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
30291 use std::{sync::mpsc, time::Duration};
30292
30293 let registry = Arc::new(TimerDriverRegistry::default());
30294 let fallback = ManualStubDriver::new();
30295 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30296 let reentrant = Arc::new(ReentrantLivenessDriver {
30297 registry: Arc::downgrade(®istry),
30298 registration: Mutex::new(None),
30299 });
30300 let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30301 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
30302
30303 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
30304 let selection_registry = Arc::clone(®istry);
30305 let selection_thread = std::thread::spawn(move || {
30306 selection_tx
30307 .send(selection_registry.current().map(|(id, _)| id))
30308 .unwrap();
30309 });
30310 assert_eq!(
30311 selection_rx
30312 .recv_timeout(Duration::from_secs(10))
30313 .expect("is_live re-entry deadlocked"),
30314 Some(fallback_id)
30315 );
30316 selection_thread.join().unwrap();
30317
30318 let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
30319 registry: Arc::downgrade(®istry),
30320 fallback: fallback_id,
30321 });
30322 let drop_id = registry.register(Arc::clone(&drop_driver));
30323 drop(drop_driver);
30324
30325 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
30326 let drop_registry = Arc::clone(®istry);
30327 let drop_thread = std::thread::spawn(move || {
30328 drop_registry.unregister(drop_id);
30329 drop_tx.send(()).unwrap();
30330 });
30331 drop_rx
30332 .recv_timeout(Duration::from_secs(10))
30333 .expect("driver destructor re-entry deadlocked");
30334 drop_thread.join().unwrap();
30335 assert!(
30336 registry.current().is_none(),
30337 "the destructor must be able to unregister the fallback"
30338 );
30339 }
30340
30341 #[test]
30342 fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
30343 const DRIVER_PAIRS: usize = 2_048;
30344
30345 let registry = Arc::new(TimerDriverRegistry::default());
30346 let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
30347 let mut live = Vec::with_capacity(DRIVER_PAIRS);
30348 let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
30349 for _ in 0..DRIVER_PAIRS {
30350 let driver = Arc::new(ReentrantSweepTimerDriver {
30351 registry: Arc::downgrade(®istry),
30352 registration: Mutex::new(None),
30353 sweeps: AtomicUsize::new(0),
30354 removed_before_sweep: AtomicBool::new(false),
30355 });
30356 let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30357 *driver.registration.lock().unwrap() = Some(id);
30358 dead.push(driver);
30359
30360 let driver = ManualStubDriver::new();
30361 live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
30362 live.push(driver);
30363 }
30364 let dead_tail = Arc::new(ReentrantSweepTimerDriver {
30365 registry: Arc::downgrade(®istry),
30366 registration: Mutex::new(None),
30367 sweeps: AtomicUsize::new(0),
30368 removed_before_sweep: AtomicBool::new(false),
30369 });
30370 let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
30371 *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
30372 dead.push(dead_tail);
30373
30374 let selection = registry.live();
30375 assert_eq!(
30376 selection
30377 .as_slice()
30378 .iter()
30379 .map(|(id, _)| *id)
30380 .collect::<Vec<_>>(),
30381 live_ids,
30382 "sweeping every interleaved dead host must retain live registration order"
30383 );
30384 drop(selection);
30385 assert!(
30386 dead.iter().all(|driver| {
30387 driver.sweeps.load(Ordering::SeqCst) == 1
30388 && driver.removed_before_sweep.load(Ordering::SeqCst)
30389 && driver.registration.lock().unwrap().is_none()
30390 }),
30391 "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
30392 );
30393
30394 {
30395 let state = registry
30396 .state
30397 .lock()
30398 .unwrap_or_else(std::sync::PoisonError::into_inner);
30399 assert_eq!(state.entries.len(), DRIVER_PAIRS);
30400 assert_eq!(state.oldest, live_ids.first().copied());
30401 assert_eq!(state.newest, live_ids.last().copied());
30402 assert_eq!(
30403 state
30404 .ordered_snapshot()
30405 .iter()
30406 .map(|(id, _)| *id)
30407 .collect::<Vec<_>>(),
30408 live_ids,
30409 "keyed removals must preserve the linked order of every live host"
30410 );
30411 }
30412 let second_selection = registry.live();
30413 assert_eq!(
30414 second_selection
30415 .as_slice()
30416 .iter()
30417 .map(|(id, _)| *id)
30418 .collect::<Vec<_>>(),
30419 live_ids,
30420 "a second selection must retain the same live order"
30421 );
30422 drop(second_selection);
30423 assert!(
30424 dead
30425 .iter()
30426 .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
30427 "permanently swept hosts must not receive repeated eviction callbacks"
30428 );
30429 assert_eq!(
30430 registry.current().map(|(id, _)| id),
30431 live_ids.last().copied(),
30432 "newest-live compatibility selection must survive the mass sweep"
30433 );
30434
30435 drop(live);
30436 drop(dead);
30437 }
30438
30439 #[test]
30440 fn timer_driver_registry_snapshot_contains_each_driver_drop() {
30441 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
30442
30443 if std::env::var_os(CHILD_ENV).is_some() {
30444 let registry = Arc::new(TimerDriverRegistry::default());
30445 let fallback = ManualStubDriver::new();
30446 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30447 let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
30448 registry: Arc::downgrade(®istry),
30449 registrations: Mutex::new(Vec::new()),
30450 });
30451 registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30452
30453 let first_cancels = Arc::new(AtomicUsize::new(0));
30454 let first_drops = Arc::new(AtomicUsize::new(0));
30455 let first = Arc::new(PanickingCancelDropTimerDriver {
30456 cancels: Arc::clone(&first_cancels),
30457 drops: Arc::clone(&first_drops),
30458 });
30459 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30460 drop(first);
30461
30462 let second_cancels = Arc::new(AtomicUsize::new(0));
30463 let second_drops = Arc::new(AtomicUsize::new(0));
30464 let second = Arc::new(PanickingCancelDropTimerDriver {
30465 cancels: Arc::clone(&second_cancels),
30466 drops: Arc::clone(&second_drops),
30467 });
30468 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30469 drop(second);
30470 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
30471
30472 assert_eq!(
30473 registry.current().map(|(id, _)| id),
30474 Some(fallback_id),
30475 "selection must retry after reentrant removals and preserve the healthy fallback"
30476 );
30477 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30478 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30479 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30480 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30481 assert_eq!(
30482 registry
30483 .state
30484 .lock()
30485 .unwrap_or_else(std::sync::PoisonError::into_inner)
30486 .entries
30487 .len(),
30488 1
30489 );
30490 return;
30491 }
30492
30493 let output = std::process::Command::new(std::env::current_exe().unwrap())
30494 .arg("--exact")
30495 .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
30496 .arg("--nocapture")
30497 .env(CHILD_ENV, "1")
30498 .output()
30499 .expect("the hostile timer-driver snapshot subprocess test must start");
30500 assert!(
30501 output.status.success(),
30502 "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30503 output.status.code(),
30504 String::from_utf8_lossy(&output.stdout),
30505 String::from_utf8_lossy(&output.stderr)
30506 );
30507 }
30508
30509 #[test]
30510 fn timer_driver_registry_live_result_contains_each_driver_drop() {
30511 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
30512
30513 if std::env::var_os(CHILD_ENV).is_some() {
30514 let registry = TimerDriverRegistry::default();
30515
30516 let first_cancels = Arc::new(AtomicUsize::new(0));
30517 let first_drops = Arc::new(AtomicUsize::new(0));
30518 let first = Arc::new(PanickingCancelDropTimerDriver {
30519 cancels: Arc::clone(&first_cancels),
30520 drops: Arc::clone(&first_drops),
30521 });
30522 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30523 drop(first);
30524
30525 let second_cancels = Arc::new(AtomicUsize::new(0));
30526 let second_drops = Arc::new(AtomicUsize::new(0));
30527 let second = Arc::new(PanickingCancelDropTimerDriver {
30528 cancels: Arc::clone(&second_cancels),
30529 drops: Arc::clone(&second_drops),
30530 });
30531 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30532 drop(second);
30533
30534 let live = registry.live();
30535 assert_eq!(live.len(), 2);
30536 registry.unregister(first_id);
30537 registry.unregister(second_id);
30538 drop(live);
30539
30540 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30541 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30542 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30543 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30544 assert!(
30545 registry
30546 .state
30547 .lock()
30548 .unwrap_or_else(std::sync::PoisonError::into_inner)
30549 .entries
30550 .is_empty()
30551 );
30552 return;
30553 }
30554
30555 let output = std::process::Command::new(std::env::current_exe().unwrap())
30556 .arg("--exact")
30557 .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
30558 .arg("--nocapture")
30559 .env(CHILD_ENV, "1")
30560 .output()
30561 .expect("the hostile live timer-driver snapshot subprocess test must start");
30562 assert!(
30563 output.status.success(),
30564 "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30565 output.status.code(),
30566 String::from_utf8_lossy(&output.stdout),
30567 String::from_utf8_lossy(&output.stderr)
30568 );
30569 }
30570
30571 #[test]
30572 fn timer_driver_registry_drop_contains_each_driver_destructor() {
30573 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
30574
30575 if std::env::var_os(CHILD_ENV).is_some() {
30576 let first_drops = Arc::new(AtomicUsize::new(0));
30577 let second_drops = Arc::new(AtomicUsize::new(0));
30578 let registry = TimerDriverRegistry::default();
30579 let first = Arc::new(PanickingCancelDropTimerDriver {
30580 cancels: Arc::new(AtomicUsize::new(0)),
30581 drops: Arc::clone(&first_drops),
30582 });
30583 let second = Arc::new(PanickingCancelDropTimerDriver {
30584 cancels: Arc::new(AtomicUsize::new(0)),
30585 drops: Arc::clone(&second_drops),
30586 });
30587 registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30588 registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30589 drop(first);
30590 drop(second);
30591
30592 drop(registry);
30593
30594 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30595 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30596 return;
30597 }
30598
30599 let output = std::process::Command::new(std::env::current_exe().unwrap())
30600 .arg("--exact")
30601 .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
30602 .arg("--nocapture")
30603 .env(CHILD_ENV, "1")
30604 .output()
30605 .expect("the hostile timer-driver registry drop subprocess test must start");
30606 assert!(
30607 output.status.success(),
30608 "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
30609 output.status.code(),
30610 String::from_utf8_lossy(&output.stdout),
30611 String::from_utf8_lossy(&output.stderr)
30612 );
30613 }
30614
30615 #[test]
30616 fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
30617 const DRIVERS: usize = 512;
30618
30619 let metrics = Arc::new(RuntimeMetrics::default());
30620 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30621 let backend =
30622 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30623 let registry = Arc::new(TimerDriverRegistry::default());
30624 let drivers = (0..DRIVERS)
30625 .map(|_| {
30626 let driver = ManualStubDriver::new();
30627 registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30628 driver
30629 })
30630 .collect::<Vec<_>>();
30631 let mut sleep = make_sleep(
30632 &backend,
30633 ®istry,
30634 Instant::now() + Duration::from_secs(60),
30635 );
30636 let mut cx = Context::from_waker(Waker::noop());
30637
30638 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30639 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30640 let timer_id = drivers[0].registers.lock().unwrap()[0].0;
30641 match &sleep.inner {
30642 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
30643 #[cfg(not(target_family = "wasm"))]
30644 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30645 }
30646 for driver in &drivers {
30647 assert_eq!(
30648 driver.registers.lock().unwrap().len(),
30649 2,
30650 "a re-poll must refresh each live driver exactly once"
30651 );
30652 }
30653
30654 for driver in drivers.iter().step_by(2) {
30655 driver.kill();
30656 }
30657 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30658 match &sleep.inner {
30659 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
30660 #[cfg(not(target_family = "wasm"))]
30661 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30662 }
30663 for (index, driver) in drivers.iter().enumerate() {
30664 let registers = driver.registers.lock().unwrap().len();
30665 let cancels = driver.cancels.lock().unwrap().clone();
30666 if index % 2 == 0 {
30667 assert_eq!(registers, 2);
30668 assert_eq!(cancels, vec![timer_id]);
30669 } else {
30670 assert_eq!(registers, 3);
30671 assert!(cancels.is_empty());
30672 }
30673 }
30674
30675 drop(sleep);
30676 for driver in drivers {
30677 assert_eq!(
30678 driver.cancels.lock().unwrap().as_slice(),
30679 &[timer_id],
30680 "every physical arm must be cancelled exactly once"
30681 );
30682 }
30683 assert_eq!(registry.pending_len(), 0);
30684 assert!(!executor.host_timers.has_pending());
30685 }
30686
30687 #[test]
30688 fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
30689 let metrics = Arc::new(RuntimeMetrics::default());
30690 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30691 let backend =
30692 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30693 let healthy = ManualStubDriver::new();
30694 let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30695 let deadline = Instant::now() + Duration::from_secs(60);
30696 let mut sleep = make_sleep(&backend, ®istry, deadline);
30697 let mut cx = Context::from_waker(Waker::noop());
30698
30699 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30700 let timer_id = healthy.registers.lock().unwrap()[0].0;
30701
30702 let panicking = PanickingRegisterTimerDriver::new();
30703 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30704 let result = catch_unwind(AssertUnwindSafe(|| {
30705 let _ = Pin::new(&mut sleep).poll(&mut cx);
30706 }));
30707 assert!(
30708 result.is_err(),
30709 "the hostile register panic must retain its normal propagation"
30710 );
30711 assert_eq!(
30712 healthy.registers.lock().unwrap().len(),
30713 2,
30714 "the previously armed driver must have completed its refresh before the later panic"
30715 );
30716 assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
30717 assert!(
30718 panicking.pending.lock().unwrap().contains_key(&timer_id),
30719 "the hostile driver must model an arm committed before it panics"
30720 );
30721
30722 drop(sleep);
30723 assert_eq!(
30724 healthy.cancels.lock().unwrap().as_slice(),
30725 &[timer_id],
30726 "unwind must preserve cancellation ownership for the previously armed driver"
30727 );
30728 assert_eq!(
30729 panicking.cancels.lock().unwrap().as_slice(),
30730 &[timer_id],
30731 "unwind must preserve cancellation ownership for the driver that armed and then panicked"
30732 );
30733 assert!(panicking.pending.lock().unwrap().is_empty());
30734 assert_eq!(registry.pending_len(), 0);
30735 assert!(!executor.host_timers.has_pending());
30736 }
30737
30738 #[test]
30739 fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
30740 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
30741
30742 if std::env::var_os(CHILD_ENV).is_some() {
30743 let metrics = Arc::new(RuntimeMetrics::default());
30744 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30745 let backend =
30746 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30747 let registry = Arc::new(TimerDriverRegistry::default());
30748 let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
30749 registry: Arc::downgrade(®istry),
30750 later: Mutex::new(None),
30751 registers: AtomicUsize::new(0),
30752 });
30753 registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
30754
30755 let later_cancels = Arc::new(AtomicUsize::new(0));
30756 let later_drops = Arc::new(AtomicUsize::new(0));
30757 let later = Arc::new(PanickingCancelDropTimerDriver {
30758 cancels: Arc::clone(&later_cancels),
30759 drops: Arc::clone(&later_drops),
30760 });
30761 let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
30762 *earlier.later.lock().unwrap() = Some(later_id);
30763 drop(later);
30764
30765 let mut sleep = make_sleep(
30766 &backend,
30767 ®istry,
30768 Instant::now() + Duration::from_secs(60),
30769 );
30770 let mut cx = Context::from_waker(Waker::noop());
30771 let result = catch_unwind(AssertUnwindSafe(|| {
30772 let _ = Pin::new(&mut sleep).poll(&mut cx);
30773 }));
30774
30775 assert!(
30776 result.is_err(),
30777 "the earlier driver's register panic must still propagate"
30778 );
30779 assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
30780 assert_eq!(
30781 later_drops.load(Ordering::SeqCst),
30782 0,
30783 "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
30784 );
30785
30786 drop(sleep);
30787 assert_eq!(
30788 later_cancels.load(Ordering::SeqCst),
30789 1,
30790 "the later driver must remain owned for cancellation despite its registry removal"
30791 );
30792 assert_eq!(
30793 later_drops.load(Ordering::SeqCst),
30794 1,
30795 "the later driver's final destructor must run once behind containment"
30796 );
30797 assert_eq!(registry.pending_len(), 0);
30798 assert!(!executor.host_timers.has_pending());
30799 return;
30800 }
30801
30802 let output = std::process::Command::new(std::env::current_exe().unwrap())
30803 .arg("--exact")
30804 .arg(
30805 "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
30806 )
30807 .arg("--nocapture")
30808 .env(CHILD_ENV, "1")
30809 .output()
30810 .expect("the hostile host timer snapshot subprocess test must start");
30811 assert!(
30812 output.status.success(),
30813 "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
30814 output.status.code(),
30815 String::from_utf8_lossy(&output.stdout),
30816 String::from_utf8_lossy(&output.stderr)
30817 );
30818 }
30819
30820 #[test]
30821 fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
30822 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
30823
30824 if std::env::var_os(CHILD_ENV).is_some() {
30825 let metrics = Arc::new(RuntimeMetrics::default());
30826 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30827 let backend =
30828 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30829 let cancels = Arc::new(AtomicUsize::new(0));
30830 let drops = Arc::new(AtomicUsize::new(0));
30831 let hostile = Arc::new(PanickingCancelDropTimerDriver {
30832 cancels: Arc::clone(&cancels),
30833 drops: Arc::clone(&drops),
30834 });
30835 let healthy = ManualStubDriver::new();
30836 let registry = Arc::new(TimerDriverRegistry::default());
30837 let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
30838 registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30839 let mut sleep = make_sleep(
30840 &backend,
30841 ®istry,
30842 Instant::now() + Duration::from_secs(60),
30843 );
30844 let mut cx = Context::from_waker(Waker::noop());
30845
30846 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30847 let timer_id = healthy.registers.lock().unwrap()[0].0;
30848
30849 registry.unregister(hostile_id);
30852 drop(hostile);
30853 drop(sleep);
30854 assert_eq!(cancels.load(Ordering::SeqCst), 1);
30855 assert_eq!(drops.load(Ordering::SeqCst), 1);
30856 assert_eq!(
30857 healthy.cancels.lock().unwrap().as_slice(),
30858 &[timer_id],
30859 "one hostile cancellation must not skip later armed drivers"
30860 );
30861 assert_eq!(registry.pending_len(), 0);
30862 assert!(!executor.host_timers.has_pending());
30863 return;
30864 }
30865
30866 let output = std::process::Command::new(std::env::current_exe().unwrap())
30867 .arg("--exact")
30868 .arg(
30869 "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
30870 )
30871 .arg("--nocapture")
30872 .env(CHILD_ENV, "1")
30873 .output()
30874 .expect("the hostile timer-driver cancellation subprocess test must start");
30875 assert!(
30876 output.status.success(),
30877 "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
30878 output.status.code(),
30879 String::from_utf8_lossy(&output.stdout),
30880 String::from_utf8_lossy(&output.stderr)
30881 );
30882 }
30883
30884 #[test]
30885 fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
30886 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
30887
30888 if std::env::var_os(CHILD_ENV).is_some() {
30889 let metrics = Arc::new(RuntimeMetrics::default());
30890 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30891 let backend =
30892 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30893 let driver = ManualStubDriver::new();
30894 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30895 let state = Arc::new(TestHostileRawWakerState {
30896 clones: AtomicUsize::new(0),
30897 drop_panics: AtomicUsize::new(0),
30898 panic_on_clone: Some(2),
30899 });
30900
30901 let result = catch_unwind(AssertUnwindSafe(|| {
30902 let mut sleep = make_sleep(
30903 &backend,
30904 ®istry,
30905 Instant::now() + Duration::from_secs(60),
30906 );
30907 let waker = test_hostile_waker(Arc::clone(&state));
30908 let mut cx = Context::from_waker(&waker);
30909 let _ = Pin::new(&mut sleep).poll(&mut cx);
30910 }));
30911 assert!(
30912 result.is_err(),
30913 "the original RawWaker clone panic must still propagate"
30914 );
30915 assert_eq!(state.clones.load(Ordering::SeqCst), 2);
30916 assert_eq!(
30917 state.drop_panics.load(Ordering::SeqCst),
30918 1,
30919 "the first clone must be destroyed once behind a contained boundary"
30920 );
30921 assert!(driver.registers.lock().unwrap().is_empty());
30922 assert_eq!(registry.pending_len(), 0);
30923 assert!(!executor.host_timers.has_pending());
30924 return;
30925 }
30926
30927 let output = std::process::Command::new(std::env::current_exe().unwrap())
30928 .arg("--exact")
30929 .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
30930 .arg("--nocapture")
30931 .env(CHILD_ENV, "1")
30932 .output()
30933 .expect("the hostile RawWaker subprocess test must start");
30934 assert!(
30935 output.status.success(),
30936 "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
30937 output.status.code(),
30938 String::from_utf8_lossy(&output.stdout),
30939 String::from_utf8_lossy(&output.stderr)
30940 );
30941 }
30942
30943 #[test]
30944 fn host_sleep_register_unwind_contains_unretained_waker_drop() {
30945 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
30946
30947 if std::env::var_os(CHILD_ENV).is_some() {
30948 let metrics = Arc::new(RuntimeMetrics::default());
30949 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30950 let backend =
30951 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30952 let driver = Arc::new(PanicBeforeRetainingTimerDriver {
30953 registers: AtomicUsize::new(0),
30954 });
30955 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30956 let state = Arc::new(TestHostileRawWakerState {
30957 clones: AtomicUsize::new(0),
30958 drop_panics: AtomicUsize::new(0),
30959 panic_on_clone: None,
30960 });
30961 let mut sleep = make_sleep(
30962 &backend,
30963 ®istry,
30964 Instant::now() + Duration::from_secs(60),
30965 );
30966 let waker = test_hostile_waker(Arc::clone(&state));
30967 let mut cx = Context::from_waker(&waker);
30968
30969 let result = catch_unwind(AssertUnwindSafe(|| {
30970 let _ = Pin::new(&mut sleep).poll(&mut cx);
30971 }));
30972 assert!(
30973 result.is_err(),
30974 "the timer-driver register panic must still propagate"
30975 );
30976 assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
30977 assert_eq!(state.clones.load(Ordering::SeqCst), 3);
30978 assert_eq!(
30979 state.drop_panics.load(Ordering::SeqCst),
30980 1,
30981 "the unretained register argument must be destroyed once behind the proxy boundary"
30982 );
30983 assert_eq!(registry.pending_len(), 1);
30984 assert!(executor.host_timers.has_pending());
30985
30986 drop(sleep);
30987 assert_eq!(
30988 state.drop_panics.load(Ordering::SeqCst),
30989 3,
30990 "repoll and registry wakers must retain their existing contained cleanup"
30991 );
30992 assert_eq!(registry.pending_len(), 0);
30993 assert!(!executor.host_timers.has_pending());
30994 return;
30995 }
30996
30997 let output = std::process::Command::new(std::env::current_exe().unwrap())
30998 .arg("--exact")
30999 .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
31000 .arg("--nocapture")
31001 .env(CHILD_ENV, "1")
31002 .output()
31003 .expect("the hostile register-waker subprocess test must start");
31004 assert!(
31005 output.status.success(),
31006 "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
31007 output.status.code(),
31008 String::from_utf8_lossy(&output.stdout),
31009 String::from_utf8_lossy(&output.stderr)
31010 );
31011 }
31012
31013 #[test]
31014 fn host_sleep_rearms_on_next_live_driver_after_eviction() {
31015 let metrics = Arc::new(RuntimeMetrics::default());
31021 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31022 let backend =
31023 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31024
31025 let a = ManualStubDriver::new();
31026 let b = ManualStubDriver::new();
31027 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31028 let deadline = Instant::now() + Duration::from_secs(60);
31029
31030 let (timer_id, b_id) = {
31031 let mut sleep = std::pin::pin!(make_sleep(&backend, ®istry, deadline));
31032 let mut cx = Context::from_waker(Waker::noop());
31033
31034 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31036 let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
31037 assert_eq!(armed_deadline, deadline);
31038 assert!(
31039 executor.host_timers.has_pending(),
31040 "first poll must arm the wake-token"
31041 );
31042
31043 a.kill();
31047 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31048 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31049 assert_eq!(
31050 b.registers.lock().unwrap().as_slice(),
31051 &[(timer_id, deadline)],
31052 "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
31053 );
31054 assert_eq!(
31055 a.cancels.lock().unwrap().as_slice(),
31056 &[timer_id],
31057 "the dead driver gets a best-effort cancel for the old arm"
31058 );
31059 assert_eq!(
31060 registry.current().map(|(id, _)| id),
31061 Some(b_id),
31062 "the dead driver was evicted"
31063 );
31064 assert!(
31065 executor.host_timers.has_pending(),
31066 "the wake-token survives the re-arm (still exactly this sleep's entry)"
31067 );
31068 (timer_id, b_id)
31069 };
31070
31071 let _ = b_id;
31074 assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
31075 assert!(
31076 !executor.host_timers.has_pending(),
31077 "drop must retire the wake-token"
31078 );
31079 }
31080
31081 #[test]
31082 fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
31083 let metrics = Arc::new(RuntimeMetrics::default());
31084 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31085 let backend =
31086 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31087 let responsive = Arc::new(StubHostTimerDriver::new());
31088 let starved = ManualStubDriver::new();
31089 let registry = Arc::new(TimerDriverRegistry::default());
31090 registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
31091 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31094
31095 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
31096 let waker = Waker::from(Arc::clone(&wake_count));
31097 let mut cx = Context::from_waker(&waker);
31098 let deadline = Instant::now() + Duration::from_millis(500);
31103 let mut sleep = make_sleep(&backend, ®istry, deadline);
31104 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31105
31106 let responsive_timer = *responsive
31107 .timers
31108 .lock()
31109 .unwrap()
31110 .keys()
31111 .next()
31112 .expect("responsive timer must be armed");
31113 assert_eq!(
31114 starved.registers.lock().unwrap().as_slice(),
31115 &[(responsive_timer, deadline)],
31116 "every live host must receive the same timer identity and absolute deadline"
31117 );
31118
31119 let fire_bound = Instant::now() + Duration::from_secs(30);
31126 while wake_count.0.load(Ordering::SeqCst) == 0 {
31127 assert!(
31128 Instant::now() < fire_bound,
31129 "the responsive host must wake a sleep whose newest host is live but starved"
31130 );
31131 std::thread::sleep(Duration::from_millis(10));
31132 }
31133 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31136 assert_eq!(
31137 starved.cancels.lock().unwrap().as_slice(),
31138 &[responsive_timer],
31139 "completion through the winning host must cancel the starved losing arm"
31140 );
31141 }
31142
31143 #[test]
31144 fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
31145 use std::sync::mpsc;
31146
31147 struct ReentrantTimerRegistrationWake {
31148 registry: Weak<TimerDriverRegistry>,
31149 wakes: AtomicUsize,
31150 reentered: AtomicBool,
31151 }
31152
31153 impl std::task::Wake for ReentrantTimerRegistrationWake {
31154 fn wake(self: Arc<Self>) {
31155 self.wakes.fetch_add(1, Ordering::SeqCst);
31156 if let Some(registry) = self.registry.upgrade() {
31157 let _ = registry.has_live_driver();
31158 self.reentered.store(true, Ordering::SeqCst);
31159 }
31160 }
31161 }
31162
31163 let metrics = Arc::new(RuntimeMetrics::default());
31164 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31165 let backend =
31166 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31167 let registry = Arc::new(TimerDriverRegistry::default());
31168 let starved = ManualStubDriver::new();
31169 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31170
31171 let deadline = Instant::now() + Duration::from_secs(2);
31178 let mut first = make_sleep(&backend, ®istry, deadline);
31179 let mut second = make_sleep(&backend, ®istry, deadline);
31180 let first_wake = Arc::new(ReentrantTimerRegistrationWake {
31181 registry: Arc::downgrade(®istry),
31182 wakes: AtomicUsize::new(0),
31183 reentered: AtomicBool::new(false),
31184 });
31185 let second_wake = Arc::new(ReentrantTimerRegistrationWake {
31186 registry: Arc::downgrade(®istry),
31187 wakes: AtomicUsize::new(0),
31188 reentered: AtomicBool::new(false),
31189 });
31190 let first_waker = Waker::from(Arc::clone(&first_wake));
31191 let second_waker = Waker::from(Arc::clone(&second_wake));
31192 let mut first_cx = Context::from_waker(&first_waker);
31193 let mut second_cx = Context::from_waker(&second_waker);
31194
31195 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31196 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31197 let initial = starved.registers.lock().unwrap().clone();
31198 let first_id = initial[0].0;
31199 let second_id = initial[1].0;
31200 assert_ne!(first_id, second_id);
31201 assert_eq!(
31202 registry.pending_len(),
31203 2,
31204 "one stable repoll entry is retained per sleep"
31205 );
31206
31207 let responsive = Arc::new(StubHostTimerDriver::new());
31208 let (registered_tx, registered_rx) = mpsc::sync_channel(1);
31209 let registration_registry = Arc::clone(®istry);
31210 let registration_driver = Arc::clone(&responsive);
31211 let registration = std::thread::spawn(move || {
31212 let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
31213 registered_tx.send(id).unwrap();
31214 });
31215 registered_rx
31219 .recv_timeout(Duration::from_secs(10))
31220 .expect("late driver registration or repoll callback re-entry deadlocked");
31221 registration.join().unwrap();
31222
31223 assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
31224 assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
31225 assert!(first_wake.reentered.load(Ordering::SeqCst));
31226 assert!(second_wake.reentered.load(Ordering::SeqCst));
31227 assert!(
31228 responsive.timers.lock().unwrap().is_empty(),
31229 "registration requests bounded re-polls instead of invoking driver callbacks inline"
31230 );
31231
31232 assert!(
31237 Instant::now() < deadline,
31238 "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
31239 this runner is starved beyond the margin this test budgets for"
31240 );
31241 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31242 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31243 let responsive_ids = responsive
31244 .timers
31245 .lock()
31246 .unwrap()
31247 .keys()
31248 .copied()
31249 .collect::<FxHashSet<_>>();
31250 assert_eq!(
31251 responsive_ids,
31252 FxHashSet::from_iter([first_id, second_id]),
31253 "every pending sleep must re-arm its existing identity on the new driver"
31254 );
31255 assert_eq!(
31256 starved
31257 .registers
31258 .lock()
31259 .unwrap()
31260 .iter()
31261 .filter(|(id, _)| *id == first_id)
31262 .count(),
31263 2,
31264 "the old host receives only the normal waker refresh on the one re-poll"
31265 );
31266 assert_eq!(
31267 starved
31268 .registers
31269 .lock()
31270 .unwrap()
31271 .iter()
31272 .filter(|(id, _)| *id == second_id)
31273 .count(),
31274 2,
31275 "the old host receives only the normal waker refresh on the one re-poll"
31276 );
31277 assert_eq!(
31278 registry.pending_len(),
31279 2,
31280 "late registration must not duplicate pending state"
31281 );
31282
31283 let fire_bound = Instant::now() + Duration::from_secs(30);
31290 while first_wake.wakes.load(Ordering::SeqCst) < 2
31291 || second_wake.wakes.load(Ordering::SeqCst) < 2
31292 {
31293 assert!(
31294 Instant::now() < fire_bound,
31295 "the late responsive host must fire both timers while the original host remains starved"
31296 );
31297 std::thread::sleep(Duration::from_millis(10));
31298 }
31299 assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
31300 assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
31301
31302 let mut cancelled = starved.cancels.lock().unwrap().clone();
31303 cancelled.sort_unstable();
31304 let mut expected = vec![first_id, second_id];
31305 expected.sort_unstable();
31306 assert_eq!(
31307 cancelled, expected,
31308 "completion must cancel both losing starved-host arms"
31309 );
31310 assert_eq!(
31311 registry.pending_len(),
31312 0,
31313 "completion retires every repoll entry"
31314 );
31315 assert!(!executor.host_timers.has_pending());
31316 }
31317
31318 #[test]
31319 fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
31320 let metrics = Arc::new(RuntimeMetrics::default());
31324 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31325 let backend =
31326 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31327
31328 let a = ManualStubDriver::new();
31329 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31330 let mut sleep = std::pin::pin!(make_sleep(
31331 &backend,
31332 ®istry,
31333 Instant::now() + Duration::from_secs(60)
31334 ));
31335 let mut cx = Context::from_waker(Waker::noop());
31336 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31337
31338 a.kill();
31339 let payload = catch_unwind(AssertUnwindSafe(|| {
31340 let _ = sleep.as_mut().poll(&mut cx);
31341 }))
31342 .expect_err("a sleep with every driver dead must panic, not pend silently");
31343 let message = payload
31344 .downcast_ref::<String>()
31345 .cloned()
31346 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
31347 .expect("panic payload must be a message");
31348 assert!(
31349 message.contains("lost every live timer driver mid-sleep"),
31350 "the diagnostic must name the mid-flight driver loss, got: {message}"
31351 );
31352 }
31353
31354 #[test]
31355 fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
31356 let metrics = Arc::new(RuntimeMetrics::default());
31363 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31364 let backend =
31365 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31366
31367 let a = ManualStubDriver::new();
31368 let b = ManualStubDriver::new();
31369 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31370 let mut sleep = std::pin::pin!(make_sleep(
31371 &backend,
31372 ®istry,
31373 Instant::now() + Duration::from_secs(60)
31374 ));
31375
31376 let flag = Arc::new(WakeFlag::default());
31378 let waker = Waker::from(Arc::clone(&flag));
31379 let mut cx = Context::from_waker(&waker);
31380 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31381 assert!(!flag.0.load(Ordering::SeqCst));
31382
31383 a.kill();
31386 registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31387 let _ = registry.has_live_driver();
31388
31389 assert!(
31390 a.swept.load(Ordering::SeqCst),
31391 "the sweep must notify the swept driver"
31392 );
31393 assert!(
31394 flag.0.load(Ordering::SeqCst),
31395 "a sleep armed on the swept driver must be woken into re-selection, not stranded"
31396 );
31397
31398 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31400 assert_eq!(
31401 b.registers.lock().unwrap().len(),
31402 1,
31403 "the re-poll must re-arm on the live driver"
31404 );
31405 }
31406
31407 #[test]
31408 fn current_thread_sleep_fires_through_stub_host_driver() {
31409 use std::sync::mpsc;
31412
31413 let (done_tx, done_rx) = mpsc::channel();
31414 let runner = std::thread::spawn(move || {
31415 let metrics = Arc::new(RuntimeMetrics::default());
31416 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31417 let backend =
31418 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31419 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31420
31421 let started = Instant::now();
31422 let mut future = std::pin::pin!(async {
31423 make_sleep(
31424 &backend,
31425 ®istry_with(driver),
31426 Instant::now() + Duration::from_millis(60),
31427 )
31428 .await;
31429 });
31430 executor.block_on(future.as_mut());
31431 assert!(started.elapsed() >= Duration::from_millis(60));
31432 assert!(
31433 !executor.host_timers.has_pending(),
31434 "a completed sleep must retire its pending-registry entry"
31435 );
31436 let _ = done_tx.send(());
31437 });
31438
31439 match done_rx.recv_timeout(Duration::from_secs(10)) {
31440 Ok(()) => runner.join().unwrap(),
31441 Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
31442 }
31443 }
31444
31445 #[test]
31446 fn current_thread_timer_shutdown_retires_pending_repoll_state() {
31447 let metrics = Arc::new(RuntimeMetrics::default());
31448 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31449 let backend =
31450 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31451 let drivers = Arc::new(TimerDriverRegistry::default());
31452 let driver = ManualStubDriver::new();
31453 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31454 let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
31455 let mut cx = Context::from_waker(Waker::noop());
31456
31457 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31458 let timer_id = driver.registers.lock().unwrap()[0].0;
31459 assert_eq!(drivers.pending_len(), 1);
31460
31461 executor.host_timers.shutdown();
31462 assert_eq!(
31463 drivers.pending_len(),
31464 0,
31465 "shutdown must retire the registration-repoll index before the Sleep is dropped"
31466 );
31467 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31468 assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
31469 }
31470
31471 #[test]
31472 fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
31473 use std::sync::mpsc;
31474
31475 let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
31476 let backend = controller.backend();
31477 let drivers = Arc::new(TimerDriverRegistry::default());
31478 let driver = ManualStubDriver::new();
31479 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31480 let sleep = make_sleep(
31481 &backend,
31482 &drivers,
31483 Instant::now() + Duration::from_secs(120),
31484 );
31485 drop(backend);
31486
31487 let (polled_tx, polled_rx) = mpsc::channel();
31488 let block_controller = Arc::clone(&controller);
31489 let blocker = std::thread::spawn(move || {
31490 let mut announced = false;
31491 let mut sleep = std::pin::pin!(sleep);
31492 let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
31493 let poll = sleep.as_mut().poll(cx);
31494 if !announced {
31495 announced = true;
31496 polled_tx.send(()).unwrap();
31497 }
31498 poll
31499 }));
31500 block_controller.block_on(future.as_mut());
31501 });
31502 polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
31503
31504 let (shutdown_tx, shutdown_rx) = mpsc::channel();
31505 let shutdown_controller = Arc::clone(&controller);
31506 let shutdown = std::thread::spawn(move || {
31507 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
31508 });
31509 shutdown_rx
31510 .recv_timeout(Duration::from_secs(2))
31511 .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
31512 .unwrap();
31513 shutdown.join().unwrap();
31514 blocker.join().unwrap();
31515 assert_eq!(driver.cancels.lock().unwrap().len(), 1);
31516 }
31517
31518 #[test]
31519 fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
31520 use std::sync::mpsc;
31531
31532 let (done_tx, done_rx) = mpsc::channel();
31533 let runner = std::thread::spawn(move || {
31534 let metrics = Arc::new(RuntimeMetrics::default());
31535 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31539 let backend =
31540 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31541 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31542
31543 let payload = catch_unwind(AssertUnwindSafe(|| {
31544 let mut future = std::pin::pin!(async {
31545 make_sleep(
31546 &backend,
31547 ®istry_with(driver),
31548 Instant::now() + Duration::from_millis(60),
31549 )
31550 .await;
31551 });
31552 executor.block_on(future.as_mut());
31553 }))
31554 .expect_err("a threadless park must self-detect even while a host timer is pending");
31555 let diagnostic = payload
31556 .downcast_ref::<BlockOnDeadlock>()
31557 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31558 assert_eq!(
31559 diagnostic.kind,
31560 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
31561 );
31562 assert_eq!(
31563 diagnostic.park_deadline, None,
31564 "the certain case carries no deadline"
31565 );
31566 let message = diagnostic.to_string();
31567 assert!(
31568 message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
31569 "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
31570 );
31571 let _ = done_tx.send(());
31572 });
31573
31574 match done_rx.recv_timeout(Duration::from_secs(10)) {
31575 Ok(()) => runner.join().unwrap(),
31576 Err(error) => panic!(
31577 "a pending host timer suppressed the threadless certain-deadlock diagnostic -- block_on parked (or completed via a helper-thread fire that cannot exist on the real target) instead of panicking ({error})"
31578 ),
31579 }
31580 }
31581
31582 #[test]
31583 fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
31584 use std::sync::mpsc;
31593
31594 let (done_tx, done_rx) = mpsc::channel();
31595 let runner = std::thread::spawn(move || {
31596 let metrics = Arc::new(RuntimeMetrics::default());
31597 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31598 let backend =
31599 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31600 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31601
31602 let _unpolled = make_sleep(
31605 &backend,
31606 ®istry_with(driver),
31607 Instant::now() + Duration::from_secs(60),
31608 );
31609
31610 let payload = catch_unwind(AssertUnwindSafe(|| {
31611 let mut future = std::pin::pin!(NeverReady);
31612 executor.block_on(future.as_mut());
31613 }))
31614 .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
31615 let diagnostic = payload
31616 .downcast_ref::<BlockOnDeadlock>()
31617 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31618 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
31619 let _ = done_tx.send(());
31620 });
31621
31622 match done_rx.recv_timeout(Duration::from_secs(10)) {
31623 Ok(()) => runner.join().unwrap(),
31624 Err(error) => panic!(
31625 "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
31626 ),
31627 }
31628 }
31629
31630 #[cfg(not(target_family = "wasm"))]
31631 #[test]
31632 fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
31633 use std::sync::mpsc;
31638
31639 let (done_tx, done_rx) = mpsc::channel();
31640 let runner = std::thread::spawn(move || {
31641 let metrics = Arc::new(RuntimeMetrics::default());
31642 let executor = Arc::new(CurrentThreadExecutor::with_detection(
31654 metrics,
31655 false,
31656 Some(Duration::from_secs(2)),
31657 ));
31658 let backend =
31659 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31660 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31661
31662 let started = Instant::now();
31663 let mut future = std::pin::pin!(async {
31664 make_sleep(
31665 &backend,
31666 ®istry_with(driver),
31667 Instant::now() + Duration::from_millis(200),
31668 )
31669 .await;
31670 });
31671 executor.block_on(future.as_mut());
31672 assert!(started.elapsed() >= Duration::from_millis(200));
31673 let _ = done_tx.send(());
31674 });
31675
31676 match done_rx.recv_timeout(Duration::from_secs(30)) {
31677 Ok(()) => runner.join().unwrap(),
31678 Err(error) => {
31679 panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
31680 }
31681 }
31682 }
31683
31684 #[test]
31685 fn host_timer_unrepresentable_grace_remains_live() {
31686 let registry = HostTimerRegistry::default();
31687 let fired = Arc::new(AtomicBool::new(false));
31688 let drivers = Arc::new(TimerDriverRegistry::default());
31689 let repoll = Arc::new(HostTimerRepoll::default());
31690 assert!(
31691 Instant::now().checked_add(Duration::MAX).is_none(),
31692 "Duration::MAX must exercise the unrepresentable-grace path"
31693 );
31694 assert!(
31695 registry.register(
31696 1,
31697 Instant::now(),
31698 futures::task::noop_waker(),
31699 &fired,
31700 &drivers,
31701 &repoll,
31702 ),
31703 "the open registry must accept the timer"
31704 );
31705
31706 assert!(
31707 registry.has_live_wait(Duration::MAX),
31708 "an unrepresentable grace deadline is effectively unbounded"
31709 );
31710 registry.remove(1);
31711 }
31712
31713 #[test]
31714 fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
31715 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
31720 let expected = diagnostic.to_string();
31721 let payload: Box<dyn Any + Send> = Box::new(diagnostic);
31722 assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
31723 }
31724}