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 self.wait_until_idle_retiring(|| false, || {});
2703 }
2704
2705 fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2713 let mut state = self
2714 .state
2715 .lock()
2716 .unwrap_or_else(std::sync::PoisonError::into_inner);
2717 while state.active != 0 {
2718 if has_deferred() {
2719 drop(state);
2720 retire();
2721 state = self
2722 .state
2723 .lock()
2724 .unwrap_or_else(std::sync::PoisonError::into_inner);
2725 continue;
2726 }
2727 state = self
2728 .idle
2729 .wait(state)
2730 .unwrap_or_else(std::sync::PoisonError::into_inner);
2731 }
2732 }
2733
2734 fn notify_idle_waiters(&self) {
2735 let _state = self
2736 .state
2737 .lock()
2738 .unwrap_or_else(std::sync::PoisonError::into_inner);
2739 self.idle.notify_all();
2740 }
2741}
2742
2743struct GenerationWorkGuard {
2744 work: Arc<GenerationWork>,
2745 task_id: Option<u64>,
2746}
2747
2748impl Drop for GenerationWorkGuard {
2749 fn drop(&mut self) {
2750 let mut state = self
2751 .work
2752 .state
2753 .lock()
2754 .unwrap_or_else(std::sync::PoisonError::into_inner);
2755 if let Some(task_id) = self.task_id {
2756 state.abort_handles.remove(&task_id);
2757 }
2758 state.active = state
2759 .active
2760 .checked_sub(1)
2761 .expect("generation work count underflow");
2762 if state.active == 0 {
2763 self.work.idle.notify_all();
2764 }
2765 }
2766}
2767
2768struct RegisteredTaskFuture<F> {
2774 future: ManuallyDrop<F>,
2775 registration: Option<GenerationWorkGuard>,
2776 generation: u64,
2777}
2778
2779impl<F> RegisteredTaskFuture<F> {
2780 fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2781 Self {
2782 future: ManuallyDrop::new(future),
2783 registration: Some(registration),
2784 generation,
2785 }
2786 }
2787}
2788
2789impl<F: Future> Future for RegisteredTaskFuture<F> {
2790 type Output = F::Output;
2791
2792 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2793 let this = unsafe { self.get_unchecked_mut() };
2795 let _generation = RuntimeGenerationGuard::enter(this.generation);
2796 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2799 }
2800}
2801
2802impl<F> Drop for RegisteredTaskFuture<F> {
2803 fn drop(&mut self) {
2804 let _generation = RuntimeGenerationGuard::enter(self.generation);
2805 let _ = catch_unwind_contained(|| {
2806 unsafe { ManuallyDrop::drop(&mut self.future) };
2808 });
2809 drop(self.registration.take());
2812 }
2813}
2814
2815#[cfg(not(target_family = "wasm"))]
2816struct RegisteredBlockingFunction<F> {
2817 function: Option<F>,
2818 registration: Option<GenerationWorkGuard>,
2819 generation: u64,
2820}
2821
2822#[cfg(not(target_family = "wasm"))]
2823impl<F> RegisteredBlockingFunction<F> {
2824 fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2825 Self {
2826 function: Some(function),
2827 registration: Some(registration),
2828 generation,
2829 }
2830 }
2831
2832 fn run<T>(mut self) -> Result<T, JoinError>
2833 where
2834 F: FnOnce() -> T,
2835 {
2836 let _generation = RuntimeGenerationGuard::enter(self.generation);
2837 let result = catch_unwind_join_error(
2838 self
2839 .function
2840 .take()
2841 .expect("blocking function must be available"),
2842 );
2843 drop(self.registration.take());
2844 result
2845 }
2846}
2847
2848#[cfg(not(target_family = "wasm"))]
2849impl<F> Drop for RegisteredBlockingFunction<F> {
2850 fn drop(&mut self) {
2851 let _generation = RuntimeGenerationGuard::enter(self.generation);
2852 if let Some(function) = self.function.take() {
2853 let _ = catch_unwind_contained(|| drop(function));
2854 }
2855 drop(self.registration.take());
2859 }
2860}
2861
2862fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2863 let _active = metrics.runnable_started();
2864 let _ = catch_unwind_contained(|| runnable.run());
2865}
2866
2867#[cfg(not(target_family = "wasm"))]
2868enum RunnableClaim {
2869 Empty,
2870 Run(Runnable),
2871 Cancel(Runnable),
2872}
2873
2874static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2875static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2876
2877fn next_current_thread_dispatch_id() -> u64 {
2878 next_unique_id(
2879 &NEXT_CURRENT_THREAD_DISPATCH_ID,
2880 "CurrentThread dispatch id space exhausted",
2881 )
2882 .checked_add(1)
2883 .expect("CurrentThread dispatch id space exhausted")
2884}
2885
2886struct CurrentThreadBlockingAdmission {
2887 state: Mutex<CurrentThreadBlockingAdmissionState>,
2888}
2889
2890struct CurrentThreadBlockingAdmissionState {
2891 owner: Option<BlockingOwnerToken>,
2892 closed: bool,
2893 #[cfg(not(target_family = "wasm"))]
2894 queue: BlockingQueue,
2895}
2896
2897struct CurrentThreadExecutor {
2898 id: u64,
2899 generation: u64,
2900 stop: Arc<GenerationStop>,
2901 queue: Mutex<CurrentThreadQueue>,
2902 blocking_admission: CurrentThreadBlockingAdmission,
2903 draining: AtomicBool,
2904 scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2905 scheduler_idle: Condvar,
2906 dispatch_pending: AtomicU64,
2910 recovery_dispatch: AtomicU64,
2914 cancelling_failed_dispatch: AtomicBool,
2920 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2921 task_drivers: &'static CurrentThreadTaskDriverRegistry,
2922 metrics: Arc<RuntimeMetrics>,
2923 threadless: bool,
2931 park_deadline: Option<Duration>,
2932 host_timers: Arc<HostTimerRegistry>,
2942 generation_work: ArcSwapOption<GenerationWork>,
2947}
2948
2949impl Drop for CurrentThreadExecutor {
2950 fn drop(&mut self) {
2951 self.retire_rejected_work();
2954 }
2955}
2956
2957struct CurrentThreadQueue {
2958 closed: bool,
2959 runnables: VecDeque<Runnable>,
2960 rejected: Vec<Runnable>,
2975 #[cfg(not(target_family = "wasm"))]
2979 rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
2980}
2981
2982#[derive(Default)]
2983struct CurrentThreadSchedulerState {
2984 active_host_turns: usize,
2985 active_dispatch_calls: usize,
2986 dispatch_publications: Vec<CurrentThreadDispatchPublication>,
2987}
2988
2989struct CurrentThreadDispatchPublication {
2990 dispatch: u64,
2991 republish_requested: bool,
2992}
2993
2994#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2995enum CurrentThreadDispatchRequest {
2996 Accepted,
2997 Coalesced,
2998 Unavailable,
2999}
3000
3001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3002struct CurrentThreadTaskDispatchFailure {
3003 dispatch: u64,
3004 epoch: u64,
3005}
3006
3007#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3008enum CurrentThreadHostDispatchResult {
3009 Accepted,
3010 Unavailable,
3011 Failed(CurrentThreadTaskDispatchFailure),
3012}
3013
3014enum CurrentThreadDispatchPublicationStep {
3015 Republish,
3016 Complete(Option<CurrentThreadHostDispatchResult>),
3017}
3018
3019struct CurrentThreadHostTurn {
3020 executor: Arc<CurrentThreadExecutor>,
3021 draining: bool,
3022 _generation: RuntimeGenerationGuard,
3023}
3024
3025impl CurrentThreadHostTurn {
3026 fn drive(&mut self) {
3027 let executor = Arc::clone(&self.executor);
3028 executor.drive_admitted_host_turn(self);
3029 }
3030
3031 fn release_draining(&mut self) {
3032 if !self.draining {
3033 return;
3034 }
3035 let _scheduler = self
3036 .executor
3037 .scheduler_idle_lock
3038 .lock()
3039 .unwrap_or_else(std::sync::PoisonError::into_inner);
3040 self.executor.draining.store(false, Ordering::Release);
3041 self.draining = false;
3042 }
3043}
3044
3045impl Drop for CurrentThreadHostTurn {
3046 fn drop(&mut self) {
3047 let mut scheduler = self
3048 .executor
3049 .scheduler_idle_lock
3050 .lock()
3051 .unwrap_or_else(std::sync::PoisonError::into_inner);
3052 if self.draining {
3053 self.executor.draining.store(false, Ordering::Release);
3054 self.draining = false;
3055 }
3056 scheduler.active_host_turns = scheduler
3057 .active_host_turns
3058 .checked_sub(1)
3059 .expect("CurrentThread active host-turn count underflow");
3060 self.executor.scheduler_idle.notify_all();
3061 }
3062}
3063
3064#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3071pub struct CurrentThreadTaskCallbackLease {
3072 _host_turn: CurrentThreadHostTurn,
3073 _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3074}
3075
3076impl CurrentThreadTaskCallbackLease {
3077 fn new(host_turn: CurrentThreadHostTurn) -> Self {
3078 Self {
3079 _host_turn: host_turn,
3080 _not_send: std::marker::PhantomData,
3081 }
3082 }
3083}
3084
3085struct CurrentThreadDispatchCall<'a> {
3086 executor: &'a CurrentThreadExecutor,
3087 publication: Option<u64>,
3088 _generation: RuntimeGenerationGuard,
3089}
3090
3091impl CurrentThreadDispatchCall<'_> {
3092 fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3093 let dispatch = self
3094 .publication
3095 .expect("the CurrentThread dispatch call must own a publication");
3096 let index = scheduler
3097 .dispatch_publications
3098 .iter()
3099 .position(|publication| publication.dispatch == dispatch)
3100 .expect("the CurrentThread dispatch publication must remain registered");
3101 scheduler.dispatch_publications.swap_remove(index);
3102 self.publication = None;
3103 }
3104}
3105
3106impl Drop for CurrentThreadDispatchCall<'_> {
3107 fn drop(&mut self) {
3108 let mut scheduler = self
3109 .executor
3110 .scheduler_idle_lock
3111 .lock()
3112 .unwrap_or_else(std::sync::PoisonError::into_inner);
3113 if let Some(dispatch) = self.publication.take()
3114 && let Some(index) = scheduler
3115 .dispatch_publications
3116 .iter()
3117 .position(|publication| publication.dispatch == dispatch)
3118 {
3119 scheduler.dispatch_publications.swap_remove(index);
3120 }
3121 scheduler.active_dispatch_calls = scheduler
3122 .active_dispatch_calls
3123 .checked_sub(1)
3124 .expect("CurrentThread active dispatch-call count underflow");
3125 self.executor.scheduler_idle.notify_all();
3126 }
3127}
3128
3129thread_local! {
3130 static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3141}
3142
3143struct CurrentThreadScheduleScope;
3146
3147impl CurrentThreadScheduleScope {
3148 fn enter() -> Self {
3149 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3150 depth.set(
3151 depth
3152 .get()
3153 .checked_add(1)
3154 .expect("CurrentThread schedule depth exhausted"),
3155 );
3156 });
3157 Self
3158 }
3159
3160 fn is_active() -> bool {
3162 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3163 }
3164}
3165
3166impl Drop for CurrentThreadScheduleScope {
3167 fn drop(&mut self) {
3168 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3169 depth.set(
3170 depth
3171 .get()
3172 .checked_sub(1)
3173 .expect("CurrentThread schedule depth underflow"),
3174 );
3175 });
3176 }
3177}
3178
3179thread_local! {
3180 static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3201 const { std::cell::Cell::new(false) };
3202}
3203
3204struct CurrentThreadRejectedWorkRetirementScope;
3207
3208impl CurrentThreadRejectedWorkRetirementScope {
3209 fn enter() -> Option<Self> {
3213 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3214 if active.replace(true) {
3215 return None;
3219 }
3220 Some(Self)
3221 })
3222 }
3223}
3224
3225impl Drop for CurrentThreadRejectedWorkRetirementScope {
3226 fn drop(&mut self) {
3227 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3228 }
3229}
3230
3231struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3232
3233impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3234 fn drop(&mut self) {
3235 self
3236 .0
3237 .cancelling_failed_dispatch
3238 .store(false, Ordering::Release);
3239 }
3240}
3241
3242enum CurrentThreadDispatchFailureAction<'a> {
3243 None,
3244 Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3245 Publish {
3246 dispatch: u64,
3247 call: CurrentThreadDispatchCall<'a>,
3248 },
3249}
3250
3251struct CurrentThreadDispatchFailureRecovery<'a> {
3252 action: CurrentThreadDispatchFailureAction<'a>,
3253 _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3254}
3255
3256struct CurrentThreadRegistryCancellationGuard<'a> {
3257 registry: &'a CurrentThreadTaskDriverRegistry,
3258 failure: Option<CurrentThreadTaskDispatchFailure>,
3259}
3260
3261impl CurrentThreadRegistryCancellationGuard<'_> {
3262 fn new(
3263 registry: &CurrentThreadTaskDriverRegistry,
3264 failure: CurrentThreadTaskDispatchFailure,
3265 ) -> CurrentThreadRegistryCancellationGuard<'_> {
3266 CurrentThreadRegistryCancellationGuard {
3267 registry,
3268 failure: Some(failure),
3269 }
3270 }
3271}
3272
3273impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3274 fn drop(&mut self) {
3275 if let Some(failure) = self.failure.take() {
3276 let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3277 }
3278 }
3279}
3280
3281struct CurrentThreadBlockingGuard<'a> {
3282 executor: &'a CurrentThreadExecutor,
3283 owner: BlockingOwnerToken,
3284 previous_owner: Option<BlockingOwnerToken>,
3285 owns_lane: bool,
3286}
3287
3288impl CurrentThreadBlockingGuard<'_> {
3289 fn counts_active_lane(&self) -> bool {
3290 self.owns_lane
3291 }
3292}
3293
3294impl Drop for CurrentThreadBlockingGuard<'_> {
3295 fn drop(&mut self) {
3296 BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3297 if !self.owns_lane {
3298 return;
3299 }
3300 let mut state = self
3301 .executor
3302 .blocking_admission
3303 .state
3304 .lock()
3305 .unwrap_or_else(std::sync::PoisonError::into_inner);
3306 debug_assert_eq!(state.owner, Some(self.owner));
3307 state.owner = None;
3308 drop(state);
3309 self.executor.stop.wake_all();
3310 }
3311}
3312
3313impl CurrentThreadExecutor {
3314 const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3317 #[cfg(not(target_family = "wasm"))]
3319 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3320 const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3324
3325 #[cfg(test)]
3329 fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3330 Self::with_detection(metrics, THREADLESS_BUILD, None)
3331 }
3332
3333 #[cfg(test)]
3339 fn with_detection(
3340 metrics: Arc<RuntimeMetrics>,
3341 threadless: bool,
3342 park_deadline: Option<Duration>,
3343 ) -> Self {
3344 Self::with_generation(
3345 metrics,
3346 threadless,
3347 park_deadline,
3348 u64::MAX,
3349 Arc::new(GenerationStop::default()),
3350 )
3351 }
3352
3353 fn with_generation(
3354 metrics: Arc<RuntimeMetrics>,
3355 threadless: bool,
3356 park_deadline: Option<Duration>,
3357 generation: u64,
3358 stop: Arc<GenerationStop>,
3359 ) -> Self {
3360 Self {
3361 id: next_unique_id(
3362 &NEXT_RUNTIME_EXECUTOR_ID,
3363 "async runtime executor id space exhausted",
3364 ),
3365 generation,
3366 stop,
3367 queue: Mutex::new(CurrentThreadQueue {
3368 closed: false,
3369 runnables: VecDeque::new(),
3370 rejected: Vec::new(),
3371 #[cfg(not(target_family = "wasm"))]
3372 rejected_blocking: Vec::new(),
3373 }),
3374 blocking_admission: CurrentThreadBlockingAdmission {
3375 state: Mutex::new(CurrentThreadBlockingAdmissionState {
3376 owner: None,
3377 closed: false,
3378 #[cfg(not(target_family = "wasm"))]
3379 queue: BlockingQueue {
3380 closed: false,
3381 head: None,
3382 tail: None,
3383 jobs: FxHashMap::default(),
3384 },
3385 }),
3386 },
3387 draining: AtomicBool::new(false),
3388 scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3389 scheduler_idle: Condvar::new(),
3390 dispatch_pending: AtomicU64::new(0),
3391 recovery_dispatch: AtomicU64::new(0),
3392 cancelling_failed_dispatch: AtomicBool::new(false),
3393 task_dispatch: dispatch_current_thread_tasks,
3394 task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3395 metrics,
3396 threadless,
3397 park_deadline,
3398 host_timers: Arc::new(HostTimerRegistry::default()),
3399 generation_work: ArcSwapOption::empty(),
3400 }
3401 }
3402
3403 fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3404 self.generation_work.store(Some(Arc::clone(work)));
3405 }
3406
3407 fn has_rejected_work(&self) -> bool {
3410 let queue = self
3411 .queue
3412 .lock()
3413 .unwrap_or_else(std::sync::PoisonError::into_inner);
3414 #[cfg(not(target_family = "wasm"))]
3415 let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3416 #[cfg(target_family = "wasm")]
3417 let has_rejected_blocking = false;
3418 !queue.rejected.is_empty() || has_rejected_blocking
3419 }
3420
3421 fn retire_rejected_work(&self) {
3430 if !self.has_rejected_work() {
3431 return;
3432 }
3433 let _generation = RuntimeGenerationGuard::enter(self.generation);
3434 loop {
3435 #[cfg(not(target_family = "wasm"))]
3436 let (rejected, rejected_blocking) = {
3437 let mut queue = self
3438 .queue
3439 .lock()
3440 .unwrap_or_else(std::sync::PoisonError::into_inner);
3441 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3442 return;
3443 }
3444 (
3445 std::mem::take(&mut queue.rejected),
3446 std::mem::take(&mut queue.rejected_blocking),
3447 )
3448 };
3449 #[cfg(target_family = "wasm")]
3450 let rejected = {
3451 let mut queue = self
3452 .queue
3453 .lock()
3454 .unwrap_or_else(std::sync::PoisonError::into_inner);
3455 if queue.rejected.is_empty() {
3456 return;
3457 }
3458 std::mem::take(&mut queue.rejected)
3459 };
3460 for runnable in rejected {
3461 let _ = catch_unwind_contained(|| drop(runnable));
3462 }
3463 #[cfg(not(target_family = "wasm"))]
3464 for job in rejected_blocking {
3465 let _ = catch_unwind_contained(|| drop(job));
3466 }
3467 }
3468 }
3469
3470 fn retire_rejected_work_off_waker_stack(&self) {
3476 if CurrentThreadScheduleScope::is_active() {
3477 return;
3478 }
3479 self.retire_rejected_work();
3480 }
3481
3482 fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3492 self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3493 }
3494
3495 fn request_rejected_work_retirement(self: &Arc<Self>) {
3596 if !self.has_rejected_work() {
3597 return;
3598 }
3599 let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3600 return;
3605 };
3606 for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3607 if !self.needs_rejected_work_retirement_dispatch() {
3608 return;
3609 }
3610 if matches!(
3611 self.request_drain_with(next_current_thread_dispatch_id),
3612 CurrentThreadDispatchRequest::Unavailable
3613 ) {
3614 return;
3616 }
3617 }
3618 if self.publish_final_rejected_work_retirement()
3619 && self.needs_rejected_work_retirement_dispatch()
3620 {
3621 let _ = self.request_drain_with(next_current_thread_dispatch_id);
3640 }
3641 }
3642
3643 fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3670 if !self.needs_rejected_work_retirement_dispatch() {
3671 return false;
3672 }
3673 let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3674 return false;
3677 };
3678 let dispatch = next_current_thread_dispatch_id();
3679 let dispatch_call = {
3680 let mut scheduler = self.lock_scheduler_for_publication();
3681 self.dispatch_pending.store(dispatch, Ordering::Release);
3682 self
3683 .begin_host_dispatch_publication(&mut scheduler, dispatch)
3684 .expect("a fresh CurrentThread retirement dispatch must own its publication")
3685 };
3686 let result = self.publish_host_dispatch(dispatch, dispatch_call);
3687 {
3688 let _queue = self
3692 .queue
3693 .lock()
3694 .unwrap_or_else(std::sync::PoisonError::into_inner);
3695 drop(cancelling);
3696 }
3697 if let Some(work) = self.generation_work.load_full() {
3700 work.notify_idle_waiters();
3701 }
3702 result == CurrentThreadHostDispatchResult::Accepted
3703 }
3704
3705 fn fresh_owner_token(&self) -> BlockingOwnerToken {
3706 BlockingOwnerToken {
3707 executor_id: self.id,
3708 frame: next_unique_id(
3709 &NEXT_BLOCKING_FRAME,
3710 "blocking owner frame id space exhausted",
3711 ),
3712 }
3713 }
3714
3715 fn enter_blocking_owner(
3716 &self,
3717 owner: BlockingOwnerToken,
3718 owns_lane: bool,
3719 ) -> CurrentThreadBlockingGuard<'_> {
3720 let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3721 CurrentThreadBlockingGuard {
3722 executor: self,
3723 owner,
3724 previous_owner,
3725 owns_lane,
3726 }
3727 }
3728
3729 fn schedule_blocking<F, T>(
3730 self: &Arc<Self>,
3731 function: F,
3732 registration: GenerationWorkGuard,
3733 ) -> JoinHandle<T>
3734 where
3735 F: FnOnce() -> T + Send + 'static,
3736 T: Send + 'static,
3737 {
3738 self.metrics.blocking_scheduled();
3740 let mut function = Some(function);
3741 let mut registration = Some(registration);
3742 let ambient_owner = BLOCKING_OWNER
3743 .with(std::cell::Cell::get)
3744 .filter(|owner| owner.executor_id == self.id);
3745 let mut run_owner = None;
3746 let mut owns_lane = false;
3747 let mut rejected = false;
3748
3749 #[cfg(not(target_family = "wasm"))]
3750 let (sender, receiver) = oneshot::channel();
3751 #[cfg(not(target_family = "wasm"))]
3752 let dependency = BlockingDependency {
3753 job: BlockingJobId(next_unique_id(
3754 &NEXT_BLOCKING_JOB_ID,
3755 "blocking job id space exhausted",
3756 )),
3757 owner: ambient_owner,
3758 };
3759 #[cfg(not(target_family = "wasm"))]
3760 let mut queued = false;
3761
3762 {
3763 let mut state = self
3764 .blocking_admission
3765 .state
3766 .lock()
3767 .unwrap_or_else(std::sync::PoisonError::into_inner);
3768 #[cfg(all(test, not(target_family = "wasm")))]
3769 run_current_thread_blocking_claim_test_hook();
3770 if self.stop.is_stopping()
3774 || state.closed
3775 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3776 {
3777 rejected = true;
3778 } else if ambient_owner.is_some() && state.owner == ambient_owner {
3779 run_owner = ambient_owner;
3780 } else {
3781 #[cfg(not(target_family = "wasm"))]
3782 let queue_empty = state.queue.is_empty();
3783 #[cfg(target_family = "wasm")]
3784 let queue_empty = true;
3785
3786 if state.owner.is_none() && queue_empty {
3787 let owner = self.fresh_owner_token();
3788 state.owner = Some(owner);
3789 run_owner = Some(owner);
3790 owns_lane = true;
3791 } else {
3792 #[cfg(not(target_family = "wasm"))]
3793 {
3794 let function = function
3795 .take()
3796 .expect("blocking function must be available");
3797 let registration = registration
3798 .take()
3799 .expect("blocking registration must be available");
3800 let generation = self.generation;
3801 let function = RegisteredBlockingFunction::new(function, registration, generation);
3802 state.queue.push(QueuedBlocking {
3803 id: dependency.job,
3804 run: Box::new(move || {
3805 let _generation = RuntimeGenerationGuard::enter(generation);
3806 let result = function
3807 .run()
3808 .map(|output| ContainedTaskOutput::new(output, generation));
3809 let _ = sender.send(result);
3810 }),
3811 });
3812 queued = true;
3813 }
3814 #[cfg(target_family = "wasm")]
3815 {
3816 rejected = true;
3820 }
3821 }
3822 }
3823 }
3824
3825 #[cfg(not(target_family = "wasm"))]
3826 if queued {
3827 self.stop.wake_all();
3828 if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3829 self.request_drain();
3830 }
3831 return JoinHandle(JoinHandleInner::Blocking {
3832 receiver,
3833 dependency,
3834 published: PublishedBlockingDependencies::default(),
3835 });
3836 }
3837
3838 if rejected {
3839 let _generation = RuntimeGenerationGuard::enter(self.generation);
3840 let _ = catch_unwind_contained(|| drop(function.take()));
3841 drop(registration.take());
3842 return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3843 message: "the async runtime stopped before the blocking task could start".to_string(),
3844 }))));
3845 }
3846
3847 let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3848 let blocking = self.enter_blocking_owner(owner, owns_lane);
3849 let _registration = registration
3850 .take()
3851 .expect("blocking registration must be available");
3852 let _generation = RuntimeGenerationGuard::enter(self.generation);
3853 let result = {
3854 let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3855 catch_unwind_join_error(
3856 function
3857 .take()
3858 .expect("blocking function must be available"),
3859 )
3860 }
3861 .map(|output| ContainedTaskOutput::new(output, self.generation));
3862 drop(blocking);
3863 #[cfg(not(target_family = "wasm"))]
3864 if owns_lane && !self.draining.load(Ordering::Acquire) {
3865 self.service_queued_blocking_work();
3869 }
3870 JoinHandle(JoinHandleInner::Ready(Some(result)))
3871 }
3872
3873 #[cfg(not(target_family = "wasm"))]
3874 fn take_queued_blocking(
3875 &self,
3876 ) -> Option<(
3877 Box<dyn FnOnce() + Send + 'static>,
3878 CurrentThreadBlockingGuard<'_>,
3879 )> {
3880 let (job, owner) = {
3881 let mut state = self
3882 .blocking_admission
3883 .state
3884 .lock()
3885 .unwrap_or_else(std::sync::PoisonError::into_inner);
3886 #[cfg(all(test, not(target_family = "wasm")))]
3887 run_current_thread_blocking_claim_test_hook();
3888 if self.stop.is_stopping()
3889 || state.closed
3890 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3891 || state.owner.is_some()
3892 {
3893 return None;
3894 }
3895 let job = state.queue.pop_front()?;
3896 let owner = self.fresh_owner_token();
3897 state.owner = Some(owner);
3898 (job, owner)
3899 };
3900 Some((job, self.enter_blocking_owner(owner, true)))
3901 }
3902
3903 #[cfg(not(target_family = "wasm"))]
3904 fn run_one_blocking(&self) -> bool {
3905 let Some((job, blocking)) = self.take_queued_blocking() else {
3906 return false;
3907 };
3908 let _generation = RuntimeGenerationGuard::enter(self.generation);
3909 {
3910 let _task = self.metrics.blocking_started(true);
3911 let _ = catch_unwind_contained(job);
3912 }
3913 drop(blocking);
3914 true
3915 }
3916
3917 #[cfg(not(target_family = "wasm"))]
3918 fn service_queued_blocking_work(&self) {
3919 while self.run_one_blocking() {}
3920 }
3921
3922 #[cfg(not(target_family = "wasm"))]
3923 fn has_serviceable_blocking_work(&self) -> bool {
3924 let state = self
3925 .blocking_admission
3926 .state
3927 .lock()
3928 .unwrap_or_else(std::sync::PoisonError::into_inner);
3929 !state.closed && state.owner.is_none() && !state.queue.is_empty()
3930 }
3931
3932 #[cfg(not(target_family = "wasm"))]
3933 fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3934 let Some(ambient_owner) = BLOCKING_OWNER
3935 .with(std::cell::Cell::get)
3936 .filter(|owner| owner.executor_id == self.id)
3937 else {
3938 return false;
3939 };
3940 let Some(current) = dependency.select_for_owner(ambient_owner) else {
3941 return false;
3942 };
3943 let job = {
3944 let mut state = self
3945 .blocking_admission
3946 .state
3947 .lock()
3948 .unwrap_or_else(std::sync::PoisonError::into_inner);
3949 #[cfg(all(test, not(target_family = "wasm")))]
3950 run_current_thread_blocking_claim_test_hook();
3951 if self.stop.is_stopping()
3952 || state.closed
3953 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3954 || state.owner != Some(ambient_owner)
3955 {
3956 return false;
3957 }
3958 if dependency.try_claim(¤t) {
3959 state.queue.remove(current.publication.dependency.job)
3960 } else {
3961 None
3962 }
3963 };
3964 let Some(job) = job else {
3965 return false;
3966 };
3967 let _generation = RuntimeGenerationGuard::enter(self.generation);
3968 let _task = self.metrics.blocking_started(false);
3969 let _ = catch_unwind_contained(job);
3970 true
3971 }
3972
3973 fn close_blocking_admission(&self) {
3974 #[cfg(not(target_family = "wasm"))]
3975 let queued = {
3976 let mut state = self
3977 .blocking_admission
3978 .state
3979 .lock()
3980 .unwrap_or_else(std::sync::PoisonError::into_inner);
3981 state.closed = true;
3982 state.queue.closed = true;
3983 state.queue.take_all()
3984 };
3985 #[cfg(target_family = "wasm")]
3986 {
3987 let mut state = self
3988 .blocking_admission
3989 .state
3990 .lock()
3991 .unwrap_or_else(std::sync::PoisonError::into_inner);
3992 state.closed = true;
3993 }
3994 #[cfg(not(target_family = "wasm"))]
3995 for job in queued {
3996 let _ = catch_unwind_contained(|| drop(job));
3997 }
3998 }
3999
4000 #[cfg(test)]
4001 fn with_task_dispatch(
4002 metrics: Arc<RuntimeMetrics>,
4003 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4004 ) -> Self {
4005 Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4006 }
4007
4008 #[cfg(test)]
4009 fn with_task_dispatch_registry(
4010 metrics: Arc<RuntimeMetrics>,
4011 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4012 task_drivers: &'static CurrentThreadTaskDriverRegistry,
4013 ) -> Self {
4014 let mut executor = Self::new(metrics);
4015 executor.task_dispatch = task_dispatch;
4016 executor.task_drivers = task_drivers;
4017 executor
4018 }
4019
4020 fn schedule(self: &Arc<Self>, runnable: Runnable) {
4025 let _scope = CurrentThreadScheduleScope::enter();
4026 self.schedule_runnable(runnable);
4027 }
4028
4029 fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4036 self.schedule_runnable(runnable);
4037 }
4038
4039 fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4040 self.metrics.runnable_scheduled();
4041 {
4042 let mut queue = self
4043 .queue
4044 .lock()
4045 .unwrap_or_else(std::sync::PoisonError::into_inner);
4046 if queue.closed {
4051 self
4058 .metrics
4059 .queued_runnables
4060 .fetch_sub(1, Ordering::Relaxed);
4061 queue.rejected.push(runnable);
4062 drop(queue);
4063 if let Some(work) = self.generation_work.load_full() {
4064 work.notify_idle_waiters();
4065 }
4066 return;
4067 }
4068 if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4069 self
4073 .metrics
4074 .queued_runnables
4075 .fetch_sub(1, Ordering::Relaxed);
4076 queue.rejected.push(runnable);
4077 return;
4078 }
4079 queue.runnables.push_back(runnable);
4080 }
4081 self.stop.wake_all();
4085 if !self.draining.load(Ordering::Acquire) {
4086 self.request_drain();
4087 }
4088 }
4089
4090 fn request_drain(self: &Arc<Self>) {
4099 let _ = self.request_drain_with(next_current_thread_dispatch_id);
4100 }
4101
4102 fn lock_scheduler_for_publication(
4103 &self,
4104 ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
4105 #[cfg(all(test, not(target_family = "wasm")))]
4106 match self.scheduler_idle_lock.try_lock() {
4107 Ok(scheduler) => scheduler,
4108 Err(std::sync::TryLockError::WouldBlock) => {
4109 run_current_thread_dispatch_publication_blocked_test_hook();
4110 self
4111 .scheduler_idle_lock
4112 .lock()
4113 .unwrap_or_else(std::sync::PoisonError::into_inner)
4114 }
4115 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4116 }
4117 #[cfg(any(not(test), target_family = "wasm"))]
4118 self
4119 .scheduler_idle_lock
4120 .lock()
4121 .unwrap_or_else(std::sync::PoisonError::into_inner)
4122 }
4123
4124 fn request_drain_with(
4125 self: &Arc<Self>,
4126 next_dispatch: impl FnOnce() -> u64,
4127 ) -> CurrentThreadDispatchRequest {
4128 let mut scheduler = self.lock_scheduler_for_publication();
4129 if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4130 return CurrentThreadDispatchRequest::Unavailable;
4131 }
4132 if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4133 return CurrentThreadDispatchRequest::Coalesced;
4134 }
4135 let dispatch = next_dispatch();
4136 if self
4137 .dispatch_pending
4138 .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4139 .is_err()
4140 {
4141 return CurrentThreadDispatchRequest::Coalesced;
4142 }
4143 let dispatch_call = self
4144 .begin_host_dispatch_publication(&mut scheduler, dispatch)
4145 .expect("a fresh CurrentThread dispatch must own its publication");
4146 drop(scheduler);
4147 match self.publish_host_dispatch(dispatch, dispatch_call) {
4148 CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4149 CurrentThreadDispatchRequest::Accepted
4150 }
4151 CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4152 }
4153 }
4154
4155 fn claim_dispatch_call<'a>(
4156 &'a self,
4157 scheduler: &mut CurrentThreadSchedulerState,
4158 ) -> CurrentThreadDispatchCall<'a> {
4159 let generation = RuntimeGenerationGuard::enter(self.generation);
4160 let active_dispatch_calls = scheduler
4161 .active_dispatch_calls
4162 .checked_add(1)
4163 .expect("CurrentThread active dispatch-call count exhausted");
4164 scheduler.active_dispatch_calls = active_dispatch_calls;
4165 CurrentThreadDispatchCall {
4166 executor: self,
4167 publication: None,
4168 _generation: generation,
4169 }
4170 }
4171
4172 fn begin_host_dispatch_publication<'a>(
4173 &'a self,
4174 scheduler: &mut CurrentThreadSchedulerState,
4175 dispatch: u64,
4176 ) -> Option<CurrentThreadDispatchCall<'a>> {
4177 if let Some(publication) = scheduler
4179 .dispatch_publications
4180 .iter_mut()
4181 .find(|publication| publication.dispatch == dispatch)
4182 {
4183 publication.republish_requested = true;
4184 return None;
4185 }
4186 let mut dispatch_call = self.claim_dispatch_call(scheduler);
4187 scheduler
4188 .dispatch_publications
4189 .push(CurrentThreadDispatchPublication {
4190 dispatch,
4191 republish_requested: false,
4192 });
4193 dispatch_call.publication = Some(dispatch);
4194 Some(dispatch_call)
4195 }
4196
4197 fn publish_host_dispatch(
4198 self: &Arc<Self>,
4199 dispatch: u64,
4200 mut dispatch_call: CurrentThreadDispatchCall<'_>,
4201 ) -> CurrentThreadHostDispatchResult {
4202 let mut panic_payload = RetainedPanicPayload::default();
4203 loop {
4204 let result = match catch_unwind(AssertUnwindSafe(|| {
4205 #[cfg(all(test, not(target_family = "wasm")))]
4206 run_current_thread_dispatch_call_test_hook();
4207 let result = (self.task_dispatch)(dispatch);
4208 #[cfg(all(test, not(target_family = "wasm")))]
4209 run_current_thread_dispatch_returned_test_hook();
4210 result
4211 })) {
4212 Ok(result) => Some(result),
4213 Err(payload) => {
4214 panic_payload.capture(payload);
4215 None
4216 }
4217 };
4218
4219 let step = catch_unwind(AssertUnwindSafe(|| {
4220 let (republish, cancelling, failure, result) = {
4221 let mut scheduler = self
4222 .scheduler_idle_lock
4223 .lock()
4224 .unwrap_or_else(std::sync::PoisonError::into_inner);
4225 let index = scheduler
4226 .dispatch_publications
4227 .iter()
4228 .position(|publication| publication.dispatch == dispatch)
4229 .expect("the CurrentThread dispatch publication must remain registered");
4230 let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4231 let republish = dispatch_remains_pending
4232 && !self.stop.is_stopping()
4233 && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4234 && scheduler.dispatch_publications[index].republish_requested;
4235 if republish {
4236 if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4237 let _ = self.task_drivers.rearm_failed_publication(failure);
4238 }
4239 scheduler.dispatch_publications[index].republish_requested = false;
4240 (true, None, None, result)
4241 } else {
4242 dispatch_call.finish_publication(&mut scheduler);
4243 let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4244 #[cfg(all(test, not(target_family = "wasm")))]
4245 run_current_thread_failed_publication_retired_test_hook();
4246 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4247 } else {
4248 None
4249 };
4250 let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4251 && dispatch_remains_pending)
4252 .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4253 .flatten();
4254 let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4255 && !dispatch_remains_pending
4256 {
4257 Some(CurrentThreadHostDispatchResult::Accepted)
4260 } else {
4261 result
4262 };
4263 (false, cancelling, failure, result)
4264 }
4265 };
4266
4267 if republish {
4268 return CurrentThreadDispatchPublicationStep::Republish;
4269 }
4270 if let Some(cancelling) = cancelling {
4271 self.cancel_work_after_host_dispatch_failure(cancelling);
4272 }
4273 if let Some(failure) = failure {
4274 self.run_host_dispatch_failure(failure.action);
4275 }
4276 CurrentThreadDispatchPublicationStep::Complete(result)
4277 }));
4278
4279 match step {
4280 Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4281 Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4282 if panic_payload.is_some() {
4283 panic_payload.resume();
4284 }
4285 return result
4286 .expect("a CurrentThread host dispatch without a result must resume its panic");
4287 }
4288 Err(payload) => {
4289 panic_payload.capture(payload);
4290 panic_payload.resume();
4291 }
4292 }
4293 }
4294 }
4295
4296 fn claim_host_turn(
4297 self: &Arc<Self>,
4298 scheduler: &mut CurrentThreadSchedulerState,
4299 ) -> CurrentThreadHostTurn {
4300 scheduler.active_host_turns = scheduler
4301 .active_host_turns
4302 .checked_add(1)
4303 .expect("CurrentThread active host-turn count exhausted");
4304 self.draining.store(true, Ordering::Release);
4305 CurrentThreadHostTurn {
4306 executor: Arc::clone(self),
4307 draining: true,
4308 _generation: RuntimeGenerationGuard::enter(self.generation),
4309 }
4310 }
4311
4312 fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4313 let mut scheduler = self
4314 .scheduler_idle_lock
4315 .lock()
4316 .unwrap_or_else(std::sync::PoisonError::into_inner);
4317 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4318 return None;
4319 }
4320 if dispatch == 0
4321 || self
4322 .dispatch_pending
4323 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4324 .is_err()
4325 {
4326 return None;
4327 }
4328 #[cfg(all(test, not(target_family = "wasm")))]
4329 run_current_thread_dispatch_claim_test_hook();
4330 let _ =
4331 self
4332 .recovery_dispatch
4333 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4334 Some(self.claim_host_turn(&mut scheduler))
4335 }
4336
4337 #[cfg(test)]
4338 fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
4339 let mut scheduler = self
4340 .scheduler_idle_lock
4341 .lock()
4342 .unwrap_or_else(std::sync::PoisonError::into_inner);
4343 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4344 return None;
4345 }
4346 Some(self.claim_host_turn(&mut scheduler))
4347 }
4348
4349 #[cfg(test)]
4350 fn drive_host_turn(self: &Arc<Self>) {
4351 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4352 let host_turn = if dispatch == 0 {
4353 self.try_admit_host_turn_without_dispatch()
4354 } else {
4355 self.try_admit_host_turn(dispatch)
4356 };
4357 if let Some(mut host_turn) = host_turn {
4358 host_turn.drive();
4359 }
4360 }
4361
4362 #[cfg(test)]
4363 fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4364 if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4365 host_turn.drive();
4366 }
4367 }
4368
4369 fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4373 self.retire_rejected_work_off_waker_stack();
4376 #[cfg(not(target_family = "wasm"))]
4377 let mut runnable_streak = 0usize;
4378 for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4379 #[cfg(not(target_family = "wasm"))]
4380 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4381 runnable_streak = 0;
4382 if self.run_one_blocking() {
4383 continue;
4384 }
4385 }
4386 if self.drain_one() {
4387 #[cfg(not(target_family = "wasm"))]
4388 {
4389 runnable_streak += 1;
4390 }
4391 continue;
4392 }
4393 #[cfg(not(target_family = "wasm"))]
4394 if self.run_one_blocking() {
4395 runnable_streak = 0;
4396 continue;
4397 }
4398 break;
4399 }
4400 host_turn.release_draining();
4405 self.request_drain_if_work_remains();
4406 }
4407
4408 fn request_drain_if_queued(self: &Arc<Self>) {
4409 self.retire_rejected_work_off_waker_stack();
4410 if !self.has_serviceable_work() {
4411 return;
4412 }
4413 let mut scheduler = self.lock_scheduler_for_publication();
4414 if self.stop.is_stopping()
4415 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4416 || self.draining.load(Ordering::Acquire)
4417 {
4418 return;
4419 }
4420 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4421 if dispatch == 0 {
4422 drop(scheduler);
4423 self.request_drain_if_work_remains();
4424 return;
4425 }
4426
4427 let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4431 return;
4432 };
4433 drop(scheduler);
4434 let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4435 }
4436
4437 fn retire_unavailable_host_dispatch_locked(
4438 &self,
4439 dispatch: u64,
4440 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4441 if self
4442 .dispatch_pending
4443 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4444 .is_err()
4445 {
4446 return None;
4447 }
4448 if self
4449 .recovery_dispatch
4450 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4451 .is_ok()
4452 {
4453 self.begin_failed_dispatch_cancellation()
4454 } else {
4455 None
4456 }
4457 }
4458
4459 fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4460 let failure = {
4462 let mut scheduler = self
4463 .scheduler_idle_lock
4464 .lock()
4465 .unwrap_or_else(std::sync::PoisonError::into_inner);
4466 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4467 };
4468 if let Some(failure) = failure {
4469 self.run_host_dispatch_failure(failure.action);
4470 }
4471 self.retire_rejected_work_off_waker_stack();
4472 }
4473
4474 fn begin_host_dispatch_failure_locked<'a>(
4475 &'a self,
4476 scheduler: &mut CurrentThreadSchedulerState,
4477 failure: CurrentThreadTaskDispatchFailure,
4478 ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4479 if !self.task_drivers.is_current_failure(failure) {
4480 return None;
4481 }
4482 let registry_cancellation =
4483 CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4484 let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4485 Some(CurrentThreadDispatchFailureRecovery {
4486 action,
4487 _registry_cancellation: registry_cancellation,
4488 })
4489 }
4490
4491 fn begin_host_dispatch_failure_transition_locked<'a>(
4492 &'a self,
4493 scheduler: &mut CurrentThreadSchedulerState,
4494 dispatch: u64,
4495 ) -> CurrentThreadDispatchFailureAction<'a> {
4496 if dispatch == 0
4497 || self
4498 .dispatch_pending
4499 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4500 .is_err()
4501 {
4502 return CurrentThreadDispatchFailureAction::None;
4503 }
4504 #[cfg(all(test, not(target_family = "wasm")))]
4505 run_current_thread_dispatch_recovery_test_hook();
4506
4507 if self
4508 .recovery_dispatch
4509 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4510 .is_ok()
4511 {
4512 return self.begin_failed_dispatch_cancellation().map_or(
4516 CurrentThreadDispatchFailureAction::None,
4517 CurrentThreadDispatchFailureAction::Cancel,
4518 );
4519 }
4520
4521 if self.stop.is_stopping()
4522 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4523 || self.draining.load(Ordering::Acquire)
4524 || !self.has_serviceable_work()
4525 {
4526 return CurrentThreadDispatchFailureAction::None;
4527 }
4528
4529 let replacement = next_current_thread_dispatch_id();
4533 self.recovery_dispatch.store(replacement, Ordering::Release);
4534 self.dispatch_pending.store(replacement, Ordering::Release);
4535 let dispatch_call = self
4536 .begin_host_dispatch_publication(scheduler, replacement)
4537 .expect("a reserved CurrentThread replacement must own its publication");
4538 CurrentThreadDispatchFailureAction::Publish {
4539 dispatch: replacement,
4540 call: dispatch_call,
4541 }
4542 }
4543
4544 #[cfg(test)]
4545 fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4546 let failure = {
4547 let mut scheduler = self
4548 .scheduler_idle_lock
4549 .lock()
4550 .unwrap_or_else(std::sync::PoisonError::into_inner);
4551 self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4552 };
4553 self.run_host_dispatch_failure(failure);
4554 }
4555
4556 fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4557 match failure {
4558 CurrentThreadDispatchFailureAction::None => {}
4559 CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4560 self.cancel_work_after_host_dispatch_failure(cancelling);
4561 }
4562 CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4563 let _ = self.publish_host_dispatch(dispatch, call);
4564 }
4565 }
4566 }
4567
4568 fn has_serviceable_work(&self) -> bool {
4569 let has_queued_runnable = {
4570 let queue = self
4571 .queue
4572 .lock()
4573 .unwrap_or_else(std::sync::PoisonError::into_inner);
4574 !queue.closed && !queue.runnables.is_empty()
4575 };
4576 #[cfg(not(target_family = "wasm"))]
4577 let has_queued_blocking = self.has_serviceable_blocking_work();
4578 #[cfg(target_family = "wasm")]
4579 let has_queued_blocking = false;
4580 has_queued_runnable || has_queued_blocking
4581 }
4582
4583 fn request_drain_if_work_remains(self: &Arc<Self>) {
4584 if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4585 self.request_drain();
4586 }
4587 }
4588
4589 fn begin_failed_dispatch_cancellation(
4590 &self,
4591 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4592 if self
4593 .cancelling_failed_dispatch
4594 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4595 .is_err()
4596 {
4597 return None;
4598 }
4599 self.dispatch_pending.store(0, Ordering::Release);
4600 self.recovery_dispatch.store(0, Ordering::Release);
4601 Some(CurrentThreadDispatchCancellationGuard(self))
4602 }
4603
4604 fn cancel_work_after_host_dispatch_failure(
4643 self: &Arc<Self>,
4644 cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4645 ) {
4646 let mut cancelling = Some(cancelling);
4647 #[cfg(not(target_family = "wasm"))]
4650 let queued_blocking = {
4651 let mut state = self
4652 .blocking_admission
4653 .state
4654 .lock()
4655 .unwrap_or_else(std::sync::PoisonError::into_inner);
4656 state.queue.take_all()
4657 };
4658 {
4659 let mut queue = self
4660 .queue
4661 .lock()
4662 .unwrap_or_else(std::sync::PoisonError::into_inner);
4663 let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4664 self
4666 .metrics
4667 .queued_runnables
4668 .fetch_sub(moved, Ordering::Relaxed);
4669 let runnables = std::mem::take(&mut queue.runnables);
4670 queue.rejected.extend(runnables);
4671 #[cfg(not(target_family = "wasm"))]
4672 queue.rejected_blocking.extend(queued_blocking);
4673 if CurrentThreadScheduleScope::is_active() {
4674 drop(cancelling.take());
4679 drop(queue);
4680 if let Some(work) = self.generation_work.load_full() {
4683 work.notify_idle_waiters();
4684 }
4685 self.request_rejected_work_retirement();
4689 return;
4690 }
4691 }
4692
4693 let _generation = RuntimeGenerationGuard::enter(self.generation);
4699 loop {
4700 #[cfg(not(target_family = "wasm"))]
4701 let (rejected, rejected_blocking) = {
4702 let mut queue = self
4703 .queue
4704 .lock()
4705 .unwrap_or_else(std::sync::PoisonError::into_inner);
4706 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4707 drop(cancelling.take());
4708 break;
4709 }
4710 (
4711 std::mem::take(&mut queue.rejected),
4712 std::mem::take(&mut queue.rejected_blocking),
4713 )
4714 };
4715 #[cfg(target_family = "wasm")]
4716 let rejected = {
4717 let mut queue = self
4718 .queue
4719 .lock()
4720 .unwrap_or_else(std::sync::PoisonError::into_inner);
4721 if queue.rejected.is_empty() {
4722 drop(cancelling.take());
4723 break;
4724 }
4725 std::mem::take(&mut queue.rejected)
4726 };
4727 for runnable in rejected {
4728 let _ = catch_unwind_contained(|| drop(runnable));
4729 }
4730 #[cfg(not(target_family = "wasm"))]
4731 for job in rejected_blocking {
4732 let _ = catch_unwind_contained(|| drop(job));
4733 }
4734 }
4735 }
4736
4737 fn take_runnable(&self) -> Option<Runnable> {
4738 let scheduler = self
4743 .scheduler_idle_lock
4744 .lock()
4745 .unwrap_or_else(std::sync::PoisonError::into_inner);
4746 #[cfg(all(test, not(target_family = "wasm")))]
4747 run_current_thread_runnable_claim_test_hook();
4748 let stop_publication = self.stop.publication_guard();
4749 if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4750 return None;
4751 }
4752 let runnable = {
4753 let mut queue = self
4754 .queue
4755 .lock()
4756 .unwrap_or_else(std::sync::PoisonError::into_inner);
4757 if queue.closed {
4758 None
4759 } else {
4760 queue.runnables.pop_front()
4761 }
4762 };
4763 drop(stop_publication);
4764 drop(scheduler);
4765 runnable
4766 }
4767
4768 fn drain_one(&self) -> bool {
4769 let runnable = self.take_runnable();
4770 if let Some(runnable) = runnable {
4771 #[cfg(not(target_family = "wasm"))]
4772 let _non_owner = BlockingOwnerGuard::enter(None);
4773 run_runnable(&self.metrics, runnable);
4774 true
4775 } else {
4776 false
4777 }
4778 }
4779
4780 fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4802 let parker = Arc::new(DriverParker::default());
4803 let _stop_registration = self.stop.register(&parker);
4804 #[cfg(not(target_family = "wasm"))]
4805 let dependency = BlockOnDependencyGuard::enter(self.generation);
4806 let waker = std::task::Waker::from(Arc::clone(&parker));
4807 let mut cx = Context::from_waker(&waker);
4808 #[cfg(not(target_family = "wasm"))]
4809 let mut runnable_streak = 0usize;
4810 loop {
4811 if self.stop.is_stopping() {
4812 return BlockOnOutcome::Stopped;
4813 }
4814 #[cfg(not(target_family = "wasm"))]
4815 dependency.clear();
4816 if future.as_mut().poll(&mut cx).is_ready() {
4817 return BlockOnOutcome::Completed;
4818 }
4819 if self.stop.is_stopping() {
4823 return BlockOnOutcome::Stopped;
4824 }
4825 #[cfg(not(target_family = "wasm"))]
4826 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4827 runnable_streak = 0;
4828 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4829 continue;
4830 }
4831 }
4832 if self.drain_one() {
4833 #[cfg(not(target_family = "wasm"))]
4834 {
4835 runnable_streak += 1;
4836 }
4837 continue;
4838 }
4839 #[cfg(not(target_family = "wasm"))]
4840 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4841 runnable_streak = 0;
4842 continue;
4843 }
4844 #[cfg(not(target_family = "wasm"))]
4845 {
4846 runnable_streak = 0;
4847 }
4848 if parker.consume_permit() {
4852 continue;
4853 }
4854 if self.threadless {
4866 if self.host_timers.has_pending() {
4867 std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
4868 }
4869 std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
4870 }
4871 match self.park_deadline {
4872 None => parker.park(),
4876 Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
4877 DeadlineParkOutcome::Woken => {}
4878 DeadlineParkOutcome::Expired(armed) => {
4879 if parker.consume_permit()
4894 || self.metrics.progress_fingerprint() != armed
4895 || self.host_timers.has_live_wait(deadline)
4896 {
4897 continue;
4898 }
4899 std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
4900 }
4901 },
4902 }
4903 }
4904 }
4905
4906 fn begin_shutdown(&self) {
4907 let _generation = RuntimeGenerationGuard::enter(self.generation);
4908 self.stop.begin_shutdown();
4909 self.close_blocking_admission();
4910 let (queued, rejected) = {
4911 let mut queue = self
4912 .queue
4913 .lock()
4914 .unwrap_or_else(std::sync::PoisonError::into_inner);
4915 queue.closed = true;
4916 #[cfg(not(target_family = "wasm"))]
4917 let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
4918 let rejected = std::mem::take(&mut queue.rejected);
4919 #[cfg(not(target_family = "wasm"))]
4920 let rejected = (rejected, rejected_blocking);
4921 (std::mem::take(&mut queue.runnables), rejected)
4922 };
4923 self.dispatch_pending.store(0, Ordering::Release);
4924 self.recovery_dispatch.store(0, Ordering::Release);
4925 self.task_drivers.retire_for_shutdown();
4926 for runnable in queued {
4927 self
4928 .metrics
4929 .queued_runnables
4930 .fetch_sub(1, Ordering::Relaxed);
4931 let _ = catch_unwind_contained(|| drop(runnable));
4934 }
4935 #[cfg(not(target_family = "wasm"))]
4938 let (rejected, rejected_blocking) = rejected;
4939 for runnable in rejected {
4940 let _ = catch_unwind_contained(|| drop(runnable));
4941 }
4942 #[cfg(not(target_family = "wasm"))]
4943 for job in rejected_blocking {
4944 let _ = catch_unwind_contained(|| drop(job));
4945 }
4946 self.host_timers.shutdown();
4947 }
4948
4949 fn wait_until_scheduler_idle(&self) {
4950 self.retire_rejected_work();
4953 let mut idle = self
4954 .scheduler_idle_lock
4955 .lock()
4956 .unwrap_or_else(std::sync::PoisonError::into_inner);
4957 while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
4958 idle = self
4959 .scheduler_idle
4960 .wait(idle)
4961 .unwrap_or_else(std::sync::PoisonError::into_inner);
4962 }
4963 debug_assert!(
4964 idle.dispatch_publications.is_empty(),
4965 "an idle CurrentThread scheduler must not retain dispatch publications"
4966 );
4967 }
4968}
4969
4970#[cfg(not(target_family = "wasm"))]
4983thread_local! {
4984 static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4985 static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4989}
4990
4991#[cfg(not(target_family = "wasm"))]
4997#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4998struct SchedulerDriverRole {
4999 executor_id: u64,
5000 can_run_blocking: bool,
5001}
5002
5003#[cfg(not(target_family = "wasm"))]
5004thread_local! {
5005 static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5006 const { std::cell::Cell::new(None) };
5007}
5008
5009#[cfg(not(target_family = "wasm"))]
5026thread_local! {
5027 static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5028 const { std::cell::Cell::new(None) };
5029}
5030
5031#[cfg(not(target_family = "wasm"))]
5032struct OnPoolWorkerGuard {
5033 previous_worker: Option<u64>,
5034 previous_driver: Option<SchedulerDriverRole>,
5035}
5036
5037#[cfg(not(target_family = "wasm"))]
5038impl OnPoolWorkerGuard {
5039 fn enter(id: u64) -> Self {
5043 Self::enter_with_role(id, true)
5044 }
5045
5046 fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5047 Self {
5048 previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5049 previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5050 flag.replace(Some(SchedulerDriverRole {
5051 executor_id: id,
5052 can_run_blocking,
5053 }))
5054 }),
5055 }
5056 }
5057}
5058
5059#[cfg(not(target_family = "wasm"))]
5060impl Drop for OnPoolWorkerGuard {
5061 fn drop(&mut self) {
5062 IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5063 ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5064 }
5065}
5066
5067#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5080struct BlockingOwnerToken {
5081 executor_id: u64,
5082 frame: u64,
5083}
5084
5085#[cfg(not(target_family = "wasm"))]
5086enum OwnedBlockingOutcome {
5087 Ineligible,
5088 OwnerBusy(BlockingOwnerToken),
5089 ReservationReleased {
5090 owner: BlockingOwnerToken,
5091 ran: bool,
5092 },
5093}
5094
5095static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5098#[cfg(not(target_family = "wasm"))]
5099static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5100
5101thread_local! {
5104 static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5105 const { std::cell::Cell::new(None) };
5106}
5107
5108#[cfg(not(target_family = "wasm"))]
5115struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5116
5117#[cfg(not(target_family = "wasm"))]
5118impl BlockingOwnerGuard {
5119 fn enter(token: Option<BlockingOwnerToken>) -> Self {
5120 Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5121 }
5122}
5123
5124#[cfg(not(target_family = "wasm"))]
5125impl Drop for BlockingOwnerGuard {
5126 fn drop(&mut self) {
5127 BLOCKING_OWNER.with(|cell| cell.set(self.0));
5128 }
5129}
5130
5131#[cfg(not(target_family = "wasm"))]
5132#[derive(Default)]
5133struct ActiveBlockingOwners {
5134 owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5138}
5139
5140#[cfg(not(target_family = "wasm"))]
5141impl ActiveBlockingOwners {
5142 fn register(&self, owner: BlockingOwnerToken) {
5143 let previous = self
5144 .owners
5145 .lock()
5146 .unwrap_or_else(std::sync::PoisonError::into_inner)
5147 .insert(owner, None);
5148 debug_assert!(previous.is_none());
5149 }
5150
5151 fn unregister(&self, owner: BlockingOwnerToken) {
5152 let removed = self
5153 .owners
5154 .lock()
5155 .unwrap_or_else(std::sync::PoisonError::into_inner)
5156 .remove(&owner);
5157 debug_assert!(removed.is_some());
5158 }
5159
5160 fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5161 let mut owners = self
5162 .owners
5163 .lock()
5164 .unwrap_or_else(std::sync::PoisonError::into_inner);
5165 let reservation = owners.get_mut(&owner)?;
5166 if reservation.is_some() {
5167 return None;
5168 }
5169 let id = next_unique_id(
5170 &NEXT_OWNER_RESERVATION,
5171 "blocking owner reservation id space exhausted",
5172 );
5173 *reservation = Some(id);
5174 Some(id)
5175 }
5176
5177 fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5178 let mut owners = self
5179 .owners
5180 .lock()
5181 .unwrap_or_else(std::sync::PoisonError::into_inner);
5182 if let Some(current) = owners.get_mut(&owner)
5183 && *current == Some(reservation)
5184 {
5185 *current = None;
5186 }
5187 }
5188
5189 fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5190 self
5191 .owners
5192 .lock()
5193 .unwrap_or_else(std::sync::PoisonError::into_inner)
5194 .get(&owner)
5195 .is_some_and(Option::is_none)
5196 }
5197}
5198
5199#[cfg(not(target_family = "wasm"))]
5200struct ActiveBlockingOwnerGuard<'a> {
5201 owners: &'a ActiveBlockingOwners,
5202 owner: BlockingOwnerToken,
5203}
5204
5205#[cfg(not(target_family = "wasm"))]
5206impl<'a> ActiveBlockingOwnerGuard<'a> {
5207 fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5208 owners.register(owner);
5209 Self { owners, owner }
5210 }
5211}
5212
5213#[cfg(not(target_family = "wasm"))]
5214impl Drop for ActiveBlockingOwnerGuard<'_> {
5215 fn drop(&mut self) {
5216 self.owners.unregister(self.owner);
5217 }
5218}
5219
5220#[cfg(not(target_family = "wasm"))]
5221struct OwnerLaneReservation<'a> {
5222 owners: &'a ActiveBlockingOwners,
5223 owner: BlockingOwnerToken,
5224 reservation: u64,
5225}
5226
5227#[cfg(not(target_family = "wasm"))]
5228impl<'a> OwnerLaneReservation<'a> {
5229 fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5230 let reservation = owners.reserve(owner)?;
5231 Some(Self {
5232 owners,
5233 owner,
5234 reservation,
5235 })
5236 }
5237}
5238
5239#[cfg(not(target_family = "wasm"))]
5240impl Drop for OwnerLaneReservation<'_> {
5241 fn drop(&mut self) {
5242 self.owners.release(self.owner, self.reservation);
5243 }
5244}
5245
5246#[cfg(not(target_family = "wasm"))]
5250type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5251
5252#[cfg(not(target_family = "wasm"))]
5253struct QueuedBlocking {
5254 id: BlockingJobId,
5255 run: BlockingClosure,
5256}
5257
5258#[cfg(not(target_family = "wasm"))]
5259struct BlockingQueueNode {
5260 previous: Option<BlockingJobId>,
5261 next: Option<BlockingJobId>,
5262 run: Box<dyn FnOnce() + Send + 'static>,
5263}
5264
5265#[cfg(not(target_family = "wasm"))]
5266struct BlockingQueue {
5267 closed: bool,
5268 head: Option<BlockingJobId>,
5269 tail: Option<BlockingJobId>,
5270 jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5271}
5272
5273#[cfg(not(target_family = "wasm"))]
5274impl BlockingQueue {
5275 fn push(&mut self, job: QueuedBlocking) {
5276 let previous = self.tail;
5277 let replaced = self.jobs.insert(
5278 job.id,
5279 BlockingQueueNode {
5280 previous,
5281 next: None,
5282 run: job.run,
5283 },
5284 );
5285 debug_assert!(replaced.is_none());
5286 if let Some(previous) = previous {
5287 self
5288 .jobs
5289 .get_mut(&previous)
5290 .expect("blocking queue tail must be indexed")
5291 .next = Some(job.id);
5292 } else {
5293 self.head = Some(job.id);
5294 }
5295 self.tail = Some(job.id);
5296 }
5297
5298 fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5299 self.head.and_then(|id| self.remove(id))
5300 }
5301
5302 fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5303 let node = self.jobs.remove(&id)?;
5304 if let Some(previous) = node.previous {
5305 self
5306 .jobs
5307 .get_mut(&previous)
5308 .expect("blocking queue predecessor must be indexed")
5309 .next = node.next;
5310 } else {
5311 self.head = node.next;
5312 }
5313 if let Some(next) = node.next {
5314 self
5315 .jobs
5316 .get_mut(&next)
5317 .expect("blocking queue successor must be indexed")
5318 .previous = node.previous;
5319 } else {
5320 self.tail = node.previous;
5321 }
5322 Some(node.run)
5323 }
5324
5325 fn is_empty(&self) -> bool {
5326 debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5327 debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5328 self.jobs.is_empty()
5329 }
5330
5331 fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5332 self.head = None;
5333 self.tail = None;
5334 std::mem::take(&mut self.jobs)
5335 .into_values()
5336 .map(|node| node.run)
5337 .collect()
5338 }
5339}
5340
5341#[cfg(not(target_family = "wasm"))]
5342struct WorkerLifecycle {
5343 remaining: Mutex<usize>,
5344 handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5345}
5346
5347#[cfg(not(target_family = "wasm"))]
5348impl WorkerLifecycle {
5349 fn new(worker_threads: usize) -> Arc<Self> {
5350 Arc::new(Self {
5351 remaining: Mutex::new(worker_threads),
5352 handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5353 })
5354 }
5355
5356 fn retain(&self, handle: std::thread::JoinHandle<()>) {
5357 self
5358 .handles
5359 .lock()
5360 .unwrap_or_else(std::sync::PoisonError::into_inner)
5361 .as_mut()
5362 .expect("runtime worker handles already joined")
5363 .push(handle);
5364 }
5365
5366 fn worker_exited(&self) {
5367 let mut remaining = self
5368 .remaining
5369 .lock()
5370 .unwrap_or_else(std::sync::PoisonError::into_inner);
5371 *remaining = remaining
5372 .checked_sub(1)
5373 .expect("runtime worker exit count underflow");
5374 }
5375
5376 fn wait_for_all_workers(&self) {
5377 let handles = self
5378 .handles
5379 .lock()
5380 .unwrap_or_else(std::sync::PoisonError::into_inner)
5381 .take()
5382 .expect("runtime worker handles already joined");
5383 for handle in handles {
5384 if let Err(payload) = handle.join() {
5385 discard_panic_payload(payload);
5386 }
5387 }
5388 }
5389
5390 #[cfg(test)]
5391 fn remaining(&self) -> usize {
5392 *self
5393 .remaining
5394 .lock()
5395 .unwrap_or_else(std::sync::PoisonError::into_inner)
5396 }
5397}
5398
5399#[cfg(not(target_family = "wasm"))]
5400struct MultiThreadExecutor {
5401 generation: u64,
5402 stop: Arc<GenerationStop>,
5403 id: u64,
5406 pool: ThreadPool,
5407 injector: Injector<Runnable>,
5414 blocking_queue: Mutex<BlockingQueue>,
5415 active_drainers: AtomicUsize,
5416 active_blocking: AtomicUsize,
5417 scheduler_idle_lock: Mutex<usize>,
5425 scheduler_idle: Condvar,
5426 worker_lifecycle: Arc<WorkerLifecycle>,
5427 parked_drivers: ParkedDrivers,
5435 active_blocking_owners: ActiveBlockingOwners,
5436 #[cfg(test)]
5437 owner_lending_attempts: AtomicUsize,
5438 #[cfg(test)]
5448 drain_linger_registrations: AtomicUsize,
5449 #[cfg(test)]
5450 drain_linger_expiries: AtomicUsize,
5451 max_drainers: usize,
5452 max_blocking: usize,
5453 park_deadline: Option<Duration>,
5460 drain_linger: Option<Duration>,
5465 deadlock_state: Arc<MultiThreadDeadlockState>,
5469 timers: TimerHeap,
5472 metrics: Arc<RuntimeMetrics>,
5473}
5474
5475#[cfg(not(target_family = "wasm"))]
5476impl MultiThreadExecutor {
5477 const RUNNABLE_BUDGET: usize = 64;
5480 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5483
5484 #[cfg(test)]
5485 fn new(
5486 options: &RuntimeOptions,
5487 metrics: Arc<RuntimeMetrics>,
5488 ) -> Result<Self, RuntimeConfigError> {
5489 Self::new_for_generation(
5490 options,
5491 metrics,
5492 u64::MAX,
5493 Arc::new(GenerationStop::default()),
5494 )
5495 }
5496
5497 fn new_for_generation(
5498 options: &RuntimeOptions,
5499 metrics: Arc<RuntimeMetrics>,
5500 generation: u64,
5501 stop: Arc<GenerationStop>,
5502 ) -> Result<Self, RuntimeConfigError> {
5503 let id = next_unique_id(
5504 &NEXT_RUNTIME_EXECUTOR_ID,
5505 "async runtime executor id space exhausted",
5506 );
5507 let thread_name_prefix = options.thread_name_prefix.clone();
5508 let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5509 options.park_deadline.is_some(),
5510 ));
5511 let timers = TimerHeap::new(
5512 generation,
5513 id,
5514 format!("{thread_name_prefix}-timer"),
5515 Arc::clone(&deadlock_state),
5516 );
5517 let pool_threads = options.worker_threads;
5521 let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5522 let exit_lifecycle = Arc::clone(&worker_lifecycle);
5523 let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5524 let pool = ThreadPoolBuilder::new()
5525 .num_threads(pool_threads)
5526 .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5527 .start_handler(move |_| {
5528 ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5529 })
5530 .exit_handler(move |_| {
5531 IN_SCHEDULER_DRIVER.with(|driver| {
5532 if driver.get().is_some_and(|role| role.executor_id == id) {
5533 driver.set(None);
5534 }
5535 });
5536 exit_lifecycle.worker_exited();
5542 })
5543 .spawn_handler(move |thread| {
5544 let mut builder = std::thread::Builder::new();
5545 if let Some(name) = thread.name() {
5546 builder = builder.name(name.to_string());
5547 }
5548 if let Some(stack_size) = thread.stack_size() {
5549 builder = builder.stack_size(stack_size);
5550 }
5551 let handle = builder.spawn(move || thread.run())?;
5552 spawn_lifecycle.retain(handle);
5553 Ok(())
5554 })
5555 .build()
5556 .map_err(|error| {
5557 worker_lifecycle.wait_for_all_workers();
5558 RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5559 })?;
5560 Ok(Self {
5561 generation,
5562 stop,
5563 id,
5564 pool,
5565 injector: Injector::new(),
5566 blocking_queue: Mutex::new(BlockingQueue {
5567 closed: false,
5568 head: None,
5569 tail: None,
5570 jobs: FxHashMap::default(),
5571 }),
5572 active_drainers: AtomicUsize::new(0),
5573 active_blocking: AtomicUsize::new(0),
5574 scheduler_idle_lock: Mutex::new(0),
5575 scheduler_idle: Condvar::new(),
5576 worker_lifecycle,
5577 parked_drivers: ParkedDrivers::default(),
5578 active_blocking_owners: ActiveBlockingOwners::default(),
5579 #[cfg(test)]
5580 owner_lending_attempts: AtomicUsize::new(0),
5581 #[cfg(test)]
5582 drain_linger_registrations: AtomicUsize::new(0),
5583 #[cfg(test)]
5584 drain_linger_expiries: AtomicUsize::new(0),
5585 max_drainers: pool_threads,
5586 max_blocking: options.max_blocking_tasks,
5587 park_deadline: options.park_deadline,
5588 drain_linger: {
5589 let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5593 (!budget.is_zero()).then_some(budget)
5594 },
5595 deadlock_state,
5596 timers,
5597 metrics,
5598 })
5599 }
5600
5601 fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5602 self.deadlock_state.begin_admission()
5603 }
5604
5605 fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5606 self.deadlock_state.try_begin_verdict()
5607 }
5608
5609 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5610 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5612 self.parked_drivers.wake_one_owner_dependency(owner)
5613 }
5614
5615 fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5616 let owner = reservation.owner;
5617 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5621 drop(reservation);
5622 self.parked_drivers.wake_one_owner_dependency(owner)
5623 }
5624
5625 fn schedule(self: &Arc<Self>, runnable: Runnable) {
5626 let mut admission = self.begin_deadlock_admission();
5627 self.metrics.runnable_scheduled();
5630 if IN_SCHEDULER_DRIVER
5650 .with(std::cell::Cell::get)
5651 .is_some_and(|role| role.executor_id == self.id)
5652 && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5653 {
5654 match LIFO_SLOT.with(std::cell::Cell::take) {
5655 None => {
5656 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5657 if let Some(admission) = admission.as_mut() {
5658 admission.mark_published();
5659 }
5660 return;
5661 }
5662 Some((id, displaced)) if id == self.id => {
5666 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5667 self.push_to_queue_and_wake(displaced);
5668 if let Some(admission) = admission.as_mut() {
5669 admission.mark_published();
5670 }
5671 return;
5672 }
5673 Some(foreign) => {
5680 LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5681 }
5682 }
5683 }
5684 self.push_to_queue_and_wake(runnable);
5685 if let Some(admission) = admission.as_mut() {
5686 admission.mark_published();
5687 }
5688 }
5689
5690 fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5694 self.injector.push(runnable);
5695 std::sync::atomic::fence(Ordering::SeqCst);
5704 self.wake_for_new_work();
5705 }
5706
5707 fn wake_for_new_work(self: &Arc<Self>) {
5716 if !self.parked_drivers.wake_one() {
5717 self.ensure_drainer();
5718 }
5719 }
5720
5721 fn wake_for_blocking_work(self: &Arc<Self>) {
5722 if !self.parked_drivers.wake_one_blocking() {
5725 self.ensure_drainer();
5726 }
5727 }
5728
5729 #[cfg(test)]
5730 fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5731 where
5732 F: FnOnce() -> T + Send + 'static,
5733 T: Send + 'static,
5734 {
5735 self.schedule_blocking_result(move || catch_unwind_join_error(function))
5736 }
5737
5738 fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5739 where
5740 F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5741 T: Send + 'static,
5742 {
5743 let mut admission = self.begin_deadlock_admission();
5744 let (sender, receiver) = oneshot::channel();
5745 let generation = self.generation;
5746 let dependency = BlockingDependency {
5747 job: BlockingJobId(next_unique_id(
5748 &NEXT_BLOCKING_JOB_ID,
5749 "blocking job id space exhausted",
5750 )),
5751 owner: BLOCKING_OWNER
5752 .with(std::cell::Cell::get)
5753 .filter(|owner| owner.executor_id == self.id),
5754 };
5755 self.metrics.blocking_scheduled();
5760 let queued = QueuedBlocking {
5761 id: dependency.job,
5762 run: Box::new(move || {
5763 let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5764 let _ = sender.send(result);
5765 }),
5766 };
5767 let rejected = {
5768 let mut queue = self
5769 .blocking_queue
5770 .lock()
5771 .unwrap_or_else(std::sync::PoisonError::into_inner);
5772 if queue.closed {
5773 Some(queued)
5774 } else {
5775 queue.push(queued);
5776 None
5777 }
5778 };
5779 if let Some(rejected) = rejected {
5780 let _generation = RuntimeGenerationGuard::enter(self.generation);
5785 let _ = catch_unwind_contained(|| drop(rejected));
5786 } else {
5787 self.wake_for_blocking_work();
5789 if let Some(admission) = admission.as_mut() {
5790 admission.mark_published();
5791 }
5792 }
5793 JoinHandle(JoinHandleInner::Blocking {
5794 receiver,
5795 dependency,
5796 published: PublishedBlockingDependencies::default(),
5797 })
5798 }
5799
5800 fn ensure_drainer(self: &Arc<Self>) {
5801 loop {
5802 let active = self.active_drainers.load(Ordering::Acquire);
5803 if active >= self.max_drainers {
5804 return;
5805 }
5806 if self
5807 .active_drainers
5808 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5809 .is_ok()
5810 {
5811 let executor = Arc::clone(self);
5812 self.pool.spawn_fifo(move || executor.drain());
5813 return;
5814 }
5815 }
5816 }
5817
5818 fn drain(self: Arc<Self>) {
5819 let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5825 let _on_pool = OnPoolWorkerGuard::enter(self.id);
5828 let _slot_backstop = LifoSlotFlushGuard(&self);
5834
5835 let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5836 let mut linger: Option<DrainLinger> = None;
5839 let mut idle_deadline: Option<Instant> = None;
5845 let mut frame_deadline: Option<Instant> = None;
5851
5852 'frame: loop {
5853 let mut runnable_streak = 0usize;
5854 for _ in 0..Self::RUNNABLE_BUDGET {
5855 if self.run_one_fair(&mut runnable_streak) {
5856 idle_deadline = None;
5857 continue;
5858 }
5859 self.flush_lifo_slot();
5867 let Some(budget) = linger_budget else {
5889 break 'frame;
5890 };
5891 if self.stop.is_stopping() {
5892 break 'frame;
5893 }
5894 let now = Instant::now();
5895 let frame = *frame_deadline.get_or_insert_with(|| {
5901 now
5902 + budget
5903 .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
5904 .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
5905 });
5906 let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
5907 if now >= deadline {
5908 #[cfg(test)]
5915 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5916 break 'frame;
5917 }
5918 let state = match &linger {
5919 Some(state) => state,
5920 None => linger.insert(DrainLinger::new(&self)),
5921 };
5922 self
5923 .parked_drivers
5924 .register_with_role(&state.parker, true, None);
5925 #[cfg(test)]
5926 self
5927 .drain_linger_registrations
5928 .fetch_add(1, Ordering::SeqCst);
5929 if self.has_queued_work_for_role(true) {
5930 self.parked_drivers.deregister(&state.parker);
5931 continue 'frame;
5932 }
5933 if self.stop.is_stopping() {
5938 self.parked_drivers.deregister(&state.parker);
5939 break 'frame;
5940 }
5941 let woken = state.parker.park_timeout(deadline - now);
5942 self.parked_drivers.deregister(&state.parker);
5943 if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
5944 continue 'frame;
5945 }
5946 #[cfg(test)]
5951 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5952 break 'frame;
5953 }
5954 break 'frame;
5959 }
5960
5961 self.flush_lifo_slot();
5964 self.finish_draining();
5965 }
5966
5967 fn pop_lifo_slot(&self) -> Option<Runnable> {
5971 LIFO_SLOT.with(|slot| match slot.take() {
5972 Some((id, runnable)) if id == self.id => Some(runnable),
5973 other => {
5974 slot.set(other);
5975 None
5976 }
5977 })
5978 }
5979
5980 fn flush_lifo_slot(self: &Arc<Self>) {
5987 let mut admission = self.begin_deadlock_admission();
5988 if let Some(runnable) = self.pop_lifo_slot() {
5989 self.push_to_queue_and_wake(runnable);
5990 if let Some(admission) = admission.as_mut() {
5991 admission.mark_published();
5992 }
5993 }
5994 }
5995
5996 fn fresh_owner_token(&self) -> BlockingOwnerToken {
6000 BlockingOwnerToken {
6001 executor_id: self.id,
6002 frame: next_unique_id(
6003 &NEXT_BLOCKING_FRAME,
6004 "blocking owner frame id space exhausted",
6005 ),
6006 }
6007 }
6008
6009 fn finish_draining(self: &Arc<Self>) {
6010 self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6020 let has_runnable = self.has_queued_runnable();
6021 let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6022 && !self
6023 .blocking_queue
6024 .lock()
6025 .unwrap_or_else(std::sync::PoisonError::into_inner)
6026 .is_empty();
6027 if has_runnable || has_blocking {
6028 self.ensure_drainer();
6029 }
6030 let idle = self
6036 .scheduler_idle_lock
6037 .lock()
6038 .unwrap_or_else(std::sync::PoisonError::into_inner);
6039 if *idle > 0 {
6040 self.scheduler_idle.notify_all();
6041 }
6042 }
6043
6044 fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6045 if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6046 self.cooperative_block_on(future)
6054 } else {
6055 self.parking_block_on(future)
6059 }
6060 }
6061
6062 fn parking_block_on(
6063 self: &Arc<Self>,
6064 mut future: Pin<&mut dyn Future<Output = ()>>,
6065 ) -> BlockOnOutcome {
6066 let parker = Arc::new(DriverParker::default());
6067 let _stop_registration = self.stop.register(&parker);
6068 let waker = Waker::from(Arc::clone(&parker));
6069 let mut cx = Context::from_waker(&waker);
6070 loop {
6071 if self.stop.is_stopping() {
6072 return BlockOnOutcome::Stopped;
6073 }
6074 if future.as_mut().poll(&mut cx).is_ready() {
6075 return BlockOnOutcome::Completed;
6076 }
6077 if self.stop.is_stopping() {
6078 return BlockOnOutcome::Stopped;
6079 }
6080 parker.park();
6081 }
6082 }
6083
6084 fn run_runnable(
6085 self: &Arc<Self>,
6086 runnable: Runnable,
6087 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6088 ) {
6089 let _non_owner = BlockingOwnerGuard::enter(None);
6092 let _active = self.metrics.runnable_started();
6093 #[cfg(test)]
6094 run_multi_thread_work_started_test_hook();
6095 drop(admission);
6099 let _ = catch_unwind_contained(|| runnable.run());
6100 }
6101
6102 fn cancel_runnable(
6103 &self,
6104 runnable: Runnable,
6105 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6106 ) {
6107 self.metrics.runnable_cancelled();
6108 drop(admission);
6111 let _generation = RuntimeGenerationGuard::enter(self.generation);
6112 let _ = catch_unwind_contained(|| drop(runnable));
6113 }
6114
6115 fn finish_runnable_claim(
6116 self: &Arc<Self>,
6117 claim: RunnableClaim,
6118 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6119 ) -> bool {
6120 match claim {
6121 RunnableClaim::Empty => {
6122 drop(admission);
6123 false
6124 }
6125 RunnableClaim::Run(runnable) => {
6126 #[cfg(test)]
6127 run_multi_thread_work_claimed_test_hook();
6128 self.run_runnable(runnable, admission);
6129 true
6130 }
6131 RunnableClaim::Cancel(runnable) => {
6132 #[cfg(test)]
6133 run_multi_thread_work_claimed_test_hook();
6134 self.cancel_runnable(runnable, admission);
6135 true
6136 }
6137 }
6138 }
6139
6140 fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6141 match runnable {
6142 Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6143 Some(runnable) => RunnableClaim::Run(runnable),
6144 None => RunnableClaim::Empty,
6145 }
6146 }
6147
6148 fn steal_one(&self) -> Option<Runnable> {
6152 loop {
6153 match self.injector.steal() {
6154 Steal::Success(runnable) => return Some(runnable),
6155 Steal::Empty => return None,
6156 Steal::Retry => continue,
6157 }
6158 }
6159 }
6160
6161 #[cfg(test)]
6168 fn push_fifo_raw(&self, runnable: Runnable) {
6169 self.injector.push(runnable);
6170 std::sync::atomic::fence(Ordering::SeqCst);
6171 }
6172
6173 fn claim_fifo_runnable(&self) -> RunnableClaim {
6174 let stop_publication = self.stop.runnable_claim_guard();
6175 let runnable = self.steal_one();
6176 let stopping = stop_publication.is_stopping();
6177 drop(stop_publication);
6178 Self::runnable_claim(stopping, runnable)
6179 }
6180
6181 fn claim_runnable(&self) -> RunnableClaim {
6182 let stop_publication = self.stop.runnable_claim_guard();
6183 let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6184 let stopping = stop_publication.is_stopping();
6185 drop(stop_publication);
6186 Self::runnable_claim(stopping, runnable)
6187 }
6188
6189 fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6190 let admission = self.begin_deadlock_admission();
6191 let claim = self.claim_fifo_runnable();
6192 self.finish_runnable_claim(claim, admission)
6193 }
6194
6195 fn run_one_runnable(self: &Arc<Self>) -> bool {
6197 let admission = self.begin_deadlock_admission();
6198 let claim = self.claim_runnable();
6201 self.finish_runnable_claim(claim, admission)
6202 }
6203
6204 fn run_one_blocking(self: &Arc<Self>) -> bool {
6205 if let Some((blocking, admission)) = self.take_blocking() {
6206 let _generation = RuntimeGenerationGuard::enter(self.generation);
6207 {
6208 let _task = self.metrics.blocking_started(true);
6209 #[cfg(test)]
6210 run_multi_thread_work_started_test_hook();
6211 drop(admission);
6214 let owner = self.fresh_owner_token();
6215 let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6216 let _owner = BlockingOwnerGuard::enter(Some(owner));
6217 let _ = catch_unwind_contained(blocking);
6222 }
6223 self.active_blocking.fetch_sub(1, Ordering::Release);
6224 return true;
6225 }
6226 false
6227 }
6228
6229 fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6233 self.run_one_fair_with_exact(runnable_streak, None)
6234 }
6235
6236 fn run_one_fair_with_exact(
6240 self: &Arc<Self>,
6241 runnable_streak: &mut usize,
6242 exact_dependency: Option<&TaskDependency>,
6243 ) -> bool {
6244 let mut exact_attempted = false;
6245 if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6246 *runnable_streak = 0;
6247 if let Some(dependency) = exact_dependency {
6248 exact_attempted = true;
6249 if self.try_owned_blocking_over_cap(dependency) {
6250 return true;
6251 }
6252 }
6253 if self.run_one_blocking() {
6254 return true;
6255 }
6256 }
6257 if self.run_one_runnable() {
6258 *runnable_streak += 1;
6259 return true;
6260 }
6261 if !exact_attempted
6262 && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6263 {
6264 *runnable_streak = 0;
6265 return true;
6266 }
6267 if self.run_one_blocking() {
6268 *runnable_streak = 0;
6269 return true;
6270 }
6271 false
6272 }
6273
6274 #[cfg(test)]
6275 fn run_one(self: &Arc<Self>) -> bool {
6276 let mut runnable_streak = 0;
6277 self.run_one_fair(&mut runnable_streak)
6278 }
6279
6280 fn run_owned_blocking_over_cap(
6290 self: &Arc<Self>,
6291 dependency: &TaskDependency,
6292 ) -> OwnedBlockingOutcome {
6293 #[cfg(test)]
6294 self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6295
6296 let Some(ambient_owner) = BLOCKING_OWNER
6297 .with(std::cell::Cell::get)
6298 .filter(|owner| owner.executor_id == self.id)
6299 else {
6300 return OwnedBlockingOutcome::Ineligible;
6301 };
6302 let Some(current) = dependency.select_for_owner(ambient_owner) else {
6303 return OwnedBlockingOutcome::Ineligible;
6304 };
6305 let admission = self.begin_deadlock_admission();
6306 let owner = current
6307 .publication
6308 .dependency
6309 .owner
6310 .expect("selected dependency must carry its owner");
6311 let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6312 else {
6313 return OwnedBlockingOutcome::OwnerBusy(owner);
6314 };
6315 #[cfg(test)]
6316 run_before_owned_blocking_queue_claim_test_hook();
6317 let job = {
6318 let mut queue = self
6319 .blocking_queue
6320 .lock()
6321 .unwrap_or_else(std::sync::PoisonError::into_inner);
6322 if queue.closed || self.stop.is_stopping() {
6326 None
6327 } else if dependency.try_claim(¤t) {
6331 queue.remove(current.publication.dependency.job)
6332 } else {
6333 None
6334 }
6335 };
6336 let ran = if let Some(job) = job {
6337 #[cfg(test)]
6338 run_multi_thread_work_claimed_test_hook();
6339 let _generation = RuntimeGenerationGuard::enter(self.generation);
6340 {
6341 let _task = self.metrics.blocking_started(false);
6342 #[cfg(test)]
6343 run_multi_thread_work_started_test_hook();
6344 drop(admission);
6347 let nested_owner = self.fresh_owner_token();
6348 let _active_owner =
6349 ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6350 let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6351 let _ = catch_unwind_contained(job);
6352 }
6353 true
6354 } else {
6355 drop(admission);
6356 false
6357 };
6358 self.release_owner_lane_and_wake(reservation);
6361 OwnedBlockingOutcome::ReservationReleased { owner, ran }
6362 }
6363
6364 fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6365 matches!(
6366 self.run_owned_blocking_over_cap(dependency),
6367 OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6368 )
6369 }
6370
6371 fn service_owner_handoff(
6372 self: &Arc<Self>,
6373 parker: &DriverParker,
6374 dependency: &TaskDependency,
6375 can_run_blocking: bool,
6376 ) -> Option<bool> {
6377 let handoff_owner = parker.take_owner_handoff()?;
6378 if !can_run_blocking {
6379 self.wake_one_owner_dependency(handoff_owner);
6380 return Some(false);
6381 }
6382 match self.run_owned_blocking_over_cap(dependency) {
6383 OwnedBlockingOutcome::Ineligible => {
6384 self.wake_one_owner_dependency(handoff_owner);
6385 Some(false)
6386 }
6387 OwnedBlockingOutcome::OwnerBusy(owner) => {
6388 if owner != handoff_owner {
6389 self.wake_one_owner_dependency(handoff_owner);
6390 }
6391 Some(false)
6392 }
6393 OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6394 if owner != handoff_owner {
6395 self.wake_one_owner_dependency(handoff_owner);
6396 }
6397 Some(ran)
6398 }
6399 }
6400 }
6401
6402 fn forward_owner_handoff(&self, parker: &DriverParker) {
6403 if let Some(owner) = parker.take_owner_handoff() {
6404 self.wake_one_owner_dependency(owner);
6405 }
6406 }
6407
6408 fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6409 let Some(ambient_owner) = BLOCKING_OWNER
6410 .with(std::cell::Cell::get)
6411 .filter(|owner| owner.executor_id == self.id)
6412 else {
6413 return false;
6414 };
6415 self.active_blocking_owners.is_available(ambient_owner)
6416 && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6417 }
6418
6419 fn has_exact_owner_work_for_role(
6420 &self,
6421 parker: &DriverParker,
6422 dependency: &TaskDependency,
6423 can_run_blocking: bool,
6424 ) -> bool {
6425 parker.has_owner_handoff()
6426 || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6427 }
6428
6429 fn cooperative_block_on(
6438 self: &Arc<Self>,
6439 mut future: Pin<&mut dyn Future<Output = ()>>,
6440 ) -> BlockOnOutcome {
6441 let parker = Arc::new(DriverParker::default());
6446 let _stop_registration = self.stop.register(&parker);
6447 let dependency = BlockOnDependencyGuard::enter(self.generation);
6448 let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6449 let can_run_blocking =
6450 driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6451 let _exit = CooperativeDriverExitGuard {
6452 executor: self,
6453 parker: &parker,
6454 can_run_blocking,
6455 };
6456 let waker = std::task::Waker::from(Arc::clone(&parker));
6461 let mut cx = Context::from_waker(&waker);
6462 let mut runnable_streak = 0usize;
6470 let mut work_streak = 0usize;
6471 let mut force_fifo = false;
6472 loop {
6473 if self.stop.is_stopping() {
6474 return BlockOnOutcome::Stopped;
6475 }
6476 dependency.clear();
6477 if future.as_mut().poll(&mut cx).is_ready() {
6478 return BlockOnOutcome::Completed;
6479 }
6480 if self.stop.is_stopping() {
6481 return BlockOnOutcome::Stopped;
6482 }
6483 if self
6484 .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6485 .is_some_and(|ran| ran)
6486 {
6487 continue;
6488 }
6489 if force_fifo {
6490 force_fifo = false;
6491 if self.run_one_fifo_runnable() {
6492 runnable_streak += 1;
6493 work_streak += 1;
6494 continue;
6495 }
6496 }
6497 let ran_work = if can_run_blocking {
6498 self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6499 } else if self.run_one_runnable() {
6500 runnable_streak += 1;
6501 true
6502 } else {
6503 false
6504 };
6505 if ran_work {
6506 work_streak += 1;
6507 if work_streak >= Self::RUNNABLE_BUDGET {
6508 self.flush_lifo_slot();
6513 work_streak = 0;
6514 force_fifo = true;
6515 }
6516 continue;
6517 }
6518 runnable_streak = 0;
6519 work_streak = 0;
6520 self.flush_lifo_slot();
6525 self.parked_drivers.register_with_role(
6529 &parker,
6530 can_run_blocking,
6531 Some(Arc::clone(&dependency.dependency)),
6532 );
6533 if self.has_queued_work_for_role(can_run_blocking)
6534 || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6535 {
6536 self.parked_drivers.deregister(&parker);
6537 continue;
6538 }
6539 if self.stop.is_stopping() {
6540 return BlockOnOutcome::Stopped;
6541 }
6542 if let Some(next) = self.next_timer_deadline() {
6557 let now = Instant::now();
6558 if next > now {
6559 parker.park_timeout(next - now);
6560 }
6561 self.parked_drivers.deregister(&parker);
6562 self.fire_due_timers();
6563 continue;
6564 }
6565 match self.park_deadline {
6566 None => parker.park(),
6567 Some(deadline) => {
6568 let deadlock_publications = self.deadlock_state.publications();
6574 match park_with_deadline(&parker, deadline, &self.metrics) {
6575 DeadlineParkOutcome::Woken => {}
6576 DeadlineParkOutcome::Expired(armed) => {
6577 #[cfg(test)]
6578 run_deadline_expiry_test_hook();
6579 self.parked_drivers.deregister(&parker);
6616 if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6617 continue;
6618 }
6619 #[cfg(test)]
6622 run_deadline_verdict_test_hook();
6623 if self.metrics.progress_fingerprint() != armed {
6624 continue;
6625 }
6626 if self.next_timer_deadline().is_some() {
6635 continue;
6636 }
6637 #[cfg(test)]
6640 run_deadline_final_verdict_test_hook();
6641 let Some(verdict) = self.try_begin_deadlock_verdict() else {
6642 continue;
6643 };
6644 #[cfg(test)]
6645 run_deadline_gated_verdict_test_hook();
6646 let stop_publication = self.stop.publication_guard();
6651 #[cfg(test)]
6652 run_deadline_synchronized_stop_test_hook();
6653 if stop_publication.is_stopping() {
6654 return BlockOnOutcome::Stopped;
6655 }
6656 let owner_handoff_publication = self
6657 .deadlock_state
6658 .owner_handoff_publication_guard()
6659 .expect("a deadline verdict requires exact-owner publication");
6660 #[cfg(test)]
6661 run_deadline_exact_owner_verdict_test_hook(&parker);
6662 if self.has_exact_owner_work_for_role(
6670 &parker,
6671 &dependency.dependency,
6672 can_run_blocking,
6673 ) || parker.consume_permit()
6674 || self.has_queued_work_for_role(can_run_blocking)
6675 || self.deadlock_state.publications() != deadlock_publications
6676 || self.metrics.progress_fingerprint() != armed
6677 || self.next_timer_deadline().is_some()
6678 {
6679 continue;
6680 }
6681 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6682 drop(owner_handoff_publication);
6687 drop(stop_publication);
6688 drop(verdict);
6689 std::panic::panic_any(diagnostic);
6690 }
6691 }
6692 }
6693 }
6694 self.parked_drivers.deregister(&parker);
6697 }
6698 }
6699
6700 fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6706 if self.has_queued_runnable() {
6707 return true;
6708 }
6709 can_run_blocking
6710 && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6711 && !self
6712 .blocking_queue
6713 .lock()
6714 .unwrap_or_else(std::sync::PoisonError::into_inner)
6715 .is_empty()
6716 }
6717
6718 fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6719 if self.has_queued_runnable() {
6720 self.wake_for_new_work();
6721 }
6722 if self.stop.is_stopping() {
6726 return;
6727 }
6728 if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6729 && !self
6730 .blocking_queue
6731 .lock()
6732 .unwrap_or_else(std::sync::PoisonError::into_inner)
6733 .is_empty()
6734 {
6735 let consumed = can_run_blocking && self.run_one_blocking();
6743 let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6744 && !self
6745 .blocking_queue
6746 .lock()
6747 .unwrap_or_else(std::sync::PoisonError::into_inner)
6748 .is_empty();
6749 if !self.stop.is_stopping() && (!consumed || has_residue) {
6750 self.wake_for_blocking_work();
6751 }
6752 }
6753 }
6754
6755 fn has_queued_runnable(&self) -> bool {
6756 std::sync::atomic::fence(Ordering::SeqCst);
6763 !self.injector.is_empty()
6764 }
6765
6766 fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6770 let admission = self.begin_deadlock_admission();
6771 loop {
6772 let active = self.active_blocking.load(Ordering::Acquire);
6773 if active >= self.max_blocking {
6774 return None;
6775 }
6776 let mut queue = self
6777 .blocking_queue
6778 .lock()
6779 .unwrap_or_else(std::sync::PoisonError::into_inner);
6780 if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6781 return None;
6782 }
6783 if self
6784 .active_blocking
6785 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6786 .is_ok()
6787 {
6788 let blocking = queue
6789 .pop_front()
6790 .expect("a non-empty blocking queue must have a FIFO head");
6791 drop(queue);
6792 #[cfg(test)]
6793 run_multi_thread_work_claimed_test_hook();
6794 return Some((blocking, admission));
6795 }
6796 }
6797 }
6798}
6799
6800#[cfg(not(target_family = "wasm"))]
6801struct MultiThreadDeadlockState {
6802 enabled: bool,
6803 verdict_closed: AtomicBool,
6804 admissions: AtomicUsize,
6805 publications: AtomicU64,
6806 owner_handoff_publication: Mutex<()>,
6807}
6808
6809#[cfg(not(target_family = "wasm"))]
6810impl MultiThreadDeadlockState {
6811 fn new(enabled: bool) -> Self {
6812 Self {
6813 enabled,
6814 verdict_closed: AtomicBool::new(false),
6815 admissions: AtomicUsize::new(0),
6816 publications: AtomicU64::new(0),
6817 owner_handoff_publication: Mutex::new(()),
6818 }
6819 }
6820
6821 fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6822 if !self.enabled {
6823 return None;
6824 }
6825 loop {
6826 while self.verdict_closed.load(Ordering::SeqCst) {
6827 #[cfg(test)]
6828 run_deadlock_closed_gate_admission_test_hook();
6829 std::hint::spin_loop();
6830 }
6831 self.admissions.fetch_add(1, Ordering::SeqCst);
6832 if !self.verdict_closed.load(Ordering::SeqCst) {
6833 return Some(MultiThreadDeadlockAdmissionGuard {
6834 state: Arc::clone(self),
6835 published: false,
6836 });
6837 }
6838 self.admissions.fetch_sub(1, Ordering::SeqCst);
6839 }
6840 }
6841
6842 fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6843 debug_assert!(
6844 self.enabled,
6845 "a deadline verdict requires an enabled admission state"
6846 );
6847 if self
6848 .verdict_closed
6849 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6850 .is_err()
6851 {
6852 return None;
6853 }
6854 if self.admissions.load(Ordering::SeqCst) != 0 {
6855 self.verdict_closed.store(false, Ordering::SeqCst);
6856 return None;
6857 }
6858 Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6859 }
6860
6861 fn publications(&self) -> u64 {
6862 self.publications.load(Ordering::SeqCst)
6863 }
6864
6865 fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
6866 self.enabled.then(|| {
6867 self
6868 .owner_handoff_publication
6869 .lock()
6870 .unwrap_or_else(std::sync::PoisonError::into_inner)
6871 })
6872 }
6873}
6874
6875#[cfg(not(target_family = "wasm"))]
6876struct MultiThreadDeadlockAdmissionGuard {
6877 state: Arc<MultiThreadDeadlockState>,
6878 published: bool,
6879}
6880
6881#[cfg(not(target_family = "wasm"))]
6882impl MultiThreadDeadlockAdmissionGuard {
6883 fn mark_published(&mut self) {
6884 self.published = true;
6885 }
6886}
6887
6888#[cfg(not(target_family = "wasm"))]
6889impl Drop for MultiThreadDeadlockAdmissionGuard {
6890 fn drop(&mut self) {
6891 if self.published {
6892 self.state.publications.fetch_add(1, Ordering::SeqCst);
6893 }
6894 self.state.admissions.fetch_sub(1, Ordering::SeqCst);
6897 }
6898}
6899
6900#[cfg(not(target_family = "wasm"))]
6901struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
6902
6903#[cfg(not(target_family = "wasm"))]
6904impl Drop for MultiThreadDeadlockVerdictGuard {
6905 fn drop(&mut self) {
6906 self.0.verdict_closed.store(false, Ordering::SeqCst);
6907 }
6908}
6909
6910#[cfg(not(target_family = "wasm"))]
6916struct CooperativeDriverExitGuard<'a> {
6917 executor: &'a Arc<MultiThreadExecutor>,
6918 parker: &'a Arc<DriverParker>,
6919 can_run_blocking: bool,
6920}
6921
6922#[cfg(not(target_family = "wasm"))]
6923impl Drop for CooperativeDriverExitGuard<'_> {
6924 fn drop(&mut self) {
6925 let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
6926 let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
6927 let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
6928 let _ = catch_unwind_contained(|| {
6929 self
6930 .executor
6931 .compensate_queued_work_wake(self.can_run_blocking);
6932 });
6933 }
6934}
6935
6936#[cfg(not(target_family = "wasm"))]
6942struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
6943
6944#[cfg(not(target_family = "wasm"))]
6945impl Drop for LifoSlotFlushGuard<'_> {
6946 fn drop(&mut self) {
6947 self.0.flush_lifo_slot();
6948 }
6949}
6950
6951#[cfg(not(target_family = "wasm"))]
6958struct DrainLinger {
6959 parker: Arc<DriverParker>,
6960 _stop_registration: GenerationStopGuard,
6961}
6962
6963#[cfg(not(target_family = "wasm"))]
6964impl DrainLinger {
6965 fn new(executor: &MultiThreadExecutor) -> Self {
6966 let parker = Arc::new(DriverParker::default());
6967 let stop_registration = executor.stop.register(&parker);
6968 Self {
6969 parker,
6970 _stop_registration: stop_registration,
6971 }
6972 }
6973}
6974
6975const PARKER_EMPTY: usize = 0;
6997const PARKER_NOTIFIED: usize = 1;
6999const PARKER_SLEEPING: usize = 2;
7001
7002#[derive(Default)]
7006struct DriverParker {
7007 state: AtomicUsize,
7008 lock: Mutex<()>,
7009 condvar: std::sync::Condvar,
7010 #[cfg(not(target_family = "wasm"))]
7011 owner_handoff_pending: AtomicBool,
7012 #[cfg(not(target_family = "wasm"))]
7013 owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7014}
7015
7016impl DriverParker {
7017 #[cfg(not(target_family = "wasm"))]
7018 fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7019 let mut pending = self
7020 .owner_handoff
7021 .lock()
7022 .unwrap_or_else(std::sync::PoisonError::into_inner);
7023 debug_assert!(pending.is_none());
7024 *pending = Some(owner);
7025 self.owner_handoff_pending.store(true, Ordering::Release);
7026 }
7027
7028 #[cfg(not(target_family = "wasm"))]
7029 fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7030 if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7031 return None;
7032 }
7033 self
7034 .owner_handoff
7035 .lock()
7036 .unwrap_or_else(std::sync::PoisonError::into_inner)
7037 .take()
7038 }
7039
7040 #[cfg(not(target_family = "wasm"))]
7041 fn has_owner_handoff(&self) -> bool {
7042 self.owner_handoff_pending.load(Ordering::Acquire)
7043 }
7044
7045 fn unpark(&self) {
7052 if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7053 drop(
7058 self
7059 .lock
7060 .lock()
7061 .unwrap_or_else(std::sync::PoisonError::into_inner),
7062 );
7063 self.condvar.notify_one();
7064 }
7065 }
7066
7067 fn consume_permit(&self) -> bool {
7072 self
7073 .state
7074 .compare_exchange(
7075 PARKER_NOTIFIED,
7076 PARKER_EMPTY,
7077 Ordering::SeqCst,
7078 Ordering::SeqCst,
7079 )
7080 .is_ok()
7081 }
7082
7083 fn park(&self) {
7087 if self.consume_permit() {
7089 return;
7090 }
7091 let mut guard = self
7092 .lock
7093 .lock()
7094 .unwrap_or_else(std::sync::PoisonError::into_inner);
7095 match self.state.compare_exchange(
7096 PARKER_EMPTY,
7097 PARKER_SLEEPING,
7098 Ordering::SeqCst,
7099 Ordering::SeqCst,
7100 ) {
7101 Ok(_) => {}
7102 Err(_) => {
7106 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7107 return;
7108 }
7109 }
7110 loop {
7111 guard = self
7112 .condvar
7113 .wait(guard)
7114 .unwrap_or_else(std::sync::PoisonError::into_inner);
7115 if self
7116 .state
7117 .compare_exchange(
7118 PARKER_NOTIFIED,
7119 PARKER_EMPTY,
7120 Ordering::SeqCst,
7121 Ordering::SeqCst,
7122 )
7123 .is_ok()
7124 {
7125 return;
7126 }
7127 }
7129 }
7130
7131 fn park_timeout(&self, timeout: Duration) -> bool {
7137 if self.consume_permit() {
7138 return true;
7139 }
7140 let mut guard = self
7141 .lock
7142 .lock()
7143 .unwrap_or_else(std::sync::PoisonError::into_inner);
7144 match self.state.compare_exchange(
7145 PARKER_EMPTY,
7146 PARKER_SLEEPING,
7147 Ordering::SeqCst,
7148 Ordering::SeqCst,
7149 ) {
7150 Ok(_) => {}
7151 Err(_) => {
7155 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7156 return true;
7157 }
7158 }
7159 let Some(deadline) = Instant::now().checked_add(timeout) else {
7160 loop {
7164 guard = self
7165 .condvar
7166 .wait(guard)
7167 .unwrap_or_else(std::sync::PoisonError::into_inner);
7168 if self
7169 .state
7170 .compare_exchange(
7171 PARKER_NOTIFIED,
7172 PARKER_EMPTY,
7173 Ordering::SeqCst,
7174 Ordering::SeqCst,
7175 )
7176 .is_ok()
7177 {
7178 return true;
7179 }
7180 }
7181 };
7182 loop {
7183 if self
7184 .state
7185 .compare_exchange(
7186 PARKER_NOTIFIED,
7187 PARKER_EMPTY,
7188 Ordering::SeqCst,
7189 Ordering::SeqCst,
7190 )
7191 .is_ok()
7192 {
7193 return true;
7194 }
7195 let now = Instant::now();
7196 if now >= deadline {
7197 return match self.state.compare_exchange(
7202 PARKER_SLEEPING,
7203 PARKER_EMPTY,
7204 Ordering::SeqCst,
7205 Ordering::SeqCst,
7206 ) {
7207 Ok(_) => false,
7208 Err(_) => {
7209 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7210 true
7211 }
7212 };
7213 }
7214 let (returned_guard, _timed_out) = self
7215 .condvar
7216 .wait_timeout(guard, deadline - now)
7217 .unwrap_or_else(std::sync::PoisonError::into_inner);
7218 guard = returned_guard;
7219 }
7222 }
7223}
7224
7225impl std::task::Wake for DriverParker {
7226 fn wake(self: Arc<Self>) {
7227 self.unpark();
7228 }
7229
7230 fn wake_by_ref(self: &Arc<Self>) {
7231 self.unpark();
7232 }
7233}
7234
7235enum DeadlineParkOutcome {
7237 Woken,
7239 Expired(ProgressFingerprint),
7248}
7249
7250fn park_with_deadline(
7259 parker: &DriverParker,
7260 deadline: Duration,
7261 metrics: &RuntimeMetrics,
7262) -> DeadlineParkOutcome {
7263 let mut armed = metrics.progress_fingerprint();
7264 loop {
7265 if parker.park_timeout(deadline) {
7266 return DeadlineParkOutcome::Woken;
7267 }
7268 let current = metrics.progress_fingerprint();
7269 if current != armed {
7270 armed = current;
7271 continue;
7272 }
7273 return DeadlineParkOutcome::Expired(armed);
7274 }
7275}
7276
7277#[cfg(all(test, not(target_family = "wasm")))]
7285thread_local! {
7286 static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7287 const { std::cell::RefCell::new(None) };
7288}
7289
7290#[cfg(all(test, not(target_family = "wasm")))]
7291fn run_deadline_expiry_test_hook() {
7292 if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7293 hook();
7294 }
7295}
7296
7297#[cfg(all(test, not(target_family = "wasm")))]
7304thread_local! {
7305 static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7306 const { std::cell::RefCell::new(None) };
7307}
7308
7309#[cfg(all(test, not(target_family = "wasm")))]
7310fn run_deadline_verdict_test_hook() {
7311 if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7312 hook();
7313 }
7314}
7315
7316#[cfg(all(test, not(target_family = "wasm")))]
7321thread_local! {
7322 static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7323 const { std::cell::RefCell::new(None) };
7324}
7325
7326#[cfg(all(test, not(target_family = "wasm")))]
7327fn run_deadline_final_verdict_test_hook() {
7328 if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7329 hook();
7330 }
7331}
7332
7333#[cfg(all(test, not(target_family = "wasm")))]
7338thread_local! {
7339 static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7340 const { std::cell::RefCell::new(None) };
7341}
7342
7343#[cfg(all(test, not(target_family = "wasm")))]
7344fn run_deadline_gated_verdict_test_hook() {
7345 if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7346 hook();
7347 }
7348}
7349
7350#[cfg(all(test, not(target_family = "wasm")))]
7354thread_local! {
7355 static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7356 const { std::cell::RefCell::new(None) };
7357}
7358
7359#[cfg(all(test, not(target_family = "wasm")))]
7360fn run_deadline_synchronized_stop_test_hook() {
7361 if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7362 hook();
7363 }
7364}
7365
7366#[cfg(all(test, not(target_family = "wasm")))]
7370type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7371
7372#[cfg(all(test, not(target_family = "wasm")))]
7373thread_local! {
7374 static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7375 std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7376 const { std::cell::RefCell::new(None) };
7377}
7378
7379#[cfg(all(test, not(target_family = "wasm")))]
7380fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7381 if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7382 hook(parker);
7383 }
7384}
7385
7386#[cfg(all(test, not(target_family = "wasm")))]
7390thread_local! {
7391 static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7392 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7393 const { std::cell::RefCell::new(None) };
7394}
7395
7396#[cfg(all(test, not(target_family = "wasm")))]
7397fn run_before_generation_stop_publication_lock_test_hook() {
7398 if let Some(hook) =
7399 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7400 {
7401 hook();
7402 }
7403}
7404
7405#[cfg(all(test, not(target_family = "wasm")))]
7409thread_local! {
7410 static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7411 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7412 const { std::cell::RefCell::new(None) };
7413}
7414
7415#[cfg(all(test, not(target_family = "wasm")))]
7416fn run_deadlock_closed_gate_admission_test_hook() {
7417 if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7418 {
7419 hook();
7420 }
7421}
7422
7423#[cfg(all(test, not(target_family = "wasm")))]
7427thread_local! {
7428 static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7429 const { std::cell::RefCell::new(None) };
7430}
7431
7432#[cfg(all(test, not(target_family = "wasm")))]
7433fn run_timer_registration_admission_test_hook() {
7434 if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7435 hook();
7436 }
7437}
7438
7439#[cfg(all(test, not(target_family = "wasm")))]
7440thread_local! {
7441 static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7442 const { std::cell::RefCell::new(None) };
7443}
7444
7445#[cfg(all(test, not(target_family = "wasm")))]
7446fn run_owner_handoff_selected_test_hook() {
7447 if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7448 hook();
7449 }
7450}
7451
7452#[cfg(not(target_family = "wasm"))]
7455#[derive(Default)]
7456struct ParkedDrivers {
7457 parked: Mutex<Vec<ParkedDriver>>,
7458 count: AtomicUsize,
7461}
7462
7463#[cfg(not(target_family = "wasm"))]
7464struct ParkedDriver {
7465 parker: Arc<DriverParker>,
7466 can_run_blocking: bool,
7467 dependency: Option<Arc<TaskDependency>>,
7468}
7469
7470#[cfg(not(target_family = "wasm"))]
7471impl ParkedDrivers {
7472 #[cfg(test)]
7475 fn register(&self, parker: &Arc<DriverParker>) {
7476 self.register_with_role(parker, true, None);
7477 }
7478
7479 fn register_with_role(
7480 &self,
7481 parker: &Arc<DriverParker>,
7482 can_run_blocking: bool,
7483 dependency: Option<Arc<TaskDependency>>,
7484 ) {
7485 let mut parked = self
7486 .parked
7487 .lock()
7488 .unwrap_or_else(std::sync::PoisonError::into_inner);
7489 parked.push(ParkedDriver {
7490 parker: Arc::clone(parker),
7491 can_run_blocking,
7492 dependency,
7493 });
7494 self.count.store(parked.len(), Ordering::SeqCst);
7495 }
7496
7497 fn deregister(&self, parker: &Arc<DriverParker>) {
7501 let removed = {
7502 let mut parked = self
7503 .parked
7504 .lock()
7505 .unwrap_or_else(std::sync::PoisonError::into_inner);
7506 let removed = parked
7507 .iter()
7508 .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7509 .map(|index| parked.swap_remove(index));
7510 self.count.store(parked.len(), Ordering::SeqCst);
7511 removed
7512 };
7513 drop(removed);
7514 }
7515
7516 fn wake_one(&self) -> bool {
7539 if self.count.load(Ordering::SeqCst) == 0 {
7540 return false;
7544 }
7545 let driver = {
7546 let mut parked = self
7547 .parked
7548 .lock()
7549 .unwrap_or_else(std::sync::PoisonError::into_inner);
7550 let driver = parked.pop();
7551 self.count.store(parked.len(), Ordering::SeqCst);
7552 driver
7553 };
7554 match driver {
7555 Some(driver) => {
7556 driver.parker.unpark();
7557 true
7558 }
7559 None => false,
7560 }
7561 }
7562
7563 fn wake_one_blocking(&self) -> bool {
7566 if self.count.load(Ordering::SeqCst) == 0 {
7567 return false;
7568 }
7569 let driver = {
7570 let mut parked = self
7571 .parked
7572 .lock()
7573 .unwrap_or_else(std::sync::PoisonError::into_inner);
7574 let driver = parked
7575 .iter()
7576 .rposition(|driver| driver.can_run_blocking)
7577 .map(|index| parked.swap_remove(index));
7578 self.count.store(parked.len(), Ordering::SeqCst);
7579 driver
7580 };
7581 match driver {
7582 Some(driver) => {
7583 driver.parker.unpark();
7584 true
7585 }
7586 None => false,
7587 }
7588 }
7589
7590 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7595 loop {
7596 if self.count.load(Ordering::SeqCst) == 0 {
7597 return false;
7598 }
7599 let candidates = {
7600 let parked = self
7601 .parked
7602 .lock()
7603 .unwrap_or_else(std::sync::PoisonError::into_inner);
7604 parked
7605 .iter()
7606 .rev()
7607 .filter_map(|driver| {
7608 if !driver.can_run_blocking {
7609 return None;
7610 }
7611 driver
7612 .dependency
7613 .as_ref()
7614 .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7615 })
7616 .collect::<Vec<_>>()
7617 };
7618 let selected = candidates.into_iter().find(|(_, dependency)| {
7619 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7620 });
7621 let Some((selected_parker, selected_dependency)) = selected else {
7622 return false;
7623 };
7624 let driver = {
7625 let mut parked = self
7626 .parked
7627 .lock()
7628 .unwrap_or_else(std::sync::PoisonError::into_inner);
7629 let driver = parked
7630 .iter()
7631 .rposition(|driver| {
7632 Arc::ptr_eq(&driver.parker, &selected_parker)
7633 && driver
7634 .dependency
7635 .as_ref()
7636 .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7637 })
7638 .map(|index| parked.swap_remove(index));
7639 if let Some(driver) = &driver {
7644 driver.parker.grant_owner_handoff(owner);
7645 }
7646 self.count.store(parked.len(), Ordering::SeqCst);
7647 driver
7648 };
7649 if let Some(driver) = driver {
7650 #[cfg(test)]
7651 run_owner_handoff_selected_test_hook();
7652 driver.parker.unpark();
7653 return true;
7654 }
7655 }
7656 }
7657
7658 fn wake_all(&self) {
7661 if self.count.load(Ordering::SeqCst) == 0 {
7662 return;
7663 }
7664 let drivers = {
7665 let mut parked = self
7666 .parked
7667 .lock()
7668 .unwrap_or_else(std::sync::PoisonError::into_inner);
7669 let drivers = parked.drain(..).collect::<Vec<_>>();
7670 self.count.store(0, Ordering::SeqCst);
7671 drivers
7672 };
7673 for driver in drivers {
7674 driver.parker.unpark();
7675 }
7676 }
7677}
7678
7679pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7724 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7758
7759 fn is_live(&self) -> bool {
7760 true
7761 }
7762
7763 fn on_swept(&self) {}
7766}
7767
7768#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7769pub struct CurrentThreadTaskDriverId(u64);
7770
7771type CurrentThreadTaskDriverSnapshot =
7772 ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7773
7774#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7775pub struct CurrentThreadTaskDelivery {
7776 driver: CurrentThreadTaskDriverId,
7777 capability: u64,
7778 dispatch: u64,
7779}
7780
7781impl CurrentThreadTaskDelivery {
7782 pub fn capability(self) -> u64 {
7784 self.capability
7785 }
7786}
7787
7788struct CurrentThreadTaskDeliveryState {
7789 delivery: CurrentThreadTaskDelivery,
7790 claimed: bool,
7791 accepted: bool,
7792}
7793
7794struct CurrentThreadTaskDriverEntry {
7795 driver: Arc<dyn CurrentThreadTaskDriver>,
7796 delivery: Option<CurrentThreadTaskDeliveryState>,
7797 pending: Option<u64>,
7798 previous: Option<CurrentThreadTaskDriverId>,
7799 next: Option<CurrentThreadTaskDriverId>,
7800}
7801
7802#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7803enum CurrentThreadDispatchPhase {
7804 #[default]
7805 Pending,
7806 Serviced,
7807 Cancelling,
7808}
7809
7810#[derive(Default)]
7811struct CurrentThreadDispatchState {
7812 phase: CurrentThreadDispatchPhase,
7813 epoch: u64,
7814 publications: usize,
7815 references: usize,
7816 accepted: bool,
7817 accepted_failure: bool,
7818}
7819
7820impl CurrentThreadDispatchState {
7821 fn is_serviced(&self) -> bool {
7822 self.phase == CurrentThreadDispatchPhase::Serviced
7823 }
7824
7825 fn is_cancelling(&self) -> bool {
7826 self.phase == CurrentThreadDispatchPhase::Cancelling
7827 }
7828
7829 fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7830 CurrentThreadTaskDispatchFailure {
7831 dispatch,
7832 epoch: self.epoch,
7833 }
7834 }
7835}
7836
7837#[derive(Default)]
7838struct CurrentThreadTaskDriverRegistryState {
7839 entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7840 oldest: Option<CurrentThreadTaskDriverId>,
7841 newest: Option<CurrentThreadTaskDriverId>,
7842 delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7843 dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7844}
7845
7846impl CurrentThreadTaskDriverRegistryState {
7847 fn insert_driver(
7848 &mut self,
7849 id: CurrentThreadTaskDriverId,
7850 driver: Arc<dyn CurrentThreadTaskDriver>,
7851 ) {
7852 let previous = self.newest;
7853 let replaced = self.entries.insert(
7854 id,
7855 CurrentThreadTaskDriverEntry {
7856 driver,
7857 delivery: None,
7858 pending: None,
7859 previous,
7860 next: None,
7861 },
7862 );
7863 assert!(
7864 replaced.is_none(),
7865 "CurrentThread task driver id must be unique"
7866 );
7867 if let Some(previous) = previous {
7868 self
7869 .entries
7870 .get_mut(&previous)
7871 .expect("the newest CurrentThread task driver must remain registered")
7872 .next = Some(id);
7873 } else {
7874 self.oldest = Some(id);
7875 }
7876 self.newest = Some(id);
7877 }
7878
7879 fn remove_driver(
7880 &mut self,
7881 id: CurrentThreadTaskDriverId,
7882 ) -> Option<CurrentThreadTaskDriverEntry> {
7883 let entry = self.entries.remove(&id)?;
7884 if let Some(previous) = entry.previous {
7885 self
7886 .entries
7887 .get_mut(&previous)
7888 .expect("the previous CurrentThread task driver must remain registered")
7889 .next = entry.next;
7890 } else {
7891 self.oldest = entry.next;
7892 }
7893 if let Some(next) = entry.next {
7894 self
7895 .entries
7896 .get_mut(&next)
7897 .expect("the next CurrentThread task driver must remain registered")
7898 .previous = entry.previous;
7899 } else {
7900 self.newest = entry.previous;
7901 }
7902 if let Some(delivery) = &entry.delivery {
7903 let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
7904 debug_assert_eq!(
7905 indexed,
7906 Some(id),
7907 "delivery capability index must match its driver"
7908 );
7909 }
7910 Some(entry)
7911 }
7912
7913 fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7914 let mut snapshot = Vec::with_capacity(self.entries.len());
7915 let mut current = self.oldest;
7916 while let Some(id) = current {
7917 let entry = self
7918 .entries
7919 .get(&id)
7920 .expect("CurrentThread task driver order must reference a live entry");
7921 snapshot.push((id, Arc::clone(&entry.driver)));
7922 current = entry.next;
7923 }
7924 debug_assert_eq!(
7925 snapshot.len(),
7926 self.entries.len(),
7927 "CurrentThread task driver order must contain every entry exactly once"
7928 );
7929 snapshot
7930 }
7931
7932 fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
7933 if self.entries.len() != expected.len() {
7934 return false;
7935 }
7936 let mut current = self.oldest;
7937 for expected in expected {
7938 if current != Some(*expected) {
7939 return false;
7940 }
7941 current = self
7942 .entries
7943 .get(expected)
7944 .expect("CurrentThread task driver order must reference a live entry")
7945 .next;
7946 }
7947 current.is_none()
7948 }
7949}
7950
7951struct CurrentThreadTaskDriverSelection {
7952 live: CurrentThreadTaskDriverSnapshot,
7953 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7954}
7955
7956#[derive(Default)]
7957struct CurrentThreadTaskDeliveryCompletion {
7958 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7959 redispatch: bool,
7960}
7961
7962struct CurrentThreadTaskDispatchCompletion {
7963 result: CurrentThreadHostDispatchResult,
7964 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7965}
7966
7967struct CurrentThreadTaskDispatchPublicationGuard<'a> {
7968 registry: &'a CurrentThreadTaskDriverRegistry,
7969 dispatch: Option<u64>,
7970}
7971
7972impl CurrentThreadTaskDispatchPublicationGuard<'_> {
7973 fn finish(mut self) -> CurrentThreadHostDispatchResult {
7974 let dispatch = self
7975 .dispatch
7976 .expect("the CurrentThread task dispatch publication must remain armed");
7977 let result = self.registry.finish_publication(dispatch);
7978 self.dispatch = None;
7979 result
7980 }
7981}
7982
7983impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
7984 fn drop(&mut self) {
7985 if let Some(dispatch) = self.dispatch.take() {
7986 self.registry.abort_publication(dispatch);
7987 }
7988 }
7989}
7990
7991#[derive(Default)]
7992struct CurrentThreadTaskDriverRegistry {
7993 state: Mutex<CurrentThreadTaskDriverRegistryState>,
7994 next_id: AtomicU64,
7995 next_delivery: AtomicU64,
7996}
7997
7998impl CurrentThreadTaskDriverRegistry {
7999 fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8000 let id = next_unique_id(
8001 &self.next_id,
8002 "CurrentThread task driver id space exhausted",
8003 );
8004 let id = CurrentThreadTaskDriverId(id);
8005 self
8006 .state
8007 .lock()
8008 .unwrap_or_else(std::sync::PoisonError::into_inner)
8009 .insert_driver(id, driver);
8010 id
8011 }
8012
8013 fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8014 let (removed, failed_dispatches) = {
8015 let mut state = self
8016 .state
8017 .lock()
8018 .unwrap_or_else(std::sync::PoisonError::into_inner);
8019 let removed = state.remove_driver(id);
8020 let mut failed_dispatches = Vec::new();
8021 if let Some(entry) = &removed {
8022 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8023 }
8024 (removed, failed_dispatches)
8025 };
8026 let _ = catch_unwind_contained(|| drop(removed));
8027 CurrentThreadTaskDeliveryCompletion {
8028 failed_dispatches,
8029 redispatch: false,
8030 }
8031 }
8032
8033 fn live(&self) -> CurrentThreadTaskDriverSelection {
8034 let mut failed_dispatches = Vec::new();
8035 loop {
8036 let snapshot = CurrentThreadTaskDriverSnapshot::new({
8037 let state = self
8038 .state
8039 .lock()
8040 .unwrap_or_else(std::sync::PoisonError::into_inner);
8041 state.ordered_snapshot()
8042 });
8043 let mut dead_ids = FxHashSet::default();
8044 let mut live_ids = Vec::new();
8045 for (id, driver) in snapshot.as_slice() {
8046 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8047 live_ids.push(*id);
8048 } else {
8049 dead_ids.insert(*id);
8050 }
8051 }
8052
8053 let (live, swept, retry) = {
8054 let mut state = self
8055 .state
8056 .lock()
8057 .unwrap_or_else(std::sync::PoisonError::into_inner);
8058 let mut swept = Vec::new();
8059 let mut current = state.oldest;
8060 while let Some(id) = current {
8061 let next = state
8062 .entries
8063 .get(&id)
8064 .expect("CurrentThread task driver order must reference a live entry")
8065 .next;
8066 if dead_ids.contains(&id)
8067 && let Some(entry) = state.remove_driver(id)
8068 {
8069 swept.push(entry);
8070 }
8071 current = next;
8072 }
8073 for entry in &swept {
8074 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8075 }
8076 let retry = !state.ordered_ids_match(&live_ids);
8077 let live = if retry {
8078 Vec::new()
8079 } else {
8080 state.ordered_snapshot()
8081 };
8082 (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8083 };
8084
8085 drop(snapshot);
8088 for entry in swept {
8089 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8090 let _ = catch_unwind_contained(|| drop(entry));
8091 }
8092 if !retry {
8093 return CurrentThreadTaskDriverSelection {
8094 live,
8095 failed_dispatches,
8096 };
8097 }
8098 }
8099 }
8100
8101 #[cfg(test)]
8102 fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8103 let mut live = self.live().live;
8104 live.pop().map(|(id, driver)| (id, driver.into_inner()))
8105 }
8106
8107 fn next_delivery(
8108 &self,
8109 driver: CurrentThreadTaskDriverId,
8110 dispatch: u64,
8111 ) -> CurrentThreadTaskDelivery {
8112 let capability = next_unique_id(
8113 &self.next_delivery,
8114 "CurrentThread task delivery id space exhausted",
8115 )
8116 .checked_add(1)
8117 .expect("CurrentThread task delivery id space exhausted");
8118 CurrentThreadTaskDelivery {
8119 driver,
8120 capability,
8121 dispatch,
8122 }
8123 }
8124
8125 fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8126 let dispatch_state = state
8127 .dispatches
8128 .get_mut(&dispatch)
8129 .expect("a referenced CurrentThread dispatch must remain registered");
8130 dispatch_state.references = dispatch_state
8131 .references
8132 .checked_add(1)
8133 .expect("CurrentThread dispatch reference count exhausted");
8134 }
8135
8136 fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8137 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8138 return;
8139 };
8140 dispatch_state.references = dispatch_state
8141 .references
8142 .checked_sub(1)
8143 .expect("CurrentThread dispatch reference count underflow");
8144 }
8145
8146 fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8147 let publication = {
8148 let mut state = self
8149 .state
8150 .lock()
8151 .unwrap_or_else(std::sync::PoisonError::into_inner);
8152 let dispatch_state = state.dispatches.entry(dispatch).or_default();
8153 if dispatch_state.is_serviced() {
8154 return CurrentThreadTaskDispatchCompletion {
8155 result: CurrentThreadHostDispatchResult::Accepted,
8156 failed_dispatches: Vec::new(),
8157 };
8158 }
8159 if dispatch_state.is_cancelling() {
8160 return CurrentThreadTaskDispatchCompletion {
8161 result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8162 failed_dispatches: Vec::new(),
8163 };
8164 }
8165 dispatch_state.publications = dispatch_state
8166 .publications
8167 .checked_add(1)
8168 .expect("CurrentThread dispatch publication count exhausted");
8169 CurrentThreadTaskDispatchPublicationGuard {
8170 registry: self,
8171 dispatch: Some(dispatch),
8172 }
8173 };
8174
8175 let CurrentThreadTaskDriverSelection {
8176 mut live,
8177 mut failed_dispatches,
8178 } = self.live();
8179 let planned_deliveries = {
8180 let mut state = self
8181 .state
8182 .lock()
8183 .unwrap_or_else(std::sync::PoisonError::into_inner);
8184 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8185 return CurrentThreadTaskDispatchCompletion {
8186 result: CurrentThreadHostDispatchResult::Unavailable,
8187 failed_dispatches,
8188 };
8189 };
8190 if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8191 Vec::new()
8192 } else {
8193 let mut reserved_deliveries = Vec::with_capacity(live.len());
8197 for (id, _) in live.as_slice().iter().rev() {
8198 let Some(entry) = state.entries.get(id) else {
8199 continue;
8200 };
8201 if entry.delivery.is_none() {
8202 reserved_deliveries.push(self.next_delivery(*id, dispatch));
8203 }
8204 }
8205 state.delivery_drivers.reserve(reserved_deliveries.len());
8206 let mut reserved_deliveries = reserved_deliveries.into_iter();
8207 let mut planned_deliveries = Vec::with_capacity(live.len());
8208 let mut retired = Vec::with_capacity(live.len());
8209 let mut accepted_reservation = false;
8210 for (id, _) in live.as_slice().iter().rev() {
8211 let Some(entry) = state.entries.get(id) else {
8212 continue;
8213 };
8214 if let Some(delivery) = &entry.delivery
8215 && delivery.delivery.dispatch == dispatch
8216 {
8217 accepted_reservation |= delivery.accepted;
8218 continue;
8219 }
8220 if entry.delivery.is_some() {
8221 let previous = state
8222 .entries
8223 .get_mut(id)
8224 .expect("the CurrentThread task driver must remain registered")
8225 .pending
8226 .replace(dispatch);
8227 if previous != Some(dispatch) {
8228 if let Some(previous) = previous {
8229 Self::remove_dispatch_reference(&mut state, previous);
8230 }
8231 Self::add_dispatch_reference(&mut state, dispatch);
8232 }
8233 retired.push(previous);
8234 accepted_reservation = true;
8235 continue;
8236 }
8237 let delivery = reserved_deliveries
8238 .next()
8239 .expect("every available CurrentThread host must have a reserved delivery");
8240 let pending = state
8241 .entries
8242 .get_mut(id)
8243 .expect("the CurrentThread task driver must remain registered")
8244 .pending
8245 .take();
8246 let accepted = pending == Some(dispatch);
8247 if !accepted && pending.is_some() {
8248 retired.push(pending);
8249 }
8250 match pending {
8251 Some(pending) if pending == dispatch => {}
8252 Some(pending) => {
8253 Self::remove_dispatch_reference(&mut state, pending);
8254 Self::add_dispatch_reference(&mut state, dispatch);
8255 }
8256 None => Self::add_dispatch_reference(&mut state, dispatch),
8257 }
8258 let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8259 assert!(
8260 indexed.is_none(),
8261 "CurrentThread delivery capability must be unique"
8262 );
8263 state
8264 .entries
8265 .get_mut(id)
8266 .expect("the CurrentThread task driver must remain registered")
8267 .delivery = Some(CurrentThreadTaskDeliveryState {
8268 delivery,
8269 claimed: false,
8270 accepted,
8271 });
8272 accepted_reservation |= accepted;
8273 planned_deliveries.push((*id, delivery));
8274 }
8275 let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8276 debug_assert!(
8277 all_reserved_deliveries_installed,
8278 "every reserved CurrentThread delivery must be installed"
8279 );
8280 if accepted_reservation {
8281 state
8282 .dispatches
8283 .get_mut(&dispatch)
8284 .expect("the publishing dispatch state must remain registered")
8285 .accepted = true;
8286 }
8287 for dispatch in retired.into_iter().flatten() {
8288 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8289 }
8290 planned_deliveries
8291 }
8292 };
8293
8294 let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8299 let mut planned_deliveries = planned_deliveries.into_iter();
8300 let mut next_delivery = planned_deliveries.next();
8301 while let Some((id, driver)) = live.pop() {
8302 let Some((planned_id, delivery)) = next_delivery else {
8303 break;
8304 };
8305 if planned_id == id {
8306 deliveries.push((id, driver, delivery));
8307 next_delivery = planned_deliveries.next();
8308 } else {
8309 next_delivery = Some((planned_id, delivery));
8310 }
8311 }
8312 debug_assert!(
8313 next_delivery.is_none() && planned_deliveries.len() == 0,
8314 "every planned CurrentThread delivery must retain its selected driver"
8315 );
8316
8317 for (id, driver, delivery) in deliveries {
8318 if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8319 self.accept_delivery(id, delivery);
8320 } else {
8321 let (entry, failures) = self.reject_delivery(id, delivery);
8322 failed_dispatches.extend(failures);
8323 if let Some(entry) = entry {
8324 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8325 let _ = catch_unwind_contained(|| drop(entry));
8326 }
8327 }
8328 }
8329
8330 let result = publication.finish();
8331 debug_assert!(
8332 !failed_dispatches
8333 .iter()
8334 .any(|failure| failure.dispatch == dispatch),
8335 "the active publication must report its own terminal outcome directly"
8336 );
8337 CurrentThreadTaskDispatchCompletion {
8338 result,
8339 failed_dispatches,
8340 }
8341 }
8342
8343 #[cfg(test)]
8344 fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8345 self.dispatch_with_completion(dispatch).result
8346 }
8347
8348 fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8349 let mut state = self
8350 .state
8351 .lock()
8352 .unwrap_or_else(std::sync::PoisonError::into_inner);
8353 let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8354 entry
8355 .delivery
8356 .as_ref()
8357 .is_some_and(|current| current.delivery == delivery)
8358 }) {
8359 state
8360 .entries
8361 .get_mut(&id)
8362 .expect("the matching CurrentThread task driver must remain registered")
8363 .delivery
8364 .as_mut()
8365 .expect("the matching delivery must remain installed")
8366 .accepted = true;
8367 true
8368 } else {
8369 false
8374 };
8375 if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8376 dispatch_state.accepted = true;
8377 dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8378 }
8379 }
8380
8381 fn reject_delivery(
8382 &self,
8383 id: CurrentThreadTaskDriverId,
8384 delivery: CurrentThreadTaskDelivery,
8385 ) -> (
8386 Option<CurrentThreadTaskDriverEntry>,
8387 Vec<CurrentThreadTaskDispatchFailure>,
8388 ) {
8389 let mut state = self
8390 .state
8391 .lock()
8392 .unwrap_or_else(std::sync::PoisonError::into_inner);
8393 if !state.entries.get(&id).is_some_and(|entry| {
8394 entry
8395 .delivery
8396 .as_ref()
8397 .is_some_and(|current| current.delivery == delivery)
8398 }) {
8399 return (None, Vec::new());
8400 }
8401 let entry = state
8402 .remove_driver(id)
8403 .expect("the matching CurrentThread task driver must remain registered");
8404 let mut failures = Vec::new();
8405 Self::finish_entry_references(&mut state, &entry, &mut failures);
8406 (Some(entry), failures)
8407 }
8408
8409 fn finish_delivery(
8410 &self,
8411 delivery: CurrentThreadTaskDelivery,
8412 failed: bool,
8413 ) -> CurrentThreadTaskDeliveryCompletion {
8414 let mut state = self
8415 .state
8416 .lock()
8417 .unwrap_or_else(std::sync::PoisonError::into_inner);
8418 if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8419 entry
8420 .delivery
8421 .as_ref()
8422 .is_some_and(|current| current.delivery == delivery)
8423 }) {
8424 return CurrentThreadTaskDeliveryCompletion::default();
8425 }
8426 let current = state
8427 .entries
8428 .get_mut(&delivery.driver)
8429 .expect("the matching CurrentThread task driver must remain registered")
8430 .delivery
8431 .take()
8432 .expect("the matching delivery must remain installed");
8433 let indexed = state.delivery_drivers.remove(&delivery.capability);
8434 debug_assert_eq!(
8435 indexed,
8436 Some(delivery.driver),
8437 "delivery capability index must match its driver"
8438 );
8439 let redispatch = state
8440 .entries
8441 .get(&delivery.driver)
8442 .expect("the matching CurrentThread task driver must remain registered")
8443 .pending
8444 .is_some_and(|dispatch| {
8445 state
8446 .dispatches
8447 .get(&dispatch)
8448 .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8449 });
8450 let mut failed_dispatches = Vec::new();
8451 Self::finish_dispatch_reference(
8456 &mut state,
8457 delivery.dispatch,
8458 true,
8459 failed || !current.claimed,
8460 &mut failed_dispatches,
8461 );
8462 CurrentThreadTaskDeliveryCompletion {
8463 failed_dispatches,
8464 redispatch,
8465 }
8466 }
8467
8468 fn claim_delivery(&self, capability: u64) -> Option<u64> {
8469 let mut state = self
8470 .state
8471 .lock()
8472 .unwrap_or_else(std::sync::PoisonError::into_inner);
8473 let driver = *state.delivery_drivers.get(&capability)?;
8474 let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8475 if delivery.capability != capability {
8476 return None;
8477 }
8478 let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8479 if dispatch_state.is_serviced()
8480 || dispatch_state.is_cancelling()
8481 || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8482 {
8483 return None;
8484 }
8485 let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8486 current.claimed = true;
8487 current.accepted = true;
8488 state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8489 Some(delivery.dispatch)
8490 }
8491
8492 fn mark_serviced(&self, dispatch: u64) {
8493 let mut state = self
8494 .state
8495 .lock()
8496 .unwrap_or_else(std::sync::PoisonError::into_inner);
8497 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8498 return;
8499 };
8500 dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8501 let mut removed_references = 0usize;
8502 for entry in state.entries.values_mut() {
8503 if entry.pending == Some(dispatch) {
8504 entry.pending = None;
8505 removed_references = removed_references
8506 .checked_add(1)
8507 .expect("CurrentThread dispatch reference count exhausted");
8508 }
8509 }
8510 let dispatch_state = state
8511 .dispatches
8512 .get_mut(&dispatch)
8513 .expect("the serviced CurrentThread dispatch must remain registered");
8514 dispatch_state.references = dispatch_state
8515 .references
8516 .checked_sub(removed_references)
8517 .expect("CurrentThread dispatch reference count underflow");
8518 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8519 }
8520
8521 fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8522 let delivery = {
8523 let state = self
8524 .state
8525 .lock()
8526 .unwrap_or_else(std::sync::PoisonError::into_inner);
8527 state
8528 .delivery_drivers
8529 .get(&capability)
8530 .and_then(|driver| state.entries.get(driver))
8531 .and_then(|entry| entry.delivery.as_ref())
8532 .map(|delivery| delivery.delivery)
8533 .filter(|delivery| delivery.capability == capability)
8534 };
8535 delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8536 self.finish_delivery(delivery, true)
8537 })
8538 }
8539
8540 fn finish_dispatch_reference(
8541 state: &mut CurrentThreadTaskDriverRegistryState,
8542 dispatch: u64,
8543 accepted: bool,
8544 failed: bool,
8545 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8546 ) {
8547 let (publications, references) = {
8548 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8549 return;
8550 };
8551 dispatch_state.references = dispatch_state
8552 .references
8553 .checked_sub(1)
8554 .expect("CurrentThread dispatch reference count underflow");
8555 dispatch_state.accepted |= accepted;
8556 dispatch_state.accepted_failure |= accepted && failed;
8557 (dispatch_state.publications, dispatch_state.references)
8558 };
8559 if references != 0 || publications != 0 {
8560 return;
8561 }
8562 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8563 return;
8564 };
8565 if dispatch_state.accepted_failure
8566 && !dispatch_state.is_serviced()
8567 && !dispatch_state.is_cancelling()
8568 {
8569 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8570 failures.push(dispatch_state.failure(dispatch));
8571 } else if !dispatch_state.is_cancelling() {
8572 state.dispatches.remove(&dispatch);
8573 }
8574 }
8575
8576 fn finish_entry_references(
8577 state: &mut CurrentThreadTaskDriverRegistryState,
8578 entry: &CurrentThreadTaskDriverEntry,
8579 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8580 ) {
8581 if let Some(delivery) = &entry.delivery {
8582 Self::finish_dispatch_reference(
8583 state,
8584 delivery.delivery.dispatch,
8585 delivery.accepted,
8586 delivery.accepted,
8587 failures,
8588 );
8589 }
8590 if let Some(pending) = entry.pending {
8591 Self::finish_dispatch_reference(state, pending, true, true, failures);
8592 }
8593 }
8594
8595 fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8596 let mut state = self
8597 .state
8598 .lock()
8599 .unwrap_or_else(std::sync::PoisonError::into_inner);
8600 let publications = {
8601 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8602 return CurrentThreadHostDispatchResult::Unavailable;
8603 };
8604 dispatch_state.publications = dispatch_state
8605 .publications
8606 .checked_sub(1)
8607 .expect("CurrentThread dispatch publication count underflow");
8608 dispatch_state.publications
8609 };
8610 if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8611 return CurrentThreadHostDispatchResult::Accepted;
8612 }
8613 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8614 return CurrentThreadHostDispatchResult::Unavailable;
8615 };
8616 if dispatch_state.is_cancelling() {
8617 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8618 }
8619 if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8620 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8621 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8622 }
8623 let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8624 state.dispatches.remove(&dispatch);
8625 if accepted {
8626 CurrentThreadHostDispatchResult::Accepted
8627 } else {
8628 CurrentThreadHostDispatchResult::Unavailable
8629 }
8630 }
8631
8632 fn abort_publication(&self, dispatch: u64) {
8633 let mut state = self
8634 .state
8635 .lock()
8636 .unwrap_or_else(std::sync::PoisonError::into_inner);
8637 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8638 return;
8639 };
8640 let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8641 return;
8642 };
8643 dispatch_state.publications = publications;
8644 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8645 }
8646
8647 fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8648 let mut state = self
8649 .state
8650 .lock()
8651 .unwrap_or_else(std::sync::PoisonError::into_inner);
8652 let can_rearm = state
8653 .dispatches
8654 .get(&failure.dispatch)
8655 .is_some_and(|dispatch_state| {
8656 dispatch_state.is_cancelling()
8657 && dispatch_state.epoch == failure.epoch
8658 && dispatch_state.publications == 0
8659 })
8660 && !Self::has_dispatch_reference(&state, failure.dispatch);
8661 if !can_rearm {
8662 return false;
8663 }
8664 let dispatch_state = state
8665 .dispatches
8666 .get_mut(&failure.dispatch)
8667 .expect("the exact failed CurrentThread dispatch must remain registered");
8668 let epoch = dispatch_state
8669 .epoch
8670 .checked_add(1)
8671 .expect("CurrentThread dispatch failure epoch space exhausted");
8672 *dispatch_state = CurrentThreadDispatchState {
8673 epoch,
8674 ..CurrentThreadDispatchState::default()
8675 };
8676 true
8677 }
8678
8679 fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8680 self
8681 .state
8682 .lock()
8683 .unwrap_or_else(std::sync::PoisonError::into_inner)
8684 .dispatches
8685 .get(&failure.dispatch)
8686 .is_some_and(|dispatch_state| {
8687 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8688 })
8689 }
8690
8691 fn retire_unreferenced_dispatch(
8692 state: &mut CurrentThreadTaskDriverRegistryState,
8693 dispatch: Option<u64>,
8694 ) {
8695 let Some(dispatch) = dispatch else {
8696 return;
8697 };
8698 if Self::has_dispatch_reference(state, dispatch) {
8699 return;
8700 }
8701 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8702 return;
8703 };
8704 if dispatch_state.publications == 0
8705 && !dispatch_state.is_cancelling()
8706 && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8707 {
8708 state.dispatches.remove(&dispatch);
8709 }
8710 }
8711
8712 fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8713 state
8714 .dispatches
8715 .get(&dispatch)
8716 .is_some_and(|dispatch_state| dispatch_state.references != 0)
8717 }
8718
8719 fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8720 let mut state = self
8721 .state
8722 .lock()
8723 .unwrap_or_else(std::sync::PoisonError::into_inner);
8724 if state
8725 .dispatches
8726 .get(&failure.dispatch)
8727 .is_some_and(|dispatch_state| {
8728 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8729 })
8730 {
8731 state.dispatches.remove(&failure.dispatch);
8732 }
8733 }
8734
8735 fn retire_for_shutdown(&self) {
8736 let mut state = self
8737 .state
8738 .lock()
8739 .unwrap_or_else(std::sync::PoisonError::into_inner);
8740 state.dispatches.clear();
8741 for entry in state.entries.values_mut() {
8742 entry.pending = None;
8743 }
8744 }
8745}
8746
8747static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8748 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8749
8750pub fn register_current_thread_task_driver(
8751 driver: Arc<dyn CurrentThreadTaskDriver>,
8752) -> CurrentThreadTaskDriverId {
8753 CURRENT_THREAD_TASK_DRIVERS.register(driver)
8754}
8755
8756pub fn request_current_thread_task_drain() {
8761 RUNTIME.request_current_thread_drain();
8762}
8763
8764pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8765 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8766}
8767
8768fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8769 let CurrentThreadTaskDispatchCompletion {
8770 result,
8771 failed_dispatches,
8772 } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8773 complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8774 failed_dispatches,
8775 redispatch: false,
8776 });
8777 result
8778}
8779
8780pub type TimerId = u64;
8781
8782static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8784
8785pub trait TimerDriver: Send + Sync + 'static {
8791 fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8795 fn cancel(&self, id: TimerId);
8798 fn is_live(&self) -> bool {
8803 true
8804 }
8805 fn on_swept(&self) {}
8815}
8816
8817#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8821pub struct TimerDriverId(u64);
8822
8823type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8824
8825struct TimerDriverEntry {
8826 driver: Arc<dyn TimerDriver>,
8827 previous: Option<TimerDriverId>,
8828 next: Option<TimerDriverId>,
8829}
8830
8831#[derive(Default)]
8832struct TimerDriverRegistryState {
8833 entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8834 oldest: Option<TimerDriverId>,
8835 newest: Option<TimerDriverId>,
8836}
8837
8838impl TimerDriverRegistryState {
8839 fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8840 let previous = self.newest;
8841 let replaced = self.entries.insert(
8842 id,
8843 TimerDriverEntry {
8844 driver,
8845 previous,
8846 next: None,
8847 },
8848 );
8849 assert!(replaced.is_none(), "timer driver id must be unique");
8850 if let Some(previous) = previous {
8851 self
8852 .entries
8853 .get_mut(&previous)
8854 .expect("the newest timer driver must remain registered")
8855 .next = Some(id);
8856 } else {
8857 self.oldest = Some(id);
8858 }
8859 self.newest = Some(id);
8860 }
8861
8862 fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
8863 let entry = self.entries.remove(&id)?;
8864 if let Some(previous) = entry.previous {
8865 self
8866 .entries
8867 .get_mut(&previous)
8868 .expect("the previous timer driver must remain registered")
8869 .next = entry.next;
8870 } else {
8871 self.oldest = entry.next;
8872 }
8873 if let Some(next) = entry.next {
8874 self
8875 .entries
8876 .get_mut(&next)
8877 .expect("the next timer driver must remain registered")
8878 .previous = entry.previous;
8879 } else {
8880 self.newest = entry.previous;
8881 }
8882 Some(entry)
8883 }
8884
8885 fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
8886 let mut snapshot = Vec::with_capacity(self.entries.len());
8887 let mut current = self.oldest;
8888 while let Some(id) = current {
8889 let entry = self
8890 .entries
8891 .get(&id)
8892 .expect("timer driver order must reference a live entry");
8893 snapshot.push((id, Arc::clone(&entry.driver)));
8894 current = entry.next;
8895 }
8896 debug_assert_eq!(
8897 snapshot.len(),
8898 self.entries.len(),
8899 "timer driver order must contain every entry exactly once"
8900 );
8901 snapshot
8902 }
8903
8904 fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
8905 if self.entries.len() != expected.len() {
8906 return false;
8907 }
8908 let mut current = self.oldest;
8909 for expected in expected {
8910 if current != Some(*expected) {
8911 return false;
8912 }
8913 current = self
8914 .entries
8915 .get(expected)
8916 .expect("timer driver order must reference a live entry")
8917 .next;
8918 }
8919 current.is_none()
8920 }
8921}
8922
8923#[derive(Default)]
8941pub struct TimerDriverRegistry {
8942 state: Mutex<TimerDriverRegistryState>,
8946 pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
8951 next_id: AtomicU64,
8952}
8953
8954impl Drop for TimerDriverRegistry {
8955 fn drop(&mut self) {
8956 let state = self
8957 .state
8958 .get_mut()
8959 .unwrap_or_else(std::sync::PoisonError::into_inner);
8960 state.oldest = None;
8961 state.newest = None;
8962 let entries = std::mem::take(&mut state.entries);
8963 for (_, entry) in entries {
8964 let _ = catch_unwind_contained(|| drop(entry));
8965 }
8966 }
8967}
8968
8969impl TimerDriverRegistry {
8970 pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8972 let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
8973 let id = TimerDriverId(id);
8974 self
8975 .state
8976 .lock()
8977 .unwrap_or_else(std::sync::PoisonError::into_inner)
8978 .insert_driver(id, driver);
8979 let pending = {
8984 let mut entries = self
8985 .pending
8986 .lock()
8987 .unwrap_or_else(std::sync::PoisonError::into_inner);
8988 let mut pending = Vec::with_capacity(entries.len());
8989 entries.retain(|_, repoll| {
8990 if let Some(repoll) = repoll.upgrade() {
8991 pending.push(repoll);
8992 true
8993 } else {
8994 false
8995 }
8996 });
8997 pending
8998 };
8999 for repoll in pending {
9000 repoll.wake();
9001 }
9002 id
9003 }
9004
9005 pub fn unregister(&self, id: TimerDriverId) {
9007 let removed = self
9008 .state
9009 .lock()
9010 .unwrap_or_else(std::sync::PoisonError::into_inner)
9011 .remove_driver(id);
9012 let _ = catch_unwind_contained(|| drop(removed));
9015 }
9016
9017 fn live(&self) -> HostTimerDriverSnapshot {
9028 loop {
9029 let snapshot = HostTimerDriverSnapshot::new({
9030 let state = self
9031 .state
9032 .lock()
9033 .unwrap_or_else(std::sync::PoisonError::into_inner);
9034 state.ordered_snapshot()
9035 });
9036 let mut dead_ids = FxHashSet::default();
9037 let mut live_ids = Vec::new();
9038 for (id, driver) in snapshot.as_slice() {
9039 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9040 live_ids.push(*id);
9041 } else {
9042 dead_ids.insert(*id);
9043 }
9044 }
9045
9046 let (live, swept, retry) = {
9047 let mut state = self
9048 .state
9049 .lock()
9050 .unwrap_or_else(std::sync::PoisonError::into_inner);
9051 let mut swept = Vec::new();
9052 let mut current = state.oldest;
9053 while let Some(id) = current {
9054 let next = state
9055 .entries
9056 .get(&id)
9057 .expect("timer driver order must reference a live entry")
9058 .next;
9059 if dead_ids.contains(&id)
9060 && let Some(entry) = state.remove_driver(id)
9061 {
9062 swept.push(entry);
9063 }
9064 current = next;
9065 }
9066 let retry = !state.ordered_ids_match(&live_ids);
9067 let live = if retry {
9068 Vec::new()
9069 } else {
9070 state.ordered_snapshot()
9071 };
9072 (HostTimerDriverSnapshot::new(live), swept, retry)
9073 };
9074
9075 drop(snapshot);
9078 for entry in swept {
9079 let _ = catch_unwind_contained(|| entry.driver.on_swept());
9080 let _ = catch_unwind_contained(|| drop(entry));
9081 }
9082 if !retry {
9083 return live;
9084 }
9085 }
9086 }
9087
9088 #[cfg(test)]
9089 fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9090 let mut live = self.live();
9091 live.pop().map(|(id, driver)| (id, driver.into_inner()))
9092 }
9093
9094 pub fn has_live_driver(&self) -> bool {
9096 !self.live().is_empty()
9097 }
9098
9099 fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9100 let replaced = self
9101 .pending
9102 .lock()
9103 .unwrap_or_else(std::sync::PoisonError::into_inner)
9104 .insert(id, Arc::downgrade(repoll));
9105 assert!(
9106 replaced.is_none(),
9107 "host timer {id} registered its repoll signal twice"
9108 );
9109 if repoll.is_closed() {
9112 self.remove_pending(id);
9113 }
9114 }
9115
9116 fn remove_pending(&self, id: TimerId) {
9117 let removed = self
9118 .pending
9119 .lock()
9120 .unwrap_or_else(std::sync::PoisonError::into_inner)
9121 .remove(&id);
9122 let _ = catch_unwind_contained(|| drop(removed));
9123 }
9124
9125 #[cfg(test)]
9126 fn pending_len(&self) -> usize {
9127 self
9128 .pending
9129 .lock()
9130 .unwrap_or_else(std::sync::PoisonError::into_inner)
9131 .len()
9132 }
9133}
9134
9135static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9138 LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9139
9140pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9146 TIMER_DRIVERS.register(driver)
9147}
9148
9149pub fn unregister_timer_driver(id: TimerDriverId) {
9153 TIMER_DRIVERS.unregister(id);
9154}
9155
9156pub fn has_live_timer_driver() -> bool {
9163 TIMER_DRIVERS.has_live_driver()
9164}
9165
9166#[derive(Default)]
9170struct HostTimerRepoll {
9171 state: Mutex<HostTimerRepollState>,
9172}
9173
9174#[derive(Default)]
9175struct HostTimerRepollState {
9176 closed: bool,
9177 notified: bool,
9178 waker: Option<Waker>,
9179}
9180
9181impl HostTimerRepoll {
9182 fn register(&self, waker: Waker) {
9183 let (replaced, wake) = {
9184 let mut state = self
9185 .state
9186 .lock()
9187 .unwrap_or_else(std::sync::PoisonError::into_inner);
9188 if state.closed {
9189 (Some(waker), None)
9190 } else if state.notified {
9191 state.notified = false;
9192 (state.waker.take(), Some(waker))
9193 } else {
9194 (state.waker.replace(waker), None)
9195 }
9196 };
9197 let _ = catch_unwind_contained(|| drop(replaced));
9198 if let Some(waker) = wake {
9199 wake_waker_contained(waker);
9200 }
9201 }
9202
9203 fn wake(&self) {
9204 let waker = {
9205 let mut state = self
9206 .state
9207 .lock()
9208 .unwrap_or_else(std::sync::PoisonError::into_inner);
9209 if state.closed {
9210 return;
9211 }
9212 if let Some(waker) = state.waker.take() {
9213 Some(waker)
9214 } else {
9215 state.notified = true;
9216 None
9217 }
9218 };
9219 if let Some(waker) = waker {
9220 wake_waker_contained(waker);
9221 }
9222 }
9223
9224 fn close(&self) {
9225 let waker = {
9226 let mut state = self
9227 .state
9228 .lock()
9229 .unwrap_or_else(std::sync::PoisonError::into_inner);
9230 state.closed = true;
9231 state.notified = false;
9232 state.waker.take()
9233 };
9234 let _ = catch_unwind_contained(|| drop(waker));
9235 }
9236
9237 fn is_closed(&self) -> bool {
9238 self
9239 .state
9240 .lock()
9241 .unwrap_or_else(std::sync::PoisonError::into_inner)
9242 .closed
9243 }
9244}
9245
9246#[derive(Default)]
9253struct HostTimerRegistry {
9254 state: Mutex<HostTimerRegistryState>,
9255}
9256
9257#[derive(Default)]
9258struct HostTimerRegistryState {
9259 closed: bool,
9260 timers: FxHashMap<TimerId, HostTimerEntry>,
9261}
9262
9263struct HostTimerEntry {
9264 deadline: Instant,
9265 waker: Waker,
9266 fired: Arc<AtomicBool>,
9267 drivers: Weak<TimerDriverRegistry>,
9268 repoll: Arc<HostTimerRepoll>,
9269}
9270
9271impl HostTimerRegistry {
9272 fn register(
9273 &self,
9274 id: TimerId,
9275 deadline: Instant,
9276 waker: Waker,
9277 fired: &Arc<AtomicBool>,
9278 drivers: &Arc<TimerDriverRegistry>,
9279 repoll: &Arc<HostTimerRepoll>,
9280 ) -> bool {
9281 let replaced = {
9282 let mut state = self
9283 .state
9284 .lock()
9285 .unwrap_or_else(std::sync::PoisonError::into_inner);
9286 if state.closed || fired.load(Ordering::SeqCst) {
9287 fired.store(true, Ordering::SeqCst);
9288 Err(waker)
9289 } else {
9290 Ok(state.timers.insert(
9291 id,
9292 HostTimerEntry {
9293 deadline,
9294 waker,
9295 fired: Arc::clone(fired),
9296 drivers: Arc::downgrade(drivers),
9297 repoll: Arc::clone(repoll),
9298 },
9299 ))
9300 }
9301 };
9302 match replaced {
9303 Ok(Some(entry)) => {
9304 let _ = catch_unwind_contained(|| drop(entry));
9305 true
9306 }
9307 Ok(None) => true,
9308 Err(waker) => {
9309 let _ = catch_unwind_contained(|| drop(waker));
9310 false
9311 }
9312 }
9313 }
9314
9315 fn remove(&self, id: TimerId) {
9316 let removed = self
9317 .state
9318 .lock()
9319 .unwrap_or_else(std::sync::PoisonError::into_inner)
9320 .timers
9321 .remove(&id);
9322 if let Some(entry) = removed {
9323 let _ = catch_unwind_contained(|| drop(entry));
9324 }
9325 }
9326
9327 fn shutdown(&self) {
9328 let timers = {
9329 let mut state = self
9330 .state
9331 .lock()
9332 .unwrap_or_else(std::sync::PoisonError::into_inner);
9333 state.closed = true;
9334 state
9335 .timers
9336 .drain()
9337 .map(|(id, entry)| {
9338 entry.fired.store(true, Ordering::SeqCst);
9339 (id, entry)
9340 })
9341 .collect::<Vec<_>>()
9342 };
9343 for (id, entry) in timers {
9344 entry.repoll.close();
9345 if let Some(drivers) = entry.drivers.upgrade() {
9346 drivers.remove_pending(id);
9347 }
9348 wake_waker_contained(entry.waker);
9349 }
9350 }
9351
9352 fn has_pending(&self) -> bool {
9357 !self
9358 .state
9359 .lock()
9360 .unwrap_or_else(std::sync::PoisonError::into_inner)
9361 .timers
9362 .is_empty()
9363 }
9364
9365 fn has_live_wait(&self, grace: Duration) -> bool {
9374 let now = Instant::now();
9375 self
9376 .state
9377 .lock()
9378 .unwrap_or_else(std::sync::PoisonError::into_inner)
9379 .timers
9380 .values()
9381 .any(|entry| {
9382 entry
9383 .deadline
9384 .checked_add(grace)
9385 .is_none_or(|deadline| deadline > now)
9386 })
9387 }
9388}
9389
9390struct HostTimerPendingGuard {
9394 registry: Arc<HostTimerRegistry>,
9395 drivers: Arc<TimerDriverRegistry>,
9396 repoll: Arc<HostTimerRepoll>,
9397 id: TimerId,
9398}
9399
9400impl Drop for HostTimerPendingGuard {
9401 fn drop(&mut self) {
9402 self.repoll.close();
9403 self.registry.remove(self.id);
9404 self.drivers.remove_pending(self.id);
9405 }
9406}
9407
9408#[cfg(not(target_family = "wasm"))]
9414struct TimerHeap {
9415 inner: Arc<Mutex<TimerHeapInner>>,
9416 parker: Arc<DriverParker>,
9417 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9418 deadlock_state: Arc<MultiThreadDeadlockState>,
9419 #[cfg(test)]
9420 fail_next_start: AtomicBool,
9421 generation: u64,
9422 executor_id: u64,
9423 thread_name: String,
9424}
9425
9426#[cfg(not(target_family = "wasm"))]
9427#[derive(Default)]
9428struct TimerHeapInner {
9429 queue: TimerDeadlineQueue,
9433 entries: FxHashMap<TimerId, HeapTimerEntry>,
9434 timekeeper_parker: Option<Arc<DriverParker>>,
9438 closed: bool,
9442}
9443
9444#[cfg(not(target_family = "wasm"))]
9445struct HeapTimerEntry {
9446 waker: Waker,
9447 fired: Arc<AtomicBool>,
9452}
9453
9454#[cfg(not(target_family = "wasm"))]
9455#[derive(Default)]
9456struct TimerDeadlineQueue {
9457 nodes: Vec<(Instant, TimerId)>,
9458 positions: FxHashMap<TimerId, usize>,
9459}
9460
9461#[cfg(not(target_family = "wasm"))]
9462impl TimerDeadlineQueue {
9463 #[cfg(test)]
9464 fn len(&self) -> usize {
9465 self.nodes.len()
9466 }
9467
9468 #[cfg(test)]
9469 fn is_empty(&self) -> bool {
9470 self.nodes.is_empty()
9471 }
9472
9473 fn clear(&mut self) {
9474 self.nodes.clear();
9475 self.positions.clear();
9476 }
9477
9478 fn peek(&self) -> Option<(Instant, TimerId)> {
9479 self.nodes.first().copied()
9480 }
9481
9482 fn push(&mut self, deadline: Instant, id: TimerId) {
9483 let index = self.nodes.len();
9484 assert!(
9485 self.positions.insert(id, index).is_none(),
9486 "timer deadline queue received duplicate id {id}"
9487 );
9488 self.nodes.push((deadline, id));
9489 self.sift_up(index);
9490 }
9491
9492 fn pop(&mut self) -> Option<(Instant, TimerId)> {
9493 let first = self.nodes.first().copied()?;
9494 self.positions.remove(&first.1);
9495 let last = self
9496 .nodes
9497 .pop()
9498 .expect("the non-empty timer heap must have a final node");
9499 if !self.nodes.is_empty() {
9500 self.nodes[0] = last;
9501 self.positions.insert(last.1, 0);
9502 self.sift_down(0);
9503 }
9504 Some(first)
9505 }
9506
9507 fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9508 let index = self.positions.remove(&id)?;
9509 let removed = self.nodes[index];
9510 let last = self.nodes.pop().expect("an indexed timer node must exist");
9511 if index < self.nodes.len() {
9512 self.nodes[index] = last;
9513 self.positions.insert(last.1, index);
9514 if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9515 self.sift_up(index);
9516 } else {
9517 self.sift_down(index);
9518 }
9519 }
9520 Some(removed)
9521 }
9522
9523 fn sift_up(&mut self, mut index: usize) {
9524 while index > 0 {
9525 let parent = (index - 1) / 2;
9526 if self.nodes[parent] <= self.nodes[index] {
9527 break;
9528 }
9529 self.swap(parent, index);
9530 index = parent;
9531 }
9532 }
9533
9534 fn sift_down(&mut self, mut index: usize) {
9535 loop {
9536 let left = index * 2 + 1;
9537 if left >= self.nodes.len() {
9538 return;
9539 }
9540 let right = left + 1;
9541 let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9542 right
9543 } else {
9544 left
9545 };
9546 if self.nodes[index] <= self.nodes[child] {
9547 return;
9548 }
9549 self.swap(index, child);
9550 index = child;
9551 }
9552 }
9553
9554 fn swap(&mut self, left: usize, right: usize) {
9555 self.nodes.swap(left, right);
9556 self.positions.insert(self.nodes[left].1, left);
9557 self.positions.insert(self.nodes[right].1, right);
9558 }
9559}
9560
9561#[cfg(not(target_family = "wasm"))]
9562impl TimerHeapInner {
9563 fn next_deadline(&self) -> Option<Instant> {
9564 self.queue.peek().map(|(deadline, _)| deadline)
9565 }
9566}
9567
9568#[cfg(not(target_family = "wasm"))]
9569impl TimerHeap {
9570 fn new(
9571 generation: u64,
9572 executor_id: u64,
9573 thread_name: String,
9574 deadlock_state: Arc<MultiThreadDeadlockState>,
9575 ) -> Self {
9576 Self {
9577 inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9578 parker: Arc::new(DriverParker::default()),
9579 thread: Mutex::new(None),
9580 deadlock_state,
9581 #[cfg(test)]
9582 fail_next_start: AtomicBool::new(false),
9583 generation,
9584 executor_id,
9585 thread_name,
9586 }
9587 }
9588
9589 fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9590 if self
9591 .inner
9592 .lock()
9593 .unwrap_or_else(std::sync::PoisonError::into_inner)
9594 .closed
9595 {
9596 return Ok(());
9597 }
9598 let current_thread = std::thread::current().id();
9599 loop {
9600 let finished = {
9601 let mut thread = self
9602 .thread
9603 .lock()
9604 .unwrap_or_else(std::sync::PoisonError::into_inner);
9605 match thread.as_ref() {
9606 Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9607 Some(handle) if !handle.is_finished() => return Ok(()),
9608 Some(_) => thread.take(),
9609 None => {
9610 if self
9613 .inner
9614 .lock()
9615 .unwrap_or_else(std::sync::PoisonError::into_inner)
9616 .closed
9617 {
9618 return Ok(());
9619 }
9620 #[cfg(test)]
9621 if self.fail_next_start.swap(false, Ordering::SeqCst) {
9622 return Err(RuntimeConfigError(
9623 "injected runtime timer thread creation failure".to_string(),
9624 ));
9625 }
9626
9627 let inner = Arc::clone(&self.inner);
9628 let parker = Arc::clone(&self.parker);
9629 let deadlock_state = Arc::clone(&self.deadlock_state);
9630 let generation = self.generation;
9631 let executor_id = self.executor_id;
9632 let handle = std::thread::Builder::new()
9633 .name(self.thread_name.clone())
9634 .spawn(move || {
9635 ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9638 let _generation = RuntimeGenerationGuard::enter(generation);
9639 timer_timekeeper_main(&inner, &parker, &deadlock_state);
9640 })
9641 .map_err(|error| {
9642 RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9643 })?;
9644 *thread = Some(handle);
9645 return Ok(());
9646 }
9647 }
9648 };
9649
9650 if let Some(finished) = finished
9654 && finished.thread().id() != current_thread
9655 && let Err(payload) = finished.join()
9656 {
9657 discard_panic_payload(payload);
9658 }
9659 if self
9660 .inner
9661 .lock()
9662 .unwrap_or_else(std::sync::PoisonError::into_inner)
9663 .closed
9664 {
9665 return Ok(());
9666 }
9667 }
9668 }
9669
9670 fn close(&self) -> Vec<Waker> {
9671 let mut inner = self
9672 .inner
9673 .lock()
9674 .unwrap_or_else(std::sync::PoisonError::into_inner);
9675 inner.closed = true;
9676 inner.queue.clear();
9677 inner.timekeeper_parker = None;
9678 inner
9679 .entries
9680 .drain()
9681 .map(|(_, entry)| {
9682 entry.fired.store(true, Ordering::SeqCst);
9683 entry.waker
9684 })
9685 .collect()
9686 }
9687
9688 fn join(&self) {
9689 let handle = self
9690 .thread
9691 .lock()
9692 .unwrap_or_else(std::sync::PoisonError::into_inner)
9693 .take();
9694 if let Some(handle) = handle {
9695 if handle.thread().id() == std::thread::current().id() {
9696 drop(handle);
9700 return;
9701 }
9702 if let Err(payload) = handle.join() {
9703 discard_panic_payload(payload);
9704 }
9705 }
9706 }
9707}
9708
9709#[cfg(not(target_family = "wasm"))]
9710impl Drop for TimerHeap {
9711 fn drop(&mut self) {
9712 let mut admission = self.deadlock_state.begin_admission();
9713 let wakers = self.close();
9714 if !wakers.is_empty()
9715 && let Some(admission) = admission.as_mut()
9716 {
9717 admission.mark_published();
9718 }
9719 self.parker.unpark();
9720 for waker in wakers {
9721 wake_waker_contained(waker);
9722 }
9723 drop(admission);
9724 self.join();
9725 }
9726}
9727
9728#[cfg(not(target_family = "wasm"))]
9729fn timer_timekeeper_main(
9730 inner: &Mutex<TimerHeapInner>,
9731 parker: &Arc<DriverParker>,
9732 deadlock_state: &Arc<MultiThreadDeadlockState>,
9733) {
9734 loop {
9735 let mut admission = deadlock_state.begin_admission();
9739 let (due, deadline) = {
9740 let mut inner = inner
9741 .lock()
9742 .unwrap_or_else(std::sync::PoisonError::into_inner);
9743 if inner.closed {
9744 inner.timekeeper_parker = None;
9745 return;
9746 }
9747 let now = Instant::now();
9748 let mut due = Vec::new();
9749 while let Some((deadline, id)) = inner.queue.peek() {
9750 if deadline > now {
9751 break;
9752 }
9753 inner.queue.pop();
9754 if let Some(entry) = inner.entries.remove(&id) {
9755 entry.fired.store(true, Ordering::SeqCst);
9756 due.push(entry.waker);
9757 }
9758 }
9759 let deadline = inner.next_deadline();
9760 inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9761 (due, deadline)
9762 };
9763
9764 if !due.is_empty() {
9765 if let Some(admission) = admission.as_mut() {
9766 admission.mark_published();
9767 }
9768 for waker in due {
9769 wake_waker_contained(waker);
9770 }
9771 continue;
9772 }
9773 drop(admission);
9776
9777 match deadline {
9778 Some(deadline) => {
9779 let now = Instant::now();
9780 if deadline > now {
9781 parker.park_timeout(deadline - now);
9782 }
9783 }
9784 None => parker.park(),
9785 }
9786 }
9787}
9788
9789#[cfg(not(target_family = "wasm"))]
9790impl MultiThreadExecutor {
9791 fn register_timer(
9797 self: &Arc<Self>,
9798 id: TimerId,
9799 deadline: Instant,
9800 waker: Waker,
9801 fired: &Arc<AtomicBool>,
9802 ) {
9803 let mut admission = self.begin_deadlock_admission();
9804 #[cfg(test)]
9805 run_timer_registration_admission_test_hook();
9806 if let Err(error) = self.timers.ensure_started() {
9810 let _ = catch_unwind_contained(|| drop(waker));
9811 panic!("{error}");
9812 }
9813 let (became_earliest, published, discarded_waker) = {
9814 let mut inner = self
9815 .timers
9816 .inner
9817 .lock()
9818 .unwrap_or_else(std::sync::PoisonError::into_inner);
9819 if inner.closed || fired.load(Ordering::SeqCst) {
9820 fired.store(true, Ordering::SeqCst);
9821 (false, false, Some(waker))
9822 } else if let Some(entry) = inner.entries.get_mut(&id) {
9823 (
9825 false,
9826 false,
9827 Some(std::mem::replace(&mut entry.waker, waker)),
9828 )
9829 } else {
9830 let previous = inner.next_deadline();
9831 inner.entries.insert(
9832 id,
9833 HeapTimerEntry {
9834 waker,
9835 fired: Arc::clone(fired),
9836 },
9837 );
9838 inner.queue.push(deadline, id);
9839 (
9840 previous.is_none_or(|previous| deadline < previous),
9841 true,
9842 None,
9843 )
9844 }
9845 };
9846 if published && let Some(admission) = admission.as_mut() {
9847 admission.mark_published();
9848 }
9849 if let Some(waker) = discarded_waker {
9850 let _ = catch_unwind_contained(|| drop(waker));
9853 }
9854 if became_earliest {
9855 self.note_earlier_timer();
9857 }
9858 }
9859
9860 fn cancel_timer(&self, id: TimerId) {
9864 let (removed, earliest_changed) = {
9865 let mut inner = self
9866 .timers
9867 .inner
9868 .lock()
9869 .unwrap_or_else(std::sync::PoisonError::into_inner);
9870 let previous = inner.next_deadline();
9871 let removed = inner.entries.remove(&id);
9872 if removed.is_some() {
9873 let queued = inner.queue.remove(id);
9874 debug_assert!(
9875 queued.is_some(),
9876 "a live timer entry must have one indexed deadline node"
9877 );
9878 }
9879 let current = inner.next_deadline();
9880 (removed, previous != current)
9881 };
9882 if let Some(entry) = removed {
9883 let _ = catch_unwind_contained(|| drop(entry));
9886 }
9887 if earliest_changed {
9888 self.timers.parker.unpark();
9889 self.parked_drivers.wake_all();
9890 }
9891 }
9892
9893 fn next_timer_deadline(&self) -> Option<Instant> {
9895 self
9896 .timers
9897 .inner
9898 .lock()
9899 .unwrap_or_else(std::sync::PoisonError::into_inner)
9900 .next_deadline()
9901 }
9902
9903 fn fire_due_timers(&self) {
9908 let mut admission = self.begin_deadlock_admission();
9909 let due: Vec<Waker> = {
9910 let mut inner = self
9911 .timers
9912 .inner
9913 .lock()
9914 .unwrap_or_else(std::sync::PoisonError::into_inner);
9915 let now = Instant::now();
9916 let mut due = Vec::new();
9917 while let Some((deadline, id)) = inner.queue.peek() {
9918 if deadline > now {
9919 break;
9920 }
9921 inner.queue.pop();
9922 if let Some(entry) = inner.entries.remove(&id) {
9923 entry.fired.store(true, Ordering::SeqCst);
9924 due.push(entry.waker);
9925 }
9926 }
9927 due
9928 };
9929 if !due.is_empty()
9930 && let Some(admission) = admission.as_mut()
9931 {
9932 admission.mark_published();
9933 }
9934 for waker in due {
9935 wake_waker_contained(waker);
9938 }
9939 }
9940
9941 fn note_earlier_timer(self: &Arc<Self>) {
9945 self.timers.parker.unpark();
9946 self.parked_drivers.wake_one();
9947 }
9948
9949 fn shutdown_timers(&self) {
9955 let mut admission = self.begin_deadlock_admission();
9956 let wakers = self.timers.close();
9957 if !wakers.is_empty()
9958 && let Some(admission) = admission.as_mut()
9959 {
9960 admission.mark_published();
9961 }
9962 for waker in wakers {
9963 wake_waker_contained(waker);
9967 }
9968 self.timers.parker.unpark();
9969 }
9970
9971 fn begin_shutdown(&self) {
9972 let _generation = RuntimeGenerationGuard::enter(self.generation);
9973 self.stop.begin_shutdown();
9978 let queued = {
9979 let mut queue = self
9980 .blocking_queue
9981 .lock()
9982 .unwrap_or_else(std::sync::PoisonError::into_inner);
9983 queue.closed = true;
9986 queue.take_all()
9987 };
9988 for queued in queued {
9993 let _ = catch_unwind_contained(|| drop(queued));
9994 }
9995 self.shutdown_timers();
9996 }
9997
9998 fn wait_until_scheduler_idle(&self) {
9999 self.timers.join();
10000 let mut idle = self
10001 .scheduler_idle_lock
10002 .lock()
10003 .unwrap_or_else(std::sync::PoisonError::into_inner);
10004 *idle += 1;
10010 while self.active_drainers.load(Ordering::Acquire) != 0 {
10011 idle = self
10012 .scheduler_idle
10013 .wait(idle)
10014 .unwrap_or_else(std::sync::PoisonError::into_inner);
10015 }
10016 *idle -= 1;
10017 }
10018}
10019
10020pub struct Sleep {
10025 deadline: Instant,
10026 inner: SleepInner,
10027}
10028
10029enum SleepInner {
10030 #[cfg(not(target_family = "wasm"))]
10031 Heap(HeapSleep),
10032 Host(HostSleep),
10033}
10034
10035#[cfg(not(target_family = "wasm"))]
10036struct HeapSleep {
10037 id: TimerId,
10038 executor: Weak<MultiThreadExecutor>,
10039 fired: Arc<AtomicBool>,
10040 registered: bool,
10041}
10042
10043struct HostSleep {
10044 id: TimerId,
10045 drivers: Arc<TimerDriverRegistry>,
10049 armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10053 registry: Arc<HostTimerRegistry>,
10056 fired: Arc<AtomicBool>,
10057 repoll: Arc<HostTimerRepoll>,
10061 pending: Option<HostTimerPendingGuard>,
10066}
10067
10068fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10069 let _ = catch_unwind_contained(|| driver.cancel(id));
10070 let _ = catch_unwind_contained(|| drop(driver));
10071}
10072
10073fn cancel_host_timer_drivers(
10074 id: TimerId,
10075 armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10076) {
10077 for (_, driver) in armed.drain() {
10078 cancel_host_timer_driver(id, driver);
10079 }
10080}
10081
10082impl Future for Sleep {
10083 type Output = ();
10084
10085 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10086 let deadline = self.deadline;
10087 let this = self.get_mut();
10088 match &mut this.inner {
10089 #[cfg(not(target_family = "wasm"))]
10090 SleepInner::Heap(heap) => {
10091 if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10092 if heap.registered {
10093 if let Some(executor) = heap.executor.upgrade() {
10096 executor.cancel_timer(heap.id);
10097 }
10098 heap.registered = false;
10099 }
10100 return Poll::Ready(());
10101 }
10102 let Some(executor) = heap.executor.upgrade() else {
10103 heap.fired.store(true, Ordering::SeqCst);
10104 heap.registered = false;
10105 return Poll::Ready(());
10106 };
10107 executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10108 heap.registered = true;
10109 if heap.fired.load(Ordering::SeqCst) {
10113 heap.registered = false;
10114 return Poll::Ready(());
10115 }
10116 Poll::Pending
10117 }
10118 SleepInner::Host(host) => {
10119 if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10120 cancel_host_timer_drivers(host.id, &mut host.armed);
10121 host.pending.take();
10122 return Poll::Ready(());
10123 }
10124 let repoll_waker = cx.waker().clone();
10129 let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10130 Ok(waker) => waker,
10131 Err(payload) => {
10132 let _ = catch_unwind_contained(|| drop(repoll_waker));
10133 resume_unwind(payload);
10134 }
10135 };
10136 host.repoll.register(repoll_waker);
10137 if !host.registry.register(
10138 host.id,
10139 deadline,
10140 registry_waker,
10141 &host.fired,
10142 &host.drivers,
10143 &host.repoll,
10144 ) {
10145 cancel_host_timer_drivers(host.id, &mut host.armed);
10146 if host.pending.take().is_none() {
10147 host.repoll.close();
10148 }
10149 return Poll::Ready(());
10150 }
10151 if host.pending.is_none() {
10152 host.drivers.register_pending(host.id, &host.repoll);
10160 host.pending = Some(HostTimerPendingGuard {
10161 registry: Arc::clone(&host.registry),
10162 drivers: Arc::clone(&host.drivers),
10163 repoll: Arc::clone(&host.repoll),
10164 id: host.id,
10165 });
10166 }
10167 let current = host.drivers.live();
10171 assert!(
10172 !current.as_slice().is_empty(),
10173 "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10174 that registered timer drivers (via \
10175 `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10176 one per importing env through `registerTimerHost`) have all been torn down, so this \
10177 `sleep_until` can never be woken."
10178 );
10179 let missing = current
10185 .as_slice()
10186 .iter()
10187 .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10188 .count();
10189 host.armed.reserve(missing);
10190 for (current_id, current_driver) in current.as_slice() {
10191 host
10192 .armed
10193 .entry(*current_id)
10194 .or_insert_with(|| Arc::clone(current_driver));
10195 }
10196 for (current_id, _) in current.as_slice() {
10197 let armed_driver = host
10198 .armed
10199 .get(current_id)
10200 .expect("every live timer driver must have cancellation ownership before callbacks");
10201 armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10202 }
10203 let current_ids = current
10204 .as_slice()
10205 .iter()
10206 .map(|(current_id, _)| *current_id)
10207 .collect::<FxHashSet<_>>();
10208 let retired = host
10209 .armed
10210 .keys()
10211 .filter(|armed_id| !current_ids.contains(armed_id))
10212 .copied()
10213 .collect::<Vec<_>>();
10214 for retired in retired {
10215 let old_driver = host
10216 .armed
10217 .remove(&retired)
10218 .expect("the retired timer driver must remain armed");
10219 cancel_host_timer_driver(host.id, old_driver);
10220 }
10221 Poll::Pending
10222 }
10223 }
10224 }
10225}
10226
10227impl Drop for Sleep {
10228 fn drop(&mut self) {
10229 match &mut self.inner {
10230 #[cfg(not(target_family = "wasm"))]
10231 SleepInner::Heap(heap) => {
10232 if heap.registered
10233 && let Some(executor) = heap.executor.upgrade()
10234 {
10235 executor.cancel_timer(heap.id);
10236 }
10237 }
10238 SleepInner::Host(host) => {
10239 host.repoll.close();
10242 cancel_host_timer_drivers(host.id, &mut host.armed);
10243 }
10245 }
10246 }
10247}
10248
10249pub fn sleep_until(deadline: Instant) -> Sleep {
10254 make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10255}
10256
10257fn make_sleep(
10261 backend: &RuntimeBackend,
10262 host_drivers: &Arc<TimerDriverRegistry>,
10263 deadline: Instant,
10264) -> Sleep {
10265 let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10266 match &backend.executor {
10267 #[cfg(not(target_family = "wasm"))]
10268 RuntimeExecutor::MultiThread(executor) => Sleep {
10269 deadline,
10270 inner: SleepInner::Heap(HeapSleep {
10271 id,
10272 executor: Arc::downgrade(executor),
10273 fired: Arc::new(AtomicBool::new(false)),
10274 registered: false,
10275 }),
10276 },
10277 RuntimeExecutor::CurrentThread(executor) => {
10278 assert!(
10286 host_drivers.has_live_driver(),
10287 "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10288 single-thread flavor delegates timers to the host event loop. Install one via \
10289 `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10290 a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10291 );
10292 Sleep {
10299 deadline,
10300 inner: SleepInner::Host(HostSleep {
10301 id,
10302 drivers: Arc::clone(host_drivers),
10303 armed: FxHashMap::default(),
10304 registry: Arc::clone(&executor.host_timers),
10305 fired: Arc::new(AtomicBool::new(false)),
10306 repoll: Arc::new(HostTimerRepoll::default()),
10307 pending: None,
10308 }),
10309 }
10310 }
10311 }
10312}
10313
10314#[derive(Clone)]
10315enum RuntimeExecutor {
10316 CurrentThread(Arc<CurrentThreadExecutor>),
10317 #[cfg(not(target_family = "wasm"))]
10318 MultiThread(Arc<MultiThreadExecutor>),
10319}
10320
10321enum WeakRuntimeExecutor {
10322 CurrentThread(Weak<CurrentThreadExecutor>),
10323 #[cfg(not(target_family = "wasm"))]
10324 MultiThread(Weak<MultiThreadExecutor>),
10325}
10326
10327impl WeakRuntimeExecutor {
10328 fn schedule(&self, runnable: Runnable) {
10329 match self {
10330 Self::CurrentThread(executor) => {
10331 if let Some(executor) = executor.upgrade() {
10332 executor.schedule(runnable);
10333 }
10334 }
10335 #[cfg(not(target_family = "wasm"))]
10336 Self::MultiThread(executor) => {
10337 if let Some(executor) = executor.upgrade() {
10338 executor.schedule(runnable);
10339 }
10340 }
10341 }
10342 }
10343}
10344
10345#[derive(Clone)]
10346struct RuntimeBackend {
10347 work: Arc<GenerationWork>,
10348 executor: RuntimeExecutor,
10349}
10350
10351impl RuntimeBackend {
10352 fn new(
10353 options: &RuntimeOptions,
10354 metrics: Arc<RuntimeMetrics>,
10355 ) -> Result<Self, RuntimeConfigError> {
10356 #[cfg(test)]
10357 if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10358 return Err(RuntimeConfigError(
10359 "injected async runtime backend creation failure".to_string(),
10360 ));
10361 }
10362
10363 let work = GenerationWork::new();
10364 let stop = Arc::new(GenerationStop::default());
10365 let executor = match options.flavor {
10366 RuntimeFlavor::CurrentThread => {
10367 let executor = CurrentThreadExecutor::with_generation(
10368 metrics,
10369 THREADLESS_BUILD,
10370 options.park_deadline,
10371 work.id,
10372 Arc::clone(&stop),
10373 );
10374 executor.attach_generation_work(&work);
10375 RuntimeExecutor::CurrentThread(Arc::new(executor))
10376 }
10377 RuntimeFlavor::MultiThread => {
10378 #[cfg(not(target_family = "wasm"))]
10379 {
10380 RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10381 options, metrics, work.id, stop,
10382 )?))
10383 }
10384 #[cfg(target_family = "wasm")]
10385 {
10386 let _ = metrics;
10387 return Err(RuntimeConfigError(
10388 "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
10389 ));
10390 }
10391 }
10392 };
10393 Ok(Self { work, executor })
10394 }
10395
10396 #[cfg(test)]
10397 fn from_executor(executor: RuntimeExecutor) -> Self {
10398 let work = GenerationWork::new();
10399 if let RuntimeExecutor::CurrentThread(executor) = &executor {
10400 executor.attach_generation_work(&work);
10401 }
10402 Self { work, executor }
10403 }
10404
10405 fn generation(&self) -> u64 {
10406 self.work.id
10407 }
10408
10409 fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10410 match &self.executor {
10411 RuntimeExecutor::CurrentThread(executor) => {
10412 WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10413 }
10414 #[cfg(not(target_family = "wasm"))]
10415 RuntimeExecutor::MultiThread(executor) => {
10416 WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10417 }
10418 }
10419 }
10420
10421 fn schedule_submission(&self, runnable: Runnable) {
10425 match &self.executor {
10426 RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10427 #[cfg(not(target_family = "wasm"))]
10428 RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10429 }
10430 }
10431
10432 fn spawn_registered_blocking<F, T>(
10433 &self,
10434 function: F,
10435 registration: GenerationWorkGuard,
10436 metrics: &Arc<RuntimeMetrics>,
10437 ) -> JoinHandle<T>
10438 where
10439 F: FnOnce() -> T + Send + 'static,
10440 T: Send + 'static,
10441 {
10442 match &self.executor {
10443 RuntimeExecutor::CurrentThread(executor) => {
10444 let _ = metrics;
10445 executor.schedule_blocking(function, registration)
10446 }
10447 #[cfg(not(target_family = "wasm"))]
10448 RuntimeExecutor::MultiThread(executor) => {
10449 let generation = self.generation();
10450 let _ = metrics;
10451 let function = RegisteredBlockingFunction::new(function, registration, generation);
10452 executor.schedule_blocking_result(move || function.run())
10453 }
10454 }
10455 }
10456
10457 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10458 match &self.executor {
10459 RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10460 #[cfg(not(target_family = "wasm"))]
10461 RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10462 }
10463 }
10464
10465 fn generation_stop(&self) -> Arc<GenerationStop> {
10466 match &self.executor {
10467 RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10468 #[cfg(not(target_family = "wasm"))]
10469 RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10470 }
10471 }
10472
10473 fn begin_shutdown(&self) {
10474 let _generation = RuntimeGenerationGuard::enter(self.generation());
10475 self.work.close_and_abort();
10476 match &self.executor {
10477 RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10478 #[cfg(not(target_family = "wasm"))]
10479 RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10480 }
10481 }
10482
10483 fn wait_until_idle(&self) {
10484 match &self.executor {
10485 RuntimeExecutor::CurrentThread(executor) => {
10486 self.work.wait_until_idle_retiring(
10492 || executor.has_rejected_work(),
10493 || executor.retire_rejected_work(),
10494 );
10495 executor.wait_until_scheduler_idle();
10496 }
10497 #[cfg(not(target_family = "wasm"))]
10498 RuntimeExecutor::MultiThread(executor) => {
10499 self.work.wait_until_idle();
10500 executor.wait_until_scheduler_idle();
10501 }
10502 }
10503 }
10504
10505 #[cfg(not(target_family = "wasm"))]
10506 fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10507 match &self.executor {
10508 RuntimeExecutor::CurrentThread(_) => None,
10509 RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10510 }
10511 }
10512
10513 fn stop_identity(&self) -> RuntimeStopIdentity {
10514 RuntimeStopIdentity {
10515 generation: self.generation(),
10516 #[cfg(not(target_family = "wasm"))]
10517 executor_id: match &self.executor {
10518 RuntimeExecutor::CurrentThread(_) => None,
10519 RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10520 },
10521 }
10522 }
10523
10524 fn is_current(&self) -> bool {
10525 self.stop_identity().is_current()
10526 }
10527}
10528
10529#[derive(Clone, Copy)]
10530struct RuntimeStopIdentity {
10531 generation: u64,
10532 #[cfg(not(target_family = "wasm"))]
10533 executor_id: Option<u64>,
10534}
10535
10536impl RuntimeStopIdentity {
10537 fn is_current(self) -> bool {
10538 if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10539 return true;
10540 }
10541 #[cfg(not(target_family = "wasm"))]
10542 if let Some(executor_id) = self.executor_id
10543 && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10544 || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10545 {
10546 return true;
10547 }
10548 false
10549 }
10550}
10551
10552enum RuntimeLifecycle {
10553 Initial,
10554 StoppedBeforeFirstUse,
10555 Running(RuntimeBackend),
10556 Stopping(RuntimeStopIdentity),
10557 StoppingWithoutBackend(ZeroBackendShutdownTarget),
10558 Stopped,
10559}
10560
10561#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10562enum ZeroBackendShutdownTarget {
10563 StoppedBeforeFirstUse,
10564 Stopped,
10565}
10566
10567impl ZeroBackendShutdownTarget {
10568 fn into_lifecycle(self) -> RuntimeLifecycle {
10569 match self {
10570 Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10571 Self::Stopped => RuntimeLifecycle::Stopped,
10572 }
10573 }
10574}
10575
10576struct RuntimeState {
10577 options: RuntimeOptions,
10578 lifecycle: RuntimeLifecycle,
10579 rejected_drops: usize,
10580}
10581
10582#[cfg(test)]
10583thread_local! {
10584 static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10585 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10586 static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10587 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10588 static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10589 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10590 static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10591 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10592 static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10593 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10594 static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10595 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10596 static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10597 std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10598}
10599
10600#[cfg(test)]
10601fn run_before_runtime_submission_lock_test_hook() {
10602 if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10603 {
10604 hook();
10605 }
10606}
10607
10608#[cfg(test)]
10609fn run_after_async_work_registration_test_hook() {
10610 if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10611 {
10612 hook();
10613 }
10614}
10615
10616#[cfg(test)]
10617fn run_after_blocking_work_registration_test_hook() {
10618 if let Some(hook) =
10619 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10620 {
10621 hook();
10622 }
10623}
10624
10625#[cfg(test)]
10626fn run_after_current_thread_host_admission_test_hook() {
10627 if let Some(hook) =
10628 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10629 {
10630 hook();
10631 }
10632}
10633
10634#[cfg(test)]
10635fn run_before_initial_start_wait_test_hook() {
10636 if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10637 hook();
10638 }
10639}
10640
10641#[cfg(test)]
10642fn run_after_generation_stop_publication_test_hook() {
10643 if let Some(hook) =
10644 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10645 {
10646 hook();
10647 }
10648}
10649
10650thread_local! {
10651 static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
10652 const { std::cell::Cell::new(0) };
10653}
10654
10655struct RejectedSubmissionDropContext;
10656
10657impl RejectedSubmissionDropContext {
10658 fn enter() -> Self {
10659 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
10660 Self
10661 }
10662
10663 fn is_current() -> bool {
10664 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
10665 }
10666}
10667
10668impl Drop for RejectedSubmissionDropContext {
10669 fn drop(&mut self) {
10670 IN_REJECTED_SUBMISSION_DROP.with(|depth| {
10671 depth.set(
10672 depth
10673 .get()
10674 .checked_sub(1)
10675 .expect("rejected submission drop depth underflow"),
10676 );
10677 });
10678 }
10679}
10680
10681struct RuntimeController {
10682 state: Mutex<RuntimeState>,
10683 lifecycle_changed: Condvar,
10684 metrics: Arc<RuntimeMetrics>,
10685 current_backend: ArcSwapOption<RuntimeBackend>,
10691}
10692
10693struct RejectedSubmissionGuard<'a> {
10694 controller: &'a RuntimeController,
10695}
10696
10697impl Drop for RejectedSubmissionGuard<'_> {
10698 fn drop(&mut self) {
10699 let mut state = self
10700 .controller
10701 .state
10702 .lock()
10703 .unwrap_or_else(std::sync::PoisonError::into_inner);
10704 state.rejected_drops = state
10705 .rejected_drops
10706 .checked_sub(1)
10707 .expect("rejected submission drop count underflow");
10708 self.controller.lifecycle_changed.notify_all();
10709 }
10710}
10711
10712impl RuntimeController {
10713 fn new() -> Self {
10714 let options = RuntimeOptions::default()
10715 .validate()
10716 .expect("default async runtime options must be valid");
10717 Self {
10718 state: Mutex::new(RuntimeState {
10719 options,
10720 lifecycle: RuntimeLifecycle::Initial,
10721 rejected_drops: 0,
10722 }),
10723 lifecycle_changed: Condvar::new(),
10724 metrics: Arc::new(RuntimeMetrics::default()),
10725 current_backend: ArcSwapOption::empty(),
10726 }
10727 }
10728
10729 fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10730 let options = options.validate()?;
10731 let mut state = self
10732 .state
10733 .lock()
10734 .unwrap_or_else(std::sync::PoisonError::into_inner);
10735 Self::ensure_configuration_mutable(&state)?;
10736 state.options = options;
10737 Ok(())
10738 }
10739
10740 fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10741 self.configure_partial_inner(patch, |_| {})
10742 }
10743
10744 fn configure_partial_inner(
10745 &self,
10746 patch: RuntimeOptionsPatch,
10747 after_merge: impl FnOnce(&RuntimeOptions),
10748 ) -> Result<(), RuntimeConfigError> {
10749 let mut state = self
10750 .state
10751 .lock()
10752 .unwrap_or_else(std::sync::PoisonError::into_inner);
10753 Self::ensure_configuration_mutable(&state)?;
10754
10755 let mut options = state.options.clone();
10756 patch.apply_to(&mut options);
10757 after_merge(&options);
10758 let options = options.validate()?;
10759 state.options = options;
10760 Ok(())
10761 }
10762
10763 fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10764 if matches!(
10765 &state.lifecycle,
10766 RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
10767 ) {
10768 return Ok(());
10769 }
10770 Err(RuntimeConfigError(
10771 "the async runtime configuration is frozen; configure it before the first async call"
10772 .to_string(),
10773 ))
10774 }
10775
10776 fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10777 let Some(active_generation) = active_runtime_generation() else {
10778 return Ok(());
10779 };
10780 let lifecycle_generation = match &state.lifecycle {
10781 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10782 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10783 RuntimeLifecycle::Initial
10784 | RuntimeLifecycle::StoppedBeforeFirstUse
10785 | RuntimeLifecycle::StoppingWithoutBackend(_)
10786 | RuntimeLifecycle::Stopped => None,
10787 };
10788 if lifecycle_generation == Some(active_generation) {
10789 return Ok(());
10790 }
10791 Err(RuntimeConfigError(
10792 "cannot access or control the async runtime from work owned by a retired generation"
10793 .to_string(),
10794 ))
10795 }
10796
10797 fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
10798 Self::ensure_active_generation_current(state)?;
10799 match &state.lifecycle {
10800 RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
10801 RuntimeLifecycle::StoppedBeforeFirstUse
10802 | RuntimeLifecycle::Stopping(_)
10803 | RuntimeLifecycle::StoppingWithoutBackend(_)
10804 | RuntimeLifecycle::Stopped => {
10805 return Err(RuntimeConfigError(
10806 "the async runtime is stopped; call start before submitting work".to_string(),
10807 ));
10808 }
10809 RuntimeLifecycle::Initial => {}
10810 }
10811 if state.rejected_drops != 0 {
10812 return Err(RuntimeConfigError(
10813 "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
10814 ));
10815 }
10816 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10817 state.lifecycle = RuntimeLifecycle::Running(backend.clone());
10818 self.current_backend.store(Some(Arc::new(backend.clone())));
10819 Ok(backend)
10820 }
10821
10822 fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
10823 let mut state = self
10824 .state
10825 .lock()
10826 .unwrap_or_else(std::sync::PoisonError::into_inner);
10827 self.backend_locked(&mut state)
10828 }
10829
10830 fn backend(&self) -> RuntimeBackend {
10831 self.try_backend().unwrap_or_else(|error| panic!("{error}"))
10832 }
10833
10834 fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
10835 let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
10836 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10837 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10838 RuntimeLifecycle::Initial
10839 | RuntimeLifecycle::StoppedBeforeFirstUse
10840 | RuntimeLifecycle::StoppingWithoutBackend(_)
10841 | RuntimeLifecycle::Stopped => None,
10842 });
10843 state.rejected_drops += 1;
10844 generation
10845 }
10846
10847 #[inline]
10855 fn fast_register<R>(
10856 &self,
10857 register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
10858 ) -> Option<(Arc<RuntimeBackend>, R)> {
10859 let guard = self.current_backend.load();
10860 let backend = guard.as_ref()?; if let Some(active) = active_runtime_generation() {
10862 if active != backend.generation() {
10863 return None; }
10865 }
10866 let registration = register(&backend.work)?; Some((Arc::clone(backend), registration))
10870 }
10871
10872 fn try_spawn_tracked<F, T>(
10873 &self,
10874 future: F,
10875 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10876 where
10877 F: Future<Output = T> + Send + 'static,
10878 T: Send + 'static,
10879 {
10880 #[cfg(test)]
10881 run_before_runtime_submission_lock_test_hook();
10882
10883 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
10884 #[cfg(test)]
10885 run_after_async_work_registration_test_hook();
10886 return Ok(spawn_registered(
10887 &backend,
10888 Arc::clone(&self.metrics),
10889 future,
10890 registration,
10891 ));
10892 }
10893
10894 let (backend, registration) = {
10895 let mut state = self
10896 .state
10897 .lock()
10898 .unwrap_or_else(std::sync::PoisonError::into_inner);
10899 let backend = match self.backend_locked(&mut state) {
10900 Ok(backend) => backend,
10901 Err(error) => {
10902 let generation = Self::begin_rejected_drop(&mut state);
10903 return Err((error, future, generation));
10904 }
10905 };
10906 let Some(registration) = backend.work.try_register_async() else {
10907 let generation = Self::begin_rejected_drop(&mut state);
10908 return Err((
10909 RuntimeConfigError(
10910 "the async runtime is stopped; call start before submitting work".to_string(),
10911 ),
10912 future,
10913 generation,
10914 ));
10915 };
10916 (backend, registration)
10917 };
10918 #[cfg(test)]
10919 run_after_async_work_registration_test_hook();
10920 Ok(spawn_registered(
10921 &backend,
10922 Arc::clone(&self.metrics),
10923 future,
10924 registration,
10925 ))
10926 }
10927
10928 fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10929 where
10930 F: Future<Output = T> + Send + 'static,
10931 T: Send + 'static,
10932 {
10933 match self.try_spawn_tracked(future) {
10934 Ok(handle) => Ok(handle),
10935 Err((error, future, _generation)) => {
10936 drop(RejectedSubmissionGuard { controller: self });
10937 Err((error, future))
10938 }
10939 }
10940 }
10941
10942 fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
10943 where
10944 F: Future<Output = T> + Send + 'static,
10945 T: Send + 'static,
10946 {
10947 match self.try_spawn_tracked(future) {
10948 Ok(handle) => handle,
10949 Err((error, future, generation)) => {
10950 let _rejection = RejectedSubmissionGuard { controller: self };
10951 let _drop_context = RejectedSubmissionDropContext::enter();
10952 let _generation = generation.map(RuntimeGenerationGuard::enter);
10953 let _ = catch_unwind_contained(|| drop(future));
10954 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10955 message: error.to_string(),
10956 }))))
10957 }
10958 }
10959 }
10960
10961 fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
10962 where
10963 F: Future<Output = ()> + Send + 'static,
10964 {
10965 match self.try_spawn(future) {
10966 Ok(handle) => {
10967 handle.detach();
10968 Ok(())
10969 }
10970 Err((_, future)) => Err(future),
10971 }
10972 }
10973
10974 fn try_spawn_blocking_tracked<F, T>(
10975 &self,
10976 function: F,
10977 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10978 where
10979 F: FnOnce() -> T + Send + 'static,
10980 T: Send + 'static,
10981 {
10982 #[cfg(test)]
10983 run_before_runtime_submission_lock_test_hook();
10984
10985 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
10986 #[cfg(test)]
10987 run_after_blocking_work_registration_test_hook();
10988 return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
10989 }
10990
10991 let (backend, registration) = {
10992 let mut state = self
10993 .state
10994 .lock()
10995 .unwrap_or_else(std::sync::PoisonError::into_inner);
10996 let backend = match self.backend_locked(&mut state) {
10997 Ok(backend) => backend,
10998 Err(error) => {
10999 let generation = Self::begin_rejected_drop(&mut state);
11000 return Err((error, function, generation));
11001 }
11002 };
11003 let Some(registration) = backend.work.try_register_work() else {
11004 let generation = Self::begin_rejected_drop(&mut state);
11005 return Err((
11006 RuntimeConfigError(
11007 "the async runtime is stopped; call start before submitting work".to_string(),
11008 ),
11009 function,
11010 generation,
11011 ));
11012 };
11013 (backend, registration)
11014 };
11015 #[cfg(test)]
11016 run_after_blocking_work_registration_test_hook();
11017 Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11018 }
11019
11020 fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11021 where
11022 F: FnOnce() -> T + Send + 'static,
11023 T: Send + 'static,
11024 {
11025 match self.try_spawn_blocking_tracked(function) {
11026 Ok(handle) => Ok(handle),
11027 Err((error, function, _generation)) => {
11028 drop(RejectedSubmissionGuard { controller: self });
11029 Err((error, function))
11030 }
11031 }
11032 }
11033
11034 fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11035 where
11036 F: FnOnce() -> T + Send + 'static,
11037 T: Send + 'static,
11038 {
11039 match self.try_spawn_blocking_tracked(function) {
11040 Ok(handle) => handle,
11041 Err((error, function, generation)) => {
11042 let _rejection = RejectedSubmissionGuard { controller: self };
11043 let _drop_context = RejectedSubmissionDropContext::enter();
11044 let _generation = generation.map(RuntimeGenerationGuard::enter);
11045 let _ = catch_unwind_contained(|| drop(function));
11046 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11047 message: error.to_string(),
11048 }))))
11049 }
11050 }
11051 }
11052
11053 #[cfg(test)]
11054 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11055 self
11056 .try_block_on(future)
11057 .unwrap_or_else(|error| panic!("{error}"))
11058 }
11059
11060 fn try_block_on_scope(
11061 &self,
11062 ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11063 let mut state = self
11064 .state
11065 .lock()
11066 .unwrap_or_else(std::sync::PoisonError::into_inner);
11067 let backend = match self.backend_locked(&mut state) {
11068 Ok(backend) => backend,
11069 Err(error) => {
11070 let generation = Self::begin_rejected_drop(&mut state);
11071 return Err((error, generation));
11072 }
11073 };
11074 let Some(registration) = backend.work.try_register_work() else {
11075 let generation = Self::begin_rejected_drop(&mut state);
11076 return Err((
11077 RuntimeConfigError(
11078 "the async runtime is stopped; call start before submitting work".to_string(),
11079 ),
11080 generation,
11081 ));
11082 };
11083 Ok((backend, registration))
11084 }
11085
11086 fn try_block_on(
11087 &self,
11088 future: Pin<&mut dyn Future<Output = ()>>,
11089 ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11090 let (backend, registration) = match self.try_block_on_scope() {
11091 Ok(scope) => scope,
11092 Err((error, _generation)) => {
11093 drop(RejectedSubmissionGuard { controller: self });
11094 return Err(error);
11095 }
11096 };
11097 let _registration = registration;
11098 let _generation = RuntimeGenerationGuard::enter(backend.generation());
11099 Ok(backend.block_on(future))
11100 }
11101
11102 fn start(&self) -> Result<(), RuntimeConfigError> {
11103 let mut state = self
11104 .state
11105 .lock()
11106 .unwrap_or_else(std::sync::PoisonError::into_inner);
11107 loop {
11108 Self::ensure_active_generation_current(&state)?;
11109 match &state.lifecycle {
11110 RuntimeLifecycle::Initial => {
11114 if state.rejected_drops == 0 {
11115 return Ok(());
11116 }
11117 if RejectedSubmissionDropContext::is_current() {
11118 return Err(RuntimeConfigError(
11119 "cannot start the async runtime while a rejected submission is being destroyed"
11120 .to_string(),
11121 ));
11122 }
11123 #[cfg(test)]
11124 run_before_initial_start_wait_test_hook();
11125 state = self
11126 .lifecycle_changed
11127 .wait(state)
11128 .unwrap_or_else(std::sync::PoisonError::into_inner);
11129 }
11130 RuntimeLifecycle::Running(_) => return Ok(()),
11131 RuntimeLifecycle::Stopping(identity) => {
11132 if identity.is_current() {
11133 return Err(RuntimeConfigError(
11134 "cannot restart the async runtime from work in the generation being stopped"
11135 .to_string(),
11136 ));
11137 }
11138 state = self
11139 .lifecycle_changed
11140 .wait(state)
11141 .unwrap_or_else(std::sync::PoisonError::into_inner);
11142 }
11143 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11144 if RejectedSubmissionDropContext::is_current() {
11145 return Err(RuntimeConfigError(
11146 "cannot restart the async runtime while a rejected submission is being destroyed"
11147 .to_string(),
11148 ));
11149 }
11150 state = self
11151 .lifecycle_changed
11152 .wait(state)
11153 .unwrap_or_else(std::sync::PoisonError::into_inner);
11154 }
11155 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11156 if state.rejected_drops == 0 {
11157 if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11158 state.lifecycle = RuntimeLifecycle::Initial;
11159 return Ok(());
11160 }
11161 break;
11162 }
11163 if RejectedSubmissionDropContext::is_current() {
11164 return Err(RuntimeConfigError(
11165 "cannot restart the async runtime while a rejected submission is being destroyed"
11166 .to_string(),
11167 ));
11168 }
11169 state = self
11170 .lifecycle_changed
11171 .wait(state)
11172 .unwrap_or_else(std::sync::PoisonError::into_inner);
11173 }
11174 }
11175 }
11176 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11177 self.current_backend.store(Some(Arc::new(backend.clone())));
11178 state.lifecycle = RuntimeLifecycle::Running(backend);
11179 Ok(())
11180 }
11181
11182 fn options(&self) -> RuntimeOptions {
11183 self
11184 .state
11185 .lock()
11186 .unwrap_or_else(std::sync::PoisonError::into_inner)
11187 .options
11188 .clone()
11189 }
11190
11191 fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11192 let state = self
11193 .state
11194 .lock()
11195 .unwrap_or_else(std::sync::PoisonError::into_inner);
11196 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11197 return None;
11198 };
11199 match &backend.executor {
11200 RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11201 #[cfg(not(target_family = "wasm"))]
11202 RuntimeExecutor::MultiThread(_) => None,
11203 }
11204 }
11205
11206 fn request_current_thread_drain(&self) {
11207 if let Some(executor) = self.running_current_thread_executor() {
11208 executor.request_drain_if_queued();
11209 }
11210 }
11211
11212 fn retire_rejected_current_thread_work(&self) {
11213 if let Some(executor) = self.running_current_thread_executor() {
11214 executor.retire_rejected_work_off_waker_stack();
11215 }
11216 }
11217
11218 fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11219 let executor = {
11220 let state = self
11221 .state
11222 .lock()
11223 .unwrap_or_else(std::sync::PoisonError::into_inner);
11224 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11225 return;
11226 };
11227 match &backend.executor {
11228 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11229 #[cfg(not(target_family = "wasm"))]
11230 RuntimeExecutor::MultiThread(_) => return,
11231 }
11232 };
11233 executor.cancel_host_dispatch(failure);
11234 }
11235
11236 #[cfg(test)]
11237 fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11238 let executor = {
11239 let state = self
11240 .state
11241 .lock()
11242 .unwrap_or_else(std::sync::PoisonError::into_inner);
11243 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11244 return;
11245 };
11246 match &backend.executor {
11247 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11248 #[cfg(not(target_family = "wasm"))]
11249 RuntimeExecutor::MultiThread(_) => return,
11250 }
11251 };
11252 executor.cancel_host_dispatch_for_test(dispatch);
11253 }
11254
11255 fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11256 let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11257 self.drive_current_thread_dispatch_with_lease(dispatch, true)
11258 }
11259
11260 fn drive_current_thread_dispatch_with_lease(
11261 &self,
11262 dispatch: u64,
11263 mark_serviced: bool,
11264 ) -> Option<CurrentThreadTaskCallbackLease> {
11265 let mut host_turn = {
11266 let state = self
11267 .state
11268 .lock()
11269 .unwrap_or_else(std::sync::PoisonError::into_inner);
11270 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11271 return None;
11272 };
11273 let executor = match &backend.executor {
11274 RuntimeExecutor::CurrentThread(executor) => executor,
11275 #[cfg(not(target_family = "wasm"))]
11276 RuntimeExecutor::MultiThread(_) => return None,
11277 };
11278 let host_turn = executor.try_admit_host_turn(dispatch)?;
11279 if mark_serviced {
11280 CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11281 }
11282 host_turn
11283 };
11284 #[cfg(test)]
11285 run_after_current_thread_host_admission_test_hook();
11286 host_turn.drive();
11287 Some(CurrentThreadTaskCallbackLease::new(host_turn))
11288 }
11289
11290 #[cfg(test)]
11291 fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11292 self
11293 .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11294 .is_some()
11295 }
11296
11297 fn shutdown(&self) -> Result<(), RuntimeConfigError> {
11298 let backend = {
11299 let mut state = self
11300 .state
11301 .lock()
11302 .unwrap_or_else(std::sync::PoisonError::into_inner);
11303 loop {
11304 Self::ensure_active_generation_current(&state)?;
11305 match &state.lifecycle {
11306 RuntimeLifecycle::Initial => {
11307 if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11308 return Err(RuntimeConfigError(
11309 "cannot shut down the async runtime while a rejected submission is being destroyed"
11310 .to_string(),
11311 ));
11312 }
11313 let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11314 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11315 self.lifecycle_changed.notify_all();
11316 while state.rejected_drops != 0 {
11317 state = self
11318 .lifecycle_changed
11319 .wait(state)
11320 .unwrap_or_else(std::sync::PoisonError::into_inner);
11321 }
11322 debug_assert!(matches!(
11323 state.lifecycle,
11324 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
11325 ));
11326 state.lifecycle = target.into_lifecycle();
11327 self.lifecycle_changed.notify_all();
11328 return Ok(());
11329 }
11330 RuntimeLifecycle::Running(backend) => {
11331 if backend.is_current() {
11332 return Err(RuntimeConfigError(
11333 "cannot shut down the async runtime from work running on that runtime".to_string(),
11334 ));
11335 }
11336 let identity = backend.stop_identity();
11337 let stop = backend.generation_stop();
11338 let stop_publication = stop.publication_guard();
11343 stop_publication.publish_stopping();
11344 let RuntimeLifecycle::Running(backend) =
11345 std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11346 else {
11347 unreachable!();
11348 };
11349 self.current_backend.store(None);
11350 drop(stop_publication);
11354 break backend;
11355 }
11356 RuntimeLifecycle::Stopping(identity) => {
11357 if identity.is_current() {
11358 return Err(RuntimeConfigError(
11359 "cannot wait for async runtime shutdown from work in the generation being stopped"
11360 .to_string(),
11361 ));
11362 }
11363 state = self
11364 .lifecycle_changed
11365 .wait(state)
11366 .unwrap_or_else(std::sync::PoisonError::into_inner);
11367 }
11368 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11369 if RejectedSubmissionDropContext::is_current() {
11370 return Err(RuntimeConfigError(
11371 "cannot shut down the async runtime while a rejected submission is being destroyed"
11372 .to_string(),
11373 ));
11374 }
11375 state = self
11376 .lifecycle_changed
11377 .wait(state)
11378 .unwrap_or_else(std::sync::PoisonError::into_inner);
11379 }
11380 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11381 if state.rejected_drops == 0 {
11382 return Ok(());
11383 }
11384 if RejectedSubmissionDropContext::is_current() {
11385 return Err(RuntimeConfigError(
11386 "cannot shut down the async runtime while a rejected submission is being destroyed"
11387 .to_string(),
11388 ));
11389 }
11390 let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11391 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11392 } else {
11393 ZeroBackendShutdownTarget::Stopped
11394 };
11395 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11396 self.lifecycle_changed.notify_all();
11397 while state.rejected_drops != 0 {
11398 state = self
11399 .lifecycle_changed
11400 .wait(state)
11401 .unwrap_or_else(std::sync::PoisonError::into_inner);
11402 }
11403 debug_assert!(matches!(
11404 state.lifecycle,
11405 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
11406 ));
11407 state.lifecycle = target.into_lifecycle();
11408 self.lifecycle_changed.notify_all();
11409 return Ok(());
11410 }
11411 }
11412 }
11413 };
11414
11415 #[cfg(test)]
11416 run_after_generation_stop_publication_test_hook();
11417 backend.begin_shutdown();
11418 backend.wait_until_idle();
11419
11420 #[cfg(not(target_family = "wasm"))]
11421 let worker_lifecycle = backend.worker_lifecycle();
11422 drop(backend);
11423
11424 #[cfg(not(target_family = "wasm"))]
11425 if let Some(worker_lifecycle) = worker_lifecycle {
11426 worker_lifecycle.wait_for_all_workers();
11427 }
11428
11429 let mut state = self
11430 .state
11431 .lock()
11432 .unwrap_or_else(std::sync::PoisonError::into_inner);
11433 while state.rejected_drops != 0 {
11434 state = self
11435 .lifecycle_changed
11436 .wait(state)
11437 .unwrap_or_else(std::sync::PoisonError::into_inner);
11438 }
11439 debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
11440 state.lifecycle = RuntimeLifecycle::Stopped;
11441 self.lifecycle_changed.notify_all();
11442 Ok(())
11443 }
11444}
11445
11446static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
11447
11448pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
11449 RUNTIME.configure(options)
11450}
11451
11452pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
11453 RUNTIME.configure_partial(patch)
11454}
11455
11456pub fn configured_options() -> RuntimeOptions {
11457 RUNTIME.options()
11458}
11459
11460pub fn is_multi_threaded() -> bool {
11461 RUNTIME.options().flavor == RuntimeFlavor::MultiThread
11462}
11463
11464fn spawn_registered<F, T>(
11465 backend: &RuntimeBackend,
11466 metrics: Arc<RuntimeMetrics>,
11467 future: F,
11468 registration: (AbortRegistration, GenerationWorkGuard),
11469) -> JoinHandle<T>
11470where
11471 F: Future<Output = T> + Send + 'static,
11472 T: Send + 'static,
11473{
11474 metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
11475 let generation = backend.generation();
11476 let (abort_registration, work_registration) = registration;
11477 #[cfg(not(target_family = "wasm"))]
11478 let dependency = Arc::new(TaskDependency::new_task(generation));
11479 #[cfg(not(target_family = "wasm"))]
11480 let task_dependency = Arc::clone(&dependency);
11481 let wrapped = async move {
11482 let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
11483 let mut abortable = std::pin::pin!(abortable);
11484 let polled = futures::future::poll_fn(|cx| {
11485 #[cfg(not(target_family = "wasm"))]
11486 task_dependency.begin_poll();
11487 #[cfg(not(target_family = "wasm"))]
11488 let _dependency = TaskDependencyGuard::enter(&task_dependency);
11489 abortable.as_mut().poll(cx)
11490 });
11491 match AssertUnwindSafe(polled).catch_unwind().await {
11492 Ok(Ok(output)) => {
11493 metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
11494 Ok(ContainedTaskOutput::new(output, generation))
11495 }
11496 Ok(Err(_)) => Err(JoinError {
11497 message: "async runtime stopped before the task completed".to_string(),
11498 }),
11499 Err(panic) => {
11500 metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
11501 Err(panic_payload_to_join_error(panic))
11502 }
11503 }
11504 };
11505 let scheduler = backend.downgrade_executor();
11506 let (runnable, task) = async_task::spawn(
11510 RegisteredTaskFuture::new(wrapped, work_registration, generation),
11511 move |runnable| {
11512 scheduler.schedule(runnable);
11513 },
11514 );
11515 backend.schedule_submission(runnable);
11516 JoinHandle(JoinHandleInner::Task {
11517 task: task.fallible(),
11518 awaiter: None,
11519 #[cfg(not(target_family = "wasm"))]
11520 dependency,
11521 })
11522}
11523
11524pub fn spawn<F, T>(future: F) -> JoinHandle<T>
11525where
11526 F: Future<Output = T> + Send + 'static,
11527 T: Send + 'static,
11528{
11529 RUNTIME.spawn(future)
11530}
11531
11532pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11537where
11538 F: Future<Output = T> + Send + 'static,
11539 T: Send + 'static,
11540{
11541 RUNTIME.try_spawn(future)
11542}
11543
11544pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
11545where
11546 F: Future<Output = ()> + Send + 'static,
11547{
11548 RUNTIME.try_spawn_detached(future)
11549}
11550
11551pub fn spawn_detached<F>(future: F)
11552where
11553 F: Future<Output = ()> + Send + 'static,
11554{
11555 spawn(future).detach();
11556}
11557
11558pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
11559where
11560 F: FnOnce() -> T + Send + 'static,
11561 T: Send + 'static,
11562{
11563 RUNTIME.spawn_blocking(function)
11564}
11565
11566pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11571where
11572 F: FnOnce() -> T + Send + 'static,
11573 T: Send + 'static,
11574{
11575 RUNTIME.try_spawn_blocking(function)
11576}
11577
11578fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
11579 let (backend, registration) = match controller.try_block_on_scope() {
11580 Ok(scope) => scope,
11581 Err((error, generation)) => {
11582 let _rejection = RejectedSubmissionGuard { controller };
11583 let _drop_context = RejectedSubmissionDropContext::enter();
11584 let _generation = generation.map(RuntimeGenerationGuard::enter);
11585 let _ = catch_unwind_contained(|| drop(future));
11586 panic!("{error}");
11587 }
11588 };
11589 let generation = backend.generation();
11590 let _registration = registration;
11591 let _generation = RuntimeGenerationGuard::enter(generation);
11592 let mut output = None;
11593 {
11594 let future = ContainedFuture::new(future, generation);
11595 let mut erased = std::pin::pin!(async {
11596 output = Some(future.await);
11597 });
11598 let _ = backend.block_on(erased.as_mut());
11599 }
11604 output.expect("async runtime returned before the future completed")
11605}
11606
11607pub fn block_on<F: Future>(future: F) -> F::Output {
11608 block_on_with_controller(&RUNTIME, future)
11609}
11610
11611pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
11612 try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
11613}
11614
11615pub fn try_block_on_dyn(
11617 future: Pin<&mut dyn Future<Output = ()>>,
11618) -> Result<(), RuntimeConfigError> {
11619 match RUNTIME.try_block_on(future)? {
11620 BlockOnOutcome::Completed => Ok(()),
11621 BlockOnOutcome::Stopped => Err(RuntimeConfigError(
11622 "the async runtime stopped before block_on completed its future".to_string(),
11623 )),
11624 }
11625}
11626
11627pub fn start() -> Result<(), RuntimeConfigError> {
11628 RUNTIME.start()
11629}
11630
11631pub fn shutdown() -> Result<(), RuntimeConfigError> {
11632 RUNTIME.shutdown()
11633}
11634
11635fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
11636 for failure in completion.failed_dispatches {
11637 RUNTIME.cancel_current_thread_dispatch(failure);
11638 }
11639 if completion.redispatch {
11640 RUNTIME.request_current_thread_drain();
11641 }
11642 RUNTIME.retire_rejected_current_thread_work();
11648}
11649
11650pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
11659 complete_current_thread_task_delivery(
11660 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
11661 );
11662}
11663
11664pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
11670 complete_current_thread_task_delivery(
11671 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
11672 );
11673}
11674
11675pub fn cancel_current_thread_task_dispatch(delivery: u64) {
11682 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
11683}
11684
11685pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11694 RUNTIME.drive_current_thread_tasks(delivery)
11695}
11696
11697pub fn reset_metrics() {
11698 RUNTIME.metrics.reset();
11699}
11700
11701pub fn metrics() -> RuntimeMetricsSnapshot {
11702 let options = RUNTIME.options();
11703 RUNTIME.metrics.snapshot(&options)
11704}
11705
11706#[cfg(test)]
11707mod tests {
11708 use super::*;
11709
11710 static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11711 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11712 LazyLock::new(Mutex::default);
11713 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11714 static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11715 static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11716 static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11717 LazyLock::new(Mutex::default);
11718 static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11719 static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11720 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11721 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11722 LazyLock::new(Mutex::default);
11723 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
11724 static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
11725 CurrentThreadTaskDriverRegistry,
11726 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11727 static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11728 static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11729 static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11730 LazyLock::new(Mutex::default);
11731 static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11732 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11733 static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11734 LazyLock::new(Mutex::default);
11735 static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11736 static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11737 LazyLock::new(Mutex::default);
11738 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11739 LazyLock::new(Mutex::default);
11740 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
11741 CurrentThreadTaskDriverRegistry,
11742 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11743 static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11744 LazyLock::new(Mutex::default);
11745 static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11746 static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11747 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11748 static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
11749 CurrentThreadTaskDriverRegistry,
11750 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11751 static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
11752 CurrentThreadTaskDriverRegistry,
11753 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11754 static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11755 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11756 static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11757 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11758 static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11759 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11760 static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11761 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11762 static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11763 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11764 static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
11765 CurrentThreadTaskDriverRegistry,
11766 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11767 static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11768 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11769 static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11770
11771 thread_local! {
11772 static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
11773 const { std::cell::Cell::new(0) };
11774 static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
11775 const { std::cell::Cell::new(0) };
11776 }
11777
11778 #[cfg(not(target_family = "wasm"))]
11779 struct StopPollProbe {
11780 polls: Arc<AtomicUsize>,
11781 first_poll: Option<std::sync::mpsc::Sender<()>>,
11782 }
11783
11784 #[cfg(not(target_family = "wasm"))]
11785 impl Future for StopPollProbe {
11786 type Output = ();
11787
11788 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
11789 self.polls.fetch_add(1, Ordering::SeqCst);
11790 if let Some(first_poll) = self.first_poll.take() {
11791 first_poll.send(()).unwrap();
11792 }
11793 Poll::Pending
11794 }
11795 }
11796
11797 #[cfg(not(target_family = "wasm"))]
11798 struct PanickingJoinAwaiter {
11799 wakes: Arc<AtomicUsize>,
11800 drops: Arc<AtomicUsize>,
11801 }
11802
11803 #[cfg(not(target_family = "wasm"))]
11804 impl std::task::Wake for PanickingJoinAwaiter {
11805 fn wake(self: Arc<Self>) {
11806 self.wakes.fetch_add(1, Ordering::SeqCst);
11807 panic!("intentional JoinHandle awaiter wake panic");
11808 }
11809
11810 fn wake_by_ref(self: &Arc<Self>) {
11811 self.wakes.fetch_add(1, Ordering::SeqCst);
11812 panic!("intentional JoinHandle awaiter wake-by-ref panic");
11813 }
11814 }
11815
11816 #[cfg(not(target_family = "wasm"))]
11817 impl Drop for PanickingJoinAwaiter {
11818 fn drop(&mut self) {
11819 self.drops.fetch_add(1, Ordering::SeqCst);
11820 panic!("intentional JoinHandle awaiter destructor panic");
11821 }
11822 }
11823
11824 #[cfg(not(target_family = "wasm"))]
11825 fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
11826 Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
11827 }
11828
11829 #[cfg(not(target_family = "wasm"))]
11830 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11831 enum JoinAwaiterWakerEventKind {
11832 Clone,
11833 Wake,
11834 WakeByRef,
11835 Drop,
11836 }
11837
11838 #[cfg(not(target_family = "wasm"))]
11839 struct JoinAwaiterWakerState {
11840 events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
11841 }
11842
11843 #[cfg(not(target_family = "wasm"))]
11844 impl JoinAwaiterWakerState {
11845 fn record(&self, kind: JoinAwaiterWakerEventKind) {
11846 self
11847 .events
11848 .lock()
11849 .unwrap_or_else(std::sync::PoisonError::into_inner)
11850 .push((kind, active_runtime_generation()));
11851 }
11852
11853 fn clear(&self) {
11854 self
11855 .events
11856 .lock()
11857 .unwrap_or_else(std::sync::PoisonError::into_inner)
11858 .clear();
11859 }
11860
11861 fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
11862 self
11863 .events
11864 .lock()
11865 .unwrap_or_else(std::sync::PoisonError::into_inner)
11866 .clone()
11867 }
11868 }
11869
11870 #[cfg(not(target_family = "wasm"))]
11871 struct JoinAwaiterWakerToken {
11872 state: Arc<JoinAwaiterWakerState>,
11873 }
11874
11875 #[cfg(not(target_family = "wasm"))]
11876 unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
11877 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11878 token.state.record(JoinAwaiterWakerEventKind::Clone);
11879 let clone = Box::new(JoinAwaiterWakerToken {
11880 state: Arc::clone(&token.state),
11881 });
11882 std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
11883 }
11884
11885 #[cfg(not(target_family = "wasm"))]
11886 unsafe fn wake_join_awaiter_waker(data: *const ()) {
11887 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11888 token.state.record(JoinAwaiterWakerEventKind::Wake);
11889 }
11890
11891 #[cfg(not(target_family = "wasm"))]
11892 unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
11893 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11894 token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
11895 }
11896
11897 #[cfg(not(target_family = "wasm"))]
11898 unsafe fn drop_join_awaiter_waker(data: *const ()) {
11899 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11900 token.state.record(JoinAwaiterWakerEventKind::Drop);
11901 }
11902
11903 #[cfg(not(target_family = "wasm"))]
11904 static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
11905 clone_join_awaiter_waker,
11906 wake_join_awaiter_waker,
11907 wake_join_awaiter_waker_by_ref,
11908 drop_join_awaiter_waker,
11909 );
11910
11911 #[cfg(not(target_family = "wasm"))]
11912 fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
11913 let token = Box::new(JoinAwaiterWakerToken { state });
11914 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
11915 unsafe { Waker::from_raw(raw) }
11916 }
11917
11918 #[cfg(not(target_family = "wasm"))]
11919 fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
11920 wait_until("the block_on driver to sleep", || {
11921 stop
11922 .parkers
11923 .lock()
11924 .unwrap_or_else(std::sync::PoisonError::into_inner)
11925 .iter()
11926 .filter_map(Weak::upgrade)
11927 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11928 });
11929 }
11930
11931 #[test]
11932 fn unique_ids_fail_before_wraparound() {
11933 let counter = AtomicU64::new(u64::MAX - 1);
11934 assert_eq!(
11935 next_unique_id(&counter, "test id space exhausted"),
11936 u64::MAX - 1
11937 );
11938
11939 let exhausted = catch_unwind(AssertUnwindSafe(|| {
11940 next_unique_id(&counter, "test id space exhausted");
11941 }));
11942 assert!(exhausted.is_err());
11943 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
11944 }
11945
11946 #[test]
11947 fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
11948 let work = GenerationWork::new();
11949 {
11950 let mut state = work.state.lock().unwrap();
11951 state.next_task_id = u64::MAX;
11952 }
11953
11954 let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
11955 assert!(exhausted.is_err());
11956
11957 let state = work.state.lock().unwrap();
11958 assert_eq!(state.next_task_id, u64::MAX);
11959 assert_eq!(state.active, 0);
11960 assert!(state.abort_handles.is_empty());
11961 }
11962
11963 #[test]
11964 fn caught_panic_payload_destructor_runs() {
11965 struct DropSignal(Arc<AtomicBool>);
11966
11967 impl Drop for DropSignal {
11968 fn drop(&mut self) {
11969 self.0.store(true, Ordering::SeqCst);
11970 }
11971 }
11972
11973 let dropped = Arc::new(AtomicBool::new(false));
11974 let payload = DropSignal(Arc::clone(&dropped));
11975
11976 assert!(
11977 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
11978 "the original panic must remain contained"
11979 );
11980 assert!(
11981 dropped.load(Ordering::SeqCst),
11982 "an ordinary caught panic payload must be destroyed"
11983 );
11984 }
11985
11986 #[test]
11987 fn hostile_caught_panic_payload_destructor_remains_contained() {
11988 struct PanicOnDrop(Arc<AtomicBool>);
11989
11990 impl Drop for PanicOnDrop {
11991 fn drop(&mut self) {
11992 self.0.store(true, Ordering::SeqCst);
11993 panic!("caught panic payload destructor escaped");
11994 }
11995 }
11996
11997 let dropped = Arc::new(AtomicBool::new(false));
11998 let payload = PanicOnDrop(Arc::clone(&dropped));
11999 let result = catch_unwind(AssertUnwindSafe(|| {
12000 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12001 }));
12002
12003 assert!(
12004 result.is_ok(),
12005 "the payload destructor's nested panic must remain contained"
12006 );
12007 assert!(
12008 result.unwrap().is_none(),
12009 "the original panic must remain contained"
12010 );
12011 assert!(
12012 dropped.load(Ordering::SeqCst),
12013 "the hostile payload destructor must be attempted"
12014 );
12015 }
12016
12017 fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12018 CurrentThreadHostDispatchResult::Accepted
12019 }
12020
12021 fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12022 CurrentThreadHostDispatchResult::Unavailable
12023 }
12024
12025 fn count_current_thread_recovery_host_dispatch(
12026 _dispatch: u64,
12027 ) -> CurrentThreadHostDispatchResult {
12028 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
12029 CurrentThreadHostDispatchResult::Accepted
12030 }
12031
12032 fn reset_current_thread_recovery_host_dispatches() {
12033 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12034 }
12035
12036 fn current_thread_recovery_host_dispatches() -> usize {
12037 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
12038 }
12039
12040 fn accept_only_first_current_thread_host_dispatch(
12041 _dispatch: u64,
12042 ) -> CurrentThreadHostDispatchResult {
12043 if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
12044 let previous = dispatches.get();
12045 dispatches.set(previous + 1);
12046 previous == 0
12047 }) {
12048 CurrentThreadHostDispatchResult::Accepted
12049 } else {
12050 CurrentThreadHostDispatchResult::Unavailable
12051 }
12052 }
12053
12054 fn reset_current_thread_one_shot_host_dispatches() {
12055 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12056 }
12057
12058 fn current_thread_one_shot_host_dispatches() -> usize {
12059 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
12060 }
12061
12062 fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12063 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12064 CurrentThreadHostDispatchResult::Accepted
12065 }
12066
12067 fn count_current_thread_blocking_budget_host_dispatch(
12068 _dispatch: u64,
12069 ) -> CurrentThreadHostDispatchResult {
12070 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12071 CurrentThreadHostDispatchResult::Accepted
12072 }
12073
12074 fn count_current_thread_admission_host_dispatch(
12075 _dispatch: u64,
12076 ) -> CurrentThreadHostDispatchResult {
12077 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12078 CurrentThreadHostDispatchResult::Accepted
12079 }
12080
12081 fn count_current_thread_interleaved_recovery_host_dispatch(
12082 _dispatch: u64,
12083 ) -> CurrentThreadHostDispatchResult {
12084 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12085 CurrentThreadHostDispatchResult::Accepted
12086 }
12087
12088 fn dispatch_late_registration_current_thread_tasks(
12089 dispatch: u64,
12090 ) -> CurrentThreadHostDispatchResult {
12091 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
12092 }
12093
12094 fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12095 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
12096 .lock()
12097 .unwrap()
12098 .push(dispatch);
12099 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
12100 }
12101
12102 fn dispatch_authoritative_panic_current_thread_tasks(
12103 dispatch: u64,
12104 ) -> CurrentThreadHostDispatchResult {
12105 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12106 .lock()
12107 .unwrap()
12108 .push(dispatch);
12109 match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
12110 0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
12111 2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
12112 _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
12113 }
12114 }
12115
12116 fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12117 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12118 }
12119
12120 fn dispatch_waker_stack_failure_current_thread_tasks(
12121 dispatch: u64,
12122 ) -> CurrentThreadHostDispatchResult {
12123 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12124 }
12125
12126 fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
12127 dispatch: u64,
12128 ) -> CurrentThreadHostDispatchResult {
12129 CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12130 }
12131
12132 fn dispatch_submission_failure_current_thread_tasks(
12133 dispatch: u64,
12134 ) -> CurrentThreadHostDispatchResult {
12135 CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12136 }
12137
12138 fn dispatch_retirement_turn_current_thread_tasks(
12139 dispatch: u64,
12140 ) -> CurrentThreadHostDispatchResult {
12141 CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
12142 }
12143
12144 fn dispatch_nested_retirement_current_thread_tasks(
12145 dispatch: u64,
12146 ) -> CurrentThreadHostDispatchResult {
12147 CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
12148 }
12149
12150 fn dispatch_sustained_producer_current_thread_tasks(
12151 dispatch: u64,
12152 ) -> CurrentThreadHostDispatchResult {
12153 CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
12154 }
12155
12156 fn dispatch_retirement_retry_current_thread_tasks(
12157 dispatch: u64,
12158 ) -> CurrentThreadHostDispatchResult {
12159 CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
12160 }
12161
12162 fn dispatch_final_retirement_offer_current_thread_tasks(
12163 dispatch: u64,
12164 ) -> CurrentThreadHostDispatchResult {
12165 CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
12166 }
12167
12168 fn dispatch_inline_final_offer_current_thread_tasks(
12169 dispatch: u64,
12170 ) -> CurrentThreadHostDispatchResult {
12171 CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
12172 }
12173
12174 fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
12175 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12176 .lock()
12177 .unwrap()
12178 .push(dispatch);
12179 assert!(
12180 !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12181 "injected CurrentThread host dispatch panic"
12182 );
12183 CurrentThreadHostDispatchResult::Accepted
12184 }
12185
12186 fn panic_once_current_thread_armed_republish_host_dispatch(
12187 dispatch: u64,
12188 ) -> CurrentThreadHostDispatchResult {
12189 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12190 .lock()
12191 .unwrap()
12192 .push(dispatch);
12193 assert!(
12194 !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12195 "injected armed CurrentThread host dispatch panic"
12196 );
12197 CurrentThreadHostDispatchResult::Accepted
12198 }
12199
12200 fn record_current_thread_republish_rearm_dispatch(
12201 dispatch: u64,
12202 ) -> CurrentThreadHostDispatchResult {
12203 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
12204 .lock()
12205 .unwrap()
12206 .push(dispatch);
12207 CurrentThreadHostDispatchResult::Accepted
12208 }
12209
12210 fn reject_initial_current_thread_publication_once(
12211 dispatch: u64,
12212 ) -> CurrentThreadHostDispatchResult {
12213 let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12214 .lock()
12215 .unwrap();
12216 dispatches.push(dispatch);
12217 if dispatches.len() == 1 {
12218 CurrentThreadHostDispatchResult::Unavailable
12219 } else {
12220 CurrentThreadHostDispatchResult::Accepted
12221 }
12222 }
12223
12224 fn reject_current_thread_replacement_publication_once(
12225 dispatch: u64,
12226 ) -> CurrentThreadHostDispatchResult {
12227 let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12228 .lock()
12229 .unwrap();
12230 dispatches.push(dispatch);
12231 if dispatches.len() == 2 {
12232 CurrentThreadHostDispatchResult::Unavailable
12233 } else {
12234 CurrentThreadHostDispatchResult::Accepted
12235 }
12236 }
12237
12238 fn fail_current_thread_replacement_publication_once(
12239 dispatch: u64,
12240 ) -> CurrentThreadHostDispatchResult {
12241 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12242 .lock()
12243 .unwrap()
12244 .push(dispatch);
12245 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
12246 }
12247
12248 fn count_current_thread_shutdown_republish_host_dispatch(
12249 _dispatch: u64,
12250 ) -> CurrentThreadHostDispatchResult {
12251 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12252 CurrentThreadHostDispatchResult::Accepted
12253 }
12254
12255 fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12256 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12257 CurrentThreadHostDispatchResult::Accepted
12258 }
12259
12260 fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
12261 let pending = executor.dispatch_pending.load(Ordering::Acquire);
12262 if pending != 0 {
12263 return pending;
12264 }
12265 let dispatch = next_current_thread_dispatch_id();
12266 match executor.dispatch_pending.compare_exchange(
12267 0,
12268 dispatch,
12269 Ordering::AcqRel,
12270 Ordering::Acquire,
12271 ) {
12272 Ok(_) => dispatch,
12273 Err(pending) => pending,
12274 }
12275 }
12276
12277 fn current_thread_dispatch_failure(
12278 dispatch: u64,
12279 epoch: u64,
12280 ) -> CurrentThreadTaskDispatchFailure {
12281 CurrentThreadTaskDispatchFailure { dispatch, epoch }
12282 }
12283
12284 fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
12285 let executor = controller
12286 .running_current_thread_executor()
12287 .expect("the test controller must have a running CurrentThread executor");
12288 current_thread_dispatch(&executor)
12289 }
12290
12291 #[test]
12292 fn current_thread_executor_drives_spawned_tasks() {
12293 let metrics = Arc::new(RuntimeMetrics::default());
12294 let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
12295 let scheduler = Arc::clone(&executor);
12296 let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
12297 scheduler.schedule(runnable);
12298 });
12299
12300 executor.schedule(runnable);
12301
12302 let mut output = None;
12303 {
12304 let mut future = std::pin::pin!(async {
12305 output = Some(task.await);
12306 });
12307 executor.block_on(future.as_mut());
12308 }
12309 assert_eq!(output, Some(42));
12310 assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
12311 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
12312 }
12313
12314 #[cfg(not(target_family = "wasm"))]
12315 #[test]
12316 fn current_thread_shutdown_wake_does_not_repoll_user_future() {
12317 use std::sync::mpsc;
12318
12319 let executor = Arc::new(CurrentThreadExecutor::with_detection(
12320 Arc::new(RuntimeMetrics::default()),
12321 false,
12322 None,
12323 ));
12324 let polls = Arc::new(AtomicUsize::new(0));
12325 let (first_poll_tx, first_poll_rx) = mpsc::channel();
12326 let (outcome_tx, outcome_rx) = mpsc::channel();
12327 let driver_executor = Arc::clone(&executor);
12328 let driver_polls = Arc::clone(&polls);
12329 let driver = std::thread::spawn(move || {
12330 let mut future = std::pin::pin!(StopPollProbe {
12331 polls: driver_polls,
12332 first_poll: Some(first_poll_tx)
12333 });
12334 outcome_tx
12335 .send(driver_executor.block_on(future.as_mut()))
12336 .unwrap();
12337 });
12338
12339 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12340 wait_for_generation_parker_to_sleep(&executor.stop);
12341 executor.stop.begin_shutdown();
12342
12343 assert_eq!(
12344 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12345 BlockOnOutcome::Stopped
12346 );
12347 driver.join().unwrap();
12348 assert_eq!(
12349 polls.load(Ordering::SeqCst),
12350 1,
12351 "a shutdown wake must be checked before the next CurrentThread user poll"
12352 );
12353 }
12354
12355 #[cfg(not(target_family = "wasm"))]
12356 #[test]
12357 fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
12358 use std::sync::mpsc;
12359
12360 let metrics = Arc::new(RuntimeMetrics::default());
12361 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12362 Arc::clone(&metrics),
12363 accept_current_thread_host_dispatch,
12364 ));
12365 let polls = Arc::new(AtomicUsize::new(0));
12366 let task_polls = Arc::clone(&polls);
12367 let (runnable, task) = async_task::spawn(
12368 async move {
12369 task_polls.fetch_add(1, Ordering::SeqCst);
12370 },
12371 |_| {},
12372 );
12373 executor.schedule(runnable);
12374 let dispatch = current_thread_dispatch(&executor);
12375
12376 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
12377 let (release_claim_tx, release_claim_rx) = mpsc::channel();
12378 let driver_executor = Arc::clone(&executor);
12379 let driver = std::thread::spawn(move || {
12380 CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
12381 *slot.borrow_mut() = Some(Box::new(move || {
12382 claim_locked_tx.send(()).unwrap();
12383 release_claim_rx.recv().unwrap();
12384 }));
12385 });
12386 driver_executor.drive_host_turn_for_dispatch(dispatch);
12387 });
12388 claim_locked_rx
12389 .recv_timeout(Duration::from_secs(2))
12390 .expect("the admitted host turn must pause before its runnable claim");
12391
12392 executor.stop.publish_stopping();
12393 release_claim_tx.send(()).unwrap();
12394 driver.join().unwrap();
12395
12396 assert_eq!(
12397 polls.load(Ordering::SeqCst),
12398 0,
12399 "stop publication must outrank a later runnable claim by an already-admitted host turn"
12400 );
12401 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12402
12403 executor.begin_shutdown();
12404 assert!(
12405 futures::executor::block_on(task.fallible()).is_none(),
12406 "shutdown must cancel the runnable rejected by the stopped claim"
12407 );
12408 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12409 }
12410
12411 #[cfg(not(target_family = "wasm"))]
12412 #[test]
12413 fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
12414 use std::sync::mpsc;
12415
12416 let metrics = Arc::new(RuntimeMetrics::default());
12417 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12418 Arc::clone(&metrics),
12419 accept_current_thread_host_dispatch,
12420 ));
12421 let polls = Arc::new(AtomicUsize::new(0));
12422 let task_polls = Arc::clone(&polls);
12423 let (runnable, task) = async_task::spawn(
12424 async move {
12425 task_polls.fetch_add(1, Ordering::SeqCst);
12426 },
12427 |_| {},
12428 );
12429 executor.schedule(runnable);
12430
12431 let queue = executor
12432 .queue
12433 .lock()
12434 .unwrap_or_else(std::sync::PoisonError::into_inner);
12435 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
12436 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
12437 let cancelling_executor = Arc::clone(&executor);
12438 let cancellation = std::thread::spawn(move || {
12439 let scheduler = cancelling_executor
12440 .scheduler_idle_lock
12441 .lock()
12442 .unwrap_or_else(std::sync::PoisonError::into_inner);
12443 let cancelling = cancelling_executor
12444 .begin_failed_dispatch_cancellation()
12445 .expect("the test must publish terminal dispatch cancellation");
12446 drop(scheduler);
12447 cancellation_published_tx.send(()).unwrap();
12448 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
12449 cancellation_done_tx.send(()).unwrap();
12450 });
12451 cancellation_published_rx
12452 .recv_timeout(Duration::from_secs(2))
12453 .expect("terminal dispatch cancellation must publish before queue draining");
12454
12455 let claimant_executor = Arc::clone(&executor);
12456 let claimant = std::thread::spawn(move || claimant_executor.drain_one());
12457 assert!(
12458 !claimant.join().unwrap(),
12459 "a runnable claim after terminal cancellation publication must be rejected"
12460 );
12461 assert_eq!(polls.load(Ordering::SeqCst), 0);
12462 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12463 assert!(
12464 matches!(
12465 cancellation_done_rx.try_recv(),
12466 Err(mpsc::TryRecvError::Empty)
12467 ),
12468 "terminal cancellation must still be waiting to drain the queued runnable"
12469 );
12470
12471 drop(queue);
12472 cancellation_done_rx
12473 .recv_timeout(Duration::from_secs(2))
12474 .expect("terminal cancellation must drain the rejected runnable");
12475 cancellation.join().unwrap();
12476 assert!(
12477 futures::executor::block_on(task.fallible()).is_none(),
12478 "terminal cancellation must cancel the runnable it prevented from starting"
12479 );
12480 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12481 }
12482
12483 #[cfg(not(target_family = "wasm"))]
12484 #[test]
12485 fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
12486 use std::sync::mpsc;
12487
12488 let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
12489 let polls = Arc::new(AtomicUsize::new(0));
12490 let (first_poll_tx, first_poll_rx) = mpsc::channel();
12491 let (outcome_tx, outcome_rx) = mpsc::channel();
12492 let driver_executor = Arc::clone(&executor);
12493 let driver_polls = Arc::clone(&polls);
12494 let driver = std::thread::spawn(move || {
12495 let mut future = std::pin::pin!(StopPollProbe {
12496 polls: driver_polls,
12497 first_poll: Some(first_poll_tx)
12498 });
12499 outcome_tx
12500 .send(driver_executor.block_on(future.as_mut()))
12501 .unwrap();
12502 });
12503
12504 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12505 wait_for_generation_parker_to_sleep(&executor.stop);
12506 executor.stop.begin_shutdown();
12507
12508 assert_eq!(
12509 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12510 BlockOnOutcome::Stopped
12511 );
12512 driver.join().unwrap();
12513 assert_eq!(
12514 polls.load(Ordering::SeqCst),
12515 1,
12516 "a shutdown wake must be checked before the next parking block_on user poll"
12517 );
12518 }
12519
12520 #[cfg(not(target_family = "wasm"))]
12521 #[test]
12522 fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
12523 use std::sync::mpsc;
12524
12525 let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
12526 let polls = Arc::new(AtomicUsize::new(0));
12527 let (first_poll_tx, first_poll_rx) = mpsc::channel();
12528 let (outcome_tx, outcome_rx) = mpsc::channel();
12529 let driver_executor = Arc::clone(&executor);
12530 let driver_polls = Arc::clone(&polls);
12531 let driver = std::thread::spawn(move || {
12532 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
12533 let mut future = std::pin::pin!(StopPollProbe {
12534 polls: driver_polls,
12535 first_poll: Some(first_poll_tx)
12536 });
12537 outcome_tx
12538 .send(driver_executor.block_on(future.as_mut()))
12539 .unwrap();
12540 });
12541
12542 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12543 wait_for_generation_parker_to_sleep(&executor.stop);
12544 executor.stop.begin_shutdown();
12545
12546 assert_eq!(
12547 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12548 BlockOnOutcome::Stopped
12549 );
12550 driver.join().unwrap();
12551 assert_eq!(
12552 polls.load(Ordering::SeqCst),
12553 1,
12554 "a shutdown wake must be checked before the next cooperative user poll"
12555 );
12556 }
12557
12558 #[test]
12559 fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
12560 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12561 Arc::new(RuntimeMetrics::default()),
12562 accept_current_thread_host_dispatch,
12563 ));
12564 let pending = u64::MAX - 1;
12565 executor.dispatch_pending.store(pending, Ordering::Release);
12566 let mut allocations = 0;
12567
12568 executor.request_drain_with(|| {
12569 allocations += 1;
12570 u64::MAX
12571 });
12572
12573 assert_eq!(allocations, 0);
12574 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
12575 }
12576
12577 #[cfg(not(target_family = "wasm"))]
12578 #[test]
12579 fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
12580 use std::sync::mpsc;
12581
12582 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12583 Arc::new(RuntimeMetrics::default()),
12584 accept_current_thread_host_dispatch,
12585 ));
12586 let dispatch = current_thread_dispatch(&executor);
12587 let scheduler = executor
12588 .scheduler_idle_lock
12589 .lock()
12590 .unwrap_or_else(std::sync::PoisonError::into_inner);
12591 let (claiming_tx, claiming_rx) = mpsc::channel();
12592 let (claimed_tx, claimed_rx) = mpsc::channel();
12593 let claiming_executor = Arc::clone(&executor);
12594 let claimant = std::thread::spawn(move || {
12595 claiming_tx.send(()).unwrap();
12596 claimed_tx
12597 .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
12598 .unwrap();
12599 });
12600 claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12601
12602 executor.stop.begin_shutdown();
12603 drop(scheduler);
12604
12605 assert!(
12606 !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12607 "an old-generation dispatch must not claim a host turn after stop becomes visible"
12608 );
12609 claimant.join().unwrap();
12610 assert_eq!(
12611 executor.dispatch_pending.load(Ordering::Acquire),
12612 dispatch,
12613 "rejecting the stopped claim must not consume its stale capability"
12614 );
12615 }
12616
12617 #[cfg(not(target_family = "wasm"))]
12618 #[test]
12619 fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
12620 use std::sync::mpsc;
12621
12622 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12623 let metrics = Arc::new(RuntimeMetrics::default());
12624 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12625 Arc::clone(&metrics),
12626 count_current_thread_stopped_host_dispatch,
12627 ));
12628 let scheduler = executor
12629 .scheduler_idle_lock
12630 .lock()
12631 .unwrap_or_else(std::sync::PoisonError::into_inner);
12632 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
12633 let scheduling_executor = Arc::clone(&executor);
12634 let scheduling = std::thread::spawn(move || {
12635 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
12636 *slot.borrow_mut() = Some(Box::new(move || {
12637 publication_blocked_tx.send(()).unwrap();
12638 }));
12639 });
12640 let scheduler = Arc::clone(&scheduling_executor);
12641 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
12642 scheduler.schedule(runnable);
12643 });
12644 task.detach();
12645 scheduling_executor.schedule(runnable);
12646 });
12647
12648 publication_blocked_rx
12649 .recv_timeout(Duration::from_secs(2))
12650 .expect("dispatch publication must block behind the scheduler-idle mutex");
12651 executor.begin_shutdown();
12652 drop(scheduler);
12653 scheduling.join().unwrap();
12654 executor.wait_until_scheduler_idle();
12655
12656 assert_eq!(
12657 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
12658 0,
12659 "a publication that reaches the scheduler-idle barrier after stop must not call the host"
12660 );
12661 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
12662 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12663 }
12664
12665 #[test]
12666 fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
12667 use std::{sync::mpsc, time::Duration};
12668
12669 let metrics = Arc::new(RuntimeMetrics::default());
12670 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12671 Arc::clone(&metrics),
12672 accept_current_thread_host_dispatch,
12673 ));
12674 let scheduler = Arc::clone(&executor);
12675 let completed = Arc::new(AtomicBool::new(false));
12676 let completed_task = Arc::clone(&completed);
12677 let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
12678 let shared = async move {
12679 wake_rx.await.expect("wake sender dropped");
12680 }
12681 .boxed()
12682 .shared();
12683 let (runnable, task) = async_task::spawn(
12684 async move {
12685 shared.await;
12686 completed_task.store(true, Ordering::SeqCst);
12687 },
12688 move |runnable| scheduler.schedule(runnable),
12689 );
12690
12691 executor.schedule(runnable);
12692 assert!(
12693 !completed.load(Ordering::SeqCst),
12694 "accepted host dispatch must defer the first poll"
12695 );
12696 executor.drive_host_turn();
12697
12698 let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
12699 std::thread::spawn(move || {
12700 wake_tx.send(()).expect("shared wake receiver dropped");
12701 send_returned_tx.send(()).unwrap();
12702 });
12703 send_returned_rx
12704 .recv_timeout(Duration::from_secs(1))
12705 .expect("Shared wake re-entered its own waker mutex through inline polling");
12706 assert!(
12707 !completed.load(Ordering::SeqCst),
12708 "the wake must remain queued until a fresh host turn"
12709 );
12710
12711 executor.drive_host_turn();
12712 futures::executor::block_on(task);
12713 assert!(completed.load(Ordering::SeqCst));
12714 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12715 }
12716
12717 #[test]
12718 fn current_thread_hostless_wake_is_enqueue_only() {
12719 use std::{sync::mpsc, time::Duration};
12720
12721 struct LockOnSecondPoll {
12722 gate: Arc<Mutex<()>>,
12723 first: bool,
12724 waker: Option<mpsc::Sender<Waker>>,
12725 completed: Arc<AtomicBool>,
12726 }
12727
12728 impl Future for LockOnSecondPoll {
12729 type Output = ();
12730
12731 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
12732 if self.first {
12733 self.first = false;
12734 self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
12735 return Poll::Pending;
12736 }
12737 drop(self.gate.lock().unwrap());
12738 self.completed.store(true, Ordering::SeqCst);
12739 Poll::Ready(())
12740 }
12741 }
12742
12743 let metrics = Arc::new(RuntimeMetrics::default());
12744 let executor = Arc::new(CurrentThreadExecutor::new(metrics));
12745 let gate = Arc::new(Mutex::new(()));
12746 let completed = Arc::new(AtomicBool::new(false));
12747 let (waker_tx, waker_rx) = mpsc::channel();
12748 let scheduler = Arc::clone(&executor);
12749 let (runnable, task) = async_task::spawn(
12750 LockOnSecondPoll {
12751 gate: Arc::clone(&gate),
12752 first: true,
12753 waker: Some(waker_tx),
12754 completed: Arc::clone(&completed),
12755 },
12756 move |runnable| scheduler.schedule(runnable),
12757 );
12758 executor.schedule(runnable);
12759 executor.drive_host_turn();
12760 let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
12761
12762 let (returned_tx, returned_rx) = mpsc::channel();
12763 let wake_gate = Arc::clone(&gate);
12764 let waking = std::thread::spawn(move || {
12765 let _held = wake_gate.lock().unwrap();
12766 waker.wake();
12767 returned_tx.send(()).unwrap();
12768 });
12769 returned_rx
12770 .recv_timeout(Duration::from_secs(1))
12771 .expect("hostless wake must enqueue without polling under the caller's lock");
12772 waking.join().unwrap();
12773 assert!(!completed.load(Ordering::SeqCst));
12774
12775 executor.drive_host_turn();
12776 futures::executor::block_on(task);
12777 assert!(completed.load(Ordering::SeqCst));
12778 }
12779
12780 #[cfg(not(target_family = "wasm"))]
12781 #[test]
12782 fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
12783 use std::sync::mpsc;
12784
12785 let controller = Arc::new(current_thread_controller(
12786 "current-thread-hostless-late-work",
12787 ));
12788 let backend = controller.backend();
12789 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
12790 panic!("configured CurrentThread backend must create a CurrentThread executor");
12791 };
12792 let executor = Arc::clone(executor);
12793 drop(backend);
12794
12795 let (value_tx, value_rx) = oneshot::channel();
12796 let (done_tx, done_rx) = mpsc::channel();
12797 let block_on_controller = Arc::clone(&controller);
12798 let runner = std::thread::spawn(move || {
12799 let mut output = None;
12800 let outcome = {
12801 let mut future = std::pin::pin!(async {
12802 output = Some(value_rx.await.expect("scheduled sender must deliver"));
12803 });
12804 block_on_controller.block_on(future.as_mut())
12805 };
12806 done_tx.send((outcome, output)).unwrap();
12807 });
12808
12809 wait_until(
12810 "the hostless CurrentThread block_on driver to sleep",
12811 || {
12812 executor
12813 .stop
12814 .parkers
12815 .lock()
12816 .unwrap_or_else(std::sync::PoisonError::into_inner)
12817 .iter()
12818 .filter_map(Weak::upgrade)
12819 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12820 },
12821 );
12822
12823 controller
12824 .try_spawn_detached(async move {
12825 value_tx
12826 .send(37usize)
12827 .expect("block_on receiver must remain alive");
12828 })
12829 .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
12830
12831 let (outcome, output) = done_rx
12832 .recv_timeout(Duration::from_secs(2))
12833 .expect("queue publication must wake the sleeping explicit driver");
12834 assert_eq!(outcome, BlockOnOutcome::Completed);
12835 assert_eq!(output, Some(37));
12836 runner.join().unwrap();
12837 controller.shutdown().unwrap();
12838 }
12839
12840 #[cfg(not(target_family = "wasm"))]
12841 #[test]
12842 fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
12843 use std::sync::mpsc;
12844
12845 struct BlockingPoll {
12846 entered: Option<mpsc::Sender<()>>,
12847 release: mpsc::Receiver<()>,
12848 }
12849
12850 impl Future for BlockingPoll {
12851 type Output = ();
12852
12853 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12854 self.entered.take().unwrap().send(()).unwrap();
12855 self.release.recv().unwrap();
12856 Poll::Ready(())
12857 }
12858 }
12859
12860 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12861 Arc::new(RuntimeMetrics::default()),
12862 reject_current_thread_host_dispatch,
12863 ));
12864 let (value_tx, value_rx) = oneshot::channel();
12865 let (older_done_tx, older_done_rx) = mpsc::channel();
12866 let older_executor = Arc::clone(&executor);
12867 let older = std::thread::spawn(move || {
12868 let mut value = None;
12869 {
12870 let mut future = std::pin::pin!(async {
12871 value = Some(value_rx.await.unwrap());
12872 });
12873 assert_eq!(
12874 older_executor.block_on(future.as_mut()),
12875 BlockOnOutcome::Completed
12876 );
12877 }
12878 older_done_tx.send(value.unwrap()).unwrap();
12879 });
12880 wait_until("the older CurrentThread driver to sleep", || {
12881 executor
12882 .stop
12883 .parkers
12884 .lock()
12885 .unwrap_or_else(std::sync::PoisonError::into_inner)
12886 .iter()
12887 .filter_map(Weak::upgrade)
12888 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12889 });
12890
12891 let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
12892 let (release_newer_tx, release_newer_rx) = mpsc::channel();
12893 let newer_executor = Arc::clone(&executor);
12894 let newer = std::thread::spawn(move || {
12895 let mut future = std::pin::pin!(BlockingPoll {
12896 entered: Some(newer_entered_tx),
12897 release: release_newer_rx
12898 });
12899 assert_eq!(
12900 newer_executor.block_on(future.as_mut()),
12901 BlockOnOutcome::Completed
12902 );
12903 });
12904 newer_entered_rx
12905 .recv_timeout(Duration::from_secs(1))
12906 .expect("the newer driver must be blocked inside its poll");
12907
12908 let scheduler = Arc::clone(&executor);
12909 let (runnable, task) = async_task::spawn(
12910 async move {
12911 value_tx.send(41usize).unwrap();
12912 },
12913 move |runnable| scheduler.schedule(runnable),
12914 );
12915 task.detach();
12916 executor.schedule(runnable);
12917
12918 assert_eq!(
12919 older_done_rx
12920 .recv_timeout(Duration::from_secs(2))
12921 .expect("queue publication must also wake the older sleeping driver"),
12922 41
12923 );
12924 release_newer_tx.send(()).unwrap();
12925 older.join().unwrap();
12926 newer.join().unwrap();
12927 }
12928
12929 #[test]
12930 fn current_thread_shutdown_cancels_host_queued_task() {
12931 use std::sync::mpsc;
12932
12933 struct DropSignal(mpsc::Sender<()>);
12934
12935 impl Drop for DropSignal {
12936 fn drop(&mut self) {
12937 self.0.send(()).unwrap();
12938 }
12939 }
12940
12941 let metrics = Arc::new(RuntimeMetrics::default());
12942 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12943 Arc::clone(&metrics),
12944 accept_current_thread_host_dispatch,
12945 ));
12946 let scheduler = Arc::clone(&executor);
12947 let (dropped_tx, dropped_rx) = mpsc::channel();
12948 let signal = DropSignal(dropped_tx);
12949 let (runnable, task) = async_task::spawn(
12950 async move {
12951 let _signal = signal;
12952 futures::future::pending::<()>().await;
12953 },
12954 move |runnable| scheduler.schedule(runnable),
12955 );
12956
12957 executor.schedule(runnable);
12958 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12959 executor.begin_shutdown();
12960
12961 dropped_rx
12962 .recv_timeout(Duration::from_secs(1))
12963 .expect("shutdown must drop a task queued behind a host turn");
12964 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12965 assert!(
12966 futures::executor::block_on(task.fallible()).is_none(),
12967 "the queued task handle must resolve as cancelled"
12968 );
12969 }
12970
12971 #[test]
12972 fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
12973 reset_current_thread_recovery_host_dispatches();
12974 let metrics = Arc::new(RuntimeMetrics::default());
12975 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12976 Arc::clone(&metrics),
12977 count_current_thread_recovery_host_dispatch,
12978 ));
12979 let scheduler = Arc::clone(&executor);
12980 let completed = Arc::new(AtomicBool::new(false));
12981 let completed_task = Arc::clone(&completed);
12982 let (runnable, task) = async_task::spawn(
12983 async move {
12984 completed_task.store(true, Ordering::SeqCst);
12985 },
12986 move |runnable| scheduler.schedule(runnable),
12987 );
12988
12989 executor.schedule(runnable);
12990 assert_eq!(current_thread_recovery_host_dispatches(), 1);
12991 let lost_dispatch = current_thread_dispatch(&executor);
12992
12993 executor.request_drain_if_queued();
12997 assert_eq!(
12998 current_thread_recovery_host_dispatches(),
12999 2,
13000 "the newly registered host must receive the pending executor dispatch"
13001 );
13002 assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13003 executor.drive_host_turn_for_dispatch(lost_dispatch);
13004 futures::executor::block_on(task);
13005 assert!(completed.load(Ordering::SeqCst));
13006 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13007
13008 let second_completed = Arc::new(AtomicBool::new(false));
13009 let second_completed_task = Arc::clone(&second_completed);
13010 let second_scheduler = Arc::clone(&executor);
13011 let (second_runnable, second_task) = async_task::spawn(
13012 async move {
13013 second_completed_task.store(true, Ordering::SeqCst);
13014 },
13015 move |runnable| second_scheduler.schedule(runnable),
13016 );
13017 executor.schedule(second_runnable);
13018 assert_eq!(
13019 current_thread_recovery_host_dispatches(),
13020 3,
13021 "work queued after the stale callback must receive a new dispatch"
13022 );
13023 let third_dispatch = current_thread_dispatch(&executor);
13024
13025 executor.drive_host_turn_for_dispatch(lost_dispatch);
13028 assert!(!second_completed.load(Ordering::SeqCst));
13029 executor.drive_host_turn_for_dispatch(third_dispatch);
13030 futures::executor::block_on(second_task);
13031 assert!(second_completed.load(Ordering::SeqCst));
13032
13033 let third_scheduler = Arc::clone(&executor);
13034 let (third_runnable, third_task) =
13035 async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
13036 executor.schedule(third_runnable);
13037 assert_eq!(
13038 current_thread_recovery_host_dispatches(),
13039 4,
13040 "a stale callback must not leave the dispatch latch stuck"
13041 );
13042 executor.drive_host_turn();
13043 futures::executor::block_on(third_task);
13044 }
13045
13046 #[test]
13047 fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
13048 let metrics = Arc::new(RuntimeMetrics::default());
13049 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13050 Arc::clone(&metrics),
13051 accept_current_thread_host_dispatch,
13052 ));
13053 let scheduler = Arc::clone(&executor);
13054 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13055 scheduler.schedule(runnable)
13056 });
13057
13058 executor.schedule(runnable);
13059 let failed_dispatch = current_thread_dispatch(&executor);
13060 executor.cancel_host_dispatch_for_test(failed_dispatch);
13061 let recovery_dispatch = current_thread_dispatch(&executor);
13062 assert_eq!(
13063 executor.recovery_dispatch.load(Ordering::Acquire),
13064 recovery_dispatch
13065 );
13066
13067 executor.request_drain_if_queued();
13068 let new_host_dispatch = current_thread_dispatch(&executor);
13069 assert_eq!(new_host_dispatch, recovery_dispatch);
13070 assert_eq!(
13071 executor.recovery_dispatch.load(Ordering::Acquire),
13072 recovery_dispatch
13073 );
13074 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13075
13076 executor.drive_host_turn_for_dispatch(new_host_dispatch);
13077 assert_eq!(futures::executor::block_on(task), 17);
13078 }
13079
13080 #[cfg(not(target_family = "wasm"))]
13081 #[test]
13082 fn current_thread_host_admission_serializes_dispatch_publication() {
13083 use std::sync::mpsc;
13084
13085 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13086 let metrics = Arc::new(RuntimeMetrics::default());
13087 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13088 Arc::clone(&metrics),
13089 count_current_thread_admission_host_dispatch,
13090 ));
13091 let first_scheduler = Arc::clone(&executor);
13092 let (first_runnable, first_task) =
13093 async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
13094 first_task.detach();
13095 executor.schedule(first_runnable);
13096 let dispatch = current_thread_dispatch(&executor);
13097 assert_eq!(
13098 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13099 1
13100 );
13101
13102 let (claimed_tx, claimed_rx) = mpsc::channel();
13103 let (release_tx, release_rx) = mpsc::channel();
13104 let driver_executor = Arc::clone(&executor);
13105 let driver = std::thread::spawn(move || {
13106 CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
13107 *slot.borrow_mut() = Some(Box::new(move || {
13108 claimed_tx.send(()).unwrap();
13109 release_rx.recv().unwrap();
13110 }));
13111 });
13112 driver_executor.drive_host_turn_for_dispatch(dispatch);
13113 });
13114 claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13115
13116 let second_ran = Arc::new(AtomicBool::new(false));
13117 let second_ran_task = Arc::clone(&second_ran);
13118 let scheduling_executor = Arc::clone(&executor);
13119 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13120 let (scheduled_tx, scheduled_rx) = mpsc::channel();
13121 let scheduling = std::thread::spawn(move || {
13122 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13123 *slot.borrow_mut() = Some(Box::new(move || {
13124 publication_blocked_tx.send(()).unwrap();
13125 }));
13126 });
13127 let scheduler = Arc::clone(&scheduling_executor);
13128 let (runnable, task) = async_task::spawn(
13129 async move {
13130 second_ran_task.store(true, Ordering::SeqCst);
13131 },
13132 move |runnable| scheduler.schedule(runnable),
13133 );
13134 task.detach();
13135 scheduling_executor.schedule(runnable);
13136 scheduled_tx.send(()).unwrap();
13137 });
13138
13139 publication_blocked_rx
13140 .recv_timeout(Duration::from_secs(2))
13141 .unwrap();
13142 assert!(
13143 matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
13144 "dispatch publication must wait until host admission publishes its draining role"
13145 );
13146 assert_eq!(
13147 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13148 1,
13149 "the admission gap must not publish a duplicate host dispatch"
13150 );
13151
13152 release_tx.send(()).unwrap();
13153 scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13154 scheduling.join().unwrap();
13155 driver.join().unwrap();
13156 if !second_ran.load(Ordering::SeqCst) {
13157 executor.drive_host_turn();
13158 }
13159 assert!(second_ran.load(Ordering::SeqCst));
13160 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13161 }
13162
13163 #[test]
13164 fn current_thread_host_turn_yields_after_runnable_budget() {
13165 struct SelfWakingFuture {
13166 remaining: usize,
13167 }
13168
13169 impl Future for SelfWakingFuture {
13170 type Output = ();
13171
13172 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
13173 if self.remaining == 0 {
13174 return Poll::Ready(());
13175 }
13176 self.remaining -= 1;
13177 cx.waker().wake_by_ref();
13178 Poll::Pending
13179 }
13180 }
13181
13182 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13183 let metrics = Arc::new(RuntimeMetrics::default());
13184 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13185 Arc::clone(&metrics),
13186 count_current_thread_budget_host_dispatch,
13187 ));
13188 let scheduler = Arc::clone(&executor);
13189 let (runnable, task) = async_task::spawn(
13190 SelfWakingFuture {
13191 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
13192 },
13193 move |runnable| scheduler.schedule(runnable),
13194 );
13195
13196 executor.schedule(runnable);
13197 executor.drive_host_turn();
13198
13199 assert_eq!(
13200 metrics.runnable_polls.load(Ordering::Relaxed),
13201 CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
13202 );
13203 assert_eq!(
13204 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13205 2,
13206 "remaining hot work must continue through a fresh host turn"
13207 );
13208
13209 executor.drive_host_turn();
13210 futures::executor::block_on(task);
13211 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13212 }
13213
13214 #[cfg(not(target_family = "wasm"))]
13215 #[test]
13216 fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
13217 const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
13218
13219 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13220 let metrics = Arc::new(RuntimeMetrics::default());
13221 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13222 Arc::clone(&metrics),
13223 count_current_thread_blocking_budget_host_dispatch,
13224 ));
13225 let work = GenerationWork::new();
13226 let observed = Arc::new(Mutex::new(Vec::new()));
13227
13228 let task_executor = Arc::clone(&executor);
13229 let wake_executor = Arc::clone(&executor);
13230 let producer_executor = Arc::clone(&executor);
13231 let producer_work = Arc::clone(&work);
13232 let producer_observed = Arc::clone(&observed);
13233 let outer_registration = work
13234 .try_register_work()
13235 .expect("the outer blocking job must be accepted");
13236 let (runnable, task) = async_task::spawn(
13237 async move {
13238 let queued = task_executor
13239 .schedule_blocking(
13240 move || {
13241 std::thread::spawn(move || {
13242 (0..QUEUED_BLOCKING_JOBS)
13243 .map(|index| {
13244 let registration = producer_work
13245 .try_register_work()
13246 .expect("queued blocking work must be accepted");
13247 let observed = Arc::clone(&producer_observed);
13248 producer_executor.schedule_blocking(
13249 move || {
13250 observed
13251 .lock()
13252 .unwrap_or_else(std::sync::PoisonError::into_inner)
13253 .push(index);
13254 },
13255 registration,
13256 )
13257 })
13258 .collect::<Vec<_>>()
13259 })
13260 .join()
13261 .expect("the blocking producer must finish")
13262 },
13263 outer_registration,
13264 )
13265 .await
13266 .expect("the inline blocking job must finish");
13267 for handle in queued {
13268 handle.await.expect("queued blocking work must finish");
13269 }
13270 },
13271 move |runnable| wake_executor.schedule(runnable),
13272 );
13273
13274 executor.schedule(runnable);
13275 executor.drive_host_turn();
13276
13277 let first_turn = observed
13278 .lock()
13279 .unwrap_or_else(std::sync::PoisonError::into_inner)
13280 .clone();
13281 assert!(
13282 !first_turn.is_empty(),
13283 "the first host turn must service queued blocking work"
13284 );
13285 assert!(
13286 first_turn.len() < QUEUED_BLOCKING_JOBS,
13287 "inline lane release must not drain the entire blocking FIFO in one host turn"
13288 );
13289 assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
13290 assert_eq!(
13291 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13292 2,
13293 "remaining blocking work must continue through a fresh host turn"
13294 );
13295
13296 let mut continuation_turns = 0;
13297 while observed
13298 .lock()
13299 .unwrap_or_else(std::sync::PoisonError::into_inner)
13300 .len()
13301 < QUEUED_BLOCKING_JOBS
13302 {
13303 continuation_turns += 1;
13304 assert!(
13305 continuation_turns <= QUEUED_BLOCKING_JOBS,
13306 "queued blocking work failed to make bounded host-turn progress"
13307 );
13308 executor.drive_host_turn();
13309 }
13310 executor.drive_host_turn();
13311 futures::executor::block_on(task);
13312
13313 assert_eq!(
13314 *observed
13315 .lock()
13316 .unwrap_or_else(std::sync::PoisonError::into_inner),
13317 (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
13318 "blocking jobs must preserve FIFO order across host turns"
13319 );
13320 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13321 }
13322
13323 #[cfg(not(target_family = "wasm"))]
13324 #[test]
13325 fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
13326 use std::sync::mpsc;
13327
13328 struct SelfWakingFuture {
13329 remaining: usize,
13330 }
13331
13332 impl Future for SelfWakingFuture {
13333 type Output = ();
13334
13335 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
13336 if self.remaining == 0 {
13337 return Poll::Ready(());
13338 }
13339 self.remaining -= 1;
13340 cx.waker().wake_by_ref();
13341 Poll::Pending
13342 }
13343 }
13344
13345 let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
13346 let old_generation = controller.backend().generation();
13347 controller
13348 .try_spawn_detached(SelfWakingFuture {
13349 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
13350 })
13351 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
13352 let dispatch = controller_current_thread_dispatch(&controller);
13353
13354 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
13355 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
13356 let driver_controller = Arc::clone(&controller);
13357 let driver = std::thread::spawn(move || {
13358 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13359 *slot.borrow_mut() = Some(Box::new(move || {
13360 dispatch_entered_tx.send(()).unwrap();
13361 release_dispatch_rx.recv().unwrap();
13362 }));
13363 });
13364 driver_controller.drive_current_thread_dispatch(dispatch, false);
13365 });
13366 dispatch_entered_rx
13367 .recv_timeout(Duration::from_secs(2))
13368 .expect("the bounded host turn must begin publishing its continuation");
13369
13370 let (shutdown_tx, shutdown_rx) = mpsc::channel();
13371 let shutdown_controller = Arc::clone(&controller);
13372 let shutdown = std::thread::spawn(move || {
13373 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
13374 });
13375 wait_until("CurrentThread shutdown to leave Running", || {
13376 let state = controller
13377 .state
13378 .lock()
13379 .unwrap_or_else(std::sync::PoisonError::into_inner);
13380 !matches!(
13381 &state.lifecycle,
13382 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
13383 )
13384 });
13385
13386 let (restart_tx, restart_rx) = mpsc::channel();
13387 let restart_controller = Arc::clone(&controller);
13388 let restart = std::thread::spawn(move || {
13389 let result = restart_controller
13390 .start()
13391 .map(|()| restart_controller.backend().generation());
13392 restart_tx.send(result).unwrap();
13393 });
13394
13395 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
13396 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
13397 let shutdown_overlapped = early_shutdown.is_some();
13398 let restart_overlapped = early_restart.is_some();
13399 release_dispatch_tx.send(()).unwrap();
13400 driver.join().unwrap();
13401
13402 let shutdown_result = early_shutdown.unwrap_or_else(|| {
13403 shutdown_rx
13404 .recv_timeout(Duration::from_secs(2))
13405 .expect("shutdown must finish after continuation publication retires")
13406 });
13407 shutdown_result.unwrap();
13408 let new_generation = early_restart.unwrap_or_else(|| {
13409 restart_rx
13410 .recv_timeout(Duration::from_secs(2))
13411 .expect("restart must follow continuation publication and shutdown")
13412 });
13413 let new_generation = new_generation.unwrap();
13414 shutdown.join().unwrap();
13415 restart.join().unwrap();
13416
13417 assert!(
13418 !shutdown_overlapped,
13419 "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
13420 );
13421 assert!(
13422 !restart_overlapped,
13423 "restart must not overlap an old-generation continuation dispatch"
13424 );
13425 assert_ne!(new_generation, old_generation);
13426 controller.shutdown().unwrap();
13427 }
13428
13429 #[cfg(not(target_family = "wasm"))]
13430 #[test]
13431 fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
13432 use std::sync::mpsc;
13433
13434 let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
13435 let old_generation = controller.backend().generation();
13436 let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
13437 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
13438 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
13439 let submit_controller = Arc::clone(&controller);
13440 let reentrant_controller = Arc::clone(&controller);
13441 let submitter = std::thread::spawn(move || {
13442 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13443 *slot.borrow_mut() = Some(Box::new(move || {
13444 reentrant_shutdown_tx
13445 .send(reentrant_controller.shutdown())
13446 .unwrap();
13447 dispatch_entered_tx.send(()).unwrap();
13448 release_dispatch_rx.recv().unwrap();
13449 }));
13450 });
13451 submit_controller
13452 .try_spawn_detached(std::future::pending::<()>())
13453 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
13454 });
13455
13456 let reentrant_shutdown = reentrant_shutdown_rx
13457 .recv_timeout(Duration::from_secs(2))
13458 .expect("dispatch publication must not self-deadlock on reentrant shutdown");
13459 dispatch_entered_rx
13460 .recv_timeout(Duration::from_secs(2))
13461 .expect("the initial host dispatch publication must reach the injected pause");
13462
13463 let (shutdown_tx, shutdown_rx) = mpsc::channel();
13464 let shutdown_controller = Arc::clone(&controller);
13465 let shutdown = std::thread::spawn(move || {
13466 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
13467 });
13468 wait_until("CurrentThread shutdown to leave Running", || {
13469 let state = controller
13470 .state
13471 .lock()
13472 .unwrap_or_else(std::sync::PoisonError::into_inner);
13473 !matches!(
13474 &state.lifecycle,
13475 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
13476 )
13477 });
13478
13479 let (restart_tx, restart_rx) = mpsc::channel();
13480 let restart_controller = Arc::clone(&controller);
13481 let restart = std::thread::spawn(move || {
13482 let result = restart_controller
13483 .start()
13484 .map(|()| restart_controller.backend().generation());
13485 restart_tx.send(result).unwrap();
13486 });
13487
13488 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
13489 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
13490 let shutdown_overlapped = early_shutdown.is_some();
13491 let restart_overlapped = early_restart.is_some();
13492 release_dispatch_tx.send(()).unwrap();
13493 submitter.join().unwrap();
13494
13495 let shutdown_result = early_shutdown.unwrap_or_else(|| {
13496 shutdown_rx
13497 .recv_timeout(Duration::from_secs(2))
13498 .expect("shutdown must finish after initial dispatch publication retires")
13499 });
13500 shutdown_result.unwrap();
13501 let new_generation = early_restart.unwrap_or_else(|| {
13502 restart_rx
13503 .recv_timeout(Duration::from_secs(2))
13504 .expect("restart must follow initial dispatch publication and shutdown")
13505 });
13506 let new_generation = new_generation.unwrap();
13507 shutdown.join().unwrap();
13508 restart.join().unwrap();
13509
13510 let reentrant_error = reentrant_shutdown
13511 .expect_err("dispatch publication must be generation-scoped scheduler work");
13512 assert!(
13513 reentrant_error
13514 .to_string()
13515 .contains("work running on that runtime")
13516 );
13517 assert!(
13518 !shutdown_overlapped,
13519 "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
13520 );
13521 assert!(
13522 !restart_overlapped,
13523 "restart must not overlap an old-generation host dispatch call"
13524 );
13525 assert_ne!(new_generation, old_generation);
13526 controller.shutdown().unwrap();
13527 }
13528
13529 #[test]
13530 fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
13531 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13532 .lock()
13533 .unwrap()
13534 .clear();
13535 CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
13536 let metrics = Arc::new(RuntimeMetrics::default());
13537 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13538 Arc::clone(&metrics),
13539 panic_once_current_thread_host_dispatch,
13540 ));
13541 let scheduler = Arc::clone(&executor);
13542 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13543 scheduler.schedule(runnable)
13544 });
13545
13546 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
13547 assert!(
13548 publication.is_err(),
13549 "the injected host dispatch panic must unwind"
13550 );
13551 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13552 {
13553 let scheduler = executor
13554 .scheduler_idle_lock
13555 .lock()
13556 .unwrap_or_else(std::sync::PoisonError::into_inner);
13557 assert_eq!(scheduler.active_dispatch_calls, 0);
13558 assert!(scheduler.dispatch_publications.is_empty());
13559 }
13560
13561 executor.request_drain_if_queued();
13562
13563 assert_ne!(dispatch, 0);
13564 assert_eq!(
13565 *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13566 .lock()
13567 .unwrap(),
13568 vec![dispatch, dispatch],
13569 "a later host registration must retry the same pending capability"
13570 );
13571 {
13572 let scheduler = executor
13573 .scheduler_idle_lock
13574 .lock()
13575 .unwrap_or_else(std::sync::PoisonError::into_inner);
13576 assert_eq!(scheduler.active_dispatch_calls, 0);
13577 assert!(scheduler.dispatch_publications.is_empty());
13578 }
13579 executor.drive_host_turn_for_dispatch(dispatch);
13580 assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
13581 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13582 }
13583
13584 #[cfg(not(target_family = "wasm"))]
13585 #[test]
13586 fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
13587 use std::sync::mpsc;
13588
13589 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13590 .lock()
13591 .unwrap()
13592 .clear();
13593 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
13594 let metrics = Arc::new(RuntimeMetrics::default());
13595 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13596 Arc::clone(&metrics),
13597 panic_once_current_thread_armed_republish_host_dispatch,
13598 ));
13599 let scheduler = Arc::clone(&executor);
13600 let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
13601 scheduler.schedule(runnable)
13602 });
13603
13604 let (called_tx, called_rx) = mpsc::channel();
13605 let (release_tx, release_rx) = mpsc::channel();
13606 let publishing_executor = Arc::clone(&executor);
13607 let publication = std::thread::spawn(move || {
13608 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13609 *slot.borrow_mut() = Some(Box::new(move || {
13610 called_tx.send(()).unwrap();
13611 release_rx.recv().unwrap();
13612 }));
13613 });
13614 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
13615 });
13616 called_rx
13617 .recv_timeout(Duration::from_secs(2))
13618 .expect("the first publication must pause before calling the host dispatcher");
13619
13620 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13621 executor.request_drain_if_queued();
13622 let publication_state = {
13623 let scheduler = executor
13624 .scheduler_idle_lock
13625 .lock()
13626 .unwrap_or_else(std::sync::PoisonError::into_inner);
13627 (
13628 scheduler.active_dispatch_calls,
13629 scheduler.dispatch_publications.len(),
13630 scheduler.dispatch_publications[0].dispatch,
13631 scheduler.dispatch_publications[0].republish_requested,
13632 )
13633 };
13634 release_tx.send(()).unwrap();
13635
13636 let publication = publication.join().unwrap();
13637 assert!(
13638 publication.is_err(),
13639 "the original host-dispatch panic must resume"
13640 );
13641 assert_ne!(dispatch, 0);
13642 assert_eq!(publication_state, (1, 1, dispatch, true));
13643 assert_eq!(
13644 *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13645 .lock()
13646 .unwrap(),
13647 vec![dispatch, dispatch],
13648 "the armed request must retry automatically before the original panic resumes"
13649 );
13650 {
13651 let scheduler = executor
13652 .scheduler_idle_lock
13653 .lock()
13654 .unwrap_or_else(std::sync::PoisonError::into_inner);
13655 assert_eq!(scheduler.active_dispatch_calls, 0);
13656 assert!(scheduler.dispatch_publications.is_empty());
13657 }
13658 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
13659 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13660
13661 executor.drive_host_turn_for_dispatch(dispatch);
13662 assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
13663 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13664 }
13665
13666 #[cfg(not(target_family = "wasm"))]
13667 #[test]
13668 fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
13669 use std::sync::mpsc;
13670
13671 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13672 .lock()
13673 .unwrap()
13674 .clear();
13675 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
13676 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
13677 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
13678 {
13679 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13680 .state
13681 .lock()
13682 .unwrap_or_else(std::sync::PoisonError::into_inner);
13683 assert!(state.entries.is_empty());
13684 assert!(state.dispatches.is_empty());
13685 }
13686 let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13687 .register(Arc::new(FailDuringAuthoritativePanicPublication));
13688 let metrics = Arc::new(RuntimeMetrics::default());
13689 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13690 Arc::clone(&metrics),
13691 dispatch_authoritative_panic_current_thread_tasks,
13692 &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
13693 ));
13694 let scheduler = Arc::clone(&executor);
13695 let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
13696 scheduler.schedule(runnable)
13697 });
13698
13699 let (called_tx, called_rx) = mpsc::channel();
13700 let (release_tx, release_rx) = mpsc::channel();
13701 let publishing_executor = Arc::clone(&executor);
13702 let publication = std::thread::spawn(move || {
13703 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13704 *slot.borrow_mut() = Some(Box::new(move || {
13705 called_tx.send(()).unwrap();
13706 release_rx.recv().unwrap();
13707 }));
13708 });
13709 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
13710 });
13711 called_rx
13712 .recv_timeout(Duration::from_secs(2))
13713 .expect("the first publication must pause before raising its panic");
13714
13715 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13716 executor.request_drain_if_queued();
13717 release_tx.send(()).unwrap();
13718
13719 let payload = publication
13720 .join()
13721 .unwrap()
13722 .expect_err("the first host-dispatch panic must resume");
13723 assert!(
13724 payload.is::<FirstHostileCurrentThreadPanic>(),
13725 "the first panic must remain authoritative over nested recovery"
13726 );
13727 assert_eq!(
13728 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
13729 1,
13730 "the later hostile payload must be discarded under containment"
13731 );
13732 assert_eq!(
13733 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
13734 0,
13735 "the authoritative payload must remain owned by the resumed unwind"
13736 );
13737 discard_panic_payload(payload);
13738 assert_eq!(
13739 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
13740 1
13741 );
13742
13743 let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13744 .lock()
13745 .unwrap()
13746 .clone();
13747 assert_eq!(dispatches.len(), 3);
13748 let replacement_dispatch = dispatches[2];
13749 assert_eq!(dispatches[0], failed_dispatch);
13750 assert_eq!(dispatches[1], failed_dispatch);
13751 assert_ne!(replacement_dispatch, failed_dispatch);
13752 assert_eq!(
13753 executor.dispatch_pending.load(Ordering::Acquire),
13754 replacement_dispatch
13755 );
13756 assert_eq!(
13757 executor.recovery_dispatch.load(Ordering::Acquire),
13758 replacement_dispatch
13759 );
13760 {
13761 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13762 .state
13763 .lock()
13764 .unwrap_or_else(std::sync::PoisonError::into_inner);
13765 assert!(
13766 !state.dispatches.contains_key(&failed_dispatch),
13767 "nested recovery unwind must retire the exact failed epoch"
13768 );
13769 assert!(!state.dispatches.contains_key(&replacement_dispatch));
13770 }
13771
13772 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
13773 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13774 let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13775 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13776 executor.request_drain_if_queued();
13777
13778 assert_eq!(
13779 *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13780 .lock()
13781 .unwrap(),
13782 vec![
13783 failed_dispatch,
13784 failed_dispatch,
13785 replacement_dispatch,
13786 replacement_dispatch
13787 ],
13788 "retry must retain the replacement reserved before the nested panic"
13789 );
13790 let delivery = healthy.deliveries()[0];
13791 assert_eq!(
13792 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
13793 Some(replacement_dispatch)
13794 );
13795 let mut host_turn = executor
13796 .try_admit_host_turn(replacement_dispatch)
13797 .expect("the retried replacement delivery must be admitted");
13798 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13799 host_turn.drive();
13800 assert!(
13801 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13802 .finish_delivery(delivery, false)
13803 .failed_dispatches
13804 .is_empty()
13805 );
13806 assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
13807 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13808
13809 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
13810 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13811 .state
13812 .lock()
13813 .unwrap_or_else(std::sync::PoisonError::into_inner);
13814 assert!(state.entries.is_empty());
13815 assert!(state.dispatches.is_empty());
13816 }
13817
13818 #[cfg(not(target_family = "wasm"))]
13819 #[test]
13820 fn current_thread_shutdown_suppresses_queued_republish() {
13821 use std::sync::mpsc;
13822
13823 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13824 let metrics = Arc::new(RuntimeMetrics::default());
13825 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13826 Arc::clone(&metrics),
13827 count_current_thread_shutdown_republish_host_dispatch,
13828 ));
13829 let scheduler = Arc::clone(&executor);
13830 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13831 scheduler.schedule(runnable)
13832 });
13833
13834 let (returned_tx, returned_rx) = mpsc::channel();
13835 let (release_tx, release_rx) = mpsc::channel();
13836 let publishing_executor = Arc::clone(&executor);
13837 let publication = std::thread::spawn(move || {
13838 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13839 *slot.borrow_mut() = Some(Box::new(move || {
13840 returned_tx.send(()).unwrap();
13841 release_rx.recv().unwrap();
13842 }));
13843 });
13844 publishing_executor.schedule(runnable);
13845 });
13846 returned_rx
13847 .recv_timeout(Duration::from_secs(2))
13848 .expect("the initial publication must reach its completion barrier");
13849
13850 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13851 executor.request_drain_if_queued();
13852 let publication_state = {
13853 let scheduler = executor
13854 .scheduler_idle_lock
13855 .lock()
13856 .unwrap_or_else(std::sync::PoisonError::into_inner);
13857 (
13858 scheduler.active_dispatch_calls,
13859 scheduler.dispatch_publications.len(),
13860 scheduler.dispatch_publications[0].dispatch,
13861 scheduler.dispatch_publications[0].republish_requested,
13862 )
13863 };
13864 executor.begin_shutdown();
13865 release_tx.send(()).unwrap();
13866 publication.join().unwrap();
13867 executor.wait_until_scheduler_idle();
13868
13869 assert_ne!(dispatch, 0);
13870 assert_eq!(publication_state, (1, 1, dispatch, true));
13871 assert_eq!(
13872 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
13873 1
13874 );
13875 {
13876 let scheduler = executor
13877 .scheduler_idle_lock
13878 .lock()
13879 .unwrap_or_else(std::sync::PoisonError::into_inner);
13880 assert_eq!(scheduler.active_dispatch_calls, 0);
13881 assert!(scheduler.dispatch_publications.is_empty());
13882 }
13883 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13884 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13885 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13886 assert_eq!(futures::executor::block_on(task.fallible()), None);
13887 }
13888
13889 #[cfg(not(target_family = "wasm"))]
13890 #[test]
13891 fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
13892 use std::sync::mpsc;
13893
13894 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13895 .lock()
13896 .unwrap()
13897 .clear();
13898 let metrics = Arc::new(RuntimeMetrics::default());
13899 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13900 Arc::clone(&metrics),
13901 record_current_thread_republish_rearm_dispatch,
13902 ));
13903 let scheduler = Arc::clone(&executor);
13904 let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
13905 scheduler.schedule(runnable)
13906 });
13907
13908 let (first_returned_tx, first_returned_rx) = mpsc::channel();
13909 let (release_first_tx, release_first_rx) = mpsc::channel();
13910 let (second_returned_tx, second_returned_rx) = mpsc::channel();
13911 let (release_second_tx, release_second_rx) = mpsc::channel();
13912 let publishing_executor = Arc::clone(&executor);
13913 let publication = std::thread::spawn(move || {
13914 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13915 *slot.borrow_mut() = Some(Box::new(move || {
13916 first_returned_tx.send(()).unwrap();
13917 release_first_rx.recv().unwrap();
13918 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13919 *slot.borrow_mut() = Some(Box::new(move || {
13920 second_returned_tx.send(()).unwrap();
13921 release_second_rx.recv().unwrap();
13922 }));
13923 });
13924 }));
13925 });
13926 publishing_executor.schedule(runnable);
13927 });
13928 first_returned_rx
13929 .recv_timeout(Duration::from_secs(2))
13930 .expect("the first publication must reach its completion barrier");
13931
13932 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13933 executor.request_drain_if_queued();
13934 executor.request_drain_if_queued();
13935 let first_publication_state = {
13936 let scheduler = executor
13937 .scheduler_idle_lock
13938 .lock()
13939 .unwrap_or_else(std::sync::PoisonError::into_inner);
13940 (
13941 scheduler.active_dispatch_calls,
13942 scheduler.dispatch_publications.len(),
13943 scheduler.dispatch_publications[0].dispatch,
13944 scheduler.dispatch_publications[0].republish_requested,
13945 )
13946 };
13947 release_first_tx.send(()).unwrap();
13948 second_returned_rx
13949 .recv_timeout(Duration::from_secs(2))
13950 .expect("the second publication must reach its completion barrier");
13951
13952 executor.request_drain_if_queued();
13953 executor.request_drain_if_queued();
13954 let second_publication_state = {
13955 let scheduler = executor
13956 .scheduler_idle_lock
13957 .lock()
13958 .unwrap_or_else(std::sync::PoisonError::into_inner);
13959 (
13960 scheduler.active_dispatch_calls,
13961 scheduler.dispatch_publications.len(),
13962 scheduler.dispatch_publications[0].dispatch,
13963 scheduler.dispatch_publications[0].republish_requested,
13964 )
13965 };
13966 release_second_tx.send(()).unwrap();
13967 publication.join().unwrap();
13968
13969 assert_ne!(dispatch, 0);
13970 assert_eq!(first_publication_state, (1, 1, dispatch, true));
13971 assert_eq!(second_publication_state, (1, 1, dispatch, true));
13972 assert_eq!(
13973 *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
13974 vec![dispatch, dispatch, dispatch],
13975 "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
13976 );
13977 {
13978 let scheduler = executor
13979 .scheduler_idle_lock
13980 .lock()
13981 .unwrap_or_else(std::sync::PoisonError::into_inner);
13982 assert_eq!(scheduler.active_dispatch_calls, 0);
13983 assert!(scheduler.dispatch_publications.is_empty());
13984 }
13985 executor.drive_host_turn_for_dispatch(dispatch);
13986 assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
13987 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13988 }
13989
13990 #[cfg(not(target_family = "wasm"))]
13991 #[test]
13992 fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
13993 use std::sync::mpsc;
13994
13995 CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13996 .lock()
13997 .unwrap()
13998 .clear();
13999 let metrics = Arc::new(RuntimeMetrics::default());
14000 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14001 Arc::clone(&metrics),
14002 reject_initial_current_thread_publication_once,
14003 ));
14004 let scheduler = Arc::clone(&executor);
14005 let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14006 scheduler.schedule(runnable)
14007 });
14008
14009 let (returned_tx, returned_rx) = mpsc::channel();
14010 let (release_tx, release_rx) = mpsc::channel();
14011 let publishing_executor = Arc::clone(&executor);
14012 let publication = std::thread::spawn(move || {
14013 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14014 *slot.borrow_mut() = Some(Box::new(move || {
14015 returned_tx.send(()).unwrap();
14016 release_rx.recv().unwrap();
14017 }));
14018 });
14019 publishing_executor.schedule(runnable);
14020 });
14021 returned_rx
14022 .recv_timeout(Duration::from_secs(2))
14023 .expect("the unavailable initial publication must reach its completion barrier");
14024
14025 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14026 executor.request_drain_if_queued();
14027 let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14028 .lock()
14029 .unwrap()
14030 .len();
14031
14032 release_tx.send(()).unwrap();
14033 publication.join().unwrap();
14034
14035 let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14036 .lock()
14037 .unwrap()
14038 .clone();
14039 assert_ne!(dispatch, 0);
14040 assert_eq!(
14041 attempts_while_unavailable_is_paused, 1,
14042 "a racing host request must join the in-flight publication instead of retiring it independently"
14043 );
14044 assert_eq!(
14045 dispatches,
14046 vec![dispatch, dispatch],
14047 "the publication owner must retry the same internal capability after the new host joins"
14048 );
14049 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14050 executor.drive_host_turn_for_dispatch(dispatch);
14051 assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
14052 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14053 }
14054
14055 #[cfg(not(target_family = "wasm"))]
14056 #[test]
14057 fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
14058 use std::sync::mpsc;
14059
14060 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14061 .lock()
14062 .unwrap()
14063 .clear();
14064 let metrics = Arc::new(RuntimeMetrics::default());
14065 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14066 Arc::clone(&metrics),
14067 reject_current_thread_replacement_publication_once,
14068 ));
14069 let scheduler = Arc::clone(&executor);
14070 let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
14071 scheduler.schedule(runnable)
14072 });
14073 executor.schedule(runnable);
14074 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14075 assert_ne!(failed_dispatch, 0);
14076
14077 let (returned_tx, returned_rx) = mpsc::channel();
14078 let (release_tx, release_rx) = mpsc::channel();
14079 let cancelling_executor = Arc::clone(&executor);
14080 let cancellation = std::thread::spawn(move || {
14081 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14082 *slot.borrow_mut() = Some(Box::new(move || {
14083 returned_tx.send(()).unwrap();
14084 release_rx.recv().unwrap();
14085 }));
14086 });
14087 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14088 });
14089 returned_rx
14090 .recv_timeout(Duration::from_secs(2))
14091 .expect("the unavailable replacement publication must reach its completion barrier");
14092
14093 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14094 executor.request_drain_if_queued();
14095 let attempts_while_unavailable_is_paused =
14096 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14097 .lock()
14098 .unwrap()
14099 .len();
14100
14101 release_tx.send(()).unwrap();
14102 cancellation.join().unwrap();
14103
14104 let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14105 .lock()
14106 .unwrap()
14107 .clone();
14108 assert_ne!(replacement_dispatch, 0);
14109 assert_ne!(replacement_dispatch, failed_dispatch);
14110 assert_eq!(
14111 attempts_while_unavailable_is_paused, 2,
14112 "a racing host request must join the in-flight replacement publication"
14113 );
14114 assert_eq!(
14115 dispatches,
14116 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14117 "republication must reuse the sole replacement capability instead of allocating another"
14118 );
14119 assert_eq!(
14120 executor.dispatch_pending.load(Ordering::Acquire),
14121 replacement_dispatch
14122 );
14123 assert_eq!(
14124 executor.recovery_dispatch.load(Ordering::Acquire),
14125 replacement_dispatch
14126 );
14127 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14128
14129 executor.drive_host_turn_for_dispatch(replacement_dispatch);
14130 assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
14131 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14132 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14133 }
14134
14135 #[cfg(not(target_family = "wasm"))]
14136 #[test]
14137 fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
14138 use std::sync::mpsc;
14139
14140 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14141 .lock()
14142 .unwrap()
14143 .clear();
14144 {
14145 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14146 .state
14147 .lock()
14148 .unwrap_or_else(std::sync::PoisonError::into_inner);
14149 assert!(state.entries.is_empty());
14150 assert!(state.dispatches.is_empty());
14151 }
14152 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14153 let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14154 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14155 let metrics = Arc::new(RuntimeMetrics::default());
14156 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14157 Arc::clone(&metrics),
14158 fail_current_thread_replacement_publication_once,
14159 &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
14160 ));
14161 let scheduler = Arc::clone(&executor);
14162 let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
14163 scheduler.schedule(runnable)
14164 });
14165 executor.schedule(runnable);
14166 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14167 assert_ne!(failed_dispatch, 0);
14168 let initial_delivery = initial.deliveries()[0];
14169 let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14170 .finish_delivery(initial_delivery, true);
14171 let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
14172 panic!("the initial accepted delivery must report one exact failure");
14173 };
14174 assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
14175 let failed_dispatch_failure = *failed_dispatch_failure;
14176 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
14177 let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14178 .register(Arc::new(FailDuringReplacementRepublishPublication));
14179
14180 let (returned_tx, returned_rx) = mpsc::channel();
14181 let (release_tx, release_rx) = mpsc::channel();
14182 let cancelling_executor = Arc::clone(&executor);
14183 let cancellation = std::thread::spawn(move || {
14184 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14185 *slot.borrow_mut() = Some(Box::new(move || {
14186 returned_tx.send(()).unwrap();
14187 release_rx.recv().unwrap();
14188 }));
14189 });
14190 cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
14191 });
14192 returned_rx
14193 .recv_timeout(Duration::from_secs(2))
14194 .expect("the failed replacement publication must reach its completion barrier");
14195
14196 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14197 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14198 let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14199 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14200 executor.request_drain_if_queued();
14201 let publication_state = {
14202 let scheduler = executor
14203 .scheduler_idle_lock
14204 .lock()
14205 .unwrap_or_else(std::sync::PoisonError::into_inner);
14206 (
14207 scheduler.active_dispatch_calls,
14208 scheduler.dispatch_publications.len(),
14209 scheduler.dispatch_publications[0].dispatch,
14210 scheduler.dispatch_publications[0].republish_requested,
14211 )
14212 };
14213 release_tx.send(()).unwrap();
14214 cancellation.join().unwrap();
14215
14216 assert_ne!(replacement_dispatch, 0);
14217 assert_ne!(replacement_dispatch, failed_dispatch);
14218 assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
14219 assert_eq!(
14220 *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14221 .lock()
14222 .unwrap(),
14223 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14224 "the newly armed host request must receive the exact replacement before cancellation"
14225 );
14226 assert_eq!(
14227 executor.dispatch_pending.load(Ordering::Acquire),
14228 replacement_dispatch
14229 );
14230 assert_eq!(
14231 executor.recovery_dispatch.load(Ordering::Acquire),
14232 replacement_dispatch
14233 );
14234 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14235
14236 let delivery = healthy.deliveries()[0];
14237 assert_eq!(delivery.dispatch, replacement_dispatch);
14238 assert_eq!(
14239 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14240 .claim_delivery(delivery.capability()),
14241 Some(replacement_dispatch)
14242 );
14243 let mut host_turn = executor
14244 .try_admit_host_turn(replacement_dispatch)
14245 .expect("the rearmed replacement delivery must be admitted");
14246 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14247 host_turn.drive();
14248 assert!(
14249 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14250 .finish_delivery(delivery, false)
14251 .failed_dispatches
14252 .is_empty()
14253 );
14254 assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
14255 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14256 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14257
14258 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
14259 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
14260 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14261 .state
14262 .lock()
14263 .unwrap_or_else(std::sync::PoisonError::into_inner);
14264 assert!(state.entries.is_empty());
14265 assert!(state.dispatches.is_empty());
14266 }
14267
14268 #[cfg(not(target_family = "wasm"))]
14269 #[test]
14270 fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
14271 use std::sync::mpsc;
14272
14273 {
14274 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14275 .state
14276 .lock()
14277 .unwrap_or_else(std::sync::PoisonError::into_inner);
14278 assert!(state.entries.is_empty());
14279 assert!(state.dispatches.is_empty());
14280 }
14281 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14282 let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14283 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14284 let metrics = Arc::new(RuntimeMetrics::default());
14285 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14286 Arc::clone(&metrics),
14287 dispatch_stale_failure_current_thread_tasks,
14288 &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
14289 ));
14290 let scheduler = Arc::clone(&executor);
14291 let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
14292 scheduler.schedule(runnable)
14293 });
14294 executor.schedule(runnable);
14295
14296 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14297 let initial_delivery = initial.deliveries()[0];
14298 let delayed_completion =
14299 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
14300 let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
14301 panic!("the initial accepted delivery must report one exact failure");
14302 };
14303 assert_eq!(
14304 *delayed_failure,
14305 current_thread_dispatch_failure(dispatch, 0)
14306 );
14307 let delayed_failure = *delayed_failure;
14308
14309 let (returned_tx, returned_rx) = mpsc::channel();
14310 let (release_tx, release_rx) = mpsc::channel();
14311 let publishing_executor = Arc::clone(&executor);
14312 let publication = std::thread::spawn(move || {
14313 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14314 *slot.borrow_mut() = Some(Box::new(move || {
14315 returned_tx.send(()).unwrap();
14316 release_rx.recv().unwrap();
14317 }));
14318 });
14319 publishing_executor.request_drain_if_queued();
14320 });
14321 returned_rx
14322 .recv_timeout(Duration::from_secs(2))
14323 .expect("the old failed epoch must pause before executor recovery");
14324
14325 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14326 let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14327 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14328 executor.request_drain_if_queued();
14329 release_tx.send(()).unwrap();
14330 publication.join().unwrap();
14331
14332 let healthy_delivery = healthy.deliveries()[0];
14333 let delayed_initial_delivery = initial.deliveries()[1];
14334 {
14335 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14336 .state
14337 .lock()
14338 .unwrap_or_else(std::sync::PoisonError::into_inner);
14339 let dispatch_state = state
14340 .dispatches
14341 .get(&dispatch)
14342 .expect("the rearmed dispatch must remain registered");
14343 assert_eq!(dispatch_state.epoch, 1);
14344 assert!(!dispatch_state.is_cancelling());
14345 }
14346
14347 executor.cancel_host_dispatch(delayed_failure);
14348
14349 assert_eq!(
14350 executor.dispatch_pending.load(Ordering::Acquire),
14351 dispatch,
14352 "the delayed old-epoch failure must not consume the rearmed executor capability"
14353 );
14354 assert_eq!(
14355 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14356 .state
14357 .lock()
14358 .unwrap_or_else(std::sync::PoisonError::into_inner)
14359 .dispatches
14360 .get(&dispatch)
14361 .map(|state| (state.epoch, state.phase)),
14362 Some((1, CurrentThreadDispatchPhase::Pending)),
14363 "the delayed old-epoch cancellation must not remove the accepted rearm"
14364 );
14365
14366 assert_eq!(
14367 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
14368 Some(dispatch)
14369 );
14370 let mut host_turn = executor
14371 .try_admit_host_turn(dispatch)
14372 .expect("the current-epoch delivery must be admitted");
14373 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
14374 host_turn.drive();
14375 assert!(
14376 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14377 .finish_delivery(healthy_delivery, false)
14378 .failed_dispatches
14379 .is_empty()
14380 );
14381 assert!(
14382 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14383 .finish_delivery(delayed_initial_delivery, false)
14384 .failed_dispatches
14385 .is_empty()
14386 );
14387 assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
14388 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14389
14390 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
14391 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
14392 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14393 .state
14394 .lock()
14395 .unwrap_or_else(std::sync::PoisonError::into_inner);
14396 assert!(state.entries.is_empty());
14397 assert!(state.dispatches.is_empty());
14398 }
14399
14400 #[cfg(not(target_family = "wasm"))]
14401 #[test]
14402 fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
14403 use std::sync::mpsc;
14404
14405 {
14406 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14407 .state
14408 .lock()
14409 .unwrap_or_else(std::sync::PoisonError::into_inner);
14410 assert!(state.entries.is_empty());
14411 assert!(state.dispatches.is_empty());
14412 }
14413 let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14414 .register(Arc::new(FailDuringLateRegistrationPublication));
14415
14416 let metrics = Arc::new(RuntimeMetrics::default());
14417 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14418 Arc::clone(&metrics),
14419 dispatch_late_registration_current_thread_tasks,
14420 &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
14421 ));
14422 let scheduler = Arc::clone(&executor);
14423 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14424 scheduler.schedule(runnable)
14425 });
14426 metrics.runnable_scheduled();
14427 executor
14428 .queue
14429 .lock()
14430 .unwrap_or_else(std::sync::PoisonError::into_inner)
14431 .runnables
14432 .push_back(runnable);
14433 let replacement_dispatch = next_current_thread_dispatch_id();
14434 executor
14435 .recovery_dispatch
14436 .store(replacement_dispatch, Ordering::Release);
14437 executor
14438 .dispatch_pending
14439 .store(replacement_dispatch, Ordering::Release);
14440
14441 let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
14442 let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
14443 let publishing_executor = Arc::clone(&executor);
14444 let publication = std::thread::spawn(move || {
14445 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
14446 *slot.borrow_mut() = Some(Box::new(move || {
14447 owner_retired_tx.send(()).unwrap();
14448 request_blocked_rx
14449 .recv_timeout(Duration::from_secs(2))
14450 .expect("the binding-like drain request must wait for the failure transition");
14451 }));
14452 });
14453 publishing_executor.request_drain_if_queued();
14454 });
14455 owner_retired_rx
14456 .recv_timeout(Duration::from_secs(2))
14457 .expect("the failed replacement publication must retire its owner");
14458
14459 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14462 let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14463 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14464 let requesting_executor = Arc::clone(&executor);
14465 let request = std::thread::spawn(move || {
14466 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14467 *slot.borrow_mut() = Some(Box::new(move || {
14468 request_blocked_tx.send(()).unwrap();
14469 }));
14470 });
14471 requesting_executor.request_drain_if_queued();
14472 });
14473
14474 publication.join().unwrap();
14475 request.join().unwrap();
14476
14477 assert!(
14478 healthy.deliveries().is_empty(),
14479 "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
14480 );
14481 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14482 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14483 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14484 assert_eq!(futures::executor::block_on(task.fallible()), None);
14485 {
14486 let scheduler = executor
14487 .scheduler_idle_lock
14488 .lock()
14489 .unwrap_or_else(std::sync::PoisonError::into_inner);
14490 assert_eq!(scheduler.active_dispatch_calls, 0);
14491 assert!(scheduler.dispatch_publications.is_empty());
14492 }
14493
14494 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
14495
14496 let scheduler = Arc::clone(&executor);
14497 let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
14498 scheduler.schedule(runnable)
14499 });
14500 executor.schedule(later_runnable);
14501 let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14502 let later_delivery = healthy.deliveries()[0];
14503 assert_eq!(
14504 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
14505 Some(later_dispatch)
14506 );
14507 let mut host_turn = executor
14508 .try_admit_host_turn(later_dispatch)
14509 .expect("the later exact delivery must be admitted");
14510 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
14511 host_turn.drive();
14512 assert!(
14513 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14514 .finish_delivery(later_delivery, false)
14515 .failed_dispatches
14516 .is_empty()
14517 );
14518 assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
14519 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14520
14521 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
14522 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14523 .state
14524 .lock()
14525 .unwrap_or_else(std::sync::PoisonError::into_inner);
14526 assert!(state.entries.is_empty());
14527 assert!(state.dispatches.is_empty());
14528 }
14529
14530 #[cfg(not(target_family = "wasm"))]
14531 #[test]
14532 fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
14533 {
14534 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
14535 .lock()
14536 .unwrap()
14537 .clear();
14538 {
14539 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14540 .state
14541 .lock()
14542 .unwrap_or_else(std::sync::PoisonError::into_inner);
14543 assert!(state.entries.is_empty());
14544 assert!(state.dispatches.is_empty());
14545 }
14546 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14547 .next_delivery
14548 .store(0, Ordering::Relaxed);
14549 let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14550 .register(Arc::new(FailDuringNestedUnwindPublication));
14551
14552 let metrics = Arc::new(RuntimeMetrics::default());
14553 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14554 Arc::clone(&metrics),
14555 dispatch_nested_unwind_current_thread_tasks,
14556 &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
14557 ));
14558 let scheduler = Arc::clone(&executor);
14559 let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
14560 scheduler.schedule(runnable)
14561 });
14562
14563 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
14564 *slot.borrow_mut() = Some(Box::new(|| {
14565 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14566 .next_delivery
14567 .store(u64::MAX, Ordering::Relaxed);
14568 }));
14569 });
14570 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14571 assert!(
14572 publication.is_err(),
14573 "replacement delivery-id exhaustion must unwind through the outer failed publication"
14574 );
14575
14576 let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
14577 .lock()
14578 .unwrap()
14579 .clone();
14580 assert_eq!(dispatches.len(), 2);
14581 let failed_dispatch = dispatches[0];
14582 let replacement_dispatch = dispatches[1];
14583 assert_ne!(failed_dispatch, replacement_dispatch);
14584 assert_eq!(
14585 executor.dispatch_pending.load(Ordering::Acquire),
14586 replacement_dispatch
14587 );
14588 assert_eq!(
14589 executor.recovery_dispatch.load(Ordering::Acquire),
14590 replacement_dispatch
14591 );
14592 {
14593 let scheduler = executor
14594 .scheduler_idle_lock
14595 .lock()
14596 .unwrap_or_else(std::sync::PoisonError::into_inner);
14597 assert_eq!(scheduler.active_dispatch_calls, 0);
14598 assert!(scheduler.dispatch_publications.is_empty());
14599 }
14600 {
14601 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14602 .state
14603 .lock()
14604 .unwrap_or_else(std::sync::PoisonError::into_inner);
14605 assert!(
14606 !state.dispatches.contains_key(&failed_dispatch),
14607 "outer unwind cleanup must retire the exact old cancelling dispatch"
14608 );
14609 assert!(
14610 !state.dispatches.contains_key(&replacement_dispatch),
14611 "the replacement publication guard must release its aborted registry state"
14612 );
14613 }
14614
14615 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14616 .next_delivery
14617 .store(1, Ordering::Relaxed);
14618 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
14619 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14620 let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14621 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14622
14623 executor.request_drain_if_queued();
14624
14625 assert_eq!(
14626 *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
14627 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14628 "retry must reuse the replacement reserved before the nested unwind"
14629 );
14630 let delivery = healthy.deliveries()[0];
14631 assert_eq!(delivery.dispatch, replacement_dispatch);
14632 assert_eq!(
14633 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
14634 Some(replacement_dispatch)
14635 );
14636 let mut host_turn = executor
14637 .try_admit_host_turn(replacement_dispatch)
14638 .expect("the retried replacement delivery must be admitted");
14639 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14640 host_turn.drive();
14641 assert!(
14642 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14643 .finish_delivery(delivery, false)
14644 .failed_dispatches
14645 .is_empty()
14646 );
14647 assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
14648 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14649
14650 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
14651 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14652 .state
14653 .lock()
14654 .unwrap_or_else(std::sync::PoisonError::into_inner);
14655 assert!(state.entries.is_empty());
14656 assert!(state.dispatches.is_empty());
14657 }
14658
14659 #[test]
14660 fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
14661 reset_current_thread_recovery_host_dispatches();
14662 let metrics = Arc::new(RuntimeMetrics::default());
14663 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14664 Arc::clone(&metrics),
14665 count_current_thread_recovery_host_dispatch,
14666 ));
14667
14668 let scheduler = Arc::clone(&executor);
14669 let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
14670 scheduler.schedule(runnable)
14671 });
14672 executor.schedule(runnable);
14673 assert_eq!(current_thread_recovery_host_dispatches(), 1);
14674 let failed_dispatch = current_thread_dispatch(&executor);
14675
14676 executor.cancel_host_dispatch_for_test(failed_dispatch);
14679 assert_eq!(
14680 current_thread_recovery_host_dispatches(),
14681 2,
14682 "cancellation must request one replacement without another submission"
14683 );
14684 let replacement_dispatch = current_thread_dispatch(&executor);
14685 assert_ne!(replacement_dispatch, failed_dispatch);
14686 assert_eq!(
14687 executor.recovery_dispatch.load(Ordering::Acquire),
14688 replacement_dispatch
14689 );
14690
14691 executor.drive_host_turn_for_dispatch(replacement_dispatch);
14692 assert_eq!(futures::executor::block_on(task), 1);
14693 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14694 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14695 }
14696
14697 #[test]
14698 fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
14699 reset_current_thread_recovery_host_dispatches();
14700 let metrics = Arc::new(RuntimeMetrics::default());
14701 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14702 Arc::clone(&metrics),
14703 count_current_thread_recovery_host_dispatch,
14704 ));
14705 let backend =
14706 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14707
14708 let registration = backend
14709 .work
14710 .try_register_async()
14711 .expect("the backend must accept work");
14712 let task = spawn_registered(
14713 &backend,
14714 Arc::clone(&metrics),
14715 std::future::pending::<()>(),
14716 registration,
14717 );
14718 let failed_dispatch = current_thread_dispatch(&executor);
14719 executor.cancel_host_dispatch_for_test(failed_dispatch);
14720 let replacement_dispatch = current_thread_dispatch(&executor);
14721
14722 executor.cancel_host_dispatch_for_test(replacement_dispatch);
14723
14724 assert_eq!(
14725 current_thread_recovery_host_dispatches(),
14726 2,
14727 "a second scheduler failure must not create an unbounded retry loop"
14728 );
14729 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14730 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14731 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14732 assert_eq!(
14733 futures::executor::block_on(task).unwrap_err().to_string(),
14734 "async runtime stopped before the task completed",
14735 "terminal host failure must settle the original operation as cancelled"
14736 );
14737 assert_eq!(backend.work.state.lock().unwrap().active, 0);
14738
14739 let later_registration = backend
14740 .work
14741 .try_register_async()
14742 .expect("terminal cancellation must reopen acceptance");
14743 let later_task = spawn_registered(
14744 &backend,
14745 Arc::clone(&metrics),
14746 async { 2usize },
14747 later_registration,
14748 );
14749 assert_eq!(
14750 current_thread_recovery_host_dispatches(),
14751 3,
14752 "terminal cancellation must not permanently close the executor"
14753 );
14754 executor.drive_host_turn();
14755 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
14756 }
14757
14758 #[cfg(not(target_family = "wasm"))]
14767 #[test]
14768 fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
14769 use std::{sync::mpsc, time::Duration};
14770
14771 let (done_tx, done_rx) = mpsc::channel();
14772 std::thread::spawn(move || {
14773 let metrics = Arc::new(RuntimeMetrics::default());
14774 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14775 Arc::clone(&metrics),
14776 accept_current_thread_host_dispatch,
14777 ));
14778
14779 let scheduler_a = Arc::clone(&executor);
14781 let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14782 scheduler_a.schedule(runnable)
14783 });
14784 let shared = async move { task_a.fallible().await }.boxed().shared();
14785 let shared_for_b = shared.clone();
14786
14787 let scheduler_b = Arc::clone(&executor);
14789 let (runnable_b, task_b) = async_task::spawn(
14790 async move {
14791 shared_for_b.await;
14792 },
14793 move |runnable| scheduler_b.schedule(runnable),
14794 );
14795 executor.schedule(runnable_b);
14796 executor.drive_host_turn();
14797 assert_eq!(
14798 metrics.queued_runnables.load(Ordering::Relaxed),
14799 0,
14800 "B must be parked and unscheduled after its first poll"
14801 );
14802
14803 executor.schedule(runnable_a);
14806 let failed_dispatch = current_thread_dispatch(&executor);
14807 executor.cancel_host_dispatch_for_test(failed_dispatch);
14808 let replacement_dispatch = current_thread_dispatch(&executor);
14809 executor.cancel_host_dispatch_for_test(replacement_dispatch);
14810
14811 assert!(
14812 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
14813 "the cancellation sweep must reopen the executor"
14814 );
14815 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14816 assert!(
14817 futures::executor::block_on(task_b.fallible()).is_none(),
14818 "B's rejected wake must still cancel B"
14819 );
14820 drop(shared);
14821 done_tx.send(()).unwrap();
14822 });
14823 done_rx
14824 .recv_timeout(Duration::from_secs(5))
14825 .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
14826 }
14827
14828 #[cfg(not(target_family = "wasm"))]
14838 #[test]
14839 fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
14840 use std::{
14841 sync::{Barrier, mpsc},
14842 time::Duration,
14843 };
14844
14845 let (done_tx, done_rx) = mpsc::channel();
14846 std::thread::spawn(move || {
14847 let metrics = Arc::new(RuntimeMetrics::default());
14848 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14849 Arc::clone(&metrics),
14850 accept_current_thread_host_dispatch,
14851 ));
14852 let backend =
14853 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14854 let generation = backend.generation();
14855
14856 let (abort_a, guard_a) = backend
14858 .work
14859 .try_register_async()
14860 .expect("the backend must accept work");
14861 let scheduler_a = Arc::clone(&executor);
14862 let (runnable_a, task_a) = async_task::spawn(
14863 RegisteredTaskFuture::new(
14864 Abortable::new(std::future::pending::<()>(), abort_a),
14865 guard_a,
14866 generation,
14867 ),
14868 move |runnable| scheduler_a.schedule(runnable),
14869 );
14870 let shared = async move { task_a.fallible().await.is_some() }
14871 .boxed()
14872 .shared();
14873 let shared_for_b = shared.clone();
14874
14875 let (abort_b, guard_b) = backend
14879 .work
14880 .try_register_async()
14881 .expect("the backend must accept work");
14882 let wake_in_flight = Arc::new(Barrier::new(2));
14883 let wake_release = Arc::new(Barrier::new(2));
14884 let scheduler_b = Arc::clone(&executor);
14885 let (runnable_b, task_b) = async_task::spawn(
14886 RegisteredTaskFuture::new(
14887 Abortable::new(
14888 async move {
14889 shared_for_b.await;
14890 },
14891 abort_b,
14892 ),
14893 guard_b,
14894 generation,
14895 ),
14896 {
14897 let wake_in_flight = Arc::clone(&wake_in_flight);
14898 let wake_release = Arc::clone(&wake_release);
14899 move |runnable| {
14900 wake_in_flight.wait();
14901 wake_release.wait();
14902 scheduler_b.schedule(runnable);
14903 }
14904 },
14905 );
14906 executor.schedule(runnable_b);
14907 executor.drive_host_turn();
14908 assert_eq!(
14909 metrics.queued_runnables.load(Ordering::Relaxed),
14910 0,
14911 "B must be parked and unscheduled after its first poll"
14912 );
14913
14914 let waker_thread = std::thread::spawn(move || drop(runnable_a));
14918 wake_in_flight.wait();
14919
14920 backend.begin_shutdown();
14923 wake_release.wait();
14924 backend.wait_until_idle();
14925
14926 waker_thread
14927 .join()
14928 .expect("the waker thread must not panic");
14929 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14930 assert!(
14931 futures::executor::block_on(task_b.fallible()).is_none(),
14932 "B's rejected wake must still cancel B"
14933 );
14934 assert_eq!(backend.work.state.lock().unwrap().active, 0);
14935 drop(shared);
14936 done_tx.send(()).unwrap();
14937 });
14938 done_rx
14939 .recv_timeout(Duration::from_secs(5))
14940 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
14941 }
14942
14943 #[cfg(not(target_family = "wasm"))]
14953 #[test]
14954 fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
14955 use std::{sync::mpsc, time::Duration};
14956
14957 let (done_tx, done_rx) = mpsc::channel();
14958 std::thread::spawn(move || {
14959 {
14960 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
14961 .state
14962 .lock()
14963 .unwrap_or_else(std::sync::PoisonError::into_inner);
14964 assert!(state.entries.is_empty());
14965 assert!(state.dispatches.is_empty());
14966 }
14967 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
14968 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
14969 ));
14970 let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
14971 .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
14972 let metrics = Arc::new(RuntimeMetrics::default());
14973 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14974 Arc::clone(&metrics),
14975 dispatch_waker_stack_failure_current_thread_tasks,
14976 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
14977 ));
14978
14979 let (tx, rx) = futures::channel::oneshot::channel::<()>();
14981 let shared = rx.map(|_| ()).boxed().shared();
14982 let shared_for_b = shared.clone();
14983 let scheduler_b = Arc::clone(&executor);
14984 let (runnable_b, task_b) = async_task::spawn(
14985 async move {
14986 shared_for_b.await;
14987 },
14988 move |runnable| scheduler_b.schedule(runnable),
14989 );
14990 executor.schedule(runnable_b);
14991
14992 let delivery = driver.deliveries()[0];
14995 let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
14996 .claim_delivery(delivery.capability())
14997 .expect("the accepted delivery must be claimable");
14998 let mut host_turn = executor
14999 .try_admit_host_turn(dispatch)
15000 .expect("the claimed delivery must be admitted");
15001 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15002 host_turn.drive();
15003 drop(host_turn);
15004 let completion =
15005 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15006 assert!(completion.failed_dispatches.is_empty());
15007 assert!(!completion.redispatch);
15008 assert_eq!(
15009 metrics.queued_runnables.load(Ordering::Relaxed),
15010 0,
15011 "B must be parked and unscheduled after its first poll"
15012 );
15013
15014 driver.fail.store(true, Ordering::SeqCst);
15016 tx.send(()).expect("B must still hold its Shared clone");
15020 assert_eq!(
15021 driver.deliveries().len(),
15022 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
15026 "the wake must publish the dispatch, its one replacement, and the \
15027 retirement turns the terminal transition owes itself"
15028 );
15029 assert!(
15030 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15031 "the terminal failure must reopen the executor before the waker returns"
15032 );
15033 assert!(
15034 executor.has_rejected_work(),
15035 "B must be moved aside, not dropped on the waker's stack"
15036 );
15037 assert_eq!(
15038 metrics.queued_runnables.load(Ordering::Relaxed),
15039 0,
15040 "the moved runnable carries its queued debit"
15041 );
15042 let mut task_b = task_b.fallible();
15043 assert!(
15044 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15045 "B must still be pending: nothing may be dropped on the waker's stack"
15046 );
15047
15048 executor.drive_host_turn();
15051 assert!(!executor.has_rejected_work());
15052 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15053 assert!(
15054 poll_ready_within(&mut task_b, Duration::from_secs(5))
15055 .expect("the host turn must cancel B")
15056 .is_none(),
15057 "the terminal failure must still cancel B"
15058 );
15059 drop(shared);
15060
15061 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
15062 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15063 .state
15064 .lock()
15065 .unwrap_or_else(std::sync::PoisonError::into_inner);
15066 assert!(state.entries.is_empty());
15067 assert!(state.dispatches.is_empty());
15068 done_tx.send(()).unwrap();
15069 });
15070 done_rx
15071 .recv_timeout(Duration::from_secs(5))
15072 .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
15073 }
15074
15075 #[cfg(not(target_family = "wasm"))]
15079 fn poll_ready_within<F: Future + Unpin>(
15080 future: &mut F,
15081 timeout: std::time::Duration,
15082 ) -> Option<F::Output> {
15083 let deadline = std::time::Instant::now() + timeout;
15084 let mut cx = Context::from_waker(futures::task::noop_waker_ref());
15085 loop {
15086 if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
15087 return Some(output);
15088 }
15089 if std::time::Instant::now() >= deadline {
15090 return None;
15091 }
15092 std::thread::sleep(std::time::Duration::from_millis(1));
15093 }
15094 }
15095
15096 #[cfg(not(target_family = "wasm"))]
15104 #[test]
15105 fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
15106 use std::{sync::mpsc, time::Duration};
15107
15108 const WAIT: Duration = Duration::from_secs(5);
15109 let (done_tx, done_rx) = mpsc::channel();
15110 std::thread::spawn(move || {
15111 let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
15112 {
15113 let state = registry
15114 .state
15115 .lock()
15116 .unwrap_or_else(std::sync::PoisonError::into_inner);
15117 assert!(state.entries.is_empty());
15118 assert!(state.dispatches.is_empty());
15119 }
15120 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
15121 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15122 let metrics = Arc::new(RuntimeMetrics::default());
15123 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15124 Arc::clone(&metrics),
15125 dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
15126 registry,
15127 ));
15128
15129 let service = |index: usize| {
15131 let delivery = driver.deliveries()[index];
15132 let dispatch = registry
15133 .claim_delivery(delivery.capability())
15134 .expect("the accepted delivery must be claimable");
15135 let mut host_turn = executor
15136 .try_admit_host_turn(dispatch)
15137 .expect("the claimed delivery must be admitted");
15138 registry.mark_serviced(dispatch);
15139 host_turn.drive();
15140 drop(host_turn);
15141 let completion = registry.finish_delivery(delivery, false);
15142 assert!(completion.failed_dispatches.is_empty());
15143 assert!(!completion.redispatch);
15144 };
15145
15146 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15148 let shared = rx.map(|_| ()).boxed().shared();
15149 let shared_for_b = shared.clone();
15150 let scheduler_b = Arc::clone(&executor);
15151 let (runnable_b, task_b) = async_task::spawn(
15152 async move {
15153 shared_for_b.await;
15154 },
15155 move |runnable| scheduler_b.schedule(runnable),
15156 );
15157 executor.schedule(runnable_b);
15158 service(0);
15159 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15160
15161 driver.fail.store(true, Ordering::SeqCst);
15164 tx.send(()).expect("B must still hold its Shared clone");
15165 const AFTER_WAKE: usize =
15169 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
15170 assert_eq!(driver.deliveries().len(), AFTER_WAKE);
15171 let mut task_b = task_b.fallible();
15172 assert!(
15173 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15174 "B must still be pending: nothing may be dropped on the waker's stack"
15175 );
15176
15177 driver.fail.store(false, Ordering::SeqCst);
15180 let scheduler_later = Arc::clone(&executor);
15181 let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
15182 scheduler_later.schedule(runnable)
15183 });
15184 executor.schedule(runnable_later);
15185 assert_eq!(
15186 driver.deliveries().len(),
15187 AFTER_WAKE + 1,
15188 "a submission after the terminal failure must publish a fresh delivery"
15189 );
15190 assert!(
15191 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15192 "the executor must be open for the healthy host"
15193 );
15194
15195 service(AFTER_WAKE);
15198 let mut task_later = task_later.fallible();
15199 assert_eq!(
15200 poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
15201 Some(42),
15202 "the later task must complete on the delivered host turn"
15203 );
15204 assert!(
15205 poll_ready_within(&mut task_b, WAIT)
15206 .expect("B must have been cancelled by the host turn")
15207 .is_none(),
15208 "the terminal failure must still cancel the work queued when it happened"
15209 );
15210 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15211 drop(shared);
15212
15213 registry.unregister(registration);
15214 let state = registry
15215 .state
15216 .lock()
15217 .unwrap_or_else(std::sync::PoisonError::into_inner);
15218 assert!(state.entries.is_empty());
15219 assert!(state.dispatches.is_empty());
15220 done_tx.send(()).unwrap();
15221 });
15222 done_rx
15223 .recv_timeout(WAIT)
15224 .expect("the healthy host never got a delivery for the later submission");
15225 }
15226
15227 #[cfg(not(target_family = "wasm"))]
15247 #[test]
15248 fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
15249 use std::{sync::mpsc, time::Duration};
15250
15251 const WAIT: Duration = Duration::from_secs(5);
15252 let (done_tx, done_rx) = mpsc::channel();
15253 std::thread::spawn(move || {
15254 let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
15255 {
15256 let state = registry
15257 .state
15258 .lock()
15259 .unwrap_or_else(std::sync::PoisonError::into_inner);
15260 assert!(state.entries.is_empty());
15261 assert!(state.dispatches.is_empty());
15262 }
15263 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
15264 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15265 let metrics = Arc::new(RuntimeMetrics::default());
15266 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15267 Arc::clone(&metrics),
15268 dispatch_retirement_turn_current_thread_tasks,
15269 registry,
15270 ));
15271
15272 let service = |index: usize| {
15274 let delivery = driver.deliveries()[index];
15275 let dispatch = registry
15276 .claim_delivery(delivery.capability())
15277 .expect("the accepted delivery must be claimable");
15278 let mut host_turn = executor
15279 .try_admit_host_turn(dispatch)
15280 .expect("the claimed delivery must be admitted");
15281 registry.mark_serviced(dispatch);
15282 host_turn.drive();
15283 drop(host_turn);
15284 let completion = registry.finish_delivery(delivery, false);
15285 assert!(completion.failed_dispatches.is_empty());
15286 assert!(!completion.redispatch);
15287 };
15288
15289 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15291 let shared = rx.map(|_| ()).boxed().shared();
15292 let shared_for_b = shared.clone();
15293 let scheduler_b = Arc::clone(&executor);
15294 let (runnable_b, task_b) = async_task::spawn(
15295 async move {
15296 shared_for_b.await;
15297 },
15298 move |runnable| scheduler_b.schedule(runnable),
15299 );
15300 executor.schedule(runnable_b);
15301 service(0);
15302 assert_eq!(
15303 metrics.queued_runnables.load(Ordering::Relaxed),
15304 0,
15305 "B must be parked and unscheduled after its first poll"
15306 );
15307
15308 driver.fail_budget.store(2, Ordering::SeqCst);
15311 tx.send(()).expect("B must still hold its Shared clone");
15312
15313 assert_eq!(
15314 driver.deliveries().len(),
15315 4,
15316 "the wake must publish the dispatch, its one replacement, and the \
15317 retirement turn the terminal transition owes itself; without that \
15318 third publication no host turn ever follows and B stays pending \
15319 forever"
15320 );
15321 assert!(
15322 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15323 "the terminal failure must reopen the executor before the waker returns"
15324 );
15325 assert!(
15326 executor.has_rejected_work(),
15327 "B must be moved aside, not dropped on the waker's stack"
15328 );
15329 assert_eq!(
15330 metrics.queued_runnables.load(Ordering::Relaxed),
15331 0,
15332 "the moved runnable carries its queued debit"
15333 );
15334 let mut task_b = task_b.fallible();
15335 assert!(
15336 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15337 "B must still be pending: nothing may be dropped on the waker's stack"
15338 );
15339
15340 service(3);
15343 assert!(
15344 !executor.has_rejected_work(),
15345 "the retirement turn must cancel the moved work on its own stack"
15346 );
15347 assert!(
15348 poll_ready_within(&mut task_b, WAIT)
15349 .expect("the retirement turn must cancel B")
15350 .is_none(),
15351 "the terminal failure must still cancel the work queued when it happened"
15352 );
15353 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15354 drop(shared);
15355
15356 registry.unregister(registration);
15357 let state = registry
15358 .state
15359 .lock()
15360 .unwrap_or_else(std::sync::PoisonError::into_inner);
15361 assert!(state.entries.is_empty());
15362 assert!(state.dispatches.is_empty());
15363 done_tx.send(()).unwrap();
15364 });
15365 done_rx.recv_timeout(WAIT).expect(
15366 "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
15367 `queue.rejected` and its JoinHandle never settled",
15368 );
15369 }
15370
15371 #[cfg(not(target_family = "wasm"))]
15401 #[test]
15402 fn current_thread_nested_retirement_failure_keeps_its_continuation() {
15403 use std::{sync::mpsc, time::Duration};
15404
15405 const WAIT: Duration = Duration::from_secs(5);
15406 let (done_tx, done_rx) = mpsc::channel();
15407 std::thread::spawn(move || {
15408 let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
15409 {
15410 let state = registry
15411 .state
15412 .lock()
15413 .unwrap_or_else(std::sync::PoisonError::into_inner);
15414 assert!(state.entries.is_empty());
15415 assert!(state.dispatches.is_empty());
15416 }
15417 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
15418 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15419 let metrics = Arc::new(RuntimeMetrics::default());
15420 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15421 Arc::clone(&metrics),
15422 dispatch_nested_retirement_current_thread_tasks,
15423 registry,
15424 ));
15425
15426 let service = |index: usize| {
15428 let delivery = driver.deliveries()[index];
15429 let dispatch = registry
15430 .claim_delivery(delivery.capability())
15431 .expect("the accepted delivery must be claimable");
15432 let mut host_turn = executor
15433 .try_admit_host_turn(dispatch)
15434 .expect("the claimed delivery must be admitted");
15435 registry.mark_serviced(dispatch);
15436 host_turn.drive();
15437 drop(host_turn);
15438 let completion = registry.finish_delivery(delivery, false);
15439 assert!(completion.failed_dispatches.is_empty());
15440 assert!(!completion.redispatch);
15441 };
15442
15443 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15446 let shared = rx.map(|_| ()).boxed().shared();
15447 let shared_for_b = shared.clone();
15448 let scheduler_b = Arc::clone(&executor);
15449 let (runnable_b, task_b) = async_task::spawn(
15450 async move {
15451 shared_for_b.await;
15452 },
15453 move |runnable| scheduler_b.schedule(runnable),
15454 );
15455 executor.schedule(runnable_b);
15456 service(0);
15457 assert_eq!(
15458 metrics.queued_runnables.load(Ordering::Relaxed),
15459 0,
15460 "B must be parked and unscheduled after its first poll"
15461 );
15462
15463 let scheduler_c = Arc::clone(&executor);
15468 let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
15469 scheduler_c.schedule(runnable);
15470 });
15471 let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
15472 let (queued_tx, queued_rx) = mpsc::channel::<()>();
15473 let producer_executor = Arc::clone(&executor);
15474 let producer = std::thread::spawn(move || {
15475 let runnable = gate_rx
15476 .recv()
15477 .expect("the retirement turn must open the window");
15478 producer_executor.schedule(runnable);
15479 queued_tx.send(()).expect("W must still be waiting");
15480 });
15481 driver.during_delivery(3, move || {
15482 gate_tx.send(runnable_c).expect("the producer must be live");
15483 queued_rx
15484 .recv_timeout(WAIT)
15485 .expect("the producer must queue C before the retirement turn fails");
15486 });
15487
15488 driver.fail_budget.store(4, Ordering::SeqCst);
15493 tx.send(()).expect("B must still hold its Shared clone");
15494 producer.join().expect("the producer must not panic");
15495
15496 assert_eq!(
15497 driver.deliveries().len(),
15498 6,
15499 "the nested terminal transition may not publish on this stack, but the \
15500 retirement request it hands back must be serviced after the outer \
15501 publication unwinds; without that sixth delivery no host turn ever \
15502 follows and both B and C stay pending forever"
15503 );
15504 assert!(
15505 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15506 "both terminal failures must reopen the executor before the waker returns"
15507 );
15508 assert!(
15509 executor.has_rejected_work(),
15510 "B and C must be moved aside, not dropped on the waker's stack"
15511 );
15512 assert_eq!(
15513 metrics.queued_runnables.load(Ordering::Relaxed),
15514 0,
15515 "the moved runnables carry their queued debit"
15516 );
15517 let mut task_b = task_b.fallible();
15518 let mut task_c = task_c.fallible();
15519 assert!(
15520 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15521 "B must still be pending: nothing may be dropped on the waker's stack"
15522 );
15523 assert!(
15524 poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
15525 "C must still be pending: nothing may be dropped on the waker's stack"
15526 );
15527
15528 service(5);
15532 assert!(
15533 !executor.has_rejected_work(),
15534 "the retirement turn must cancel both moved runnables on its own stack"
15535 );
15536 assert!(
15537 poll_ready_within(&mut task_b, WAIT)
15538 .expect("the retirement turn must cancel B")
15539 .is_none(),
15540 "the terminal failure must still cancel the work queued when it happened"
15541 );
15542 assert!(
15543 poll_ready_within(&mut task_c, WAIT)
15544 .expect("the retirement turn must cancel C")
15545 .is_none(),
15546 "the nested terminal failure must cancel the work it moved aside too"
15547 );
15548 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15549 drop(shared);
15550
15551 registry.unregister(registration);
15552 let state = registry
15553 .state
15554 .lock()
15555 .unwrap_or_else(std::sync::PoisonError::into_inner);
15556 assert!(state.entries.is_empty());
15557 assert!(state.dispatches.is_empty());
15558 done_tx.send(()).unwrap();
15559 });
15560 done_rx.recv_timeout(WAIT).expect(
15561 "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
15562 runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
15563 );
15564 }
15565
15566 #[cfg(not(target_family = "wasm"))]
15601 #[test]
15602 fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
15603 use std::{sync::mpsc, time::Duration};
15604
15605 const WAIT: Duration = Duration::from_secs(5);
15606 const PRODUCER_BUDGET: usize = 4096;
15609 const BOUNDED_DELIVERIES: usize =
15612 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
15613
15614 let (done_tx, done_rx) = mpsc::channel();
15615 std::thread::spawn(move || {
15616 let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
15617 {
15618 let state = registry
15619 .state
15620 .lock()
15621 .unwrap_or_else(std::sync::PoisonError::into_inner);
15622 assert!(state.entries.is_empty());
15623 assert!(state.dispatches.is_empty());
15624 }
15625 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
15626 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15627 let metrics = Arc::new(RuntimeMetrics::default());
15628 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15629 Arc::clone(&metrics),
15630 dispatch_sustained_producer_current_thread_tasks,
15631 registry,
15632 ));
15633 let backend =
15634 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15635
15636 let service = |index: usize| {
15638 let delivery = driver.deliveries()[index];
15639 let dispatch = registry
15640 .claim_delivery(delivery.capability())
15641 .expect("the accepted delivery must be claimable");
15642 let mut host_turn = executor
15643 .try_admit_host_turn(dispatch)
15644 .expect("the claimed delivery must be admitted");
15645 registry.mark_serviced(dispatch);
15646 host_turn.drive();
15647 drop(host_turn);
15648 let completion = registry.finish_delivery(delivery, false);
15649 assert!(completion.failed_dispatches.is_empty());
15650 assert!(!completion.redispatch);
15651 };
15652
15653 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15657 let shared = rx.map(|_| ()).boxed().shared();
15658 let shared_for_b = shared.clone();
15659 let work_registration = backend
15660 .work
15661 .try_register_async()
15662 .expect("the backend must accept work");
15663 let task_b = spawn_registered(
15664 &backend,
15665 Arc::clone(&metrics),
15666 async move {
15667 shared_for_b.await;
15668 },
15669 work_registration,
15670 );
15671 service(0);
15672 assert_eq!(
15673 metrics.queued_runnables.load(Ordering::Relaxed),
15674 0,
15675 "B must be parked and unscheduled after its first poll"
15676 );
15677 assert_eq!(backend.work.state.lock().unwrap().active, 1);
15678
15679 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
15684 let (queued_tx, queued_rx) = mpsc::channel::<()>();
15685 let producer_executor = Arc::clone(&executor);
15686 let producer = std::thread::spawn(move || {
15687 let mut submissions = 0usize;
15688 while queue_one_rx.recv().is_ok() {
15689 let scheduler = Arc::clone(&producer_executor);
15690 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15691 scheduler.schedule(runnable);
15692 });
15693 task.detach();
15694 producer_executor.schedule(runnable);
15695 submissions += 1;
15696 if queued_tx.send(()).is_err() {
15697 break;
15698 }
15699 }
15700 submissions
15701 });
15702 driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
15703
15704 driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
15707 let before = driver.deliveries().len();
15708 tx.send(()).expect("B must still hold its Shared clone");
15709 let during_wake = driver.deliveries().len() - before;
15710 let unspent = driver.disarm_producer();
15711 let submissions = producer.join().expect("the producer must not panic");
15712
15713 assert_eq!(
15714 during_wake, BOUNDED_DELIVERIES,
15715 "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
15716 on the waker's stack plus its one final offer; without a budget it \
15717 services the producer's stream instead and this wake never returns"
15718 );
15719 assert_eq!(
15720 submissions, during_wake,
15721 "the producer must have queued a runnable during every publication"
15722 );
15723 assert!(
15724 unspent > 0,
15725 "the producer must still have been willing to submit when the turns stopped"
15726 );
15727 assert!(
15728 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15729 "every terminal failure, and the final offer, must reopen the executor \
15730 before the waker returns"
15731 );
15732 assert!(
15733 executor.has_rejected_work(),
15734 "the moved work must be parked, not dropped on the waker's stack"
15735 );
15736 assert_eq!(
15737 executor.dispatch_pending.load(Ordering::Acquire),
15738 0,
15739 "a host that refused every offer including the final one leaves none \
15740 outstanding: this is the documented end state for a host that never accepts"
15741 );
15742 assert_eq!(
15743 metrics.queued_runnables.load(Ordering::Relaxed),
15744 0,
15745 "the moved runnables carry their queued debit"
15746 );
15747 let mut task_b = Box::pin(task_b);
15748 assert!(
15749 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15750 "B must still be pending: nothing may be dropped on the waker's stack"
15751 );
15752 assert_eq!(
15753 backend.work.state.lock().unwrap().active,
15754 1,
15755 "B's generation guard rides with its parked runnable"
15756 );
15757
15758 backend.wait_until_idle();
15762 assert!(!executor.has_rejected_work());
15763 assert_eq!(backend.work.state.lock().unwrap().active, 0);
15764 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15765 assert!(
15766 poll_ready_within(&mut task_b, WAIT)
15767 .expect("the idle wait must cancel B")
15768 .is_err(),
15769 "the terminal failure must still cancel the work queued when it happened"
15770 );
15771 drop(shared);
15772
15773 registry.unregister(registration);
15774 let state = registry
15775 .state
15776 .lock()
15777 .unwrap_or_else(std::sync::PoisonError::into_inner);
15778 assert!(state.entries.is_empty());
15779 assert!(state.dispatches.is_empty());
15780 done_tx.send(()).unwrap();
15781 });
15782 done_rx.recv_timeout(WAIT).expect(
15783 "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
15784 the wake never returned",
15785 );
15786 }
15787
15788 #[cfg(not(target_family = "wasm"))]
15822 #[test]
15823 fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
15824 use std::{sync::mpsc, time::Duration};
15825
15826 const WAIT: Duration = Duration::from_secs(5);
15827 const BUDGETED_DELIVERIES: usize =
15831 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
15832
15833 let (done_tx, done_rx) = mpsc::channel();
15834 std::thread::spawn(move || {
15835 struct DropFlag(Arc<AtomicBool>);
15836 impl Drop for DropFlag {
15837 fn drop(&mut self) {
15838 self.0.store(true, Ordering::SeqCst);
15839 }
15840 }
15841
15842 let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
15843 {
15844 let state = registry
15845 .state
15846 .lock()
15847 .unwrap_or_else(std::sync::PoisonError::into_inner);
15848 assert!(state.entries.is_empty());
15849 assert!(state.dispatches.is_empty());
15850 }
15851 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
15852 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15853 let metrics = Arc::new(RuntimeMetrics::default());
15854 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15855 Arc::clone(&metrics),
15856 dispatch_final_retirement_offer_current_thread_tasks,
15857 registry,
15858 ));
15859 let backend =
15860 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15861
15862 let service = |index: usize| {
15863 let delivery = driver.deliveries()[index];
15864 let dispatch = registry
15865 .claim_delivery(delivery.capability())
15866 .expect("the accepted delivery must be claimable");
15867 let mut host_turn = executor
15868 .try_admit_host_turn(dispatch)
15869 .expect("the claimed delivery must be admitted");
15870 registry.mark_serviced(dispatch);
15871 host_turn.drive();
15872 drop(host_turn);
15873 let completion = registry.finish_delivery(delivery, false);
15874 assert!(completion.failed_dispatches.is_empty());
15875 assert!(!completion.redispatch);
15876 };
15877
15878 let dropped = Arc::new(AtomicBool::new(false));
15879 let flag = DropFlag(Arc::clone(&dropped));
15880 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15881 let shared = rx.map(|_| ()).boxed().shared();
15882 let shared_for_b = shared.clone();
15883 let work_registration = backend
15884 .work
15885 .try_register_async()
15886 .expect("the backend must accept work");
15887 let task_b = spawn_registered(
15888 &backend,
15889 Arc::clone(&metrics),
15890 async move {
15891 let _flag = flag;
15892 shared_for_b.await;
15893 },
15894 work_registration,
15895 );
15896 service(0);
15897 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15898 assert_eq!(backend.work.state.lock().unwrap().active, 1);
15899
15900 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
15901 let (queued_tx, queued_rx) = mpsc::channel::<()>();
15902 let producer_executor = Arc::clone(&executor);
15903 let producer = std::thread::spawn(move || {
15904 let mut submissions = 0usize;
15905 while queue_one_rx.recv().is_ok() {
15906 let scheduler = Arc::clone(&producer_executor);
15907 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15908 scheduler.schedule(runnable);
15909 });
15910 task.detach();
15911 producer_executor.schedule(runnable);
15912 submissions += 1;
15913 if queued_tx.send(()).is_err() {
15914 break;
15915 }
15916 }
15917 submissions
15918 });
15919 driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
15922 driver
15923 .fail_budget
15924 .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
15925
15926 let before = driver.deliveries().len();
15927 tx.send(()).expect("B must still hold its Shared clone");
15928 let during_wake = driver.deliveries().len() - before;
15929 let unspent = driver.disarm_producer();
15930 let submissions = producer.join().expect("the producer must not panic");
15931
15932 assert_eq!(
15933 during_wake,
15934 BUDGETED_DELIVERIES + 1,
15935 "the budget must be followed by the one offer a producer cannot \
15936 inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
15937 deliveries and never asks the host that would have accepted"
15938 );
15939 assert_eq!(
15940 submissions, BUDGETED_DELIVERIES,
15941 "the producer must have queued a runnable during every budgeted \
15942 publication and gone quiescent before the final offer"
15943 );
15944 assert_eq!(
15945 unspent, 0,
15946 "the producer must have run dry exactly as the budget ran out"
15947 );
15948 assert_eq!(
15949 driver.fail_budget.load(Ordering::SeqCst),
15950 0,
15951 "the host must have refused every budgeted delivery and no more"
15952 );
15953 assert!(
15954 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15955 "the final offer must reopen the executor before the waker returns"
15956 );
15957 assert!(
15958 executor.has_rejected_work(),
15959 "nothing may be dropped on the waker's stack"
15960 );
15961 assert_ne!(
15962 executor.dispatch_pending.load(Ordering::Acquire),
15963 0,
15964 "the wake must leave a dispatch outstanding: that accepted delivery is \
15965 the continuation the parked work has"
15966 );
15967 let mut task_b = Box::pin(task_b);
15968 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
15969 assert!(!dropped.load(Ordering::SeqCst));
15970 assert_eq!(backend.work.state.lock().unwrap().active, 1);
15971
15972 service(driver.deliveries().len() - 1);
15975 assert!(
15976 !executor.has_rejected_work(),
15977 "that host turn is a depth-0 stack and must retire everything the wake \
15978 moved aside"
15979 );
15980 assert!(
15981 poll_ready_within(&mut task_b, WAIT)
15982 .expect("the final offer's host turn must cancel B")
15983 .is_err()
15984 );
15985 assert!(
15986 dropped.load(Ordering::SeqCst),
15987 "B's future must be destroyed, on the host's stack and not the waker's"
15988 );
15989 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15990 assert_eq!(
15991 backend.work.state.lock().unwrap().active,
15992 0,
15993 "no GenerationWorkGuard may outlive the retirement"
15994 );
15995 drop(shared);
15996
15997 registry.unregister(registration);
15998 let state = registry
15999 .state
16000 .lock()
16001 .unwrap_or_else(std::sync::PoisonError::into_inner);
16002 assert!(state.entries.is_empty());
16003 assert!(state.dispatches.is_empty());
16004 done_tx.send(()).unwrap();
16005 });
16006 done_rx.recv_timeout(WAIT).expect(
16007 "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16008 );
16009 }
16010
16011 #[cfg(not(target_family = "wasm"))]
16029 #[test]
16030 fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
16031 use std::{sync::mpsc, time::Duration};
16032
16033 const WAIT: Duration = Duration::from_secs(5);
16034 let (done_tx, done_rx) = mpsc::channel();
16035 std::thread::spawn(move || {
16036 struct DropFlag(Arc<AtomicBool>);
16037 impl Drop for DropFlag {
16038 fn drop(&mut self) {
16039 self.0.store(true, Ordering::SeqCst);
16040 }
16041 }
16042
16043 let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
16044 {
16045 let state = registry
16046 .state
16047 .lock()
16048 .unwrap_or_else(std::sync::PoisonError::into_inner);
16049 assert!(state.entries.is_empty());
16050 assert!(state.dispatches.is_empty());
16051 }
16052 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16053 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16054 let metrics = Arc::new(RuntimeMetrics::default());
16055 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16056 Arc::clone(&metrics),
16057 dispatch_retirement_retry_current_thread_tasks,
16058 registry,
16059 ));
16060 let backend =
16061 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16062
16063 let service = |index: usize| {
16064 let delivery = driver.deliveries()[index];
16065 let dispatch = registry
16066 .claim_delivery(delivery.capability())
16067 .expect("the accepted delivery must be claimable");
16068 let mut host_turn = executor
16069 .try_admit_host_turn(dispatch)
16070 .expect("the claimed delivery must be admitted");
16071 registry.mark_serviced(dispatch);
16072 host_turn.drive();
16073 drop(host_turn);
16074 let completion = registry.finish_delivery(delivery, false);
16075 assert!(completion.failed_dispatches.is_empty());
16076 assert!(!completion.redispatch);
16077 };
16078
16079 let dropped = Arc::new(AtomicBool::new(false));
16080 let flag = DropFlag(Arc::clone(&dropped));
16081 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16082 let shared = rx.map(|_| ()).boxed().shared();
16083 let shared_for_b = shared.clone();
16084 let work_registration = backend
16085 .work
16086 .try_register_async()
16087 .expect("the backend must accept work");
16088 let task_b = spawn_registered(
16089 &backend,
16090 Arc::clone(&metrics),
16091 async move {
16092 let _flag = flag;
16093 shared_for_b.await;
16094 },
16095 work_registration,
16096 );
16097 service(0);
16098 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16099 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16100
16101 let before = driver.deliveries().len();
16106 driver.fail_budget.store(3, Ordering::SeqCst);
16107 tx.send(()).expect("B must still hold its Shared clone");
16108 let during_wake = driver.deliveries().len() - before;
16109
16110 assert_eq!(
16111 during_wake, 4,
16112 "a refused offer owes another turn just as a nested transition does: \
16113 work is parked and no dispatch is outstanding"
16114 );
16115 assert_ne!(
16116 executor.dispatch_pending.load(Ordering::Acquire),
16117 0,
16118 "the accepted second offer is the continuation the parked work has"
16119 );
16120 assert!(executor.has_rejected_work());
16121 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16122 let mut task_b = Box::pin(task_b);
16123 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16124 assert!(!dropped.load(Ordering::SeqCst));
16125
16126 service(driver.deliveries().len() - 1);
16128 assert!(!executor.has_rejected_work());
16129 assert!(
16130 poll_ready_within(&mut task_b, WAIT)
16131 .expect("the accepted offer's host turn must cancel B")
16132 .is_err()
16133 );
16134 assert!(dropped.load(Ordering::SeqCst));
16135 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16136 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16137 drop(shared);
16138
16139 registry.unregister(registration);
16140 let state = registry
16141 .state
16142 .lock()
16143 .unwrap_or_else(std::sync::PoisonError::into_inner);
16144 assert!(state.entries.is_empty());
16145 assert!(state.dispatches.is_empty());
16146 done_tx.send(()).unwrap();
16147 });
16148 done_rx
16149 .recv_timeout(WAIT)
16150 .expect("a refused retirement offer left B stranded with a healthy host");
16151 }
16152
16153 #[cfg(not(target_family = "wasm"))]
16185 #[test]
16186 fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
16187 use std::{sync::mpsc, time::Duration};
16188
16189 const WAIT: Duration = Duration::from_secs(5);
16190 const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16192
16193 let (done_tx, done_rx) = mpsc::channel();
16194 std::thread::spawn(move || {
16195 struct DropFlag(Arc<AtomicBool>);
16196 impl Drop for DropFlag {
16197 fn drop(&mut self) {
16198 self.0.store(true, Ordering::SeqCst);
16199 }
16200 }
16201
16202 let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
16203 {
16204 let state = registry
16205 .state
16206 .lock()
16207 .unwrap_or_else(std::sync::PoisonError::into_inner);
16208 assert!(state.entries.is_empty());
16209 assert!(state.dispatches.is_empty());
16210 }
16211 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16212 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16213 let metrics = Arc::new(RuntimeMetrics::default());
16214 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16215 Arc::clone(&metrics),
16216 dispatch_inline_final_offer_current_thread_tasks,
16217 registry,
16218 ));
16219 let backend =
16220 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16221
16222 let service = |index: usize| {
16223 let delivery = driver.deliveries()[index];
16224 let dispatch = registry
16225 .claim_delivery(delivery.capability())
16226 .expect("the accepted delivery must be claimable");
16227 let mut host_turn = executor
16228 .try_admit_host_turn(dispatch)
16229 .expect("the claimed delivery must be admitted");
16230 registry.mark_serviced(dispatch);
16231 host_turn.drive();
16232 drop(host_turn);
16233 let completion = registry.finish_delivery(delivery, false);
16234 assert!(completion.failed_dispatches.is_empty());
16235 assert!(!completion.redispatch);
16236 };
16237
16238 let dropped = Arc::new(AtomicBool::new(false));
16239 let flag = DropFlag(Arc::clone(&dropped));
16240 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16241 let shared = rx.map(|_| ()).boxed().shared();
16242 let shared_for_b = shared.clone();
16243 let work_registration = backend
16244 .work
16245 .try_register_async()
16246 .expect("the backend must accept work");
16247 let task_b = spawn_registered(
16248 &backend,
16249 Arc::clone(&metrics),
16250 async move {
16251 let _flag = flag;
16252 shared_for_b.await;
16253 },
16254 work_registration,
16255 );
16256 service(0);
16257 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16258 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16259
16260 let before = driver.deliveries().len();
16265 let serviced_inline = Arc::new(AtomicBool::new(false));
16266 {
16267 let executor = Arc::clone(&executor);
16268 let hook_driver = Arc::clone(&driver);
16269 let serviced_inline = Arc::clone(&serviced_inline);
16270 driver.during_delivery(before + REFUSED_DELIVERIES, move || {
16271 let delivery = *hook_driver
16272 .deliveries()
16273 .last()
16274 .expect("the final offer must be recorded before the hook runs");
16275 let dispatch = registry
16276 .claim_delivery(delivery.capability())
16277 .expect("the final offer must be claimable");
16278 let mut host_turn = executor
16279 .try_admit_host_turn(dispatch)
16280 .expect("the claimed final offer must be admitted");
16281 registry.mark_serviced(dispatch);
16282 host_turn.drive();
16283 drop(host_turn);
16284 let completion = registry.finish_delivery(delivery, false);
16285 assert!(completion.failed_dispatches.is_empty());
16286 serviced_inline.store(true, Ordering::SeqCst);
16287 });
16288 }
16289 driver
16290 .fail_budget
16291 .store(REFUSED_DELIVERIES, Ordering::SeqCst);
16292 tx.send(()).expect("B must still hold its Shared clone");
16293 let during_wake = driver.deliveries().len() - before;
16294
16295 assert!(
16296 serviced_inline.load(Ordering::SeqCst),
16297 "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
16298 );
16299 assert_eq!(
16300 during_wake,
16301 REFUSED_DELIVERIES + 2,
16302 "an accepted offer whose host turn could not act owes the same continuation a refused \
16303 one owes: work parked and no dispatch outstanding"
16304 );
16305 assert_ne!(
16306 executor.dispatch_pending.load(Ordering::Acquire),
16307 0,
16308 "the publication that follows the consumed offer is the continuation the parked work has"
16309 );
16310 assert!(executor.has_rejected_work());
16311 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16312 let mut task_b = Box::pin(task_b);
16313 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16314 assert!(!dropped.load(Ordering::SeqCst));
16315
16316 service(driver.deliveries().len() - 1);
16318 assert!(!executor.has_rejected_work());
16319 assert!(
16320 poll_ready_within(&mut task_b, WAIT)
16321 .expect("the host turn that follows the consumed offer must cancel B")
16322 .is_err()
16323 );
16324 assert!(dropped.load(Ordering::SeqCst));
16325 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16326 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16327 drop(shared);
16328
16329 registry.unregister(registration);
16330 let state = registry
16331 .state
16332 .lock()
16333 .unwrap_or_else(std::sync::PoisonError::into_inner);
16334 assert!(state.entries.is_empty());
16335 assert!(state.dispatches.is_empty());
16336 done_tx.send(()).unwrap();
16337 });
16338 done_rx.recv_timeout(WAIT).expect(
16339 "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
16340 );
16341 }
16342
16343 #[cfg(not(target_family = "wasm"))]
16358 #[test]
16359 fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
16360 use std::time::Duration;
16361
16362 let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
16363 {
16364 let state = registry
16365 .state
16366 .lock()
16367 .unwrap_or_else(std::sync::PoisonError::into_inner);
16368 assert!(state.entries.is_empty());
16369 assert!(state.dispatches.is_empty());
16370 }
16371 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
16372 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16373 let metrics = Arc::new(RuntimeMetrics::default());
16374 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16375 Arc::clone(&metrics),
16376 dispatch_submission_failure_current_thread_tasks,
16377 registry,
16378 ));
16379 let backend =
16380 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16381
16382 driver.fail.store(true, Ordering::SeqCst);
16385
16386 let work_registration = backend
16387 .work
16388 .try_register_async()
16389 .expect("the backend must accept work");
16390 let task = spawn_registered(
16391 &backend,
16392 Arc::clone(&metrics),
16393 std::future::pending::<()>(),
16394 work_registration,
16395 );
16396
16397 assert_eq!(
16398 driver.deliveries().len(),
16399 2,
16400 "the submission must publish the dispatch and exactly one replacement"
16401 );
16402 assert!(
16403 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16404 "the terminal failure must reopen the executor"
16405 );
16406 assert!(
16407 !executor.has_rejected_work(),
16408 "a submission's own stack is not a waker's: the rejected work must be cancelled here, because nothing else will"
16409 );
16410 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16411 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16412
16413 let mut task = Box::pin(task);
16414 assert_eq!(
16415 poll_ready_within(&mut task, Duration::ZERO)
16416 .expect("the submission must settle without any later entry")
16417 .unwrap_err()
16418 .to_string(),
16419 "async runtime stopped before the task completed",
16420 "terminal host failure must settle the submitted operation as cancelled"
16421 );
16422
16423 registry.unregister(registration);
16424 let state = registry
16425 .state
16426 .lock()
16427 .unwrap_or_else(std::sync::PoisonError::into_inner);
16428 assert!(state.entries.is_empty());
16429 assert!(state.dispatches.is_empty());
16430 }
16431
16432 #[cfg(not(target_family = "wasm"))]
16446 #[test]
16447 fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
16448 use std::{
16449 sync::{Barrier, mpsc},
16450 time::Duration,
16451 };
16452
16453 let (done_tx, done_rx) = mpsc::channel();
16454 std::thread::spawn(move || {
16455 let metrics = Arc::new(RuntimeMetrics::default());
16456 let options = RuntimeOptions {
16457 flavor: RuntimeFlavor::MultiThread,
16458 worker_threads: 2,
16459 max_blocking_tasks: 1,
16460 thread_name_prefix: "mt-inline-drop".to_string(),
16461 park_deadline: None,
16462 drain_linger: DEFAULT_DRAIN_LINGER,
16463 };
16464 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
16465 let backend =
16466 RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
16467 let generation = backend.generation();
16468
16469 let (abort_a, guard_a) = backend
16471 .work
16472 .try_register_async()
16473 .expect("the backend must accept work");
16474 let scheduler_a = Arc::clone(&executor);
16475 let (runnable_a, task_a) = async_task::spawn(
16476 RegisteredTaskFuture::new(
16477 Abortable::new(std::future::pending::<()>(), abort_a),
16478 guard_a,
16479 generation,
16480 ),
16481 move |runnable| scheduler_a.schedule(runnable),
16482 );
16483 let shared = async move { task_a.fallible().await.is_some() }
16484 .boxed()
16485 .shared();
16486 let shared_for_b = shared.clone();
16487
16488 let (abort_b, guard_b) = backend
16492 .work
16493 .try_register_async()
16494 .expect("the backend must accept work");
16495 let wake_in_flight = Arc::new(Barrier::new(2));
16496 let wake_release = Arc::new(Barrier::new(2));
16497 let scheduler_b = Arc::clone(&executor);
16498 let (runnable_b, task_b) = async_task::spawn(
16499 RegisteredTaskFuture::new(
16500 Abortable::new(
16501 async move {
16502 shared_for_b.await;
16503 },
16504 abort_b,
16505 ),
16506 guard_b,
16507 generation,
16508 ),
16509 {
16510 let wake_in_flight = Arc::clone(&wake_in_flight);
16511 let wake_release = Arc::clone(&wake_release);
16512 move |runnable| {
16513 wake_in_flight.wait();
16514 wake_release.wait();
16515 scheduler_b.schedule(runnable);
16516 }
16517 },
16518 );
16519 executor.schedule(runnable_b);
16524 wait_until("B parked after its first poll", || {
16525 metrics.runnable_polls.load(Ordering::SeqCst) == 1
16526 && metrics.active_runnables.load(Ordering::SeqCst) == 0
16527 && metrics.queued_runnables.load(Ordering::SeqCst) == 0
16528 });
16529
16530 let waker_thread = std::thread::spawn(move || drop(runnable_a));
16534 wake_in_flight.wait();
16535
16536 backend.begin_shutdown();
16539 wake_release.wait();
16540 backend.wait_until_idle();
16541
16542 waker_thread
16543 .join()
16544 .expect("the waker thread must not panic");
16545 assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
16546 assert!(
16547 futures::executor::block_on(task_b.fallible()).is_none(),
16548 "B's late wake must still cancel B"
16549 );
16550 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16551 drop(shared);
16552 done_tx.send(()).unwrap();
16553 });
16554 done_rx
16555 .recv_timeout(Duration::from_secs(5))
16556 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
16557 }
16558
16559 #[cfg(not(target_family = "wasm"))]
16560 #[test]
16561 fn current_thread_recovery_reservation_blocks_new_host_publication() {
16562 use std::sync::mpsc;
16563
16564 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
16565 let metrics = Arc::new(RuntimeMetrics::default());
16566 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
16567 Arc::clone(&metrics),
16568 count_current_thread_interleaved_recovery_host_dispatch,
16569 ));
16570 let backend =
16571 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16572
16573 let registration = backend
16574 .work
16575 .try_register_async()
16576 .expect("the backend must accept work");
16577 let task = spawn_registered(
16578 &backend,
16579 Arc::clone(&metrics),
16580 std::future::pending::<()>(),
16581 registration,
16582 );
16583 let failed_dispatch = current_thread_dispatch(&executor);
16584 assert_eq!(
16585 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16586 1
16587 );
16588
16589 let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
16590 let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
16591 let cancelling_executor = Arc::clone(&executor);
16592 let cancellation = std::thread::spawn(move || {
16593 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
16594 *slot.borrow_mut() = Some(Box::new(move || {
16595 recovery_cleared_tx.send(()).unwrap();
16596 release_recovery_rx.recv().unwrap();
16597 }));
16598 });
16599 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
16600 });
16601 recovery_cleared_rx
16602 .recv_timeout(Duration::from_secs(2))
16603 .expect("failed dispatch cancellation must pause after consuming the old capability");
16604
16605 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
16606 let (publication_done_tx, publication_done_rx) = mpsc::channel();
16607 let publishing_executor = Arc::clone(&executor);
16608 let publication = std::thread::spawn(move || {
16609 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
16610 *slot.borrow_mut() = Some(Box::new(move || {
16611 publication_blocked_tx.send(()).unwrap();
16612 }));
16613 });
16614 publishing_executor.request_drain_if_queued();
16615 publication_done_tx.send(()).unwrap();
16616 });
16617
16618 if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
16619 release_recovery_tx.send(()).unwrap();
16620 cancellation.join().unwrap();
16621 publication.join().unwrap();
16622 panic!("a newly registered host must wait for recovery reservation: {error}");
16623 }
16624 assert!(
16625 matches!(
16626 publication_done_rx.try_recv(),
16627 Err(mpsc::TryRecvError::Empty)
16628 ),
16629 "the new host must not publish in the consumed-to-recovery gap"
16630 );
16631 release_recovery_tx.send(()).unwrap();
16632
16633 cancellation.join().unwrap();
16634 publication_done_rx
16635 .recv_timeout(Duration::from_secs(2))
16636 .unwrap();
16637 publication.join().unwrap();
16638
16639 let replacement_dispatch = current_thread_dispatch(&executor);
16640 assert_ne!(replacement_dispatch, failed_dispatch);
16641 assert_eq!(
16642 executor.recovery_dispatch.load(Ordering::Acquire),
16643 replacement_dispatch
16644 );
16645 assert_eq!(
16646 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16647 3,
16648 "the original publication and two host attempts for its one replacement must run"
16649 );
16650
16651 executor.cancel_host_dispatch_for_test(replacement_dispatch);
16652
16653 assert_eq!(
16654 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16655 3,
16656 "failure of the tagged replacement must cancel work without a second replacement"
16657 );
16658 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
16659 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
16660 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16661 assert_eq!(
16662 futures::executor::block_on(task).unwrap_err().to_string(),
16663 "async runtime stopped before the task completed"
16664 );
16665
16666 let later_registration = backend
16667 .work
16668 .try_register_async()
16669 .expect("terminal cancellation must reopen acceptance");
16670 let later_task = spawn_registered(
16671 &backend,
16672 Arc::clone(&metrics),
16673 async { 2usize },
16674 later_registration,
16675 );
16676 assert_eq!(
16677 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16678 4
16679 );
16680 executor.drive_host_turn();
16681 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
16682 }
16683
16684 #[cfg(not(target_family = "wasm"))]
16685 #[test]
16686 fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
16687 use std::sync::mpsc;
16688
16689 struct BlockingDrop {
16690 entered: mpsc::Sender<()>,
16691 release: mpsc::Receiver<()>,
16692 }
16693
16694 impl Drop for BlockingDrop {
16695 fn drop(&mut self) {
16696 self.entered.send(()).unwrap();
16697 self.release.recv().unwrap();
16698 }
16699 }
16700
16701 reset_current_thread_recovery_host_dispatches();
16702 let metrics = Arc::new(RuntimeMetrics::default());
16703 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
16704 Arc::clone(&metrics),
16705 count_current_thread_recovery_host_dispatch,
16706 ));
16707 let backend =
16708 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16709
16710 let holder_registration = backend
16711 .work
16712 .try_register_work()
16713 .expect("the backend must accept the blocking holder");
16714 let (holder_started_tx, holder_started_rx) = mpsc::channel();
16715 let (release_holder_tx, release_holder_rx) = mpsc::channel();
16716 let holder_executor = Arc::clone(&executor);
16717 let holder = std::thread::spawn(move || {
16718 let handle = holder_executor.schedule_blocking(
16719 move || {
16720 holder_started_tx.send(()).unwrap();
16721 release_holder_rx.recv().unwrap();
16722 },
16723 holder_registration,
16724 );
16725 futures::executor::block_on(handle).unwrap();
16726 });
16727 holder_started_rx
16728 .recv_timeout(Duration::from_secs(2))
16729 .expect("the first blocking call must hold the CurrentThread lane");
16730
16731 let cancelled_registration = backend
16732 .work
16733 .try_register_work()
16734 .expect("the backend must accept queued blocking work");
16735 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
16736 let (release_drop_tx, release_drop_rx) = mpsc::channel();
16737 let blocking_drop = BlockingDrop {
16738 entered: drop_entered_tx,
16739 release: release_drop_rx,
16740 };
16741 let cancelled = executor.schedule_blocking(
16742 move || {
16743 let _blocking_drop = blocking_drop;
16744 },
16745 cancelled_registration,
16746 );
16747
16748 let async_registration = backend
16749 .work
16750 .try_register_async()
16751 .expect("the backend must accept queued async work");
16752 let pending = spawn_registered(
16753 &backend,
16754 Arc::clone(&metrics),
16755 std::future::pending::<()>(),
16756 async_registration,
16757 );
16758 let failed_dispatch = current_thread_dispatch(&executor);
16759 executor.cancel_host_dispatch_for_test(failed_dispatch);
16760 let replacement_dispatch = current_thread_dispatch(&executor);
16761
16762 let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
16763 let cancelling_executor = Arc::clone(&executor);
16764 let cancellation = std::thread::spawn(move || {
16765 cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
16766 cancel_done_tx.send(()).unwrap();
16767 });
16768 drop_entered_rx
16769 .recv_timeout(Duration::from_secs(2))
16770 .expect("terminal dispatch cancellation must destroy the queued blocking capture");
16771 let active_during_drop = backend.work.state.lock().unwrap().active;
16772 let cancellation_blocked = cancel_done_rx.try_recv().is_err();
16773
16774 release_drop_tx.send(()).unwrap();
16775 cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16776 cancellation.join().unwrap();
16777
16778 assert_eq!(
16779 active_during_drop, 2,
16780 "the blocking capture's work registration must remain active until its destructor returns"
16781 );
16782 assert!(
16783 cancellation_blocked,
16784 "terminal cancellation must wait for capture destruction"
16785 );
16786 assert!(futures::executor::block_on(cancelled).is_err());
16787 assert!(futures::executor::block_on(pending).is_err());
16788 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16789
16790 release_holder_tx.send(()).unwrap();
16791 holder.join().unwrap();
16792 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16793 }
16794
16795 #[test]
16796 fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
16797 reset_current_thread_one_shot_host_dispatches();
16798 let metrics = Arc::new(RuntimeMetrics::default());
16799 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
16800 Arc::clone(&metrics),
16801 accept_only_first_current_thread_host_dispatch,
16802 ));
16803
16804 let scheduler = Arc::clone(&executor);
16805 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16806 scheduler.schedule(runnable)
16807 });
16808 executor.schedule(runnable);
16809 let failed_dispatch = current_thread_dispatch(&executor);
16810
16811 executor.cancel_host_dispatch_for_test(failed_dispatch);
16812
16813 assert_eq!(
16814 current_thread_one_shot_host_dispatches(),
16815 2,
16816 "the original and its one bounded replacement must be attempted"
16817 );
16818 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
16819 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
16820 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16821 assert!(
16822 futures::executor::block_on(task.fallible()).is_none(),
16823 "an unavailable replacement host must settle the original task as cancelled"
16824 );
16825 }
16826
16827 enum TestCurrentThreadTaskDriverBehavior {
16828 Healthy,
16829 PanicInLivenessAndSweep,
16830 PanicInDispatchAndSweep,
16831 }
16832
16833 struct TestCurrentThreadTaskDriver {
16834 behavior: TestCurrentThreadTaskDriverBehavior,
16835 dispatched: AtomicBool,
16836 }
16837
16838 #[derive(Default)]
16839 struct ManualCurrentThreadTaskDriver {
16840 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
16841 }
16842
16843 struct FailDuringAuthoritativePanicPublication;
16844 struct FailDuringLateRegistrationPublication;
16845 struct FailDuringNestedUnwindPublication;
16846 struct FailDuringReplacementRepublishPublication;
16847 struct FirstHostileCurrentThreadPanic;
16848 struct LaterHostileCurrentThreadPanic;
16849
16850 impl Drop for FirstHostileCurrentThreadPanic {
16851 fn drop(&mut self) {
16852 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
16853 panic!("the first retained CurrentThread panic payload was dropped");
16854 }
16855 }
16856
16857 impl Drop for LaterHostileCurrentThreadPanic {
16858 fn drop(&mut self) {
16859 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
16860 panic!("a later CurrentThread panic payload escaped containment");
16861 }
16862 }
16863
16864 impl ManualCurrentThreadTaskDriver {
16865 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
16866 self.deliveries.lock().unwrap().clone()
16867 }
16868 }
16869
16870 impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
16871 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
16872 self.deliveries.lock().unwrap().push(delivery);
16873 true
16874 }
16875 }
16876
16877 struct SynchronouslyFailingCurrentThreadTaskDriver {
16882 registry: &'static CurrentThreadTaskDriverRegistry,
16883 fail: AtomicBool,
16884 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
16885 }
16886
16887 impl SynchronouslyFailingCurrentThreadTaskDriver {
16888 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
16889 Self {
16890 registry,
16891 fail: AtomicBool::new(false),
16892 deliveries: Mutex::default(),
16893 }
16894 }
16895
16896 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
16897 self.deliveries.lock().unwrap().clone()
16898 }
16899 }
16900
16901 impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
16902 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
16903 self.deliveries.lock().unwrap().push(delivery);
16904 if self.fail.load(Ordering::SeqCst) {
16905 let registry = self.registry;
16906 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
16907 .join()
16908 .expect("the failing helper must not panic");
16909 assert!(
16910 completion.failed_dispatches.is_empty(),
16911 "the failed delivery must remain provisional until its publication retires"
16912 );
16913 }
16914 true
16915 }
16916 }
16917
16918 type DeliveryHook = Box<dyn FnOnce() + Send>;
16926
16927 struct BoundedFailureCurrentThreadTaskDriver {
16928 registry: &'static CurrentThreadTaskDriverRegistry,
16929 fail_budget: AtomicUsize,
16930 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
16931 during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
16932 }
16933
16934 impl BoundedFailureCurrentThreadTaskDriver {
16935 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
16936 Self {
16937 registry,
16938 fail_budget: AtomicUsize::new(0),
16939 deliveries: Mutex::default(),
16940 during_delivery: Mutex::default(),
16941 }
16942 }
16943
16944 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
16945 self.deliveries.lock().unwrap().clone()
16946 }
16947
16948 fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
16953 *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
16954 }
16955 }
16956
16957 impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
16958 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
16959 let index = {
16960 let mut deliveries = self.deliveries.lock().unwrap();
16961 deliveries.push(delivery);
16962 deliveries.len() - 1
16963 };
16964 let hook = {
16965 let mut slot = self.during_delivery.lock().unwrap();
16966 if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
16967 slot.take().map(|(_, hook)| hook)
16968 } else {
16969 None
16970 }
16971 };
16972 if let Some(hook) = hook {
16973 hook();
16974 }
16975 let budget = self.fail_budget.load(Ordering::SeqCst);
16976 if budget != 0 {
16977 self.fail_budget.store(budget - 1, Ordering::SeqCst);
16978 let registry = self.registry;
16979 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
16980 .join()
16981 .expect("the failing helper must not panic");
16982 assert!(
16983 completion.failed_dispatches.is_empty(),
16984 "the failed delivery must remain provisional until its publication retires"
16985 );
16986 }
16987 true
16988 }
16989 }
16990
16991 struct SustainedProducerGate {
16998 remaining: usize,
16999 queue_one: std::sync::mpsc::Sender<()>,
17000 queued: std::sync::mpsc::Receiver<()>,
17001 }
17002
17003 struct SustainedProducerCurrentThreadTaskDriver {
17004 registry: &'static CurrentThreadTaskDriverRegistry,
17005 fail_budget: AtomicUsize,
17008 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17009 producer: Mutex<Option<SustainedProducerGate>>,
17010 }
17011
17012 impl SustainedProducerCurrentThreadTaskDriver {
17013 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17014 Self {
17015 registry,
17016 fail_budget: AtomicUsize::new(0),
17017 deliveries: Mutex::default(),
17018 producer: Mutex::default(),
17019 }
17020 }
17021
17022 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17023 self.deliveries.lock().unwrap().clone()
17024 }
17025
17026 fn arm_producer(
17027 &self,
17028 remaining: usize,
17029 queue_one: std::sync::mpsc::Sender<()>,
17030 queued: std::sync::mpsc::Receiver<()>,
17031 ) {
17032 *self.producer.lock().unwrap() = Some(SustainedProducerGate {
17033 remaining,
17034 queue_one,
17035 queued,
17036 });
17037 }
17038
17039 fn disarm_producer(&self) -> usize {
17042 self
17043 .producer
17044 .lock()
17045 .unwrap()
17046 .take()
17047 .map_or(0, |gate| gate.remaining)
17048 }
17049 }
17050
17051 impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
17052 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17053 self.deliveries.lock().unwrap().push(delivery);
17054 {
17055 let mut slot = self.producer.lock().unwrap();
17056 if let Some(gate) = slot.as_mut()
17057 && gate.remaining != 0
17058 {
17059 gate.remaining -= 1;
17060 gate.queue_one.send(()).expect("the producer must be live");
17061 gate
17062 .queued
17063 .recv_timeout(std::time::Duration::from_secs(5))
17064 .expect("the producer must queue one runnable per publication");
17065 }
17066 }
17067 let budget = self.fail_budget.load(Ordering::SeqCst);
17068 if budget != 0 {
17069 if budget != usize::MAX {
17070 self.fail_budget.store(budget - 1, Ordering::SeqCst);
17071 }
17072 let completion = self.registry.finish_delivery(delivery, true);
17073 assert!(
17074 completion.failed_dispatches.is_empty(),
17075 "the failed delivery must remain provisional until its publication retires"
17076 );
17077 }
17078 true
17079 }
17080 }
17081
17082 impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
17083 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17084 let completion =
17085 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
17086 assert!(
17087 completion.failed_dispatches.is_empty(),
17088 "the failed delivery must remain provisional until its publication retires"
17089 );
17090 true
17091 }
17092 }
17093
17094 impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
17095 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17096 let completion =
17097 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
17098 assert!(
17099 completion.failed_dispatches.is_empty(),
17100 "the failed delivery must remain provisional until its publication retires"
17101 );
17102 true
17103 }
17104 }
17105
17106 impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
17107 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17108 let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
17109 assert!(
17110 completion.failed_dispatches.is_empty(),
17111 "the failed delivery must remain provisional until its publication retires"
17112 );
17113 true
17114 }
17115 }
17116
17117 impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
17118 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17119 let completion =
17120 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
17121 assert!(
17122 completion.failed_dispatches.is_empty(),
17123 "the failed delivery must remain provisional until its publication retires"
17124 );
17125 true
17126 }
17127 }
17128
17129 struct ReentrantCurrentThreadTaskDriver {
17130 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17131 registration: Mutex<Option<CurrentThreadTaskDriverId>>,
17132 }
17133
17134 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
17135 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17136 true
17137 }
17138
17139 fn is_live(&self) -> bool {
17140 let registration = *self.registration.lock().unwrap();
17141 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
17142 registry.unregister(id);
17143 }
17144 false
17145 }
17146 }
17147
17148 struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
17149 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17150 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17151 }
17152
17153 impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
17154 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17155 true
17156 }
17157
17158 fn is_live(&self) -> bool {
17159 if let Some(registry) = self.registry.upgrade() {
17160 for registration in self.registrations.lock().unwrap().iter().copied() {
17161 let completion = registry.unregister(registration);
17162 assert!(completion.failed_dispatches.is_empty());
17163 assert!(!completion.redispatch);
17164 }
17165 }
17166 false
17167 }
17168 }
17169
17170 struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17171 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17172 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17173 drops: Arc<AtomicUsize>,
17174 }
17175
17176 impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17177 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17178 if let Some(registry) = self.registry.upgrade() {
17179 for registration in self.registrations.lock().unwrap().iter().copied() {
17180 let completion = registry.unregister(registration);
17181 assert!(completion.failed_dispatches.is_empty());
17182 assert!(!completion.redispatch);
17183 }
17184 }
17185 true
17186 }
17187 }
17188
17189 impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17190 fn drop(&mut self) {
17191 self.drops.fetch_add(1, Ordering::SeqCst);
17192 panic!("intentional reentrant CurrentThread task-driver destructor panic");
17193 }
17194 }
17195
17196 struct PanickingDropCurrentThreadTaskDriver {
17197 drops: Arc<AtomicUsize>,
17198 }
17199
17200 impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
17201 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17202 true
17203 }
17204 }
17205
17206 impl Drop for PanickingDropCurrentThreadTaskDriver {
17207 fn drop(&mut self) {
17208 self.drops.fetch_add(1, Ordering::SeqCst);
17209 panic!("intentional CurrentThread task-driver destructor panic");
17210 }
17211 }
17212
17213 struct ReentrantCurrentThreadTaskDriverDrop {
17214 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17215 fallback: CurrentThreadTaskDriverId,
17216 }
17217
17218 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
17219 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17220 true
17221 }
17222 }
17223
17224 impl Drop for ReentrantCurrentThreadTaskDriverDrop {
17225 fn drop(&mut self) {
17226 if let Some(registry) = self.registry.upgrade() {
17227 registry.unregister(self.fallback);
17228 }
17229 }
17230 }
17231
17232 impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
17233 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17234 assert!(
17235 !matches!(
17236 self.behavior,
17237 TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
17238 ),
17239 "intentional task-driver dispatch panic"
17240 );
17241 self.dispatched.store(true, Ordering::SeqCst);
17242 true
17243 }
17244
17245 fn is_live(&self) -> bool {
17246 assert!(
17247 !matches!(
17248 self.behavior,
17249 TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
17250 ),
17251 "intentional task-driver liveness panic"
17252 );
17253 true
17254 }
17255
17256 fn on_swept(&self) {
17257 assert!(
17258 matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
17259 "intentional task-driver sweep panic"
17260 );
17261 }
17262 }
17263
17264 #[test]
17265 fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
17266 let registry = CurrentThreadTaskDriverRegistry::default();
17267 let fallback = Arc::new(TestCurrentThreadTaskDriver {
17268 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
17269 dispatched: AtomicBool::new(false),
17270 });
17271 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
17272 registry.register(Arc::new(TestCurrentThreadTaskDriver {
17273 behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
17274 dispatched: AtomicBool::new(false),
17275 }));
17276 registry.register(Arc::new(TestCurrentThreadTaskDriver {
17277 behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
17278 dispatched: AtomicBool::new(false),
17279 }));
17280
17281 assert_eq!(
17282 registry.dispatch(37),
17283 CurrentThreadHostDispatchResult::Accepted,
17284 "a panicking newest host must fall back to a live host"
17285 );
17286 assert!(fallback.dispatched.load(Ordering::SeqCst));
17287 }
17288
17289 #[test]
17290 fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
17291 let registry = CurrentThreadTaskDriverRegistry::default();
17292 let first = Arc::new(ManualCurrentThreadTaskDriver::default());
17293 let second = Arc::new(ManualCurrentThreadTaskDriver::default());
17294 registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
17295 registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
17296 registry
17297 .next_delivery
17298 .store(u64::MAX - 1, Ordering::Relaxed);
17299
17300 let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
17301 assert!(
17302 publication.is_err(),
17303 "delivery-id exhaustion on the second host must unwind the publication"
17304 );
17305 {
17306 let state = registry
17307 .state
17308 .lock()
17309 .unwrap_or_else(std::sync::PoisonError::into_inner);
17310 assert!(
17311 !state.dispatches.contains_key(&38),
17312 "unwind cleanup must release the exact registry publication barrier"
17313 );
17314 assert!(
17315 state
17316 .entries
17317 .values()
17318 .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
17319 "failed transactional reservation must leave every physical host slot clean"
17320 );
17321 }
17322 assert!(first.deliveries().is_empty());
17323 assert!(second.deliveries().is_empty());
17324
17325 registry.next_delivery.store(0, Ordering::Relaxed);
17326 assert_eq!(
17327 registry.dispatch(38),
17328 CurrentThreadHostDispatchResult::Accepted
17329 );
17330 let first_delivery = first.deliveries()[0];
17331 let second_delivery = second.deliveries()[0];
17332 assert_eq!(
17333 registry.claim_delivery(first_delivery.capability()),
17334 Some(38)
17335 );
17336 assert_eq!(
17337 registry.claim_delivery(second_delivery.capability()),
17338 Some(38)
17339 );
17340 registry.mark_serviced(38);
17341 assert!(
17342 registry
17343 .finish_delivery(first_delivery, false)
17344 .failed_dispatches
17345 .is_empty()
17346 );
17347 assert!(
17348 registry
17349 .finish_delivery(second_delivery, false)
17350 .failed_dispatches
17351 .is_empty()
17352 );
17353
17354 assert_eq!(
17355 registry.dispatch(39),
17356 CurrentThreadHostDispatchResult::Accepted
17357 );
17358 let first_later = first.deliveries()[1];
17359 let second_later = second.deliveries()[1];
17360 assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
17361 assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
17362 registry.mark_serviced(39);
17363 assert!(
17364 registry
17365 .finish_delivery(first_later, false)
17366 .failed_dispatches
17367 .is_empty()
17368 );
17369 assert!(
17370 registry
17371 .finish_delivery(second_later, false)
17372 .failed_dispatches
17373 .is_empty()
17374 );
17375 assert!(
17376 registry
17377 .state
17378 .lock()
17379 .unwrap_or_else(std::sync::PoisonError::into_inner)
17380 .dispatches
17381 .is_empty()
17382 );
17383 let state = registry
17384 .state
17385 .lock()
17386 .unwrap_or_else(std::sync::PoisonError::into_inner);
17387 assert!(
17388 state
17389 .entries
17390 .values()
17391 .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
17392 );
17393 }
17394
17395 #[test]
17396 fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
17397 let registry = CurrentThreadTaskDriverRegistry::default();
17398 let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
17399 let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
17400 let dispatch = 40;
17401
17402 assert_eq!(
17403 registry.dispatch(dispatch),
17404 CurrentThreadHostDispatchResult::Accepted
17405 );
17406 let failed_delivery = failed.deliveries()[0];
17407 let failure = current_thread_dispatch_failure(dispatch, 0);
17408 assert_eq!(
17409 registry
17410 .finish_delivery(failed_delivery, true)
17411 .failed_dispatches,
17412 vec![failure]
17413 );
17414 registry.unregister(failed_id);
17415
17416 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
17417 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
17418 assert!(
17419 registry.rearm_failed_publication(failure),
17420 "a closed unreferenced dispatch must reopen for its armed publication owner"
17421 );
17422 assert_eq!(
17423 registry.dispatch(dispatch),
17424 CurrentThreadHostDispatchResult::Accepted
17425 );
17426 let replacement_delivery = replacement.deliveries()[0];
17427 assert_eq!(replacement_delivery.dispatch, dispatch);
17428 assert_eq!(
17429 registry.claim_delivery(replacement_delivery.capability()),
17430 Some(dispatch)
17431 );
17432 registry.mark_serviced(dispatch);
17433 assert!(
17434 registry
17435 .finish_delivery(replacement_delivery, false)
17436 .failed_dispatches
17437 .is_empty()
17438 );
17439 assert!(registry.state.lock().unwrap().dispatches.is_empty());
17440 }
17441
17442 #[test]
17443 fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
17444 struct RacingTaskDriver {
17445 executor: Arc<CurrentThreadExecutor>,
17446 drives: bool,
17447 dispatches: Mutex<Vec<u64>>,
17448 }
17449
17450 impl CurrentThreadTaskDriver for RacingTaskDriver {
17451 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17452 self.dispatches.lock().unwrap().push(delivery.dispatch);
17453 if self.drives {
17454 self
17455 .executor
17456 .drive_host_turn_for_dispatch(delivery.dispatch);
17457 }
17458 true
17459 }
17460 }
17461
17462 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17463 Arc::new(RuntimeMetrics::default()),
17464 accept_current_thread_host_dispatch,
17465 ));
17466 let polls = Arc::new(AtomicUsize::new(0));
17467 let task_polls = Arc::clone(&polls);
17468 let scheduler = Arc::clone(&executor);
17469 let (runnable, task) = async_task::spawn(
17470 async move {
17471 task_polls.fetch_add(1, Ordering::SeqCst);
17472 },
17473 move |runnable| scheduler.schedule(runnable),
17474 );
17475 executor.schedule(runnable);
17476 let dispatch = current_thread_dispatch(&executor);
17477
17478 let registry = CurrentThreadTaskDriverRegistry::default();
17479 let responsive = Arc::new(RacingTaskDriver {
17480 executor: Arc::clone(&executor),
17481 drives: true,
17482 dispatches: Mutex::new(Vec::new()),
17483 });
17484 let starved = Arc::new(RacingTaskDriver {
17485 executor: Arc::clone(&executor),
17486 drives: false,
17487 dispatches: Mutex::new(Vec::new()),
17488 });
17489 registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
17490 registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
17492
17493 assert_eq!(
17494 registry.dispatch(dispatch),
17495 CurrentThreadHostDispatchResult::Accepted
17496 );
17497 assert_eq!(
17498 responsive.dispatches.lock().unwrap().as_slice(),
17499 &[dispatch]
17500 );
17501 assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
17502 assert_eq!(
17503 polls.load(Ordering::SeqCst),
17504 1,
17505 "the responsive older host must win even when the newest host is starved"
17506 );
17507
17508 executor.drive_host_turn_for_dispatch(dispatch);
17511 assert_eq!(polls.load(Ordering::SeqCst), 1);
17512 futures::executor::block_on(task);
17513 }
17514
17515 #[test]
17516 fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
17517 const HOSTS: usize = 1_024;
17518
17519 let registry = CurrentThreadTaskDriverRegistry::default();
17520 let mut ids = Vec::with_capacity(HOSTS);
17521 let mut drivers = Vec::with_capacity(HOSTS);
17522 for _ in 0..HOSTS {
17523 let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
17524 ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
17525 drivers.push(driver);
17526 }
17527
17528 let dispatch = u64::MAX - 300;
17529 assert_eq!(
17530 registry.dispatch(dispatch),
17531 CurrentThreadHostDispatchResult::Accepted
17532 );
17533 let deliveries = drivers
17534 .iter()
17535 .map(|driver| driver.deliveries()[0])
17536 .collect::<Vec<_>>();
17537 {
17538 let state = registry
17539 .state
17540 .lock()
17541 .unwrap_or_else(std::sync::PoisonError::into_inner);
17542 assert_eq!(state.delivery_drivers.len(), HOSTS);
17543 assert_eq!(
17544 state
17545 .dispatches
17546 .get(&dispatch)
17547 .map(|state| state.references),
17548 Some(HOSTS)
17549 );
17550 assert_eq!(
17551 state
17552 .ordered_snapshot()
17553 .iter()
17554 .map(|(id, _)| *id)
17555 .collect::<Vec<_>>(),
17556 ids,
17557 "keyed driver lookup must retain registration order"
17558 );
17559 }
17560
17561 for delivery in &deliveries {
17562 assert_eq!(
17563 registry.claim_delivery(delivery.capability()),
17564 Some(dispatch)
17565 );
17566 }
17567 registry.mark_serviced(dispatch);
17568 for delivery in deliveries {
17569 assert!(
17570 registry
17571 .finish_delivery(delivery, false)
17572 .failed_dispatches
17573 .is_empty()
17574 );
17575 }
17576 {
17577 let state = registry
17578 .state
17579 .lock()
17580 .unwrap_or_else(std::sync::PoisonError::into_inner);
17581 assert!(state.delivery_drivers.is_empty());
17582 assert!(state.dispatches.is_empty());
17583 }
17584
17585 let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
17586 for id in &removed {
17587 let completion = registry.unregister(*id);
17588 assert!(completion.failed_dispatches.is_empty());
17589 assert!(!completion.redispatch);
17590 }
17591 let expected = ids
17592 .iter()
17593 .copied()
17594 .filter(|id| !removed.contains(id))
17595 .collect::<Vec<_>>();
17596 let live = registry.live().live;
17597 assert_eq!(
17598 live
17599 .as_slice()
17600 .iter()
17601 .map(|(id, _)| *id)
17602 .collect::<Vec<_>>(),
17603 expected,
17604 "O(1) removals must preserve the relative order of surviving host registrations"
17605 );
17606 drop(live);
17607
17608 for id in expected {
17609 registry.unregister(id);
17610 }
17611 let state = registry
17612 .state
17613 .lock()
17614 .unwrap_or_else(std::sync::PoisonError::into_inner);
17615 assert!(state.entries.is_empty());
17616 assert!(state.oldest.is_none());
17617 assert!(state.newest.is_none());
17618 }
17619
17620 #[test]
17621 fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
17622 let registry = CurrentThreadTaskDriverRegistry::default();
17623 let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
17624 let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
17625 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
17626 registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
17627 registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
17628 registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
17629
17630 let dispatch = 41;
17631 assert_eq!(
17632 registry.dispatch(dispatch),
17633 CurrentThreadHostDispatchResult::Accepted
17634 );
17635 let failing_delivery = failing.deliveries()[0];
17636 let delayed_delivery = delayed.deliveries()[0];
17637 let healthy_delivery = healthy.deliveries()[0];
17638 assert_eq!(failing_delivery.dispatch, dispatch);
17639 assert_eq!(delayed_delivery.dispatch, dispatch);
17640 assert_eq!(healthy_delivery.dispatch, dispatch);
17641
17642 assert!(
17643 registry
17644 .finish_delivery(failing_delivery, true)
17645 .failed_dispatches
17646 .is_empty(),
17647 "one host failure must not recover while other accepted attempts remain"
17648 );
17649 assert_eq!(
17650 registry.claim_delivery(healthy_delivery.capability()),
17651 Some(dispatch)
17652 );
17653 registry.mark_serviced(dispatch);
17654 assert!(
17655 registry
17656 .finish_delivery(healthy_delivery, false)
17657 .failed_dispatches
17658 .is_empty()
17659 );
17660 assert_eq!(
17661 registry.claim_delivery(delayed_delivery.capability()),
17662 None,
17663 "a delayed losing delivery must become stale after another host services the dispatch"
17664 );
17665 assert!(
17666 registry
17667 .finish_delivery(delayed_delivery, true)
17668 .failed_dispatches
17669 .is_empty(),
17670 "a stale losing failure must not recover or cancel a serviced dispatch"
17671 );
17672 assert!(
17673 registry.state.lock().unwrap().dispatches.is_empty(),
17674 "all per-dispatch acknowledgement state must retire"
17675 );
17676 }
17677
17678 #[test]
17679 fn current_thread_unclaimed_callback_success_is_delivery_failure() {
17680 let registry = CurrentThreadTaskDriverRegistry::default();
17681 let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
17682 registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
17683
17684 let dispatch = 56;
17685 assert_eq!(
17686 registry.dispatch(dispatch),
17687 CurrentThreadHostDispatchResult::Accepted
17688 );
17689 let delivery = no_op.deliveries()[0];
17690 let completion = registry.finish_delivery(delivery, false);
17691 let failure = current_thread_dispatch_failure(dispatch, 0);
17692 assert_eq!(
17693 completion.failed_dispatches,
17694 vec![failure],
17695 "a callback return without an exact capability claim must enter recovery"
17696 );
17697 assert!(
17698 registry
17699 .state
17700 .lock()
17701 .unwrap()
17702 .dispatches
17703 .get(&dispatch)
17704 .is_some_and(CurrentThreadDispatchState::is_cancelling)
17705 );
17706 registry.finish_cancellation(failure);
17707 assert!(registry.state.lock().unwrap().dispatches.is_empty());
17708 }
17709
17710 #[test]
17711 fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
17712 struct FailDuringPublication {
17713 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17714 }
17715
17716 impl CurrentThreadTaskDriver for FailDuringPublication {
17717 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17718 let completion = self
17719 .registry
17720 .upgrade()
17721 .expect("the registry must outlive publication")
17722 .finish_delivery(delivery, true);
17723 assert!(
17724 completion.failed_dispatches.is_empty(),
17725 "failure must remain provisional until the broadcast closes"
17726 );
17727 true
17728 }
17729 }
17730
17731 struct RejectProvisional;
17732
17733 impl CurrentThreadTaskDriver for RejectProvisional {
17734 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17735 false
17736 }
17737 }
17738
17739 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
17740 registry.register(Arc::new(RejectProvisional));
17741 registry.register(Arc::new(FailDuringPublication {
17744 registry: Arc::downgrade(®istry),
17745 }));
17746
17747 let failure = current_thread_dispatch_failure(57, 0);
17748 assert_eq!(
17749 registry.dispatch(57),
17750 CurrentThreadHostDispatchResult::Failed(failure),
17751 "the final provisional rejection must expose the earlier accepted failure"
17752 );
17753 assert!(
17754 registry
17755 .state
17756 .lock()
17757 .unwrap()
17758 .dispatches
17759 .get(&57)
17760 .is_some_and(CurrentThreadDispatchState::is_cancelling)
17761 );
17762 assert_eq!(
17763 registry.dispatch(57),
17764 CurrentThreadHostDispatchResult::Failed(failure),
17765 "a racing replacement publication must preserve terminal failure until cancellation runs"
17766 );
17767 registry.finish_cancellation(failure);
17768 }
17769
17770 #[test]
17771 fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
17772 struct RejectProvisional;
17773
17774 impl CurrentThreadTaskDriver for RejectProvisional {
17775 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17776 false
17777 }
17778 }
17779
17780 let registry = CurrentThreadTaskDriverRegistry::default();
17781 registry.register(Arc::new(RejectProvisional));
17782 registry.register(Arc::new(RejectProvisional));
17783
17784 assert_eq!(
17785 registry.dispatch(58),
17786 CurrentThreadHostDispatchResult::Unavailable
17787 );
17788 assert!(
17789 registry.state.lock().unwrap().dispatches.is_empty(),
17790 "a broadcast with no accepted attempt must not enter failure recovery"
17791 );
17792 }
17793
17794 #[test]
17795 fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
17796 let registry = CurrentThreadTaskDriverRegistry::default();
17797 let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
17798 let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
17799
17800 assert_eq!(
17801 registry.dispatch(1),
17802 CurrentThreadHostDispatchResult::Accepted
17803 );
17804 let first = blocked.deliveries()[0];
17805 for dispatch in 2..=256 {
17806 assert_eq!(
17807 registry.dispatch(dispatch),
17808 CurrentThreadHostDispatchResult::Accepted
17809 );
17810 assert_eq!(
17811 blocked.deliveries().len(),
17812 1,
17813 "a blocked host must never receive a second queued or in-flight callback"
17814 );
17815 }
17816 {
17817 let state = registry.state.lock().unwrap();
17818 let entry = state
17819 .entries
17820 .get(&blocked_id)
17821 .expect("the blocked host must remain registered");
17822 assert_eq!(entry.pending, Some(256));
17823 assert_eq!(
17824 state.dispatches.len(),
17825 2,
17826 "only the in-flight and latest pending dispatches may retain registry state"
17827 );
17828 }
17829
17830 assert_eq!(registry.claim_delivery(first.capability()), Some(1));
17831 registry.mark_serviced(1);
17832 let completion = registry.finish_delivery(first, false);
17833 assert!(completion.failed_dispatches.is_empty());
17834 assert!(completion.redispatch);
17835 assert_eq!(
17836 registry.dispatch(256),
17837 CurrentThreadHostDispatchResult::Accepted
17838 );
17839 let deliveries = blocked.deliveries();
17840 assert_eq!(deliveries.len(), 2);
17841 assert_eq!(
17842 deliveries[1].dispatch, 256,
17843 "completion must schedule exactly the latest coalesced capability"
17844 );
17845 assert_eq!(registry.claim_delivery(first.capability()), None);
17846 assert_eq!(
17847 registry.claim_delivery(deliveries[1].capability()),
17848 Some(256)
17849 );
17850 registry.mark_serviced(256);
17851 assert!(
17852 registry
17853 .finish_delivery(deliveries[1], false)
17854 .failed_dispatches
17855 .is_empty()
17856 );
17857 assert!(registry.state.lock().unwrap().dispatches.is_empty());
17858 }
17859
17860 #[test]
17861 fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
17862 struct RejectAfterFirst {
17863 calls: AtomicUsize,
17864 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17865 }
17866
17867 impl CurrentThreadTaskDriver for RejectAfterFirst {
17868 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17869 self.deliveries.lock().unwrap().push(delivery);
17870 self.calls.fetch_add(1, Ordering::SeqCst) == 0
17871 }
17872 }
17873
17874 let registry = CurrentThreadTaskDriverRegistry::default();
17875 let host = Arc::new(RejectAfterFirst {
17876 calls: AtomicUsize::new(0),
17877 deliveries: Mutex::new(Vec::new()),
17878 });
17879 registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
17880 assert_eq!(
17881 registry.dispatch(11),
17882 CurrentThreadHostDispatchResult::Accepted
17883 );
17884 let first = host.deliveries.lock().unwrap()[0];
17885 assert_eq!(
17886 registry.dispatch(12),
17887 CurrentThreadHostDispatchResult::Accepted
17888 );
17889
17890 assert_eq!(registry.claim_delivery(first.capability()), Some(11));
17891 registry.mark_serviced(11);
17892 let completion = registry.finish_delivery(first, false);
17893 assert!(completion.redispatch);
17894 let failure = current_thread_dispatch_failure(12, 0);
17895 assert_eq!(
17896 registry.dispatch(12),
17897 CurrentThreadHostDispatchResult::Failed(failure),
17898 "an accepted coalesced capability must report failure if its eventual host queue rejects it"
17899 );
17900 assert!(
17901 registry
17902 .state
17903 .lock()
17904 .unwrap()
17905 .dispatches
17906 .get(&12)
17907 .is_some_and(CurrentThreadDispatchState::is_cancelling)
17908 );
17909 registry.finish_cancellation(failure);
17910 assert!(registry.state.lock().unwrap().dispatches.is_empty());
17911 }
17912
17913 #[test]
17914 fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
17915 let registry = CurrentThreadTaskDriverRegistry::default();
17916 let old = Arc::new(ManualCurrentThreadTaskDriver::default());
17917 let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
17918 assert_eq!(
17919 registry.dispatch(73),
17920 CurrentThreadHostDispatchResult::Accepted
17921 );
17922 let old_delivery = old.deliveries()[0];
17923
17924 let completion = registry.unregister(old_id);
17925 let failure = current_thread_dispatch_failure(73, 0);
17926 assert_eq!(
17927 completion.failed_dispatches,
17928 vec![failure],
17929 "unregistering the final accepting host must retire its exact dispatch"
17930 );
17931 registry.finish_cancellation(failure);
17932 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
17933 let replacement_id =
17934 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
17935 assert_ne!(replacement_id, old_id);
17936 assert_eq!(
17937 registry.dispatch(74),
17938 CurrentThreadHostDispatchResult::Accepted
17939 );
17940 let replacement_delivery = replacement.deliveries()[0];
17941 assert_ne!(replacement_delivery.capability(), old_delivery.capability());
17942
17943 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
17944 assert!(
17945 registry
17946 .finish_delivery(old_delivery, true)
17947 .failed_dispatches
17948 .is_empty()
17949 );
17950 assert!(
17951 registry
17952 .fail_delivery(old_delivery.capability())
17953 .failed_dispatches
17954 .is_empty()
17955 );
17956 assert_eq!(
17957 registry.claim_delivery(replacement_delivery.capability()),
17958 Some(74)
17959 );
17960 registry.mark_serviced(74);
17961 assert!(
17962 registry
17963 .finish_delivery(replacement_delivery, false)
17964 .failed_dispatches
17965 .is_empty()
17966 );
17967 assert!(registry.state.lock().unwrap().dispatches.is_empty());
17968 }
17969
17970 #[test]
17971 fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
17972 struct QueuePendingThenReject {
17973 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17974 }
17975
17976 impl CurrentThreadTaskDriver for QueuePendingThenReject {
17977 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17978 assert_eq!(delivery.dispatch, 81);
17979 let completion = self
17980 .registry
17981 .upgrade()
17982 .expect("the registry must outlive publication")
17983 .dispatch_with_completion(82);
17984 assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
17985 assert!(completion.failed_dispatches.is_empty());
17986 false
17987 }
17988 }
17989
17990 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
17991 registry.register(Arc::new(QueuePendingThenReject {
17992 registry: Arc::downgrade(®istry),
17993 }));
17994
17995 let completion = registry.dispatch_with_completion(81);
17996 assert_eq!(
17997 completion.result,
17998 CurrentThreadHostDispatchResult::Unavailable
17999 );
18000 let failure = current_thread_dispatch_failure(82, 0);
18001 assert_eq!(
18002 completion.failed_dispatches,
18003 vec![failure],
18004 "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18005 );
18006 assert!(
18007 registry
18008 .state
18009 .lock()
18010 .unwrap()
18011 .dispatches
18012 .get(&82)
18013 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18014 );
18015 registry.finish_cancellation(failure);
18016 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18017 }
18018
18019 #[test]
18020 fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
18021 let registry = CurrentThreadTaskDriverRegistry::default();
18022 let host = Arc::new(ManualCurrentThreadTaskDriver::default());
18023 let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18024 assert_eq!(
18025 registry.dispatch(91),
18026 CurrentThreadHostDispatchResult::Accepted
18027 );
18028 let old_delivery = host.deliveries()[0];
18029 assert_eq!(
18030 registry.dispatch(92),
18031 CurrentThreadHostDispatchResult::Accepted
18032 );
18033
18034 registry.retire_for_shutdown();
18035 {
18036 let state = registry.state.lock().unwrap();
18037 assert!(state.dispatches.is_empty());
18038 let entry = state
18039 .entries
18040 .get(&host_id)
18041 .expect("the host must remain registered");
18042 assert_eq!(entry.pending, None);
18043 assert!(
18044 entry.delivery.is_some(),
18045 "the physical callback remains single-flight until it actually retires"
18046 );
18047 }
18048 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18049
18050 assert_eq!(
18051 registry.dispatch(93),
18052 CurrentThreadHostDispatchResult::Accepted
18053 );
18054 assert_eq!(host.deliveries().len(), 1);
18055 let completion = registry.finish_delivery(old_delivery, true);
18056 assert!(
18057 completion.failed_dispatches.is_empty(),
18058 "a retired-generation failure must not cancel restarted work"
18059 );
18060 assert!(completion.redispatch);
18061 assert_eq!(
18062 registry.dispatch(93),
18063 CurrentThreadHostDispatchResult::Accepted
18064 );
18065 let restarted_delivery = host.deliveries()[1];
18066 assert_eq!(restarted_delivery.dispatch, 93);
18067 assert_eq!(
18068 registry.claim_delivery(restarted_delivery.capability()),
18069 Some(93)
18070 );
18071 registry.mark_serviced(93);
18072 assert!(
18073 registry
18074 .finish_delivery(restarted_delivery, false)
18075 .failed_dispatches
18076 .is_empty()
18077 );
18078
18079 registry.unregister(host_id);
18080 let state = registry.state.lock().unwrap();
18081 assert!(state.entries.is_empty());
18082 assert!(state.dispatches.is_empty());
18083 }
18084
18085 #[test]
18086 fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
18087 use std::{sync::mpsc, time::Duration};
18088
18089 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18090 let fallback = Arc::new(TestCurrentThreadTaskDriver {
18091 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18092 dispatched: AtomicBool::new(false),
18093 });
18094 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18095 let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
18096 registry: Arc::downgrade(®istry),
18097 registration: Mutex::new(None),
18098 });
18099 let reentrant_id =
18100 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18101 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
18102
18103 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
18104 let selection_registry = Arc::clone(®istry);
18105 let selection_thread = std::thread::spawn(move || {
18106 selection_tx
18107 .send(selection_registry.current().map(|(id, _)| id))
18108 .unwrap();
18109 });
18110 assert_eq!(
18111 selection_rx
18112 .recv_timeout(Duration::from_secs(10))
18113 .expect("is_live re-entry deadlocked"),
18114 Some(fallback_id)
18115 );
18116 selection_thread.join().unwrap();
18117
18118 let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
18119 Arc::new(ReentrantCurrentThreadTaskDriverDrop {
18120 registry: Arc::downgrade(®istry),
18121 fallback: fallback_id,
18122 });
18123 let drop_id = registry.register(Arc::clone(&drop_driver));
18124 drop(drop_driver);
18125
18126 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
18127 let drop_registry = Arc::clone(®istry);
18128 let drop_thread = std::thread::spawn(move || {
18129 drop_registry.unregister(drop_id);
18130 drop_tx.send(()).unwrap();
18131 });
18132 drop_rx
18133 .recv_timeout(Duration::from_secs(10))
18134 .expect("driver destructor re-entry deadlocked");
18135 drop_thread.join().unwrap();
18136 assert!(
18137 registry.current().is_none(),
18138 "the destructor must unregister the fallback"
18139 );
18140 }
18141
18142 #[test]
18143 fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
18144 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
18145
18146 if std::env::var_os(CHILD_ENV).is_some() {
18147 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18148 let fallback = Arc::new(TestCurrentThreadTaskDriver {
18149 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18150 dispatched: AtomicBool::new(false),
18151 });
18152 let fallback_id =
18153 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18154 let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
18155 registry: Arc::downgrade(®istry),
18156 registrations: Mutex::new(Vec::new()),
18157 });
18158 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18159
18160 let first_drops = Arc::new(AtomicUsize::new(0));
18161 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18162 drops: Arc::clone(&first_drops),
18163 });
18164 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18165 drop(first);
18166
18167 let second_drops = Arc::new(AtomicUsize::new(0));
18168 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18169 drops: Arc::clone(&second_drops),
18170 });
18171 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18172 drop(second);
18173 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
18174
18175 assert_eq!(
18176 registry.current().map(|(id, _)| id),
18177 Some(fallback_id),
18178 "selection must retry after reentrant removals and preserve the healthy fallback"
18179 );
18180 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18181 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18182 let state = registry
18183 .state
18184 .lock()
18185 .unwrap_or_else(std::sync::PoisonError::into_inner);
18186 assert_eq!(state.entries.len(), 1);
18187 assert!(state.entries.contains_key(&fallback_id));
18188 assert!(state.dispatches.is_empty());
18189 return;
18190 }
18191
18192 let output = std::process::Command::new(std::env::current_exe().unwrap())
18193 .arg("--exact")
18194 .arg(
18195 "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
18196 )
18197 .arg("--nocapture")
18198 .env(CHILD_ENV, "1")
18199 .output()
18200 .expect("the hostile task-driver snapshot subprocess test must start");
18201 assert!(
18202 output.status.success(),
18203 "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18204 output.status.code(),
18205 String::from_utf8_lossy(&output.stdout),
18206 String::from_utf8_lossy(&output.stderr)
18207 );
18208 }
18209
18210 #[test]
18211 fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
18212 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
18213
18214 if std::env::var_os(CHILD_ENV).is_some() {
18215 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18216
18217 let first_drops = Arc::new(AtomicUsize::new(0));
18218 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18219 drops: Arc::clone(&first_drops),
18220 });
18221 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18222 drop(first);
18223
18224 let second_drops = Arc::new(AtomicUsize::new(0));
18225 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18226 drops: Arc::clone(&second_drops),
18227 });
18228 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18229 drop(second);
18230
18231 let reentrant_drops = Arc::new(AtomicUsize::new(0));
18232 let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18233 registry: Arc::downgrade(®istry),
18234 registrations: Mutex::new(Vec::new()),
18235 drops: Arc::clone(&reentrant_drops),
18236 });
18237 let reentrant_id =
18238 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18239 *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
18240 drop(reentrant);
18241
18242 let dispatch = 59;
18243 let failure = current_thread_dispatch_failure(dispatch, 0);
18244 assert_eq!(
18245 registry.dispatch(dispatch),
18246 CurrentThreadHostDispatchResult::Failed(failure),
18247 "reentrant removal of every provisional host must report accepted delivery failure"
18248 );
18249 assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
18250 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18251 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18252 registry.finish_cancellation(failure);
18253 let state = registry
18254 .state
18255 .lock()
18256 .unwrap_or_else(std::sync::PoisonError::into_inner);
18257 assert!(state.entries.is_empty());
18258 assert!(state.dispatches.is_empty());
18259 return;
18260 }
18261
18262 let output = std::process::Command::new(std::env::current_exe().unwrap())
18263 .arg("--exact")
18264 .arg(
18265 "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
18266 )
18267 .arg("--nocapture")
18268 .env(CHILD_ENV, "1")
18269 .output()
18270 .expect("the hostile task-driver delivery subprocess test must start");
18271 assert!(
18272 output.status.success(),
18273 "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18274 output.status.code(),
18275 String::from_utf8_lossy(&output.stdout),
18276 String::from_utf8_lossy(&output.stderr)
18277 );
18278 }
18279
18280 #[test]
18281 fn dropping_join_handle_detaches_task_like_tokio() {
18282 let completed = Arc::new(AtomicBool::new(false));
18283 let completed_task = Arc::clone(&completed);
18284 let (runnable, task) = async_task::spawn(
18285 async move {
18286 completed_task.store(true, Ordering::SeqCst);
18287 Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
18288 },
18289 |_| {},
18290 );
18291 let handle = JoinHandle(JoinHandleInner::Task {
18292 task: task.fallible(),
18293 awaiter: None,
18294 dependency: Arc::new(TaskDependency::default()),
18295 });
18296
18297 drop(handle);
18298 runnable.run();
18299 assert!(completed.load(Ordering::SeqCst));
18300 }
18301
18302 #[cfg(not(target_family = "wasm"))]
18303 #[test]
18304 fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
18305 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
18306
18307 if std::env::var_os(CHILD_ENV).is_some() {
18308 let wakes = Arc::new(AtomicUsize::new(0));
18309 let drops = Arc::new(AtomicUsize::new(0));
18310 let (runnable, task) = async_task::spawn(
18311 async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
18312 |_| {},
18313 );
18314 let mut handle = JoinHandle(JoinHandleInner::Task {
18315 task: task.fallible(),
18316 awaiter: None,
18317 dependency: Arc::new(TaskDependency::default()),
18318 });
18319 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
18320 {
18321 let mut cx = Context::from_waker(&waker);
18322 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18323 }
18324 drop(waker);
18325
18326 runnable.run();
18327
18328 let waker = futures::task::noop_waker();
18329 let mut cx = Context::from_waker(&waker);
18330 assert!(matches!(
18331 Pin::new(&mut handle).poll(&mut cx),
18332 Poll::Ready(Ok(()))
18333 ));
18334 assert_eq!(
18335 drops.load(Ordering::SeqCst),
18336 1,
18337 "a completed handle must not retain the caller's awaiter"
18338 );
18339 drop(handle);
18340 assert!(wakes.load(Ordering::SeqCst) >= 1);
18341 assert_eq!(drops.load(Ordering::SeqCst), 1);
18342 return;
18343 }
18344
18345 let output = std::process::Command::new(std::env::current_exe().unwrap())
18346 .arg("--exact")
18347 .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
18348 .arg("--nocapture")
18349 .env(CHILD_ENV, "1")
18350 .output()
18351 .expect("the JoinHandle completion awaiter subprocess must start");
18352 assert!(
18353 output.status.success(),
18354 "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
18355 output.status.code(),
18356 String::from_utf8_lossy(&output.stdout),
18357 String::from_utf8_lossy(&output.stderr)
18358 );
18359 }
18360
18361 #[cfg(not(target_family = "wasm"))]
18362 #[test]
18363 fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
18364 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
18365
18366 if std::env::var_os(CHILD_ENV).is_some() {
18367 let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
18368 let wakes = Arc::new(AtomicUsize::new(0));
18369 let drops = Arc::new(AtomicUsize::new(0));
18370 let mut handle = controller.spawn(std::future::pending::<()>());
18371 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
18372 {
18373 let mut cx = Context::from_waker(&waker);
18374 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18375 }
18376 drop(waker);
18377
18378 controller
18379 .shutdown()
18380 .expect("shutdown must survive the hostile awaiter");
18381 drop(handle);
18382 assert!(wakes.load(Ordering::SeqCst) >= 1);
18383 assert_eq!(drops.load(Ordering::SeqCst), 1);
18384 return;
18385 }
18386
18387 let output = std::process::Command::new(std::env::current_exe().unwrap())
18388 .arg("--exact")
18389 .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
18390 .arg("--nocapture")
18391 .env(CHILD_ENV, "1")
18392 .output()
18393 .expect("the JoinHandle shutdown awaiter subprocess must start");
18394 assert!(
18395 output.status.success(),
18396 "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
18397 output.status.code(),
18398 String::from_utf8_lossy(&output.stdout),
18399 String::from_utf8_lossy(&output.stderr)
18400 );
18401 }
18402
18403 #[cfg(not(target_family = "wasm"))]
18404 #[test]
18405 fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
18406 let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
18407 let mut handle = controller.spawn(std::future::pending::<()>());
18408 let old_generation = controller.backend().generation();
18409 let state = Arc::new(JoinAwaiterWakerState {
18410 events: Mutex::new(Vec::new()),
18411 });
18412 let waker = {
18413 let _generation = RuntimeGenerationGuard::enter(old_generation);
18414 join_awaiter_waker(Arc::clone(&state))
18415 };
18416 {
18417 let _generation = RuntimeGenerationGuard::enter(old_generation);
18418 let mut cx = Context::from_waker(&waker);
18419 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18420 }
18421 {
18422 let _generation = RuntimeGenerationGuard::enter(old_generation);
18423 drop(waker);
18424 }
18425
18426 controller
18427 .shutdown()
18428 .expect("the first generation must stop");
18429 controller.start().expect("the runtime must restart");
18430 let new_generation = controller.backend().generation();
18431 assert_ne!(new_generation, old_generation);
18432 state.clear();
18433
18434 let replacement = futures::task::noop_waker();
18435 {
18436 let _generation = RuntimeGenerationGuard::enter(new_generation);
18437 let mut cx = Context::from_waker(&replacement);
18438 assert!(matches!(
18439 Pin::new(&mut handle).poll(&mut cx),
18440 Poll::Ready(Err(_))
18441 ));
18442 }
18443
18444 let events = state.snapshot();
18445 assert!(
18446 !events.is_empty(),
18447 "completion must retire the registered awaiter waker"
18448 );
18449 assert!(
18450 events.iter().all(|(kind, generation)| {
18451 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
18452 }),
18453 "old-generation awaiter retirement escaped its generation: {events:?}"
18454 );
18455 controller
18456 .shutdown()
18457 .expect("the replacement generation must stop");
18458 }
18459
18460 #[cfg(not(target_family = "wasm"))]
18461 #[test]
18462 fn dropping_join_handle_retains_awaiter_generation_after_restart() {
18463 let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
18464 let mut handle = controller.spawn(std::future::pending::<()>());
18465 let old_generation = controller.backend().generation();
18466 let state = Arc::new(JoinAwaiterWakerState {
18467 events: Mutex::new(Vec::new()),
18468 });
18469 let waker = {
18470 let _generation = RuntimeGenerationGuard::enter(old_generation);
18471 join_awaiter_waker(Arc::clone(&state))
18472 };
18473 {
18474 let _generation = RuntimeGenerationGuard::enter(old_generation);
18475 let mut cx = Context::from_waker(&waker);
18476 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18477 }
18478 {
18479 let _generation = RuntimeGenerationGuard::enter(old_generation);
18480 drop(waker);
18481 }
18482
18483 controller
18484 .shutdown()
18485 .expect("the first generation must stop");
18486 controller.start().expect("the runtime must restart");
18487 let new_generation = controller.backend().generation();
18488 assert_ne!(new_generation, old_generation);
18489 state.clear();
18490
18491 {
18492 let _generation = RuntimeGenerationGuard::enter(new_generation);
18493 drop(handle);
18494 }
18495
18496 let events = state.snapshot();
18497 assert!(
18498 !events.is_empty(),
18499 "handle drop must retire the registered awaiter waker"
18500 );
18501 assert!(
18502 events.iter().all(|(kind, generation)| {
18503 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
18504 }),
18505 "old-generation awaiter destruction escaped its generation: {events:?}"
18506 );
18507 controller
18508 .shutdown()
18509 .expect("the replacement generation must stop");
18510 }
18511
18512 #[cfg(not(target_family = "wasm"))]
18513 #[test]
18514 fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
18515 use std::{sync::mpsc, time::Duration};
18516
18517 let options = RuntimeOptions {
18518 flavor: RuntimeFlavor::MultiThread,
18519 worker_threads: 2,
18520 max_blocking_tasks: 1,
18521 thread_name_prefix: "worker-classification".to_string(),
18522 park_deadline: None,
18523 drain_linger: DEFAULT_DRAIN_LINGER,
18524 };
18525 let executor =
18526 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18527 let id = executor.id;
18528 let (tx, rx) = mpsc::sync_channel(1);
18529
18530 executor.pool.spawn(move || {
18531 tx.send((
18532 ON_POOL_WORKER.with(std::cell::Cell::get),
18533 IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
18534 ))
18535 .unwrap();
18536 });
18537
18538 let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
18539 assert_eq!(
18540 worker,
18541 Some(id),
18542 "nested Rayon work must use cooperative block_on"
18543 );
18544 assert_eq!(
18545 driver, None,
18546 "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
18547 );
18548 }
18549
18550 #[cfg(not(target_family = "wasm"))]
18551 #[test]
18552 fn multi_thread_block_on_does_not_park_pool_worker() {
18553 use std::sync::{Barrier, mpsc};
18558 use std::time::Duration;
18559
18560 let (done_tx, done_rx) = mpsc::channel();
18561 let runner = std::thread::spawn(move || {
18562 let metrics = Arc::new(RuntimeMetrics::default());
18563 let options = RuntimeOptions {
18564 flavor: RuntimeFlavor::MultiThread,
18565 worker_threads: 2,
18566 max_blocking_tasks: 2,
18567 thread_name_prefix: "rd1-test".to_string(),
18568 park_deadline: None,
18569 drain_linger: DEFAULT_DRAIN_LINGER,
18570 };
18571 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18572
18573 let task_count = 2usize;
18574 let barrier = Arc::new(Barrier::new(task_count));
18577
18578 let mut tasks = Vec::new();
18579 for _ in 0..task_count {
18580 let exec = Arc::clone(&executor);
18581 let barrier = Arc::clone(&barrier);
18582 let body = async move {
18583 barrier.wait();
18584 let inner_exec = Arc::clone(&exec);
18585 let mut inner = std::pin::pin!(async move {
18586 let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
18587 assert_eq!(value, 7);
18588 });
18589 exec.block_on(inner.as_mut());
18591 };
18592 let scheduler = Arc::clone(&executor);
18593 let (runnable, task) =
18594 async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
18595 executor.schedule(runnable);
18596 tasks.push(task);
18597 }
18598
18599 for task in tasks {
18600 futures::executor::block_on(task);
18601 }
18602 let _ = done_tx.send(());
18603 });
18604
18605 match done_rx.recv_timeout(Duration::from_secs(10)) {
18606 Ok(()) => runner.join().unwrap(),
18607 Err(error) => panic!(
18608 "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
18609 ),
18610 }
18611 }
18612
18613 #[cfg(not(target_family = "wasm"))]
18614 #[test]
18615 fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
18616 use std::sync::mpsc;
18635
18636 for round in 0..3 {
18637 let metrics = Arc::new(RuntimeMetrics::default());
18638 let options = RuntimeOptions {
18639 flavor: RuntimeFlavor::MultiThread,
18640 worker_threads: 2,
18641 max_blocking_tasks: 1,
18642 thread_name_prefix: format!("linger-starve-{round}"),
18643 park_deadline: None,
18644 drain_linger: DEFAULT_DRAIN_LINGER,
18645 };
18646 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18647
18648 let r_done = Arc::new(AtomicBool::new(false));
18649 let give_up = Arc::new(AtomicBool::new(false));
18650 let (done_tx, done_rx) = mpsc::channel::<Duration>();
18651
18652 let t1_executor = Arc::clone(&executor);
18658 let t1_started = Arc::new(AtomicBool::new(false));
18659 let t1_started_setter = Arc::clone(&t1_started);
18660 let t1_body = async move {
18661 t1_started_setter.store(true, Ordering::SeqCst);
18662 let deadline = Instant::now() + Duration::from_secs(10);
18663 while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
18664 if Instant::now() >= deadline {
18665 return;
18667 }
18668 std::hint::spin_loop();
18669 }
18670 };
18671 let t1_scheduler = Arc::clone(&executor);
18672 let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
18673 executor.schedule(t1_runnable);
18674 wait_until("the worker-pinning task to start", || {
18675 t1_started.load(Ordering::SeqCst)
18676 });
18677
18678 let blocking_executor = Arc::clone(&executor);
18680 let r_done_setter = Arc::clone(&r_done);
18681 let started = Instant::now();
18682 let handle = executor.schedule_blocking(move || {
18683 let (r_tx, r_rx) = oneshot::channel::<()>();
18684 blocking_executor.pool.spawn(move || {
18685 r_done_setter.store(true, Ordering::SeqCst);
18686 let _ = r_tx.send(());
18687 });
18688 let mut wait_for_r = std::pin::pin!(async move {
18689 let _ = r_rx.await;
18690 });
18691 blocking_executor.block_on(wait_for_r.as_mut());
18692 let _ = done_tx.send(started.elapsed());
18693 });
18694
18695 let arm_deadline = Instant::now() + Duration::from_secs(15);
18699 while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
18700 && !r_done.load(Ordering::SeqCst)
18701 {
18702 assert!(
18703 Instant::now() < arm_deadline,
18704 "timed out arming the linger-starvation interleaving"
18705 );
18706 std::thread::yield_now();
18707 }
18708
18709 let producer_executor = Arc::clone(&executor);
18712 let producer_r_done = Arc::clone(&r_done);
18713 let producer_give_up = Arc::clone(&give_up);
18714 let producer = std::thread::spawn(move || {
18715 let cadence = Duration::from_micros(200);
18716 let mut next_tick = Instant::now();
18717 while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
18718 let scheduler = Arc::clone(&producer_executor);
18719 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
18720 producer_executor.schedule(runnable);
18721 task.detach();
18722 next_tick += cadence;
18723 while Instant::now() < next_tick {
18724 std::hint::spin_loop();
18725 }
18726 }
18727 });
18728
18729 let outcome = done_rx.recv_timeout(Duration::from_secs(5));
18730 give_up.store(true, Ordering::SeqCst);
18731 producer.join().unwrap();
18732 match outcome {
18733 Ok(elapsed) => {
18734 futures::executor::block_on(handle).expect("blocking closure join");
18735 drop(t1_task);
18736 eprintln!("round {round}: completed in {elapsed:?}");
18737 }
18738 Err(error) => panic!(
18739 "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
18740 sub-budget periodic work must not keep a drain frame resident forever"
18741 ),
18742 }
18743 }
18744 }
18745
18746 #[cfg(not(target_family = "wasm"))]
18747 #[test]
18748 #[ignore = "microbenchmark: run explicitly in release mode"]
18749 fn parked_drivers_registry_cycle_microbench() {
18750 for &n in &[8usize, 64, 256] {
18757 let registry = ParkedDrivers::default();
18758 let parkers: Vec<Arc<DriverParker>> =
18759 (0..n).map(|_| Arc::new(DriverParker::default())).collect();
18760 for parker in &parkers {
18761 registry.register_with_role(parker, true, None);
18762 }
18763 let hot = &parkers[n - 1];
18764
18765 let iters = 2_000_000u32;
18766 let start = Instant::now();
18767 for _ in 0..iters {
18768 assert!(registry.wake_one());
18769 registry.deregister(hot);
18770 assert!(hot.consume_permit());
18771 registry.register_with_role(hot, true, None);
18772 }
18773 let wake_cycle = start.elapsed() / iters;
18774
18775 let start = Instant::now();
18776 for _ in 0..iters {
18777 registry.deregister(hot);
18778 registry.register_with_role(hot, true, None);
18779 }
18780 let timeout_cycle = start.elapsed() / iters;
18781
18782 let owner = BlockingOwnerToken {
18783 executor_id: u64::MAX,
18784 frame: u64::MAX,
18785 };
18786 let start = Instant::now();
18787 for _ in 0..iters {
18788 assert!(!registry.wake_one_owner_dependency(owner));
18789 }
18790 let owner_sweep = start.elapsed() / iters;
18791
18792 eprintln!(
18793 "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
18794 timeout-deregister+re-register {timeout_cycle:?}, \
18795 owner-dependency sweep {owner_sweep:?}"
18796 );
18797 }
18798 }
18799
18800 #[cfg(not(target_family = "wasm"))]
18801 #[test]
18802 fn multi_thread_block_on_from_caller_thread_still_parks() {
18803 let metrics = Arc::new(RuntimeMetrics::default());
18806 let options = RuntimeOptions {
18807 flavor: RuntimeFlavor::MultiThread,
18808 worker_threads: 3,
18809 max_blocking_tasks: 2,
18810 thread_name_prefix: "rd1-caller".to_string(),
18811 park_deadline: None,
18812 drain_linger: DEFAULT_DRAIN_LINGER,
18813 };
18814 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18815
18816 let mut output = None;
18817 let mut future = std::pin::pin!(async {
18818 output = Some(41usize + 1);
18819 });
18820 executor.block_on(future.as_mut());
18821 assert_eq!(output, Some(42));
18822 }
18823
18824 #[cfg(not(target_family = "wasm"))]
18825 #[test]
18826 fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
18827 use std::sync::mpsc;
18850 use std::time::Duration;
18851
18852 struct CallerProbe {
18857 task: Task<usize>,
18858 parked_tx: Option<mpsc::Sender<()>>,
18859 }
18860 impl Future for CallerProbe {
18861 type Output = usize;
18862 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
18863 let poll = Pin::new(&mut self.task).poll(cx);
18865 if poll.is_pending()
18866 && let Some(tx) = self.parked_tx.take()
18867 {
18868 let _ = tx.send(());
18870 }
18871 poll
18872 }
18873 }
18874
18875 let (done_tx, done_rx) = mpsc::channel();
18876 let runner = std::thread::spawn(move || {
18877 let metrics = Arc::new(RuntimeMetrics::default());
18878 let options = RuntimeOptions {
18879 flavor: RuntimeFlavor::MultiThread,
18880 worker_threads: 2,
18881 max_blocking_tasks: 2,
18882 thread_name_prefix: "rd10-caller".to_string(),
18883 park_deadline: None,
18884 drain_linger: DEFAULT_DRAIN_LINGER,
18885 };
18886 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18887
18888 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
18893 let scheduler = Arc::clone(&executor);
18894 let (runnable, task) = async_task::spawn(
18895 async move { gate_rx.await.expect("gate sender dropped") },
18896 move |r| {
18897 scheduler.schedule(r);
18898 },
18899 );
18900 executor.schedule(runnable);
18901
18902 let (parked_tx, parked_rx) = mpsc::channel::<()>();
18906 let releaser = std::thread::spawn(move || {
18907 if parked_rx.recv().is_ok() {
18908 let _ = gate_tx.send(42usize);
18909 }
18910 });
18911
18912 let mut output = None;
18915 {
18916 let mut future = std::pin::pin!(async {
18917 output = Some(
18918 CallerProbe {
18919 task,
18920 parked_tx: Some(parked_tx),
18921 }
18922 .await,
18923 );
18924 });
18925 executor.block_on(future.as_mut());
18926 }
18927
18928 releaser.join().unwrap();
18929 assert_eq!(
18930 output,
18931 Some(42usize),
18932 "block_on must return the pool-computed value"
18933 );
18934 let _ = done_tx.send(());
18935 });
18936
18937 match done_rx.recv_timeout(Duration::from_secs(10)) {
18938 Ok(()) => runner.join().unwrap(),
18939 Err(error) => panic!(
18940 "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
18941 ),
18942 }
18943 }
18944
18945 #[cfg(not(target_family = "wasm"))]
18946 #[test]
18947 fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
18948 use std::sync::{Barrier, mpsc};
18954 use std::time::Duration;
18955
18956 let (done_tx, done_rx) = mpsc::channel();
18957 let runner = std::thread::spawn(move || {
18958 let metrics = Arc::new(RuntimeMetrics::default());
18959 let options = RuntimeOptions {
18960 flavor: RuntimeFlavor::MultiThread,
18961 worker_threads: 2,
18962 max_blocking_tasks: 2,
18963 thread_name_prefix: "rd1-park".to_string(),
18964 park_deadline: None,
18965 drain_linger: DEFAULT_DRAIN_LINGER,
18966 };
18967 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18968
18969 let task_count = 2usize;
18970 let barrier = Arc::new(Barrier::new(task_count));
18971
18972 let mut senders = Vec::new();
18973 let mut tasks = Vec::new();
18974 for _ in 0..task_count {
18975 let (tx, rx) = oneshot::channel::<usize>();
18976 senders.push(tx);
18977 let exec = Arc::clone(&executor);
18978 let barrier = Arc::clone(&barrier);
18979 let body = async move {
18980 barrier.wait();
18982 let child_exec = Arc::clone(&exec);
18985 let (child_runnable, child_task) =
18986 async_task::spawn(async move { rx.await.unwrap() }, move |r| {
18987 child_exec.schedule(r)
18988 });
18989 exec.schedule(child_runnable);
18990 let mut inner = std::pin::pin!(async move {
18991 assert_eq!(child_task.await, 9usize);
18992 });
18993 exec.block_on(inner.as_mut());
18994 };
18995 let scheduler = Arc::clone(&executor);
18996 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
18997 executor.schedule(runnable);
18998 tasks.push(task);
18999 }
19000
19001 std::thread::sleep(Duration::from_millis(200));
19003 for tx in senders {
19004 let _ = tx.send(9usize);
19005 }
19006
19007 for task in tasks {
19008 futures::executor::block_on(task);
19009 }
19010 let _ = done_tx.send(());
19011 });
19012
19013 match done_rx.recv_timeout(Duration::from_secs(10)) {
19014 Ok(()) => runner.join().unwrap(),
19015 Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
19016 }
19017 }
19018
19019 #[cfg(not(target_family = "wasm"))]
19025 fn wait_until(what: &str, condition: impl Fn() -> bool) {
19026 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
19027 while !condition() {
19028 assert!(
19029 std::time::Instant::now() < deadline,
19030 "timed out waiting for: {what}"
19031 );
19032 std::thread::yield_now();
19033 }
19034 }
19035
19036 #[cfg(not(target_family = "wasm"))]
19045 fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
19046 let deadline = std::time::Instant::now() + timeout;
19047 while !handle.is_finished() {
19048 assert!(
19049 std::time::Instant::now() < deadline,
19050 "thread wedged past its {timeout:?} bound while: {what}"
19051 );
19052 std::thread::sleep(Duration::from_millis(10));
19053 }
19054 handle.join().unwrap();
19055 }
19056
19057 #[cfg(not(target_family = "wasm"))]
19058 fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
19059 executor: &Arc<MultiThreadExecutor>,
19060 start_counter: &AtomicU64,
19061 claim: impl FnOnce() -> T + Send + 'static,
19062 ) -> T {
19063 use std::sync::mpsc;
19064
19065 let (claimed_tx, claimed_rx) = mpsc::channel();
19066 let (release_claim_tx, release_claim_rx) = mpsc::channel();
19067 let (started_tx, started_rx) = mpsc::channel();
19068 let (release_start_tx, release_start_rx) = mpsc::channel();
19069 let starts_before = start_counter.load(Ordering::SeqCst);
19070 let worker = std::thread::spawn(move || {
19071 MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
19072 *slot.borrow_mut() = Some(Box::new(move || {
19073 claimed_tx.send(()).unwrap();
19074 release_claim_rx.recv().unwrap();
19075 }));
19076 });
19077 MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
19078 *slot.borrow_mut() = Some(Box::new(move || {
19079 started_tx.send(()).unwrap();
19080 release_start_rx.recv().unwrap();
19081 }));
19082 });
19083 claim()
19084 });
19085 claimed_rx
19086 .recv_timeout(Duration::from_secs(2))
19087 .expect("the scheduler claim must pause after removing work");
19088
19089 let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19090 let verdict = executor.try_begin_deadlock_verdict();
19091 let claim_verdict_rejected = verdict.is_none();
19092 drop(verdict);
19093 release_claim_tx.send(()).unwrap();
19094
19095 started_rx
19096 .recv_timeout(Duration::from_secs(2))
19097 .expect("the scheduler claim must pause after publishing its start");
19098 let admission_active_after_start =
19099 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19100 let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
19101 let verdict = executor.try_begin_deadlock_verdict();
19102 let start_verdict_rejected = verdict.is_none();
19103 drop(verdict);
19104 release_start_tx.send(()).unwrap();
19105 let output = worker.join().unwrap();
19106
19107 assert!(
19108 admission_active,
19109 "removing scheduler work must retain a detector admission until start accounting"
19110 );
19111 assert!(
19112 claim_verdict_rejected,
19113 "a deadline verdict must not close while removed work has not published its start"
19114 );
19115 assert!(
19116 start_published,
19117 "work start accounting must publish before admission retires"
19118 );
19119 assert!(
19120 admission_active_after_start,
19121 "the detector admission must remain active until after start accounting"
19122 );
19123 assert!(
19124 start_verdict_rejected,
19125 "a deadline verdict must not close between start accounting and admission retirement"
19126 );
19127 output
19128 }
19129
19130 #[cfg(not(target_family = "wasm"))]
19131 #[test]
19132 fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
19133 let executor =
19134 multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
19135
19136 let fifo_ran = Arc::new(AtomicBool::new(false));
19137 let fifo_task_ran = Arc::clone(&fifo_ran);
19138 let (fifo, fifo_task) = async_task::spawn(
19139 async move {
19140 fifo_task_ran.store(true, Ordering::SeqCst);
19141 },
19142 |_| {},
19143 );
19144 executor.metrics.runnable_scheduled();
19145 executor.push_fifo_raw(fifo);
19146 let fifo_executor = Arc::clone(&executor);
19147 assert!(run_claim_with_deadlock_gate_assertion(
19148 &executor,
19149 &executor.metrics.runnable_polls,
19150 move || {
19151 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19152 fifo_executor.run_one_fifo_runnable()
19153 }
19154 ));
19155 futures::executor::block_on(fifo_task);
19156 assert!(fifo_ran.load(Ordering::SeqCst));
19157
19158 let fifo_order = Arc::new(Mutex::new(Vec::new()));
19159 let mut fifo_tasks = Vec::new();
19160 for name in ["first", "second"] {
19161 let task_order = Arc::clone(&fifo_order);
19162 let (runnable, task) = async_task::spawn(
19163 async move {
19164 task_order.lock().unwrap().push(name);
19165 },
19166 |_| {},
19167 );
19168 executor.metrics.runnable_scheduled();
19169 executor.push_fifo_raw(runnable);
19170 fifo_tasks.push(task);
19171 }
19172 for _ in 0..2 {
19173 let fifo_executor = Arc::clone(&executor);
19174 assert!(run_claim_with_deadlock_gate_assertion(
19175 &executor,
19176 &executor.metrics.runnable_polls,
19177 move || {
19178 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19179 fifo_executor.run_one_runnable()
19180 }
19181 ));
19182 }
19183 for task in fifo_tasks {
19184 futures::executor::block_on(task);
19185 }
19186 assert_eq!(
19187 *fifo_order.lock().unwrap(),
19188 ["first", "second"],
19189 "the detector-enabled normal fallback must preserve FIFO order"
19190 );
19191
19192 let lifo_ran = Arc::new(AtomicBool::new(false));
19193 let lifo_task_ran = Arc::clone(&lifo_ran);
19194 let (lifo, lifo_task) = async_task::spawn(
19195 async move {
19196 lifo_task_ran.store(true, Ordering::SeqCst);
19197 },
19198 |_| {},
19199 );
19200 executor.metrics.runnable_scheduled();
19201 let lifo_executor = Arc::clone(&executor);
19202 assert!(run_claim_with_deadlock_gate_assertion(
19203 &executor,
19204 &executor.metrics.runnable_polls,
19205 move || {
19206 let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
19207 LIFO_SLOT.with(|slot| {
19208 assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
19209 });
19210 lifo_executor.run_one_runnable()
19211 }
19212 ));
19213 futures::executor::block_on(lifo_task);
19214 assert!(lifo_ran.load(Ordering::SeqCst));
19215
19216 executor.begin_shutdown();
19217 executor.wait_until_scheduler_idle();
19218 }
19219
19220 #[cfg(not(target_family = "wasm"))]
19221 #[test]
19222 fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
19223 let executor =
19224 multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
19225
19226 let fifo_ran = Arc::new(AtomicBool::new(false));
19227 let fifo_job_ran = Arc::clone(&fifo_ran);
19228 executor.metrics.blocking_scheduled();
19229 executor
19230 .blocking_queue
19231 .lock()
19232 .unwrap()
19233 .push(QueuedBlocking {
19234 id: BlockingJobId(u64::MAX - 90),
19235 run: Box::new(move || {
19236 fifo_job_ran.store(true, Ordering::SeqCst);
19237 }),
19238 });
19239 let fifo_executor = Arc::clone(&executor);
19240 assert!(run_claim_with_deadlock_gate_assertion(
19241 &executor,
19242 &executor.metrics.blocking_tasks_started,
19243 move || {
19244 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19245 fifo_executor.run_one_blocking()
19246 }
19247 ));
19248 assert!(fifo_ran.load(Ordering::SeqCst));
19249
19250 let owner = executor.fresh_owner_token();
19251 let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19252 let exact_job = BlockingJobId(u64::MAX - 91);
19253 let exact_ran = Arc::new(AtomicBool::new(false));
19254 let exact_job_ran = Arc::clone(&exact_ran);
19255 executor.metrics.blocking_scheduled();
19256 executor
19257 .blocking_queue
19258 .lock()
19259 .unwrap()
19260 .push(QueuedBlocking {
19261 id: exact_job,
19262 run: Box::new(move || {
19263 exact_job_ran.store(true, Ordering::SeqCst);
19264 }),
19265 });
19266 let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
19267 dependency.set_owned(BlockingDependency {
19268 job: exact_job,
19269 owner: Some(owner),
19270 });
19271 let exact_executor = Arc::clone(&executor);
19272 assert!(run_claim_with_deadlock_gate_assertion(
19273 &executor,
19274 &executor.metrics.blocking_tasks_started,
19275 move || {
19276 let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
19277 let _owner = BlockingOwnerGuard::enter(Some(owner));
19278 exact_executor.try_owned_blocking_over_cap(&dependency)
19279 }
19280 ));
19281 assert!(exact_ran.load(Ordering::SeqCst));
19282
19283 drop(active_owner);
19284 executor.begin_shutdown();
19285 executor.wait_until_scheduler_idle();
19286 }
19287
19288 #[cfg(not(target_family = "wasm"))]
19289 #[test]
19290 fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
19291 use std::sync::mpsc;
19299 use std::time::Duration;
19300
19301 let registry = Arc::new(ParkedDrivers::default());
19302
19303 let guard = registry.parked.lock().unwrap();
19305 let (done_tx, done_rx) = mpsc::channel();
19306 let waker_thread = {
19307 let registry = Arc::clone(®istry);
19308 std::thread::spawn(move || {
19309 done_tx.send(registry.wake_one()).unwrap();
19310 })
19311 };
19312 let woke = done_rx
19313 .recv_timeout(Duration::from_secs(5))
19314 .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
19315 assert!(!woke, "an empty registry has nobody to wake");
19316 waker_thread.join().unwrap();
19317 drop(guard);
19318
19319 let parker = Arc::new(DriverParker::default());
19323 registry.register(&parker);
19324 let guard = registry.parked.lock().unwrap();
19325 let (done_tx, done_rx) = mpsc::channel();
19326 let waker_thread = {
19327 let registry = Arc::clone(®istry);
19328 std::thread::spawn(move || {
19329 done_tx.send(registry.wake_one()).unwrap();
19330 })
19331 };
19332 assert!(
19333 done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
19334 "wake_one with a registered parker must take the slow path (block on the held lock)"
19335 );
19336 drop(guard);
19337 let woke = done_rx
19338 .recv_timeout(Duration::from_secs(5))
19339 .expect("slow-path wake_one must complete once the lock is released");
19340 assert!(
19341 woke,
19342 "the slow path must pop and wake the registered parker"
19343 );
19344 waker_thread.join().unwrap();
19345 parker.park();
19348 assert_eq!(
19349 registry.count.load(Ordering::SeqCst),
19350 0,
19351 "wake_one deregisters what it pops"
19352 );
19353 }
19354
19355 #[cfg(not(target_family = "wasm"))]
19356 #[test]
19357 fn driver_parker_permit_granted_while_running_is_not_lost() {
19358 use std::sync::mpsc;
19362 use std::time::Duration;
19363
19364 let parker = Arc::new(DriverParker::default());
19365 parker.unpark();
19367 parker.unpark();
19368
19369 let (tx, rx) = mpsc::channel();
19370 let driver = {
19371 let parker = Arc::clone(&parker);
19372 std::thread::spawn(move || {
19373 parker.park(); tx.send(1).unwrap();
19375 parker.park(); tx.send(2).unwrap();
19377 })
19378 };
19379
19380 assert_eq!(
19381 rx.recv_timeout(Duration::from_secs(5))
19382 .expect("a stored permit must not be lost"),
19383 1
19384 );
19385 assert!(
19386 rx.recv_timeout(Duration::from_millis(300)).is_err(),
19387 "two pre-park wakes must coalesce into one permit (second park must sleep)"
19388 );
19389 parker.unpark();
19390 assert_eq!(
19391 rx.recv_timeout(Duration::from_secs(5))
19392 .expect("unpark must wake a sleeping driver"),
19393 2
19394 );
19395 driver.join().unwrap();
19396 }
19397
19398 #[cfg(not(target_family = "wasm"))]
19399 #[test]
19400 fn driver_parker_unrepresentable_timeout_remains_wakeable() {
19401 use std::sync::mpsc;
19402
19403 let parker = Arc::new(DriverParker::default());
19404 assert!(
19405 Instant::now().checked_add(Duration::MAX).is_none(),
19406 "Duration::MAX must exercise the unrepresentable-deadline path"
19407 );
19408 let (done_tx, done_rx) = mpsc::channel();
19409 let driver = {
19410 let parker = Arc::clone(&parker);
19411 std::thread::spawn(move || {
19412 done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
19413 })
19414 };
19415 wait_until("the unbounded timeout driver to sleep", || {
19416 parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
19417 });
19418 assert!(
19419 done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
19420 "an unrepresentable timeout must not expire immediately"
19421 );
19422
19423 let unparker = {
19424 let parker = Arc::clone(&parker);
19425 std::thread::spawn(move || parker.unpark())
19426 };
19427 assert!(
19428 done_rx
19429 .recv_timeout(Duration::from_secs(1))
19430 .expect("an unrepresentable timeout must remain wakeable"),
19431 "a permit must be reported as a wake"
19432 );
19433 unparker.join().unwrap();
19434 driver.join().unwrap();
19435 }
19436
19437 #[cfg(not(target_family = "wasm"))]
19438 #[test]
19439 fn parked_drivers_wake_one_wakes_exactly_one() {
19440 use std::sync::mpsc;
19444 use std::time::Duration;
19445
19446 let registry = Arc::new(ParkedDrivers::default());
19447 let (woke_tx, woke_rx) = mpsc::channel();
19448 let mut drivers = Vec::new();
19449 for index in 0..2 {
19450 let registry = Arc::clone(®istry);
19451 let woke_tx = woke_tx.clone();
19452 drivers.push(std::thread::spawn(move || {
19453 let parker = Arc::new(DriverParker::default());
19454 registry.register(&parker);
19455 parker.park();
19456 registry.deregister(&parker);
19457 woke_tx.send(index).unwrap();
19458 }));
19459 }
19460 wait_until("both drivers registered as parked", || {
19461 registry.count.load(Ordering::SeqCst) == 2
19462 });
19463
19464 assert!(registry.wake_one(), "wake_one must wake a parked driver");
19465 woke_rx
19466 .recv_timeout(Duration::from_secs(5))
19467 .expect("exactly one driver must wake");
19468 assert!(
19469 woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
19470 "the second driver must stay parked after a single wake_one"
19471 );
19472 assert_eq!(
19473 registry.count.load(Ordering::SeqCst),
19474 1,
19475 "one driver must remain registered"
19476 );
19477
19478 assert!(
19479 registry.wake_one(),
19480 "a second wake_one must wake the remaining driver"
19481 );
19482 woke_rx
19483 .recv_timeout(Duration::from_secs(5))
19484 .expect("the second driver must wake");
19485 for driver in drivers {
19486 driver.join().unwrap();
19487 }
19488 }
19489
19490 #[cfg(not(target_family = "wasm"))]
19491 #[test]
19492 fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
19493 use std::sync::mpsc;
19494
19495 struct ReenterRegistryOnDrop {
19496 registry: Arc<ParkedDrivers>,
19497 dropped: mpsc::Sender<()>,
19498 }
19499
19500 #[expect(
19501 clippy::manual_noop_waker,
19502 reason = "the test observes the waker payload's Drop"
19503 )]
19504 impl std::task::Wake for ReenterRegistryOnDrop {
19505 fn wake(self: Arc<Self>) {}
19506 }
19507
19508 impl Drop for ReenterRegistryOnDrop {
19509 fn drop(&mut self) {
19510 let parker = Arc::new(DriverParker::default());
19511 self.registry.register(&parker);
19512 self.registry.deregister(&parker);
19513 self.dropped.send(()).unwrap();
19514 }
19515 }
19516
19517 let registry = Arc::new(ParkedDrivers::default());
19518 let dependency = Arc::new(TaskDependency::default());
19519 let (dropped_tx, dropped_rx) = mpsc::channel();
19520 let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
19521 registry: Arc::clone(®istry),
19522 dropped: dropped_tx,
19523 }));
19524 dependency.register_waiter(&waiter);
19525 drop(waiter);
19526
19527 let parker = Arc::new(DriverParker::default());
19528 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
19529 drop(dependency);
19530
19531 let retiring_registry = Arc::clone(®istry);
19532 let retiring_parker = Arc::clone(&parker);
19533 let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
19534 dropped_rx
19535 .recv_timeout(Duration::from_secs(1))
19536 .expect("retained dependency waiters must be destroyed after releasing the registry lock");
19537 retiring.join().unwrap();
19538 }
19539
19540 #[cfg(not(target_family = "wasm"))]
19541 #[test]
19542 fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
19543 use std::sync::mpsc;
19544
19545 let registry = Arc::new(ParkedDrivers::default());
19546 let owner = BlockingOwnerToken {
19547 executor_id: u64::MAX - 70,
19548 frame: u64::MAX - 71,
19549 };
19550 let dependency = Arc::new(TaskDependency::default());
19551 dependency.set_owned(BlockingDependency {
19552 job: BlockingJobId(u64::MAX - 72),
19553 owner: Some(owner),
19554 });
19555 let parker = Arc::new(DriverParker::default());
19556 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
19557
19558 let (predicate_tx, predicate_rx) = mpsc::channel();
19559 let (release_tx, release_rx) = mpsc::channel();
19560 let selecting_registry = Arc::clone(®istry);
19561 let selector = std::thread::spawn(move || {
19562 DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
19563 *slot.borrow_mut() = Some(Box::new(move || {
19564 predicate_tx.send(()).unwrap();
19565 release_rx.recv().unwrap();
19566 }));
19567 });
19568 selecting_registry.wake_one_owner_dependency(owner)
19569 });
19570
19571 predicate_rx
19572 .recv_timeout(Duration::from_secs(2))
19573 .expect("owner selection must reach the dependency predicate");
19574 let registry_was_unlocked = registry.parked.try_lock().is_ok();
19575 release_tx.send(()).unwrap();
19576 assert!(
19577 selector.join().unwrap(),
19578 "the exact-owner parker must still be selected"
19579 );
19580
19581 assert!(
19582 registry_was_unlocked,
19583 "owner dependency predicates must not run while the parked-driver registry is locked"
19584 );
19585 assert!(parker.consume_permit());
19586 assert_eq!(parker.take_owner_handoff(), Some(owner));
19587 }
19588
19589 #[cfg(not(target_family = "wasm"))]
19590 #[test]
19591 fn owner_handoff_skips_newer_untagged_dependency() {
19592 let registry = ParkedDrivers::default();
19593 let owner = BlockingOwnerToken {
19594 executor_id: u64::MAX - 1,
19595 frame: u64::MAX - 2,
19596 };
19597 let owned_dependency = Arc::new(TaskDependency::default());
19598 owned_dependency.set_owned(BlockingDependency {
19599 job: BlockingJobId(u64::MAX - 3),
19600 owner: Some(owner),
19601 });
19602 let untagged_dependency = Arc::new(TaskDependency::default());
19603 untagged_dependency.set_owned(BlockingDependency {
19604 job: BlockingJobId(u64::MAX - 4),
19605 owner: None,
19606 });
19607 let owned = Arc::new(DriverParker::default());
19608 let untagged = Arc::new(DriverParker::default());
19609
19610 registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
19611 registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
19614
19615 assert!(registry.wake_one_owner_dependency(owner));
19616 assert!(
19617 owned.consume_permit(),
19618 "the exact owner dependency must receive the retry"
19619 );
19620 assert!(
19621 !untagged.consume_permit(),
19622 "an untagged dependency must not consume an exact owner handoff"
19623 );
19624 registry.deregister(&untagged);
19625 }
19626
19627 #[cfg(not(target_family = "wasm"))]
19628 #[test]
19629 fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
19630 let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
19631 let owner = executor.fresh_owner_token();
19632 let valid_dependency = Arc::new(TaskDependency::default());
19633 valid_dependency.set_owned(BlockingDependency {
19634 job: BlockingJobId(u64::MAX - 45),
19635 owner: Some(owner),
19636 });
19637 let withdrawn_dependency = Arc::new(TaskDependency::default());
19638 withdrawn_dependency.set_owned(BlockingDependency {
19639 job: BlockingJobId(u64::MAX - 46),
19640 owner: Some(owner),
19641 });
19642 let valid = Arc::new(DriverParker::default());
19643 let withdrawn = Arc::new(DriverParker::default());
19644
19645 executor
19646 .parked_drivers
19647 .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
19648 executor.parked_drivers.register_with_role(
19649 &withdrawn,
19650 true,
19651 Some(Arc::clone(&withdrawn_dependency)),
19652 );
19653 assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
19654 withdrawn_dependency.clear();
19655
19656 assert_eq!(
19657 executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
19658 Some(false),
19659 "a selected dependency withdrawn before retry must forward the owner handoff"
19660 );
19661 assert!(
19662 valid.consume_permit(),
19663 "the next live same-owner dependency must receive the forwarded wake"
19664 );
19665 assert_eq!(
19666 valid.take_owner_handoff(),
19667 Some(owner),
19668 "the forwarded wake must retain the exact owner identity"
19669 );
19670 }
19671
19672 #[cfg(not(target_family = "wasm"))]
19673 #[test]
19674 fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
19675 use std::sync::mpsc;
19676
19677 let executor = multi_thread_executor(2, None, "owner-handoff-publication");
19678 let owner = executor.fresh_owner_token();
19679 let fallback_dependency = Arc::new(TaskDependency::default());
19680 fallback_dependency.set_owned(BlockingDependency {
19681 job: BlockingJobId(u64::MAX - 47),
19682 owner: Some(owner),
19683 });
19684 let exiting_dependency = Arc::new(TaskDependency::default());
19685 exiting_dependency.set_owned(BlockingDependency {
19686 job: BlockingJobId(u64::MAX - 48),
19687 owner: Some(owner),
19688 });
19689 let fallback = Arc::new(DriverParker::default());
19690 let exiting = Arc::new(DriverParker::default());
19691
19692 executor.parked_drivers.register_with_role(
19693 &fallback,
19694 true,
19695 Some(Arc::clone(&fallback_dependency)),
19696 );
19697 executor.parked_drivers.register_with_role(
19698 &exiting,
19699 true,
19700 Some(Arc::clone(&exiting_dependency)),
19701 );
19702
19703 let (selected_tx, selected_rx) = mpsc::channel();
19704 let (release_selection_tx, release_selection_rx) = mpsc::channel();
19705 let selector_executor = Arc::clone(&executor);
19706 let selector = std::thread::spawn(move || {
19707 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
19708 *slot.borrow_mut() = Some(Box::new(move || {
19709 selected_tx.send(()).unwrap();
19710 release_selection_rx.recv().unwrap();
19711 }));
19712 });
19713 assert!(
19714 selector_executor
19715 .parked_drivers
19716 .wake_one_owner_dependency(owner)
19717 );
19718 });
19719 selected_rx
19720 .recv_timeout(Duration::from_secs(2))
19721 .expect("selection must pause after removing the exact-owner parker");
19722
19723 let (exited_tx, exited_rx) = mpsc::channel();
19724 let exiting_executor = Arc::clone(&executor);
19725 let exiting_driver = Arc::clone(&exiting);
19726 let driver = std::thread::spawn(move || {
19727 exiting_driver.park();
19728 exiting_executor.parked_drivers.deregister(&exiting_driver);
19729 exiting_executor.forward_owner_handoff(&exiting_driver);
19730 exited_tx.send(()).unwrap();
19731 });
19732 exiting.unpark();
19733 exited_rx
19734 .recv_timeout(Duration::from_secs(2))
19735 .expect("the direct future wake must let the selected driver exit");
19736
19737 release_selection_tx.send(()).unwrap();
19738 selector.join().unwrap();
19739 driver.join().unwrap();
19740
19741 assert!(
19742 fallback.consume_permit(),
19743 "an exiting selected driver must forward the owner handoff to the next dependency"
19744 );
19745 assert_eq!(
19746 fallback.take_owner_handoff(),
19747 Some(owner),
19748 "the forwarded handoff must retain the exact owner identity"
19749 );
19750 }
19751
19752 #[cfg(not(target_family = "wasm"))]
19753 #[test]
19754 fn cooperative_panic_forwards_pending_owner_handoff() {
19755 use std::sync::mpsc;
19756
19757 struct PanicAfterWake {
19758 dependency: BlockingDependency,
19759 first_poll: Option<mpsc::Sender<()>>,
19760 }
19761
19762 impl Future for PanicAfterWake {
19763 type Output = ();
19764
19765 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
19766 if let Some(first_poll) = self.first_poll.take() {
19767 let mut published = PublishedBlockingDependencies::default();
19770 record_blocking_dependency(&mut published, self.dependency);
19771 first_poll.send(()).unwrap();
19772 return Poll::Pending;
19773 }
19774 panic!("intentional cooperative driver panic after owner handoff");
19775 }
19776 }
19777
19778 let executor = multi_thread_executor(2, None, "panic-owner-handoff");
19779 let owner = executor.fresh_owner_token();
19780 let fallback_dependency = Arc::new(TaskDependency::default());
19781 fallback_dependency.set_owned(BlockingDependency {
19782 job: BlockingJobId(u64::MAX - 49),
19783 owner: Some(owner),
19784 });
19785 let fallback = Arc::new(DriverParker::default());
19786 executor.parked_drivers.register_with_role(
19787 &fallback,
19788 true,
19789 Some(Arc::clone(&fallback_dependency)),
19790 );
19791
19792 let (first_poll_tx, first_poll_rx) = mpsc::channel();
19793 let (panicked_tx, panicked_rx) = mpsc::channel();
19794 let driver_executor = Arc::clone(&executor);
19795 let driver = std::thread::spawn(move || {
19796 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
19797 let mut future = std::pin::pin!(PanicAfterWake {
19798 dependency: BlockingDependency {
19799 job: BlockingJobId(u64::MAX - 50),
19800 owner: Some(owner)
19801 },
19802 first_poll: Some(first_poll_tx),
19803 });
19804 let panicked = catch_unwind(AssertUnwindSafe(|| {
19805 driver_executor.block_on(future.as_mut());
19806 }))
19807 .is_err();
19808 panicked_tx.send(panicked).unwrap();
19809 });
19810
19811 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
19812 wait_until("the panicking cooperative driver to park", || {
19813 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
19814 });
19815 assert!(
19816 executor.parked_drivers.wake_one_owner_dependency(owner),
19817 "the exact owner handoff must select the cooperative driver"
19818 );
19819 assert!(
19820 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
19821 "the test future must unwind from its second poll"
19822 );
19823 driver.join().unwrap();
19824
19825 assert!(
19826 fallback.consume_permit(),
19827 "panic cleanup must forward the unconsumed owner handoff"
19828 );
19829 assert_eq!(
19830 fallback.take_owner_handoff(),
19831 Some(owner),
19832 "panic cleanup must preserve the exact owner identity"
19833 );
19834 }
19835
19836 #[cfg(not(target_family = "wasm"))]
19837 #[test]
19838 fn cooperative_panic_flushes_lifo_slot() {
19839 struct ScheduleThenPanic {
19840 executor: Arc<MultiThreadExecutor>,
19841 ran: Arc<AtomicBool>,
19842 }
19843
19844 impl Future for ScheduleThenPanic {
19845 type Output = ();
19846
19847 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
19848 let ran = Arc::clone(&self.ran);
19849 let scheduler = Arc::clone(&self.executor);
19850 let (runnable, task) = async_task::spawn(
19851 async move {
19852 ran.store(true, Ordering::SeqCst);
19853 },
19854 move |runnable| scheduler.schedule(runnable),
19855 );
19856 self.executor.schedule(runnable);
19857 task.detach();
19858 panic!("intentional cooperative driver panic with a populated LIFO slot");
19859 }
19860 }
19861
19862 let executor = multi_thread_executor(2, None, "panic-lifo-flush");
19863 executor
19864 .active_drainers
19865 .store(executor.max_drainers, Ordering::SeqCst);
19866 let ran = Arc::new(AtomicBool::new(false));
19867 let panicked = {
19868 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
19869 let mut future = std::pin::pin!(ScheduleThenPanic {
19870 executor: Arc::clone(&executor),
19871 ran: Arc::clone(&ran),
19872 });
19873 catch_unwind(AssertUnwindSafe(|| {
19874 executor.block_on(future.as_mut());
19875 }))
19876 .is_err()
19877 };
19878
19879 assert!(panicked, "the test future must unwind from its first poll");
19880 assert!(
19881 executor.pop_lifo_slot().is_none(),
19882 "panic cleanup must empty the LIFO slot"
19883 );
19884 assert_eq!(
19885 executor.injector.len(),
19886 1,
19887 "panic cleanup must move the slot runnable to the shared FIFO"
19888 );
19889 assert!(
19890 executor.run_one(),
19891 "the flushed runnable must remain executable"
19892 );
19893 assert!(ran.load(Ordering::SeqCst));
19894 executor.active_drainers.store(0, Ordering::SeqCst);
19895 }
19896
19897 #[cfg(not(target_family = "wasm"))]
19898 #[test]
19899 fn cooperative_panic_compensates_absorbed_queue_wake() {
19900 use std::sync::mpsc;
19901
19902 struct PanicAfterWake {
19903 first_poll: Option<mpsc::Sender<()>>,
19904 }
19905
19906 impl Future for PanicAfterWake {
19907 type Output = ();
19908
19909 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
19910 if let Some(first_poll) = self.first_poll.take() {
19911 first_poll.send(()).unwrap();
19912 return Poll::Pending;
19913 }
19914 panic!("intentional cooperative driver panic after a queue wake");
19915 }
19916 }
19917
19918 let executor = multi_thread_executor(2, None, "panic-wake-compensation");
19919 executor
19920 .active_drainers
19921 .store(executor.max_drainers, Ordering::SeqCst);
19922 let fallback = Arc::new(DriverParker::default());
19923 executor.parked_drivers.register(&fallback);
19924 let (first_poll_tx, first_poll_rx) = mpsc::channel();
19925 let (panicked_tx, panicked_rx) = mpsc::channel();
19926 let driver_executor = Arc::clone(&executor);
19927 let driver = std::thread::spawn(move || {
19928 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
19929 let mut future = std::pin::pin!(PanicAfterWake {
19930 first_poll: Some(first_poll_tx)
19931 });
19932 let panicked = catch_unwind(AssertUnwindSafe(|| {
19933 driver_executor.block_on(future.as_mut());
19934 }))
19935 .is_err();
19936 panicked_tx.send(panicked).unwrap();
19937 });
19938
19939 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
19940 wait_until(
19941 "the panicking cooperative driver to park after the fallback",
19942 || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
19943 );
19944
19945 let ran = Arc::new(AtomicBool::new(false));
19946 let ran_task = Arc::clone(&ran);
19947 let scheduler = Arc::clone(&executor);
19948 let (runnable, task) = async_task::spawn(
19949 async move {
19950 ran_task.store(true, Ordering::SeqCst);
19951 },
19952 move |runnable| scheduler.schedule(runnable),
19953 );
19954 task.detach();
19955 executor.metrics.runnable_scheduled();
19956 executor.push_fifo_raw(runnable);
19957 assert!(
19958 executor.parked_drivers.wake_one(),
19959 "the queued runnable's wake must be absorbed by the cooperative driver"
19960 );
19961
19962 assert!(
19963 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
19964 "the test future must unwind after absorbing the queue wake"
19965 );
19966 driver.join().unwrap();
19967 assert!(
19968 fallback.consume_permit(),
19969 "panic cleanup must compensate the absorbed queue wake to the fallback driver"
19970 );
19971 assert!(
19972 executor.run_one(),
19973 "the queued runnable must remain executable"
19974 );
19975 assert!(ran.load(Ordering::SeqCst));
19976 executor.active_drainers.store(0, Ordering::SeqCst);
19977 }
19978
19979 #[cfg(not(target_family = "wasm"))]
19980 #[test]
19981 fn multi_thread_future_wake_targets_its_own_parked_driver() {
19982 use std::sync::mpsc;
19990 use std::time::Duration;
19991
19992 let (done_tx, done_rx) = mpsc::channel();
19993 let runner = std::thread::spawn(move || {
19994 let metrics = Arc::new(RuntimeMetrics::default());
19995 let options = RuntimeOptions {
19996 flavor: RuntimeFlavor::MultiThread,
19997 worker_threads: 2,
19998 max_blocking_tasks: 2,
19999 thread_name_prefix: "wake-target".to_string(),
20000 park_deadline: None,
20001 drain_linger: DEFAULT_DRAIN_LINGER,
20002 };
20003 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20004
20005 let (tx1, rx1) = oneshot::channel::<usize>();
20006 let (tx2, rx2) = oneshot::channel::<usize>();
20007 let t1_done = Arc::new(AtomicBool::new(false));
20008 let t2_done = Arc::new(AtomicBool::new(false));
20009
20010 let t1_exec = Arc::clone(&executor);
20012 let t1_flag = Arc::clone(&t1_done);
20013 let t1_body = async move {
20014 let mut inner = std::pin::pin!(async move {
20015 assert_eq!(rx1.await.unwrap(), 7usize);
20016 });
20017 t1_exec.block_on(inner.as_mut());
20018 t1_flag.store(true, Ordering::SeqCst);
20019 };
20020 let sched1 = Arc::clone(&executor);
20021 let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
20022 executor.schedule(runnable1);
20023 wait_until("driver 1 parked", || {
20024 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20025 });
20026
20027 let t2_exec = Arc::clone(&executor);
20031 let t2_flag = Arc::clone(&t2_done);
20032 let t2_body = async move {
20033 let mut inner = std::pin::pin!(async move {
20034 assert_eq!(rx2.await.unwrap(), 9usize);
20035 });
20036 t2_exec.block_on(inner.as_mut());
20037 t2_flag.store(true, Ordering::SeqCst);
20038 };
20039 let sched2 = Arc::clone(&executor);
20040 let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
20041 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(runnable2);
20043 executor.ensure_drainer();
20044 wait_until("driver 2 parked", || {
20045 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20046 });
20047
20048 tx1.send(7usize).unwrap();
20051 wait_until("T1 completed via its own driver's wake", || {
20052 t1_done.load(Ordering::SeqCst)
20053 });
20054 futures::executor::block_on(task1);
20055 assert!(
20056 !t2_done.load(Ordering::SeqCst),
20057 "D2 must still be parked: its future was never completed"
20058 );
20059 wait_until(
20076 "the retiring drain frame registered its linger parker",
20077 || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
20078 );
20079 wait_until("the lingering drainer's idle budget expired", || {
20080 executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
20081 });
20082 assert_eq!(
20083 executor.parked_drivers.count.load(Ordering::SeqCst),
20084 1,
20085 "exactly the untargeted driver must remain parked after the linger expired"
20086 );
20087 assert!(
20088 !t2_done.load(Ordering::SeqCst),
20089 "D2 must still be parked after the lingering drainer expired"
20090 );
20091
20092 tx2.send(9usize).unwrap();
20094 futures::executor::block_on(task2);
20095 assert!(t2_done.load(Ordering::SeqCst));
20096 let _ = done_tx.send(());
20097 });
20098
20099 match done_rx.recv_timeout(Duration::from_secs(10)) {
20100 Ok(()) => runner.join().unwrap(),
20101 Err(error) => panic!(
20102 "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
20103 ),
20104 }
20105 }
20106
20107 #[cfg(not(target_family = "wasm"))]
20108 #[test]
20109 fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
20110 use std::sync::mpsc;
20118 use std::time::Duration;
20119
20120 let (done_tx, done_rx) = mpsc::channel();
20121 let runner = std::thread::spawn(move || {
20122 let metrics = Arc::new(RuntimeMetrics::default());
20123 let options = RuntimeOptions {
20124 flavor: RuntimeFlavor::MultiThread,
20125 worker_threads: 1,
20126 max_blocking_tasks: 1,
20127 thread_name_prefix: "wake-compensate".to_string(),
20128 park_deadline: None,
20129 drain_linger: DEFAULT_DRAIN_LINGER,
20130 };
20131 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20132
20133 let (gate_tx, gate_rx) = oneshot::channel::<()>();
20135 let td_exec = Arc::clone(&executor);
20136 let td_body = async move {
20137 let mut inner = std::pin::pin!(async move {
20138 gate_rx.await.unwrap();
20139 });
20140 td_exec.block_on(inner.as_mut());
20141 };
20142 let sched = Arc::clone(&executor);
20143 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
20144 executor.schedule(td_runnable);
20145 wait_until("the sole driver parked", || {
20146 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20147 });
20148
20149 let (ran_tx, ran_rx) = mpsc::channel::<()>();
20152 let sched_stranded = Arc::clone(&executor);
20153 let (stranded_runnable, stranded_task) = async_task::spawn(
20154 async move {
20155 ran_tx.send(()).unwrap();
20156 },
20157 move |r| sched_stranded.schedule(r),
20158 );
20159 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(stranded_runnable);
20161 stranded_task.detach();
20162
20163 {
20168 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20169 let mut ready = std::pin::pin!(async {});
20170 executor.block_on(ready.as_mut());
20171 }
20172
20173 ran_rx.recv_timeout(Duration::from_secs(5)).expect(
20174 "exit compensation must wake the parked driver to run the stranded queued runnable",
20175 );
20176
20177 gate_tx.send(()).unwrap();
20179 futures::executor::block_on(td_task);
20180 let _ = done_tx.send(());
20181 });
20182
20183 match done_rx.recv_timeout(Duration::from_secs(10)) {
20184 Ok(()) => runner.join().unwrap(),
20185 Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
20186 }
20187 }
20188
20189 #[cfg(not(target_family = "wasm"))]
20190 #[test]
20191 fn cooperative_exit_compensates_blocking_only_residue() {
20192 use std::sync::mpsc;
20193
20194 struct Controlled {
20195 ready: Arc<AtomicBool>,
20196 entered: Option<mpsc::Sender<()>>,
20197 }
20198
20199 impl Future for Controlled {
20200 type Output = ();
20201
20202 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20203 if let Some(entered) = self.entered.take() {
20204 entered.send(()).unwrap();
20205 }
20206 if self.ready.load(Ordering::SeqCst) {
20207 Poll::Ready(())
20208 } else {
20209 Poll::Pending
20210 }
20211 }
20212 }
20213
20214 let options = RuntimeOptions {
20215 flavor: RuntimeFlavor::MultiThread,
20216 worker_threads: 2,
20217 max_blocking_tasks: 1,
20218 thread_name_prefix: "blocking-exit-compensation".to_string(),
20219 park_deadline: None,
20220 drain_linger: DEFAULT_DRAIN_LINGER,
20221 };
20222 let executor =
20223 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20224 let ready = Arc::new(AtomicBool::new(false));
20225 let (entered_tx, entered_rx) = mpsc::channel();
20226 let (returned_tx, returned_rx) = mpsc::channel();
20227 let (release_tx, release_rx) = mpsc::channel();
20228 let task_exec = Arc::clone(&executor);
20229 let task_ready = Arc::clone(&ready);
20230 let scheduler = Arc::clone(&executor);
20231 let (runnable, task) = async_task::spawn(
20232 async move {
20233 let mut future = std::pin::pin!(Controlled {
20234 ready: task_ready,
20235 entered: Some(entered_tx)
20236 });
20237 task_exec.block_on(future.as_mut());
20238 returned_tx.send(()).unwrap();
20239 release_rx.recv().unwrap();
20240 },
20241 move |runnable| scheduler.schedule(runnable),
20242 );
20243 executor.schedule(runnable);
20244 entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
20245
20246 wait_until("the cooperative frame parked", || {
20256 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20257 });
20258
20259 let timer_exec = Arc::clone(&executor);
20260 let timer_scheduler = Arc::clone(&executor);
20261 let (timer_runnable, timer_task) = async_task::spawn(
20262 async move {
20263 heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
20264 },
20265 move |runnable| timer_scheduler.schedule(runnable),
20266 );
20267 executor.schedule(timer_runnable);
20268 wait_until("the dedicated timekeeper parked", || {
20269 executor
20270 .timers
20271 .inner
20272 .lock()
20273 .unwrap()
20274 .timekeeper_parker
20275 .is_some()
20276 });
20277 wait_until(
20282 "the cooperative frame re-parked in its timer-bounded park",
20283 || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
20284 );
20285
20286 ready.store(true, Ordering::SeqCst);
20287 let (blocking_tx, blocking_rx) = mpsc::channel();
20288 let blocking = executor.schedule_blocking(move || {
20289 blocking_tx.send(()).unwrap();
20290 });
20291 blocking_rx
20294 .recv_timeout(Duration::from_secs(10))
20295 .expect("blocking-only residue must be serviced by a Rayon worker");
20296 returned_rx
20297 .recv_timeout(Duration::from_secs(10))
20298 .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
20299 release_tx.send(()).unwrap();
20300 futures::executor::block_on(task);
20301 futures::executor::block_on(blocking).unwrap();
20302 executor.shutdown_timers();
20303 futures::executor::block_on(timer_task);
20304 }
20305
20306 #[cfg(not(target_family = "wasm"))]
20307 #[test]
20308 fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
20309 let options = RuntimeOptions {
20310 flavor: RuntimeFlavor::MultiThread,
20311 worker_threads: 2,
20312 max_blocking_tasks: 1,
20313 thread_name_prefix: "stopped-exit-compensation".to_string(),
20314 park_deadline: None,
20315 drain_linger: DEFAULT_DRAIN_LINGER,
20316 };
20317 let executor =
20318 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20319 let ran = Arc::new(AtomicBool::new(false));
20320 let blocking_ran = Arc::clone(&ran);
20321
20322 executor
20326 .active_drainers
20327 .store(executor.max_drainers, Ordering::SeqCst);
20328 let blocking = executor.schedule_blocking(move || {
20329 blocking_ran.store(true, Ordering::SeqCst);
20330 });
20331 assert!(!executor.blocking_queue.lock().unwrap().is_empty());
20332
20333 executor.stop.begin_shutdown();
20334 executor.compensate_queued_work_wake(true);
20335 let ran_after_stop = ran.load(Ordering::SeqCst);
20336
20337 executor.active_drainers.store(0, Ordering::SeqCst);
20338 executor.begin_shutdown();
20339 let blocking_result = futures::executor::block_on(blocking);
20340
20341 assert!(
20342 !ran_after_stop,
20343 "exit compensation started queued blocking work after stop"
20344 );
20345 assert!(
20346 blocking_result.is_err(),
20347 "shutdown must cancel the queued blocking job"
20348 );
20349 }
20350
20351 #[cfg(not(target_family = "wasm"))]
20352 #[test]
20353 fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
20354 use std::sync::mpsc;
20363 use std::time::Duration;
20364
20365 const ROUNDS: usize = 20_000;
20366
20367 let (done_tx, done_rx) = mpsc::channel();
20368 let registry = Arc::new(ParkedDrivers::default());
20369 let work = Arc::new(AtomicBool::new(false));
20370
20371 let consumer = {
20372 let registry = Arc::clone(®istry);
20373 let work = Arc::clone(&work);
20374 std::thread::spawn(move || {
20375 let parker = Arc::new(DriverParker::default());
20376 for _ in 0..ROUNDS {
20377 loop {
20378 if work.swap(false, Ordering::SeqCst) {
20379 break;
20380 }
20381 registry.register(&parker);
20384 if work.load(Ordering::SeqCst) {
20385 registry.deregister(&parker);
20386 continue;
20387 }
20388 parker.park();
20389 registry.deregister(&parker);
20390 }
20391 }
20392 done_tx.send(()).unwrap();
20393 })
20394 };
20395
20396 for _ in 0..ROUNDS {
20397 work.store(true, Ordering::SeqCst);
20398 let _ = registry.wake_one();
20402 while work.load(Ordering::SeqCst) {
20403 std::hint::spin_loop();
20404 }
20405 }
20406
20407 done_rx
20408 .recv_timeout(Duration::from_secs(30))
20409 .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
20410 consumer.join().unwrap();
20411 }
20412
20413 #[cfg(not(target_family = "wasm"))]
20414 #[test]
20415 fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
20416 let options = RuntimeOptions {
20420 flavor: RuntimeFlavor::MultiThread,
20421 worker_threads: 2,
20422 max_blocking_tasks: 2,
20423 thread_name_prefix: "lifo-order".to_string(),
20424 park_deadline: None,
20425 drain_linger: DEFAULT_DRAIN_LINGER,
20426 };
20427 let executor =
20428 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20429 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20430
20431 let old_order = Arc::clone(&order);
20434 let sched = Arc::clone(&executor);
20435 let (old_runnable, old_task) = async_task::spawn(
20436 async move {
20437 old_order.lock().unwrap().push("old");
20438 },
20439 move |r| sched.schedule(r),
20440 );
20441 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(old_runnable);
20443 old_task.detach();
20444
20445 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20448 let new_order = Arc::clone(&order);
20449 let sched = Arc::clone(&executor);
20450 let (new_runnable, new_task) = async_task::spawn(
20451 async move {
20452 new_order.lock().unwrap().push("new");
20453 },
20454 move |r| sched.schedule(r),
20455 );
20456 executor.schedule(new_runnable);
20457 new_task.detach();
20458 assert_eq!(
20459 executor.injector.len(),
20460 1,
20461 "the slot-path schedule must not push to the shared FIFO"
20462 );
20463
20464 assert!(executor.run_one(), "run_one must pop the slot first");
20465 assert!(executor.run_one(), "run_one must then pop the FIFO");
20466 assert!(!executor.run_one(), "no work must remain");
20467 assert_eq!(
20468 *order.lock().unwrap(),
20469 vec!["new", "old"],
20470 "the slot runnable (newest) must run before older FIFO work"
20471 );
20472 }
20473
20474 #[cfg(not(target_family = "wasm"))]
20475 #[test]
20476 fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
20477 let options = RuntimeOptions {
20478 flavor: RuntimeFlavor::MultiThread,
20479 worker_threads: 2,
20480 max_blocking_tasks: 1,
20481 thread_name_prefix: "fifo-owner".to_string(),
20482 park_deadline: None,
20483 drain_linger: DEFAULT_DRAIN_LINGER,
20484 };
20485 let executor =
20486 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20487 let observed_owner = Arc::new(Mutex::new(None));
20488 let observed = Arc::clone(&observed_owner);
20489 let (runnable, task) = async_task::spawn(
20490 async move {
20491 *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
20492 },
20493 |_| {},
20494 );
20495 task.detach();
20496 executor.metrics.runnable_scheduled();
20497 executor.push_fifo_raw(runnable);
20498 executor.active_drainers.store(1, Ordering::Release);
20499
20500 let owner = executor.fresh_owner_token();
20501 let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
20502 Arc::clone(&executor).drain();
20503
20504 assert_eq!(
20505 *observed_owner.lock().unwrap(),
20506 Some(None),
20507 "FIFO runnables must not borrow a blocking closure's over-cap privilege"
20508 );
20509 assert_eq!(
20510 BLOCKING_OWNER.with(std::cell::Cell::get),
20511 Some(owner),
20512 "the ambient blocking owner must be restored after the runnable"
20513 );
20514 }
20515
20516 #[cfg(not(target_family = "wasm"))]
20517 #[test]
20518 fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
20519 use std::sync::mpsc;
20523 use std::time::Duration;
20524
20525 let (done_tx, done_rx) = mpsc::channel();
20526 let runner = std::thread::spawn(move || {
20527 let options = RuntimeOptions {
20528 flavor: RuntimeFlavor::MultiThread,
20529 worker_threads: 1,
20530 max_blocking_tasks: 1,
20531 thread_name_prefix: "lifo-drain".to_string(),
20532 park_deadline: None,
20533 drain_linger: DEFAULT_DRAIN_LINGER,
20534 };
20535 let executor =
20536 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20537 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20538
20539 let p_exec = Arc::clone(&executor);
20540 let p_order = Arc::clone(&order);
20541 let p_body = async move {
20542 p_order.lock().unwrap().push("P");
20543 let c_order = Arc::clone(&p_order);
20545 let sched = Arc::clone(&p_exec);
20546 let (c_runnable, c_task) = async_task::spawn(
20547 async move {
20548 c_order.lock().unwrap().push("C");
20549 },
20550 move |r| sched.schedule(r),
20551 );
20552 p_exec.schedule(c_runnable);
20553 c_task.detach();
20554 };
20555
20556 let mut tasks = Vec::new();
20560 let sched = Arc::clone(&executor);
20561 let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
20562 executor.metrics.runnable_scheduled();
20563 executor.push_fifo_raw(p_runnable);
20564 tasks.push(p_task);
20565 for name in ["T1", "T2"] {
20566 let t_order = Arc::clone(&order);
20567 let sched = Arc::clone(&executor);
20568 let (t_runnable, t_task) = async_task::spawn(
20569 async move {
20570 t_order.lock().unwrap().push(name);
20571 },
20572 move |r| sched.schedule(r),
20573 );
20574 executor.metrics.runnable_scheduled();
20575 executor.push_fifo_raw(t_runnable);
20576 tasks.push(t_task);
20577 }
20578 executor.ensure_drainer();
20579
20580 for task in tasks {
20581 futures::executor::block_on(task);
20582 }
20583 wait_until("the detached child C ran", || {
20584 order.lock().unwrap().len() == 4
20585 });
20586 assert_eq!(
20587 *order.lock().unwrap(),
20588 vec!["P", "C", "T1", "T2"],
20589 "the slot child C must run right after its scheduler P, ahead of older FIFO work"
20590 );
20591 let _ = done_tx.send(());
20592 });
20593
20594 match done_rx.recv_timeout(Duration::from_secs(10)) {
20595 Ok(()) => runner.join().unwrap(),
20596 Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
20597 }
20598 }
20599
20600 #[cfg(not(target_family = "wasm"))]
20601 #[test]
20602 fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
20603 let options = RuntimeOptions {
20609 flavor: RuntimeFlavor::MultiThread,
20610 worker_threads: 2,
20611 max_blocking_tasks: 2,
20612 thread_name_prefix: "lifo-displace".to_string(),
20613 park_deadline: None,
20614 drain_linger: DEFAULT_DRAIN_LINGER,
20615 };
20616 let executor =
20617 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20618 executor
20619 .active_drainers
20620 .store(executor.max_drainers, Ordering::SeqCst);
20621 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20622
20623 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20624 for name in ["a", "b"] {
20625 let n_order = Arc::clone(&order);
20626 let sched = Arc::clone(&executor);
20627 let (runnable, task) = async_task::spawn(
20628 async move {
20629 n_order.lock().unwrap().push(name);
20630 },
20631 move |r| sched.schedule(r),
20632 );
20633 executor.schedule(runnable);
20634 task.detach();
20635 }
20636 assert_eq!(
20637 executor.injector.len(),
20638 1,
20639 "the displaced occupant (a) must have been pushed to the shared FIFO"
20640 );
20641
20642 assert!(executor.run_one(), "run_one must pop the slot (b) first");
20643 assert!(
20644 executor.run_one(),
20645 "run_one must then pop the displaced (a) from the FIFO"
20646 );
20647 assert!(!executor.run_one(), "no work must remain");
20648 assert_eq!(
20649 *order.lock().unwrap(),
20650 vec!["b", "a"],
20651 "the newest schedule must win the slot; the displaced one must survive via the FIFO"
20652 );
20653 executor.active_drainers.store(0, Ordering::SeqCst);
20654 }
20655
20656 #[cfg(not(target_family = "wasm"))]
20657 #[test]
20658 fn lifo_slot_is_executor_scoped() {
20659 let options = RuntimeOptions {
20664 flavor: RuntimeFlavor::MultiThread,
20665 worker_threads: 2,
20666 max_blocking_tasks: 2,
20667 thread_name_prefix: "lifo-scope".to_string(),
20668 park_deadline: None,
20669 drain_linger: DEFAULT_DRAIN_LINGER,
20670 };
20671 let exec1 =
20672 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20673 let exec2 =
20674 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20675 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
20676 exec1
20677 .active_drainers
20678 .store(exec1.max_drainers, Ordering::SeqCst);
20679 exec2
20680 .active_drainers
20681 .store(exec2.max_drainers, Ordering::SeqCst);
20682 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20683
20684 let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
20686 let r1_order = Arc::clone(&order);
20687 let sched = Arc::clone(&exec1);
20688 let (r1, t1) = async_task::spawn(
20689 async move {
20690 r1_order.lock().unwrap().push("one");
20691 },
20692 move |r| sched.schedule(r),
20693 );
20694 exec1.schedule(r1);
20695 t1.detach();
20696 assert!(
20697 exec1.injector.is_empty(),
20698 "r1 must sit in the slot, not exec1's FIFO"
20699 );
20700
20701 {
20702 let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
20705 let r2_order = Arc::clone(&order);
20706 let sched = Arc::clone(&exec2);
20707 let (r2, t2) = async_task::spawn(
20708 async move {
20709 r2_order.lock().unwrap().push("two");
20710 },
20711 move |r| sched.schedule(r),
20712 );
20713 exec2.schedule(r2);
20714 t2.detach();
20715 assert_eq!(
20716 exec2.injector.len(),
20717 1,
20718 "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
20719 );
20720 assert!(
20721 exec2.pop_lifo_slot().is_none(),
20722 "exec2 must not pop exec1's slot entry (executor-scoped pop)"
20723 );
20724 assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
20725 }
20726
20727 assert!(
20729 exec1.run_one(),
20730 "exec1's slot entry must have survived the foreign activity"
20731 );
20732 assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
20733 exec1.active_drainers.store(0, Ordering::SeqCst);
20734 exec2.active_drainers.store(0, Ordering::SeqCst);
20735 }
20736
20737 #[cfg(not(target_family = "wasm"))]
20738 #[test]
20739 fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
20740 use std::sync::mpsc;
20747 use std::time::Duration;
20748
20749 const CHAIN: u64 = 200; fn spawn_link(
20752 executor: &Arc<MultiThreadExecutor>,
20753 remaining: u64,
20754 counter: Arc<AtomicU64>,
20755 done: mpsc::Sender<()>,
20756 ) {
20757 let body_exec = Arc::clone(executor);
20758 let body = async move {
20759 counter.fetch_add(1, Ordering::SeqCst);
20760 if remaining > 0 {
20761 spawn_link(&body_exec, remaining - 1, counter, done);
20762 } else {
20763 done.send(()).unwrap();
20764 }
20765 };
20766 let sched = Arc::clone(executor);
20767 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
20768 executor.schedule(runnable);
20769 task.detach();
20770 }
20771
20772 let (done_tx, done_rx) = mpsc::channel();
20773 let runner = std::thread::spawn(move || {
20774 let metrics = Arc::new(RuntimeMetrics::default());
20775 let options = RuntimeOptions {
20776 flavor: RuntimeFlavor::MultiThread,
20777 worker_threads: 1,
20778 max_blocking_tasks: 1,
20779 thread_name_prefix: "lifo-budget".to_string(),
20780 park_deadline: None,
20781 drain_linger: DEFAULT_DRAIN_LINGER,
20782 };
20783 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
20784
20785 let counter = Arc::new(AtomicU64::new(0));
20786 let (chain_tx, chain_rx) = mpsc::channel();
20787 spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
20788
20789 chain_rx
20790 .recv_timeout(Duration::from_secs(10))
20791 .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
20792 assert_eq!(
20793 counter.load(Ordering::SeqCst),
20794 CHAIN,
20795 "every chain link must have run"
20796 );
20797 wait_until("runnable counters settle to zero (no slot leak)", || {
20798 metrics.queued_runnables.load(Ordering::Relaxed) == 0
20799 && metrics.active_runnables.load(Ordering::Relaxed) == 0
20800 });
20801 let _ = done_tx.send(());
20802 });
20803
20804 match done_rx.recv_timeout(Duration::from_secs(15)) {
20805 Ok(()) => runner.join().unwrap(),
20806 Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
20807 }
20808 }
20809
20810 #[cfg(not(target_family = "wasm"))]
20811 #[test]
20812 fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
20813 let options = RuntimeOptions {
20820 flavor: RuntimeFlavor::MultiThread,
20821 worker_threads: 2,
20822 max_blocking_tasks: 2,
20823 thread_name_prefix: "lifo-flush".to_string(),
20824 park_deadline: None,
20825 drain_linger: DEFAULT_DRAIN_LINGER,
20826 };
20827 let executor =
20828 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20829 executor
20830 .active_drainers
20831 .store(executor.max_drainers, Ordering::SeqCst);
20832 let ran = Arc::new(AtomicBool::new(false));
20833
20834 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20835 let ran_flag = Arc::clone(&ran);
20836 let sched = Arc::clone(&executor);
20837 let (runnable, task) = async_task::spawn(
20838 async move {
20839 ran_flag.store(true, Ordering::SeqCst);
20840 },
20841 move |r| sched.schedule(r),
20842 );
20843 executor.schedule(runnable);
20844 task.detach();
20845 assert!(
20846 executor.injector.is_empty(),
20847 "the runnable must start in the slot"
20848 );
20849
20850 executor.flush_lifo_slot();
20851 assert!(
20852 executor.pop_lifo_slot().is_none(),
20853 "the slot must be empty after a flush"
20854 );
20855 assert_eq!(
20856 executor.injector.len(),
20857 1,
20858 "the flushed runnable must be on the shared FIFO"
20859 );
20860 assert!(executor.run_one(), "the flushed runnable must still run");
20861 assert!(
20862 ran.load(Ordering::SeqCst),
20863 "the flushed task must complete, not be dropped"
20864 );
20865 executor.active_drainers.store(0, Ordering::SeqCst);
20866 }
20867
20868 #[cfg(not(target_family = "wasm"))]
20869 #[test]
20870 fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
20871 let options = RuntimeOptions {
20876 flavor: RuntimeFlavor::MultiThread,
20877 worker_threads: 2,
20878 max_blocking_tasks: 2,
20879 thread_name_prefix: "lifo-owner".to_string(),
20880 park_deadline: None,
20881 drain_linger: DEFAULT_DRAIN_LINGER,
20882 };
20883 let executor =
20884 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20885 let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
20886
20887 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20888
20889 let observed_slot = Arc::clone(&observed);
20893 let sched = Arc::clone(&executor);
20894 let (runnable, task) = async_task::spawn(
20895 async move {
20896 *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
20897 },
20898 move |r| sched.schedule(r),
20899 );
20900 executor.schedule(runnable);
20901 task.detach();
20902 assert!(
20903 executor.injector.is_empty(),
20904 "the runnable must sit in the slot"
20905 );
20906
20907 let token = executor.fresh_owner_token();
20911 let _owner = BlockingOwnerGuard::enter(Some(token));
20912 assert!(executor.run_one(), "the slot runnable must run");
20913 assert_eq!(
20914 *observed.lock().unwrap(),
20915 Some(None),
20916 "a slot runnable must observe a CLEARED blocking owner"
20917 );
20918 assert_eq!(
20919 BLOCKING_OWNER.with(std::cell::Cell::get),
20920 Some(token),
20921 "the ambient owner frame must be restored after the slot runnable"
20922 );
20923 }
20924
20925 #[cfg(not(target_family = "wasm"))]
20931 struct PingPongShared {
20932 count: AtomicU64,
20933 stop: AtomicBool,
20934 stop_after: u64,
20935 wakers: Mutex<[Option<std::task::Waker>; 2]>,
20936 }
20937
20938 #[cfg(not(target_family = "wasm"))]
20939 struct PingPong {
20940 shared: Arc<PingPongShared>,
20941 me: usize,
20942 }
20943
20944 #[cfg(not(target_family = "wasm"))]
20945 impl Future for PingPong {
20946 type Output = ();
20947 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
20948 let shared = &self.shared;
20949 let polls = shared.count.fetch_add(1, Ordering::SeqCst);
20950 if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
20951 let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
20952 if let Some(partner) = partner {
20953 partner.wake();
20954 }
20955 return Poll::Ready(());
20956 }
20957 let mut wakers = shared.wakers.lock().unwrap();
20958 wakers[self.me] = Some(cx.waker().clone());
20959 if let Some(partner) = wakers[1 - self.me].take() {
20960 drop(wakers);
20961 partner.wake();
20962 }
20963 Poll::Pending
20964 }
20965 }
20966
20967 #[cfg(not(target_family = "wasm"))]
20968 fn spawn_ping_pong_pair(
20969 executor: &Arc<MultiThreadExecutor>,
20970 shared: &Arc<PingPongShared>,
20971 ) -> Vec<Task<()>> {
20972 let mut tasks = Vec::new();
20973 for me in 0..2 {
20974 let sched = Arc::clone(executor);
20975 let (runnable, task) = async_task::spawn(
20976 PingPong {
20977 shared: Arc::clone(shared),
20978 me,
20979 },
20980 move |r| sched.schedule(r),
20981 );
20982 executor.schedule(runnable);
20983 tasks.push(task);
20984 }
20985 tasks
20986 }
20987
20988 #[cfg(not(target_family = "wasm"))]
20989 #[test]
20990 fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
20991 use std::sync::mpsc;
20998 use std::time::Duration;
20999
21000 let (done_tx, done_rx) = mpsc::channel();
21001 let runner = std::thread::spawn(move || {
21002 let options = RuntimeOptions {
21003 flavor: RuntimeFlavor::MultiThread,
21004 worker_threads: 1,
21005 max_blocking_tasks: 0,
21006 thread_name_prefix: "lifo-streak".to_string(),
21007 park_deadline: None,
21008 drain_linger: DEFAULT_DRAIN_LINGER,
21009 };
21010 let executor =
21011 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21012
21013 let shared = Arc::new(PingPongShared {
21014 count: AtomicU64::new(0),
21015 stop: AtomicBool::new(false),
21016 stop_after: u64::MAX,
21017 wakers: Mutex::new([None, None]),
21018 });
21019 let pair = spawn_ping_pong_pair(&executor, &shared);
21020
21021 let (t3_tx, t3_rx) = mpsc::channel::<()>();
21023 let sched = Arc::clone(&executor);
21024 let (t3_runnable, t3_task) = async_task::spawn(
21025 async move {
21026 t3_tx.send(()).unwrap();
21027 },
21028 move |r| sched.schedule(r),
21029 );
21030 executor.schedule(t3_runnable);
21031
21032 t3_rx
21033 .recv_timeout(Duration::from_secs(10))
21034 .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
21035 assert!(
21036 shared.count.load(Ordering::SeqCst) >= 32,
21037 "the pair must have been bouncing through the slot before T3 got its turn"
21038 );
21039
21040 shared.stop.store(true, Ordering::SeqCst);
21042 let stored: Vec<_> = shared
21043 .wakers
21044 .lock()
21045 .unwrap()
21046 .iter_mut()
21047 .filter_map(Option::take)
21048 .collect();
21049 for waker in stored {
21050 waker.wake();
21051 }
21052 for task in pair {
21053 futures::executor::block_on(task);
21054 }
21055 futures::executor::block_on(t3_task);
21056 let _ = done_tx.send(());
21057 });
21058
21059 match done_rx.recv_timeout(Duration::from_secs(15)) {
21060 Ok(()) => runner.join().unwrap(),
21061 Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
21062 }
21063 }
21064
21065 #[cfg(not(target_family = "wasm"))]
21066 #[test]
21067 fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
21068 use std::sync::mpsc;
21075 use std::time::Duration;
21076
21077 const BOUNCES: u64 = 2_000; let (done_tx, done_rx) = mpsc::channel();
21080 let runner = std::thread::spawn(move || {
21081 let options = RuntimeOptions {
21082 flavor: RuntimeFlavor::MultiThread,
21083 worker_threads: 1,
21084 max_blocking_tasks: 1,
21085 thread_name_prefix: "lifo-blocking".to_string(),
21086 park_deadline: None,
21087 drain_linger: DEFAULT_DRAIN_LINGER,
21088 };
21089 let executor =
21090 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21091
21092 let shared = Arc::new(PingPongShared {
21093 count: AtomicU64::new(0),
21094 stop: AtomicBool::new(false),
21095 stop_after: BOUNCES,
21096 wakers: Mutex::new([None, None]),
21097 });
21098 let pair = spawn_ping_pong_pair(&executor, &shared);
21099
21100 let blocking = executor.schedule_blocking(|| 11usize);
21102
21103 assert_eq!(
21104 futures::executor::block_on(blocking).unwrap(),
21105 11,
21106 "the queued blocking job must complete despite the hot LIFO pair"
21107 );
21108 for task in pair {
21109 futures::executor::block_on(task);
21110 }
21111 assert!(
21112 shared.count.load(Ordering::SeqCst) >= BOUNCES,
21113 "the pair must have actually run its full bounce quota"
21114 );
21115 let _ = done_tx.send(());
21116 });
21117
21118 match done_rx.recv_timeout(Duration::from_secs(15)) {
21119 Ok(()) => runner.join().unwrap(),
21120 Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
21121 }
21122 }
21123
21124 #[cfg(not(target_family = "wasm"))]
21125 #[test]
21126 fn infinite_hot_runnable_yields_to_blocking_fifo() {
21127 use std::sync::mpsc;
21128
21129 struct HotFuture {
21130 stop: Arc<AtomicBool>,
21131 }
21132
21133 impl Future for HotFuture {
21134 type Output = ();
21135
21136 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21137 if self.stop.load(Ordering::SeqCst) {
21138 Poll::Ready(())
21139 } else {
21140 cx.waker().wake_by_ref();
21141 Poll::Pending
21142 }
21143 }
21144 }
21145
21146 let options = RuntimeOptions {
21147 flavor: RuntimeFlavor::MultiThread,
21148 worker_threads: 2,
21149 max_blocking_tasks: 1,
21150 thread_name_prefix: "blocking-fairness".to_string(),
21151 park_deadline: None,
21152 drain_linger: DEFAULT_DRAIN_LINGER,
21153 };
21154 let executor =
21155 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21156 let stop = Arc::new(AtomicBool::new(false));
21157 let scheduler = Arc::clone(&executor);
21158 let (runnable, task) = async_task::spawn(
21159 HotFuture {
21160 stop: Arc::clone(&stop),
21161 },
21162 move |r| scheduler.schedule(r),
21163 );
21164 task.detach();
21165 let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
21166 executor
21167 .blocking_queue
21168 .lock()
21169 .unwrap()
21170 .push(QueuedBlocking {
21171 id: BlockingJobId(u64::MAX),
21172 run: Box::new(move || blocking_tx.send(()).unwrap()),
21173 });
21174
21175 let _driver = OnPoolWorkerGuard::enter(executor.id);
21176 executor.schedule(runnable);
21177 let mut runnable_streak = 0;
21178 for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
21179 assert!(executor.run_one_fair(&mut runnable_streak));
21180 }
21181 blocking_rx
21182 .try_recv()
21183 .expect("an infinite self-waker must yield a bounded quantum to blocking work");
21184
21185 stop.store(true, Ordering::SeqCst);
21186 assert!(executor.run_one_fair(&mut runnable_streak));
21187 }
21188
21189 #[cfg(not(target_family = "wasm"))]
21190 #[test]
21191 fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
21192 use std::sync::mpsc;
21193
21194 struct HotFuture {
21195 stop: Arc<AtomicBool>,
21196 }
21197
21198 impl Future for HotFuture {
21199 type Output = ();
21200
21201 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21202 if self.stop.load(Ordering::SeqCst) {
21203 Poll::Ready(())
21204 } else {
21205 cx.waker().wake_by_ref();
21206 Poll::Pending
21207 }
21208 }
21209 }
21210
21211 let options = RuntimeOptions {
21212 flavor: RuntimeFlavor::MultiThread,
21213 worker_threads: 1,
21214 max_blocking_tasks: 1,
21215 thread_name_prefix: "exact-dependency-fairness".to_string(),
21216 park_deadline: None,
21217 drain_linger: DEFAULT_DRAIN_LINGER,
21218 };
21219 let executor =
21220 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21221 let stop = Arc::new(AtomicBool::new(false));
21222 let (queued_tx, queued_rx) = mpsc::channel();
21223 let (exact_tx, exact_rx) = mpsc::channel();
21224 let owner_executor = Arc::clone(&executor);
21225 let owner_stop = Arc::clone(&stop);
21226 let owner = executor.schedule_blocking(move || {
21227 let scheduler = Arc::clone(&owner_executor);
21228 let hot_stop = Arc::clone(&owner_stop);
21229 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
21230 scheduler.schedule(runnable);
21231 });
21232 owner_executor.schedule(runnable);
21233
21234 let exact_stop = Arc::clone(&owner_stop);
21235 let exact = owner_executor.schedule_blocking(move || {
21236 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
21237 41usize
21238 });
21239 queued_tx.send(()).unwrap();
21240
21241 let mut value = None;
21242 {
21243 let mut future = std::pin::pin!(async {
21244 value = Some(exact.await.unwrap());
21245 hot_task.await;
21246 });
21247 assert_eq!(
21248 owner_executor.block_on(future.as_mut()),
21249 BlockOnOutcome::Completed
21250 );
21251 }
21252 value.unwrap()
21253 });
21254
21255 queued_rx
21256 .recv_timeout(Duration::from_secs(2))
21257 .expect("the exact dependency must be queued behind the saturated owner");
21258 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
21259 stop.store(true, Ordering::SeqCst);
21260 assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
21261 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
21262 assert_eq!(
21263 exact_before_stop,
21264 Some(true),
21265 "a hot local runnable must yield to the exact dependency before it is stopped"
21266 );
21267 }
21268
21269 #[cfg(not(target_family = "wasm"))]
21270 #[test]
21271 fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
21272 use std::sync::mpsc;
21273
21274 struct BlockingPoll {
21275 started: Option<mpsc::Sender<()>>,
21276 released: Arc<(Mutex<bool>, Condvar)>,
21277 }
21278
21279 impl Future for BlockingPoll {
21280 type Output = ();
21281
21282 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21283 if let Some(started) = self.started.take() {
21284 started.send(()).unwrap();
21285 }
21286 let (released, changed) = &*self.released;
21287 let mut released = released
21288 .lock()
21289 .unwrap_or_else(std::sync::PoisonError::into_inner);
21290 while !*released {
21291 released = changed
21292 .wait(released)
21293 .unwrap_or_else(std::sync::PoisonError::into_inner);
21294 }
21295 Poll::Ready(())
21296 }
21297 }
21298
21299 let options = RuntimeOptions {
21300 flavor: RuntimeFlavor::MultiThread,
21301 worker_threads: 3,
21302 max_blocking_tasks: 2,
21303 thread_name_prefix: "exact-before-last-slot".to_string(),
21304 park_deadline: None,
21305 drain_linger: DEFAULT_DRAIN_LINGER,
21306 };
21307 let executor =
21308 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21309
21310 let released = Arc::new((Mutex::new(false), Condvar::new()));
21311 let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
21312 let mut blockers = Vec::new();
21313 for _ in 0..2 {
21314 let scheduler = Arc::clone(&executor);
21315 let blocker_released = Arc::clone(&released);
21316 let started = blocker_started_tx.clone();
21317 let (runnable, task) = async_task::spawn(
21318 BlockingPoll {
21319 started: Some(started),
21320 released: blocker_released,
21321 },
21322 move |runnable| scheduler.schedule(runnable),
21323 );
21324 executor.schedule(runnable);
21325 blockers.push(task);
21326 }
21327 drop(blocker_started_tx);
21328 for _ in 0..2 {
21329 blocker_started_rx
21330 .recv_timeout(Duration::from_secs(2))
21331 .expect("two workers must be gated before the owner starts");
21332 }
21333
21334 let dependency_started = Arc::new(AtomicBool::new(false));
21335 let owner_executor = Arc::clone(&executor);
21336 let owner_dependency_started = Arc::clone(&dependency_started);
21337 let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
21338 let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
21339 let owner_signal = Arc::clone(&dependency_signal);
21340 let owner = executor.schedule_blocking(move || {
21341 let unrelated_signal = Arc::clone(&owner_signal);
21342 let unrelated_started = Arc::clone(&owner_dependency_started);
21343 let unrelated = owner_executor.schedule_blocking(move || {
21344 let observed = unrelated_started.load(Ordering::SeqCst);
21345 unrelated_observed_tx.send(observed).unwrap();
21346 let (ready, changed) = &*unrelated_signal;
21347 let mut ready = ready
21348 .lock()
21349 .unwrap_or_else(std::sync::PoisonError::into_inner);
21350 while !*ready {
21351 ready = changed
21352 .wait(ready)
21353 .unwrap_or_else(std::sync::PoisonError::into_inner);
21354 }
21355 });
21356
21357 let exact_signal = Arc::clone(&owner_signal);
21358 let exact_started = Arc::clone(&owner_dependency_started);
21359 let exact = owner_executor.schedule_blocking(move || {
21360 exact_started.store(true, Ordering::SeqCst);
21361 let (ready, changed) = &*exact_signal;
21362 *ready
21363 .lock()
21364 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
21365 changed.notify_all();
21366 41usize
21367 });
21368
21369 let mut output = None;
21370 {
21371 let mut future = std::pin::pin!(async {
21372 output = Some(exact.await.unwrap());
21373 unrelated.await.unwrap();
21374 });
21375 assert_eq!(
21376 owner_executor.block_on(future.as_mut()),
21377 BlockOnOutcome::Completed
21378 );
21379 }
21380 output.unwrap()
21381 });
21382
21383 let (owner_done_tx, owner_done_rx) = mpsc::channel();
21384 let owner_waiter = std::thread::spawn(move || {
21385 owner_done_tx
21386 .send(futures::executor::block_on(owner))
21387 .unwrap();
21388 });
21389 let (completed_while_gated, owner_result) =
21390 match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
21391 Ok(result) => (true, result),
21392 Err(_) => {
21393 let (released, changed) = &*released;
21396 *released
21397 .lock()
21398 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
21399 changed.notify_all();
21400 (
21401 false,
21402 owner_done_rx
21403 .recv_timeout(Duration::from_secs(2))
21404 .expect("releasing the other workers must let the broken ordering recover"),
21405 )
21406 }
21407 };
21408 let unrelated_observed = unrelated_observed_rx
21409 .recv_timeout(Duration::from_secs(2))
21410 .expect("the unrelated FIFO job must eventually run");
21411
21412 {
21413 let (released, changed) = &*released;
21414 *released
21415 .lock()
21416 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
21417 changed.notify_all();
21418 }
21419 owner_waiter.join().unwrap();
21420 for blocker in blockers {
21421 futures::executor::block_on(blocker);
21422 }
21423 executor.begin_shutdown();
21424 executor.wait_until_scheduler_idle();
21425
21426 assert!(
21427 completed_while_gated,
21428 "the exact dependency must complete while unrelated workers remain gated"
21429 );
21430 assert_eq!(owner_result.unwrap(), 41);
21431 assert!(
21432 unrelated_observed,
21433 "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
21434 );
21435 }
21436
21437 #[cfg(not(target_family = "wasm"))]
21438 #[test]
21439 fn blocking_saturation_preserves_a_runnable_execution_lane() {
21440 use std::{sync::mpsc, time::Duration};
21441
21442 let options = RuntimeOptions {
21443 flavor: RuntimeFlavor::MultiThread,
21444 worker_threads: 2,
21445 max_blocking_tasks: 2,
21446 thread_name_prefix: "blocking-reserve".to_string(),
21447 park_deadline: None,
21448 drain_linger: DEFAULT_DRAIN_LINGER,
21449 }
21450 .validate()
21451 .expect("production options must reserve a runnable lane");
21452 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
21453 let executor =
21454 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21455 let (release_tx, release_rx) = mpsc::channel();
21456 let (started_tx, started_rx) = mpsc::channel();
21457 let first = executor.schedule_blocking(move || {
21458 started_tx.send(()).unwrap();
21459 release_rx.recv().unwrap();
21460 });
21461 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
21462
21463 let (queued_tx, queued_rx) = mpsc::channel();
21464 let second = executor.schedule_blocking(move || {
21465 queued_tx.send(()).unwrap();
21466 });
21467 assert!(
21468 queued_rx.try_recv().is_err(),
21469 "the second blocking job must remain queued while the sole blocking slot is occupied"
21470 );
21471
21472 let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
21473 let scheduler = Arc::clone(&executor);
21474 let (runnable, task) = async_task::spawn(
21475 async move {
21476 runnable_tx.send(()).unwrap();
21477 },
21478 move |r| scheduler.schedule(r),
21479 );
21480 task.detach();
21481 executor.schedule(runnable);
21482 runnable_rx
21483 .recv_timeout(Duration::from_secs(1))
21484 .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
21485
21486 release_tx.send(()).unwrap();
21487 futures::executor::block_on(first).unwrap();
21488 futures::executor::block_on(second).unwrap();
21489 queued_rx
21490 .recv_timeout(Duration::from_secs(1))
21491 .expect("queued blocking work must run after the occupied slot is released");
21492 }
21493
21494 #[cfg(not(target_family = "wasm"))]
21495 #[test]
21496 fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
21497 use std::sync::mpsc;
21505 use std::time::Duration;
21506
21507 struct SpawnOnReady {
21508 executor: Arc<MultiThreadExecutor>,
21509 child_tx: Option<mpsc::Sender<u64>>,
21510 }
21511 impl Future for SpawnOnReady {
21512 type Output = ();
21513 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21514 let child_tx = self.child_tx.take().expect("polled once");
21515 let sched = Arc::clone(&self.executor);
21516 let (runnable, task) = async_task::spawn(
21517 async move {
21518 child_tx.send(21).unwrap();
21519 },
21520 move |r| sched.schedule(r),
21521 );
21522 self.executor.schedule(runnable);
21524 task.detach();
21525 Poll::Ready(())
21526 }
21527 }
21528
21529 let (done_tx, done_rx) = mpsc::channel();
21530 let runner = std::thread::spawn(move || {
21531 let options = RuntimeOptions {
21532 flavor: RuntimeFlavor::MultiThread,
21533 worker_threads: 2,
21534 max_blocking_tasks: 2,
21535 thread_name_prefix: "exit-flush".to_string(),
21536 park_deadline: None,
21537 drain_linger: DEFAULT_DRAIN_LINGER,
21538 };
21539 let executor =
21540 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21541
21542 let (child_tx, child_rx) = mpsc::channel::<u64>();
21543 {
21544 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21547 let mut future = std::pin::pin!(SpawnOnReady {
21548 executor: Arc::clone(&executor),
21549 child_tx: Some(child_tx)
21550 });
21551 executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
21552 }
21553
21554 let value = child_rx
21558 .recv_timeout(Duration::from_secs(5))
21559 .expect("the child scheduled by the final poll was stranded in the LIFO slot");
21560 assert_eq!(value, 21);
21561 let _ = done_tx.send(());
21562 });
21563
21564 match done_rx.recv_timeout(Duration::from_secs(10)) {
21565 Ok(()) => runner.join().unwrap(),
21566 Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
21567 }
21568 }
21569
21570 #[cfg(not(target_family = "wasm"))]
21574 fn spawn_hot_chain(
21575 executor: &Arc<MultiThreadExecutor>,
21576 stop: Arc<AtomicBool>,
21577 hops: Arc<AtomicU64>,
21578 ) {
21579 if stop.load(Ordering::SeqCst) {
21580 return;
21581 }
21582 let link_exec = Arc::clone(executor);
21583 let body = async move {
21584 hops.fetch_add(1, Ordering::SeqCst);
21585 spawn_hot_chain(&link_exec, stop, hops);
21586 };
21587 let sched = Arc::clone(executor);
21588 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21589 executor.schedule(runnable);
21590 task.detach();
21591 }
21592
21593 #[cfg(not(target_family = "wasm"))]
21594 #[test]
21595 fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
21596 use std::sync::mpsc;
21605 use std::time::Duration;
21606
21607 let (done_tx, done_rx) = mpsc::channel();
21608 let runner = std::thread::spawn(move || {
21609 let options = RuntimeOptions {
21610 flavor: RuntimeFlavor::MultiThread,
21611 worker_threads: 1,
21612 max_blocking_tasks: 0,
21615 thread_name_prefix: "coop-budget".to_string(),
21616 park_deadline: None,
21617 drain_linger: DEFAULT_DRAIN_LINGER,
21618 };
21619 let executor =
21620 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21621
21622 let stop = Arc::new(AtomicBool::new(false));
21623 let hops = Arc::new(AtomicU64::new(0));
21624
21625 let m_exec = Arc::clone(&executor);
21629 let m_stop = Arc::clone(&stop);
21630 let m_hops = Arc::clone(&hops);
21631 let m_body = async move {
21632 let (tx, rx) = oneshot::channel::<u64>();
21633 let sched_f = Arc::clone(&m_exec);
21635 let (f_runnable, f_task) = async_task::spawn(
21636 async move {
21637 let _ = tx.send(7);
21638 },
21639 move |r| sched_f.schedule(r),
21640 );
21641 m_exec.schedule(f_runnable);
21642 f_task.detach();
21643 spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
21646
21647 let mut inner = std::pin::pin!(async move {
21648 assert_eq!(
21649 rx.await.unwrap(),
21650 7,
21651 "F must run and complete the awaited future"
21652 );
21653 });
21654 m_exec.block_on(inner.as_mut());
21655 m_stop.store(true, Ordering::SeqCst);
21656 };
21657 let sched_m = Arc::clone(&executor);
21658 let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
21659 executor.schedule(m_runnable);
21660
21661 futures::executor::block_on(m_task);
21662 assert!(
21663 hops.load(Ordering::SeqCst) >= 64,
21664 "the chain must have been genuinely hot (>= one budget window) before F ran"
21665 );
21666 let _ = done_tx.send(());
21667 });
21668
21669 match done_rx.recv_timeout(Duration::from_secs(10)) {
21670 Ok(()) => runner.join().unwrap(),
21671 Err(error) => panic!(
21672 "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
21673 ),
21674 }
21675 }
21676
21677 #[cfg(not(target_family = "wasm"))]
21678 #[test]
21679 fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
21680 use std::sync::mpsc;
21681
21682 struct RefillLifo {
21683 executor: Arc<MultiThreadExecutor>,
21684 fifo_ran: Arc<AtomicBool>,
21685 }
21686
21687 impl Future for RefillLifo {
21688 type Output = ();
21689
21690 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21691 if self.fifo_ran.load(Ordering::SeqCst) {
21692 return Poll::Ready(());
21693 }
21694 let scheduler = Arc::clone(&self.executor);
21695 let (runnable, task) =
21696 async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
21697 self.executor.schedule(runnable);
21698 task.detach();
21699 Poll::Pending
21700 }
21701 }
21702
21703 let (done_tx, done_rx) = mpsc::channel();
21704 let runner = std::thread::spawn(move || {
21705 let options = RuntimeOptions {
21706 flavor: RuntimeFlavor::MultiThread,
21707 worker_threads: 1,
21708 max_blocking_tasks: 0,
21709 thread_name_prefix: "forced-fifo".to_string(),
21710 park_deadline: None,
21711 drain_linger: DEFAULT_DRAIN_LINGER,
21712 };
21713 let executor =
21714 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21715 executor
21716 .active_drainers
21717 .store(executor.max_drainers, Ordering::SeqCst);
21718
21719 let fifo_ran = Arc::new(AtomicBool::new(false));
21720 let fifo_flag = Arc::clone(&fifo_ran);
21721 let scheduler = Arc::clone(&executor);
21722 let (fifo, task) = async_task::spawn(
21723 async move {
21724 fifo_flag.store(true, Ordering::SeqCst);
21725 },
21726 move |runnable| scheduler.schedule(runnable),
21727 );
21728 task.detach();
21729 executor.metrics.runnable_scheduled();
21730 executor.push_fifo_raw(fifo);
21731
21732 {
21733 let _driver = OnPoolWorkerGuard::enter(executor.id);
21734 let mut future = std::pin::pin!(RefillLifo {
21735 executor: Arc::clone(&executor),
21736 fifo_ran: Arc::clone(&fifo_ran)
21737 });
21738 executor.block_on(future.as_mut());
21739 while executor.run_one() {}
21740 }
21741 executor.active_drainers.store(0, Ordering::SeqCst);
21742 assert!(fifo_ran.load(Ordering::SeqCst));
21743 done_tx.send(()).unwrap();
21744 });
21745 done_rx
21746 .recv_timeout(Duration::from_secs(3))
21747 .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
21748 runner.join().unwrap();
21749 }
21750
21751 #[cfg(not(target_family = "wasm"))]
21752 #[test]
21753 fn blocking_closure_spawn_is_not_stranded_in_slot() {
21754 use std::sync::mpsc;
21761 use std::time::Duration;
21762
21763 let (done_tx, done_rx) = mpsc::channel();
21764 let runner = std::thread::spawn(move || {
21765 let options = RuntimeOptions {
21766 flavor: RuntimeFlavor::MultiThread,
21767 worker_threads: 2,
21768 max_blocking_tasks: 2,
21769 thread_name_prefix: "blk-spawn-wait".to_string(),
21770 park_deadline: None,
21771 drain_linger: DEFAULT_DRAIN_LINGER,
21772 };
21773 let executor =
21774 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21775
21776 let blk_exec = Arc::clone(&executor);
21779 let handle = executor.schedule_blocking(move || {
21780 let (tx, rx) = mpsc::channel::<u64>();
21781 let sched = Arc::clone(&blk_exec);
21782 let (runnable, task) = async_task::spawn(
21783 async move {
21784 tx.send(21).unwrap();
21785 },
21786 move |r| sched.schedule(r),
21787 );
21788 blk_exec.schedule(runnable);
21789 task.detach();
21790 rx.recv_timeout(Duration::from_secs(5))
21791 .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
21792 });
21793 assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
21794
21795 let (v_tx, v_rx) = mpsc::channel::<u64>();
21798 let v_exec = Arc::clone(&executor);
21799 let v_handle = executor.schedule_blocking(move || {
21800 let sched = Arc::clone(&v_exec);
21801 let (runnable, task) = async_task::spawn(
21802 async move {
21803 v_tx.send(42).unwrap();
21804 },
21805 move |r| sched.schedule(r),
21806 );
21807 v_exec.schedule(runnable);
21808 task.detach();
21809 1u64
21810 });
21811 assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
21812 assert_eq!(
21813 v_rx
21814 .recv_timeout(Duration::from_secs(5))
21815 .expect("fire-and-forget child must complete"),
21816 42
21817 );
21818 let _ = done_tx.send(());
21819 });
21820
21821 match done_rx.recv_timeout(Duration::from_secs(15)) {
21822 Ok(()) => runner.join().unwrap(),
21823 Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
21824 }
21825 }
21826
21827 #[cfg(not(target_family = "wasm"))]
21828 #[test]
21829 fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
21830 use std::sync::{Barrier, mpsc};
21836 use std::time::Duration;
21837
21838 let (done_tx, done_rx) = mpsc::channel();
21839 let runner = std::thread::spawn(move || {
21840 let metrics = Arc::new(RuntimeMetrics::default());
21841 let options = RuntimeOptions {
21842 flavor: RuntimeFlavor::MultiThread,
21843 worker_threads: 2,
21844 max_blocking_tasks: 2,
21845 thread_name_prefix: "rd1-nested-blk".to_string(),
21846 park_deadline: None,
21847 drain_linger: DEFAULT_DRAIN_LINGER,
21848 };
21849 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21850
21851 let outer_count = 2usize; let barrier = Arc::new(Barrier::new(outer_count));
21853
21854 let mut handles = Vec::new();
21855 for _ in 0..outer_count {
21856 let exec = Arc::clone(&executor);
21857 let barrier = Arc::clone(&barrier);
21858 let handle = executor.schedule_blocking(move || {
21859 barrier.wait();
21861 let inner_exec = Arc::clone(&exec);
21862 let mut value = 0usize;
21863 {
21864 let mut inner = std::pin::pin!(async {
21865 value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21866 });
21867 exec.block_on(inner.as_mut());
21868 }
21869 value
21870 });
21871 handles.push(handle);
21872 }
21873
21874 for handle in handles {
21875 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
21876 }
21877 let _ = done_tx.send(());
21878 });
21879
21880 match done_rx.recv_timeout(Duration::from_secs(10)) {
21881 Ok(()) => runner.join().unwrap(),
21882 Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
21883 }
21884 }
21885
21886 #[cfg(not(target_family = "wasm"))]
21887 #[test]
21888 fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
21889 let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
21890 let owner = executor.fresh_owner_token();
21891 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21892 let job = BlockingJobId(u64::MAX - 5);
21893 let dependency = TaskDependency::default();
21894 dependency.set_owned(BlockingDependency { job, owner: None });
21895 let ran = Arc::new(AtomicBool::new(false));
21896 let job_ran = Arc::clone(&ran);
21897 executor
21898 .blocking_queue
21899 .lock()
21900 .unwrap()
21901 .push(QueuedBlocking {
21902 id: job,
21903 run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
21904 });
21905
21906 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
21907 assert!(
21908 !executor.try_owned_blocking_over_cap(&dependency),
21909 "an untagged worker must not infer ancestry from a sole active owner"
21910 );
21911 assert!(!ran.load(Ordering::SeqCst));
21912 assert!(
21913 executor
21914 .blocking_queue
21915 .lock()
21916 .unwrap()
21917 .jobs
21918 .contains_key(&job),
21919 "rejected owner inference must leave the exact job queued"
21920 );
21921 }
21922
21923 #[cfg(not(target_family = "wasm"))]
21924 #[test]
21925 fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
21926 use std::sync::{Barrier, mpsc};
21927
21928 let options = RuntimeOptions {
21929 flavor: RuntimeFlavor::MultiThread,
21930 worker_threads: 2,
21931 max_blocking_tasks: 2,
21932 thread_name_prefix: "exact-owner-frame".to_string(),
21933 park_deadline: None,
21934 drain_linger: DEFAULT_DRAIN_LINGER,
21935 };
21936 let executor =
21937 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21938 let owners_saturated = Arc::new(Barrier::new(3));
21939 let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
21940 let begin_lending = Arc::new(Barrier::new(3));
21941 let release_dependencies = Arc::new(Barrier::new(3));
21942 let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
21943 let mut owners = Vec::new();
21944
21945 for owner_index in 0..2 {
21946 let owner_executor = Arc::clone(&executor);
21947 let owners_saturated = Arc::clone(&owners_saturated);
21948 let dependency_queued_tx = dependency_queued_tx.clone();
21949 let begin_lending = Arc::clone(&begin_lending);
21950 let release_dependencies = Arc::clone(&release_dependencies);
21951 let dependency_started_tx = dependency_started_tx.clone();
21952 owners.push(executor.schedule_blocking(move || {
21953 let owner_thread = std::thread::current().id();
21954 owners_saturated.wait();
21955 let child_started = dependency_started_tx.clone();
21956 let child_release = Arc::clone(&release_dependencies);
21957 let child = owner_executor.schedule_blocking(move || {
21958 assert_eq!(
21959 std::thread::current().id(),
21960 owner_thread,
21961 "an owner frame must execute only its own exact dependency on its lent lane"
21962 );
21963 child_started.send(owner_index).unwrap();
21964 child_release.wait();
21965 owner_index
21966 });
21967 dependency_queued_tx.send(()).unwrap();
21968 begin_lending.wait();
21969
21970 let mut result = None;
21971 {
21972 let mut future = std::pin::pin!(async {
21973 result = Some(child.await.unwrap());
21974 });
21975 assert_eq!(
21976 owner_executor.block_on(future.as_mut()),
21977 BlockOnOutcome::Completed
21978 );
21979 }
21980 result.unwrap()
21981 }));
21982 }
21983 drop(dependency_queued_tx);
21984 drop(dependency_started_tx);
21985 owners_saturated.wait();
21986
21987 for _ in 0..2 {
21988 dependency_queued_rx
21989 .recv_timeout(Duration::from_secs(2))
21990 .expect("both owner-scoped dependencies must be queued while the cap is saturated");
21991 }
21992 let (unrelated_tx, unrelated_rx) = mpsc::channel();
21993 let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
21994 begin_lending.wait();
21995
21996 let mut started = Vec::new();
21997 for _ in 0..2 {
21998 started.push(
21999 dependency_started_rx
22000 .recv_timeout(Duration::from_secs(2))
22001 .expect("each owner must service its own exact dependency"),
22002 );
22003 }
22004 started.sort_unstable();
22005 assert_eq!(started, vec![0, 1]);
22006 assert!(
22007 unrelated_rx.try_recv().is_err(),
22008 "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
22009 );
22010
22011 release_dependencies.wait();
22012 for (owner_index, owner) in owners.into_iter().enumerate() {
22013 assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
22014 }
22015 unrelated_rx
22016 .recv_timeout(Duration::from_secs(2))
22017 .expect("the unrelated job must run after an owner releases a counted lane");
22018 futures::executor::block_on(unrelated).unwrap();
22019 }
22020
22021 #[cfg(not(target_family = "wasm"))]
22022 #[test]
22023 fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
22024 use std::sync::{Mutex, mpsc};
22025
22026 const DEPENDENCY_COUNT: usize = 4;
22027
22028 let options = RuntimeOptions {
22029 flavor: RuntimeFlavor::MultiThread,
22030 worker_threads: 3,
22031 max_blocking_tasks: 1,
22032 thread_name_prefix: "parked-unrelated-worker".to_string(),
22033 park_deadline: None,
22034 drain_linger: DEFAULT_DRAIN_LINGER,
22035 };
22036 let executor =
22037 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22038 let (parked_tx, parked_rx) = mpsc::channel();
22039 let (release_parked_tx, release_parked_rx) = mpsc::channel();
22040 executor.pool.spawn(move || {
22041 parked_tx.send(()).unwrap();
22042 release_parked_rx.recv().unwrap();
22043 });
22044 parked_rx
22045 .recv_timeout(Duration::from_secs(2))
22046 .expect("the unrelated Rayon worker must remain occupied");
22047
22048 let owner_executor = Arc::clone(&executor);
22049 let owner = executor.schedule_blocking(move || {
22050 let owner = BLOCKING_OWNER
22051 .with(std::cell::Cell::get)
22052 .expect("the blocking closure must carry its exact owner frame");
22053 let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
22054 let results = Arc::new(Mutex::new(Vec::new()));
22055 let descendant_results = Arc::clone(&results);
22056 let descendant_executor = Arc::clone(&owner_executor);
22057 rayon::scope(move |scope| {
22058 scope.spawn(move |_| {
22059 assert_eq!(
22060 BLOCKING_OWNER.with(std::cell::Cell::get),
22061 None,
22062 "the parked owner must force its descendant onto the remaining worker"
22063 );
22064 let _owner = BlockingOwnerGuard::enter(Some(owner));
22065 descendant_started_tx.send(()).unwrap();
22066 for value in 0..DEPENDENCY_COUNT {
22067 let handle = descendant_executor.schedule_blocking(move || value);
22068 let mut result = None;
22069 {
22070 let mut future = std::pin::pin!(async {
22071 result = Some(handle.await.unwrap());
22072 });
22073 assert_eq!(
22074 descendant_executor.block_on(future.as_mut()),
22075 BlockOnOutcome::Completed
22076 );
22077 }
22078 descendant_results.lock().unwrap().push(result.unwrap());
22079 }
22080 });
22081 descendant_started_rx
22082 .recv_timeout(Duration::from_secs(2))
22083 .expect("the nested descendant must start on the only unoccupied worker");
22084 });
22085 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22086 });
22087
22088 assert_eq!(
22089 futures::executor::block_on(owner).unwrap(),
22090 (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
22091 );
22092
22093 let (idle_tx, idle_rx) = mpsc::channel();
22094 let idle_executor = Arc::clone(&executor);
22095 let idle_waiter = std::thread::spawn(move || {
22096 idle_executor.begin_shutdown();
22097 idle_executor.wait_until_scheduler_idle();
22098 idle_tx.send(()).unwrap();
22099 });
22100 idle_rx
22101 .recv_timeout(Duration::from_secs(2))
22102 .expect("an unrelated parked worker must not retain owner-probe idle accounting");
22103
22104 release_parked_tx.send(()).unwrap();
22105 idle_waiter.join().unwrap();
22106 }
22107
22108 #[cfg(not(target_family = "wasm"))]
22109 #[test]
22110 fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
22111 use std::sync::{Condvar, Mutex, mpsc};
22112
22113 let options = RuntimeOptions {
22114 flavor: RuntimeFlavor::MultiThread,
22115 worker_threads: 3,
22116 max_blocking_tasks: 1,
22117 thread_name_prefix: "same-owner-rearm".to_string(),
22118 park_deadline: None,
22119 drain_linger: DEFAULT_DRAIN_LINGER,
22120 };
22121 let executor =
22122 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22123 let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
22124 let (job_started_tx, job_started_rx) = mpsc::channel();
22125 let owner_executor = Arc::clone(&executor);
22126 let owner_releases = Arc::clone(&releases);
22127 let owner = executor.schedule_blocking(move || {
22128 let owner = BLOCKING_OWNER
22129 .with(std::cell::Cell::get)
22130 .expect("the blocking closure must carry its exact owner frame");
22131 let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
22132 let results = Arc::new(Mutex::new(Vec::new()));
22133 let scope_results = Arc::clone(&results);
22134 rayon::scope(move |scope| {
22135 for index in 0..2 {
22136 let descendant_ready_tx = descendant_ready_tx.clone();
22137 let descendant_executor = Arc::clone(&owner_executor);
22138 let descendant_results = Arc::clone(&scope_results);
22139 let job_started_tx = job_started_tx.clone();
22140 let releases = Arc::clone(&owner_releases);
22141 scope.spawn(move |_| {
22142 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22143 let _owner = BlockingOwnerGuard::enter(Some(owner));
22144 descendant_ready_tx.send(()).unwrap();
22145 let handle = descendant_executor.schedule_blocking(move || {
22146 job_started_tx.send(index).unwrap();
22147 let (released, changed) = &*releases;
22148 let mut state = released.lock().unwrap();
22149 while !state[index] {
22150 state = changed.wait(state).unwrap();
22151 }
22152 index
22153 });
22154 let mut result = None;
22155 {
22156 let mut future = std::pin::pin!(async {
22157 result = Some(handle.await.unwrap());
22158 });
22159 assert_eq!(
22160 descendant_executor.block_on(future.as_mut()),
22161 BlockOnOutcome::Completed
22162 );
22163 }
22164 descendant_results.lock().unwrap().push(result.unwrap());
22165 });
22166 }
22167 drop(descendant_ready_tx);
22168 for _ in 0..2 {
22169 descendant_ready_rx
22170 .recv_timeout(Duration::from_secs(2))
22171 .expect("both descendants must be stolen before the owner leaves its Rayon scope");
22172 }
22173 });
22174 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22175 });
22176
22177 let first = job_started_rx
22178 .recv_timeout(Duration::from_secs(2))
22179 .expect("one exact dependency must borrow the owner lane");
22180 assert!(
22181 job_started_rx
22182 .recv_timeout(Duration::from_millis(100))
22183 .is_err(),
22184 "one owner frame must not service two dependencies concurrently"
22185 );
22186 let unrelated_parker = Arc::new(DriverParker::default());
22187 executor
22188 .parked_drivers
22189 .register_with_role(&unrelated_parker, true, None);
22190 {
22191 let (released, changed) = &*releases;
22192 released.lock().unwrap()[first] = true;
22193 changed.notify_all();
22194 }
22195
22196 let second = job_started_rx
22197 .recv_timeout(Duration::from_secs(2))
22198 .expect("completion must rearm one parked same-owner dependency");
22199 assert_ne!(second, first);
22200 assert!(
22201 !unrelated_parker.consume_permit(),
22202 "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
22203 );
22204 executor.parked_drivers.deregister(&unrelated_parker);
22205 {
22206 let (released, changed) = &*releases;
22207 released.lock().unwrap()[second] = true;
22208 changed.notify_all();
22209 }
22210
22211 let mut results = futures::executor::block_on(owner).unwrap();
22212 results.sort_unstable();
22213 assert_eq!(results, vec![0, 1]);
22214 }
22215
22216 #[cfg(not(target_family = "wasm"))]
22217 #[test]
22218 fn failed_exact_claim_rearms_parked_same_owner_dependency() {
22219 let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
22220 let owner = executor.fresh_owner_token();
22221 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22222 let failed_dependency = Arc::new(TaskDependency::default());
22223 failed_dependency.set_owned(BlockingDependency {
22224 job: BlockingJobId(u64::MAX - 21),
22225 owner: Some(owner),
22226 });
22227 let parked_dependency = Arc::new(TaskDependency::default());
22228 parked_dependency.set_owned(BlockingDependency {
22229 job: BlockingJobId(u64::MAX - 22),
22230 owner: Some(owner),
22231 });
22232 let parker = Arc::new(DriverParker::default());
22233
22234 let queue = executor.blocking_queue.lock().unwrap();
22235 let claimant_executor = Arc::clone(&executor);
22236 let claimant_dependency = Arc::clone(&failed_dependency);
22237 let claimant = std::thread::spawn(move || {
22238 let _owner = BlockingOwnerGuard::enter(Some(owner));
22239 claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
22240 });
22241 wait_until("the failed claimant to reserve the owner lane", || {
22242 !executor.active_blocking_owners.is_available(owner)
22243 });
22244 executor
22245 .parked_drivers
22246 .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
22247 failed_dependency.clear();
22248 drop(queue);
22249
22250 assert!(
22251 !claimant.join().unwrap(),
22252 "the cancelled dependency must not execute"
22253 );
22254 assert!(
22255 parker.consume_permit(),
22256 "releasing a failed exact claim must hand the owner lane to another parked dependency"
22257 );
22258 }
22259
22260 #[cfg(not(target_family = "wasm"))]
22261 #[test]
22262 fn exact_owner_lending_does_not_start_blocking_after_stop() {
22263 use std::sync::mpsc;
22264
22265 let options = RuntimeOptions {
22266 flavor: RuntimeFlavor::MultiThread,
22267 worker_threads: 2,
22268 max_blocking_tasks: 1,
22269 thread_name_prefix: "stopped-exact-owner".to_string(),
22270 park_deadline: None,
22271 drain_linger: DEFAULT_DRAIN_LINGER,
22272 };
22273 let executor =
22274 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22275 let ran = Arc::new(AtomicBool::new(false));
22276 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
22277 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22278 let owner_executor = Arc::clone(&executor);
22279 let child_ran = Arc::clone(&ran);
22280 let owner = executor.schedule_blocking(move || {
22281 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
22282 *slot.borrow_mut() = Some(Box::new(move || {
22283 claim_entered_tx.send(()).unwrap();
22284 release_claim_rx.recv().unwrap();
22285 }));
22286 });
22287 let child = owner_executor.schedule_blocking(move || {
22288 child_ran.store(true, Ordering::SeqCst);
22289 });
22290 let mut future = std::pin::pin!(async move {
22291 let _ = child.await;
22292 });
22293 owner_executor.block_on(future.as_mut())
22294 });
22295
22296 claim_entered_rx
22297 .recv_timeout(Duration::from_secs(2))
22298 .expect("the cooperative driver must pause before the exact-owner queue claim");
22299 assert_eq!(
22300 executor.blocking_queue.lock().unwrap().jobs.len(),
22301 1,
22302 "the exact dependency must remain queued before stop publication"
22303 );
22304
22305 executor.stop.begin_shutdown();
22308 release_claim_tx.send(()).unwrap();
22309
22310 assert_eq!(
22311 futures::executor::block_on(owner).unwrap(),
22312 BlockOnOutcome::Stopped,
22313 "the cooperative driver must stop instead of executing its queued dependency"
22314 );
22315 assert!(
22316 !ran.load(Ordering::SeqCst),
22317 "exact-owner lending started blocking work after stop"
22318 );
22319 assert_eq!(
22320 executor.blocking_queue.lock().unwrap().jobs.len(),
22321 1,
22322 "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
22323 );
22324
22325 executor.begin_shutdown();
22326 executor.wait_until_scheduler_idle();
22327 assert!(executor.blocking_queue.lock().unwrap().is_empty());
22328 assert!(!ran.load(Ordering::SeqCst));
22329 }
22330
22331 #[cfg(not(target_family = "wasm"))]
22332 #[test]
22333 fn exact_owner_lending_attempts_scale_linearly_with_topology() {
22334 const DEPENDENCY_COUNT: usize = 1024;
22335
22336 let options = RuntimeOptions {
22337 flavor: RuntimeFlavor::MultiThread,
22338 worker_threads: 2,
22339 max_blocking_tasks: 1,
22340 thread_name_prefix: "linear-owner-lending".to_string(),
22341 park_deadline: None,
22342 drain_linger: DEFAULT_DRAIN_LINGER,
22343 };
22344 let executor =
22345 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22346 let completed = Arc::new(AtomicUsize::new(0));
22347
22348 for index in 0..DEPENDENCY_COUNT {
22349 let owner = executor.fresh_owner_token();
22350 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22351 let dependency = TaskDependency::new(u64::MAX, Some(owner));
22352 let blocking = BlockingDependency {
22353 job: BlockingJobId(u64::MAX - index as u64),
22354 owner: Some(owner),
22355 };
22356 dependency.set_owned(blocking);
22357 let completed = Arc::clone(&completed);
22358 executor
22359 .blocking_queue
22360 .lock()
22361 .unwrap()
22362 .push(QueuedBlocking {
22363 id: blocking.job,
22364 run: Box::new(move || {
22365 completed.fetch_add(1, Ordering::SeqCst);
22366 }),
22367 });
22368
22369 let _owner = BlockingOwnerGuard::enter(Some(owner));
22370 assert!(executor.try_owned_blocking_over_cap(&dependency));
22371 }
22372
22373 assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
22374 assert_eq!(
22375 executor.owner_lending_attempts.load(Ordering::Relaxed),
22376 DEPENDENCY_COUNT,
22377 "targeted lending must perform one O(1) claim attempt per exact dependency"
22378 );
22379 let queue = executor.blocking_queue.lock().unwrap();
22380 assert!(queue.is_empty());
22381 assert_eq!(queue.head, None);
22382 assert_eq!(queue.tail, None);
22383 }
22384
22385 #[cfg(not(target_family = "wasm"))]
22386 #[test]
22387 fn one_dependency_interspersed_owner_publications_stays_linear() {
22388 const PUBLICATION_COUNT: usize = 1024;
22389
22390 let executor = multi_thread_executor(2, None, "linear-publication-lending");
22391 let owner = executor.fresh_owner_token();
22392 let wrong_owner = executor.fresh_owner_token();
22393 let dependency = TaskDependency::new(u64::MAX, Some(owner));
22394 let completed = Arc::new(AtomicUsize::new(0));
22395 for index in 0..PUBLICATION_COUNT {
22396 let cancelled_source = TaskDependency::default();
22397 cancelled_source.set_owned(BlockingDependency {
22398 job: BlockingJobId(u64::MAX - 100_000 - index as u64),
22399 owner: Some(owner),
22400 });
22401 dependency.set_borrowed(
22402 &cancelled_source
22403 .get()
22404 .expect("the source publication must initially be live"),
22405 );
22406 cancelled_source.clear();
22407
22408 dependency.set_owned(BlockingDependency {
22409 job: BlockingJobId(u64::MAX - 200_000 - index as u64),
22410 owner: Some(wrong_owner),
22411 });
22412 let blocking = BlockingDependency {
22413 job: BlockingJobId(u64::MAX - 1_000 - index as u64),
22414 owner: Some(owner),
22415 };
22416 dependency.set_owned(blocking);
22417 let completed = Arc::clone(&completed);
22418 executor
22419 .blocking_queue
22420 .lock()
22421 .unwrap()
22422 .push(QueuedBlocking {
22423 id: blocking.job,
22424 run: Box::new(move || {
22425 completed.fetch_add(1, Ordering::SeqCst);
22426 }),
22427 });
22428 }
22429 dependency.reset_entry_visits();
22430
22431 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22432 let _owner = BlockingOwnerGuard::enter(Some(owner));
22433 for _ in 0..PUBLICATION_COUNT {
22434 assert!(
22435 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22436 "the owner-handoff predicate must find the next exact publication"
22437 );
22438 assert!(
22439 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
22440 "the park-availability predicate must find the next serviceable publication"
22441 );
22442 assert!(executor.try_owned_blocking_over_cap(&dependency));
22443 }
22444
22445 assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
22446 assert!(
22447 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22448 "wrong-owner publications must not satisfy an exact handoff"
22449 );
22450 assert!(
22451 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
22452 "wrong-owner publications must not make the owner's lane appear serviceable"
22453 );
22454 assert!(
22455 dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
22456 "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
22457 dependency.entry_visits()
22458 );
22459 assert!(executor.blocking_queue.lock().unwrap().is_empty());
22460 }
22461
22462 #[cfg(not(target_family = "wasm"))]
22463 #[test]
22464 fn binding_unowned_dependency_updates_exact_owner_predicate() {
22465 let owner = BlockingOwnerToken {
22466 executor_id: u64::MAX - 200,
22467 frame: u64::MAX - 201,
22468 };
22469 let dependency = TaskDependency::default();
22470 let blocking = BlockingDependency {
22471 job: BlockingJobId(u64::MAX - 202),
22472 owner: None,
22473 };
22474 dependency.set_owned(blocking);
22475
22476 assert!(
22477 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22478 "an unowned publication must not satisfy an exact owner handoff"
22479 );
22480 assert!(
22481 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
22482 "an unowned publication is available to the ambient owner"
22483 );
22484
22485 let selected = dependency
22486 .select_for_owner(owner)
22487 .expect("the owner must bind the unowned publication");
22488 assert_eq!(selected.publication.dependency.owner, Some(owner));
22489 assert!(
22490 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22491 "binding must immediately publish the entry to exact-owner predicates"
22492 );
22493 assert!(dependency.try_claim(&selected));
22494 assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
22495 }
22496
22497 #[cfg(not(target_family = "wasm"))]
22498 #[test]
22499 fn owner_selection_preserves_unowned_and_exact_publication_order() {
22500 let owner = BlockingOwnerToken {
22501 executor_id: u64::MAX - 210,
22502 frame: u64::MAX - 211,
22503 };
22504 let dependency = TaskDependency::default();
22505 let first = BlockingDependency {
22506 job: BlockingJobId(u64::MAX - 212),
22507 owner: None,
22508 };
22509 let second = BlockingDependency {
22510 job: BlockingJobId(u64::MAX - 213),
22511 owner: Some(owner),
22512 };
22513 dependency.set_owned(first);
22514 dependency.set_owned(second);
22515
22516 let selected = dependency
22517 .select_for_owner(owner)
22518 .expect("the first publication must be found");
22519 assert_eq!(
22520 selected.publication.dependency,
22521 BlockingDependency {
22522 owner: Some(owner),
22523 ..first
22524 }
22525 );
22526 assert!(dependency.try_claim(&selected));
22527
22528 let selected = dependency
22529 .select_for_owner(owner)
22530 .expect("the second publication must remain eligible");
22531 assert_eq!(selected.publication.dependency, second);
22532 }
22533
22534 #[cfg(not(target_family = "wasm"))]
22535 #[test]
22536 fn blocking_queue_exact_removal_reclaims_fifo_links() {
22537 const JOB_COUNT: u64 = 4096;
22538
22539 let mut queue = BlockingQueue {
22540 closed: false,
22541 head: None,
22542 tail: None,
22543 jobs: FxHashMap::default(),
22544 };
22545 for id in 1..=JOB_COUNT {
22546 queue.push(QueuedBlocking {
22547 id: BlockingJobId(id),
22548 run: Box::new(|| {}),
22549 });
22550 }
22551 for id in (2..=JOB_COUNT).step_by(2) {
22552 drop(
22553 queue
22554 .remove(BlockingJobId(id))
22555 .expect("even job must remain indexed"),
22556 );
22557 }
22558 for id in (1..=JOB_COUNT).step_by(2) {
22559 drop(
22560 queue
22561 .remove(BlockingJobId(id))
22562 .expect("odd job must remain indexed"),
22563 );
22564 }
22565 assert!(queue.jobs.is_empty());
22566 assert_eq!(queue.head, None);
22567 assert_eq!(queue.tail, None);
22568
22569 let order = Arc::new(Mutex::new(Vec::new()));
22570 for id in 1..=3 {
22571 let order = Arc::clone(&order);
22572 queue.push(QueuedBlocking {
22573 id: BlockingJobId(id),
22574 run: Box::new(move || order.lock().unwrap().push(id)),
22575 });
22576 }
22577 drop(
22578 queue
22579 .remove(BlockingJobId(2))
22580 .expect("middle job must be removable"),
22581 );
22582 queue.pop_front().expect("FIFO head must remain linked")();
22583 queue.pop_front().expect("FIFO tail must remain linked")();
22584 assert!(queue.pop_front().is_none());
22585 assert_eq!(*order.lock().unwrap(), vec![1, 3]);
22586 assert_eq!(queue.head, None);
22587 assert_eq!(queue.tail, None);
22588 }
22589
22590 #[cfg(not(target_family = "wasm"))]
22591 #[test]
22592 fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
22593 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
22594
22595 if std::env::var_os(CHILD_ENV).is_some() {
22596 configure(RuntimeOptions {
22597 flavor: RuntimeFlavor::MultiThread,
22598 worker_threads: 2,
22599 max_blocking_tasks: 1,
22600 thread_name_prefix: "dependency-lending-metrics".to_string(),
22601 park_deadline: None,
22602 drain_linger: DEFAULT_DRAIN_LINGER,
22603 })
22604 .unwrap();
22605
22606 let outer = spawn_blocking(|| {
22607 let inner = spawn_blocking(|| 42usize);
22608 block_on(inner).unwrap()
22609 });
22610 assert_eq!(block_on(outer).unwrap(), 42);
22611
22612 shutdown().unwrap();
22617 let snapshot = metrics();
22618 assert_eq!(snapshot.blocking_tasks_started, 2);
22619 assert_eq!(snapshot.blocking_tasks_completed, 2);
22620 assert_eq!(snapshot.active_blocking_tasks, 0);
22621 assert_eq!(
22622 snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
22623 "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
22624 );
22625 return;
22626 }
22627
22628 let output = std::process::Command::new(std::env::current_exe().unwrap())
22629 .arg("--exact")
22630 .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
22631 .arg("--nocapture")
22632 .env(CHILD_ENV, "1")
22633 .output()
22634 .expect("the dependency-lending metrics subprocess must start");
22635 assert!(
22636 output.status.success(),
22637 "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
22638 output.status.code(),
22639 String::from_utf8_lossy(&output.stdout),
22640 String::from_utf8_lossy(&output.stderr)
22641 );
22642 }
22643
22644 #[cfg(not(target_family = "wasm"))]
22645 #[test]
22646 fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
22647 use std::sync::mpsc;
22648
22649 let (done_tx, done_rx) = mpsc::channel();
22650 let runner = std::thread::spawn(move || {
22651 let options = RuntimeOptions {
22652 flavor: RuntimeFlavor::MultiThread,
22653 worker_threads: 2,
22654 max_blocking_tasks: 1,
22655 thread_name_prefix: "exact-blocking-job".to_string(),
22656 park_deadline: None,
22657 drain_linger: DEFAULT_DRAIN_LINGER,
22658 };
22659 let executor =
22660 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22661 let outer_exec = Arc::clone(&executor);
22662 let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
22663 let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
22664 let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
22665 let outer = executor.schedule_blocking(move || {
22666 let sibling = outer_exec.schedule_blocking(move || {
22667 sibling_started_tx.send(()).unwrap();
22668 release_sibling_rx.recv().unwrap();
22669 });
22670 let awaited = outer_exec.schedule_blocking(move || {
22671 awaited_ran_tx.send(()).unwrap();
22672 17usize
22673 });
22674 let mut value = None;
22675 {
22676 let mut future = std::pin::pin!(async {
22677 value = Some(awaited.await.unwrap());
22678 });
22679 outer_exec.block_on(future.as_mut());
22680 }
22681 (sibling, value.unwrap())
22682 });
22683
22684 awaited_ran_rx
22685 .recv_timeout(Duration::from_secs(2))
22686 .expect("the exact awaited job must bypass the earlier detached sibling");
22687 let (sibling, value) = futures::executor::block_on(outer).unwrap();
22688 assert_eq!(value, 17);
22689 sibling_started_rx
22690 .recv_timeout(Duration::from_secs(2))
22691 .expect("the sibling must run after the owner releases its counted slot");
22692 release_sibling_tx.send(()).unwrap();
22693 futures::executor::block_on(sibling).unwrap();
22694 done_tx.send(()).unwrap();
22695 });
22696 done_rx
22697 .recv_timeout(Duration::from_secs(5))
22698 .expect("same-owner sibling selection deadlocked the exact awaited job");
22699 runner.join().unwrap();
22700 }
22701
22702 #[cfg(not(target_family = "wasm"))]
22703 #[test]
22704 fn blocking_dependency_propagates_through_async_join_lineage() {
22705 use std::sync::mpsc;
22706
22707 let (done_tx, done_rx) = mpsc::channel();
22708 let runner = std::thread::spawn(move || {
22709 let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
22710 let owner_controller = Arc::clone(&controller);
22711 let (child_tx, child_rx) = mpsc::channel();
22712 let outer = controller
22713 .try_spawn_blocking(move || {
22714 let async_controller = Arc::clone(&owner_controller);
22715 let async_child = owner_controller
22716 .try_spawn(async move {
22717 async_controller
22718 .try_spawn_blocking(move || {
22719 child_tx.send(()).unwrap();
22720 23usize
22721 })
22722 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
22723 .await
22724 .unwrap()
22725 })
22726 .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
22727 let mut value = None;
22728 {
22729 let mut future = std::pin::pin!(async {
22730 value = Some(async_child.await.unwrap());
22731 });
22732 owner_controller.block_on(future.as_mut());
22733 }
22734 value.unwrap()
22735 })
22736 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22737
22738 child_rx
22739 .recv_timeout(Duration::from_secs(2))
22740 .expect("the async descendant's exact blocking dependency must inherit the owner lane");
22741 assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
22742 controller.shutdown().unwrap();
22743 done_tx.send(()).unwrap();
22744 });
22745 done_rx
22746 .recv_timeout(Duration::from_secs(5))
22747 .expect("blocking dependency lineage deadlocked");
22748 runner.join().unwrap();
22749 }
22750
22751 #[cfg(not(target_family = "wasm"))]
22752 #[test]
22753 fn blocking_dependency_does_not_escape_a_serviced_runnable() {
22754 use std::sync::mpsc;
22755
22756 let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
22757 let outer_controller = Arc::clone(&controller);
22758 let (child_queued_tx, child_queued_rx) = mpsc::channel();
22759 let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
22760 let (release_outer_tx, release_outer_rx) = oneshot::channel();
22761 let outer = controller
22762 .try_spawn_blocking(move || {
22763 let child_controller = Arc::clone(&outer_controller);
22764 let child = outer_controller
22765 .try_spawn(async move {
22766 let blocking = child_controller
22767 .try_spawn_blocking(move || {
22768 unrelated_ran_tx.send(()).unwrap();
22769 31usize
22770 })
22771 .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
22772 child_queued_tx.send(()).unwrap();
22773 blocking.await.unwrap()
22774 })
22775 .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
22776 let mut released = false;
22777 {
22778 let mut future = std::pin::pin!(async {
22779 release_outer_rx.await.unwrap();
22780 released = true;
22781 });
22782 outer_controller.block_on(future.as_mut());
22783 }
22784 assert!(released);
22785 child
22786 })
22787 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22788
22789 child_queued_rx
22790 .recv_timeout(Duration::from_secs(2))
22791 .expect("the cooperative driver must service the unrelated async runnable");
22792 assert!(
22793 unrelated_ran_rx
22794 .recv_timeout(Duration::from_millis(200))
22795 .is_err(),
22796 "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
22797 );
22798
22799 release_outer_tx.send(()).unwrap();
22800 let child = futures::executor::block_on(outer).unwrap();
22801 unrelated_ran_rx
22802 .recv_timeout(Duration::from_secs(2))
22803 .expect("the unrelated blocking job must run after the owner releases its counted slot");
22804 assert_eq!(futures::executor::block_on(child).unwrap(), 31);
22805 controller.shutdown().unwrap();
22806 }
22807
22808 #[cfg(not(target_family = "wasm"))]
22809 #[test]
22810 fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
22811 let parent = Arc::new(TaskDependency::default());
22812 let _context = TaskDependencyGuard::enter(&parent);
22813 let dependency = BlockingDependency {
22814 job: BlockingJobId(u64::MAX),
22815 owner: None,
22816 };
22817 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22818 let mut handle = JoinHandle(JoinHandleInner::Blocking {
22819 receiver,
22820 dependency,
22821 published: PublishedBlockingDependencies::default(),
22822 });
22823 let waker = futures::task::noop_waker();
22824 let mut cx = Context::from_waker(&waker);
22825
22826 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
22827 assert_eq!(parent.get_dependency(), Some(dependency));
22828 drop(handle);
22829 assert_eq!(
22830 parent.get_dependency(),
22831 None,
22832 "an abandoned JoinHandle must clear stale dependency lineage"
22833 );
22834 drop(sender);
22835 }
22836
22837 #[cfg(not(target_family = "wasm"))]
22838 #[test]
22839 fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
22840 let owner = BlockingOwnerToken {
22841 executor_id: u64::MAX - 40,
22842 frame: u64::MAX - 41,
22843 };
22844 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22845 let _context = TaskDependencyGuard::enter(&parent);
22846 let dependency = BlockingDependency {
22847 job: BlockingJobId(u64::MAX - 42),
22848 owner: None,
22849 };
22850 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22851 let mut handle = JoinHandle(JoinHandleInner::Blocking {
22852 receiver,
22853 dependency,
22854 published: PublishedBlockingDependencies::default(),
22855 });
22856 let waker = futures::task::noop_waker();
22857 let mut cx = Context::from_waker(&waker);
22858
22859 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
22860 assert_eq!(
22861 parent.get_dependency(),
22862 Some(BlockingDependency {
22863 owner: Some(owner),
22864 ..dependency
22865 }),
22866 "the ambient task owner must enrich an untagged direct blocking dependency"
22867 );
22868 drop(handle);
22869 assert_eq!(
22870 parent.get_dependency(),
22871 None,
22872 "detach cleanup must match the stable job id after owner enrichment"
22873 );
22874 drop(sender);
22875 }
22876
22877 #[cfg(not(target_family = "wasm"))]
22878 #[test]
22879 fn one_poll_preserves_every_observed_blocking_dependency() {
22880 let parent = Arc::new(TaskDependency::default());
22881 let _context = TaskDependencyGuard::enter(&parent);
22882 let first = BlockingDependency {
22883 job: BlockingJobId(u64::MAX - 60),
22884 owner: None,
22885 };
22886 let second = BlockingDependency {
22887 job: BlockingJobId(u64::MAX - 61),
22888 owner: None,
22889 };
22890 let (first_sender, first_receiver) =
22891 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22892 let (second_sender, second_receiver) =
22893 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22894 let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
22895 receiver: first_receiver,
22896 dependency: first,
22897 published: PublishedBlockingDependencies::default(),
22898 });
22899 let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
22900 receiver: second_receiver,
22901 dependency: second,
22902 published: PublishedBlockingDependencies::default(),
22903 });
22904 let waker = futures::task::noop_waker();
22905 let mut cx = Context::from_waker(&waker);
22906
22907 assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
22908 assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
22909 assert_eq!(
22910 parent.get_dependencies(),
22911 vec![first, second],
22912 "publishing a later dependency in one user poll must not replace an earlier dependency"
22913 );
22914
22915 drop(first_handle);
22916 drop(second_handle);
22917 drop(first_sender);
22918 drop(second_sender);
22919 }
22920
22921 #[cfg(not(target_family = "wasm"))]
22922 #[test]
22923 fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
22924 let owner = BlockingOwnerToken {
22925 executor_id: u64::MAX - 900,
22926 frame: u64::MAX - 901,
22927 };
22928 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22929 let _context = TaskDependencyGuard::enter(&parent);
22930 let waker = futures::task::noop_waker();
22931 let mut cx = Context::from_waker(&waker);
22932
22933 let j1 = BlockingDependency {
22934 job: BlockingJobId(u64::MAX - 910),
22935 owner: Some(owner),
22936 };
22937 let j2 = BlockingDependency {
22938 job: BlockingJobId(u64::MAX - 911),
22939 owner: Some(owner),
22940 };
22941
22942 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22943 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22944
22945 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
22946 receiver: r1,
22947 dependency: j1,
22948 published: PublishedBlockingDependencies::default(),
22949 });
22950 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
22953 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
22954 let entries_after_double_poll = parent
22955 .state
22956 .lock()
22957 .unwrap_or_else(std::sync::PoisonError::into_inner)
22958 .current
22959 .len();
22960 drop(h1);
22961
22962 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
22963 receiver: r2,
22964 dependency: j2,
22965 published: PublishedBlockingDependencies::default(),
22966 });
22967 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
22968
22969 assert_eq!(
22970 entries_after_double_poll, 1,
22971 "a repeat Pending poll must supersede the handle's own previous publication"
22972 );
22973 let selected = parent
22974 .select_for_owner(owner)
22975 .expect("a live dependency must be selectable");
22976 assert_eq!(
22977 selected.publication.dependency.job, j2.job,
22978 "a DETACHED job must not be selected for owner lending ahead of the live one"
22979 );
22980 drop((s1, s2));
22981 }
22982
22983 #[cfg(not(target_family = "wasm"))]
22984 #[test]
22985 fn handle_polled_under_a_nested_frame_retires_both_publications() {
22986 let owner = BlockingOwnerToken {
22987 executor_id: u64::MAX - 920,
22988 frame: u64::MAX - 921,
22989 };
22990 let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22991 let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22992 let waker = futures::task::noop_waker();
22993 let mut cx = Context::from_waker(&waker);
22994
22995 let j1 = BlockingDependency {
22996 job: BlockingJobId(u64::MAX - 930),
22997 owner: Some(owner),
22998 };
22999 let j2 = BlockingDependency {
23000 job: BlockingJobId(u64::MAX - 931),
23001 owner: Some(owner),
23002 };
23003 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23004 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23005 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23006 receiver: r1,
23007 dependency: j1,
23008 published: Default::default(),
23009 });
23010
23011 {
23012 let _outer_ctx = TaskDependencyGuard::enter(&outer);
23013 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23017 {
23018 let _inner_ctx = TaskDependencyGuard::enter(&inner);
23019 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23020 }
23021 drop(h1);
23022 assert!(
23023 outer.get_dependencies().is_empty(),
23024 "detaching must retire the publication made in the outer frame too"
23025 );
23026 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23027 receiver: r2,
23028 dependency: j2,
23029 published: Default::default(),
23030 });
23031 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23032 let selected = outer.select_for_owner(owner).expect("selectable");
23033 assert_eq!(
23034 selected.publication.dependency.job, j2.job,
23035 "a detached job published in an outer frame must not outrank a live one"
23036 );
23037 drop(h2);
23038 }
23039 drop((s1, s2));
23040 }
23041
23042 #[cfg(not(target_family = "wasm"))]
23043 #[test]
23044 fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
23045 const FRAME_COUNT: usize = 64;
23046 let waker = futures::task::noop_waker();
23047 let mut cx = Context::from_waker(&waker);
23048 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23049 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23050 receiver,
23051 dependency: BlockingDependency {
23052 job: BlockingJobId(u64::MAX - 940),
23053 owner: None,
23054 },
23055 published: PublishedBlockingDependencies::default(),
23056 });
23057 let mut frames = Vec::new();
23058 for _ in 0..FRAME_COUNT {
23059 let frame = Arc::new(TaskDependency::default());
23060 frames.push(Arc::downgrade(&frame));
23061 let _context = TaskDependencyGuard::enter(&frame);
23062 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23063 }
23064 let retained = frames
23069 .iter()
23070 .filter(|frame| frame.strong_count() > 0)
23071 .count();
23072 assert_eq!(
23073 retained, 0,
23074 "a completed frame must not be kept alive by a pending handle's publication history"
23075 );
23076 drop(handle);
23077 drop(sender);
23078 }
23079
23080 #[cfg(not(target_family = "wasm"))]
23081 #[test]
23082 fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
23083 let owner = BlockingOwnerToken {
23088 executor_id: u64::MAX - 950,
23089 frame: u64::MAX - 951,
23090 };
23091 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23092 let _context = TaskDependencyGuard::enter(&parent);
23093 let waker = futures::task::noop_waker();
23094 let mut cx = Context::from_waker(&waker);
23095
23096 let j1 = BlockingDependency {
23097 job: BlockingJobId(u64::MAX - 960),
23098 owner: Some(owner),
23099 };
23100 let j2 = BlockingDependency {
23101 job: BlockingJobId(u64::MAX - 961),
23102 owner: Some(owner),
23103 };
23104 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23105 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23106 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23107 receiver: r1,
23108 dependency: j1,
23109 published: PublishedBlockingDependencies::default(),
23110 });
23111 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23112 receiver: r2,
23113 dependency: j2,
23114 published: PublishedBlockingDependencies::default(),
23115 });
23116
23117 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23118 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23119 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23120
23121 let selected = parent
23122 .select_for_owner(owner)
23123 .expect("a live dependency must be selectable");
23124 assert_eq!(
23125 selected.publication.dependency.job, j1.job,
23126 "re-polling a handle must not push its dependency behind one published later"
23127 );
23128 drop((h1, h2, s1, s2));
23129 }
23130
23131 #[cfg(not(target_family = "wasm"))]
23132 #[test]
23133 fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
23134 const FRAME_COUNT: usize = 256;
23141 let waker = futures::task::noop_waker();
23142 let mut cx = Context::from_waker(&waker);
23143 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23144 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23145 receiver,
23146 dependency: BlockingDependency {
23147 job: BlockingJobId(u64::MAX - 970),
23148 owner: None,
23149 },
23150 published: PublishedBlockingDependencies::default(),
23151 });
23152 let mut retained = Vec::new();
23153 for _ in 0..FRAME_COUNT {
23154 let frame = Arc::new(TaskDependency::default());
23155 retained.push(Arc::clone(&frame));
23156 let _context = TaskDependencyGuard::enter(&frame);
23157 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23158 frame.begin_poll();
23160 }
23161 let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
23162 panic!("the handle must still be a blocking handle");
23163 };
23164 let remembered = usize::from(published.first.is_some()) + published.rest.len();
23165 assert!(
23166 remembered <= 2,
23167 "a drained frame must not stay in the publication history, remembered={remembered}"
23168 );
23169 drop(handle);
23170 drop(sender);
23171 }
23172
23173 #[cfg(not(target_family = "wasm"))]
23174 #[test]
23175 fn mass_detach_of_pending_blocking_handles_stays_linear() {
23176 const HANDLE_COUNT: usize = 512;
23187
23188 let parent = Arc::new(TaskDependency::default());
23189 let _context = TaskDependencyGuard::enter(&parent);
23190 let waker = futures::task::noop_waker();
23191 let mut cx = Context::from_waker(&waker);
23192
23193 let mut senders = Vec::with_capacity(HANDLE_COUNT);
23194 let mut handles = Vec::with_capacity(HANDLE_COUNT);
23195 for index in 0..HANDLE_COUNT {
23196 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23197 senders.push(sender);
23198 handles.push(JoinHandle(JoinHandleInner::Blocking {
23199 receiver,
23200 dependency: BlockingDependency {
23201 job: BlockingJobId(u64::MAX - 300_000 - index as u64),
23202 owner: None,
23203 },
23204 published: PublishedBlockingDependencies::default(),
23205 }));
23206 }
23207 for handle in &mut handles {
23208 assert!(Pin::new(handle).poll(&mut cx).is_pending());
23209 }
23210 assert_eq!(
23211 parent
23212 .state
23213 .lock()
23214 .unwrap_or_else(std::sync::PoisonError::into_inner)
23215 .current
23216 .len(),
23217 HANDLE_COUNT,
23218 "every handle must be registered and still pending before the detach"
23219 );
23220
23221 parent.reset_entry_visits();
23222 drop(handles);
23223
23224 assert!(
23225 parent.entry_visits() <= HANDLE_COUNT * 4,
23226 "mass-detach must visit O(n) entries across n handles, visits={}",
23227 parent.entry_visits()
23228 );
23229 assert_eq!(
23230 parent.get_dependency(),
23231 None,
23232 "every detached handle must have retired its own publication"
23233 );
23234 drop(senders);
23235 }
23236
23237 #[cfg(not(target_family = "wasm"))]
23238 #[test]
23239 fn dependency_tls_borrow_is_released_before_reentrant_wake() {
23240 use std::sync::mpsc;
23241
23242 struct ReenterDependencyContext {
23243 dependency: Arc<TaskDependency>,
23244 woke: mpsc::Sender<()>,
23245 }
23246
23247 impl std::task::Wake for ReenterDependencyContext {
23248 fn wake(self: Arc<Self>) {
23249 let _context = TaskDependencyGuard::enter(&self.dependency);
23250 self.woke.send(()).unwrap();
23251 }
23252 }
23253
23254 let parent = Arc::new(TaskDependency::default());
23255 let nested = Arc::new(TaskDependency::default());
23256 let (woke_tx, woke_rx) = mpsc::channel();
23257 let waiter = Waker::from(Arc::new(ReenterDependencyContext {
23258 dependency: Arc::clone(&nested),
23259 woke: woke_tx,
23260 }));
23261 parent.register_waiter(&waiter);
23262 let blocking = BlockingDependency {
23263 job: BlockingJobId(u64::MAX - 62),
23264 owner: None,
23265 };
23266
23267 let _context = TaskDependencyGuard::enter(&parent);
23268 let mut published = PublishedBlockingDependencies::default();
23269 record_blocking_dependency(&mut published, blocking);
23270 woke_rx
23271 .recv_timeout(Duration::from_secs(1))
23272 .expect("dependency notification must permit reentrant TLS context access");
23273
23274 parent.register_waiter(&waiter);
23275 clear_blocking_dependency(published);
23276 woke_rx
23277 .recv_timeout(Duration::from_secs(1))
23278 .expect("dependency clearing must permit reentrant TLS context access");
23279 }
23280
23281 #[cfg(not(target_family = "wasm"))]
23282 #[test]
23283 fn dependency_free_begin_poll_does_not_lock_state() {
23284 use std::sync::mpsc;
23285
23286 let dependency = Arc::new(TaskDependency::default());
23287 let state = dependency.state.lock().unwrap();
23288 let (completed_tx, completed_rx) = mpsc::channel();
23289 let polling_dependency = Arc::clone(&dependency);
23290 let polling = std::thread::spawn(move || {
23291 polling_dependency.begin_poll();
23292 completed_tx.send(()).unwrap();
23293 });
23294
23295 let completed_without_lock = completed_rx
23296 .recv_timeout(Duration::from_millis(200))
23297 .is_ok();
23298 drop(state);
23299 polling.join().unwrap();
23300 assert!(
23301 completed_without_lock,
23302 "a task with no published dependency must skip the dependency-state mutex"
23303 );
23304 }
23305
23306 #[cfg(not(target_family = "wasm"))]
23307 #[test]
23308 fn deep_dependency_liveness_chain_is_iterative() {
23309 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
23310
23311 if std::env::var_os(CHILD_ENV).is_some() {
23312 const DEPTH: usize = 100_000;
23313
23314 let mut links = Vec::with_capacity(DEPTH + 1);
23315 links.push(DependencyLink::root());
23316 for _ in 0..DEPTH {
23317 links.push(DependencyLink::derived(links.last().unwrap()));
23318 }
23319 assert!(links.last().unwrap().is_live());
23320 links[0].cancel();
23321 assert!(!links.last().unwrap().is_live());
23322 return;
23323 }
23324
23325 let output = std::process::Command::new(std::env::current_exe().unwrap())
23326 .arg("--exact")
23327 .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
23328 .arg("--nocapture")
23329 .env(CHILD_ENV, "1")
23330 .output()
23331 .expect("the subprocess test must start");
23332 assert!(
23333 output.status.success(),
23334 "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
23335 output.status.code(),
23336 String::from_utf8_lossy(&output.stdout),
23337 String::from_utf8_lossy(&output.stderr)
23338 );
23339 }
23340
23341 #[cfg(not(target_family = "wasm"))]
23342 #[test]
23343 fn task_dependency_set_contains_panicking_waiter() {
23344 struct PanicWake;
23345 impl std::task::Wake for PanicWake {
23346 fn wake(self: Arc<Self>) {
23347 panic!("intentional TaskDependency::set waiter panic");
23348 }
23349 }
23350
23351 let dependency = TaskDependency::default();
23352 let blocking = BlockingDependency {
23353 job: BlockingJobId(u64::MAX),
23354 owner: None,
23355 };
23356 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
23357
23358 let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
23359 assert!(
23360 result.is_ok(),
23361 "set notification must not unwind its caller"
23362 );
23363 assert_eq!(
23364 dependency.get_dependency(),
23365 Some(blocking),
23366 "set must commit before notifying its waiter"
23367 );
23368 }
23369
23370 #[cfg(not(target_family = "wasm"))]
23371 #[test]
23372 fn task_dependency_clear_contains_panicking_waiter() {
23373 struct PanicWake;
23374 impl std::task::Wake for PanicWake {
23375 fn wake(self: Arc<Self>) {
23376 panic!("intentional TaskDependency::clear waiter panic");
23377 }
23378 }
23379
23380 let dependency = TaskDependency::default();
23381 dependency.set_owned(BlockingDependency {
23382 job: BlockingJobId(u64::MAX),
23383 owner: None,
23384 });
23385 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
23386
23387 let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
23388 assert!(
23389 result.is_ok(),
23390 "clear notification must not unwind its caller"
23391 );
23392 assert_eq!(
23393 dependency.get_dependency(),
23394 None,
23395 "clear must commit before notifying its waiter"
23396 );
23397 }
23398
23399 #[cfg(not(target_family = "wasm"))]
23400 #[test]
23401 fn task_dependency_clear_if_contains_panicking_waiter() {
23402 struct PanicWake;
23403 impl std::task::Wake for PanicWake {
23404 fn wake(self: Arc<Self>) {
23405 panic!("intentional TaskDependency::clear_if waiter panic");
23406 }
23407 }
23408
23409 let dependency = TaskDependency::default();
23410 let blocking = BlockingDependency {
23411 job: BlockingJobId(u64::MAX),
23412 owner: None,
23413 };
23414 let sequence = dependency.set_owned(blocking);
23415 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
23416
23417 let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
23418 assert!(
23419 result.is_ok(),
23420 "retire notification must not unwind its caller"
23421 );
23422 assert_eq!(
23423 dependency.get_dependency(),
23424 None,
23425 "retire must commit before notifying its waiter"
23426 );
23427 }
23428
23429 #[cfg(not(target_family = "wasm"))]
23430 #[test]
23431 fn task_dependency_waiter_replacement_contains_panicking_drop() {
23432 use std::sync::mpsc;
23433
23434 const GENERATION: u64 = u64::MAX - 20;
23435
23436 struct PanicOnDropWake {
23437 dropped: mpsc::Sender<Option<u64>>,
23438 }
23439
23440 #[expect(
23441 clippy::manual_noop_waker,
23442 reason = "the test observes the waker payload's Drop"
23443 )]
23444 impl std::task::Wake for PanicOnDropWake {
23445 fn wake(self: Arc<Self>) {}
23446 }
23447
23448 impl Drop for PanicOnDropWake {
23449 fn drop(&mut self) {
23450 self
23451 .dropped
23452 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
23453 .unwrap();
23454 panic!("intentional replaced dependency waiter destructor panic");
23455 }
23456 }
23457
23458 let dependency = TaskDependency::new(GENERATION, None);
23459 let (dropped_tx, dropped_rx) = mpsc::channel();
23460 let waiter = Waker::from(Arc::new(PanicOnDropWake {
23461 dropped: dropped_tx,
23462 }));
23463 dependency.register_waiter(&waiter);
23464 drop(waiter);
23465
23466 let replacement = futures::task::noop_waker();
23467 let result = catch_unwind(AssertUnwindSafe(|| {
23468 dependency.register_waiter(&replacement)
23469 }));
23470 assert!(
23471 result.is_ok(),
23472 "replacing a hostile waiter must not unwind registration"
23473 );
23474 assert_eq!(
23475 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
23476 Some(GENERATION),
23477 "the replaced waiter must be destroyed under its runtime generation"
23478 );
23479 }
23480
23481 #[cfg(not(target_family = "wasm"))]
23482 #[test]
23483 fn task_dependency_retirement_contains_stored_waiter_drop() {
23484 use std::sync::mpsc;
23485
23486 const GENERATION: u64 = u64::MAX - 21;
23487
23488 struct PanicOnDropWake {
23489 dropped: mpsc::Sender<Option<u64>>,
23490 }
23491
23492 #[expect(
23493 clippy::manual_noop_waker,
23494 reason = "the test observes the waker payload's Drop"
23495 )]
23496 impl std::task::Wake for PanicOnDropWake {
23497 fn wake(self: Arc<Self>) {}
23498 }
23499
23500 impl Drop for PanicOnDropWake {
23501 fn drop(&mut self) {
23502 self
23503 .dropped
23504 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
23505 .unwrap();
23506 panic!("intentional stored dependency waiter destructor panic");
23507 }
23508 }
23509
23510 let dependency = TaskDependency::new(GENERATION, None);
23511 let (dropped_tx, dropped_rx) = mpsc::channel();
23512 let waiter = Waker::from(Arc::new(PanicOnDropWake {
23513 dropped: dropped_tx,
23514 }));
23515 dependency.register_waiter(&waiter);
23516 drop(waiter);
23517
23518 let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
23519 assert!(
23520 result.is_ok(),
23521 "retiring a hostile stored waiter must not unwind"
23522 );
23523 assert_eq!(
23524 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
23525 Some(GENERATION),
23526 "the retained waiter must be destroyed under its runtime generation"
23527 );
23528 }
23529
23530 #[cfg(not(target_family = "wasm"))]
23531 #[test]
23532 fn detaching_task_clears_dependency_waiter_parent_retention() {
23533 struct RetainingWake {
23534 retained_parent: Arc<()>,
23535 }
23536
23537 impl std::task::Wake for RetainingWake {
23538 fn wake(self: Arc<Self>) {
23539 let _ = &self.retained_parent;
23540 }
23541 }
23542
23543 let dependency = Arc::new(TaskDependency::default());
23544 let retained_parent = Arc::new(());
23545 let retained_parent_weak = Arc::downgrade(&retained_parent);
23546 let waiter = Waker::from(Arc::new(RetainingWake {
23547 retained_parent: Arc::clone(&retained_parent),
23548 }));
23549 dependency.register_waiter(&waiter);
23550 drop(waiter);
23551 drop(retained_parent);
23552 assert!(
23553 retained_parent_weak.upgrade().is_some(),
23554 "the stored dependency waiter must retain its parent before detachment"
23555 );
23556
23557 let (runnable, task) = async_task::spawn(
23558 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
23559 |_| {},
23560 );
23561 drop(JoinHandle(JoinHandleInner::Task {
23562 task: task.fallible(),
23563 awaiter: None,
23564 dependency: Arc::clone(&dependency),
23565 }));
23566 assert!(
23567 retained_parent_weak.upgrade().is_none(),
23568 "detachment must clear the dependency waiter instead of retaining its parent task"
23569 );
23570 drop(runnable);
23571 }
23572
23573 #[cfg(not(target_family = "wasm"))]
23574 #[test]
23575 fn detaching_task_clears_many_parent_publications_linearly() {
23576 const PUBLICATION_COUNT: usize = 1024;
23577
23578 struct CountWake(Arc<AtomicUsize>);
23579
23580 impl std::task::Wake for CountWake {
23581 fn wake(self: Arc<Self>) {
23582 self.0.fetch_add(1, Ordering::SeqCst);
23583 }
23584 }
23585
23586 fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
23587 dependency
23588 .state
23589 .lock()
23590 .unwrap_or_else(std::sync::PoisonError::into_inner)
23591 .current
23592 .back()
23593 .expect("the sentinel publication must be appended")
23594 .publication
23595 .clone()
23596 }
23597
23598 let owner = BlockingOwnerToken {
23599 executor_id: u64::MAX - 300,
23600 frame: u64::MAX - 301,
23601 };
23602 let other_owner = BlockingOwnerToken {
23603 executor_id: u64::MAX - 302,
23604 frame: u64::MAX - 303,
23605 };
23606 let child = Arc::new(TaskDependency::default());
23607 for index in 0..PUBLICATION_COUNT {
23608 child.set_owned(BlockingDependency {
23609 job: BlockingJobId(1_000_000 + index as u64),
23610 owner: (index % 2 == 0).then_some(owner),
23611 });
23612 }
23613 let child_publications = child.snapshot_all_live();
23614 let source = child_publications[0].clone();
23615
23616 let parent = Arc::new(TaskDependency::default());
23617 let _context = TaskDependencyGuard::enter(&parent);
23618 let (runnable, task) = async_task::spawn(
23619 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
23620 |_| {},
23621 );
23622 let mut handle = JoinHandle(JoinHandleInner::Task {
23623 task: task.fallible(),
23624 awaiter: None,
23625 dependency: Arc::clone(&child),
23626 });
23627 let waker = futures::task::noop_waker();
23628 let mut cx = Context::from_waker(&waker);
23629 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23630 assert_eq!(
23631 parent.get_dependencies().len(),
23632 PUBLICATION_COUNT,
23633 "polling the child handle must propagate every current publication"
23634 );
23635
23636 let mut wrong_claim_source = source.clone();
23637 wrong_claim_source.claim = Arc::new(DependencyClaim::new());
23638 parent.set_borrowed(&wrong_claim_source);
23639 let wrong_claim_publication = latest_publication(&parent);
23640
23641 let mut wrong_job_source = source.clone();
23642 wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
23643 parent.set_borrowed(&wrong_job_source);
23644 let wrong_job_publication = latest_publication(&parent);
23645
23646 let wrong_immediate_source = source.derived(None);
23647 parent.set_borrowed(&wrong_immediate_source);
23648 let wrong_immediate_publication = latest_publication(&parent);
23649
23650 let wakes = Arc::new(AtomicUsize::new(0));
23651 parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
23652 parent.reset_entry_visits();
23653 drop(handle);
23654
23655 assert_eq!(
23656 wakes.load(Ordering::SeqCst),
23657 1,
23658 "one batched parent cleanup must emit one waiter notification"
23659 );
23660 assert!(
23661 parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
23662 "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
23663 parent.entry_visits()
23664 );
23665 assert!(
23666 wrong_claim_publication.is_live(),
23667 "claim identity must remain part of the match"
23668 );
23669 assert!(
23670 wrong_job_publication.is_live(),
23671 "job identity must remain part of the match"
23672 );
23673 assert!(
23674 wrong_immediate_publication.is_live(),
23675 "only an exact immediate-parent link may be retired"
23676 );
23677
23678 {
23679 let state = parent
23680 .state
23681 .lock()
23682 .unwrap_or_else(std::sync::PoisonError::into_inner);
23683 assert_eq!(state.current.len(), 3);
23684 assert!(
23691 state
23692 .current
23693 .iter()
23694 .all(|entry| entry.publication.is_live()),
23695 "only the three non-matching sentinels may remain, all still live"
23696 );
23697 assert!(state.unowned_sequences.is_empty());
23698 assert_eq!(state.owner_sequences.len(), 1);
23699 assert_eq!(
23700 state.owner_sequences.get(&owner).map(VecDeque::len),
23701 Some(3)
23702 );
23703 }
23704 assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23705 assert!(
23706 !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
23707 "cancelled unowned publications must not survive in availability predicates"
23708 );
23709 drop(runnable);
23710 }
23711
23712 #[cfg(not(target_family = "wasm"))]
23713 #[test]
23714 fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
23715 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
23716
23717 if std::env::var_os(CHILD_ENV).is_some() {
23718 struct PanicWake;
23719 impl std::task::Wake for PanicWake {
23720 fn wake(self: Arc<Self>) {
23721 panic!("intentional dependency waker panic");
23722 }
23723 }
23724
23725 struct PanicOnDrop;
23726 impl Drop for PanicOnDrop {
23727 fn drop(&mut self) {
23728 panic!("intentional buffered blocking result destructor panic");
23729 }
23730 }
23731
23732 let parent = Arc::new(TaskDependency::default());
23733 let dependency = BlockingDependency {
23734 job: BlockingJobId(u64::MAX - 1),
23735 owner: None,
23736 };
23737 let sequence = parent.set_owned(dependency);
23741 parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
23742 let _context = TaskDependencyGuard::enter(&parent);
23743 let (sender, receiver) = oneshot::channel();
23744 let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
23745 ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
23746 ));
23747 drop(JoinHandle(JoinHandleInner::Blocking {
23748 receiver,
23749 dependency,
23750 published: PublishedBlockingDependencies {
23751 first: Some(PublishedBlockingDependency {
23752 state: Arc::downgrade(&parent),
23753 sequence,
23754 }),
23755 rest: Vec::new(),
23756 },
23757 }));
23758 assert_eq!(parent.get_dependency(), None);
23759 return;
23760 }
23761
23762 let output = std::process::Command::new(std::env::current_exe().unwrap())
23763 .arg("--exact")
23764 .arg(
23765 "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
23766 )
23767 .arg("--nocapture")
23768 .env(CHILD_ENV, "1")
23769 .output()
23770 .expect("the subprocess test must start");
23771 assert!(
23772 output.status.success(),
23773 "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
23774 output.status.code(),
23775 String::from_utf8_lossy(&output.stdout),
23776 String::from_utf8_lossy(&output.stderr)
23777 );
23778 }
23779
23780 #[cfg(not(target_family = "wasm"))]
23781 #[test]
23782 fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
23783 use std::sync::{Barrier, mpsc};
23792 use std::time::Duration;
23793
23794 let (done_tx, done_rx) = mpsc::channel();
23795 let peak_out = Arc::new(Mutex::new(None));
23796 let peak_slot = Arc::clone(&peak_out);
23797 let runner = std::thread::spawn(move || {
23798 let metrics = Arc::new(RuntimeMetrics::default());
23799 let options = RuntimeOptions {
23800 flavor: RuntimeFlavor::MultiThread,
23801 worker_threads: 4,
23802 max_blocking_tasks: 2,
23803 thread_name_prefix: "rd1-cap".to_string(),
23804 park_deadline: None,
23805 drain_linger: DEFAULT_DRAIN_LINGER,
23806 };
23807 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
23808
23809 let holder_count = 2usize; let driver_count = 2usize;
23811
23812 let (held_tx, held_rx) = mpsc::channel();
23814 let release = Arc::new(Barrier::new(holder_count + 1));
23815
23816 let mut holder_handles = Vec::new();
23817 for _ in 0..holder_count {
23818 let held_tx = held_tx.clone();
23819 let release = Arc::clone(&release);
23820 let handle = executor.schedule_blocking(move || {
23821 held_tx.send(()).unwrap();
23822 release.wait();
23823 0usize
23824 });
23825 holder_handles.push(handle);
23826 }
23827 for _ in 0..holder_count {
23829 held_rx.recv().unwrap();
23830 }
23831
23832 let mut driver_tasks = Vec::new();
23835 for _ in 0..driver_count {
23836 let exec = Arc::clone(&executor);
23837 let body = async move {
23838 let inner_exec = Arc::clone(&exec);
23839 let mut inner = std::pin::pin!(async move {
23840 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
23841 assert_eq!(v, 5);
23842 });
23843 exec.block_on(inner.as_mut());
23844 };
23845 let scheduler = Arc::clone(&executor);
23846 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
23847 executor.schedule(runnable);
23848 driver_tasks.push(task);
23849 }
23850
23851 std::thread::sleep(Duration::from_millis(300));
23854
23855 release.wait();
23857
23858 for task in driver_tasks {
23859 futures::executor::block_on(task);
23860 }
23861 for handle in holder_handles {
23862 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
23863 }
23864
23865 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
23866 let _ = done_tx.send(());
23867 });
23868
23869 match done_rx.recv_timeout(Duration::from_secs(10)) {
23870 Ok(()) => runner.join().unwrap(),
23871 Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
23872 }
23873 let peak = peak_out.lock().unwrap().expect("peak captured");
23874 assert!(
23875 peak <= 2,
23876 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
23877 );
23878 }
23879
23880 #[cfg(not(target_family = "wasm"))]
23881 #[test]
23882 fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
23883 use std::sync::{Barrier, mpsc};
23899 use std::time::Duration;
23900
23901 let (done_tx, done_rx) = mpsc::channel();
23902 let peak_out = Arc::new(Mutex::new(None));
23903 let peak_slot = Arc::clone(&peak_out);
23904 let runner = std::thread::spawn(move || {
23905 let metrics = Arc::new(RuntimeMetrics::default());
23906 let options = RuntimeOptions {
23907 flavor: RuntimeFlavor::MultiThread,
23908 worker_threads: 2,
23909 max_blocking_tasks: 2,
23910 thread_name_prefix: "rd1-owner-runnable".to_string(),
23911 park_deadline: None,
23912 drain_linger: DEFAULT_DRAIN_LINGER,
23913 };
23914 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
23915
23916 let saturated = Arc::new(Barrier::new(2));
23917 let release = Arc::new(Barrier::new(2));
23918
23919 let h2_saturated = Arc::clone(&saturated);
23921 let h2_release = Arc::clone(&release);
23922 let h2 = executor.schedule_blocking(move || {
23923 h2_saturated.wait();
23924 h2_release.wait();
23925 0usize
23926 });
23927
23928 let o_exec = Arc::clone(&executor);
23931 let o_saturated = Arc::clone(&saturated);
23932 let o = executor.schedule_blocking(move || {
23933 o_saturated.wait(); let r_exec = Arc::clone(&o_exec);
23936 let body = async move {
23937 let inner_exec = Arc::clone(&r_exec);
23938 let mut inner = std::pin::pin!(async move {
23939 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
23940 assert_eq!(v, 5);
23941 });
23942 r_exec.block_on(inner.as_mut());
23943 };
23944 let sched = Arc::clone(&o_exec);
23945 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
23946 o_exec.schedule(runnable);
23947
23948 let mut wait_r = std::pin::pin!(async move {
23949 task.await;
23950 });
23951 o_exec.block_on(wait_r.as_mut());
23952 1usize
23953 });
23954
23955 std::thread::sleep(Duration::from_millis(300));
23958 release.wait();
23960
23961 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
23962 assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
23963
23964 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
23965 let _ = done_tx.send(());
23966 });
23967
23968 match done_rx.recv_timeout(Duration::from_secs(10)) {
23969 Ok(()) => runner.join().unwrap(),
23970 Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
23971 }
23972 let peak = peak_out.lock().unwrap().expect("peak captured");
23973 assert!(
23974 peak <= 2,
23975 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
23976 );
23977 }
23978
23979 #[cfg(not(target_family = "wasm"))]
23980 #[test]
23981 fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
23982 use std::sync::{Barrier, mpsc};
23991
23992 let (done_tx, done_rx) = mpsc::channel();
23993 let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
23994 let order_read = Arc::clone(&order_out);
23995 let runner = std::thread::spawn(move || {
23996 let metrics = Arc::new(RuntimeMetrics::default());
23997 let options = RuntimeOptions {
23998 flavor: RuntimeFlavor::MultiThread,
23999 worker_threads: 2,
24000 max_blocking_tasks: 2,
24001 thread_name_prefix: "rd1-unrelated".to_string(),
24002 park_deadline: None,
24003 drain_linger: DEFAULT_DRAIN_LINGER,
24004 };
24005 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24006
24007 let (held_tx, held_rx) = mpsc::channel();
24010 let u_ready = Arc::new(Barrier::new(2)); let h_release = Arc::new(Barrier::new(2)); let h_held = held_tx.clone();
24015 let h_rel = Arc::clone(&h_release);
24016 let h = executor.schedule_blocking(move || {
24017 h_held.send(()).unwrap();
24018 h_rel.wait();
24019 0usize
24020 });
24021
24022 let o_exec = Arc::clone(&executor);
24025 let o_ready = Arc::clone(&u_ready);
24026 let o_order = Arc::clone(&order_out);
24027 let o = executor.schedule_blocking(move || {
24028 held_tx.send(()).unwrap();
24029 o_ready.wait(); let inner_exec = Arc::clone(&o_exec);
24031 let d_order = Arc::clone(&o_order);
24032 let mut inner = std::pin::pin!(async move {
24033 let v = inner_exec
24034 .schedule_blocking(move || {
24035 d_order.lock().unwrap().push("D");
24036 5usize
24037 })
24038 .await
24039 .unwrap();
24040 assert_eq!(v, 5);
24041 });
24042 o_exec.block_on(inner.as_mut());
24043 1usize
24044 });
24045
24046 held_rx.recv().unwrap();
24048 held_rx.recv().unwrap();
24049
24050 let u_order = Arc::clone(&order_out);
24052 let u = executor.schedule_blocking(move || {
24053 u_order.lock().unwrap().push("U");
24054 2usize
24055 });
24056
24057 u_ready.wait();
24059
24060 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24062 assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
24063
24064 h_release.wait();
24066 assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
24067
24068 let _ = done_tx.send(());
24069 });
24070
24071 match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
24072 Ok(()) => runner.join().unwrap(),
24073 Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
24074 }
24075 let order = order_read.lock().unwrap().clone();
24076 assert_eq!(
24077 order,
24078 vec!["D", "U"],
24079 "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
24080 );
24081 }
24082
24083 #[cfg(not(target_family = "wasm"))]
24084 #[test]
24085 fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
24086 use std::sync::{Barrier, mpsc};
24087
24088 let opts = RuntimeOptions {
24089 flavor: RuntimeFlavor::MultiThread,
24090 worker_threads: 2,
24091 max_blocking_tasks: 2,
24092 thread_name_prefix: "rd1-scope".to_string(),
24093 park_deadline: None,
24094 drain_linger: DEFAULT_DRAIN_LINGER,
24095 };
24096 let exec1 =
24097 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24098 let exec2 =
24099 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24100 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
24101
24102 let holder_count = opts.max_blocking_tasks;
24103 let (held_tx, held_rx) = mpsc::channel();
24104 let release = Arc::new(Barrier::new(holder_count + 1));
24105 let mut holder_handles = Vec::new();
24106 for _ in 0..holder_count {
24107 let held_tx = held_tx.clone();
24108 let release = Arc::clone(&release);
24109 holder_handles.push(exec2.schedule_blocking(move || {
24110 held_tx.send(()).unwrap();
24111 release.wait();
24112 0usize
24113 }));
24114 }
24115 for _ in 0..holder_count {
24116 held_rx.recv().unwrap();
24117 }
24118 assert_eq!(
24119 exec2.active_blocking.load(Ordering::Acquire),
24120 opts.max_blocking_tasks
24121 );
24122
24123 let exact = BlockingJobId(u64::MAX);
24124 let ran = Arc::new(AtomicBool::new(false));
24125 let ran_job = Arc::clone(&ran);
24126 exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
24127 id: exact,
24128 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24129 });
24130 let dependency = TaskDependency::default();
24131
24132 let token1 = exec1.fresh_owner_token();
24133 {
24134 let _owner = BlockingOwnerGuard::enter(Some(token1));
24135 dependency.set_owned(BlockingDependency {
24136 job: exact,
24137 owner: Some(token1),
24138 });
24139 assert!(
24140 !exec2.try_owned_blocking_over_cap(&dependency),
24141 "a foreign-executor ambient token must fail the executor gate"
24142 );
24143 }
24144
24145 let token2 = exec2.fresh_owner_token();
24146 {
24147 let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
24148 let _owner = BlockingOwnerGuard::enter(Some(token2));
24149 dependency.set_owned(BlockingDependency {
24150 job: BlockingJobId(u64::MAX - 1),
24151 owner: Some(token2),
24152 });
24153 assert!(
24154 !exec2.try_owned_blocking_over_cap(&dependency),
24155 "a foreign or unrelated dependency id must fail the exact-job gate"
24156 );
24157 dependency.set_owned(BlockingDependency {
24158 job: BlockingJobId(exact.0 - 2),
24159 owner: Some(token2),
24160 });
24161 assert!(
24162 !exec2.try_owned_blocking_over_cap(&dependency),
24163 "a different job id must not consume the queued dependency"
24164 );
24165 dependency.set_owned(BlockingDependency {
24166 job: exact,
24167 owner: Some(token2),
24168 });
24169 assert!(
24170 exec2.try_owned_blocking_over_cap(&dependency),
24171 "the exact awaited job must run over cap"
24172 );
24173 }
24174 assert!(
24175 ran.load(Ordering::SeqCst),
24176 "the exact dependency must have run"
24177 );
24178
24179 release.wait();
24180 for handle in holder_handles {
24181 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24182 }
24183 }
24184
24185 #[cfg(not(target_family = "wasm"))]
24186 #[test]
24187 fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
24188 let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
24189 let first_owner = executor.fresh_owner_token();
24190 let second_owner = executor.fresh_owner_token();
24191 let _first_owner =
24192 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
24193 let _second_owner =
24194 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
24195 let exact = BlockingJobId(u64::MAX - 30);
24196 let dependency = TaskDependency::default();
24197 dependency.set_owned(BlockingDependency {
24198 job: exact,
24199 owner: None,
24200 });
24201 let ran = Arc::new(AtomicBool::new(false));
24202 let ran_job = Arc::clone(&ran);
24203 executor
24204 .blocking_queue
24205 .lock()
24206 .unwrap()
24207 .push(QueuedBlocking {
24208 id: exact,
24209 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24210 });
24211
24212 assert!(
24213 !executor.try_owned_blocking_over_cap(&dependency),
24214 "an untagged driver must not choose between multiple active owner frames"
24215 );
24216 assert!(!ran.load(Ordering::SeqCst));
24217 let queued = executor.blocking_queue.lock().unwrap().remove(exact);
24218 assert!(
24219 queued.is_some(),
24220 "ambiguous ownership must leave the exact job queued"
24221 );
24222 }
24223
24224 #[cfg(not(target_family = "wasm"))]
24225 #[test]
24226 fn owner_lending_claims_live_dependency_not_stale_snapshot() {
24227 let executor = multi_thread_executor(2, None, "live-parked-dependency");
24228 let owner = executor.fresh_owner_token();
24229 let dependency = TaskDependency::new(u64::MAX, Some(owner));
24230 let stale = BlockingJobId(u64::MAX - 10);
24231 let live = BlockingJobId(u64::MAX - 9);
24232 dependency.set_owned(BlockingDependency {
24233 job: stale,
24234 owner: Some(owner),
24235 });
24236 dependency.begin_poll();
24237 dependency.set_owned(BlockingDependency {
24238 job: live,
24239 owner: Some(owner),
24240 });
24241
24242 let stale_ran = Arc::new(AtomicBool::new(false));
24243 let stale_job_ran = Arc::clone(&stale_ran);
24244 let live_ran = Arc::new(AtomicBool::new(false));
24245 let live_job_ran = Arc::clone(&live_ran);
24246 {
24247 let mut queue = executor
24248 .blocking_queue
24249 .lock()
24250 .unwrap_or_else(std::sync::PoisonError::into_inner);
24251 queue.push(QueuedBlocking {
24252 id: stale,
24253 run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
24254 });
24255 queue.push(QueuedBlocking {
24256 id: live,
24257 run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
24258 });
24259 }
24260
24261 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24262 let _owner = BlockingOwnerGuard::enter(Some(owner));
24263 assert!(
24264 executor.try_owned_blocking_over_cap(&dependency),
24265 "the owner lane must claim the currently published dependency"
24266 );
24267 assert!(
24268 live_ran.load(Ordering::SeqCst),
24269 "the live dependency must run"
24270 );
24271 assert!(
24272 !stale_ran.load(Ordering::SeqCst),
24273 "the stale registered id must remain queued"
24274 );
24275 let remaining = executor
24276 .blocking_queue
24277 .lock()
24278 .unwrap_or_else(std::sync::PoisonError::into_inner)
24279 .jobs
24280 .keys()
24281 .copied()
24282 .collect::<Vec<_>>();
24283 assert_eq!(remaining, vec![stale]);
24284 }
24285
24286 #[cfg(not(target_family = "wasm"))]
24287 #[test]
24288 fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
24289 let executor = multi_thread_executor(2, None, "cancelled-child-publication");
24290 let owner = executor.fresh_owner_token();
24291 let child = TaskDependency::new_task(u64::MAX);
24292 let parent = TaskDependency::new(u64::MAX, Some(owner));
24293 let exact = BlockingDependency {
24294 job: BlockingJobId(u64::MAX - 31),
24295 owner: Some(owner),
24296 };
24297
24298 child.set_owned(exact);
24299 let stale = child
24300 .get()
24301 .expect("the child must publish its direct dependency");
24302 child.begin_poll();
24303 parent.set_borrowed(&stale);
24304
24305 let ran = Arc::new(AtomicBool::new(false));
24306 let ran_job = Arc::clone(&ran);
24307 executor
24308 .blocking_queue
24309 .lock()
24310 .unwrap()
24311 .push(QueuedBlocking {
24312 id: exact.job,
24313 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24314 });
24315 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24316 let _owner = BlockingOwnerGuard::enter(Some(owner));
24317 assert!(
24318 !executor.try_owned_blocking_over_cap(&parent),
24319 "a publication copied before child cancellation must remain cancelled after parent install"
24320 );
24321 assert!(!ran.load(Ordering::SeqCst));
24322
24323 child.set_owned(exact);
24324 parent.set_borrowed(
24325 &child
24326 .get()
24327 .expect("the replacement publication must be live"),
24328 );
24329 assert!(
24330 executor.try_owned_blocking_over_cap(&parent),
24331 "a fresh child publication must still authorize its exact queued job"
24332 );
24333 assert!(ran.load(Ordering::SeqCst));
24334 }
24335
24336 #[cfg(not(target_family = "wasm"))]
24337 #[test]
24338 fn intermediate_repoll_does_not_cancel_child_source_claim() {
24339 let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
24340 let owner = executor.fresh_owner_token();
24341 let child = TaskDependency::new_task(u64::MAX);
24342 let intermediate = TaskDependency::new_task(u64::MAX);
24343 let parent = TaskDependency::new(u64::MAX, Some(owner));
24344 let exact = BlockingDependency {
24345 job: BlockingJobId(u64::MAX - 43),
24346 owner: Some(owner),
24347 };
24348
24349 child.set_owned(exact);
24350 intermediate.set_borrowed(&child.get().expect("the child source must be live"));
24351 parent.set_borrowed(
24352 &intermediate
24353 .get()
24354 .expect("the intermediate publication must be live"),
24355 );
24356
24357 intermediate.begin_poll();
24358 assert!(
24359 child.get().is_some(),
24360 "withdrawing an intermediate hop must not cancel the child's source claim"
24361 );
24362 assert!(
24363 parent.get().is_none(),
24364 "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
24365 );
24366
24367 intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
24368 parent.set_borrowed(
24369 &intermediate
24370 .get()
24371 .expect("the intermediate must republish the still-live source"),
24372 );
24373 let ran = Arc::new(AtomicBool::new(false));
24374 let ran_job = Arc::clone(&ran);
24375 executor
24376 .blocking_queue
24377 .lock()
24378 .unwrap()
24379 .push(QueuedBlocking {
24380 id: exact.job,
24381 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24382 });
24383
24384 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24385 let _owner = BlockingOwnerGuard::enter(Some(owner));
24386 assert!(
24387 executor.try_owned_blocking_over_cap(&parent),
24388 "a fresh chain through the repolled intermediate must retain the exact source claim"
24389 );
24390 assert!(ran.load(Ordering::SeqCst));
24391 }
24392
24393 #[cfg(not(target_family = "wasm"))]
24394 #[test]
24395 fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
24396 use std::sync::mpsc;
24397
24398 let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
24399 let owner = executor.fresh_owner_token();
24400 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24401 let child = Arc::new(TaskDependency::new_task(u64::MAX));
24402 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24403 let exact = BlockingDependency {
24404 job: BlockingJobId(u64::MAX - 44),
24405 owner: Some(owner),
24406 };
24407 child.set_owned(exact);
24408 parent.set_borrowed(&child.get().expect("the child source must be live"));
24409
24410 let ran = Arc::new(AtomicBool::new(false));
24411 let ran_job = Arc::clone(&ran);
24412 executor
24413 .blocking_queue
24414 .lock()
24415 .unwrap()
24416 .push(QueuedBlocking {
24417 id: exact.job,
24418 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24419 });
24420
24421 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
24422 let (release_claim_tx, release_claim_rx) = mpsc::channel();
24423 let claimant_executor = Arc::clone(&executor);
24424 let claimant_parent = Arc::clone(&parent);
24425 let claimant = std::thread::spawn(move || {
24426 DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
24427 *slot.borrow_mut() = Some(Box::new(move || {
24428 claim_entered_tx.send(()).unwrap();
24429 release_claim_rx.recv().unwrap();
24430 }));
24431 });
24432 let _owner = BlockingOwnerGuard::enter(Some(owner));
24433 claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
24434 });
24435 claim_entered_rx
24436 .recv_timeout(Duration::from_secs(2))
24437 .expect("the exact claimant must hold the shared transition lock");
24438
24439 let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
24440 let withdrawing_child = Arc::clone(&child);
24441 let withdrawal = std::thread::spawn(move || {
24442 withdrawing_child.begin_poll();
24443 withdrawn_tx.send(()).unwrap();
24444 });
24445 let withdrew_before_claim = withdrawn_rx
24446 .recv_timeout(Duration::from_millis(200))
24447 .is_ok();
24448 release_claim_tx.send(()).unwrap();
24449
24450 assert!(
24451 claimant.join().unwrap(),
24452 "the claim that linearized first must execute the exact job"
24453 );
24454 withdrawal.join().unwrap();
24455 assert!(
24456 !withdrew_before_claim,
24457 "source withdrawal must not interleave after claim validation but before claim commit"
24458 );
24459 assert!(ran.load(Ordering::SeqCst));
24460 }
24461
24462 #[cfg(not(target_family = "wasm"))]
24463 #[test]
24464 fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
24465 struct PendThenReady {
24486 polls: usize,
24487 }
24488 impl Future for PendThenReady {
24489 type Output = ();
24490 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24491 self.polls += 1;
24492 if self.polls >= 2 {
24493 Poll::Ready(())
24494 } else {
24495 cx.waker().wake_by_ref();
24499 Poll::Pending
24500 }
24501 }
24502 }
24503
24504 fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
24507 let scheduler = Arc::clone(target);
24508 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
24509 target.push_fifo_raw(runnable);
24510 task.detach();
24511 }
24512
24513 let opts = RuntimeOptions {
24514 flavor: RuntimeFlavor::MultiThread,
24515 worker_threads: 2,
24516 max_blocking_tasks: 2,
24517 thread_name_prefix: "rd-onpool-scope".to_string(),
24518 park_deadline: None,
24519 drain_linger: DEFAULT_DRAIN_LINGER,
24520 };
24521 let exec1 =
24522 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24523 let exec2 =
24524 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24525 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
24526
24527 let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
24529
24530 enqueue_runnable(&exec2);
24534 {
24535 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
24536 exec2.block_on(probe.as_mut());
24537 }
24538 assert_eq!(
24539 exec2.metrics.runnable_polls.load(Ordering::Relaxed),
24540 0,
24541 "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
24542 );
24543 assert_eq!(
24544 exec2.injector.len(),
24545 1,
24546 "the foreign call must leave exec2's runnable untouched on its queue"
24547 );
24548
24549 enqueue_runnable(&exec1);
24553 {
24554 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
24555 exec1.block_on(probe.as_mut());
24556 }
24557 assert_eq!(
24558 exec1.metrics.runnable_polls.load(Ordering::Relaxed),
24559 1,
24560 "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
24561 );
24562 assert!(
24563 exec1.injector.is_empty(),
24564 "the cooperative branch must have drained exec1's queued runnable"
24565 );
24566
24567 while exec2.steal_one().is_some() {}
24570 }
24571
24572 #[cfg(not(target_family = "wasm"))]
24573 #[test]
24574 fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
24575 use std::sync::mpsc;
24579 use std::time::Duration;
24580
24581 const PRODUCER_COUNT: usize = 8;
24582
24583 fn schedule_pool_workload(
24584 executor: &Arc<MultiThreadExecutor>,
24585 tx: &mpsc::Sender<Option<usize>>,
24586 count: usize,
24587 ) {
24588 for i in 0..count {
24589 let tx = tx.clone();
24590 let scheduler = Arc::clone(executor);
24591 let (runnable, task) =
24592 async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
24593 scheduler.schedule(r);
24594 });
24595 executor.schedule(runnable);
24596 task.detach();
24597 }
24598 let tx = tx.clone();
24599 let scheduler = Arc::clone(executor);
24600 let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
24601 scheduler.schedule(r)
24602 });
24603 executor.schedule(runnable);
24604 task.detach();
24605 }
24606
24607 let expected_sum: usize = (0..PRODUCER_COUNT).sum();
24608 let (done_tx, done_rx) = mpsc::sync_channel(1);
24609 let runner = std::thread::spawn(move || {
24610 let options = RuntimeOptions {
24611 flavor: RuntimeFlavor::MultiThread,
24612 worker_threads: 1,
24613 max_blocking_tasks: 1,
24614 thread_name_prefix: "rd2-pre-fix".to_string(),
24615 park_deadline: None,
24616 drain_linger: DEFAULT_DRAIN_LINGER,
24617 }
24618 .validate()
24619 .expect("production MultiThread options must provide a reserve lane");
24620 let executor =
24621 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
24622
24623 let (tx, rx) = mpsc::channel::<Option<usize>>();
24624 let (started_tx, started_rx) = mpsc::sync_channel(1);
24625 let handle = executor.schedule_blocking(move || {
24626 started_tx.send(()).unwrap();
24627 let mut sum = 0usize;
24628 while let Ok(msg) = rx.recv() {
24629 match msg {
24630 Some(value) => sum += value,
24631 None => break,
24632 }
24633 }
24634 sum
24635 });
24636 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
24637 schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
24638 let sum = futures::executor::block_on(handle).unwrap();
24639 done_tx.send(sum).unwrap();
24640 });
24641
24642 match done_rx.recv_timeout(Duration::from_secs(10)) {
24643 Ok(sum) => {
24644 runner.join().unwrap();
24645 assert_eq!(sum, expected_sum);
24646 }
24647 Err(error) => {
24648 panic!("a saturated blocking lane starved the async producers that feed it ({error})")
24649 }
24650 }
24651 }
24652
24653 #[test]
24654 fn current_thread_blocking_work_completes_inline() {
24655 let metrics = Arc::new(RuntimeMetrics::default());
24656 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
24657 CurrentThreadExecutor::new(Arc::clone(&metrics)),
24658 )));
24659 let registration = backend.work.try_register_work().unwrap();
24660
24661 let value =
24662 futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
24663 .expect("blocking task should complete");
24664
24665 assert_eq!(value, 7);
24666 assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
24667 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
24668 }
24669
24670 #[cfg(not(target_family = "wasm"))]
24671 #[test]
24672 fn current_thread_blocking_work_obeys_single_lane_across_callers() {
24673 use std::sync::mpsc;
24674
24675 let controller = Arc::new(current_thread_controller(
24676 "current-thread-blocking-admission",
24677 ));
24678 let backend = controller.backend();
24679 let (first_started_tx, first_started_rx) = mpsc::channel();
24680 let (release_first_tx, release_first_rx) = mpsc::channel();
24681 let first_controller = Arc::clone(&controller);
24682 let first = std::thread::spawn(move || {
24683 let handle = first_controller
24684 .try_spawn_blocking(move || {
24685 first_started_tx.send(()).unwrap();
24686 release_first_rx.recv().unwrap();
24687 1usize
24688 })
24689 .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
24690 futures::executor::block_on(handle).unwrap()
24691 });
24692 first_started_rx
24693 .recv_timeout(Duration::from_secs(1))
24694 .unwrap();
24695
24696 let (second_started_tx, second_started_rx) = mpsc::channel();
24697 let second_controller = Arc::clone(&controller);
24698 let second = std::thread::spawn(move || {
24699 let handle = second_controller
24700 .try_spawn_blocking(move || {
24701 second_started_tx.send(()).unwrap();
24702 2usize
24703 })
24704 .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
24705 futures::executor::block_on(handle).unwrap()
24706 });
24707
24708 wait_until("both CurrentThread blocking calls to be accepted", || {
24709 backend
24710 .work
24711 .state
24712 .lock()
24713 .unwrap_or_else(std::sync::PoisonError::into_inner)
24714 .active
24715 == 2
24716 });
24717 assert!(
24718 second_started_rx
24719 .recv_timeout(Duration::from_millis(100))
24720 .is_err(),
24721 "CurrentThread must not execute two blocking closures concurrently"
24722 );
24723
24724 release_first_tx.send(()).unwrap();
24725 second_started_rx
24726 .recv_timeout(Duration::from_secs(1))
24727 .expect("the waiting blocking call must acquire the released lane");
24728 assert_eq!(first.join().unwrap(), 1);
24729 assert_eq!(second.join().unwrap(), 2);
24730 assert_eq!(
24731 controller
24732 .metrics
24733 .blocking_tasks_started
24734 .load(Ordering::Relaxed),
24735 2
24736 );
24737 assert_eq!(
24738 controller
24739 .metrics
24740 .blocking_tasks_completed
24741 .load(Ordering::Relaxed),
24742 2
24743 );
24744 assert_eq!(
24745 controller
24746 .metrics
24747 .active_blocking_tasks
24748 .load(Ordering::Relaxed),
24749 0
24750 );
24751 assert_eq!(
24752 controller
24753 .metrics
24754 .max_active_blocking_tasks
24755 .load(Ordering::Relaxed),
24756 1,
24757 "the public active-lane metric must remain within CurrentThread's reported capacity"
24758 );
24759 controller.shutdown().unwrap();
24760 }
24761
24762 #[cfg(not(target_family = "wasm"))]
24763 #[test]
24764 fn current_thread_nested_blocking_reuses_the_active_lane() {
24765 use std::sync::mpsc;
24766
24767 let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
24768 let (done_tx, done_rx) = mpsc::channel();
24769 let runner_controller = Arc::clone(&controller);
24770 let runner = std::thread::spawn(move || {
24771 let nested_controller = Arc::clone(&runner_controller);
24772 let outer = runner_controller
24773 .try_spawn_blocking(move || {
24774 let inner = nested_controller
24775 .try_spawn_blocking(|| 41usize)
24776 .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
24777 futures::executor::block_on(inner).unwrap() + 1
24778 })
24779 .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
24780 done_tx
24781 .send(futures::executor::block_on(outer).unwrap())
24782 .unwrap();
24783 });
24784
24785 assert_eq!(
24786 done_rx
24787 .recv_timeout(Duration::from_secs(2))
24788 .expect("a nested CurrentThread blocking call must not wait on its own lane"),
24789 42
24790 );
24791 runner.join().unwrap();
24792 assert_eq!(
24793 controller
24794 .metrics
24795 .blocking_tasks_started
24796 .load(Ordering::Relaxed),
24797 2
24798 );
24799 assert_eq!(
24800 controller
24801 .metrics
24802 .blocking_tasks_completed
24803 .load(Ordering::Relaxed),
24804 2
24805 );
24806 assert_eq!(
24807 controller
24808 .metrics
24809 .active_blocking_tasks
24810 .load(Ordering::Relaxed),
24811 0
24812 );
24813 assert_eq!(
24814 controller
24815 .metrics
24816 .max_active_blocking_tasks
24817 .load(Ordering::Relaxed),
24818 1,
24819 "nested work borrows the outer lane instead of reporting a second physical lane"
24820 );
24821 controller.shutdown().unwrap();
24822 }
24823
24824 #[cfg(not(target_family = "wasm"))]
24825 #[test]
24826 fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
24827 use std::sync::mpsc;
24828
24829 struct HotFuture {
24830 stop: Arc<AtomicBool>,
24831 }
24832
24833 impl Future for HotFuture {
24834 type Output = ();
24835
24836 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24837 if self.stop.load(Ordering::SeqCst) {
24838 Poll::Ready(())
24839 } else {
24840 cx.waker().wake_by_ref();
24841 Poll::Pending
24842 }
24843 }
24844 }
24845
24846 let stop = Arc::new(AtomicBool::new(false));
24847 let (ready_tx, ready_rx) = mpsc::channel();
24848 let (exact_tx, exact_rx) = mpsc::channel();
24849 let (done_tx, done_rx) = mpsc::channel();
24850 let runner_stop = Arc::clone(&stop);
24851 let runner = std::thread::spawn(move || {
24852 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
24853 RuntimeMetrics::default(),
24854 )));
24855 let owner = executor.fresh_owner_token();
24856 let job = BlockingJobId(u64::MAX - 70);
24857 let (result_tx, result_rx) = oneshot::channel();
24858 {
24859 let mut state = executor
24860 .blocking_admission
24861 .state
24862 .lock()
24863 .unwrap_or_else(std::sync::PoisonError::into_inner);
24864 state.owner = Some(owner);
24865 let exact_stop = Arc::clone(&runner_stop);
24866 state.queue.push(QueuedBlocking {
24867 id: job,
24868 run: Box::new(move || {
24869 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
24870 let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
24871 }),
24872 });
24873 }
24874 let exact = JoinHandle(JoinHandleInner::Blocking {
24875 receiver: result_rx,
24876 dependency: BlockingDependency {
24877 job,
24878 owner: Some(owner),
24879 },
24880 published: PublishedBlockingDependencies::default(),
24881 });
24882 let _owner = executor.enter_blocking_owner(owner, false);
24883
24884 let scheduler = Arc::clone(&executor);
24885 let hot_stop = Arc::clone(&runner_stop);
24886 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
24887 scheduler.schedule(runnable);
24888 });
24889 executor.schedule(runnable);
24890 ready_tx.send(()).unwrap();
24891
24892 let mut value = None;
24893 {
24894 let mut future = std::pin::pin!(async {
24895 value = Some(exact.await.unwrap());
24896 hot_task.await;
24897 });
24898 assert_eq!(
24899 executor.block_on(future.as_mut()),
24900 BlockOnOutcome::Completed
24901 );
24902 }
24903 executor
24904 .blocking_admission
24905 .state
24906 .lock()
24907 .unwrap_or_else(std::sync::PoisonError::into_inner)
24908 .owner = None;
24909 done_tx.send(value.unwrap()).unwrap();
24910 });
24911
24912 ready_rx
24913 .recv_timeout(Duration::from_secs(2))
24914 .expect("the fairness driver must start");
24915 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
24916 stop.store(true, Ordering::SeqCst);
24917 assert_eq!(
24918 done_rx
24919 .recv_timeout(Duration::from_secs(2))
24920 .expect("the CurrentThread driver must retire"),
24921 41
24922 );
24923 runner.join().unwrap();
24924 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
24925 assert_eq!(
24926 exact_before_stop,
24927 Some(true),
24928 "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
24929 );
24930 }
24931
24932 #[cfg(not(target_family = "wasm"))]
24933 #[test]
24934 fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
24935 use std::sync::mpsc;
24936
24937 let controller = Arc::new(current_thread_controller(
24938 "current-thread-cross-driver-blocking",
24939 ));
24940 let backend = controller.backend();
24941 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24942 panic!("configured CurrentThread backend must create a CurrentThread executor");
24943 };
24944 let executor = Arc::clone(executor);
24945 drop(backend);
24946
24947 let (release_driver_tx, release_driver_rx) = oneshot::channel();
24948 let (driver_started_tx, driver_started_rx) = mpsc::channel();
24949 let driver_controller = Arc::clone(&controller);
24950 let driver = std::thread::spawn(move || {
24951 driver_started_tx.send(std::thread::current().id()).unwrap();
24952 block_on_with_controller(&driver_controller, async {
24953 release_driver_rx
24954 .await
24955 .expect("driver release sender must remain alive");
24956 });
24957 });
24958 let driver_thread = driver_started_rx
24959 .recv_timeout(Duration::from_secs(1))
24960 .unwrap();
24961 wait_until("the secondary CurrentThread driver to park", || {
24962 executor
24963 .stop
24964 .parkers
24965 .lock()
24966 .unwrap_or_else(std::sync::PoisonError::into_inner)
24967 .iter()
24968 .filter_map(Weak::upgrade)
24969 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
24970 });
24971
24972 let (child_polled_tx, child_polled_rx) = mpsc::channel();
24973 let (release_child_tx, release_child_rx) = mpsc::channel();
24974 let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
24975 let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
24976 let (owner_done_tx, owner_done_rx) = mpsc::channel();
24977 let owner_controller = Arc::clone(&controller);
24978 let owner = std::thread::spawn(move || {
24979 let owner_thread = std::thread::current().id();
24980 let await_controller = Arc::clone(&owner_controller);
24981 let child_controller = Arc::clone(&owner_controller);
24982 let outer = owner_controller
24983 .try_spawn_blocking(move || {
24984 let spawn_controller = Arc::clone(&child_controller);
24985 let child = spawn_controller
24986 .try_spawn(async move {
24987 child_polled_tx.send(std::thread::current().id()).unwrap();
24988 release_child_rx.recv().unwrap();
24989 child_controller
24990 .try_spawn_blocking(move || {
24991 blocking_ran_tx.send(std::thread::current().id()).unwrap();
24992 41usize
24993 })
24994 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
24995 .await
24996 .unwrap()
24997 + 1
24998 })
24999 .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
25000
25001 assert_eq!(
25002 child_polled_rx
25003 .recv_timeout(Duration::from_secs(1))
25004 .unwrap(),
25005 driver_thread,
25006 "the secondary driver must own the child poll that reaches blocking admission"
25007 );
25008 owner_awaiting_tx.send(owner_thread).unwrap();
25009 block_on_with_controller(&await_controller, child).unwrap()
25010 })
25011 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
25012 owner_done_tx
25013 .send(futures::executor::block_on(outer))
25014 .unwrap();
25015 });
25016
25017 let owner_thread = owner_awaiting_rx
25018 .recv_timeout(Duration::from_secs(1))
25019 .unwrap();
25020 wait_until(
25021 "the blocking owner to park while awaiting the cross-driver child",
25022 || {
25023 executor
25024 .stop
25025 .parkers
25026 .lock()
25027 .unwrap_or_else(std::sync::PoisonError::into_inner)
25028 .iter()
25029 .filter_map(Weak::upgrade)
25030 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25031 },
25032 );
25033 release_child_tx.send(()).unwrap();
25034
25035 assert_eq!(
25036 blocking_ran_rx
25037 .recv_timeout(Duration::from_secs(2))
25038 .unwrap(),
25039 owner_thread,
25040 "the exact descendant must be serviced by the lane-owning driver"
25041 );
25042 assert_eq!(
25043 owner_done_rx
25044 .recv_timeout(Duration::from_secs(2))
25045 .expect("cross-driver blocking dependency must not deadlock")
25046 .unwrap(),
25047 42
25048 );
25049 release_driver_tx.send(()).unwrap();
25050 owner.join().unwrap();
25051 driver.join().unwrap();
25052 controller.shutdown().unwrap();
25053 assert_eq!(
25054 controller
25055 .metrics
25056 .blocking_tasks_started
25057 .load(Ordering::Relaxed),
25058 2
25059 );
25060 assert_eq!(
25061 controller
25062 .metrics
25063 .blocking_tasks_completed
25064 .load(Ordering::Relaxed),
25065 2
25066 );
25067 assert_eq!(
25068 controller
25069 .metrics
25070 .active_blocking_tasks
25071 .load(Ordering::Relaxed),
25072 0
25073 );
25074 assert_eq!(
25075 controller
25076 .metrics
25077 .max_active_blocking_tasks
25078 .load(Ordering::Relaxed),
25079 1,
25080 "cross-driver dependency service must reuse the owner's physical lane"
25081 );
25082 }
25083
25084 #[cfg(not(target_family = "wasm"))]
25085 #[test]
25086 fn current_thread_blocking_admission_rechecks_stop_under_lock() {
25087 use std::sync::mpsc;
25088
25089 let controller = Arc::new(current_thread_controller(
25090 "current-thread-blocking-stop-race",
25091 ));
25092 let backend = controller.backend();
25093 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25094 panic!("configured CurrentThread backend must create a CurrentThread executor");
25095 };
25096 let executor = Arc::clone(executor);
25097 drop(backend);
25098
25099 let ran = Arc::new(AtomicBool::new(false));
25100 let blocking_ran = Arc::clone(&ran);
25101 let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
25102 let (release_admission_tx, release_admission_rx) = mpsc::channel();
25103 let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
25104 let submit_controller = Arc::clone(&controller);
25105 let submitter = std::thread::spawn(move || {
25106 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25107 *slot.borrow_mut() = Some(Box::new(move || {
25108 admission_locked_tx.send(()).unwrap();
25109 release_admission_rx.recv().unwrap();
25110 }));
25111 });
25112 let handle = submit_controller
25113 .try_spawn_blocking(move || {
25114 blocking_ran.store(true, Ordering::SeqCst);
25115 })
25116 .unwrap_or_else(|_| {
25117 panic!("the submission linearized before shutdown and must be accepted")
25118 });
25119 blocking_result_tx
25120 .send(futures::executor::block_on(handle))
25121 .unwrap();
25122 });
25123 admission_locked_rx
25124 .recv_timeout(Duration::from_secs(2))
25125 .expect("the blocking submitter must pause while holding the admission lock");
25126
25127 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
25128 let shutdown_controller = Arc::clone(&controller);
25129 let shutdown = std::thread::spawn(move || {
25130 shutdown_result_tx
25131 .send(shutdown_controller.shutdown())
25132 .unwrap();
25133 });
25134 wait_until("CurrentThread shutdown to publish generation stop", || {
25135 executor.stop.is_stopping()
25136 });
25137 release_admission_tx.send(()).unwrap();
25138
25139 let blocking_error = blocking_result_rx
25140 .recv_timeout(Duration::from_secs(2))
25141 .expect("the admitted blocking handle must resolve")
25142 .expect_err("stop publication must reject the blocking lane claim");
25143 assert_eq!(
25144 blocking_error.to_string(),
25145 "the async runtime stopped before the blocking task could start"
25146 );
25147 assert!(
25148 !ran.load(Ordering::SeqCst),
25149 "blocking work must not start after stop publication"
25150 );
25151 shutdown_result_rx
25152 .recv_timeout(Duration::from_secs(2))
25153 .expect("shutdown must finish after the rejected admission retires")
25154 .unwrap();
25155 submitter.join().unwrap();
25156 shutdown.join().unwrap();
25157 }
25158
25159 #[cfg(not(target_family = "wasm"))]
25160 #[test]
25161 fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
25162 use std::sync::mpsc;
25163
25164 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25165 RuntimeMetrics::default(),
25166 )));
25167 let ran = Arc::new(AtomicBool::new(false));
25168 let job_ran = Arc::clone(&ran);
25169 {
25170 let mut state = executor
25171 .blocking_admission
25172 .state
25173 .lock()
25174 .unwrap_or_else(std::sync::PoisonError::into_inner);
25175 state.queue.push(QueuedBlocking {
25176 id: BlockingJobId(u64::MAX - 71),
25177 run: Box::new(move || {
25178 job_ran.store(true, Ordering::SeqCst);
25179 }),
25180 });
25181 }
25182
25183 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25184 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25185 let (claim_result_tx, claim_result_rx) = mpsc::channel();
25186 let claim_executor = Arc::clone(&executor);
25187 let claimant = std::thread::spawn(move || {
25188 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25189 *slot.borrow_mut() = Some(Box::new(move || {
25190 claim_locked_tx.send(()).unwrap();
25191 release_claim_rx.recv().unwrap();
25192 }));
25193 });
25194 claim_result_tx
25195 .send(claim_executor.run_one_blocking())
25196 .unwrap();
25197 });
25198 claim_locked_rx
25199 .recv_timeout(Duration::from_secs(2))
25200 .expect("the FIFO claimant must pause while holding the admission lock");
25201
25202 executor.stop.begin_shutdown();
25203 release_claim_tx.send(()).unwrap();
25204 assert!(
25205 !claim_result_rx
25206 .recv_timeout(Duration::from_secs(2))
25207 .expect("the stopped FIFO claim must finish"),
25208 "stop publication must reject the queued FIFO claim"
25209 );
25210 claimant.join().unwrap();
25211 assert!(
25212 !ran.load(Ordering::SeqCst),
25213 "queued FIFO work must not start after stop publication"
25214 );
25215 assert!(
25216 !executor
25217 .blocking_admission
25218 .state
25219 .lock()
25220 .unwrap_or_else(std::sync::PoisonError::into_inner)
25221 .queue
25222 .is_empty(),
25223 "the rejected FIFO job must remain queued for shutdown cancellation"
25224 );
25225
25226 executor.close_blocking_admission();
25227 assert!(
25228 executor
25229 .blocking_admission
25230 .state
25231 .lock()
25232 .unwrap_or_else(std::sync::PoisonError::into_inner)
25233 .queue
25234 .is_empty()
25235 );
25236 }
25237
25238 #[cfg(not(target_family = "wasm"))]
25239 #[test]
25240 fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
25241 use std::sync::mpsc;
25242
25243 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25244 RuntimeMetrics::default(),
25245 )));
25246 let ran = Arc::new(AtomicBool::new(false));
25247 let job_ran = Arc::clone(&ran);
25248 {
25249 let mut state = executor
25250 .blocking_admission
25251 .state
25252 .lock()
25253 .unwrap_or_else(std::sync::PoisonError::into_inner);
25254 state.queue.push(QueuedBlocking {
25255 id: BlockingJobId(u64::MAX - 73),
25256 run: Box::new(move || {
25257 job_ran.store(true, Ordering::SeqCst);
25258 }),
25259 });
25260 }
25261
25262 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25263 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25264 let (claim_result_tx, claim_result_rx) = mpsc::channel();
25265 let claim_executor = Arc::clone(&executor);
25266 let claimant = std::thread::spawn(move || {
25267 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25268 *slot.borrow_mut() = Some(Box::new(move || {
25269 claim_locked_tx.send(()).unwrap();
25270 release_claim_rx.recv().unwrap();
25271 }));
25272 });
25273 claim_result_tx
25274 .send(claim_executor.run_one_blocking())
25275 .unwrap();
25276 });
25277 claim_locked_rx
25278 .recv_timeout(Duration::from_secs(2))
25279 .expect("the FIFO claimant must pause while holding the admission lock");
25280
25281 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
25282 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
25283 let cancelling_executor = Arc::clone(&executor);
25284 let cancellation = std::thread::spawn(move || {
25285 let cancelling = cancelling_executor
25286 .begin_failed_dispatch_cancellation()
25287 .expect("the test must publish terminal dispatch cancellation");
25288 cancellation_published_tx.send(()).unwrap();
25289 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
25290 cancellation_done_tx.send(()).unwrap();
25291 });
25292 cancellation_published_rx
25293 .recv_timeout(Duration::from_secs(2))
25294 .expect("terminal dispatch cancellation must publish before queue cancellation");
25295 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25296 assert!(
25297 matches!(
25298 cancellation_done_rx.try_recv(),
25299 Err(mpsc::TryRecvError::Empty)
25300 ),
25301 "terminal cancellation must wait for the blocking-admission lock"
25302 );
25303
25304 release_claim_tx.send(()).unwrap();
25305 assert!(
25306 !claim_result_rx
25307 .recv_timeout(Duration::from_secs(2))
25308 .expect("the cancelled FIFO claim must finish"),
25309 "terminal cancellation publication must reject the queued FIFO claim"
25310 );
25311 cancellation_done_rx
25312 .recv_timeout(Duration::from_secs(2))
25313 .expect("terminal cancellation must drain the rejected FIFO job");
25314 claimant.join().unwrap();
25315 cancellation.join().unwrap();
25316
25317 assert!(
25318 !ran.load(Ordering::SeqCst),
25319 "terminally cancelled FIFO work must not start"
25320 );
25321 assert!(
25322 executor
25323 .blocking_admission
25324 .state
25325 .lock()
25326 .unwrap_or_else(std::sync::PoisonError::into_inner)
25327 .queue
25328 .is_empty(),
25329 "terminal cancellation must drain the rejected FIFO job"
25330 );
25331 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25332 }
25333
25334 #[cfg(not(target_family = "wasm"))]
25335 #[test]
25336 fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
25337 use std::sync::mpsc;
25338
25339 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25340 RuntimeMetrics::default(),
25341 )));
25342 let owner = executor.fresh_owner_token();
25343 let job = BlockingJobId(u64::MAX - 72);
25344 let ran = Arc::new(AtomicBool::new(false));
25345 let job_ran = Arc::clone(&ran);
25346 {
25347 let mut state = executor
25348 .blocking_admission
25349 .state
25350 .lock()
25351 .unwrap_or_else(std::sync::PoisonError::into_inner);
25352 state.owner = Some(owner);
25353 state.queue.push(QueuedBlocking {
25354 id: job,
25355 run: Box::new(move || {
25356 job_ran.store(true, Ordering::SeqCst);
25357 }),
25358 });
25359 }
25360 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25361 dependency.set_owned(BlockingDependency {
25362 job,
25363 owner: Some(owner),
25364 });
25365
25366 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25367 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25368 let (claim_result_tx, claim_result_rx) = mpsc::channel();
25369 let claim_executor = Arc::clone(&executor);
25370 let claim_dependency = Arc::clone(&dependency);
25371 let claimant = std::thread::spawn(move || {
25372 let _owner = claim_executor.enter_blocking_owner(owner, false);
25373 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25374 *slot.borrow_mut() = Some(Box::new(move || {
25375 claim_locked_tx.send(()).unwrap();
25376 release_claim_rx.recv().unwrap();
25377 }));
25378 });
25379 claim_result_tx
25380 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
25381 .unwrap();
25382 });
25383 claim_locked_rx
25384 .recv_timeout(Duration::from_secs(2))
25385 .expect("the owner claimant must pause while holding the admission lock");
25386
25387 executor.stop.begin_shutdown();
25388 release_claim_tx.send(()).unwrap();
25389 assert!(
25390 !claim_result_rx
25391 .recv_timeout(Duration::from_secs(2))
25392 .expect("the stopped owner claim must finish"),
25393 "stop publication must reject the exact-owner claim"
25394 );
25395 claimant.join().unwrap();
25396 assert!(
25397 !ran.load(Ordering::SeqCst),
25398 "queued exact-owner work must not start after stop publication"
25399 );
25400 assert!(
25401 !executor
25402 .blocking_admission
25403 .state
25404 .lock()
25405 .unwrap_or_else(std::sync::PoisonError::into_inner)
25406 .queue
25407 .is_empty(),
25408 "the rejected exact-owner job must remain queued for shutdown cancellation"
25409 );
25410
25411 executor
25412 .blocking_admission
25413 .state
25414 .lock()
25415 .unwrap_or_else(std::sync::PoisonError::into_inner)
25416 .owner = None;
25417 executor.close_blocking_admission();
25418 assert!(
25419 executor
25420 .blocking_admission
25421 .state
25422 .lock()
25423 .unwrap_or_else(std::sync::PoisonError::into_inner)
25424 .queue
25425 .is_empty()
25426 );
25427 }
25428
25429 #[cfg(not(target_family = "wasm"))]
25430 #[test]
25431 fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
25432 use std::sync::mpsc;
25433
25434 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25435 RuntimeMetrics::default(),
25436 )));
25437 let owner = executor.fresh_owner_token();
25438 let job = BlockingJobId(u64::MAX - 74);
25439 let ran = Arc::new(AtomicBool::new(false));
25440 let job_ran = Arc::clone(&ran);
25441 {
25442 let mut state = executor
25443 .blocking_admission
25444 .state
25445 .lock()
25446 .unwrap_or_else(std::sync::PoisonError::into_inner);
25447 state.owner = Some(owner);
25448 state.queue.push(QueuedBlocking {
25449 id: job,
25450 run: Box::new(move || {
25451 job_ran.store(true, Ordering::SeqCst);
25452 }),
25453 });
25454 }
25455 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25456 dependency.set_owned(BlockingDependency {
25457 job,
25458 owner: Some(owner),
25459 });
25460
25461 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25462 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25463 let (claim_result_tx, claim_result_rx) = mpsc::channel();
25464 let claim_executor = Arc::clone(&executor);
25465 let claim_dependency = Arc::clone(&dependency);
25466 let claimant = std::thread::spawn(move || {
25467 let _owner = claim_executor.enter_blocking_owner(owner, false);
25468 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25469 *slot.borrow_mut() = Some(Box::new(move || {
25470 claim_locked_tx.send(()).unwrap();
25471 release_claim_rx.recv().unwrap();
25472 }));
25473 });
25474 claim_result_tx
25475 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
25476 .unwrap();
25477 });
25478 claim_locked_rx
25479 .recv_timeout(Duration::from_secs(2))
25480 .expect("the owner claimant must pause while holding the admission lock");
25481
25482 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
25483 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
25484 let cancelling_executor = Arc::clone(&executor);
25485 let cancellation = std::thread::spawn(move || {
25486 let cancelling = cancelling_executor
25487 .begin_failed_dispatch_cancellation()
25488 .expect("the test must publish terminal dispatch cancellation");
25489 cancellation_published_tx.send(()).unwrap();
25490 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
25491 cancellation_done_tx.send(()).unwrap();
25492 });
25493 cancellation_published_rx
25494 .recv_timeout(Duration::from_secs(2))
25495 .expect("terminal dispatch cancellation must publish before queue cancellation");
25496 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25497 assert!(
25498 matches!(
25499 cancellation_done_rx.try_recv(),
25500 Err(mpsc::TryRecvError::Empty)
25501 ),
25502 "terminal cancellation must wait for the blocking-admission lock"
25503 );
25504
25505 release_claim_tx.send(()).unwrap();
25506 assert!(
25507 !claim_result_rx
25508 .recv_timeout(Duration::from_secs(2))
25509 .expect("the cancelled owner claim must finish"),
25510 "terminal cancellation publication must reject the exact-owner claim"
25511 );
25512 cancellation_done_rx
25513 .recv_timeout(Duration::from_secs(2))
25514 .expect("terminal cancellation must drain the rejected exact-owner job");
25515 claimant.join().unwrap();
25516 cancellation.join().unwrap();
25517
25518 assert!(
25519 !ran.load(Ordering::SeqCst),
25520 "terminally cancelled exact-owner work must not start"
25521 );
25522 assert!(
25523 executor
25524 .blocking_admission
25525 .state
25526 .lock()
25527 .unwrap_or_else(std::sync::PoisonError::into_inner)
25528 .queue
25529 .is_empty(),
25530 "terminal cancellation must drain the rejected exact-owner job"
25531 );
25532 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25533 }
25534
25535 #[cfg(not(target_family = "wasm"))]
25536 #[test]
25537 fn current_thread_shutdown_cancels_waiting_blocking_work() {
25538 use std::sync::mpsc;
25539
25540 struct DropSignal(mpsc::Sender<()>);
25541
25542 impl Drop for DropSignal {
25543 fn drop(&mut self) {
25544 let _ = self.0.send(());
25545 }
25546 }
25547
25548 let controller = Arc::new(current_thread_controller(
25549 "current-thread-blocking-shutdown",
25550 ));
25551 let backend = controller.backend();
25552 let (first_started_tx, first_started_rx) = mpsc::channel();
25553 let (release_first_tx, release_first_rx) = mpsc::channel();
25554 let first_controller = Arc::clone(&controller);
25555 let first = std::thread::spawn(move || {
25556 let handle = first_controller
25557 .try_spawn_blocking(move || {
25558 first_started_tx.send(()).unwrap();
25559 release_first_rx.recv().unwrap();
25560 })
25561 .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
25562 futures::executor::block_on(handle)
25563 });
25564 first_started_rx
25565 .recv_timeout(Duration::from_secs(1))
25566 .unwrap();
25567
25568 let waiting_ran = Arc::new(AtomicBool::new(false));
25569 let waiting_ran_task = Arc::clone(&waiting_ran);
25570 let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
25571 let waiting_drop = DropSignal(waiting_dropped_tx);
25572 let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
25573 let waiting_controller = Arc::clone(&controller);
25574 let waiting = std::thread::spawn(move || {
25575 let handle = waiting_controller
25576 .try_spawn_blocking(move || {
25577 let _waiting_drop = waiting_drop;
25578 waiting_ran_task.store(true, Ordering::SeqCst);
25579 })
25580 .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
25581 waiting_result_tx
25582 .send(futures::executor::block_on(handle))
25583 .unwrap();
25584 });
25585 wait_until("both CurrentThread blocking calls to be accepted", || {
25586 backend
25587 .work
25588 .state
25589 .lock()
25590 .unwrap_or_else(std::sync::PoisonError::into_inner)
25591 .active
25592 == 2
25593 });
25594
25595 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25596 let shutdown_controller = Arc::clone(&controller);
25597 let shutdown = std::thread::spawn(move || {
25598 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25599 });
25600 waiting_dropped_rx
25601 .recv_timeout(Duration::from_secs(1))
25602 .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
25603 assert!(
25604 !waiting_ran.load(Ordering::SeqCst),
25605 "waiting blocking work must not start"
25606 );
25607 assert!(
25608 waiting_result_rx
25609 .recv_timeout(Duration::from_secs(1))
25610 .unwrap()
25611 .is_err(),
25612 "the waiting blocking handle must resolve as cancelled"
25613 );
25614 assert!(
25615 shutdown_rx
25616 .recv_timeout(Duration::from_millis(100))
25617 .is_err(),
25618 "shutdown must still wait for the blocking closure that already owns the lane"
25619 );
25620
25621 release_first_tx.send(()).unwrap();
25622 first.join().unwrap().unwrap();
25623 waiting.join().unwrap();
25624 shutdown_rx
25625 .recv_timeout(Duration::from_secs(1))
25626 .unwrap()
25627 .unwrap();
25628 shutdown.join().unwrap();
25629 assert_eq!(
25630 controller
25631 .metrics
25632 .blocking_tasks_started
25633 .load(Ordering::Relaxed),
25634 1
25635 );
25636 assert_eq!(
25637 controller
25638 .metrics
25639 .blocking_tasks_completed
25640 .load(Ordering::Relaxed),
25641 1
25642 );
25643 assert_eq!(
25644 controller
25645 .metrics
25646 .active_blocking_tasks
25647 .load(Ordering::Relaxed),
25648 0
25649 );
25650 }
25651
25652 #[cfg(not(target_family = "wasm"))]
25653 #[test]
25654 fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
25655 use std::{panic::panic_any, sync::mpsc};
25656
25657 struct ReentrantPayload {
25658 controller: Arc<RuntimeController>,
25659 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
25660 }
25661
25662 impl Drop for ReentrantPayload {
25663 fn drop(&mut self) {
25664 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
25665 let shutdown = self.controller.shutdown();
25666 self.result.send((generation, shutdown)).unwrap();
25667 }
25668 }
25669
25670 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
25671
25672 impl Drop for PanicPayloadOnDrop {
25673 fn drop(&mut self) {
25674 panic_any(self.0.take().expect("the result must panic only once"));
25675 }
25676 }
25677
25678 let controller = Arc::new(current_thread_controller(
25679 "current-thread-panic-payload-generation",
25680 ));
25681 let backend = controller.backend();
25682 let generation = backend.generation();
25683 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25684 panic!("configured CurrentThread backend must create a CurrentThread executor");
25685 };
25686 let executor = Arc::clone(executor);
25687 drop(backend);
25688
25689 let owner = executor.fresh_owner_token();
25692 executor
25693 .blocking_admission
25694 .state
25695 .lock()
25696 .unwrap_or_else(std::sync::PoisonError::into_inner)
25697 .owner = Some(owner);
25698 let (result_tx, result_rx) = mpsc::channel();
25699 let payload_controller = Arc::clone(&controller);
25700 let handle = controller
25701 .try_spawn_blocking(move || {
25702 PanicPayloadOnDrop(Some(ReentrantPayload {
25703 controller: payload_controller,
25704 result: result_tx,
25705 }))
25706 })
25707 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
25708 drop(handle);
25709 executor
25710 .blocking_admission
25711 .state
25712 .lock()
25713 .unwrap_or_else(std::sync::PoisonError::into_inner)
25714 .owner = None;
25715 let dispatch = current_thread_dispatch(&executor);
25716 controller.drive_current_thread_dispatch(dispatch, false);
25717
25718 let (observed_generation, shutdown) = result_rx
25719 .recv_timeout(Duration::from_secs(1))
25720 .expect("the caught result panic payload must be destroyed in the scheduler turn");
25721 assert_eq!(observed_generation, Some(generation));
25722 let error = shutdown.expect_err("payload destruction is still work in the active generation");
25723 assert!(error.to_string().contains("work running on that runtime"));
25724 controller.shutdown().unwrap();
25725 }
25726
25727 #[test]
25728 fn current_thread_detached_blocking_result_drop_is_contained() {
25729 use std::sync::mpsc;
25730
25731 struct PanicOnDrop(mpsc::Sender<()>);
25732
25733 impl Drop for PanicOnDrop {
25734 fn drop(&mut self) {
25735 self.0.send(()).unwrap();
25736 panic!("intentional CurrentThread blocking result destructor panic");
25737 }
25738 }
25739
25740 let controller = current_thread_controller("current-thread-blocking-result-drop");
25741 let (dropped_tx, dropped_rx) = mpsc::channel();
25742 let handle = controller
25743 .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
25744 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
25745
25746 let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
25747 dropped_rx
25748 .recv_timeout(Duration::from_secs(1))
25749 .expect("dropping the detached handle must reclaim its blocking result");
25750 assert!(
25751 result.is_ok(),
25752 "the blocking result destructor panic must be contained"
25753 );
25754 controller.shutdown().unwrap();
25755 }
25756
25757 #[test]
25762 fn validate_rejects_zero_worker_threads() {
25763 let error = RuntimeOptions {
25764 flavor: RuntimeFlavor::MultiThread,
25765 worker_threads: 0,
25766 max_blocking_tasks: 1,
25767 thread_name_prefix: "rd8".to_string(),
25768 park_deadline: None,
25769 drain_linger: DEFAULT_DRAIN_LINGER,
25770 }
25771 .validate()
25772 .expect_err("worker_threads == 0 must be rejected");
25773 assert_eq!(
25774 error.to_string(),
25775 "worker_threads must be greater than zero"
25776 );
25777 }
25778
25779 #[test]
25780 fn validate_rejects_zero_max_blocking_tasks() {
25781 let error = RuntimeOptions {
25782 flavor: RuntimeFlavor::MultiThread,
25783 worker_threads: 2,
25784 max_blocking_tasks: 0,
25785 thread_name_prefix: "rd8".to_string(),
25786 park_deadline: None,
25787 drain_linger: DEFAULT_DRAIN_LINGER,
25788 }
25789 .validate()
25790 .expect_err("max_blocking_tasks == 0 must be rejected");
25791 assert_eq!(
25792 error.to_string(),
25793 "max_blocking_tasks must be greater than zero"
25794 );
25795 }
25796
25797 #[test]
25798 fn validate_current_thread_coerces_worker_threads_to_one() {
25799 let validated = RuntimeOptions {
25802 flavor: RuntimeFlavor::CurrentThread,
25803 worker_threads: 8,
25804 max_blocking_tasks: 8,
25805 thread_name_prefix: "rd8".to_string(),
25806 park_deadline: None,
25807 drain_linger: DEFAULT_DRAIN_LINGER,
25808 }
25809 .validate()
25810 .expect("CurrentThread options must validate");
25811 assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
25812 assert_eq!(validated.worker_threads, 1);
25813 assert_eq!(validated.max_blocking_tasks, 1);
25814 }
25815
25816 #[cfg(not(target_family = "wasm"))]
25819 #[test]
25820 fn validate_reserves_one_worker_from_blocking_admission() {
25821 let validated = RuntimeOptions {
25822 flavor: RuntimeFlavor::MultiThread,
25823 worker_threads: 2,
25824 max_blocking_tasks: 8,
25825 thread_name_prefix: "rd8".to_string(),
25826 park_deadline: None,
25827 drain_linger: DEFAULT_DRAIN_LINGER,
25828 }
25829 .validate()
25830 .expect("MultiThread options must validate");
25831 assert_eq!(validated.worker_threads, 2);
25832 assert_eq!(validated.max_blocking_tasks, 1);
25833 }
25834
25835 #[cfg(not(target_family = "wasm"))]
25836 #[test]
25837 fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
25838 let validated = RuntimeOptions {
25839 flavor: RuntimeFlavor::MultiThread,
25840 worker_threads: 1,
25841 max_blocking_tasks: 8,
25842 thread_name_prefix: "rd8-minimum".to_string(),
25843 park_deadline: None,
25844 drain_linger: DEFAULT_DRAIN_LINGER,
25845 }
25846 .validate()
25847 .expect("MultiThread options must validate");
25848 assert_eq!(validated.worker_threads, 2);
25849 assert_eq!(validated.max_blocking_tasks, 1);
25850 }
25851
25852 #[cfg(not(target_family = "wasm"))]
25853 #[test]
25854 fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
25855 let simulated_32_bit = RuntimeOptions {
25856 flavor: RuntimeFlavor::MultiThread,
25857 worker_threads: 256,
25858 max_blocking_tasks: 256,
25859 thread_name_prefix: "rd8-rayon-cap-32".to_string(),
25860 park_deadline: None,
25861 drain_linger: DEFAULT_DRAIN_LINGER,
25862 }
25863 .validate_with_rayon_max_threads(Some(255))
25864 .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
25865 assert_eq!(simulated_32_bit.worker_threads, 255);
25866 assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
25867
25868 let rayon_max_threads = max_num_threads();
25869 let requested_threads = rayon_max_threads
25870 .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
25871 .checked_add(1)
25872 .expect("supported worker counts must leave room for an oversized request");
25873 let controller = RuntimeController::new();
25874 controller
25875 .configure(RuntimeOptions {
25876 flavor: RuntimeFlavor::MultiThread,
25877 worker_threads: requested_threads,
25878 max_blocking_tasks: requested_threads,
25879 thread_name_prefix: "rd8-rayon-cap".to_string(),
25880 park_deadline: None,
25881 drain_linger: DEFAULT_DRAIN_LINGER,
25882 })
25883 .expect("an oversized request must clamp to the production worker ceiling");
25884
25885 let reported = controller.options();
25886 let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
25887 assert_eq!(reported.worker_threads, expected_workers);
25888 assert_eq!(
25889 reported.max_blocking_tasks,
25890 expected_workers - 1,
25891 "the blocking cap must reserve a lane from the physically realizable worker count"
25892 );
25893 }
25894
25895 #[cfg(not(target_family = "wasm"))]
25896 #[test]
25897 fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
25898 let extreme = Duration::from_micros(u64::MAX);
25905 let validated = RuntimeOptions {
25906 flavor: RuntimeFlavor::MultiThread,
25907 worker_threads: 2,
25908 max_blocking_tasks: 1,
25909 thread_name_prefix: "linger-clamp".to_string(),
25910 park_deadline: None,
25911 drain_linger: extreme,
25912 }
25913 .validate_with_rayon_max_threads(Some(2))
25914 .expect("an extreme linger budget must clamp, not fail validation");
25915 assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
25916
25917 let controller = RuntimeController::new();
25918 controller
25919 .configure_partial(RuntimeOptionsPatch {
25920 drain_linger: Some(extreme),
25921 ..RuntimeOptionsPatch::default()
25922 })
25923 .expect("an extreme linger patch must clamp, not fail validation");
25924 assert_eq!(
25925 controller.options().drain_linger,
25926 MAX_DRAIN_LINGER,
25927 "introspection must report the clamped budget, not the requested one"
25928 );
25929 controller
25930 .configure_partial(RuntimeOptionsPatch {
25931 drain_linger: Some(Duration::ZERO),
25932 ..RuntimeOptionsPatch::default()
25933 })
25934 .expect("ZERO (lingering disabled) must remain a valid budget");
25935 assert_eq!(controller.options().drain_linger, Duration::ZERO);
25936
25937 let metrics = Arc::new(RuntimeMetrics::default());
25938 let unvalidated = RuntimeOptions {
25939 flavor: RuntimeFlavor::MultiThread,
25940 worker_threads: 2,
25941 max_blocking_tasks: 2,
25942 thread_name_prefix: "linger-clamp-exec".to_string(),
25943 park_deadline: None,
25944 drain_linger: extreme,
25945 };
25946 let executor = MultiThreadExecutor::new(&unvalidated, metrics)
25947 .expect("the executor must construct with an extreme unvalidated budget");
25948 assert_eq!(
25949 executor.drain_linger,
25950 Some(MAX_DRAIN_LINGER),
25951 "the constructor's defensive clamp must bound unvalidated budgets"
25952 );
25953
25954 let metrics = Arc::new(RuntimeMetrics::default());
25955 let disabled = RuntimeOptions {
25956 flavor: RuntimeFlavor::MultiThread,
25957 worker_threads: 2,
25958 max_blocking_tasks: 2,
25959 thread_name_prefix: "linger-clamp-off".to_string(),
25960 park_deadline: None,
25961 drain_linger: Duration::ZERO,
25962 };
25963 let executor = MultiThreadExecutor::new(&disabled, metrics)
25964 .expect("the executor must construct with lingering disabled");
25965 assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
25966 }
25967
25968 #[cfg(not(target_family = "wasm"))]
25969 #[test]
25970 fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
25971 let controller = RuntimeController::new();
25972 let options = controller.options();
25973 assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
25974 assert_eq!(
25975 options.max_blocking_tasks,
25976 options.worker_threads.saturating_sub(1).max(1),
25977 "the unconfigured Rust API must use the same validated reserve as the binding"
25978 );
25979 }
25980
25981 #[cfg(not(target_family = "wasm"))]
25982 #[test]
25983 fn concurrent_partial_configuration_merges_from_latest_committed_options() {
25984 use std::{sync::mpsc, time::Duration};
25985
25986 let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
25987 let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
25988 let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
25989 let first_controller = Arc::clone(&controller);
25990 let first = std::thread::spawn(move || {
25991 first_controller
25992 .configure_partial_inner(
25993 RuntimeOptionsPatch {
25994 max_blocking_tasks: Some(3),
25995 ..RuntimeOptionsPatch::default()
25996 },
25997 |candidate| {
25998 assert_eq!(
25999 (candidate.worker_threads, candidate.max_blocking_tasks),
26000 (8, 3)
26001 );
26002 first_merged_tx
26003 .send(())
26004 .expect("the race coordinator must still be listening");
26005 release_first_rx
26006 .recv_timeout(Duration::from_secs(5))
26007 .expect("the first partial configuration must be released");
26008 },
26009 )
26010 .expect("the first partial configuration must commit");
26011 });
26012 first_merged_rx
26013 .recv_timeout(Duration::from_secs(5))
26014 .expect("the first partial configuration must hold the controller lock after merging");
26015
26016 let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
26017 let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
26018 let second_controller = Arc::clone(&controller);
26019 let second = std::thread::spawn(move || {
26020 second_started_tx
26021 .send(())
26022 .expect("the race coordinator must still be listening");
26023 second_controller
26024 .configure_partial_inner(
26025 RuntimeOptionsPatch {
26026 worker_threads: Some(6),
26027 ..RuntimeOptionsPatch::default()
26028 },
26029 |candidate| {
26030 second_candidate_tx
26031 .send((candidate.worker_threads, candidate.max_blocking_tasks))
26032 .expect("the race coordinator must still be listening");
26033 },
26034 )
26035 .expect("the second partial configuration must commit");
26036 });
26037 second_started_rx
26038 .recv_timeout(Duration::from_secs(5))
26039 .expect("the second partial configuration thread must start");
26040 release_first_tx
26041 .send(())
26042 .expect("the first partial configuration must still be waiting");
26043
26044 first
26045 .join()
26046 .expect("the first configuration thread must not panic");
26047 second
26048 .join()
26049 .expect("the second configuration thread must not panic");
26050 assert_eq!(
26051 second_candidate_rx
26052 .recv_timeout(Duration::from_secs(5))
26053 .expect("the second partial configuration must expose its merged candidate"),
26054 (6, 3),
26055 "the second caller must merge against the first caller's committed options"
26056 );
26057
26058 let options = controller.options();
26059 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
26060 assert_eq!(options.thread_name_prefix, "partial-config-race");
26061 }
26062
26063 #[cfg(not(target_family = "wasm"))]
26064 #[test]
26065 fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
26066 use std::{sync::mpsc, time::Duration};
26067
26068 let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
26069 let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
26070 let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
26071 let invalid_controller = Arc::clone(&controller);
26072 let invalid = std::thread::spawn(move || {
26073 invalid_controller
26074 .configure_partial_inner(
26075 RuntimeOptionsPatch {
26076 worker_threads: Some(0),
26077 ..RuntimeOptionsPatch::default()
26078 },
26079 |candidate| {
26080 assert_eq!(
26081 (candidate.worker_threads, candidate.max_blocking_tasks),
26082 (0, 2)
26083 );
26084 invalid_merged_tx
26085 .send(())
26086 .expect("the race coordinator must still be listening");
26087 release_invalid_rx
26088 .recv_timeout(Duration::from_secs(5))
26089 .expect("the invalid partial configuration must be released");
26090 },
26091 )
26092 .expect_err("the invalid partial configuration must be rejected")
26093 .to_string()
26094 });
26095 invalid_merged_rx
26096 .recv_timeout(Duration::from_secs(5))
26097 .expect("the invalid partial configuration must hold the lock after merging");
26098
26099 let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
26100 let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
26101 let valid_controller = Arc::clone(&controller);
26102 let valid = std::thread::spawn(move || {
26103 valid_started_tx
26104 .send(())
26105 .expect("the race coordinator must still be listening");
26106 valid_controller
26107 .configure_partial_inner(
26108 RuntimeOptionsPatch {
26109 max_blocking_tasks: Some(4),
26110 ..RuntimeOptionsPatch::default()
26111 },
26112 |candidate| {
26113 valid_candidate_tx
26114 .send((candidate.worker_threads, candidate.max_blocking_tasks))
26115 .expect("the race coordinator must still be listening");
26116 },
26117 )
26118 .expect("the valid partial configuration must commit");
26119 });
26120 valid_started_rx
26121 .recv_timeout(Duration::from_secs(5))
26122 .expect("the valid partial configuration thread must start");
26123 release_invalid_tx
26124 .send(())
26125 .expect("the invalid partial configuration must still be waiting");
26126
26127 assert_eq!(
26128 invalid
26129 .join()
26130 .expect("the invalid configuration thread must not panic"),
26131 "worker_threads must be greater than zero"
26132 );
26133 valid
26134 .join()
26135 .expect("the valid configuration thread must not panic");
26136 assert_eq!(
26137 valid_candidate_rx
26138 .recv_timeout(Duration::from_secs(5))
26139 .expect("the valid partial configuration must expose its merged candidate"),
26140 (6, 4),
26141 "the valid caller must merge against the last committed options, not the rejected candidate"
26142 );
26143
26144 let options = controller.options();
26145 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
26146 assert_eq!(options.thread_name_prefix, "partial-config-rollback");
26147 }
26148
26149 #[test]
26150 fn configure_after_backend_started_is_rejected() {
26151 let controller = RuntimeController::new();
26154 controller
26155 .configure(RuntimeOptions {
26156 flavor: RuntimeFlavor::CurrentThread,
26157 worker_threads: 1,
26158 max_blocking_tasks: 1,
26159 thread_name_prefix: "rd8".to_string(),
26160 park_deadline: None,
26161 drain_linger: DEFAULT_DRAIN_LINGER,
26162 })
26163 .expect("first configure before the backend exists must succeed");
26164
26165 let _backend = controller.backend();
26167
26168 let error = controller
26169 .configure(RuntimeOptions::default())
26170 .expect_err("configure after the backend started must be rejected");
26171 assert_eq!(
26172 error.to_string(),
26173 "the async runtime configuration is frozen; configure it before the first async call"
26174 );
26175 }
26176
26177 #[test]
26178 fn partial_configuration_after_backend_started_is_rejected() {
26179 let controller = current_thread_controller("partial-config-frozen");
26180 let _backend = controller.backend();
26181
26182 let error = controller
26183 .configure_partial(RuntimeOptionsPatch {
26184 worker_threads: Some(2),
26185 ..RuntimeOptionsPatch::default()
26186 })
26187 .expect_err("partial configuration after the backend started must be rejected");
26188 assert_eq!(
26189 error.to_string(),
26190 "the async runtime configuration is frozen; configure it before the first async call"
26191 );
26192 }
26193
26194 fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
26195 let controller = RuntimeController::new();
26196 controller
26197 .configure(RuntimeOptions {
26198 flavor: RuntimeFlavor::CurrentThread,
26199 worker_threads: 1,
26200 max_blocking_tasks: 1,
26201 thread_name_prefix: thread_name_prefix.to_string(),
26202 park_deadline: None,
26203 drain_linger: DEFAULT_DRAIN_LINGER,
26204 })
26205 .expect("CurrentThread test configuration must be accepted");
26206 controller
26207 }
26208
26209 #[cfg(not(target_family = "wasm"))]
26210 fn multi_thread_controller(
26211 thread_name_prefix: &str,
26212 worker_threads: usize,
26213 max_blocking_tasks: usize,
26214 ) -> RuntimeController {
26215 let controller = RuntimeController::new();
26216 controller
26217 .configure(RuntimeOptions {
26218 flavor: RuntimeFlavor::MultiThread,
26219 worker_threads,
26220 max_blocking_tasks,
26221 thread_name_prefix: thread_name_prefix.to_string(),
26222 park_deadline: None,
26223 drain_linger: DEFAULT_DRAIN_LINGER,
26224 })
26225 .expect("MultiThread test configuration must be accepted");
26226 controller
26227 }
26228
26229 #[test]
26230 fn blocking_backend_creation_failure_preserves_diagnostic() {
26231 let controller = current_thread_controller("blocking-backend-creation-failure");
26232 let ran = Arc::new(AtomicBool::new(false));
26233 let work_ran = Arc::clone(&ran);
26234
26235 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26236 let Err((error, work)) = controller.try_spawn_blocking(move || {
26237 work_ran.store(true, Ordering::SeqCst);
26238 17usize
26239 }) else {
26240 panic!("the injected backend creation failure must reject blocking work");
26241 };
26242 assert_eq!(
26243 error.to_string(),
26244 "injected async runtime backend creation failure"
26245 );
26246 assert!(
26247 !ran.load(Ordering::SeqCst),
26248 "rejected blocking work must not run"
26249 );
26250 assert_eq!(
26251 work(),
26252 17,
26253 "the rejected blocking closure must remain usable"
26254 );
26255 assert!(ran.load(Ordering::SeqCst));
26256
26257 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26258 let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
26259 .expect_err("infallible blocking submission must return a failed join handle");
26260 assert_eq!(
26261 error.to_string(),
26262 "injected async runtime backend creation failure"
26263 );
26264
26265 let handle = controller
26266 .try_spawn_blocking(|| 31usize)
26267 .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
26268 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
26269 controller.shutdown().unwrap();
26270 }
26271
26272 #[cfg(not(target_family = "wasm"))]
26273 #[test]
26274 fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
26275 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
26276
26277 if std::env::var_os(CHILD_ENV).is_some() {
26278 struct PollProbe {
26279 polls: Arc<AtomicUsize>,
26280 }
26281
26282 impl Future for PollProbe {
26283 type Output = ();
26284
26285 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26286 self.polls.fetch_add(1, Ordering::SeqCst);
26287 Poll::Ready(())
26288 }
26289 }
26290
26291 shutdown().expect("the isolated runtime must stop before block_on admission");
26292 let polls = Arc::new(AtomicUsize::new(0));
26293 let mut future = std::pin::pin!(PollProbe {
26294 polls: Arc::clone(&polls)
26295 });
26296 let error = try_block_on_dyn(future.as_mut())
26297 .expect_err("the stopped runtime must reject the borrowed future");
26298 assert_eq!(
26299 error.to_string(),
26300 "the async runtime is stopped; call start before submitting work"
26301 );
26302 assert_eq!(
26303 polls.load(Ordering::SeqCst),
26304 0,
26305 "rejected block_on must not poll its future"
26306 );
26307
26308 start().expect("the isolated runtime must restart");
26309 try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
26310 assert_eq!(polls.load(Ordering::SeqCst), 1);
26311 shutdown().expect("the restarted isolated runtime must stop");
26312 return;
26313 }
26314
26315 let output = std::process::Command::new(std::env::current_exe().unwrap())
26316 .arg("--exact")
26317 .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
26318 .arg("--nocapture")
26319 .env(CHILD_ENV, "1")
26320 .output()
26321 .expect("the stopped block_on subprocess must start");
26322 assert!(
26323 output.status.success(),
26324 "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
26325 output.status.code(),
26326 String::from_utf8_lossy(&output.stdout),
26327 String::from_utf8_lossy(&output.stderr)
26328 );
26329 }
26330
26331 #[cfg(not(target_family = "wasm"))]
26332 #[test]
26333 fn try_block_on_dyn_reports_concurrent_shutdown() {
26334 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
26335
26336 if std::env::var_os(CHILD_ENV).is_some() {
26337 use std::sync::mpsc;
26338
26339 struct PendingProbe {
26340 entered: Option<mpsc::Sender<()>>,
26341 complete: Arc<AtomicBool>,
26342 }
26343
26344 impl Future for PendingProbe {
26345 type Output = ();
26346
26347 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26348 if let Some(entered) = self.entered.take() {
26349 entered.send(()).unwrap();
26350 }
26351 if self.complete.load(Ordering::SeqCst) {
26352 Poll::Ready(())
26353 } else {
26354 Poll::Pending
26355 }
26356 }
26357 }
26358
26359 configure(RuntimeOptions {
26360 flavor: RuntimeFlavor::CurrentThread,
26361 worker_threads: 1,
26362 max_blocking_tasks: 1,
26363 thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
26364 park_deadline: None,
26365 drain_linger: DEFAULT_DRAIN_LINGER,
26366 })
26367 .expect("the isolated runtime must accept CurrentThread configuration");
26368 start().expect("the isolated runtime must start");
26369
26370 let (entered_tx, entered_rx) = mpsc::channel();
26371 let (error_tx, error_rx) = mpsc::channel();
26372 let (retry_tx, retry_rx) = mpsc::channel();
26373 let complete = Arc::new(AtomicBool::new(false));
26374 let future_complete = Arc::clone(&complete);
26375 let runner = std::thread::spawn(move || {
26376 let mut future = std::pin::pin!(PendingProbe {
26377 entered: Some(entered_tx),
26378 complete: future_complete
26379 });
26380 let error = try_block_on_dyn(future.as_mut())
26381 .expect_err("concurrent shutdown must interrupt the borrowed future")
26382 .to_string();
26383 error_tx.send(error).unwrap();
26384 retry_rx.recv().unwrap();
26385 try_block_on_dyn(future.as_mut())
26386 .expect("restart must accept the same interrupted borrowed future");
26387 });
26388 entered_rx
26389 .recv_timeout(Duration::from_secs(2))
26390 .expect("the borrowed future must be polled before shutdown");
26391 shutdown().expect("shutdown must wait for the active block_on scope to retire");
26392 assert_eq!(
26393 error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
26394 "the async runtime stopped before block_on completed its future"
26395 );
26396 start().expect("the isolated runtime must restart after the interrupted drive");
26397 complete.store(true, Ordering::SeqCst);
26398 retry_tx.send(()).unwrap();
26399 runner.join().unwrap();
26400 shutdown().expect("the retried isolated runtime must stop");
26401 return;
26402 }
26403
26404 let output = std::process::Command::new(std::env::current_exe().unwrap())
26405 .arg("--exact")
26406 .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
26407 .arg("--nocapture")
26408 .env(CHILD_ENV, "1")
26409 .output()
26410 .expect("the concurrent block_on shutdown subprocess must start");
26411 assert!(
26412 output.status.success(),
26413 "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
26414 output.status.code(),
26415 String::from_utf8_lossy(&output.stdout),
26416 String::from_utf8_lossy(&output.stderr)
26417 );
26418 }
26419
26420 #[test]
26421 fn initial_lifecycle_start_preserves_lazy_configuration() {
26422 let controller = RuntimeController::new();
26423 controller
26424 .start()
26425 .expect("initial lifecycle start must succeed");
26426 controller
26427 .configure(RuntimeOptions {
26428 flavor: RuntimeFlavor::CurrentThread,
26429 worker_threads: 1,
26430 max_blocking_tasks: 1,
26431 thread_name_prefix: "lifecycle-initial-start".to_string(),
26432 park_deadline: None,
26433 drain_linger: DEFAULT_DRAIN_LINGER,
26434 })
26435 .expect("module-registration start must not consume the configuration window");
26436
26437 let state = controller
26438 .state
26439 .lock()
26440 .unwrap_or_else(std::sync::PoisonError::into_inner);
26441 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26442 }
26443
26444 #[test]
26445 fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
26446 let controller = RuntimeController::new();
26447
26448 for _ in 0..3 {
26449 controller
26450 .start()
26451 .expect("zero-work lifecycle start must succeed");
26452 assert!(matches!(
26453 &controller
26454 .state
26455 .lock()
26456 .unwrap_or_else(std::sync::PoisonError::into_inner)
26457 .lifecycle,
26458 RuntimeLifecycle::Initial
26459 ));
26460
26461 controller
26462 .shutdown()
26463 .expect("zero-work lifecycle shutdown must succeed");
26464 assert!(matches!(
26465 &controller
26466 .state
26467 .lock()
26468 .unwrap_or_else(std::sync::PoisonError::into_inner)
26469 .lifecycle,
26470 RuntimeLifecycle::StoppedBeforeFirstUse
26471 ));
26472 }
26473
26474 controller
26475 .start()
26476 .expect("a later zero-work restart must remain lazy");
26477 controller
26478 .configure(RuntimeOptions {
26479 flavor: RuntimeFlavor::CurrentThread,
26480 worker_threads: 1,
26481 max_blocking_tasks: 1,
26482 thread_name_prefix: "lifecycle-zero-work-config".to_string(),
26483 park_deadline: None,
26484 drain_linger: DEFAULT_DRAIN_LINGER,
26485 })
26486 .expect("zero-work lifecycle cycles must not consume the configuration window");
26487
26488 let state = controller
26489 .state
26490 .lock()
26491 .unwrap_or_else(std::sync::PoisonError::into_inner);
26492 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26493 }
26494
26495 #[cfg(not(target_family = "wasm"))]
26496 #[test]
26497 fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
26498 use std::sync::mpsc;
26499
26500 struct BlockingDropFuture {
26501 entered: mpsc::Sender<()>,
26502 release: mpsc::Receiver<()>,
26503 }
26504
26505 impl Future for BlockingDropFuture {
26506 type Output = ();
26507
26508 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26509 Poll::Pending
26510 }
26511 }
26512
26513 impl Drop for BlockingDropFuture {
26514 fn drop(&mut self) {
26515 self.entered.send(()).unwrap();
26516 self.release.recv().unwrap();
26517 }
26518 }
26519
26520 let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
26521 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26522 let (release_drop_tx, release_drop_rx) = mpsc::channel();
26523 let submit_controller = Arc::clone(&controller);
26524 let submitter = std::thread::spawn(move || {
26525 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26526 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
26527 entered: drop_entered_tx,
26528 release: release_drop_rx,
26529 }));
26530 assert!(
26531 result.is_err(),
26532 "the injected backend creation failure must reject the submission"
26533 );
26534 });
26535 drop_entered_rx
26536 .recv_timeout(Duration::from_secs(2))
26537 .expect("the rejected future destructor must start");
26538
26539 let retry_ran = Arc::new(AtomicBool::new(false));
26540 let retry_ran_task = Arc::clone(&retry_ran);
26541 let Err(retry) = controller.try_spawn_detached(async move {
26542 retry_ran_task.store(true, Ordering::SeqCst);
26543 }) else {
26544 panic!("backend creation must remain closed during rejected destruction");
26545 };
26546 drop(retry);
26547 assert!(!retry_ran.load(Ordering::SeqCst));
26548
26549 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26550 let shutdown_controller = Arc::clone(&controller);
26551 let shutdown = std::thread::spawn(move || {
26552 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26553 });
26554 wait_until("initial shutdown to close runtime admission", || {
26555 matches!(
26556 controller
26557 .state
26558 .lock()
26559 .unwrap_or_else(std::sync::PoisonError::into_inner)
26560 .lifecycle,
26561 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
26562 )
26563 });
26564 assert!(
26565 shutdown_rx
26566 .recv_timeout(Duration::from_millis(200))
26567 .is_err(),
26568 "initial shutdown must wait for rejected user state destruction"
26569 );
26570 assert!(
26571 controller.try_spawn_detached(async {}).is_err(),
26572 "shutdown must keep first-generation admission closed while it waits"
26573 );
26574
26575 let (start_tx, start_rx) = mpsc::channel();
26576 let start_controller = Arc::clone(&controller);
26577 let starter = std::thread::spawn(move || {
26578 start_tx.send(start_controller.start()).unwrap();
26579 });
26580 assert!(
26581 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26582 "start must wait for the zero-backend shutdown transition to finish"
26583 );
26584
26585 release_drop_tx.send(()).unwrap();
26586 submitter.join().unwrap();
26587 shutdown_rx
26588 .recv_timeout(Duration::from_secs(2))
26589 .unwrap()
26590 .unwrap();
26591 shutdown.join().unwrap();
26592 start_rx
26593 .recv_timeout(Duration::from_secs(2))
26594 .unwrap()
26595 .unwrap();
26596 starter.join().unwrap();
26597
26598 let ran = Arc::new(AtomicBool::new(false));
26599 let ran_task = Arc::clone(&ran);
26600 controller
26601 .try_spawn_detached(async move {
26602 ran_task.store(true, Ordering::SeqCst);
26603 })
26604 .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
26605 let dispatch = controller_current_thread_dispatch(&controller);
26606 controller.drive_current_thread_dispatch(dispatch, false);
26607 assert!(ran.load(Ordering::SeqCst));
26608 controller.shutdown().unwrap();
26609 }
26610
26611 #[cfg(not(target_family = "wasm"))]
26612 #[test]
26613 fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
26614 use std::sync::mpsc;
26615
26616 struct BlockingDropFuture {
26617 entered: mpsc::Sender<()>,
26618 release: mpsc::Receiver<()>,
26619 }
26620
26621 impl Future for BlockingDropFuture {
26622 type Output = ();
26623
26624 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26625 Poll::Pending
26626 }
26627 }
26628
26629 impl Drop for BlockingDropFuture {
26630 fn drop(&mut self) {
26631 self.entered.send(()).unwrap();
26632 self.release.recv().unwrap();
26633 }
26634 }
26635
26636 let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
26637 let old_generation = controller.backend().generation();
26638 controller.shutdown().unwrap();
26639
26640 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26641 let (release_drop_tx, release_drop_rx) = mpsc::channel();
26642 let submit_controller = Arc::clone(&controller);
26643 let submitter = std::thread::spawn(move || {
26644 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
26645 entered: drop_entered_tx,
26646 release: release_drop_rx,
26647 }));
26648 assert!(
26649 result.is_err(),
26650 "the stopped runtime must reject the submission"
26651 );
26652 });
26653 drop_entered_rx
26654 .recv_timeout(Duration::from_secs(2))
26655 .unwrap();
26656
26657 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26658 let shutdown_controller = Arc::clone(&controller);
26659 let shutdown = std::thread::spawn(move || {
26660 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26661 });
26662 wait_until(
26663 "stopped shutdown to enter its zero-backend transition",
26664 || {
26665 matches!(
26666 controller
26667 .state
26668 .lock()
26669 .unwrap_or_else(std::sync::PoisonError::into_inner)
26670 .lifecycle,
26671 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
26672 )
26673 },
26674 );
26675
26676 let (start_tx, start_rx) = mpsc::channel();
26677 let start_controller = Arc::clone(&controller);
26678 let starter = std::thread::spawn(move || {
26679 start_tx.send(start_controller.start()).unwrap();
26680 });
26681 assert!(
26682 shutdown_rx
26683 .recv_timeout(Duration::from_millis(200))
26684 .is_err(),
26685 "stopped shutdown must wait for rejected destruction"
26686 );
26687 assert!(
26688 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26689 "restart must wait for the stopped zero-backend transition"
26690 );
26691
26692 release_drop_tx.send(()).unwrap();
26693 submitter.join().unwrap();
26694 shutdown_rx
26695 .recv_timeout(Duration::from_secs(2))
26696 .unwrap()
26697 .unwrap();
26698 shutdown.join().unwrap();
26699 start_rx
26700 .recv_timeout(Duration::from_secs(2))
26701 .unwrap()
26702 .unwrap();
26703 starter.join().unwrap();
26704
26705 let new_generation = controller.backend().generation();
26706 assert_ne!(new_generation, old_generation);
26707 controller.shutdown().unwrap();
26708 }
26709
26710 #[test]
26711 fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
26712 use std::sync::mpsc;
26713
26714 struct ShutdownOnDrop {
26715 controller: Arc<RuntimeController>,
26716 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26717 }
26718
26719 impl Future for ShutdownOnDrop {
26720 type Output = ();
26721
26722 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26723 Poll::Pending
26724 }
26725 }
26726
26727 impl Drop for ShutdownOnDrop {
26728 fn drop(&mut self) {
26729 self.result.send(self.controller.shutdown()).unwrap();
26730 }
26731 }
26732
26733 let controller = Arc::new(current_thread_controller(
26734 "failed-initial-reentrant-shutdown",
26735 ));
26736 let (result_tx, result_rx) = mpsc::channel();
26737 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26738 let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
26739 controller: Arc::clone(&controller),
26740 result: result_tx,
26741 }));
26742 assert!(rejected.is_err());
26743
26744 let error = result_rx
26745 .recv_timeout(Duration::from_secs(1))
26746 .expect("the rejected destructor must not self-deadlock")
26747 .expect_err("rejected destruction cannot wait for its own shutdown");
26748 assert!(error.to_string().contains("rejected submission"));
26749 {
26750 let state = controller
26751 .state
26752 .lock()
26753 .unwrap_or_else(std::sync::PoisonError::into_inner);
26754 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26755 assert_eq!(state.rejected_drops, 0);
26756 }
26757
26758 let handle = controller
26759 .try_spawn(async { 23usize })
26760 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
26761 let dispatch = controller_current_thread_dispatch(&controller);
26762 controller.drive_current_thread_dispatch(dispatch, false);
26763 assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
26764 controller.shutdown().unwrap();
26765 }
26766
26767 #[test]
26768 fn failed_initial_submission_destructor_cannot_reenter_start() {
26769 use std::sync::mpsc;
26770
26771 struct StartOnDrop {
26772 controller: Arc<RuntimeController>,
26773 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26774 release: mpsc::Receiver<()>,
26775 }
26776
26777 impl Future for StartOnDrop {
26778 type Output = ();
26779
26780 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26781 Poll::Pending
26782 }
26783 }
26784
26785 impl Drop for StartOnDrop {
26786 fn drop(&mut self) {
26787 self.result.send(self.controller.start()).unwrap();
26788 self.release.recv().unwrap();
26789 }
26790 }
26791
26792 let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
26793 let (result_tx, result_rx) = mpsc::channel();
26794 let (release_drop_tx, release_drop_rx) = mpsc::channel();
26795 let (submission_tx, submission_rx) = mpsc::channel();
26796 let submit_controller = Arc::clone(&controller);
26797 let submitter = std::thread::spawn(move || {
26798 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26799 let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
26800 controller: Arc::clone(&submit_controller),
26801 result: result_tx,
26802 release: release_drop_rx,
26803 }));
26804 submission_tx.send(rejected).unwrap();
26805 });
26806
26807 let error = result_rx
26808 .recv_timeout(Duration::from_secs(1))
26809 .expect("the rejected destructor must not self-deadlock")
26810 .expect_err("rejected destruction must not report a successful runtime start");
26811 assert!(error.to_string().contains("rejected submission"));
26812 {
26813 let state = controller
26814 .state
26815 .lock()
26816 .unwrap_or_else(std::sync::PoisonError::into_inner);
26817 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26818 assert_eq!(state.rejected_drops, 1);
26819 }
26820
26821 let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
26822 let (external_start_tx, external_start_rx) = mpsc::channel();
26823 let start_controller = Arc::clone(&controller);
26824 let starter = std::thread::spawn(move || {
26825 BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
26826 *slot.borrow_mut() = Some(Box::new(move || {
26827 wait_entered_tx.send(()).unwrap();
26828 }));
26829 });
26830 external_start_tx.send(start_controller.start()).unwrap();
26831 });
26832 wait_entered_rx
26833 .recv_timeout(Duration::from_secs(1))
26834 .expect("external start must reach the Initial rejected-destruction wait");
26835 assert!(
26836 external_start_rx.try_recv().is_err(),
26837 "external start must not return while rejected destruction remains active"
26838 );
26839
26840 release_drop_tx.send(()).unwrap();
26841 assert!(
26842 submission_rx
26843 .recv_timeout(Duration::from_secs(2))
26844 .unwrap()
26845 .is_err()
26846 );
26847 submitter.join().unwrap();
26848 external_start_rx
26849 .recv_timeout(Duration::from_secs(2))
26850 .unwrap()
26851 .unwrap();
26852 starter.join().unwrap();
26853
26854 {
26855 let state = controller
26856 .state
26857 .lock()
26858 .unwrap_or_else(std::sync::PoisonError::into_inner);
26859 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26860 assert_eq!(state.rejected_drops, 0);
26861 }
26862
26863 let handle = controller
26864 .try_spawn(async { 31usize })
26865 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
26866 let dispatch = controller_current_thread_dispatch(&controller);
26867 controller.drive_current_thread_dispatch(dispatch, false);
26868 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
26869 controller.shutdown().unwrap();
26870 }
26871
26872 #[test]
26873 fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
26874 use std::sync::mpsc;
26875
26876 struct BlockingDropFuture {
26877 controller: Arc<RuntimeController>,
26878 entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26879 release: mpsc::Receiver<()>,
26880 }
26881
26882 impl Future for BlockingDropFuture {
26883 type Output = ();
26884
26885 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26886 Poll::Pending
26887 }
26888 }
26889
26890 impl Drop for BlockingDropFuture {
26891 fn drop(&mut self) {
26892 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26893 self
26894 .entered
26895 .send((generation, self.controller.shutdown()))
26896 .unwrap();
26897 self.release.recv().unwrap();
26898 }
26899 }
26900
26901 let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
26902 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26903 let (release_drop_tx, release_drop_rx) = mpsc::channel();
26904 let (runner_done_tx, runner_done_rx) = mpsc::channel();
26905 let runner_controller = Arc::clone(&controller);
26906 let runner = std::thread::spawn(move || {
26907 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26908 let result = catch_unwind(AssertUnwindSafe(|| {
26909 block_on_with_controller(
26910 &runner_controller,
26911 BlockingDropFuture {
26912 controller: Arc::clone(&runner_controller),
26913 entered: drop_entered_tx,
26914 release: release_drop_rx,
26915 },
26916 );
26917 }));
26918 runner_done_tx.send(result.is_err()).unwrap();
26919 });
26920
26921 let (generation, reentrant_shutdown) = drop_entered_rx
26922 .recv_timeout(Duration::from_secs(2))
26923 .expect("failed block_on acquisition must destroy its owned future");
26924 assert_eq!(generation, None, "no runtime generation was created");
26925 let error = reentrant_shutdown
26926 .expect_err("rejected block_on destruction cannot wait for its own shutdown");
26927 assert!(error.to_string().contains("rejected submission"));
26928
26929 let Err(retry_error) = controller.try_backend() else {
26930 panic!("backend retry must remain closed during rejected block_on destruction");
26931 };
26932 assert!(retry_error.to_string().contains("rejected submission"));
26933
26934 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26935 let shutdown_controller = Arc::clone(&controller);
26936 let shutdown = std::thread::spawn(move || {
26937 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26938 });
26939 assert!(
26940 shutdown_rx
26941 .recv_timeout(Duration::from_millis(200))
26942 .is_err(),
26943 "initial shutdown must wait for rejected block_on destruction"
26944 );
26945
26946 release_drop_tx.send(()).unwrap();
26947 assert!(
26948 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
26949 "block_on must preserve its infallible panic contract after contained cleanup"
26950 );
26951 runner.join().unwrap();
26952 shutdown_rx
26953 .recv_timeout(Duration::from_secs(2))
26954 .unwrap()
26955 .unwrap();
26956 shutdown.join().unwrap();
26957
26958 controller
26959 .start()
26960 .expect("zero-generation shutdown must remain restartable");
26961 let ran = Arc::new(AtomicBool::new(false));
26962 let ran_task = Arc::clone(&ran);
26963 controller
26964 .try_spawn_detached(async move {
26965 ran_task.store(true, Ordering::SeqCst);
26966 })
26967 .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
26968 let dispatch = controller_current_thread_dispatch(&controller);
26969 controller.drive_current_thread_dispatch(dispatch, false);
26970 assert!(ran.load(Ordering::SeqCst));
26971 controller.shutdown().unwrap();
26972 }
26973
26974 #[test]
26975 fn public_block_on_contains_poll_and_drop_double_panic() {
26976 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
26977
26978 if std::env::var_os(CHILD_ENV).is_some() {
26979 struct PanicInPollAndDrop;
26980
26981 impl Future for PanicInPollAndDrop {
26982 type Output = ();
26983
26984 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26985 panic!("intentional block_on poll panic");
26986 }
26987 }
26988
26989 impl Drop for PanicInPollAndDrop {
26990 fn drop(&mut self) {
26991 panic!("intentional block_on future destructor panic");
26992 }
26993 }
26994
26995 let controller = current_thread_controller("block-on-double-panic");
26996 assert!(
26997 catch_unwind(AssertUnwindSafe(|| {
26998 block_on_with_controller(&controller, PanicInPollAndDrop);
26999 }))
27000 .is_err(),
27001 "the original poll panic must still reach the caller"
27002 );
27003 controller
27004 .shutdown()
27005 .expect("contained future destruction must not strand the runtime");
27006 return;
27007 }
27008
27009 let output = std::process::Command::new(std::env::current_exe().unwrap())
27010 .arg("--exact")
27011 .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
27012 .arg("--nocapture")
27013 .env(CHILD_ENV, "1")
27014 .output()
27015 .expect("the subprocess test must start");
27016 assert!(
27017 output.status.success(),
27018 "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
27019 output.status.code(),
27020 String::from_utf8_lossy(&output.stdout),
27021 String::from_utf8_lossy(&output.stderr)
27022 );
27023 }
27024
27025 #[test]
27026 fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
27027 let controller = RuntimeController::new();
27028 controller
27029 .start()
27030 .expect("initial lifecycle start must succeed");
27031 controller
27032 .shutdown()
27033 .expect("zero-work lifecycle shutdown must succeed");
27034 controller
27035 .start()
27036 .expect("zero-work lifecycle restart must remain lazy");
27037 controller
27038 .configure(RuntimeOptions {
27039 flavor: RuntimeFlavor::CurrentThread,
27040 worker_threads: 1,
27041 max_blocking_tasks: 1,
27042 thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
27043 park_deadline: None,
27044 drain_linger: DEFAULT_DRAIN_LINGER,
27045 })
27046 .expect("configuration before first real async use must succeed");
27047
27048 let first_generation = controller.backend().generation();
27049 controller
27050 .shutdown()
27051 .expect("the first real generation must stop");
27052 assert!(matches!(
27053 &controller
27054 .state
27055 .lock()
27056 .unwrap_or_else(std::sync::PoisonError::into_inner)
27057 .lifecycle,
27058 RuntimeLifecycle::Stopped
27059 ));
27060 let error = controller
27061 .configure(RuntimeOptions::default())
27062 .expect_err("a real generation must permanently freeze configuration");
27063 assert_eq!(
27064 error.to_string(),
27065 "the async runtime configuration is frozen; configure it before the first async call"
27066 );
27067
27068 controller
27069 .start()
27070 .expect("the stopped real generation must restart");
27071 let restarted_generation = {
27072 let state = controller
27073 .state
27074 .lock()
27075 .unwrap_or_else(std::sync::PoisonError::into_inner);
27076 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
27077 panic!("a real generation restart must eagerly create its replacement backend");
27078 };
27079 backend.generation()
27080 };
27081 assert_ne!(
27082 restarted_generation, first_generation,
27083 "a real generation restart must not reuse the retired backend"
27084 );
27085 controller
27086 .shutdown()
27087 .expect("the replacement generation must stop");
27088 }
27089
27090 #[test]
27091 fn runtime_controller_rejects_submissions_after_shutdown() {
27092 let controller = current_thread_controller("lifecycle-reject");
27093 controller.start().expect("runtime must start");
27094 drop(controller.backend());
27095 controller.shutdown().expect("runtime must stop");
27096
27097 let async_ran = Arc::new(AtomicBool::new(false));
27098 let async_ran_task = Arc::clone(&async_ran);
27099 let Err(rejected_future) = controller.try_spawn_detached(async move {
27100 async_ran_task.store(true, Ordering::SeqCst);
27101 }) else {
27102 panic!("shutdown must reject async work");
27103 };
27104 assert!(
27105 !async_ran.load(Ordering::SeqCst),
27106 "a rejected future must not be polled"
27107 );
27108 futures::executor::block_on(rejected_future);
27109 assert!(
27110 async_ran.load(Ordering::SeqCst),
27111 "the original future must remain usable"
27112 );
27113
27114 let blocking_ran = Arc::new(AtomicBool::new(false));
27115 let blocking_ran_task = Arc::clone(&blocking_ran);
27116 let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
27117 blocking_ran_task.store(true, Ordering::SeqCst);
27118 7
27119 }) else {
27120 panic!("shutdown must reject blocking work");
27121 };
27122 assert_eq!(
27123 blocking_error.to_string(),
27124 "the async runtime is stopped; call start before submitting work"
27125 );
27126 assert!(
27127 !blocking_ran.load(Ordering::SeqCst),
27128 "rejected blocking work must not run"
27129 );
27130 assert_eq!(
27131 rejected_work(),
27132 7,
27133 "the original closure must remain usable"
27134 );
27135
27136 let config_error = controller
27137 .configure(RuntimeOptions::default())
27138 .expect_err("shutdown must not make runtime configuration mutable again");
27139 assert_eq!(
27140 config_error.to_string(),
27141 "the async runtime configuration is frozen; configure it before the first async call"
27142 );
27143
27144 let state = controller
27145 .state
27146 .lock()
27147 .unwrap_or_else(std::sync::PoisonError::into_inner);
27148 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
27149 }
27150
27151 #[test]
27152 fn rejected_future_can_be_resubmitted_after_restart() {
27153 let controller = current_thread_controller("lifecycle-retry-future");
27154 controller.start().expect("runtime must start");
27155 drop(controller.backend());
27156 controller.shutdown().expect("runtime must stop");
27157
27158 let runs = Arc::new(AtomicUsize::new(0));
27159 let runs_task = Arc::clone(&runs);
27160 let future = async move {
27161 runs_task.fetch_add(1, Ordering::SeqCst);
27162 23
27163 };
27164 let Err((error, future)) = controller.try_spawn(future) else {
27165 panic!("stopped runtime must reject the future");
27166 };
27167 assert!(error.to_string().contains("runtime is stopped"));
27168 assert_eq!(runs.load(Ordering::SeqCst), 0);
27169
27170 controller.start().expect("runtime must restart");
27171 let handle = controller
27172 .try_spawn(future)
27173 .unwrap_or_else(|_| panic!("restart must accept retained future"));
27174 let dispatch = controller_current_thread_dispatch(&controller);
27175 controller.drive_current_thread_dispatch(dispatch, false);
27176 assert_eq!(
27177 futures::executor::block_on(handle).expect("retained future must complete"),
27178 23
27179 );
27180 assert_eq!(runs.load(Ordering::SeqCst), 1);
27181 controller
27182 .shutdown()
27183 .expect("replacement runtime must stop");
27184 }
27185
27186 #[test]
27187 fn runtime_controller_start_after_shutdown_accepts_work() {
27188 let controller = current_thread_controller("lifecycle-restart");
27189 controller.start().expect("runtime must start");
27190 drop(controller.backend());
27191 controller.shutdown().expect("runtime must stop");
27192 controller.start().expect("runtime must restart");
27193
27194 let async_ran = Arc::new(AtomicBool::new(false));
27195 let async_ran_task = Arc::clone(&async_ran);
27196 controller
27197 .try_spawn_detached(async move {
27198 async_ran_task.store(true, Ordering::SeqCst);
27199 })
27200 .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
27201 assert!(
27202 !async_ran.load(Ordering::SeqCst),
27203 "hostless CurrentThread submission must remain enqueue-only"
27204 );
27205 let dispatch = controller_current_thread_dispatch(&controller);
27206 controller.drive_current_thread_dispatch(dispatch, false);
27207 assert!(
27208 async_ran.load(Ordering::SeqCst),
27209 "restarted runtime must poll submitted work"
27210 );
27211
27212 let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
27213 panic!("restarted runtime must accept blocking work");
27214 };
27215 assert_eq!(
27216 futures::executor::block_on(handle).expect("blocking work must complete after restart"),
27217 11
27218 );
27219 }
27220
27221 #[test]
27222 fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
27223 let controller = current_thread_controller("stale-host-generation");
27224 let old_generation = controller.backend().generation();
27225 let old_dispatch = controller_current_thread_dispatch(&controller);
27226 controller.shutdown().unwrap();
27227 controller.start().unwrap();
27228 let new_generation = controller.backend().generation();
27229 assert_ne!(new_generation, old_generation);
27230
27231 let ran = Arc::new(AtomicBool::new(false));
27232 let ran_task = Arc::clone(&ran);
27233 controller
27234 .try_spawn_detached(async move {
27235 ran_task.store(true, Ordering::SeqCst);
27236 })
27237 .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
27238 let new_dispatch = controller_current_thread_dispatch(&controller);
27239 assert_ne!(new_dispatch, old_dispatch);
27240
27241 controller.drive_current_thread_dispatch(old_dispatch, false);
27242 assert!(
27243 !ran.load(Ordering::SeqCst),
27244 "a callback from the retired generation must not touch the restarted executor"
27245 );
27246 controller.drive_current_thread_dispatch(new_dispatch, false);
27247 assert!(ran.load(Ordering::SeqCst));
27248 controller.shutdown().unwrap();
27249 }
27250
27251 #[test]
27252 fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
27253 let controller = current_thread_controller("stale-host-cancellation");
27254 let old_generation = controller.backend().generation();
27255 let old_dispatch = controller_current_thread_dispatch(&controller);
27256 controller.shutdown().unwrap();
27257 controller.start().unwrap();
27258 let backend = controller.backend();
27259 let new_generation = backend.generation();
27260 assert_ne!(new_generation, old_generation);
27261 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
27262 panic!("configured CurrentThread backend must create a CurrentThread executor");
27263 };
27264 let new_dispatch = current_thread_dispatch(executor);
27265 assert_ne!(new_dispatch, old_dispatch);
27266
27267 controller.cancel_current_thread_dispatch_for_test(old_dispatch);
27268 assert_eq!(
27269 executor.dispatch_pending.load(Ordering::Acquire),
27270 new_dispatch,
27271 "a delayed failure from the retired generation must not release the replacement latch"
27272 );
27273
27274 controller.cancel_current_thread_dispatch_for_test(new_dispatch);
27275 assert_eq!(
27276 executor.dispatch_pending.load(Ordering::Acquire),
27277 0,
27278 "the live generation must be able to cancel its accepted dispatch"
27279 );
27280 drop(backend);
27281 controller.shutdown().unwrap();
27282 }
27283
27284 #[cfg(not(target_family = "wasm"))]
27285 #[test]
27286 fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
27287 use std::sync::mpsc;
27288
27289 type ProbeResult = (
27290 &'static str,
27291 Option<u64>,
27292 Result<(), String>,
27293 Result<(), String>,
27294 Result<(), String>,
27295 );
27296
27297 struct StaleGenerationProbe {
27298 label: &'static str,
27299 controller: Arc<RuntimeController>,
27300 result: mpsc::Sender<ProbeResult>,
27301 }
27302
27303 impl Drop for StaleGenerationProbe {
27304 fn drop(&mut self) {
27305 let generation = active_runtime_generation();
27306 let submission = match self.controller.try_spawn(async {}) {
27307 Ok(handle) => {
27308 drop(handle);
27309 Ok(())
27310 }
27311 Err((error, future)) => {
27312 drop(future);
27313 Err(error.to_string())
27314 }
27315 };
27316 let start = self.controller.start().map_err(|error| error.to_string());
27317 let shutdown = self
27318 .controller
27319 .shutdown()
27320 .map_err(|error| error.to_string());
27321 self
27322 .result
27323 .send((self.label, generation, submission, start, shutdown))
27324 .unwrap();
27325 }
27326 }
27327
27328 let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
27329 let old_generation = controller.backend().generation();
27330 let (result_tx, result_rx) = mpsc::channel();
27331
27332 let async_controller = Arc::clone(&controller);
27333 let async_result = result_tx.clone();
27334 let async_handle = controller
27335 .try_spawn(async move {
27336 StaleGenerationProbe {
27337 label: "async",
27338 controller: async_controller,
27339 result: async_result,
27340 }
27341 })
27342 .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
27343 let old_dispatch = controller_current_thread_dispatch(&controller);
27344 controller.drive_current_thread_dispatch(old_dispatch, false);
27345
27346 let ready_controller = Arc::clone(&controller);
27347 let ready_handle = controller
27348 .try_spawn_blocking(move || StaleGenerationProbe {
27349 label: "ready",
27350 controller: ready_controller,
27351 result: result_tx,
27352 })
27353 .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
27354
27355 controller.shutdown().unwrap();
27356 controller.start().unwrap();
27357 let new_generation = controller.backend().generation();
27358 assert_ne!(new_generation, old_generation);
27359
27360 drop(async_handle);
27361 let async_result = result_rx
27362 .recv_timeout(Duration::from_secs(2))
27363 .expect("the retained async output must be destroyed without waiting on the replacement");
27364 drop(ready_handle);
27365 let ready_result = result_rx
27366 .recv_timeout(Duration::from_secs(2))
27367 .expect("the retained immediate output must be destroyed without touching the replacement");
27368
27369 for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
27370 let (label, generation, submission, start, shutdown) = result;
27371 assert_eq!(label, expected_label);
27372 assert_eq!(generation, Some(old_generation));
27373 for error in [submission, start, shutdown] {
27374 assert!(
27375 error
27376 .expect_err("retired-generation output destruction must be rejected")
27377 .contains("retired generation")
27378 );
27379 }
27380 }
27381
27382 let survivor = controller
27383 .try_spawn(async { 29usize })
27384 .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
27385 let new_dispatch = controller_current_thread_dispatch(&controller);
27386 controller.drive_current_thread_dispatch(new_dispatch, false);
27387 assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
27388 controller.shutdown().unwrap();
27389 }
27390
27391 #[cfg(not(target_family = "wasm"))]
27392 #[test]
27393 fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
27394 use std::sync::mpsc;
27395
27396 struct ShutdownOnDrop {
27397 controller: Arc<RuntimeController>,
27398 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
27399 }
27400
27401 impl Drop for ShutdownOnDrop {
27402 fn drop(&mut self) {
27403 self
27404 .result
27405 .send((active_runtime_generation(), self.controller.shutdown()))
27406 .unwrap();
27407 }
27408 }
27409
27410 let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
27411 let old_generation = controller.backend().generation();
27412 let (result_tx, result_rx) = mpsc::channel();
27413 let old_handle = controller
27414 .try_spawn_blocking({
27415 let controller = Arc::clone(&controller);
27416 move || ShutdownOnDrop {
27417 controller,
27418 result: result_tx,
27419 }
27420 })
27421 .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
27422 wait_until(
27423 "the first-generation blocking result to be buffered",
27424 || {
27425 controller
27426 .metrics
27427 .blocking_tasks_completed
27428 .load(Ordering::Acquire)
27429 == 1
27430 },
27431 );
27432 controller.shutdown().unwrap();
27433 controller.start().unwrap();
27434 let new_generation = controller.backend().generation();
27435 assert_ne!(new_generation, old_generation);
27436
27437 let (holder_started_tx, holder_started_rx) = mpsc::channel();
27438 let (release_holder_tx, release_holder_rx) = mpsc::channel();
27439 let holder = controller
27440 .try_spawn_blocking(move || {
27441 holder_started_tx.send(()).unwrap();
27442 release_holder_rx.recv().unwrap();
27443 })
27444 .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
27445 holder_started_rx
27446 .recv_timeout(Duration::from_secs(2))
27447 .unwrap();
27448
27449 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27450 let shutdown_controller = Arc::clone(&controller);
27451 let shutdown = std::thread::spawn(move || {
27452 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27453 });
27454 wait_until("the replacement generation to enter Stopping", || {
27455 matches!(
27456 controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
27457 RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
27458 )
27459 });
27460
27461 let dropper = std::thread::spawn(move || drop(old_handle));
27462 let (observed_generation, reentrant_shutdown) =
27463 match result_rx.recv_timeout(Duration::from_secs(2)) {
27464 Ok(result) => result,
27465 Err(error) => {
27466 release_holder_tx.send(()).unwrap();
27467 let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
27468 let _ = dropper.join();
27469 let _ = shutdown.join();
27470 panic!("retained first-generation output waited on replacement shutdown: {error}");
27471 }
27472 };
27473 assert_eq!(observed_generation, Some(old_generation));
27474 let error = reentrant_shutdown
27475 .expect_err("a retained first-generation output must not control replacement shutdown");
27476 assert!(error.to_string().contains("retired generation"));
27477
27478 release_holder_tx.send(()).unwrap();
27479 shutdown_rx
27480 .recv_timeout(Duration::from_secs(2))
27481 .unwrap()
27482 .unwrap();
27483 dropper.join().unwrap();
27484 shutdown.join().unwrap();
27485 futures::executor::block_on(holder).unwrap();
27486 }
27487
27488 #[cfg(not(target_family = "wasm"))]
27489 #[test]
27490 fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
27491 use std::sync::mpsc;
27492
27493 let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
27494 let generation = controller.backend().generation();
27495 let dispatch = controller_current_thread_dispatch(&controller);
27496 let (admitted_tx, admitted_rx) = mpsc::channel();
27497 let (release_tx, release_rx) = mpsc::channel();
27498 let driver_controller = Arc::clone(&controller);
27499 let driver = std::thread::spawn(move || {
27500 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
27501 *slot.borrow_mut() = Some(Box::new(move || {
27502 admitted_tx.send(()).unwrap();
27503 release_rx.recv().unwrap();
27504 }));
27505 });
27506 driver_controller.drive_current_thread_dispatch(dispatch, false);
27507 });
27508 admitted_rx
27509 .recv_timeout(Duration::from_secs(2))
27510 .expect("the host turn must claim its scheduler role before execution");
27511
27512 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27513 let shutdown_controller = Arc::clone(&controller);
27514 let shutdown = std::thread::spawn(move || {
27515 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27516 });
27517 wait_until(
27518 "shutdown to close the admitted CurrentThread generation",
27519 || {
27520 let state = controller
27521 .state
27522 .lock()
27523 .unwrap_or_else(std::sync::PoisonError::into_inner);
27524 matches!(
27525 &state.lifecycle,
27526 RuntimeLifecycle::Stopping(identity) if identity.generation == generation
27527 )
27528 },
27529 );
27530 assert!(
27531 shutdown_rx
27532 .recv_timeout(Duration::from_millis(200))
27533 .is_err(),
27534 "shutdown must wait for an admitted host callback even before it starts draining"
27535 );
27536
27537 release_tx.send(()).unwrap();
27538 driver.join().unwrap();
27539 shutdown_rx
27540 .recv_timeout(Duration::from_secs(2))
27541 .unwrap()
27542 .unwrap();
27543 shutdown.join().unwrap();
27544 }
27545
27546 #[test]
27547 fn spawn_racing_shutdown_cannot_recreate_backend() {
27548 use std::sync::mpsc;
27549
27550 let controller = Arc::new(current_thread_controller("lifecycle-race"));
27551 controller.start().expect("runtime must start");
27552 drop(controller.backend());
27553
27554 let (at_hook_tx, at_hook_rx) = mpsc::channel();
27555 let (release_tx, release_rx) = mpsc::channel();
27556 let ran = Arc::new(AtomicBool::new(false));
27557 let ran_task = Arc::clone(&ran);
27558 let worker_controller = Arc::clone(&controller);
27559 let worker = std::thread::spawn(move || {
27560 BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
27561 *slot.borrow_mut() = Some(Box::new(move || {
27562 at_hook_tx
27563 .send(())
27564 .expect("test coordinator must still be listening");
27565 release_rx
27566 .recv()
27567 .expect("test coordinator must release the submission");
27568 }));
27569 });
27570
27571 let future = async move {
27572 ran_task.store(true, Ordering::SeqCst);
27573 };
27574 match worker_controller.try_spawn_detached(future) {
27575 Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
27576 Err(future) => drop(future),
27577 }
27578 });
27579
27580 at_hook_rx
27581 .recv_timeout(Duration::from_secs(5))
27582 .expect("submitting thread must stop before taking the controller lock");
27583 controller.shutdown().expect("runtime must stop");
27584 release_tx
27585 .send(())
27586 .expect("submitting thread must still be waiting");
27587 worker.join().expect("submitting thread must not panic");
27588
27589 assert!(
27590 !ran.load(Ordering::SeqCst),
27591 "the rejected task must never run"
27592 );
27593 let state = controller
27594 .state
27595 .lock()
27596 .unwrap_or_else(std::sync::PoisonError::into_inner);
27597 assert!(
27598 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
27599 "the rejected submission must not lazily recreate a backend"
27600 );
27601 }
27602
27603 #[cfg(not(target_family = "wasm"))]
27604 #[test]
27605 fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
27606 use std::sync::mpsc;
27607
27608 struct BlockingDropFuture {
27609 work: Arc<GenerationWork>,
27610 entered: mpsc::Sender<(Option<u64>, usize)>,
27611 release: mpsc::Receiver<()>,
27612 }
27613
27614 impl Future for BlockingDropFuture {
27615 type Output = ();
27616
27617 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27618 Poll::Pending
27619 }
27620 }
27621
27622 impl Drop for BlockingDropFuture {
27623 fn drop(&mut self) {
27624 let active = self
27625 .work
27626 .state
27627 .lock()
27628 .unwrap_or_else(std::sync::PoisonError::into_inner)
27629 .active;
27630 self
27631 .entered
27632 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
27633 .unwrap();
27634 self.release.recv().unwrap();
27635 }
27636 }
27637
27638 let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
27639 let backend = controller.backend();
27640 let old_generation = backend.generation();
27641 let work = Arc::clone(&backend.work);
27642 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
27643 panic!("configured CurrentThread backend must create a CurrentThread executor");
27644 };
27645 let executor = Arc::clone(executor);
27646 drop(backend);
27647
27648 let (registered_tx, registered_rx) = mpsc::channel();
27649 let (release_submission_tx, release_submission_rx) = mpsc::channel();
27650 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27651 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27652 let submit_controller = Arc::clone(&controller);
27653 let submit_work = Arc::clone(&work);
27654 let submitter = std::thread::spawn(move || {
27655 AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
27656 *slot.borrow_mut() = Some(Box::new(move || {
27657 registered_tx.send(()).unwrap();
27658 release_submission_rx.recv().unwrap();
27659 }));
27660 });
27661 submit_controller
27662 .try_spawn_detached(BlockingDropFuture {
27663 work: submit_work,
27664 entered: drop_entered_tx,
27665 release: release_drop_rx,
27666 })
27667 .unwrap_or_else(|_| {
27668 panic!("the submission linearized before shutdown and must be accepted")
27669 });
27670 });
27671 registered_rx
27672 .recv_timeout(Duration::from_secs(2))
27673 .expect("the submitting thread must register generation work before shutdown");
27674
27675 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27676 let shutdown_controller = Arc::clone(&controller);
27677 let shutdown = std::thread::spawn(move || {
27678 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27679 });
27680 wait_until("CurrentThread shutdown to close the runnable queue", || {
27681 executor
27682 .queue
27683 .lock()
27684 .unwrap_or_else(std::sync::PoisonError::into_inner)
27685 .closed
27686 });
27687 release_submission_tx.send(()).unwrap();
27688
27689 let (drop_generation, active_during_drop) = drop_entered_rx
27690 .recv_timeout(Duration::from_secs(2))
27691 .expect("the closed queue must destroy the accepted unpolled future");
27692 assert_eq!(drop_generation, Some(old_generation));
27693 assert_eq!(
27694 active_during_drop, 1,
27695 "accepted generation work must remain registered through user future destruction"
27696 );
27697
27698 let (start_tx, start_rx) = mpsc::channel();
27699 let start_controller = Arc::clone(&controller);
27700 let starter = std::thread::spawn(move || {
27701 start_tx.send(start_controller.start()).unwrap();
27702 });
27703 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
27704 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
27705 let shutdown_overlapped = early_shutdown.is_some();
27706 let restart_overlapped = early_start.is_some();
27707
27708 release_drop_tx.send(()).unwrap();
27709 submitter.join().unwrap();
27710 let shutdown_result = early_shutdown.unwrap_or_else(|| {
27711 shutdown_rx
27712 .recv_timeout(Duration::from_secs(2))
27713 .expect("shutdown must finish after drop")
27714 });
27715 shutdown_result.unwrap();
27716 shutdown.join().unwrap();
27717 let start_result = early_start.unwrap_or_else(|| {
27718 start_rx
27719 .recv_timeout(Duration::from_secs(2))
27720 .expect("restart must follow shutdown")
27721 });
27722 start_result.unwrap();
27723 starter.join().unwrap();
27724
27725 assert!(
27726 !shutdown_overlapped,
27727 "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
27728 );
27729 assert!(
27730 !restart_overlapped,
27731 "restart must not create a new generation while an accepted old-generation future is being destroyed"
27732 );
27733 assert_ne!(controller.backend().generation(), old_generation);
27734 controller.shutdown().unwrap();
27735 }
27736
27737 #[cfg(not(target_family = "wasm"))]
27738 #[test]
27739 fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
27740 use std::sync::mpsc;
27741
27742 struct BlockingDrop {
27743 work: Arc<GenerationWork>,
27744 entered: mpsc::Sender<(Option<u64>, usize)>,
27745 release: mpsc::Receiver<()>,
27746 }
27747
27748 impl Drop for BlockingDrop {
27749 fn drop(&mut self) {
27750 let active = self
27751 .work
27752 .state
27753 .lock()
27754 .unwrap_or_else(std::sync::PoisonError::into_inner)
27755 .active;
27756 self
27757 .entered
27758 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
27759 .unwrap();
27760 self.release.recv().unwrap();
27761 }
27762 }
27763
27764 let controller = Arc::new(multi_thread_controller(
27765 "accepted-blocking-submission-drop",
27766 2,
27767 1,
27768 ));
27769 let backend = controller.backend();
27770 let old_generation = backend.generation();
27771 let work = Arc::clone(&backend.work);
27772 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27773 panic!("configured MultiThread backend must create a MultiThread executor");
27774 };
27775 let executor = Arc::downgrade(executor);
27776 drop(backend);
27777
27778 let (registered_tx, registered_rx) = mpsc::channel();
27779 let (release_submission_tx, release_submission_rx) = mpsc::channel();
27780 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27781 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27782 let submit_controller = Arc::clone(&controller);
27783 let submitter = std::thread::spawn(move || {
27784 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
27785 *slot.borrow_mut() = Some(Box::new(move || {
27786 registered_tx.send(()).unwrap();
27787 release_submission_rx.recv().unwrap();
27788 }));
27789 });
27790 let drop_signal = BlockingDrop {
27791 work,
27792 entered: drop_entered_tx,
27793 release: release_drop_rx,
27794 };
27795 let handle = submit_controller
27796 .try_spawn_blocking(move || {
27797 let _drop_signal = drop_signal;
27798 })
27799 .unwrap_or_else(|_| {
27800 panic!("the submission linearized before shutdown and must be accepted")
27801 });
27802 assert!(
27803 futures::executor::block_on(handle).is_err(),
27804 "the executor must cancel work that reaches its queue after shutdown closed it"
27805 );
27806 });
27807 registered_rx
27808 .recv_timeout(Duration::from_secs(2))
27809 .expect("the submitting thread must register generation work before shutdown");
27810
27811 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27812 let shutdown_controller = Arc::clone(&controller);
27813 let shutdown = std::thread::spawn(move || {
27814 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27815 });
27816 wait_until("MultiThread shutdown to close the blocking queue", || {
27817 executor
27818 .upgrade()
27819 .expect("the retiring executor must remain alive until shutdown completes")
27820 .blocking_queue
27821 .lock()
27822 .unwrap_or_else(std::sync::PoisonError::into_inner)
27823 .closed
27824 });
27825 release_submission_tx.send(()).unwrap();
27826
27827 let (drop_generation, active_during_drop) = drop_entered_rx
27828 .recv_timeout(Duration::from_secs(2))
27829 .expect("the closed queue must destroy the accepted blocking closure");
27830 assert_eq!(drop_generation, Some(old_generation));
27831 assert_eq!(
27832 active_during_drop, 1,
27833 "accepted generation work must remain registered through blocking closure destruction"
27834 );
27835
27836 let (start_tx, start_rx) = mpsc::channel();
27837 let start_controller = Arc::clone(&controller);
27838 let starter = std::thread::spawn(move || {
27839 start_tx.send(start_controller.start()).unwrap();
27840 });
27841 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
27842 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
27843 let shutdown_overlapped = early_shutdown.is_some();
27844 let restart_overlapped = early_start.is_some();
27845
27846 release_drop_tx.send(()).unwrap();
27847 submitter.join().unwrap();
27848 let shutdown_result = early_shutdown.unwrap_or_else(|| {
27849 shutdown_rx
27850 .recv_timeout(Duration::from_secs(2))
27851 .expect("shutdown must finish after drop")
27852 });
27853 shutdown_result.unwrap();
27854 shutdown.join().unwrap();
27855 let start_result = early_start.unwrap_or_else(|| {
27856 start_rx
27857 .recv_timeout(Duration::from_secs(2))
27858 .expect("restart must follow shutdown")
27859 });
27860 start_result.unwrap();
27861 starter.join().unwrap();
27862
27863 assert!(
27864 !shutdown_overlapped,
27865 "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
27866 );
27867 assert!(
27868 !restart_overlapped,
27869 "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
27870 );
27871 assert_ne!(controller.backend().generation(), old_generation);
27872 controller.shutdown().unwrap();
27873 }
27874
27875 #[cfg(not(target_family = "wasm"))]
27876 #[test]
27877 fn multi_thread_controller_reports_its_exact_physical_worker_count() {
27878 let controller = multi_thread_controller("lifecycle-physical", 1, 8);
27879 let options = controller.options();
27880 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
27881
27882 let backend = controller.backend();
27883 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27884 panic!("configured MultiThread backend must create a Rayon executor");
27885 };
27886 assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
27887 let lifecycle = Arc::clone(&executor.worker_lifecycle);
27888 drop(backend);
27889
27890 controller.shutdown().expect("runtime must stop");
27891 assert_eq!(
27892 lifecycle.remaining(),
27893 0,
27894 "shutdown must observe every physical worker exit"
27895 );
27896 }
27897
27898 #[cfg(not(target_family = "wasm"))]
27899 #[test]
27900 fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
27901 use std::sync::mpsc;
27918
27919 let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
27920 let backend = controller.backend();
27921 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27922 panic!("configured MultiThread backend must create a Rayon executor");
27923 };
27924 let worker_count = executor.pool.current_num_threads();
27925 let completed = Arc::new(AtomicUsize::new(0));
27926 let probe_completed = Arc::clone(&completed);
27927 executor.pool.broadcast(move |_| {
27928 WORKER_TLS_DROP_PROBE.with(|slot| {
27929 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
27930 action: Some(WorkerTlsDropAction::Record {
27931 completed: Arc::clone(&probe_completed),
27932 }),
27933 });
27934 assert!(
27935 previous.is_none(),
27936 "each worker must install exactly one TLS drop probe"
27937 );
27938 });
27939 });
27940 drop(backend);
27941
27942 assert_eq!(
27943 completed.load(Ordering::SeqCst),
27944 0,
27945 "no worker TLS destructor may run before shutdown retires the workers"
27946 );
27947
27948 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27949 let shutdown_controller = Arc::clone(&controller);
27950 let sample_completed = Arc::clone(&completed);
27951 let shutdown = std::thread::spawn(move || {
27952 let result = shutdown_controller.shutdown();
27953 let seen_at_return = sample_completed.load(Ordering::SeqCst);
27957 shutdown_tx.send((result, seen_at_return)).unwrap();
27958 });
27959 let (shutdown_result, seen_at_return) = shutdown_rx
27960 .recv_timeout(Duration::from_secs(10))
27961 .expect("shutdown must finish once worker TLS destructors have run");
27962 shutdown_result.unwrap();
27963 assert_eq!(
27964 seen_at_return, worker_count,
27965 "every worker TLS destructor must have completed before shutdown() returned: \
27966 the worker-join barrier must include TLS-destructor completion"
27967 );
27968 join_within(
27969 "the shutdown thread exits after teardown",
27970 shutdown,
27971 Duration::from_secs(30),
27972 );
27973 }
27974
27975 #[cfg(not(target_family = "wasm"))]
27976 #[test]
27977 fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
27978 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
27979
27980 if std::env::var_os(CHILD_ENV).is_some() {
27981 use std::sync::mpsc;
27982
27983 let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
27984 let backend = controller.backend();
27985 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27986 panic!("configured MultiThread backend must create a Rayon executor");
27987 };
27988 let installed = Arc::new(AtomicBool::new(false));
27989 let (completed_tx, completed_rx) = mpsc::channel();
27990 let probe_controller = Arc::clone(&controller);
27991 let probe_installed = Arc::clone(&installed);
27992 executor.pool.broadcast(move |_| {
27993 if probe_installed.swap(true, Ordering::SeqCst) {
27994 return;
27995 }
27996 WORKER_TLS_DROP_PROBE.with(|slot| {
27997 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
27998 action: Some(WorkerTlsDropAction::ReenterLifecycle {
27999 controller: Arc::clone(&probe_controller),
28000 completed: completed_tx.clone(),
28001 }),
28002 });
28003 assert!(
28004 previous.is_none(),
28005 "the selected worker must have no prior TLS probe"
28006 );
28007 });
28008 });
28009 assert!(
28010 installed.load(Ordering::SeqCst),
28011 "one worker must install the lifecycle probe"
28012 );
28013 drop(backend);
28014
28015 controller
28016 .shutdown()
28017 .expect("external shutdown must join the retiring workers");
28018 let (start, shutdown) = completed_rx
28019 .recv_timeout(Duration::from_secs(2))
28020 .expect("the TLS destructor must complete both lifecycle reentry attempts");
28021 assert_eq!(
28022 start.unwrap_err(),
28023 "cannot restart the async runtime from work in the generation being stopped"
28024 );
28025 assert_eq!(
28026 shutdown.unwrap_err(),
28027 "cannot wait for async runtime shutdown from work in the generation being stopped"
28028 );
28029 return;
28030 }
28031
28032 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28033 .arg("--exact")
28034 .arg(
28035 "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
28036 )
28037 .arg("--nocapture")
28038 .env(CHILD_ENV, "1")
28039 .stdout(std::process::Stdio::piped())
28040 .stderr(std::process::Stdio::piped())
28041 .spawn()
28042 .expect("the TLS lifecycle-reentry subprocess must start");
28043 let deadline = Instant::now() + Duration::from_secs(5);
28044 let status = loop {
28045 if let Some(status) = child
28046 .try_wait()
28047 .expect("the subprocess status must be readable")
28048 {
28049 break status;
28050 }
28051 if Instant::now() >= deadline {
28052 let _ = child.kill();
28053 let output = child
28054 .wait_with_output()
28055 .expect("the timed-out subprocess must be reaped");
28056 panic!(
28057 "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
28058 String::from_utf8_lossy(&output.stdout),
28059 String::from_utf8_lossy(&output.stderr)
28060 );
28061 }
28062 std::thread::sleep(Duration::from_millis(10));
28063 };
28064 let output = child
28065 .wait_with_output()
28066 .expect("the completed subprocess output must be read");
28067 assert!(
28068 status.success(),
28069 "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
28070 status.code(),
28071 String::from_utf8_lossy(&output.stdout),
28072 String::from_utf8_lossy(&output.stderr)
28073 );
28074 }
28075
28076 #[cfg(not(target_family = "wasm"))]
28077 #[test]
28078 fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
28079 use std::sync::mpsc;
28080
28081 struct DropSignal(mpsc::Sender<()>);
28082 impl Drop for DropSignal {
28083 fn drop(&mut self) {
28084 let _ = self.0.send(());
28085 }
28086 }
28087
28088 let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
28089 let (polled_tx, polled_rx) = mpsc::channel();
28090 let (dropped_tx, dropped_rx) = mpsc::channel();
28091 controller
28092 .try_spawn_detached(async move {
28093 let _drop_signal = DropSignal(dropped_tx);
28094 polled_tx.send(()).unwrap();
28095 std::future::pending::<()>().await;
28096 })
28097 .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
28098 polled_rx
28099 .recv_timeout(Duration::from_secs(5))
28100 .expect("the pending task must be polled before shutdown");
28101
28102 controller
28103 .shutdown()
28104 .expect("shutdown must cancel accepted async work");
28105 dropped_rx
28106 .recv_timeout(Duration::from_secs(5))
28107 .expect("shutdown must drop the accepted task future before returning");
28108 }
28109
28110 #[cfg(not(target_family = "wasm"))]
28111 #[test]
28112 fn current_thread_shutdown_outranks_perpetual_self_wake() {
28113 use std::sync::mpsc;
28114
28115 struct SelfWakingPending {
28116 entered: Option<mpsc::Sender<()>>,
28117 }
28118
28119 impl Future for SelfWakingPending {
28120 type Output = ();
28121
28122 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
28123 if let Some(entered) = self.entered.take() {
28124 entered.send(()).unwrap();
28125 }
28126 cx.waker().wake_by_ref();
28127 Poll::Pending
28128 }
28129 }
28130
28131 let controller = Arc::new(current_thread_controller(
28132 "current-thread-self-wake-shutdown",
28133 ));
28134 let (entered_tx, entered_rx) = mpsc::channel();
28135 let (outcome_tx, outcome_rx) = mpsc::channel();
28136 let block_on_controller = Arc::clone(&controller);
28137 let runner = std::thread::spawn(move || {
28138 let mut future = std::pin::pin!(SelfWakingPending {
28139 entered: Some(entered_tx)
28140 });
28141 outcome_tx
28142 .send(block_on_controller.block_on(future.as_mut()))
28143 .unwrap();
28144 });
28145 entered_rx
28146 .recv_timeout(Duration::from_secs(2))
28147 .expect("the self-waking future must be polled before shutdown");
28148
28149 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28150 let shutdown_controller = Arc::clone(&controller);
28151 let shutdown = std::thread::spawn(move || {
28152 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28153 });
28154 shutdown_rx
28155 .recv_timeout(Duration::from_secs(2))
28156 .expect("shutdown must outrank a stored self-wake permit")
28157 .unwrap();
28158 assert_eq!(
28159 outcome_rx
28160 .recv_timeout(Duration::from_secs(2))
28161 .expect("the raw block_on driver must report shutdown"),
28162 BlockOnOutcome::Stopped
28163 );
28164 runner.join().unwrap();
28165 shutdown.join().unwrap();
28166 }
28167
28168 #[cfg(not(target_family = "wasm"))]
28169 #[test]
28170 fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
28171 use std::sync::mpsc;
28172
28173 let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
28174 let backend = controller.backend();
28175 let executor = match &backend.executor {
28176 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28177 RuntimeExecutor::CurrentThread(_) => unreachable!(),
28178 };
28179 let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
28180 let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
28181 for _ in 0..2 {
28182 let worker_ready_tx = worker_ready_tx.clone();
28183 let release_workers = Arc::clone(&release_workers);
28184 executor.pool.spawn(move || {
28185 worker_ready_tx.send(()).unwrap();
28186 let (released, changed) = &*release_workers;
28187 let mut released = released
28188 .lock()
28189 .unwrap_or_else(std::sync::PoisonError::into_inner);
28190 while !*released {
28191 released = changed
28192 .wait(released)
28193 .unwrap_or_else(std::sync::PoisonError::into_inner);
28194 }
28195 });
28196 }
28197 drop(worker_ready_tx);
28198 for _ in 0..2 {
28199 worker_ready_rx
28200 .recv_timeout(Duration::from_secs(5))
28201 .expect("both workers must be occupied before the FIFO runnable is submitted");
28202 }
28203 drop(executor);
28204 drop(backend);
28205
28206 let polls = Arc::new(AtomicUsize::new(0));
28207 let handle = controller.spawn(StopPollProbe {
28208 polls: Arc::clone(&polls),
28209 first_poll: None,
28210 });
28211 wait_until("the FIFO runnable to be queued", || {
28212 controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
28213 });
28214
28215 let observed_queue = Arc::new(Mutex::new(None));
28216 let hook_observed_queue = Arc::clone(&observed_queue);
28217 let hook_metrics = Arc::clone(&controller.metrics);
28218 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
28219 *slot.borrow_mut() = Some(Box::new(move || {
28220 let (released, changed) = &*release_workers;
28221 *released
28222 .lock()
28223 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
28224 changed.notify_all();
28225 wait_until("the stopped FIFO runnable claim", || {
28226 hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
28227 });
28228 *hook_observed_queue
28229 .lock()
28230 .unwrap_or_else(std::sync::PoisonError::into_inner) =
28231 Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
28232 }));
28233 });
28234
28235 controller
28236 .shutdown()
28237 .expect("shutdown must cancel the stopped FIFO claim");
28238 assert_eq!(polls.load(Ordering::SeqCst), 0);
28239 assert_eq!(
28240 *observed_queue
28241 .lock()
28242 .unwrap_or_else(std::sync::PoisonError::into_inner),
28243 Some(0)
28244 );
28245 assert_eq!(
28246 controller.metrics.queued_runnables.load(Ordering::SeqCst),
28247 0
28248 );
28249 drop(handle);
28250 }
28251
28252 #[cfg(not(target_family = "wasm"))]
28253 #[test]
28254 fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
28255 use std::sync::mpsc;
28256
28257 let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
28258 let backend = controller.backend();
28259 let executor = match &backend.executor {
28260 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28261 RuntimeExecutor::CurrentThread(_) => unreachable!(),
28262 };
28263 let polls = Arc::new(AtomicUsize::new(0));
28264 let scheduler = Arc::downgrade(&executor);
28265 let (runnable, task) = async_task::spawn(
28266 StopPollProbe {
28267 polls: Arc::clone(&polls),
28268 first_poll: None,
28269 },
28270 move |runnable| {
28271 if let Some(executor) = scheduler.upgrade() {
28272 executor.schedule(runnable);
28273 }
28274 },
28275 );
28276 let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
28277 let (release_slot_tx, release_slot_rx) = mpsc::channel();
28278 let (claim_done_tx, claim_done_rx) = mpsc::channel();
28279 let worker_executor = Arc::clone(&executor);
28280 let worker_metrics = Arc::clone(&controller.metrics);
28281 executor.pool.spawn(move || {
28282 let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
28283 worker_executor.schedule(runnable);
28284 slot_ready_tx.send(()).unwrap();
28285 release_slot_rx.recv().unwrap();
28286 let claimed = worker_executor.run_one_runnable();
28287 claim_done_tx
28288 .send((
28289 claimed,
28290 worker_metrics.queued_runnables.load(Ordering::SeqCst),
28291 ))
28292 .unwrap();
28293 });
28294 slot_ready_rx
28295 .recv_timeout(Duration::from_secs(5))
28296 .expect("the worker must publish the LIFO runnable before shutdown");
28297 assert_eq!(
28298 controller.metrics.queued_runnables.load(Ordering::SeqCst),
28299 1
28300 );
28301 drop(executor);
28302 drop(backend);
28303
28304 let observed_claim = Arc::new(Mutex::new(None));
28305 let hook_observed_claim = Arc::clone(&observed_claim);
28306 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
28307 *slot.borrow_mut() = Some(Box::new(move || {
28308 release_slot_tx.send(()).unwrap();
28309 let result = claim_done_rx
28310 .recv_timeout(Duration::from_secs(5))
28311 .expect("the stopped LIFO runnable claim must complete before abort");
28312 *hook_observed_claim
28313 .lock()
28314 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
28315 }));
28316 });
28317
28318 controller
28319 .shutdown()
28320 .expect("shutdown must cancel the stopped LIFO claim");
28321 assert_eq!(polls.load(Ordering::SeqCst), 0);
28322 assert_eq!(
28323 *observed_claim
28324 .lock()
28325 .unwrap_or_else(std::sync::PoisonError::into_inner),
28326 Some((true, 0))
28327 );
28328 assert_eq!(
28329 controller.metrics.queued_runnables.load(Ordering::SeqCst),
28330 0
28331 );
28332 drop(task);
28333 }
28334
28335 #[cfg(not(target_family = "wasm"))]
28336 #[test]
28337 fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
28338 use std::sync::mpsc;
28339
28340 struct BlockingDrop {
28341 entered: mpsc::Sender<()>,
28342 release: mpsc::Receiver<()>,
28343 }
28344
28345 impl Drop for BlockingDrop {
28346 fn drop(&mut self) {
28347 self.entered.send(()).unwrap();
28348 self.release.recv().unwrap();
28349 }
28350 }
28351
28352 struct TwoPollProbe {
28353 first_poll: Option<mpsc::Sender<()>>,
28354 release_first_poll: mpsc::Receiver<()>,
28355 second_poll: mpsc::Sender<()>,
28356 }
28357
28358 impl Future for TwoPollProbe {
28359 type Output = ();
28360
28361 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
28362 if let Some(first_poll) = self.first_poll.take() {
28363 first_poll.send(()).unwrap();
28364 self.release_first_poll.recv().unwrap();
28365 } else {
28366 self.second_poll.send(()).unwrap();
28367 }
28368 cx.waker().wake_by_ref();
28369 Poll::Pending
28370 }
28371 }
28372
28373 let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
28374 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28375 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28376 let blocking_drop = BlockingDrop {
28377 entered: drop_entered_tx,
28378 release: release_drop_rx,
28379 };
28380 controller
28381 .try_spawn_detached(async move {
28382 let _drop = blocking_drop;
28383 std::future::pending::<()>().await;
28384 })
28385 .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
28386
28387 let (first_poll_tx, first_poll_rx) = mpsc::channel();
28388 let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
28389 let (second_poll_tx, second_poll_rx) = mpsc::channel();
28390 let (outcome_tx, outcome_rx) = mpsc::channel();
28391 let runner_controller = Arc::clone(&controller);
28392 let runner = std::thread::spawn(move || {
28393 let mut future = std::pin::pin!(TwoPollProbe {
28394 first_poll: Some(first_poll_tx),
28395 release_first_poll: release_first_poll_rx,
28396 second_poll: second_poll_tx,
28397 });
28398 outcome_tx
28399 .send(runner_controller.block_on(future.as_mut()))
28400 .unwrap();
28401 });
28402 first_poll_rx
28403 .recv_timeout(Duration::from_secs(2))
28404 .expect("the active block_on poll must enter before shutdown");
28405
28406 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28407 let shutdown_controller = Arc::clone(&controller);
28408 let shutdown = std::thread::spawn(move || {
28409 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28410 });
28411 drop_entered_rx
28412 .recv_timeout(Duration::from_secs(2))
28413 .expect("shutdown must begin destroying queued work");
28414
28415 release_first_poll_tx.send(()).unwrap();
28416 let polled_again = second_poll_rx
28417 .recv_timeout(Duration::from_millis(200))
28418 .is_ok();
28419 release_drop_tx.send(()).unwrap();
28420
28421 assert!(
28422 !polled_again,
28423 "an active block_on driver must observe generation stop before queued user cleanup blocks"
28424 );
28425 assert_eq!(
28426 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
28427 BlockOnOutcome::Stopped
28428 );
28429 shutdown_rx
28430 .recv_timeout(Duration::from_secs(2))
28431 .unwrap()
28432 .unwrap();
28433 runner.join().unwrap();
28434 shutdown.join().unwrap();
28435 }
28436
28437 #[cfg(not(target_family = "wasm"))]
28438 #[test]
28439 fn shutdown_cancels_nested_block_on_within_deadline() {
28440 use std::sync::mpsc;
28441
28442 let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
28443 let task_controller = Arc::clone(&controller);
28444 let (entered_tx, entered_rx) = mpsc::channel();
28445 let task = controller
28446 .try_spawn(async move {
28447 entered_tx.send(()).unwrap();
28448 block_on_with_controller(&task_controller, std::future::pending::<()>());
28449 })
28450 .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
28451 entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
28452
28453 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28454 let shutdown_controller = Arc::clone(&controller);
28455 let shutdown = std::thread::spawn(move || {
28456 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28457 });
28458 shutdown_rx
28459 .recv_timeout(Duration::from_secs(2))
28460 .expect("shutdown must wake and cancel an active nested block_on")
28461 .unwrap();
28462 shutdown.join().unwrap();
28463 assert!(
28464 futures::executor::block_on(task).is_err(),
28465 "the cancelled nested block_on task must resolve as stopped"
28466 );
28467 }
28468
28469 #[cfg(not(target_family = "wasm"))]
28470 #[test]
28471 fn stopped_public_block_on_drops_future_before_generation_retirement() {
28472 use std::sync::mpsc;
28473
28474 struct PendingDrop {
28475 entered: Option<mpsc::Sender<()>>,
28476 dropped: mpsc::Sender<Option<u64>>,
28477 release: mpsc::Receiver<()>,
28478 }
28479
28480 impl Future for PendingDrop {
28481 type Output = ();
28482
28483 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28484 if let Some(entered) = self.entered.take() {
28485 entered.send(()).unwrap();
28486 }
28487 Poll::Pending
28488 }
28489 }
28490
28491 impl Drop for PendingDrop {
28492 fn drop(&mut self) {
28493 self
28494 .dropped
28495 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
28496 .unwrap();
28497 self.release.recv().unwrap();
28498 }
28499 }
28500
28501 let controller = Arc::new(current_thread_controller(
28502 "public-block-on-generation-destruction",
28503 ));
28504 let generation = controller.backend().generation();
28505 let (entered_tx, entered_rx) = mpsc::channel();
28506 let (dropped_tx, dropped_rx) = mpsc::channel();
28507 let (release_tx, release_rx) = mpsc::channel();
28508 let (runner_done_tx, runner_done_rx) = mpsc::channel();
28509 let block_controller = Arc::clone(&controller);
28510 let runner = std::thread::spawn(move || {
28511 let result = catch_unwind(AssertUnwindSafe(|| {
28512 block_on_with_controller(
28513 &block_controller,
28514 PendingDrop {
28515 entered: Some(entered_tx),
28516 dropped: dropped_tx,
28517 release: release_rx,
28518 },
28519 );
28520 }));
28521 runner_done_tx.send(result.is_err()).unwrap();
28522 });
28523 entered_rx
28524 .recv_timeout(Duration::from_secs(2))
28525 .expect("the public block_on future must be polled before shutdown");
28526
28527 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28528 let shutdown_controller = Arc::clone(&controller);
28529 let shutdown = std::thread::spawn(move || {
28530 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28531 });
28532
28533 assert_eq!(
28534 dropped_rx
28535 .recv_timeout(Duration::from_secs(2))
28536 .expect("shutdown must wake block_on and begin destroying its future"),
28537 Some(generation),
28538 "the stopped future must be destroyed under its retiring generation identity"
28539 );
28540 assert!(
28541 shutdown_rx
28542 .recv_timeout(Duration::from_millis(200))
28543 .is_err(),
28544 "shutdown must not publish Stopped while the public block_on future destructor is running"
28545 );
28546
28547 release_tx.send(()).unwrap();
28548 assert!(
28549 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
28550 "the public helper must retain its existing early-stop panic behavior"
28551 );
28552 shutdown_rx
28553 .recv_timeout(Duration::from_secs(2))
28554 .unwrap()
28555 .unwrap();
28556 runner.join().unwrap();
28557 shutdown.join().unwrap();
28558 }
28559
28560 #[cfg(not(target_family = "wasm"))]
28561 #[test]
28562 fn shutdown_contains_panicking_nested_block_on_future_drop() {
28563 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
28564
28565 if std::env::var_os(CHILD_ENV).is_some() {
28566 use std::sync::mpsc;
28567
28568 struct PendingPanicOnDrop {
28569 entered: Option<mpsc::Sender<()>>,
28570 }
28571
28572 impl Future for PendingPanicOnDrop {
28573 type Output = ();
28574
28575 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28576 if let Some(entered) = self.entered.take() {
28577 entered.send(()).unwrap();
28578 }
28579 Poll::Pending
28580 }
28581 }
28582
28583 impl Drop for PendingPanicOnDrop {
28584 fn drop(&mut self) {
28585 panic!("intentional nested block_on future destructor panic");
28586 }
28587 }
28588
28589 let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
28590 let task_controller = Arc::clone(&controller);
28591 let (entered_tx, entered_rx) = mpsc::channel();
28592 let task = controller
28593 .try_spawn(async move {
28594 block_on_with_controller(
28595 &task_controller,
28596 PendingPanicOnDrop {
28597 entered: Some(entered_tx),
28598 },
28599 );
28600 })
28601 .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
28602 entered_rx
28603 .recv_timeout(Duration::from_secs(2))
28604 .expect("the nested block_on future must be polled before shutdown");
28605
28606 controller
28607 .shutdown()
28608 .expect("shutdown must contain nested future destruction");
28609 assert!(
28610 futures::executor::block_on(task).is_err(),
28611 "shutdown cancellation must still surface as a task error"
28612 );
28613 return;
28614 }
28615
28616 let output = std::process::Command::new(std::env::current_exe().unwrap())
28617 .arg("--exact")
28618 .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
28619 .arg("--nocapture")
28620 .env(CHILD_ENV, "1")
28621 .output()
28622 .expect("the subprocess test must start");
28623 assert!(
28624 output.status.success(),
28625 "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
28626 output.status.code(),
28627 String::from_utf8_lossy(&output.stdout),
28628 String::from_utf8_lossy(&output.stderr)
28629 );
28630 }
28631
28632 #[test]
28633 fn panicking_future_drop_is_contained_before_async_task_drop() {
28634 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
28635
28636 if std::env::var_os(CHILD_ENV).is_some() {
28637 struct PanicOnDrop;
28638 impl Drop for PanicOnDrop {
28639 fn drop(&mut self) {
28640 panic!("intentional future destructor panic");
28641 }
28642 }
28643
28644 let controller = current_thread_controller("future-drop-subprocess");
28645 let bomb = PanicOnDrop;
28646 controller
28647 .try_spawn_detached(async move {
28648 let _bomb = bomb;
28649 std::future::pending::<()>().await;
28650 })
28651 .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
28652 controller
28653 .shutdown()
28654 .expect("shutdown must contain the future destructor panic");
28655 return;
28656 }
28657
28658 let output = std::process::Command::new(std::env::current_exe().unwrap())
28659 .arg("--exact")
28660 .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
28661 .arg("--nocapture")
28662 .env(CHILD_ENV, "1")
28663 .output()
28664 .expect("the subprocess test must start");
28665 assert!(
28666 output.status.success(),
28667 "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
28668 output.status.code(),
28669 String::from_utf8_lossy(&output.stdout),
28670 String::from_utf8_lossy(&output.stderr)
28671 );
28672 }
28673
28674 #[test]
28675 fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
28676 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
28677
28678 if std::env::var_os(CHILD_ENV).is_some() {
28679 struct PanicOnDrop;
28680 impl Drop for PanicOnDrop {
28681 fn drop(&mut self) {
28682 panic!("intentional detached output destructor panic");
28683 }
28684 }
28685
28686 let controller = current_thread_controller("output-drop-subprocess");
28687 let handle = controller
28688 .try_spawn(async { PanicOnDrop })
28689 .unwrap_or_else(|_| panic!("runtime must accept the task"));
28690 drop(handle);
28691 let dispatch = controller_current_thread_dispatch(&controller);
28692 controller.drive_current_thread_dispatch(dispatch, false);
28693 controller.shutdown().unwrap();
28694 return;
28695 }
28696
28697 let output = std::process::Command::new(std::env::current_exe().unwrap())
28698 .arg("--exact")
28699 .arg(
28700 "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
28701 )
28702 .arg("--nocapture")
28703 .env(CHILD_ENV, "1")
28704 .output()
28705 .expect("the subprocess test must start");
28706 assert!(
28707 output.status.success(),
28708 "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
28709 output.status.code(),
28710 String::from_utf8_lossy(&output.stdout),
28711 String::from_utf8_lossy(&output.stderr)
28712 );
28713 }
28714
28715 #[cfg(not(target_family = "wasm"))]
28716 #[test]
28717 fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
28718 use std::sync::mpsc;
28719
28720 struct BlockingDrop {
28721 controller: Arc<RuntimeController>,
28722 entered: mpsc::Sender<Option<u64>>,
28723 release: mpsc::Receiver<()>,
28724 reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
28725 }
28726
28727 impl Drop for BlockingDrop {
28728 fn drop(&mut self) {
28729 self
28730 .entered
28731 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
28732 .unwrap();
28733 self.release.recv().unwrap();
28734 self
28735 .reentrant_shutdown
28736 .send(self.controller.shutdown())
28737 .unwrap();
28738 }
28739 }
28740
28741 let controller = Arc::new(current_thread_controller(
28742 "current-thread-host-drain-output-retirement",
28743 ));
28744 let old_generation = controller.backend().generation();
28745 let (entered_tx, entered_rx) = mpsc::channel();
28746 let (release_tx, release_rx) = mpsc::channel();
28747 let (reentrant_tx, reentrant_rx) = mpsc::channel();
28748 let output_controller = Arc::clone(&controller);
28749 let handle = controller
28750 .try_spawn(async move {
28751 BlockingDrop {
28752 controller: output_controller,
28753 entered: entered_tx,
28754 release: release_rx,
28755 reentrant_shutdown: reentrant_tx,
28756 }
28757 })
28758 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
28759 drop(handle);
28760 let dispatch = controller_current_thread_dispatch(&controller);
28761
28762 let driver_controller = Arc::clone(&controller);
28763 let driver =
28764 std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
28765 assert_eq!(
28766 entered_rx
28767 .recv_timeout(Duration::from_secs(2))
28768 .expect("the host turn must begin destroying the detached output"),
28769 Some(old_generation),
28770 "detached output destruction must retain the generation that produced it"
28771 );
28772
28773 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28774 let shutdown_controller = Arc::clone(&controller);
28775 let shutdown = std::thread::spawn(move || {
28776 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28777 });
28778 wait_until(
28779 "CurrentThread shutdown to enter the old generation's stopping state",
28780 || {
28781 let state = controller
28782 .state
28783 .lock()
28784 .unwrap_or_else(std::sync::PoisonError::into_inner);
28785 matches!(
28786 &state.lifecycle,
28787 RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
28788 )
28789 },
28790 );
28791
28792 let (restart_tx, restart_rx) = mpsc::channel();
28793 let restart_controller = Arc::clone(&controller);
28794 let restart = std::thread::spawn(move || {
28795 restart_controller.start().unwrap();
28796 restart_tx
28797 .send(restart_controller.backend().generation())
28798 .unwrap();
28799 });
28800
28801 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28802 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
28803 let shutdown_overlapped = early_shutdown.is_some();
28804 let restart_overlapped = early_restart.is_some();
28805 release_tx.send(()).unwrap();
28806
28807 let reentrant_error = reentrant_rx
28808 .recv_timeout(Duration::from_secs(2))
28809 .expect("the retiring output destructor must finish its lifecycle check")
28810 .expect_err("old-generation output destruction must not shut down a replacement runtime");
28811 assert!(
28812 reentrant_error
28813 .to_string()
28814 .contains("generation being stopped"),
28815 "the destructor must still observe its old generation as Stopping: {reentrant_error}"
28816 );
28817
28818 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28819 shutdown_rx
28820 .recv_timeout(Duration::from_secs(2))
28821 .expect("shutdown must finish after detached output destruction retires")
28822 });
28823 shutdown_result.unwrap();
28824 let new_generation = early_restart.unwrap_or_else(|| {
28825 restart_rx
28826 .recv_timeout(Duration::from_secs(2))
28827 .expect("restart must finish after the old host turn retires")
28828 });
28829
28830 driver.join().unwrap();
28831 shutdown.join().unwrap();
28832 restart.join().unwrap();
28833 assert!(
28834 !shutdown_overlapped,
28835 "shutdown must not publish Stopped while a host turn is destroying detached output"
28836 );
28837 assert!(
28838 !restart_overlapped,
28839 "restart must not overlap a host turn from the retiring generation"
28840 );
28841 assert_ne!(new_generation, old_generation);
28842 controller.shutdown().unwrap();
28843 }
28844
28845 #[cfg(not(target_family = "wasm"))]
28846 #[test]
28847 fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
28848 use std::sync::mpsc;
28849
28850 struct ShutdownOnDrop {
28851 controller: Arc<RuntimeController>,
28852 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
28853 }
28854
28855 impl Drop for ShutdownOnDrop {
28856 fn drop(&mut self) {
28857 let _ = self.result.send(self.controller.shutdown());
28858 }
28859 }
28860
28861 let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
28862 let (running_tx, running_rx) = mpsc::channel();
28863 let (release_tx, release_rx) = mpsc::channel();
28864 let running = controller
28865 .try_spawn_blocking(move || {
28866 running_tx.send(()).unwrap();
28867 release_rx.recv().unwrap();
28868 })
28869 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
28870 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
28871
28872 let (reentrant_tx, reentrant_rx) = mpsc::channel();
28873 let bomb = ShutdownOnDrop {
28874 controller: Arc::clone(&controller),
28875 result: reentrant_tx,
28876 };
28877 let queued = controller
28878 .try_spawn_blocking(move || {
28879 let _bomb = &bomb;
28880 })
28881 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
28882
28883 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28884 let shutdown_controller = Arc::clone(&controller);
28885 let shutdown = std::thread::spawn(move || {
28886 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28887 });
28888 let error = reentrant_rx
28889 .recv_timeout(Duration::from_secs(2))
28890 .expect("queued destruction must not self-deadlock")
28891 .expect_err("the retiring generation must reject reentrant shutdown");
28892 assert!(error.to_string().contains("generation being stopped"));
28893 release_tx.send(()).unwrap();
28894 shutdown_rx
28895 .recv_timeout(Duration::from_secs(2))
28896 .unwrap()
28897 .unwrap();
28898 shutdown.join().unwrap();
28899 futures::executor::block_on(running).unwrap();
28900 assert!(futures::executor::block_on(queued).is_err());
28901 }
28902
28903 #[cfg(not(target_family = "wasm"))]
28904 #[test]
28905 fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
28906 use std::sync::mpsc;
28907
28908 struct ShutdownAfterRelease {
28909 controller: Arc<RuntimeController>,
28910 entered: mpsc::Sender<()>,
28911 release: mpsc::Receiver<()>,
28912 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
28913 }
28914
28915 impl Drop for ShutdownAfterRelease {
28916 fn drop(&mut self) {
28917 self.entered.send(()).unwrap();
28918 self.release.recv().unwrap();
28919 self.result.send(self.controller.shutdown()).unwrap();
28920 }
28921 }
28922
28923 let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
28924 let (holder_started_tx, holder_started_rx) = mpsc::channel();
28925 let (release_holder_tx, release_holder_rx) = mpsc::channel();
28926 let holder = controller
28927 .try_spawn_blocking(move || {
28928 holder_started_tx.send(()).unwrap();
28929 release_holder_rx.recv().unwrap();
28930 })
28931 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
28932 holder_started_rx
28933 .recv_timeout(Duration::from_secs(2))
28934 .unwrap();
28935
28936 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28937 let shutdown_controller = Arc::clone(&controller);
28938 let shutdown = std::thread::spawn(move || {
28939 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28940 });
28941 wait_until("the runtime entered Stopping", || {
28942 matches!(
28943 controller
28944 .state
28945 .lock()
28946 .unwrap_or_else(std::sync::PoisonError::into_inner)
28947 .lifecycle,
28948 RuntimeLifecycle::Stopping(_)
28949 )
28950 });
28951
28952 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28953 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28954 let (reentrant_tx, reentrant_rx) = mpsc::channel();
28955 let rejected_controller = Arc::clone(&controller);
28956 let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
28957 let rejected = std::thread::spawn(move || {
28958 let bomb = ShutdownAfterRelease {
28959 controller: Arc::clone(&rejected_controller),
28960 entered: drop_entered_tx,
28961 release: release_drop_rx,
28962 result: reentrant_tx,
28963 };
28964 let handle = rejected_controller.spawn(async move {
28965 let _bomb = bomb;
28966 std::future::pending::<()>().await;
28967 });
28968 rejected_done_tx
28969 .send(futures::executor::block_on(handle))
28970 .unwrap();
28971 });
28972 drop_entered_rx
28973 .recv_timeout(Duration::from_secs(2))
28974 .unwrap();
28975
28976 release_holder_tx.send(()).unwrap();
28977 assert!(
28978 shutdown_rx
28979 .recv_timeout(Duration::from_millis(200))
28980 .is_err(),
28981 "shutdown crossed into Stopped before rejected destruction finished"
28982 );
28983
28984 let (start_tx, start_rx) = mpsc::channel();
28985 let start_controller = Arc::clone(&controller);
28986 let starter = std::thread::spawn(move || {
28987 start_tx.send(start_controller.start()).unwrap();
28988 });
28989 assert!(
28990 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
28991 "restart crossed into a new generation before rejected destruction finished"
28992 );
28993
28994 release_drop_tx.send(()).unwrap();
28995 let error = reentrant_rx
28996 .recv_timeout(Duration::from_secs(2))
28997 .expect("the rejected destructor must complete")
28998 .expect_err("the retiring generation must reject the destructor's shutdown");
28999 assert!(error.to_string().contains("generation being stopped"));
29000 assert!(
29001 rejected_done_rx
29002 .recv_timeout(Duration::from_secs(2))
29003 .unwrap()
29004 .is_err()
29005 );
29006 rejected.join().unwrap();
29007
29008 shutdown_rx
29009 .recv_timeout(Duration::from_secs(2))
29010 .unwrap()
29011 .unwrap();
29012 shutdown.join().unwrap();
29013 start_rx
29014 .recv_timeout(Duration::from_secs(2))
29015 .unwrap()
29016 .unwrap();
29017 starter.join().unwrap();
29018 futures::executor::block_on(holder).unwrap();
29019 controller.shutdown().unwrap();
29020 }
29021
29022 #[cfg(not(target_family = "wasm"))]
29023 #[test]
29024 fn shutdown_contains_hostile_caught_panic_payload() {
29025 use std::{panic::panic_any, sync::mpsc};
29026
29027 struct PanicAgain;
29028 impl Drop for PanicAgain {
29029 fn drop(&mut self) {
29030 panic!("caught panic payload destructor escaped");
29031 }
29032 }
29033
29034 struct PanicPayloadOnDrop(mpsc::Sender<()>);
29035 impl Drop for PanicPayloadOnDrop {
29036 fn drop(&mut self) {
29037 let _ = self.0.send(());
29038 panic_any(PanicAgain);
29039 }
29040 }
29041
29042 let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
29043 let (running_tx, running_rx) = mpsc::channel();
29044 let (release_tx, release_rx) = mpsc::channel();
29045 let running = controller
29046 .try_spawn_blocking(move || {
29047 running_tx.send(()).unwrap();
29048 release_rx.recv().unwrap();
29049 })
29050 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29051 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29052
29053 let (drop_tx, drop_rx) = mpsc::channel();
29054 let payload = PanicPayloadOnDrop(drop_tx);
29055 let queued = controller
29056 .try_spawn_blocking(move || {
29057 let _payload = &payload;
29058 })
29059 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29060
29061 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29062 let shutdown_controller = Arc::clone(&controller);
29063 let shutdown = std::thread::spawn(move || {
29064 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29065 });
29066 drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29067 release_tx.send(()).unwrap();
29068 shutdown_rx
29069 .recv_timeout(Duration::from_secs(2))
29070 .expect("hostile panic payload destruction must remain contained")
29071 .unwrap();
29072 shutdown
29073 .join()
29074 .expect("the caught payload must not panic during destruction");
29075 futures::executor::block_on(running).unwrap();
29076 assert!(futures::executor::block_on(queued).is_err());
29077 }
29078
29079 #[cfg(not(target_family = "wasm"))]
29080 #[test]
29081 fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
29082 use std::sync::mpsc;
29083
29084 struct DropSignal(mpsc::Sender<()>);
29085 impl Drop for DropSignal {
29086 fn drop(&mut self) {
29087 let _ = self.0.send(());
29088 }
29089 }
29090
29091 let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
29092 let (running_tx, running_rx) = mpsc::channel();
29093 let (release_tx, release_rx) = mpsc::channel();
29094 let running = controller
29095 .try_spawn_blocking(move || {
29096 running_tx.send(()).unwrap();
29097 release_rx.recv().unwrap();
29098 7usize
29099 })
29100 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29101 running_rx
29102 .recv_timeout(Duration::from_secs(5))
29103 .expect("the first blocking job must hold the only blocking slot");
29104
29105 let queued_ran = Arc::new(AtomicBool::new(false));
29106 let queued_ran_work = Arc::clone(&queued_ran);
29107 let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
29108 let queued_drop_signal = DropSignal(queued_dropped_tx);
29109 let queued = controller
29110 .try_spawn_blocking(move || {
29111 let _drop_signal = queued_drop_signal;
29112 queued_ran_work.store(true, Ordering::SeqCst);
29113 11usize
29114 })
29115 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29116
29117 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29118 let shutdown_controller = Arc::clone(&controller);
29119 let shutdown = std::thread::spawn(move || {
29120 shutdown_controller.shutdown().unwrap();
29121 shutdown_done_tx.send(()).unwrap();
29122 });
29123
29124 queued_dropped_rx
29125 .recv_timeout(Duration::from_secs(5))
29126 .expect("shutdown must drop queued blocking work");
29127 assert!(
29128 !queued_ran.load(Ordering::SeqCst),
29129 "queued blocking work must not execute"
29130 );
29131 assert!(
29132 shutdown_done_rx.try_recv().is_err(),
29133 "shutdown must wait while an accepted blocking closure is still running"
29134 );
29135
29136 release_tx.send(()).unwrap();
29137 shutdown_done_rx
29138 .recv_timeout(Duration::from_secs(5))
29139 .expect("shutdown must finish after running blocking work retires");
29140 shutdown.join().unwrap();
29141 assert_eq!(futures::executor::block_on(running).unwrap(), 7);
29142 assert!(
29143 futures::executor::block_on(queued).is_err(),
29144 "the queued blocking handle must resolve as cancelled"
29145 );
29146 }
29147
29148 #[cfg(not(target_family = "wasm"))]
29149 #[test]
29150 fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
29151 use std::sync::mpsc;
29152
29153 struct PanicOnDrop(mpsc::Sender<()>);
29154 impl Drop for PanicOnDrop {
29155 fn drop(&mut self) {
29156 let _ = self.0.send(());
29157 panic!("intentional queued blocking destructor panic");
29158 }
29159 }
29160
29161 let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
29162 let (running_tx, running_rx) = mpsc::channel();
29163 let (release_tx, release_rx) = mpsc::channel();
29164 let running = controller
29165 .try_spawn_blocking(move || {
29166 running_tx.send(()).unwrap();
29167 release_rx.recv().unwrap();
29168 })
29169 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29170 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29171
29172 let (drop_tx, drop_rx) = mpsc::channel();
29173 let panic_on_drop = PanicOnDrop(drop_tx);
29174 let queued = controller
29175 .try_spawn_blocking(move || {
29176 let _keep_captured_until_run = &panic_on_drop;
29177 })
29178 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29179
29180 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29181 let shutdown_controller = Arc::clone(&controller);
29182 let shutdown = std::thread::spawn(move || {
29183 shutdown_controller.shutdown().unwrap();
29184 shutdown_done_tx.send(()).unwrap();
29185 });
29186
29187 drop_rx
29188 .recv_timeout(Duration::from_secs(5))
29189 .expect("shutdown must attempt to drop the queued closure");
29190 release_tx.send(()).unwrap();
29191 shutdown_done_rx
29192 .recv_timeout(Duration::from_secs(5))
29193 .expect("a panicking queued destructor must not strand shutdown");
29194 shutdown
29195 .join()
29196 .expect("shutdown must contain the destructor panic");
29197 futures::executor::block_on(running).unwrap();
29198 assert!(futures::executor::block_on(queued).is_err());
29199
29200 controller
29201 .start()
29202 .expect("the stopped controller must restart");
29203 let restarted = controller
29204 .try_spawn_blocking(|| 17usize)
29205 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29206 assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
29207 controller.shutdown().unwrap();
29208 }
29209
29210 #[cfg(not(target_family = "wasm"))]
29211 #[test]
29212 fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
29213 use std::{panic::panic_any, sync::mpsc};
29214
29215 struct ReentrantPayload {
29216 controller: Arc<RuntimeController>,
29217 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
29218 }
29219
29220 impl Drop for ReentrantPayload {
29221 fn drop(&mut self) {
29222 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
29223 let shutdown = self.controller.shutdown();
29224 self.result.send((generation, shutdown)).unwrap();
29225 }
29226 }
29227
29228 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
29229
29230 impl Drop for PanicPayloadOnDrop {
29231 fn drop(&mut self) {
29232 panic_any(self.0.take().expect("the result must panic only once"));
29233 }
29234 }
29235
29236 let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
29237 let generation = controller.backend().generation();
29238 let (started_tx, started_rx) = mpsc::channel();
29239 let (release_tx, release_rx) = mpsc::channel();
29240 let (result_tx, result_rx) = mpsc::channel();
29241 let payload_controller = Arc::clone(&controller);
29242 let handle = controller
29243 .try_spawn_blocking(move || {
29244 started_tx.send(()).unwrap();
29245 release_rx.recv().unwrap();
29246 PanicPayloadOnDrop(Some(ReentrantPayload {
29247 controller: payload_controller,
29248 result: result_tx,
29249 }))
29250 })
29251 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29252 started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29253
29254 drop(handle);
29257 release_tx.send(()).unwrap();
29258 let (observed_generation, reentrant_shutdown) = result_rx
29259 .recv_timeout(Duration::from_secs(5))
29260 .expect("the dropped result panic payload must not self-deadlock");
29261 assert_eq!(observed_generation, Some(generation));
29262 let error =
29263 reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
29264 assert!(error.to_string().contains("work running on that runtime"));
29265
29266 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29267 let shutdown_controller = Arc::clone(&controller);
29268 let shutdown = std::thread::spawn(move || {
29269 shutdown_controller.shutdown().unwrap();
29270 shutdown_done_tx.send(()).unwrap();
29271 });
29272 shutdown_done_rx
29273 .recv_timeout(Duration::from_secs(5))
29274 .expect("a dropped-result destructor panic must not strand shutdown");
29275 shutdown
29276 .join()
29277 .expect("shutdown must contain the destructor panic");
29278
29279 controller
29280 .start()
29281 .expect("the stopped controller must restart");
29282 let restarted = controller
29283 .try_spawn_blocking(|| 19usize)
29284 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29285 assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
29286 controller.shutdown().unwrap();
29287 }
29288
29289 #[cfg(not(target_family = "wasm"))]
29290 #[test]
29291 fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
29292 use std::sync::mpsc;
29293
29294 struct PanicOnDrop(mpsc::Sender<()>);
29295
29296 impl Drop for PanicOnDrop {
29297 fn drop(&mut self) {
29298 self.0.send(()).unwrap();
29299 panic!("intentional buffered blocking result destructor panic");
29300 }
29301 }
29302
29303 let options = RuntimeOptions {
29304 flavor: RuntimeFlavor::MultiThread,
29305 worker_threads: 2,
29306 max_blocking_tasks: 1,
29307 thread_name_prefix: "buffered-blocking-result-drop".to_string(),
29308 park_deadline: None,
29309 drain_linger: DEFAULT_DRAIN_LINGER,
29310 };
29311 let executor =
29312 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
29313 let (started_tx, started_rx) = mpsc::channel();
29314 let (dropped_tx, dropped_rx) = mpsc::channel();
29315 let handle = executor.schedule_blocking(move || {
29316 started_tx.send(()).unwrap();
29317 PanicOnDrop(dropped_tx)
29318 });
29319 started_rx
29320 .recv_timeout(Duration::from_secs(2))
29321 .expect("the blocking closure must run");
29322 wait_until("the completed blocking result to be buffered", || {
29323 executor.active_blocking.load(Ordering::Acquire) == 0
29324 });
29325 assert!(
29326 dropped_rx.try_recv().is_err(),
29327 "the buffered result must remain owned by the live receiver"
29328 );
29329
29330 let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
29331 assert!(
29332 dropped.is_ok(),
29333 "dropping the buffered result must contain its destructor panic"
29334 );
29335 dropped_rx
29336 .recv_timeout(Duration::from_secs(2))
29337 .expect("dropping the receiver must reclaim the buffered result");
29338 }
29339
29340 #[cfg(not(target_family = "wasm"))]
29341 #[test]
29342 fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
29343 use std::sync::mpsc;
29344
29345 struct PanicOnDrop(mpsc::Sender<()>);
29346 impl Drop for PanicOnDrop {
29347 fn drop(&mut self) {
29348 let _ = self.0.send(());
29349 panic!("intentional late blocking destructor panic");
29350 }
29351 }
29352
29353 let executor = multi_thread_executor(2, None, "closed-queue-drop");
29354 executor.begin_shutdown();
29355
29356 let (drop_tx, drop_rx) = mpsc::channel();
29357 let panic_on_drop = PanicOnDrop(drop_tx);
29358 let handle = executor.schedule_blocking(move || {
29359 let _keep_captured_until_run = &panic_on_drop;
29360 });
29361
29362 drop_rx
29363 .recv_timeout(Duration::from_secs(5))
29364 .expect("the closed queue must retire the rejected closure");
29365 assert!(
29366 futures::executor::block_on(handle).is_err(),
29367 "the rejected blocking handle must resolve as cancelled"
29368 );
29369 }
29370
29371 #[cfg(not(target_family = "wasm"))]
29372 #[test]
29373 fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
29374 struct PanicWake;
29375 impl std::task::Wake for PanicWake {
29376 fn wake(self: Arc<Self>) {
29377 panic!("intentional timer waker panic");
29378 }
29379 }
29380
29381 let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
29382 let backend = controller.backend();
29383 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29384 panic!("configured MultiThread backend must create a Rayon executor");
29385 };
29386 let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
29387 let waker = Waker::from(Arc::new(PanicWake));
29388 let mut cx = Context::from_waker(&waker);
29389 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29390 drop(backend);
29391
29392 controller
29393 .shutdown()
29394 .expect("shutdown must contain the timer waker panic");
29395 controller
29396 .start()
29397 .expect("the stopped controller must restart");
29398 let restarted = controller
29399 .try_spawn_blocking(|| 23usize)
29400 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29401 assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
29402 controller.shutdown().unwrap();
29403 }
29404
29405 #[cfg(not(target_family = "wasm"))]
29406 #[test]
29407 fn multi_thread_restart_waits_for_old_generation_worker_exit() {
29408 use std::sync::mpsc;
29409
29410 let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
29411 let backend = controller.backend();
29412 let old_generation = backend.generation();
29413 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29414 panic!("configured MultiThread backend must create a Rayon executor");
29415 };
29416 let old_workers = Arc::clone(&executor.worker_lifecycle);
29417 drop(backend);
29418
29419 let (running_tx, running_rx) = mpsc::channel();
29420 let (release_tx, release_rx) = mpsc::channel();
29421 let running = controller
29422 .try_spawn_blocking(move || {
29423 running_tx.send(()).unwrap();
29424 release_rx.recv().unwrap();
29425 })
29426 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29427 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29428
29429 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29430 let shutdown_controller = Arc::clone(&controller);
29431 let shutdown = std::thread::spawn(move || {
29432 shutdown_controller.shutdown().unwrap();
29433 shutdown_done_tx.send(()).unwrap();
29434 });
29435 wait_until("controller entered Stopping", || {
29436 let state = controller
29437 .state
29438 .lock()
29439 .unwrap_or_else(std::sync::PoisonError::into_inner);
29440 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
29441 });
29442
29443 let (start_done_tx, start_done_rx) = mpsc::channel();
29444 let start_controller = Arc::clone(&controller);
29445 let starter = std::thread::spawn(move || {
29446 start_controller.start().unwrap();
29447 start_done_tx.send(()).unwrap();
29448 });
29449 assert!(
29450 start_done_rx.try_recv().is_err(),
29451 "start must wait while shutdown is quiescing"
29452 );
29453 assert!(
29454 old_workers.remaining() > 0,
29455 "the held old generation must still own workers"
29456 );
29457
29458 release_tx.send(()).unwrap();
29459 futures::executor::block_on(running).unwrap();
29460 shutdown_done_rx
29461 .recv_timeout(Duration::from_secs(5))
29462 .unwrap();
29463 start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29464 shutdown.join().unwrap();
29465 starter.join().unwrap();
29466 assert_eq!(
29467 old_workers.remaining(),
29468 0,
29469 "restart may begin only after every old worker exits"
29470 );
29471
29472 let backend = controller.backend();
29473 assert_ne!(backend.generation(), old_generation);
29474 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29475 panic!("restarted backend must remain MultiThread");
29476 };
29477 assert_eq!(
29478 executor.pool.current_num_threads(),
29479 controller.options().worker_threads
29480 );
29481 drop(backend);
29482 controller.shutdown().unwrap();
29483 }
29484
29485 #[cfg(not(target_family = "wasm"))]
29486 #[test]
29487 fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
29488 let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
29489 let task_controller = Arc::clone(&controller);
29490 let handle = controller
29491 .try_spawn(async move {
29492 task_controller
29493 .shutdown()
29494 .expect_err("runtime work must not wait for its own generation")
29495 .to_string()
29496 })
29497 .unwrap_or_else(|_| panic!("running runtime must accept the task"));
29498 let message = futures::executor::block_on(handle).unwrap();
29499 assert!(message.contains("cannot shut down"));
29500 controller.shutdown().unwrap();
29501 }
29502
29503 #[cfg(not(target_family = "wasm"))]
29510 #[test]
29511 fn multi_thread_blocking_active_never_exceeds_max_blocking() {
29512 use std::sync::{Barrier, mpsc};
29534 use std::time::Duration;
29535
29536 let (done_tx, done_rx) = mpsc::channel();
29537 let peak_out = Arc::new(Mutex::new(None));
29538 let peak_slot = Arc::clone(&peak_out);
29539 let runner = std::thread::spawn(move || {
29540 let metrics = Arc::new(RuntimeMetrics::default());
29541 let options = RuntimeOptions {
29542 flavor: RuntimeFlavor::MultiThread,
29543 worker_threads: 4,
29544 max_blocking_tasks: 2,
29545 thread_name_prefix: "rd9-blk-cap".to_string(),
29546 park_deadline: None,
29547 drain_linger: DEFAULT_DRAIN_LINGER,
29548 };
29549 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
29550
29551 let holder_count = options.max_blocking_tasks; let extra_count = 4usize; let (held_tx, held_rx) = mpsc::channel();
29556 let release = Arc::new(Barrier::new(holder_count + 1));
29557 let mut holder_handles = Vec::new();
29558 for _ in 0..holder_count {
29559 let held_tx = held_tx.clone();
29560 let release = Arc::clone(&release);
29561 holder_handles.push(executor.schedule_blocking(move || {
29562 held_tx.send(()).unwrap();
29563 release.wait();
29564 0usize
29565 }));
29566 }
29567 for _ in 0..holder_count {
29569 held_rx.recv().unwrap();
29570 }
29571
29572 let (extra_started_tx, extra_started_rx) = mpsc::channel();
29574 let mut extra_handles = Vec::new();
29575 for i in 0..extra_count {
29576 let extra_started_tx = extra_started_tx.clone();
29577 extra_handles.push(executor.schedule_blocking(move || {
29578 extra_started_tx.send(i).unwrap();
29579 i * 2
29580 }));
29581 }
29582
29583 assert!(
29586 matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
29587 "a beyond-cap blocking job ran before the cap was released (cap check broken)"
29588 );
29589
29590 release.wait();
29592
29593 for handle in holder_handles {
29594 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
29595 }
29596 for (i, handle) in extra_handles.into_iter().enumerate() {
29599 assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
29600 }
29601
29602 assert_eq!(
29605 metrics.blocking_tasks_started.load(Ordering::Relaxed),
29606 (holder_count + extra_count) as u64
29607 );
29608
29609 *peak_slot
29610 .lock()
29611 .unwrap_or_else(std::sync::PoisonError::into_inner) =
29612 Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
29613 let _ = done_tx.send(());
29614 });
29615
29616 match done_rx.recv_timeout(Duration::from_secs(10)) {
29617 Ok(()) => runner.join().unwrap(),
29618 Err(error) => panic!("blocking-cap test did not complete ({error})"),
29619 }
29620 let peak = peak_out
29621 .lock()
29622 .unwrap_or_else(std::sync::PoisonError::into_inner)
29623 .expect("peak captured");
29624 assert!(
29625 peak >= 1,
29626 "at least one blocking job must have run (peak {peak})"
29627 );
29628 assert!(
29629 peak <= 2,
29630 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
29631 );
29632 }
29633
29634 #[cfg(not(target_family = "wasm"))]
29635 #[test]
29636 fn multi_thread_drain_budget_completes_large_runnable_batch() {
29637 use std::sync::mpsc;
29642 use std::time::{Duration, Instant};
29643
29644 let (done_tx, done_rx) = mpsc::channel();
29645 let runner = std::thread::spawn(move || {
29646 let metrics = Arc::new(RuntimeMetrics::default());
29647 let options = RuntimeOptions {
29648 flavor: RuntimeFlavor::MultiThread,
29649 worker_threads: 2,
29650 max_blocking_tasks: 2,
29651 thread_name_prefix: "rd9-budget".to_string(),
29652 park_deadline: None,
29653 drain_linger: DEFAULT_DRAIN_LINGER,
29654 };
29655 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
29656
29657 let task_count = 200usize; let completed = Arc::new(AtomicU64::new(0));
29659 let mut tasks = Vec::new();
29660 for _ in 0..task_count {
29661 let completed = Arc::clone(&completed);
29662 let scheduler = Arc::clone(&executor);
29663 let (runnable, task) = async_task::spawn(
29664 async move {
29665 completed.fetch_add(1, Ordering::Relaxed);
29666 },
29667 move |r| scheduler.schedule(r),
29668 );
29669 executor.schedule(runnable);
29670 tasks.push(task);
29671 }
29672 for task in tasks {
29673 futures::executor::block_on(task);
29674 }
29675
29676 assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
29679 assert_eq!(
29680 metrics.runnable_polls.load(Ordering::Relaxed),
29681 task_count as u64
29682 );
29683
29684 let deadline = Instant::now() + Duration::from_secs(5);
29688 loop {
29689 let active = metrics.active_runnables.load(Ordering::Relaxed);
29690 let queued = metrics.queued_runnables.load(Ordering::Relaxed);
29691 if active == 0 && queued == 0 {
29692 break;
29693 }
29694 assert!(
29695 Instant::now() < deadline,
29696 "runnable counters did not settle to 0: active={active} queued={queued}"
29697 );
29698 std::thread::yield_now();
29699 }
29700 let _ = done_tx.send(());
29701 });
29702
29703 match done_rx.recv_timeout(Duration::from_secs(15)) {
29704 Ok(()) => runner.join().unwrap(),
29705 Err(error) => panic!("drain-budget test did not complete ({error})"),
29706 }
29707 }
29708
29709 #[cfg(not(target_family = "wasm"))]
29710 #[test]
29711 fn multi_thread_spawned_panic_surfaces_as_join_error() {
29712 use std::sync::mpsc;
29719 use std::time::Duration;
29720
29721 let (done_tx, done_rx) = mpsc::channel();
29722 let runner = std::thread::spawn(move || {
29723 let metrics = Arc::new(RuntimeMetrics::default());
29724 let options = RuntimeOptions {
29725 flavor: RuntimeFlavor::MultiThread,
29726 worker_threads: 2,
29727 max_blocking_tasks: 2,
29728 thread_name_prefix: "rd9-panic".to_string(),
29729 park_deadline: None,
29730 drain_linger: DEFAULT_DRAIN_LINGER,
29731 };
29732 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
29733
29734 let scheduler = Arc::clone(&executor);
29735 let wrapped = async {
29736 AssertUnwindSafe(async { panic!("x") })
29737 .catch_unwind()
29738 .await
29739 .map(|()| ContainedTaskOutput::new((), u64::MAX))
29740 .map_err(panic_payload_to_join_error)
29741 };
29742 let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
29743 executor.schedule(runnable);
29744
29745 let result: Result<(), JoinError> =
29746 futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
29747 task: task.fallible(),
29748 awaiter: None,
29749 dependency: Arc::new(TaskDependency::default()),
29750 }));
29751 let _ = done_tx.send(result);
29752 });
29753
29754 let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
29755 Ok(result) => {
29756 runner.join().unwrap();
29757 result
29758 }
29759 Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
29760 };
29761 let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
29762 assert_eq!(error.to_string(), "x");
29763 }
29764
29765 #[test]
29766 fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
29767 use std::sync::mpsc;
29768
29769 let metrics = Arc::new(RuntimeMetrics::default());
29770 metrics.tasks_spawned.store(1, Ordering::Relaxed);
29771 metrics.tasks_completed.store(2, Ordering::Relaxed);
29772 metrics.tasks_panicked.store(3, Ordering::Relaxed);
29773 metrics.runnable_schedules.store(4, Ordering::Relaxed);
29774 metrics.runnable_polls.store(5, Ordering::Relaxed);
29775 metrics
29776 .blocking_tasks_scheduled
29777 .store(14, Ordering::Relaxed);
29778 metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
29779 metrics
29780 .blocking_tasks_completed
29781 .store(11, Ordering::Relaxed);
29782 metrics.runnable_scheduled();
29783 let (guards_live_tx, guards_live_rx) = mpsc::channel();
29784 let (release_tx, release_rx) = mpsc::channel();
29785 let guard_metrics = Arc::clone(&metrics);
29786 let guard_thread = std::thread::spawn(move || {
29787 let runnable_guard = guard_metrics.runnable_started();
29788 let blocking_guard = guard_metrics.blocking_started(true);
29789 guards_live_tx.send(()).unwrap();
29790 release_rx.recv().unwrap();
29791 drop(runnable_guard);
29792 drop(blocking_guard);
29793 });
29794 guards_live_rx.recv().unwrap();
29795 let fingerprint_before_reset = metrics.progress_fingerprint();
29796
29797 metrics.reset();
29798
29799 assert!(
29800 metrics.progress_fingerprint() != fingerprint_before_reset,
29801 "a reset must advance the deadlock detector generation even if counters later repeat"
29802 );
29803 assert_eq!(
29804 metrics.tasks_spawned.load(Ordering::Relaxed),
29805 0,
29806 "tasks_spawned"
29807 );
29808 assert_eq!(
29809 metrics.tasks_completed.load(Ordering::Relaxed),
29810 0,
29811 "tasks_completed"
29812 );
29813 assert_eq!(
29814 metrics.tasks_panicked.load(Ordering::Relaxed),
29815 0,
29816 "tasks_panicked"
29817 );
29818 assert_eq!(
29819 metrics.runnable_schedules.load(Ordering::Relaxed),
29820 0,
29821 "runnable_schedules"
29822 );
29823 assert_eq!(
29824 metrics.runnable_polls.load(Ordering::Relaxed),
29825 0,
29826 "runnable_polls"
29827 );
29828 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
29829 assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
29830 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
29831 assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
29832 assert_eq!(
29833 metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
29834 0,
29835 "blocking_tasks_scheduled"
29836 );
29837 assert_eq!(
29838 metrics.blocking_tasks_started.load(Ordering::Relaxed),
29839 0,
29840 "blocking_tasks_started"
29841 );
29842 assert_eq!(
29843 metrics.blocking_tasks_completed.load(Ordering::Relaxed),
29844 0,
29845 "blocking_tasks_completed"
29846 );
29847 assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
29848 assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
29849
29850 release_tx.send(()).unwrap();
29851 guard_thread.join().unwrap();
29852 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
29853 assert_eq!(
29854 metrics.active_blocking_tasks.load(Ordering::Relaxed),
29855 0,
29856 "a live blocking guard completed after reset without unsigned underflow"
29857 );
29858 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
29859 }
29860
29861 #[test]
29862 fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
29863 use std::sync::mpsc;
29864
29865 fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
29866 RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
29867 *slot.borrow_mut() = Some(Box::new(move || {
29868 entered.send(()).unwrap();
29869 release.recv().unwrap();
29870 }));
29871 });
29872 }
29873
29874 let options = RuntimeOptions {
29875 flavor: RuntimeFlavor::CurrentThread,
29876 worker_threads: 1,
29877 max_blocking_tasks: 1,
29878 thread_name_prefix: "metrics-snapshot".to_string(),
29879 park_deadline: None,
29880 drain_linger: DEFAULT_DRAIN_LINGER,
29881 };
29882
29883 let queued_metrics = Arc::new(RuntimeMetrics::default());
29884 let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
29885 let (queued_release_tx, queued_release_rx) = mpsc::channel();
29886 let queued_writer_metrics = Arc::clone(&queued_metrics);
29887 let queued_writer = std::thread::spawn(move || {
29888 pause_next_high_water_update(queued_entered_tx, queued_release_rx);
29889 queued_writer_metrics.runnable_scheduled();
29890 });
29891 queued_entered_rx.recv().unwrap();
29892 assert_eq!(
29893 queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
29894 0
29895 );
29896 let snapshot = queued_metrics.snapshot(&options);
29897 assert_eq!(snapshot.queued_runnables, 1);
29898 assert_eq!(snapshot.max_queued_runnables, 1);
29899 queued_release_tx.send(()).unwrap();
29900 queued_writer.join().unwrap();
29901 queued_metrics.runnable_cancelled();
29902
29903 let active_metrics = Arc::new(RuntimeMetrics::default());
29904 active_metrics.queued_runnables.store(1, Ordering::Relaxed);
29905 let (active_entered_tx, active_entered_rx) = mpsc::channel();
29906 let (active_release_tx, active_release_rx) = mpsc::channel();
29907 let active_writer_metrics = Arc::clone(&active_metrics);
29908 let active_writer = std::thread::spawn(move || {
29909 pause_next_high_water_update(active_entered_tx, active_release_rx);
29910 let _active = active_writer_metrics.runnable_started();
29911 });
29912 active_entered_rx.recv().unwrap();
29913 assert_eq!(
29914 active_metrics.max_active_runnables.load(Ordering::Relaxed),
29915 0
29916 );
29917 let snapshot = active_metrics.snapshot(&options);
29918 assert_eq!(snapshot.active_runnables, 1);
29919 assert_eq!(snapshot.max_active_runnables, 1);
29920 active_release_tx.send(()).unwrap();
29921 active_writer.join().unwrap();
29922
29923 let blocking_metrics = Arc::new(RuntimeMetrics::default());
29924 let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
29925 let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
29926 let blocking_writer_metrics = Arc::clone(&blocking_metrics);
29927 let blocking_writer = std::thread::spawn(move || {
29928 pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
29929 let _active = blocking_writer_metrics.blocking_started(true);
29930 });
29931 blocking_entered_rx.recv().unwrap();
29932 assert_eq!(
29933 blocking_metrics
29934 .max_active_blocking_tasks
29935 .load(Ordering::Relaxed),
29936 0
29937 );
29938 let snapshot = blocking_metrics.snapshot(&options);
29939 assert_eq!(snapshot.active_blocking_tasks, 1);
29940 assert_eq!(snapshot.max_active_blocking_tasks, 1);
29941 blocking_release_tx.send(()).unwrap();
29942 blocking_writer.join().unwrap();
29943 }
29944
29945 #[test]
29946 fn runtime_metrics_reset_fails_before_generation_wraparound() {
29947 let metrics = RuntimeMetrics::default();
29948 metrics
29949 .reset_generation
29950 .store(u64::MAX - 1, Ordering::SeqCst);
29951 metrics.tasks_spawned.store(7, Ordering::Relaxed);
29952
29953 let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
29954 assert!(exhausted.is_err());
29955 assert!(!metrics.reset_lock.is_poisoned());
29956 assert_eq!(
29957 metrics.reset_generation.load(Ordering::SeqCst),
29958 u64::MAX - 1
29959 );
29960 assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
29961 }
29962
29963 struct NeverReady;
29975 impl Future for NeverReady {
29976 type Output = ();
29977 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29978 Poll::Pending
29979 }
29980 }
29981
29982 #[cfg(not(target_family = "wasm"))]
29983 struct ReadyAfterOwnerLanePublication {
29984 dependency: BlockingDependency,
29985 ready: Arc<AtomicBool>,
29986 }
29987
29988 #[cfg(not(target_family = "wasm"))]
29989 impl Future for ReadyAfterOwnerLanePublication {
29990 type Output = ();
29991
29992 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29993 if self.ready.load(Ordering::SeqCst) {
29994 Poll::Ready(())
29995 } else {
29996 let mut published = PublishedBlockingDependencies::default();
29999 record_blocking_dependency(&mut published, self.dependency);
30000 Poll::Pending
30001 }
30002 }
30003 }
30004
30005 #[test]
30006 fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
30007 use std::sync::mpsc;
30014
30015 let (done_tx, done_rx) = mpsc::channel();
30016 let runner = std::thread::spawn(move || {
30017 let metrics = Arc::new(RuntimeMetrics::default());
30018 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30021
30022 let payload = catch_unwind(AssertUnwindSafe(|| {
30023 let mut future = std::pin::pin!(NeverReady);
30024 executor.block_on(future.as_mut());
30025 }))
30026 .expect_err("a threadless park with an empty queue and no pending wake must panic");
30027 let diagnostic = payload
30028 .downcast_ref::<BlockOnDeadlock>()
30029 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
30030 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
30031 assert_eq!(
30032 diagnostic.park_deadline, None,
30033 "the certain case carries no deadline"
30034 );
30035 let message = diagnostic.to_string();
30036 assert!(
30037 message.contains("block_on") && message.contains("JS"),
30038 "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
30039 );
30040 let _ = done_tx.send(());
30041 });
30042
30043 match done_rx.recv_timeout(Duration::from_secs(10)) {
30044 Ok(()) => runner.join().unwrap(),
30045 Err(error) => panic!(
30046 "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
30047 ),
30048 }
30049 }
30050
30051 #[test]
30052 fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
30053 use std::sync::mpsc;
30059
30060 struct PendThenReady {
30061 polls: usize,
30062 }
30063 impl Future for PendThenReady {
30064 type Output = ();
30065 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
30066 self.polls += 1;
30067 if self.polls >= 2 {
30068 Poll::Ready(())
30069 } else {
30070 cx.waker().wake_by_ref();
30071 Poll::Pending
30072 }
30073 }
30074 }
30075
30076 let (done_tx, done_rx) = mpsc::channel();
30077 let runner = std::thread::spawn(move || {
30078 let metrics = Arc::new(RuntimeMetrics::default());
30079 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30080 let mut future = std::pin::pin!(PendThenReady { polls: 0 });
30081 executor.block_on(future.as_mut());
30082 assert_eq!(
30083 future.polls, 2,
30084 "the self-woken future must have been re-polled to completion"
30085 );
30086 let _ = done_tx.send(());
30087 });
30088
30089 match done_rx.recv_timeout(Duration::from_secs(10)) {
30090 Ok(()) => runner.join().unwrap(),
30091 Err(error) => panic!(
30092 "a self-waking Pending future must complete without panicking on a threadless build ({error})"
30093 ),
30094 }
30095 }
30096
30097 #[cfg(not(target_family = "wasm"))]
30098 #[test]
30099 fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
30100 use std::sync::mpsc;
30105
30106 let (done_tx, done_rx) = mpsc::channel();
30107 let runner = std::thread::spawn(move || {
30108 let metrics = Arc::new(RuntimeMetrics::default());
30109 let executor = Arc::new(CurrentThreadExecutor::with_detection(
30110 metrics,
30111 false,
30112 Some(Duration::from_millis(50)),
30113 ));
30114
30115 let payload = catch_unwind(AssertUnwindSafe(|| {
30116 let mut future = std::pin::pin!(NeverReady);
30117 executor.block_on(future.as_mut());
30118 }))
30119 .expect_err("a dead native CT park must fire the armed deadline");
30120 let diagnostic = payload
30121 .downcast_ref::<BlockOnDeadlock>()
30122 .expect("typed payload");
30123 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
30124 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
30125 drop(executor);
30128 let _ = done_tx.send(());
30129 });
30130
30131 match done_rx.recv_timeout(Duration::from_secs(10)) {
30132 Ok(()) => join_within(
30133 "the CT deadline-fire runner exits after teardown",
30134 runner,
30135 Duration::from_secs(30),
30136 ),
30137 Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
30138 }
30139 }
30140
30141 #[cfg(not(target_family = "wasm"))]
30142 #[test]
30143 fn current_thread_native_park_deadline_resets_on_runtime_progress() {
30144 use std::sync::mpsc;
30161
30162 let deadline = Duration::from_millis(1000);
30163 let metrics = Arc::new(RuntimeMetrics::default());
30164 let executor = Arc::new(CurrentThreadExecutor::with_detection(
30165 Arc::clone(&metrics),
30166 false,
30167 Some(deadline),
30168 ));
30169
30170 let (value_tx, value_rx) = oneshot::channel::<usize>();
30171 let (done_tx, done_rx) = mpsc::channel();
30172 let runner = std::thread::spawn(move || {
30173 let mut output = None;
30174 {
30175 let mut future = std::pin::pin!(async {
30176 output = Some(value_rx.await.unwrap());
30177 });
30178 executor.block_on(future.as_mut());
30179 }
30180 assert_eq!(output, Some(9));
30181 drop(executor);
30183 let _ = done_tx.send(());
30184 });
30185
30186 let hold_until = Instant::now() + deadline * 5 / 2;
30192 while Instant::now() < hold_until {
30193 std::thread::sleep(Duration::from_millis(25));
30194 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
30195 }
30196 value_tx.send(9).unwrap();
30197
30198 match done_rx.recv_timeout(Duration::from_secs(30)) {
30199 Ok(()) => join_within(
30200 "the CT deadline-reset runner exits after teardown",
30201 runner,
30202 Duration::from_secs(30),
30203 ),
30204 Err(error) => panic!(
30205 "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
30206 ),
30207 }
30208 }
30209
30210 #[cfg(not(target_family = "wasm"))]
30211 #[test]
30212 fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
30213 use std::sync::mpsc;
30218
30219 let (done_tx, done_rx) = mpsc::channel();
30220 let runner = std::thread::spawn(move || {
30221 let options = RuntimeOptions {
30222 flavor: RuntimeFlavor::MultiThread,
30223 worker_threads: 1,
30224 max_blocking_tasks: 1,
30225 thread_name_prefix: "deadline-fire".to_string(),
30226 park_deadline: Some(Duration::from_millis(50)),
30227 drain_linger: DEFAULT_DRAIN_LINGER,
30228 };
30229 let executor =
30230 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30231
30232 let payload = {
30233 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30234 catch_unwind(AssertUnwindSafe(|| {
30235 let mut future = std::pin::pin!(NeverReady);
30236 executor.block_on(future.as_mut());
30237 }))
30238 .expect_err("a dead cooperative park must fire the armed deadline")
30239 };
30240 let diagnostic = payload
30241 .downcast_ref::<BlockOnDeadlock>()
30242 .expect("typed payload");
30243 assert_eq!(
30244 diagnostic.kind,
30245 BlockOnDeadlockKind::MultiThreadCooperativeDeadline
30246 );
30247 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
30248 let message = diagnostic.to_string();
30249 assert!(
30250 message.contains("block_on"),
30251 "diagnostic must name the hazard, got: {message}"
30252 );
30253 assert_eq!(
30254 executor.parked_drivers.count.load(Ordering::SeqCst),
30255 0,
30256 "the firing driver must deregister itself before panicking"
30257 );
30258 drop(executor);
30260 let _ = done_tx.send(());
30261 });
30262
30263 match done_rx.recv_timeout(Duration::from_secs(10)) {
30264 Ok(()) => join_within(
30265 "the MT deadline-fire runner exits after executor teardown",
30266 runner,
30267 Duration::from_secs(30),
30268 ),
30269 Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
30270 }
30271 }
30272
30273 #[cfg(not(target_family = "wasm"))]
30274 #[test]
30275 fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
30276 use std::sync::mpsc;
30292
30293 let (done_tx, done_rx) = mpsc::channel();
30294 let runner = std::thread::spawn(move || {
30295 let deadline = Duration::from_millis(1000);
30296 let metrics = Arc::new(RuntimeMetrics::default());
30297 let options = RuntimeOptions {
30298 flavor: RuntimeFlavor::MultiThread,
30299 worker_threads: 1,
30300 max_blocking_tasks: 1,
30301 thread_name_prefix: "deadline-reset".to_string(),
30302 park_deadline: Some(deadline),
30303 drain_linger: DEFAULT_DRAIN_LINGER,
30304 };
30305 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
30306
30307 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30310 let td_exec = Arc::clone(&executor);
30311 let completed = Arc::new(AtomicBool::new(false));
30312 let td_flag = Arc::clone(&completed);
30313 let td_body = async move {
30314 let mut inner = std::pin::pin!(async move {
30315 assert_eq!(gate_rx.await.unwrap(), 9);
30316 });
30317 td_exec.block_on(inner.as_mut());
30318 td_flag.store(true, Ordering::SeqCst);
30319 };
30320 let sched = Arc::clone(&executor);
30321 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
30322 executor.schedule(td_runnable);
30323 wait_until("the sole driver parked", || {
30324 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
30325 });
30326
30327 let hold_until = Instant::now() + deadline * 5 / 2;
30333 while Instant::now() < hold_until {
30334 std::thread::sleep(Duration::from_millis(25));
30335 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
30336 }
30337 assert!(
30338 !completed.load(Ordering::SeqCst),
30339 "the driver must still be parked awaiting its late wake"
30340 );
30341 gate_tx.send(9).unwrap();
30342 futures::executor::block_on(td_task);
30343 assert!(
30344 completed.load(Ordering::SeqCst),
30345 "the late wake must complete the parked driver"
30346 );
30347 drop(executor);
30349 let _ = done_tx.send(());
30350 });
30351
30352 match done_rx.recv_timeout(Duration::from_secs(30)) {
30353 Ok(()) => join_within(
30354 "the MT deadline-reset runner exits after executor teardown",
30355 runner,
30356 Duration::from_secs(30),
30357 ),
30358 Err(error) => panic!(
30359 "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
30360 ),
30361 }
30362 }
30363
30364 #[cfg(not(target_family = "wasm"))]
30365 #[test]
30366 fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
30367 use std::sync::mpsc;
30372
30373 let (done_tx, done_rx) = mpsc::channel();
30374 let runner = std::thread::spawn(move || {
30375 let options = RuntimeOptions {
30376 flavor: RuntimeFlavor::MultiThread,
30377 worker_threads: 2,
30378 max_blocking_tasks: 2,
30379 thread_name_prefix: "foreign-exempt".to_string(),
30380 park_deadline: Some(Duration::from_millis(40)),
30381 drain_linger: DEFAULT_DRAIN_LINGER,
30382 };
30383 let executor =
30384 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30385
30386 let (value_tx, value_rx) = oneshot::channel::<usize>();
30387 let waker_thread = std::thread::spawn(move || {
30388 std::thread::sleep(Duration::from_millis(300));
30390 let _ = value_tx.send(7);
30391 });
30392
30393 let started = Instant::now();
30394 let mut output = None;
30395 {
30396 let mut future = std::pin::pin!(async {
30397 output = Some(value_rx.await.unwrap());
30398 });
30399 executor.block_on(future.as_mut());
30401 }
30402 assert_eq!(output, Some(7));
30403 assert!(
30404 started.elapsed() >= Duration::from_millis(250),
30405 "the park must genuinely have outlived the 40ms deadline many times over"
30406 );
30407 waker_thread.join().unwrap();
30408 drop(executor);
30413 let _ = done_tx.send(());
30414 });
30415
30416 match done_rx.recv_timeout(Duration::from_secs(10)) {
30417 Ok(()) => join_within(
30418 "the exempt foreign-park runner exits after executor teardown",
30419 runner,
30420 Duration::from_secs(30),
30421 ),
30422 Err(error) => {
30423 panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
30424 }
30425 }
30426 }
30427
30428 #[cfg(not(target_family = "wasm"))]
30429 #[test]
30430 fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
30431 use std::sync::mpsc;
30446
30447 let (done_tx, done_rx) = mpsc::channel();
30448 let runner = std::thread::spawn(move || {
30449 let options = RuntimeOptions {
30450 flavor: RuntimeFlavor::MultiThread,
30451 worker_threads: 1,
30452 max_blocking_tasks: 1,
30453 thread_name_prefix: "edge-wake".to_string(),
30454 park_deadline: Some(Duration::from_millis(40)),
30455 drain_linger: DEFAULT_DRAIN_LINGER,
30456 };
30457 let executor =
30458 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30459
30460 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30462 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
30464
30465 let hook_exec = Arc::clone(&executor);
30466 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
30467 *slot.borrow_mut() = Some(Box::new(move || {
30468 let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
30469 let handle = hook_exec.schedule_blocking(move || {
30473 let _ = gate_tx.send(5usize);
30474 0usize
30475 });
30476 drop(handle); let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
30478 hook_saw_tx.send((before, after)).unwrap();
30479 }));
30480 });
30481
30482 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30485 let mut output = None;
30486 {
30487 let mut future = std::pin::pin!(async {
30488 output = Some(gate_rx.await.unwrap());
30489 });
30490 executor.block_on(future.as_mut());
30491 }
30492 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
30497 assert_eq!(
30498 output,
30499 Some(5),
30500 "the edge-delivered wake's job must have run and completed the future"
30501 );
30502
30503 let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
30504 assert_eq!(
30505 before, 1,
30506 "the hook must observe the parker STILL registered (race window)"
30507 );
30508 assert_eq!(
30509 after, 0,
30510 "the racing wake_one must have popped the parker (wake DELIVERED)"
30511 );
30512 drop(executor);
30514 let _ = done_tx.send(());
30515 });
30516
30517 match done_rx.recv_timeout(Duration::from_secs(10)) {
30518 Ok(()) => join_within(
30519 "the expiry-edge-wake runner exits after executor teardown",
30520 runner,
30521 Duration::from_secs(30),
30522 ),
30523 Err(error) => panic!(
30524 "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
30525 ),
30526 }
30527 }
30528
30529 #[cfg(not(target_family = "wasm"))]
30530 #[test]
30531 fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
30532 use std::sync::mpsc;
30544
30545 let (done_tx, done_rx) = mpsc::channel();
30546 let runner = std::thread::spawn(move || {
30547 let options = RuntimeOptions {
30548 flavor: RuntimeFlavor::MultiThread,
30549 worker_threads: 1,
30550 max_blocking_tasks: 1,
30551 thread_name_prefix: "verdict-blocking".to_string(),
30552 park_deadline: Some(Duration::from_millis(40)),
30553 drain_linger: DEFAULT_DRAIN_LINGER,
30554 };
30555 let executor =
30556 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30557 executor
30558 .active_drainers
30559 .store(executor.max_drainers, Ordering::SeqCst);
30560
30561 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30563 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
30566
30567 let hook_exec = Arc::clone(&executor);
30568 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
30569 *slot.borrow_mut() = Some(Box::new(move || {
30570 let handle = hook_exec.schedule_blocking(move || {
30571 let _ = gate_tx.send(11usize);
30572 0usize
30573 });
30574 drop(handle); hook_saw_tx
30576 .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
30577 .unwrap();
30578 }));
30579 });
30580
30581 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30584 let mut output = None;
30585 {
30586 let mut future = std::pin::pin!(async {
30587 output = Some(gate_rx.await.unwrap());
30588 });
30589 executor.block_on(future.as_mut());
30590 }
30591 assert_eq!(
30592 output,
30593 Some(11),
30594 "the enqueued job must have been run by the surviving driver via run_one"
30595 );
30596
30597 let registered_at_hook = hook_saw_rx
30598 .recv()
30599 .expect("the verdict hook must have fired");
30600 assert_eq!(
30601 registered_at_hook, 0,
30602 "the hook fires post-deregister: no permit can be delivered, only the fingerprint's enqueue counter can catch this submission"
30603 );
30604 executor.active_drainers.store(0, Ordering::SeqCst);
30605 let _ = done_tx.send(());
30606 });
30607
30608 match done_rx.recv_timeout(Duration::from_secs(10)) {
30609 Ok(()) => runner.join().unwrap(),
30610 Err(error) => panic!(
30611 "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
30612 ),
30613 }
30614 }
30615
30616 #[cfg(not(target_family = "wasm"))]
30617 #[test]
30618 fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
30619 use std::sync::mpsc;
30625
30626 struct ReadyAfterPublication(Arc<AtomicBool>);
30627
30628 impl Future for ReadyAfterPublication {
30629 type Output = ();
30630
30631 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
30632 if self.0.load(Ordering::SeqCst) {
30633 Poll::Ready(())
30634 } else {
30635 Poll::Pending
30636 }
30637 }
30638 }
30639
30640 let (done_tx, done_rx) = mpsc::channel();
30641 let runner = std::thread::spawn(move || {
30642 let options = RuntimeOptions {
30643 flavor: RuntimeFlavor::MultiThread,
30644 worker_threads: 1,
30645 max_blocking_tasks: 1,
30646 thread_name_prefix: "final-verdict-publication".to_string(),
30647 park_deadline: Some(Duration::from_millis(40)),
30648 drain_linger: DEFAULT_DRAIN_LINGER,
30649 };
30650 let executor =
30651 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30652
30653 let ready = Arc::new(AtomicBool::new(false));
30654 let baseline_fingerprint = executor.metrics.progress_fingerprint();
30655 let baseline_publications = executor.deadlock_state.publications();
30656 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
30657 let hook_exec = Arc::clone(&executor);
30658 let hook_ready = Arc::clone(&ready);
30659 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
30660 *slot.borrow_mut() = Some(Box::new(move || {
30661 let publication_executor = Arc::clone(&hook_exec);
30662 std::thread::spawn(move || {
30663 let mut admission = publication_executor
30664 .begin_deadlock_admission()
30665 .expect("the deadline detector must enable admission accounting");
30666 hook_ready.store(true, Ordering::SeqCst);
30667 admission.mark_published();
30668 })
30669 .join()
30670 .unwrap();
30671 hook_saw_tx
30672 .send((
30673 hook_exec.deadlock_state.publications(),
30674 !hook_exec.has_queued_runnable(),
30675 hook_exec
30676 .blocking_queue
30677 .lock()
30678 .unwrap_or_else(std::sync::PoisonError::into_inner)
30679 .is_empty(),
30680 hook_exec.next_timer_deadline().is_none(),
30681 hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
30682 hook_exec.parked_drivers.count.load(Ordering::SeqCst),
30683 ))
30684 .unwrap();
30685 }));
30686 });
30687
30688 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30689 let mut future = std::pin::pin!(ReadyAfterPublication(ready));
30690 assert_eq!(
30691 executor.block_on(future.as_mut()),
30692 BlockOnOutcome::Completed,
30693 "the publication epoch must veto the first verdict and permit the next poll"
30694 );
30695 let (
30696 publications,
30697 runnable_queue_empty,
30698 blocking_queue_empty,
30699 timer_heap_empty,
30700 fingerprint_unchanged,
30701 parked_drivers,
30702 ) = hook_saw_rx
30703 .recv()
30704 .expect("the final-verdict hook must have fired");
30705 assert_eq!(
30706 publications,
30707 baseline_publications
30708 .checked_add(1)
30709 .expect("test publication epoch must not overflow"),
30710 "the cross-thread admission must publish exactly one epoch"
30711 );
30712 assert!(
30713 runnable_queue_empty,
30714 "the publication test must not queue a runnable"
30715 );
30716 assert!(
30717 blocking_queue_empty,
30718 "the publication test must not queue blocking work"
30719 );
30720 assert!(
30721 timer_heap_empty,
30722 "the publication test must not register a timer"
30723 );
30724 assert!(
30725 fingerprint_unchanged,
30726 "ordinary metrics progress must not veto the publication-only verdict"
30727 );
30728 assert_eq!(
30729 parked_drivers, 0,
30730 "the publication must land after the cooperative parker was deregistered"
30731 );
30732 let _ = done_tx.send(());
30733 });
30734
30735 match done_rx.recv_timeout(Duration::from_secs(10)) {
30736 Ok(()) => runner.join().unwrap(),
30737 Err(error) => panic!(
30738 "a publication-only cross-thread admission was not observed by the final verdict ({error})"
30739 ),
30740 }
30741 }
30742
30743 #[cfg(not(target_family = "wasm"))]
30744 #[test]
30745 fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
30746 use std::sync::mpsc;
30754
30755 let executor = multi_thread_executor(
30756 1,
30757 Some(Duration::from_millis(40)),
30758 "deadline-selected-handoff",
30759 );
30760 let owner = executor.fresh_owner_token();
30761 executor.active_blocking_owners.register(owner);
30762 executor
30763 .active_blocking
30764 .store(executor.max_blocking, Ordering::SeqCst);
30765
30766 let ready = Arc::new(AtomicBool::new(false));
30767 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
30768 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
30769 let (selected_tx, selected_rx) = mpsc::channel();
30770 let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
30771 let (released_tx, released_rx) = mpsc::channel();
30772 let (renewed_tx, renewed_rx) = mpsc::channel();
30773 let release_executor = Arc::clone(&executor);
30774 let renewal_executor = Arc::clone(&executor);
30775 let release_ready = Arc::clone(&ready);
30776 let releaser = std::thread::spawn(move || {
30777 let reservation =
30778 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
30779 .expect("the synthetic exact-owner lane must start reserved");
30780 reservation_ready_tx.send(()).unwrap();
30781 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
30782 *slot.borrow_mut() = Some(Box::new(move || {
30783 selected_tx.send(()).unwrap();
30784 allow_unpark_rx.recv().unwrap();
30785 let renewed = renewal_executor
30786 .active_blocking_owners
30787 .reserve(owner)
30788 .expect("the released exact-owner lane must be reservable before unpark");
30789 release_ready.store(true, Ordering::SeqCst);
30790 renewed_tx.send(renewed).unwrap();
30791 }));
30792 });
30793 release_reservation_rx.recv().unwrap();
30794 released_tx
30795 .send(release_executor.release_owner_lane_and_wake(reservation))
30796 .unwrap();
30797 });
30798 reservation_ready_rx
30799 .recv_timeout(Duration::from_secs(2))
30800 .expect("the exact-owner reservation must be established before the park");
30801
30802 let baseline_fingerprint = executor.metrics.progress_fingerprint();
30803 let baseline_publications = executor.deadlock_state.publications();
30804 let expiry_executor = Arc::clone(&executor);
30805 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
30806 *slot.borrow_mut() = Some(Box::new(move || {
30807 release_reservation_tx.send(()).unwrap();
30808 selected_rx
30809 .recv_timeout(Duration::from_secs(2))
30810 .expect("reservation release must select the still-registered exact-owner driver");
30811 assert_eq!(
30812 expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
30813 0,
30814 "handoff selection must remove the driver before pausing ahead of unpark"
30815 );
30816 }));
30817 });
30818
30819 let verdict_executor = Arc::clone(&executor);
30820 let verdict_ready = Arc::clone(&ready);
30821 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
30822 *slot.borrow_mut() = Some(Box::new(move || {
30823 assert!(
30824 verdict_executor.active_blocking_owners.is_available(owner),
30825 "reservation release must publish lane availability before handoff selection"
30826 );
30827 assert!(!verdict_executor.has_queued_runnable());
30828 assert!(
30829 verdict_executor
30830 .blocking_queue
30831 .lock()
30832 .unwrap_or_else(std::sync::PoisonError::into_inner)
30833 .is_empty()
30834 );
30835 assert!(verdict_executor.next_timer_deadline().is_none());
30836 assert!(
30837 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
30838 "ordinary metrics progress must not save the selected-handoff verdict"
30839 );
30840 assert_eq!(
30841 verdict_executor.deadlock_state.publications(),
30842 baseline_publications
30843 );
30844 assert!(
30845 !verdict_ready.load(Ordering::SeqCst),
30846 "the selected handoff must still be paused before unpark at the gated verdict"
30847 );
30848 allow_unpark_tx.send(()).unwrap();
30849 }));
30850 });
30851 let exact_owner_executor = Arc::clone(&executor);
30852 DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
30853 *slot.borrow_mut() = Some(Box::new(move |parker| {
30854 assert!(
30855 parker.consume_permit(),
30856 "the selected handoff must unpark before exact-owner final predicates run"
30857 );
30858 assert!(
30859 parker.has_owner_handoff(),
30860 "consuming the ordinary permit must leave the exact-owner handoff pending"
30861 );
30862 assert!(
30863 !exact_owner_executor
30864 .active_blocking_owners
30865 .is_available(owner),
30866 "the renewed reservation must prevent lane availability from saving the verdict"
30867 );
30868 }));
30869 });
30870
30871 {
30872 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30873 let _owner = BlockingOwnerGuard::enter(Some(owner));
30874 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
30875 dependency: BlockingDependency {
30876 job: BlockingJobId(u64::MAX - 80),
30877 owner: Some(owner)
30878 },
30879 ready: Arc::clone(&ready),
30880 });
30881 assert_eq!(
30882 executor.block_on(future.as_mut()),
30883 BlockOnOutcome::Completed
30884 );
30885 }
30886 assert!(
30887 released_rx
30888 .recv_timeout(Duration::from_secs(2))
30889 .expect("the selected handoff publication must finish"),
30890 "reservation release must have selected the cooperative driver"
30891 );
30892 let renewed = renewed_rx
30893 .recv_timeout(Duration::from_secs(2))
30894 .expect("the selected handoff must renew the owner reservation before unpark");
30895 releaser.join().unwrap();
30896
30897 executor.active_blocking_owners.release(owner, renewed);
30898 executor.active_blocking.store(0, Ordering::SeqCst);
30899 executor.active_blocking_owners.unregister(owner);
30900 executor.begin_shutdown();
30901 executor.wait_until_scheduler_idle();
30902 }
30903
30904 #[cfg(not(target_family = "wasm"))]
30905 #[test]
30906 fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
30907 use std::sync::mpsc;
30912
30913 let executor = multi_thread_executor(
30914 1,
30915 Some(Duration::from_millis(40)),
30916 "deadline-owner-no-parker",
30917 );
30918 let owner = executor.fresh_owner_token();
30919 executor.active_blocking_owners.register(owner);
30920 executor
30921 .active_blocking
30922 .store(executor.max_blocking, Ordering::SeqCst);
30923
30924 let ready = Arc::new(AtomicBool::new(false));
30925 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
30926 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
30927 let (released_tx, released_rx) = mpsc::channel();
30928 let release_executor = Arc::clone(&executor);
30929 let release_ready = Arc::clone(&ready);
30930 let releaser = std::thread::spawn(move || {
30931 let reservation =
30932 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
30933 .expect("the synthetic exact-owner lane must start reserved");
30934 reservation_ready_tx.send(()).unwrap();
30935 release_reservation_rx.recv().unwrap();
30936 let woke = release_executor.release_owner_lane_and_wake(reservation);
30937 release_ready.store(true, Ordering::SeqCst);
30938 released_tx.send(woke).unwrap();
30939 });
30940 reservation_ready_rx
30941 .recv_timeout(Duration::from_secs(2))
30942 .expect("the exact-owner reservation must be established before the park");
30943
30944 let baseline_fingerprint = executor.metrics.progress_fingerprint();
30945 let baseline_publications = executor.deadlock_state.publications();
30946 let verdict_executor = Arc::clone(&executor);
30947 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
30948 *slot.borrow_mut() = Some(Box::new(move || {
30949 assert_eq!(
30950 verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
30951 0,
30952 "the release must begin only after deadline deregistration"
30953 );
30954 release_reservation_tx.send(()).unwrap();
30955 assert!(
30956 !released_rx
30957 .recv_timeout(Duration::from_secs(2))
30958 .expect("the no-parker owner release must finish"),
30959 "no registered parker may receive the released owner lane"
30960 );
30961 assert!(verdict_executor.active_blocking_owners.is_available(owner));
30962 assert!(!verdict_executor.has_queued_runnable());
30963 assert!(
30964 verdict_executor
30965 .blocking_queue
30966 .lock()
30967 .unwrap_or_else(std::sync::PoisonError::into_inner)
30968 .is_empty()
30969 );
30970 assert!(verdict_executor.next_timer_deadline().is_none());
30971 assert!(
30972 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
30973 "ordinary metrics progress must not save the no-parker release verdict"
30974 );
30975 assert_eq!(
30976 verdict_executor.deadlock_state.publications(),
30977 baseline_publications
30978 );
30979 }));
30980 });
30981
30982 {
30983 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30984 let _owner = BlockingOwnerGuard::enter(Some(owner));
30985 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
30986 dependency: BlockingDependency {
30987 job: BlockingJobId(u64::MAX - 81),
30988 owner: Some(owner)
30989 },
30990 ready: Arc::clone(&ready),
30991 });
30992 assert_eq!(
30993 executor.block_on(future.as_mut()),
30994 BlockOnOutcome::Completed
30995 );
30996 }
30997 releaser.join().unwrap();
30998
30999 executor.active_blocking.store(0, Ordering::SeqCst);
31000 executor.active_blocking_owners.unregister(owner);
31001 executor.begin_shutdown();
31002 executor.wait_until_scheduler_idle();
31003 }
31004
31005 #[cfg(not(target_family = "wasm"))]
31006 #[test]
31007 fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
31008 use std::sync::mpsc;
31022
31023 let controller = Arc::new(RuntimeController::new());
31024 controller
31025 .configure(RuntimeOptions {
31026 flavor: RuntimeFlavor::MultiThread,
31027 worker_threads: 2,
31028 max_blocking_tasks: 1,
31029 thread_name_prefix: "shutdown-gated-verdict".to_string(),
31030 park_deadline: Some(Duration::from_millis(40)),
31031 drain_linger: DEFAULT_DRAIN_LINGER,
31032 })
31033 .expect("the shutdown race test configuration must be accepted");
31034
31035 let backend = controller.backend();
31036 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31037 panic!("the configured backend must be MultiThread");
31038 };
31039 let executor_id = executor.id;
31040 let weak_executor = Arc::downgrade(executor);
31041 drop(backend);
31042
31043 let (task_polled_tx, task_polled_rx) = mpsc::channel();
31044 let task = controller
31045 .try_spawn(async move {
31046 task_polled_tx.send(()).unwrap();
31047 std::future::pending::<()>().await;
31048 })
31049 .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
31050 task_polled_rx
31051 .recv_timeout(Duration::from_secs(2))
31052 .expect("the accepted task must publish an abortable pending waker");
31053
31054 let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
31055 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
31056 let (driver_result_tx, driver_result_rx) = mpsc::channel();
31057 let driver_controller = Arc::clone(&controller);
31058 let driver = std::thread::spawn(move || {
31059 DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
31060 *slot.borrow_mut() = Some(Box::new(move || {
31061 gate_closed_tx.send(()).unwrap();
31062 release_verdict_rx.recv().unwrap();
31063 }));
31064 });
31065
31066 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
31067 let mut pending = std::pin::pin!(NeverReady);
31068 let result = catch_unwind(AssertUnwindSafe(|| {
31069 driver_controller.block_on(pending.as_mut())
31070 }));
31071 let result = match result {
31072 Ok(outcome) => Ok(outcome),
31073 Err(payload) => {
31074 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
31075 discard_panic_payload(payload);
31076 Err(was_deadlock)
31077 }
31078 };
31079 driver_result_tx.send(result).unwrap();
31080 });
31081
31082 gate_closed_rx
31083 .recv_timeout(Duration::from_secs(2))
31084 .expect("the cooperative driver must pause with the verdict gate closed");
31085
31086 let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
31087 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
31088 let shutdown_controller = Arc::clone(&controller);
31089 let shutdown = std::thread::spawn(move || {
31090 DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
31091 *slot.borrow_mut() = Some(Box::new(move || {
31092 abort_blocked_tx.send(()).unwrap();
31093 }));
31094 });
31095 shutdown_result_tx
31096 .send(shutdown_controller.shutdown())
31097 .unwrap();
31098 });
31099
31100 abort_blocked_rx
31101 .recv_timeout(Duration::from_secs(2))
31102 .expect("the accepted task's abort wake must reach the closed verdict gate");
31103 let lifecycle_is_stopping = {
31104 let state = controller
31105 .state
31106 .lock()
31107 .unwrap_or_else(std::sync::PoisonError::into_inner);
31108 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
31109 };
31110 let stop_was_published = weak_executor
31111 .upgrade()
31112 .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
31113
31114 release_verdict_tx.send(()).unwrap();
31115
31116 let driver_result = driver_result_rx
31117 .recv_timeout(Duration::from_secs(2))
31118 .expect("the gated driver must resolve after observing shutdown");
31119 let shutdown_result = shutdown_result_rx
31120 .recv_timeout(Duration::from_secs(5))
31121 .expect("shutdown must finish after the verdict gate reopens");
31122 join_within(
31127 "the gated cooperative driver exits after teardown",
31128 driver,
31129 Duration::from_secs(30),
31130 );
31131 join_within(
31132 "the shutdown thread exits after teardown",
31133 shutdown,
31134 Duration::from_secs(30),
31135 );
31136
31137 assert!(
31138 lifecycle_is_stopping,
31139 "the abort wake must occur after Running -> Stopping"
31140 );
31141 assert!(
31142 stop_was_published,
31143 "generation stop must be visible before shutdown invokes accepted-task aborts"
31144 );
31145 assert_eq!(
31146 driver_result,
31147 Ok(BlockOnOutcome::Stopped),
31148 "shutdown must not be reported as a gated BlockOnDeadlock"
31149 );
31150 shutdown_result.unwrap();
31151 assert!(
31152 futures::executor::block_on(task).is_err(),
31153 "the accepted pending task must resolve as cancelled"
31154 );
31155 }
31156
31157 #[cfg(not(target_family = "wasm"))]
31158 #[test]
31159 fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
31160 use std::sync::mpsc;
31165
31166 let controller = Arc::new(RuntimeController::new());
31167 controller
31168 .configure(RuntimeOptions {
31169 flavor: RuntimeFlavor::MultiThread,
31170 worker_threads: 2,
31171 max_blocking_tasks: 1,
31172 thread_name_prefix: "verdict-before-shutdown".to_string(),
31173 park_deadline: Some(Duration::from_millis(40)),
31174 drain_linger: DEFAULT_DRAIN_LINGER,
31175 })
31176 .expect("the verdict-first test configuration must be accepted");
31177
31178 let backend = controller.backend();
31179 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31180 panic!("the configured backend must be MultiThread");
31181 };
31182 let executor_id = executor.id;
31183 drop(backend);
31184
31185 let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
31186 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
31187 let (driver_result_tx, driver_result_rx) = mpsc::channel();
31188 let driver_controller = Arc::clone(&controller);
31189 let driver = std::thread::spawn(move || {
31190 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
31191 *slot.borrow_mut() = Some(Box::new(move || {
31192 verdict_locked_tx.send(()).unwrap();
31193 release_verdict_rx.recv().unwrap();
31194 }));
31195 });
31196
31197 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
31198 let mut pending = std::pin::pin!(NeverReady);
31199 let result = catch_unwind(AssertUnwindSafe(|| {
31200 driver_controller.block_on(pending.as_mut())
31201 }));
31202 let result = match result {
31203 Ok(outcome) => Ok(outcome),
31204 Err(payload) => {
31205 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
31206 discard_panic_payload(payload);
31207 Err(was_deadlock)
31208 }
31209 };
31210 driver_result_tx.send(result).unwrap();
31211 });
31212
31213 verdict_locked_rx
31214 .recv_timeout(Duration::from_secs(2))
31215 .expect("the verdict must hold the synchronized stop-publication guard");
31216
31217 let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
31218 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
31219 let shutdown_controller = Arc::clone(&controller);
31220 let shutdown = std::thread::spawn(move || {
31221 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
31222 *slot.borrow_mut() = Some(Box::new(move || {
31223 shutdown_waiting_tx.send(()).unwrap();
31224 }));
31225 });
31226 shutdown_result_tx
31227 .send(shutdown_controller.shutdown())
31228 .unwrap();
31229 });
31230
31231 shutdown_waiting_rx
31232 .recv_timeout(Duration::from_secs(2))
31233 .expect("shutdown must reach the lifecycle-to-publication lock edge");
31234 let early_shutdown = shutdown_result_rx
31235 .recv_timeout(Duration::from_millis(100))
31236 .ok();
31237 let shutdown_was_blocked = early_shutdown.is_none();
31238 release_verdict_tx.send(()).unwrap();
31239
31240 let driver_result = driver_result_rx
31241 .recv_timeout(Duration::from_secs(2))
31242 .expect("the verdict-first driver must finish after release");
31243 let shutdown_result = early_shutdown.unwrap_or_else(|| {
31244 shutdown_result_rx
31245 .recv_timeout(Duration::from_secs(5))
31246 .expect("shutdown must finish after the verdict releases publication")
31247 });
31248 driver.join().unwrap();
31249 shutdown.join().unwrap();
31250
31251 assert!(
31252 shutdown_was_blocked,
31253 "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
31254 );
31255 assert_eq!(
31256 driver_result,
31257 Err(true),
31258 "a verdict that linearizes before shutdown must retain the typed deadlock"
31259 );
31260 shutdown_result.unwrap();
31261 }
31262
31263 #[cfg(not(target_family = "wasm"))]
31264 #[test]
31265 fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
31266 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
31267
31268 if std::env::var_os(CHILD_ENV).is_some() {
31269 let executor =
31270 multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
31271 executor
31274 .active_drainers
31275 .store(executor.max_drainers, Ordering::SeqCst);
31276
31277 let hook_returned = Arc::new(AtomicBool::new(false));
31278 let hook_flag = Arc::clone(&hook_returned);
31279 let hook_executor = Arc::clone(&executor);
31280 let previous_hook = std::panic::take_hook();
31281 std::panic::set_hook(Box::new(move |_| {
31282 let handle = hook_executor.schedule_blocking(|| ());
31283 drop(handle);
31284 hook_flag.store(true, Ordering::SeqCst);
31285 }));
31286
31287 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
31288 let result = catch_unwind(AssertUnwindSafe(|| {
31289 let mut pending = std::pin::pin!(std::future::pending::<()>());
31290 executor.block_on(pending.as_mut());
31291 }));
31292 std::panic::set_hook(previous_hook);
31293
31294 let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
31295 assert!(
31296 payload.downcast_ref::<BlockOnDeadlock>().is_some(),
31297 "the deadline must retain its structured diagnostic"
31298 );
31299 assert!(
31300 hook_returned.load(Ordering::SeqCst),
31301 "the panic hook's scheduler submission must return before unwind"
31302 );
31303
31304 executor.active_drainers.store(0, Ordering::SeqCst);
31305 executor.begin_shutdown();
31306 executor.wait_until_scheduler_idle();
31307 return;
31308 }
31309
31310 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
31311 .arg("--exact")
31312 .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
31313 .arg("--nocapture")
31314 .env(CHILD_ENV, "1")
31315 .stdout(std::process::Stdio::piped())
31316 .stderr(std::process::Stdio::piped())
31317 .spawn()
31318 .expect("the panic-hook subprocess must start");
31319 let deadline = Instant::now() + Duration::from_secs(5);
31320 let status = loop {
31321 if let Some(status) = child
31322 .try_wait()
31323 .expect("the subprocess status must be readable")
31324 {
31325 break status;
31326 }
31327 if Instant::now() >= deadline {
31328 let _ = child.kill();
31329 let output = child
31330 .wait_with_output()
31331 .expect("the timed-out subprocess must be reaped");
31332 panic!(
31333 "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
31334 String::from_utf8_lossy(&output.stdout),
31335 String::from_utf8_lossy(&output.stderr)
31336 );
31337 }
31338 std::thread::sleep(Duration::from_millis(10));
31339 };
31340 let output = child
31341 .wait_with_output()
31342 .expect("the completed subprocess output must be read");
31343 assert!(
31344 status.success(),
31345 "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
31346 status.code(),
31347 String::from_utf8_lossy(&output.stdout),
31348 String::from_utf8_lossy(&output.stderr)
31349 );
31350 }
31351
31352 #[test]
31363 fn contained_waker_separates_wake_and_drop_panics() {
31364 struct PanicWakeAndDrop;
31365
31366 impl std::task::Wake for PanicWakeAndDrop {
31367 fn wake(self: Arc<Self>) {
31368 panic!("intentional consuming wake panic");
31369 }
31370
31371 fn wake_by_ref(self: &Arc<Self>) {
31372 panic!("intentional borrowed wake panic");
31373 }
31374 }
31375
31376 impl Drop for PanicWakeAndDrop {
31377 fn drop(&mut self) {
31378 panic!("intentional RawWaker destructor panic");
31379 }
31380 }
31381
31382 wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
31383 }
31384
31385 struct TestHostileRawWakerState {
31386 clones: AtomicUsize,
31387 drop_panics: AtomicUsize,
31388 panic_on_clone: Option<usize>,
31389 }
31390
31391 struct TestHostileRawWakerToken {
31392 state: Arc<TestHostileRawWakerState>,
31393 panic_on_drop: bool,
31394 }
31395
31396 unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
31397 let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
31398 let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
31399 assert_ne!(
31400 token.state.panic_on_clone,
31401 Some(clone),
31402 "intentional RawWaker clone panic"
31403 );
31404 let clone = Box::new(TestHostileRawWakerToken {
31405 state: Arc::clone(&token.state),
31406 panic_on_drop: true,
31407 });
31408 std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
31409 }
31410
31411 unsafe fn wake_test_hostile_waker(data: *const ()) {
31412 unsafe { drop_test_hostile_waker(data) };
31413 }
31414
31415 unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
31416
31417 unsafe fn drop_test_hostile_waker(data: *const ()) {
31418 let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
31419 if token.panic_on_drop {
31420 token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
31421 panic!("intentional cloned RawWaker destructor panic");
31422 }
31423 }
31424
31425 static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
31426 clone_test_hostile_waker,
31427 wake_test_hostile_waker,
31428 wake_test_hostile_waker_by_ref,
31429 drop_test_hostile_waker,
31430 );
31431
31432 fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
31433 let token = Box::new(TestHostileRawWakerToken {
31434 state,
31435 panic_on_drop: false,
31436 });
31437 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
31438 unsafe { Waker::from_raw(raw) }
31439 }
31440
31441 struct CountingWake(AtomicUsize);
31443 impl std::task::Wake for CountingWake {
31444 fn wake(self: Arc<Self>) {
31445 self.0.fetch_add(1, Ordering::SeqCst);
31446 }
31447 }
31448
31449 type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
31451
31452 struct StubHostTimerDriver {
31456 timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
31457 }
31458
31459 impl StubHostTimerDriver {
31460 fn new() -> Self {
31461 Self {
31462 timers: Mutex::new(FxHashMap::default()),
31463 }
31464 }
31465 }
31466
31467 impl TimerDriver for StubHostTimerDriver {
31468 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
31469 let mut timers = self.timers.lock().unwrap();
31470 if let Some(cell) = timers.get(&id) {
31471 *cell.0.lock().unwrap() = waker;
31472 return;
31473 }
31474 let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
31475 timers.insert(id, Arc::clone(&cell));
31476 std::thread::spawn(move || {
31477 let now = Instant::now();
31478 if deadline > now {
31479 std::thread::sleep(deadline - now);
31480 }
31481 if !cell.1.load(Ordering::SeqCst) {
31482 cell.0.lock().unwrap().wake_by_ref();
31483 }
31484 });
31485 }
31486
31487 fn cancel(&self, id: TimerId) {
31488 let removed = self.timers.lock().unwrap().remove(&id);
31489 if let Some(cell) = removed {
31490 cell.1.store(true, Ordering::SeqCst);
31491 }
31492 }
31493 }
31494
31495 struct ManualStubDriver {
31501 live: AtomicBool,
31502 registers: Mutex<Vec<(TimerId, Instant)>>,
31503 cancels: Mutex<Vec<TimerId>>,
31504 pending: Mutex<FxHashMap<TimerId, Waker>>,
31505 swept: AtomicBool,
31506 }
31507
31508 impl ManualStubDriver {
31509 fn new() -> Arc<Self> {
31510 Arc::new(Self {
31511 live: AtomicBool::new(true),
31512 registers: Mutex::new(Vec::new()),
31513 cancels: Mutex::new(Vec::new()),
31514 pending: Mutex::new(FxHashMap::default()),
31515 swept: AtomicBool::new(false),
31516 })
31517 }
31518
31519 fn kill(&self) {
31520 self.live.store(false, Ordering::SeqCst);
31521 }
31522 }
31523
31524 impl TimerDriver for ManualStubDriver {
31525 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
31526 self.registers.lock().unwrap().push((id, deadline));
31527 self.pending.lock().unwrap().insert(id, waker);
31528 }
31529
31530 fn cancel(&self, id: TimerId) {
31531 self.cancels.lock().unwrap().push(id);
31532 self.pending.lock().unwrap().remove(&id);
31533 }
31534
31535 fn is_live(&self) -> bool {
31536 self.live.load(Ordering::SeqCst)
31537 }
31538
31539 fn on_swept(&self) {
31540 self.swept.store(true, Ordering::SeqCst);
31543 let wakers: Vec<Waker> = self
31544 .pending
31545 .lock()
31546 .unwrap()
31547 .drain()
31548 .map(|(_, w)| w)
31549 .collect();
31550 for waker in wakers {
31551 waker.wake();
31552 }
31553 }
31554 }
31555
31556 struct PanickingTimerDriver {
31557 swept: AtomicBool,
31558 }
31559
31560 impl TimerDriver for PanickingTimerDriver {
31561 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31562
31563 fn cancel(&self, _id: TimerId) {}
31564
31565 fn is_live(&self) -> bool {
31566 panic!("intentional timer-driver liveness panic");
31567 }
31568
31569 fn on_swept(&self) {
31570 self.swept.store(true, Ordering::SeqCst);
31571 panic!("intentional timer-driver sweep panic");
31572 }
31573 }
31574
31575 struct PanickingRegisterTimerDriver {
31576 registers: AtomicUsize,
31577 cancels: Mutex<Vec<TimerId>>,
31578 pending: Mutex<FxHashMap<TimerId, Waker>>,
31579 }
31580
31581 impl PanickingRegisterTimerDriver {
31582 fn new() -> Arc<Self> {
31583 Arc::new(Self {
31584 registers: AtomicUsize::new(0),
31585 cancels: Mutex::new(Vec::new()),
31586 pending: Mutex::new(FxHashMap::default()),
31587 })
31588 }
31589 }
31590
31591 impl TimerDriver for PanickingRegisterTimerDriver {
31592 fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
31593 self.registers.fetch_add(1, Ordering::SeqCst);
31594 self.pending.lock().unwrap().insert(id, waker);
31595 panic!("intentional timer-driver register panic after arming");
31596 }
31597
31598 fn cancel(&self, id: TimerId) {
31599 self.cancels.lock().unwrap().push(id);
31600 self.pending.lock().unwrap().remove(&id);
31601 }
31602 }
31603
31604 struct PanicBeforeRetainingTimerDriver {
31605 registers: AtomicUsize,
31606 }
31607
31608 impl TimerDriver for PanicBeforeRetainingTimerDriver {
31609 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
31610 self.registers.fetch_add(1, Ordering::SeqCst);
31611 panic!("intentional timer-driver register panic before retaining its waker");
31612 }
31613
31614 fn cancel(&self, _id: TimerId) {}
31615 }
31616
31617 struct PanickingCancelDropTimerDriver {
31618 cancels: Arc<AtomicUsize>,
31619 drops: Arc<AtomicUsize>,
31620 }
31621
31622 impl TimerDriver for PanickingCancelDropTimerDriver {
31623 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31624
31625 fn cancel(&self, _id: TimerId) {
31626 self.cancels.fetch_add(1, Ordering::SeqCst);
31627 panic!("intentional timer-driver cancel panic");
31628 }
31629 }
31630
31631 impl Drop for PanickingCancelDropTimerDriver {
31632 fn drop(&mut self) {
31633 self.drops.fetch_add(1, Ordering::SeqCst);
31634 panic!("intentional timer-driver destructor panic");
31635 }
31636 }
31637
31638 struct UnregisterLaterThenPanicTimerDriver {
31639 registry: Weak<TimerDriverRegistry>,
31640 later: Mutex<Option<TimerDriverId>>,
31641 registers: AtomicUsize,
31642 }
31643
31644 impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
31645 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
31646 self.registers.fetch_add(1, Ordering::SeqCst);
31647 let later = *self.later.lock().unwrap();
31648 if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
31649 registry.unregister(later);
31650 }
31651 panic!("intentional earlier timer-driver panic after unregistering a later driver");
31652 }
31653
31654 fn cancel(&self, _id: TimerId) {}
31655 }
31656
31657 struct ReentrantLivenessDriver {
31658 registry: std::sync::Weak<TimerDriverRegistry>,
31659 registration: Mutex<Option<TimerDriverId>>,
31660 }
31661
31662 impl TimerDriver for ReentrantLivenessDriver {
31663 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31664
31665 fn cancel(&self, _id: TimerId) {}
31666
31667 fn is_live(&self) -> bool {
31668 let registration = *self.registration.lock().unwrap();
31669 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
31670 registry.unregister(id);
31671 }
31672 false
31673 }
31674 }
31675
31676 struct ReentrantMultiUnregisterTimerDriver {
31677 registry: std::sync::Weak<TimerDriverRegistry>,
31678 registrations: Mutex<Vec<TimerDriverId>>,
31679 }
31680
31681 impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
31682 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31683
31684 fn cancel(&self, _id: TimerId) {}
31685
31686 fn is_live(&self) -> bool {
31687 if let Some(registry) = self.registry.upgrade() {
31688 for registration in self.registrations.lock().unwrap().iter().copied() {
31689 registry.unregister(registration);
31690 }
31691 }
31692 false
31693 }
31694 }
31695
31696 struct ReentrantSweepTimerDriver {
31697 registry: std::sync::Weak<TimerDriverRegistry>,
31698 registration: Mutex<Option<TimerDriverId>>,
31699 sweeps: AtomicUsize,
31700 removed_before_sweep: AtomicBool,
31701 }
31702
31703 impl TimerDriver for ReentrantSweepTimerDriver {
31704 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31705
31706 fn cancel(&self, _id: TimerId) {}
31707
31708 fn is_live(&self) -> bool {
31709 false
31710 }
31711
31712 fn on_swept(&self) {
31713 self.sweeps.fetch_add(1, Ordering::SeqCst);
31714 let registration = self.registration.lock().unwrap().take();
31715 if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
31716 let state = registry
31717 .state
31718 .try_lock()
31719 .expect("the sweep callback must run outside the timer-driver registry lock");
31720 assert!(
31721 !state.entries.contains_key(®istration),
31722 "the dead driver must be unlinked before its sweep callback runs"
31723 );
31724 self.removed_before_sweep.store(true, Ordering::SeqCst);
31725 drop(state);
31726 registry.unregister(registration);
31729 }
31730 }
31731 }
31732
31733 struct ReentrantDropDriver {
31734 registry: std::sync::Weak<TimerDriverRegistry>,
31735 fallback: TimerDriverId,
31736 }
31737
31738 impl TimerDriver for ReentrantDropDriver {
31739 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31740
31741 fn cancel(&self, _id: TimerId) {}
31742 }
31743
31744 impl Drop for ReentrantDropDriver {
31745 fn drop(&mut self) {
31746 if let Some(registry) = self.registry.upgrade() {
31747 registry.unregister(self.fallback);
31748 }
31749 }
31750 }
31751
31752 #[derive(Default)]
31755 struct WakeFlag(AtomicBool);
31756
31757 impl std::task::Wake for WakeFlag {
31758 fn wake(self: Arc<Self>) {
31759 self.0.store(true, Ordering::SeqCst);
31760 }
31761 }
31762
31763 #[cfg(not(target_family = "wasm"))]
31764 fn multi_thread_executor(
31765 workers: usize,
31766 park_deadline: Option<Duration>,
31767 prefix: &str,
31768 ) -> Arc<MultiThreadExecutor> {
31769 let options = RuntimeOptions {
31770 flavor: RuntimeFlavor::MultiThread,
31771 worker_threads: workers,
31772 max_blocking_tasks: workers,
31773 thread_name_prefix: prefix.to_string(),
31774 park_deadline,
31775 drain_linger: DEFAULT_DRAIN_LINGER,
31776 };
31777 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
31778 }
31779
31780 fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
31783 let registry = Arc::new(TimerDriverRegistry::default());
31784 registry.register(driver);
31785 registry
31786 }
31787
31788 #[cfg(not(target_family = "wasm"))]
31789 fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
31790 make_sleep(
31792 &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
31793 &Arc::new(TimerDriverRegistry::default()),
31794 deadline,
31795 )
31796 }
31797
31798 #[cfg(not(target_family = "wasm"))]
31799 #[test]
31800 fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
31801 use std::sync::mpsc;
31802
31803 let executor = multi_thread_executor(
31804 1,
31805 Some(Duration::from_secs(1)),
31806 "timer-registration-admission",
31807 );
31808 let (entered_tx, entered_rx) = mpsc::channel();
31809 let (release_tx, release_rx) = mpsc::channel();
31810 let registration_executor = Arc::clone(&executor);
31811 let registration = std::thread::spawn(move || {
31812 TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
31813 *slot.borrow_mut() = Some(Box::new(move || {
31814 entered_tx.send(()).unwrap();
31815 release_rx.recv().unwrap();
31816 }));
31817 });
31818 registration_executor.register_timer(
31819 1,
31820 Instant::now() + Duration::from_secs(60),
31821 futures::task::noop_waker(),
31822 &Arc::new(AtomicBool::new(false)),
31823 );
31824 });
31825
31826 entered_rx
31827 .recv_timeout(Duration::from_secs(2))
31828 .expect("timer registration must pause after entering verdict admission");
31829 let verdict = executor.try_begin_deadlock_verdict();
31830 let admission_blocked_verdict = verdict.is_none();
31831 drop(verdict);
31832 release_tx.send(()).unwrap();
31833 registration.join().unwrap();
31834
31835 assert!(
31836 admission_blocked_verdict,
31837 "a timer registration must remain announced until its heap entry is visible"
31838 );
31839 assert!(
31840 executor.next_timer_deadline().is_some(),
31841 "the admitted registration must publish its timer after release"
31842 );
31843 executor.begin_shutdown();
31844 executor.wait_until_scheduler_idle();
31845 }
31846
31847 #[cfg(not(target_family = "wasm"))]
31848 #[derive(Clone, Copy, Debug)]
31849 enum TimerFireAdmissionPath {
31850 Timekeeper,
31851 Cooperative,
31852 Shutdown,
31853 }
31854
31855 #[cfg(not(target_family = "wasm"))]
31856 struct ScheduleAfterTimerWakeRelease {
31857 executor: Arc<MultiThreadExecutor>,
31858 runnable: Mutex<Option<Runnable>>,
31859 entered: std::sync::mpsc::Sender<()>,
31860 release: Mutex<std::sync::mpsc::Receiver<()>>,
31861 scheduled: std::sync::mpsc::Sender<()>,
31862 }
31863
31864 #[cfg(not(target_family = "wasm"))]
31865 impl ScheduleAfterTimerWakeRelease {
31866 fn publish(&self) {
31867 self.entered.send(()).unwrap();
31868 self
31869 .release
31870 .lock()
31871 .unwrap_or_else(std::sync::PoisonError::into_inner)
31872 .recv()
31873 .unwrap();
31874 let runnable = self
31875 .runnable
31876 .lock()
31877 .unwrap_or_else(std::sync::PoisonError::into_inner)
31878 .take();
31879 if let Some(runnable) = runnable {
31880 self.executor.schedule(runnable);
31881 }
31882 self.scheduled.send(()).unwrap();
31883 }
31884 }
31885
31886 #[cfg(not(target_family = "wasm"))]
31887 impl std::task::Wake for ScheduleAfterTimerWakeRelease {
31888 fn wake(self: Arc<Self>) {
31889 self.publish();
31890 }
31891
31892 fn wake_by_ref(self: &Arc<Self>) {
31893 self.publish();
31894 }
31895 }
31896
31897 #[cfg(not(target_family = "wasm"))]
31898 fn insert_timer_for_fire_admission_test(
31899 executor: &MultiThreadExecutor,
31900 id: TimerId,
31901 deadline: Instant,
31902 waker: Waker,
31903 fired: Arc<AtomicBool>,
31904 ) {
31905 let mut inner = executor
31906 .timers
31907 .inner
31908 .lock()
31909 .unwrap_or_else(std::sync::PoisonError::into_inner);
31910 inner.entries.insert(id, HeapTimerEntry { waker, fired });
31911 inner.queue.push(deadline, id);
31912 }
31913
31914 #[cfg(not(target_family = "wasm"))]
31915 fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
31916 use std::sync::mpsc;
31917
31918 let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
31919 let (runnable, task) = async_task::spawn(async {}, |_| {});
31920 task.detach();
31921 let (entered_tx, entered_rx) = mpsc::channel();
31922 let (release_tx, release_rx) = mpsc::channel();
31923 let (scheduled_tx, scheduled_rx) = mpsc::channel();
31924 let fired = Arc::new(AtomicBool::new(false));
31925 let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
31926 executor: Arc::clone(&executor),
31927 runnable: Mutex::new(Some(runnable)),
31928 entered: entered_tx,
31929 release: Mutex::new(release_rx),
31930 scheduled: scheduled_tx,
31931 }));
31932
31933 let fire_thread = match path {
31934 TimerFireAdmissionPath::Timekeeper => {
31935 executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
31936 None
31937 }
31938 TimerFireAdmissionPath::Cooperative => {
31939 insert_timer_for_fire_admission_test(
31940 &executor,
31941 1,
31942 Instant::now(),
31943 waker,
31944 Arc::clone(&fired),
31945 );
31946 let fire_executor = Arc::clone(&executor);
31947 Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
31948 }
31949 TimerFireAdmissionPath::Shutdown => {
31950 insert_timer_for_fire_admission_test(
31951 &executor,
31952 1,
31953 Instant::now() + Duration::from_secs(60),
31954 waker,
31955 Arc::clone(&fired),
31956 );
31957 let fire_executor = Arc::clone(&executor);
31958 Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
31959 }
31960 };
31961
31962 entered_rx
31966 .recv_timeout(Duration::from_secs(10))
31967 .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
31968 assert!(
31969 executor.next_timer_deadline().is_none(),
31970 "{path:?} must exercise the remove-before-wake window"
31971 );
31972 assert!(
31973 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
31974 "{path:?} must retain admission while its waker is publishing"
31975 );
31976 let verdict = executor.try_begin_deadlock_verdict();
31977 let admission_blocked_verdict = verdict.is_none();
31978 drop(verdict);
31979
31980 release_tx.send(()).unwrap();
31981 scheduled_rx
31982 .recv_timeout(Duration::from_secs(10))
31983 .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
31984 if let Some(fire_thread) = fire_thread {
31985 fire_thread.join().unwrap();
31986 }
31987 assert!(
31988 admission_blocked_verdict,
31989 "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
31990 );
31991
31992 executor.begin_shutdown();
31993 executor.wait_until_scheduler_idle();
31994 }
31995
31996 #[cfg(not(target_family = "wasm"))]
31997 #[test]
31998 fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
31999 for path in [
32000 TimerFireAdmissionPath::Timekeeper,
32001 TimerFireAdmissionPath::Cooperative,
32002 TimerFireAdmissionPath::Shutdown,
32003 ] {
32004 assert_timer_fire_path_holds_deadlock_admission(path);
32005 }
32006 }
32007
32008 #[cfg(not(target_family = "wasm"))]
32009 #[test]
32010 fn multi_thread_sleep_fires_from_foreign_block_on() {
32011 use std::sync::mpsc;
32016
32017 let (done_tx, done_rx) = mpsc::channel();
32018 let runner = std::thread::spawn(move || {
32019 let executor = multi_thread_executor(2, None, "timer-foreign");
32020 let started = Instant::now();
32021 let mut future = std::pin::pin!(async {
32022 heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
32023 });
32024 executor.block_on(future.as_mut());
32025 assert!(
32026 started.elapsed() >= Duration::from_millis(80),
32027 "the sleep must not resolve before its deadline"
32028 );
32029 let _ = done_tx.send(());
32030 });
32031
32032 match done_rx.recv_timeout(Duration::from_secs(10)) {
32033 Ok(()) => runner.join().unwrap(),
32034 Err(error) => {
32035 panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
32036 }
32037 }
32038 }
32039
32040 #[cfg(not(target_family = "wasm"))]
32041 #[test]
32042 fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
32043 use std::sync::mpsc;
32046
32047 let (done_tx, done_rx) = mpsc::channel();
32048 let runner = std::thread::spawn(move || {
32049 let executor = multi_thread_executor(1, None, "timer-drainer");
32050
32051 let sleep_exec = Arc::clone(&executor);
32054 let sleep_done = Arc::new(AtomicBool::new(false));
32055 let sleep_flag = Arc::clone(&sleep_done);
32056 let sched = Arc::clone(&executor);
32057 let (sleep_runnable, sleep_task) = async_task::spawn(
32058 async move {
32059 heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
32060 sleep_flag.store(true, Ordering::SeqCst);
32061 },
32062 move |r| sched.schedule(r),
32063 );
32064 executor.schedule(sleep_runnable);
32065 wait_until("the timekeeper parked on the 60s deadline", || {
32066 executor
32067 .timers
32068 .inner
32069 .lock()
32070 .unwrap()
32071 .timekeeper_parker
32072 .is_some()
32073 });
32074
32075 let counter = Arc::new(AtomicUsize::new(0));
32077 let mut tasks = Vec::new();
32078 for _ in 0..32 {
32079 let counter = Arc::clone(&counter);
32080 let sched = Arc::clone(&executor);
32081 let (runnable, task) = async_task::spawn(
32082 async move {
32083 counter.fetch_add(1, Ordering::SeqCst);
32084 },
32085 move |r| sched.schedule(r),
32086 );
32087 executor.schedule(runnable);
32088 tasks.push(task);
32089 }
32090 for task in tasks {
32091 futures::executor::block_on(task);
32092 }
32093 assert_eq!(counter.load(Ordering::SeqCst), 32);
32094 assert!(
32095 !sleep_done.load(Ordering::SeqCst),
32096 "the 60s timer must still be pending: the work ran DURING the timer wait"
32097 );
32098 executor.shutdown_timers();
32100 futures::executor::block_on(sleep_task);
32101 let _ = done_tx.send(());
32102 });
32103
32104 match done_rx.recv_timeout(Duration::from_secs(15)) {
32105 Ok(()) => runner.join().unwrap(),
32106 Err(error) => {
32107 panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
32108 }
32109 }
32110 }
32111
32112 #[cfg(not(target_family = "wasm"))]
32113 #[test]
32114 fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
32115 let executor = multi_thread_executor(2, None, "timer-lazy-start");
32116 assert!(
32117 executor.timers.thread.lock().unwrap().is_none(),
32118 "constructing a runtime without timers must not create an extra OS thread"
32119 );
32120
32121 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32122 assert!(
32123 executor.timers.thread.lock().unwrap().is_none(),
32124 "an unpolled sleep has not registered a deadline"
32125 );
32126 let waker = futures::task::noop_waker();
32127 let mut cx = Context::from_waker(&waker);
32128 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32129 assert!(
32130 executor.timers.thread.lock().unwrap().is_some(),
32131 "the first timer registration must start the dedicated service"
32132 );
32133 }
32134
32135 #[cfg(not(target_family = "wasm"))]
32136 #[test]
32137 fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
32138 use std::sync::mpsc;
32139
32140 let executor = multi_thread_executor(1, None, "timer-closed-start");
32141 executor.shutdown_timers();
32142 let handle_guard = executor.timers.thread.lock().unwrap();
32143 let ensure_executor = Arc::clone(&executor);
32144 let (completed_tx, completed_rx) = mpsc::channel();
32145 let ensure = std::thread::spawn(move || {
32146 ensure_executor.timers.ensure_started().unwrap();
32147 completed_tx.send(()).unwrap();
32148 });
32149
32150 completed_rx.recv_timeout(Duration::from_secs(10)).expect(
32153 "ensure_started consulted or joined the timer handle before observing the closed heap",
32154 );
32155 drop(handle_guard);
32156 ensure.join().unwrap();
32157 }
32158
32159 #[cfg(not(target_family = "wasm"))]
32160 #[test]
32161 fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
32162 const TIMERS: u64 = 4096;
32163
32164 let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
32165 let base = Instant::now() + Duration::from_secs(3600);
32166 for id in 0..TIMERS {
32167 executor.register_timer(
32168 id,
32169 base + Duration::from_secs(id),
32170 futures::task::noop_waker(),
32171 &Arc::new(AtomicBool::new(false)),
32172 );
32173 }
32174 {
32175 let inner = executor.timers.inner.lock().unwrap();
32176 assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
32177 assert_eq!(inner.queue.len(), inner.entries.len());
32178 }
32179
32180 for id in 1..TIMERS {
32183 executor.cancel_timer(id);
32184 }
32185 {
32186 let inner = executor.timers.inner.lock().unwrap();
32187 assert_eq!(inner.entries.len(), 1);
32188 assert_eq!(inner.queue.len(), 1);
32189 assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
32190 }
32191
32192 executor.cancel_timer(0);
32193 {
32194 let inner = executor.timers.inner.lock().unwrap();
32195 assert!(inner.entries.is_empty());
32196 assert!(inner.queue.is_empty());
32197 }
32198 executor.shutdown_timers();
32199 executor.timers.join();
32200 }
32201
32202 #[cfg(not(target_family = "wasm"))]
32203 #[test]
32204 fn timer_registration_is_transactional_when_timekeeper_start_fails() {
32205 let executor = multi_thread_executor(2, None, "timer-start-failure");
32206 executor
32207 .timers
32208 .fail_next_start
32209 .store(true, Ordering::SeqCst);
32210
32211 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
32212 let waker = Waker::from(Arc::clone(&wake_count));
32213 let baseline_waker_refs = Arc::strong_count(&wake_count);
32214 let mut cx = Context::from_waker(&waker);
32215 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32216
32217 let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
32218 assert!(
32219 failure.is_err(),
32220 "the injected timer-thread start failure must panic the poll"
32221 );
32222 {
32223 let inner = executor.timers.inner.lock().unwrap();
32224 assert!(
32225 inner.entries.is_empty(),
32226 "a failed start must not retain a timer entry"
32227 );
32228 assert!(
32229 inner.queue.is_empty(),
32230 "a failed start must not retain a stale heap node"
32231 );
32232 assert!(
32233 inner.timekeeper_parker.is_none(),
32234 "a failed start must not publish a timekeeper parker"
32235 );
32236 }
32237 assert!(
32238 executor.timers.thread.lock().unwrap().is_none(),
32239 "a failed start must not retain a thread handle"
32240 );
32241 assert_eq!(
32242 Arc::strong_count(&wake_count),
32243 baseline_waker_refs,
32244 "a failed start must release the cloned timer waker"
32245 );
32246
32247 assert!(
32248 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
32249 "the same Sleep must be safely retryable after a transient start failure"
32250 );
32251 {
32252 let inner = executor.timers.inner.lock().unwrap();
32253 assert_eq!(
32254 inner.entries.len(),
32255 1,
32256 "the retry must publish exactly one timer entry"
32257 );
32258 assert_eq!(
32259 inner.queue.len(),
32260 1,
32261 "the retry must publish exactly one heap node"
32262 );
32263 }
32264 assert!(
32265 executor.timers.thread.lock().unwrap().is_some(),
32266 "the retry must start the dedicated timekeeper"
32267 );
32268
32269 drop(sleep);
32270 assert!(
32271 executor.timers.inner.lock().unwrap().entries.is_empty(),
32272 "dropping the retried Sleep must cancel its registration"
32273 );
32274 executor.shutdown_timers();
32275 executor.wait_until_scheduler_idle();
32276 assert!(
32277 executor.timers.thread.lock().unwrap().is_none(),
32278 "shutdown after a recovered start failure must join the timekeeper"
32279 );
32280 }
32281
32282 #[cfg(not(target_family = "wasm"))]
32283 #[test]
32284 fn pending_timer_preserves_every_configured_rayon_worker() {
32285 use std::sync::mpsc;
32286
32287 let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
32288 let waker = futures::task::noop_waker();
32289 let mut cx = Context::from_waker(&waker);
32290 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32291 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32292 wait_until("the dedicated timer thread to park", || {
32293 executor
32294 .timers
32295 .inner
32296 .lock()
32297 .unwrap()
32298 .timekeeper_parker
32299 .is_some()
32300 });
32301
32302 let released = Arc::new((Mutex::new(false), Condvar::new()));
32303 let (started_tx, started_rx) = mpsc::channel();
32304 let (completed_tx, completed_rx) = mpsc::channel();
32305 for _ in 0..2 {
32306 let released = Arc::clone(&released);
32307 let started_tx = started_tx.clone();
32308 let completed_tx = completed_tx.clone();
32309 executor.pool.spawn(move || {
32310 started_tx.send(()).unwrap();
32311 let (lock, changed) = &*released;
32312 let mut released = lock
32313 .lock()
32314 .unwrap_or_else(std::sync::PoisonError::into_inner);
32315 while !*released {
32316 released = changed
32317 .wait(released)
32318 .unwrap_or_else(std::sync::PoisonError::into_inner);
32319 }
32320 completed_tx.send(()).unwrap();
32321 });
32322 }
32323 drop(started_tx);
32324 drop(completed_tx);
32325
32326 let first = started_rx.recv_timeout(Duration::from_secs(2));
32327 let second = started_rx.recv_timeout(Duration::from_secs(2));
32328 {
32329 let (lock, changed) = &*released;
32330 *lock
32331 .lock()
32332 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
32333 changed.notify_all();
32334 }
32335 drop(sleep);
32336 for _ in 0..2 {
32337 completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
32338 }
32339
32340 assert!(
32341 first.is_ok(),
32342 "the first configured Rayon worker did not start"
32343 );
32344 assert!(
32345 second.is_ok(),
32346 "a pending timer consumed one of the two configured Rayon workers"
32347 );
32348 }
32349
32350 #[cfg(not(target_family = "wasm"))]
32351 #[test]
32352 fn timekeeper_never_enters_a_blocking_closure() {
32353 use std::{
32354 sync::{Barrier, mpsc},
32355 time::Duration,
32356 };
32357
32358 let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
32359 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
32360 let waker = Waker::from(Arc::clone(&wake_count));
32361 let mut cx = Context::from_waker(&waker);
32362 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
32363 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32364 wait_until(
32365 "the timekeeper parked before blocking work was submitted",
32366 || {
32367 executor
32368 .timers
32369 .inner
32370 .lock()
32371 .unwrap()
32372 .timekeeper_parker
32373 .is_some()
32374 },
32375 );
32376
32377 let release = Arc::new(Barrier::new(2));
32378 let blocker_release = Arc::clone(&release);
32379 let (started_tx, started_rx) = mpsc::sync_channel(1);
32380 let blocking = executor.schedule_blocking(move || {
32381 started_tx.send(()).unwrap();
32382 blocker_release.wait();
32383 });
32384 started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
32387
32388 wait_until(
32389 "the timer waker to fire while the Rayon worker is blocked",
32390 || wake_count.0.load(Ordering::SeqCst) != 0,
32391 );
32392 release.wait();
32393 futures::executor::block_on(blocking).unwrap();
32394 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
32395 }
32396
32397 #[cfg(not(target_family = "wasm"))]
32398 #[test]
32399 fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
32400 use std::sync::mpsc;
32401
32402 struct PendingEntered(Option<mpsc::Sender<()>>);
32403
32404 impl Future for PendingEntered {
32405 type Output = ();
32406
32407 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32408 if let Some(entered) = self.0.take() {
32409 entered.send(()).unwrap();
32410 }
32411 Poll::Pending
32412 }
32413 }
32414
32415 let options = RuntimeOptions {
32416 flavor: RuntimeFlavor::MultiThread,
32417 worker_threads: 2,
32418 max_blocking_tasks: 1,
32419 thread_name_prefix: "nested-timer-wake".to_string(),
32420 park_deadline: None,
32421 drain_linger: DEFAULT_DRAIN_LINGER,
32422 };
32423 let executor =
32424 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32425
32426 let timer_exec = Arc::clone(&executor);
32427 let (timer_tx, timer_rx) = mpsc::channel();
32428 let timer_scheduler = Arc::clone(&executor);
32429 let (timer_runnable, timer_task) = async_task::spawn(
32430 async move {
32431 heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
32432 timer_tx.send(()).unwrap();
32433 },
32434 move |runnable| timer_scheduler.schedule(runnable),
32435 );
32436 executor.schedule(timer_runnable);
32437 wait_until(
32438 "the dedicated timekeeper parked before nested block_on",
32439 || {
32440 executor
32441 .timers
32442 .inner
32443 .lock()
32444 .unwrap()
32445 .timekeeper_parker
32446 .is_some()
32447 },
32448 );
32449
32450 let (entered_tx, entered_rx) = mpsc::channel();
32451 let nested_exec = Arc::clone(&executor);
32452 let nested_scheduler = Arc::clone(&executor);
32453 let (nested_runnable, nested_task) = async_task::spawn(
32454 async move {
32455 let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
32456 let outcome = nested_exec.block_on(pending.as_mut());
32457 drop(pending);
32458 assert_eq!(
32459 outcome,
32460 BlockOnOutcome::Stopped,
32461 "shutdown must stop the raw nested block_on driver"
32462 );
32463 },
32464 move |runnable| nested_scheduler.schedule(runnable),
32465 );
32466 executor.schedule(nested_runnable);
32467 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
32468
32469 let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
32470 let (release_tx, release_rx) = mpsc::channel();
32471 let blocking = executor.schedule_blocking(move || {
32472 blocking_started_tx.send(()).unwrap();
32473 release_rx.recv().unwrap();
32474 });
32475 blocking_started_rx
32476 .recv_timeout(Duration::from_secs(2))
32477 .expect("a general worker must service blocking work");
32478 timer_rx
32479 .recv_timeout(Duration::from_secs(2))
32480 .expect("the timer wake must reach the nested cooperative driver");
32481
32482 release_tx.send(()).unwrap();
32483 futures::executor::block_on(blocking).unwrap();
32484 executor.begin_shutdown();
32485 assert_eq!(
32486 futures::executor::block_on(nested_task.fallible()),
32487 Some(())
32488 );
32489 futures::executor::block_on(timer_task);
32490 }
32491
32492 #[cfg(not(target_family = "wasm"))]
32493 #[test]
32494 fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
32495 use std::sync::mpsc;
32499
32500 let (done_tx, done_rx) = mpsc::channel();
32501 let runner = std::thread::spawn(move || {
32502 let executor = multi_thread_executor(2, None, "timer-rearm");
32503
32504 let long_exec = Arc::clone(&executor);
32505 let long_done = Arc::new(AtomicBool::new(false));
32506 let long_flag = Arc::clone(&long_done);
32507 let sched = Arc::clone(&executor);
32508 let (long_runnable, long_task) = async_task::spawn(
32509 async move {
32510 heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
32511 long_flag.store(true, Ordering::SeqCst);
32512 },
32513 move |r| sched.schedule(r),
32514 );
32515 executor.schedule(long_runnable);
32516 wait_until("the timekeeper parked on the 60s sentinel", || {
32517 executor
32518 .timers
32519 .inner
32520 .lock()
32521 .unwrap()
32522 .timekeeper_parker
32523 .is_some()
32524 });
32525
32526 let started = Instant::now();
32527 let mut future = std::pin::pin!(async {
32528 heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
32529 });
32530 executor.block_on(future.as_mut());
32531 let elapsed = started.elapsed();
32532 assert!(
32533 elapsed >= Duration::from_millis(60),
32534 "fired before its deadline: {elapsed:?}"
32535 );
32536 assert!(
32537 !long_done.load(Ordering::SeqCst),
32538 "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
32539 timekeeper, not by the sentinel expiring"
32540 );
32541 executor.shutdown_timers();
32542 futures::executor::block_on(long_task);
32543 let _ = done_tx.send(());
32544 });
32545
32546 match done_rx.recv_timeout(Duration::from_secs(15)) {
32547 Ok(()) => runner.join().unwrap(),
32548 Err(error) => panic!(
32549 "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
32550 ),
32551 }
32552 }
32553
32554 #[cfg(not(target_family = "wasm"))]
32555 #[test]
32556 fn multi_thread_sleep_cancels_registration_on_drop() {
32557 let executor = multi_thread_executor(1, None, "timer-cancel");
32560 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
32561 let waker = Waker::from(Arc::clone(&wake_count));
32562 let mut cx = Context::from_waker(&waker);
32563
32564 let deadline = Instant::now() + Duration::from_millis(250);
32568 let mut sleep = heap_sleep(&executor, deadline);
32569 assert!(
32570 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
32571 "must register, not resolve"
32572 );
32573 assert!(
32574 executor.next_timer_deadline().is_some(),
32575 "the registration must be in the heap"
32576 );
32577 drop(sleep);
32578 assert!(
32579 executor.next_timer_deadline().is_none(),
32580 "cancel-on-drop must retire the heap registration"
32581 );
32582 while Instant::now() < deadline + Duration::from_millis(25) {
32587 std::thread::sleep(Duration::from_millis(10));
32588 }
32589 executor.fire_due_timers();
32590 assert_eq!(
32591 wake_count.0.load(Ordering::SeqCst),
32592 0,
32593 "a dropped Sleep's waker must never fire"
32594 );
32595 }
32596
32597 #[cfg(not(target_family = "wasm"))]
32598 #[test]
32599 fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
32600 let executor = multi_thread_executor(2, None, "timer-cancel-retire");
32601 let waker = futures::task::noop_waker();
32602 let mut cx = Context::from_waker(&waker);
32603 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32604
32605 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32606 wait_until(
32607 "the timekeeper parked on the timer that will be cancelled",
32608 || {
32609 executor
32610 .timers
32611 .inner
32612 .lock()
32613 .unwrap()
32614 .timekeeper_parker
32615 .is_some()
32616 },
32617 );
32618
32619 drop(sleep);
32620 wait_until(
32621 "the cancelled timer's timekeeper returned to its idle wait",
32622 || {
32623 executor.next_timer_deadline().is_none()
32624 && executor
32625 .timers
32626 .inner
32627 .lock()
32628 .unwrap()
32629 .timekeeper_parker
32630 .is_none()
32631 },
32632 );
32633 }
32634
32635 #[cfg(not(target_family = "wasm"))]
32636 #[test]
32637 fn timer_waker_replacement_drops_outside_heap_lock() {
32638 use std::sync::mpsc;
32639
32640 struct DropOtherTimer(Mutex<Option<Sleep>>);
32641
32642 #[expect(
32643 clippy::manual_noop_waker,
32644 reason = "the no-op wake type has a reentrant Drop used by this regression"
32645 )]
32646 impl std::task::Wake for DropOtherTimer {
32647 fn wake(self: Arc<Self>) {}
32648 }
32649
32650 impl Drop for DropOtherTimer {
32651 fn drop(&mut self) {
32652 drop(self.0.lock().unwrap().take());
32653 }
32654 }
32655
32656 let (done_tx, done_rx) = mpsc::channel();
32657 let runner = std::thread::spawn(move || {
32658 let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
32659 let deadline = Instant::now() + Duration::from_secs(120);
32660 let mut other = heap_sleep(&executor, deadline);
32661 let noop = futures::task::noop_waker();
32662 let mut noop_cx = Context::from_waker(&noop);
32663 assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
32664
32665 let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
32666 let mut primary = heap_sleep(&executor, deadline);
32667 let mut old_cx = Context::from_waker(&old);
32668 assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
32669 drop(old);
32670
32671 let replacement = futures::task::noop_waker();
32672 let mut replacement_cx = Context::from_waker(&replacement);
32673 assert!(
32674 Pin::new(&mut primary)
32675 .poll(&mut replacement_cx)
32676 .is_pending()
32677 );
32678 drop(primary);
32679 executor.shutdown_timers();
32680 done_tx.send(()).unwrap();
32681 });
32682 done_rx
32685 .recv_timeout(Duration::from_secs(10))
32686 .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
32687 runner.join().unwrap();
32688 }
32689
32690 #[cfg(not(target_family = "wasm"))]
32691 #[test]
32692 fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
32693 use std::sync::mpsc;
32694
32695 struct DropFinalExecutorOnWake {
32696 executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
32697 completed: mpsc::Sender<()>,
32698 }
32699
32700 impl std::task::Wake for DropFinalExecutorOnWake {
32701 fn wake(self: Arc<Self>) {
32702 let executor = self
32703 .executor
32704 .lock()
32705 .unwrap_or_else(std::sync::PoisonError::into_inner)
32706 .take()
32707 .expect("the timer waker must own the final executor reference");
32708 assert_eq!(
32709 ON_TIMER_THREAD.with(std::cell::Cell::get),
32710 Some(executor.id),
32711 "the dedicated timer thread must deliver the timer wake"
32712 );
32713 drop(executor);
32714 self
32715 .completed
32716 .send(())
32717 .expect("the test receiver must outlive the timer-thread callback");
32718 }
32719 }
32720
32721 let executor = multi_thread_executor(1, None, "timer-final-arc");
32722 let (completed_tx, completed_rx) = mpsc::channel();
32723 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
32724 {
32725 let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
32726 executor: Mutex::new(Some(Arc::clone(&executor))),
32727 completed: completed_tx,
32728 }));
32729 let mut cx = Context::from_waker(&waker);
32730 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32731 }
32732 wait_until(
32733 "the timer thread parked before owning the final executor Arc",
32734 || {
32735 executor
32736 .timers
32737 .inner
32738 .lock()
32739 .unwrap()
32740 .timekeeper_parker
32741 .is_some()
32742 },
32743 );
32744
32745 drop(executor);
32749 completed_rx
32752 .recv_timeout(Duration::from_secs(10))
32753 .expect("dropping the final executor Arc on the timer thread attempted to join itself");
32754 }
32755
32756 #[cfg(not(target_family = "wasm"))]
32757 #[test]
32758 fn multi_thread_shutdown_with_pending_timers_drain_fires() {
32759 use std::sync::mpsc;
32764
32765 let (done_tx, done_rx) = mpsc::channel();
32766 let runner = std::thread::spawn(move || {
32767 let controller = Arc::new(RuntimeController::new());
32768 let backend = controller.backend();
32770 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
32771 panic!("default native backend must be MultiThread");
32772 };
32773 let executor = Arc::downgrade(executor);
32774 let mut late = heap_sleep(
32775 &executor
32776 .upgrade()
32777 .expect("the initial executor must be live"),
32778 Instant::now() + Duration::from_secs(120),
32779 );
32780 drop(backend);
32781
32782 let (parked_tx, parked_rx) = mpsc::channel();
32783 let block_controller = Arc::clone(&controller);
32784 let block_exec = Weak::clone(&executor);
32785 let blocker = std::thread::spawn(move || {
32786 let mut future = std::pin::pin!(async {
32787 let executor = block_exec
32788 .upgrade()
32789 .expect("the runtime must remain live while blocking");
32790 let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
32791 drop(executor);
32792 parked_tx.send(()).unwrap();
32793 sleep.await;
32794 });
32795 block_controller.block_on(future.as_mut());
32796 });
32797 parked_rx.recv().unwrap();
32798 wait_until("the 120s sleep registered in the heap", || {
32799 executor
32800 .upgrade()
32801 .is_some_and(|executor| executor.next_timer_deadline().is_some())
32802 });
32803
32804 controller
32806 .shutdown()
32807 .expect("runtime shutdown must succeed");
32808 blocker
32809 .join()
32810 .expect("the blocked sleep must be drain-fired by shutdown, not hang");
32811
32812 let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
32815 let mut cx = Context::from_waker(&waker);
32816 assert!(
32817 Pin::new(&mut late).poll(&mut cx).is_ready(),
32818 "a Sleep polled after shutdown must fire early, never park a closing runtime"
32819 );
32820 let _ = done_tx.send(());
32821 });
32822
32823 match done_rx.recv_timeout(Duration::from_secs(10)) {
32824 Ok(()) => runner.join().unwrap(),
32825 Err(error) => {
32826 panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
32827 }
32828 }
32829 }
32830
32831 #[cfg(not(target_family = "wasm"))]
32832 #[test]
32833 fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
32834 use std::sync::mpsc;
32843
32844 let (done_tx, done_rx) = mpsc::channel();
32845 let runner = std::thread::spawn(move || {
32846 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
32847
32848 let task_exec = Arc::clone(&executor);
32849 let body = async move {
32850 let started = Instant::now();
32851 let mut inner = std::pin::pin!(async {
32852 heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
32853 });
32854 task_exec.block_on(inner.as_mut());
32856 assert!(started.elapsed() >= Duration::from_millis(250));
32857 };
32858 let sched = Arc::clone(&executor);
32859 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
32860 executor.schedule(runnable);
32861
32862 futures::executor::block_on(task);
32866 drop(executor);
32872 let _ = done_tx.send(());
32873 });
32874
32875 match done_rx.recv_timeout(Duration::from_secs(10)) {
32876 Ok(()) => join_within(
32877 "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
32878 runner,
32879 Duration::from_secs(30),
32880 ),
32881 Err(error) => {
32882 panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
32883 }
32884 }
32885 }
32886
32887 #[cfg(not(target_family = "wasm"))]
32888 #[test]
32889 fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
32890 use std::sync::mpsc;
32897
32898 struct GateWake {
32901 gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
32902 }
32903 impl std::task::Wake for GateWake {
32904 fn wake(self: Arc<Self>) {
32905 let gate = self.gate.lock().unwrap().take();
32906 if let Some(gate) = gate {
32907 let _ = gate.send(23);
32908 }
32909 }
32910 }
32911
32912 let (done_tx, done_rx) = mpsc::channel();
32913 let runner = std::thread::spawn(move || {
32914 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
32915 executor
32919 .active_drainers
32920 .store(executor.max_drainers, Ordering::SeqCst);
32921
32922 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32925 let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
32926
32927 let hook_exec = Arc::clone(&executor);
32928 let hook_gate = Arc::clone(&gate_tx);
32929 let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
32932 let hook_sleep_slot = Arc::clone(&parked_sleep);
32933 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
32934 *slot.borrow_mut() = Some(Box::new(move || {
32935 let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
32936 let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
32937 let mut cx = Context::from_waker(&waker);
32938 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32939 *hook_sleep_slot.lock().unwrap() = Some(sleep);
32940 }));
32941 });
32942
32943 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32946 let mut output = None;
32947 {
32948 let mut future = std::pin::pin!(async {
32949 output = Some(gate_rx.await.unwrap());
32950 });
32951 executor.block_on(future.as_mut());
32952 }
32953 DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
32959 assert_eq!(
32960 output,
32961 Some(23),
32962 "the edge-registered timer must have fired and released the gate"
32963 );
32964 executor.active_drainers.store(0, Ordering::SeqCst);
32965 drop(executor);
32968 let _ = done_tx.send(());
32969 });
32970
32971 match done_rx.recv_timeout(Duration::from_secs(10)) {
32972 Ok(()) => join_within(
32973 "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
32974 runner,
32975 Duration::from_secs(30),
32976 ),
32977 Err(error) => panic!(
32978 "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
32979 ),
32980 }
32981 }
32982
32983 #[test]
32984 fn current_thread_sleep_without_host_driver_fails_loud() {
32985 let metrics = Arc::new(RuntimeMetrics::default());
32989 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
32990 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
32991
32992 let payload = catch_unwind(AssertUnwindSafe(|| {
32993 let _sleep = make_sleep(
32994 &backend,
32995 &Arc::new(TimerDriverRegistry::default()),
32996 Instant::now() + Duration::from_millis(10),
32997 );
32998 }))
32999 .expect_err("sleep_until on CurrentThread without a driver must panic");
33000 let message = payload
33001 .downcast_ref::<String>()
33002 .cloned()
33003 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
33004 .expect("panic payload must be a message");
33005 assert!(
33006 message.contains("CurrentThread runtime has no live timer driver registered"),
33007 "the diagnostic must name the missing driver, got: {message}"
33008 );
33009 }
33010
33011 #[test]
33012 fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
33013 let registry = TimerDriverRegistry::default();
33017 let a = ManualStubDriver::new();
33018 let b = ManualStubDriver::new();
33019 let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
33020 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
33021 assert_ne!(a_id, b_id, "registration handles must be distinct");
33022 assert_eq!(
33023 registry.current().map(|(id, _)| id),
33024 Some(b_id),
33025 "newest live registrant serves"
33026 );
33027 assert!(registry.has_live_driver());
33028
33029 b.kill();
33032 assert_eq!(
33033 registry.current().map(|(id, _)| id),
33034 Some(a_id),
33035 "dead newest must be skipped"
33036 );
33037 b.live.store(true, Ordering::SeqCst);
33040 assert_eq!(
33041 registry.current().map(|(id, _)| id),
33042 Some(a_id),
33043 "swept entries stay gone"
33044 );
33045
33046 registry.unregister(a_id);
33049 assert!(registry.current().is_none());
33050 assert!(
33051 !registry.has_live_driver(),
33052 "an emptied registry must not report a live driver"
33053 );
33054 }
33055
33056 #[test]
33057 fn timer_driver_registry_contains_callback_panics_and_falls_back() {
33058 let registry = TimerDriverRegistry::default();
33059 let fallback = ManualStubDriver::new();
33060 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
33061 let panicking = Arc::new(PanickingTimerDriver {
33062 swept: AtomicBool::new(false),
33063 });
33064 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
33065
33066 assert_eq!(
33067 registry.current().map(|(id, _)| id),
33068 Some(fallback_id),
33069 "a panicking liveness callback must be treated as a dead driver"
33070 );
33071 assert!(
33072 panicking.swept.load(Ordering::SeqCst),
33073 "a panicking liveness callback must still run its contained sweep hook"
33074 );
33075 assert!(
33076 registry.has_live_driver(),
33077 "the healthy fallback must remain selectable"
33078 );
33079 }
33080
33081 #[test]
33082 fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
33083 use std::{sync::mpsc, time::Duration};
33084
33085 let registry = Arc::new(TimerDriverRegistry::default());
33086 let fallback = ManualStubDriver::new();
33087 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
33088 let reentrant = Arc::new(ReentrantLivenessDriver {
33089 registry: Arc::downgrade(®istry),
33090 registration: Mutex::new(None),
33091 });
33092 let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
33093 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
33094
33095 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
33096 let selection_registry = Arc::clone(®istry);
33097 let selection_thread = std::thread::spawn(move || {
33098 selection_tx
33099 .send(selection_registry.current().map(|(id, _)| id))
33100 .unwrap();
33101 });
33102 assert_eq!(
33103 selection_rx
33104 .recv_timeout(Duration::from_secs(10))
33105 .expect("is_live re-entry deadlocked"),
33106 Some(fallback_id)
33107 );
33108 selection_thread.join().unwrap();
33109
33110 let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
33111 registry: Arc::downgrade(®istry),
33112 fallback: fallback_id,
33113 });
33114 let drop_id = registry.register(Arc::clone(&drop_driver));
33115 drop(drop_driver);
33116
33117 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
33118 let drop_registry = Arc::clone(®istry);
33119 let drop_thread = std::thread::spawn(move || {
33120 drop_registry.unregister(drop_id);
33121 drop_tx.send(()).unwrap();
33122 });
33123 drop_rx
33124 .recv_timeout(Duration::from_secs(10))
33125 .expect("driver destructor re-entry deadlocked");
33126 drop_thread.join().unwrap();
33127 assert!(
33128 registry.current().is_none(),
33129 "the destructor must be able to unregister the fallback"
33130 );
33131 }
33132
33133 #[test]
33134 fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
33135 const DRIVER_PAIRS: usize = 2_048;
33136
33137 let registry = Arc::new(TimerDriverRegistry::default());
33138 let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
33139 let mut live = Vec::with_capacity(DRIVER_PAIRS);
33140 let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
33141 for _ in 0..DRIVER_PAIRS {
33142 let driver = Arc::new(ReentrantSweepTimerDriver {
33143 registry: Arc::downgrade(®istry),
33144 registration: Mutex::new(None),
33145 sweeps: AtomicUsize::new(0),
33146 removed_before_sweep: AtomicBool::new(false),
33147 });
33148 let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33149 *driver.registration.lock().unwrap() = Some(id);
33150 dead.push(driver);
33151
33152 let driver = ManualStubDriver::new();
33153 live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
33154 live.push(driver);
33155 }
33156 let dead_tail = Arc::new(ReentrantSweepTimerDriver {
33157 registry: Arc::downgrade(®istry),
33158 registration: Mutex::new(None),
33159 sweeps: AtomicUsize::new(0),
33160 removed_before_sweep: AtomicBool::new(false),
33161 });
33162 let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
33163 *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
33164 dead.push(dead_tail);
33165
33166 let selection = registry.live();
33167 assert_eq!(
33168 selection
33169 .as_slice()
33170 .iter()
33171 .map(|(id, _)| *id)
33172 .collect::<Vec<_>>(),
33173 live_ids,
33174 "sweeping every interleaved dead host must retain live registration order"
33175 );
33176 drop(selection);
33177 assert!(
33178 dead.iter().all(|driver| {
33179 driver.sweeps.load(Ordering::SeqCst) == 1
33180 && driver.removed_before_sweep.load(Ordering::SeqCst)
33181 && driver.registration.lock().unwrap().is_none()
33182 }),
33183 "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
33184 );
33185
33186 {
33187 let state = registry
33188 .state
33189 .lock()
33190 .unwrap_or_else(std::sync::PoisonError::into_inner);
33191 assert_eq!(state.entries.len(), DRIVER_PAIRS);
33192 assert_eq!(state.oldest, live_ids.first().copied());
33193 assert_eq!(state.newest, live_ids.last().copied());
33194 assert_eq!(
33195 state
33196 .ordered_snapshot()
33197 .iter()
33198 .map(|(id, _)| *id)
33199 .collect::<Vec<_>>(),
33200 live_ids,
33201 "keyed removals must preserve the linked order of every live host"
33202 );
33203 }
33204 let second_selection = registry.live();
33205 assert_eq!(
33206 second_selection
33207 .as_slice()
33208 .iter()
33209 .map(|(id, _)| *id)
33210 .collect::<Vec<_>>(),
33211 live_ids,
33212 "a second selection must retain the same live order"
33213 );
33214 drop(second_selection);
33215 assert!(
33216 dead
33217 .iter()
33218 .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
33219 "permanently swept hosts must not receive repeated eviction callbacks"
33220 );
33221 assert_eq!(
33222 registry.current().map(|(id, _)| id),
33223 live_ids.last().copied(),
33224 "newest-live compatibility selection must survive the mass sweep"
33225 );
33226
33227 drop(live);
33228 drop(dead);
33229 }
33230
33231 #[test]
33232 fn timer_driver_registry_snapshot_contains_each_driver_drop() {
33233 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
33234
33235 if std::env::var_os(CHILD_ENV).is_some() {
33236 let registry = Arc::new(TimerDriverRegistry::default());
33237 let fallback = ManualStubDriver::new();
33238 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
33239 let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
33240 registry: Arc::downgrade(®istry),
33241 registrations: Mutex::new(Vec::new()),
33242 });
33243 registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
33244
33245 let first_cancels = Arc::new(AtomicUsize::new(0));
33246 let first_drops = Arc::new(AtomicUsize::new(0));
33247 let first = Arc::new(PanickingCancelDropTimerDriver {
33248 cancels: Arc::clone(&first_cancels),
33249 drops: Arc::clone(&first_drops),
33250 });
33251 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
33252 drop(first);
33253
33254 let second_cancels = Arc::new(AtomicUsize::new(0));
33255 let second_drops = Arc::new(AtomicUsize::new(0));
33256 let second = Arc::new(PanickingCancelDropTimerDriver {
33257 cancels: Arc::clone(&second_cancels),
33258 drops: Arc::clone(&second_drops),
33259 });
33260 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
33261 drop(second);
33262 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
33263
33264 assert_eq!(
33265 registry.current().map(|(id, _)| id),
33266 Some(fallback_id),
33267 "selection must retry after reentrant removals and preserve the healthy fallback"
33268 );
33269 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
33270 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
33271 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
33272 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
33273 assert_eq!(
33274 registry
33275 .state
33276 .lock()
33277 .unwrap_or_else(std::sync::PoisonError::into_inner)
33278 .entries
33279 .len(),
33280 1
33281 );
33282 return;
33283 }
33284
33285 let output = std::process::Command::new(std::env::current_exe().unwrap())
33286 .arg("--exact")
33287 .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
33288 .arg("--nocapture")
33289 .env(CHILD_ENV, "1")
33290 .output()
33291 .expect("the hostile timer-driver snapshot subprocess test must start");
33292 assert!(
33293 output.status.success(),
33294 "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
33295 output.status.code(),
33296 String::from_utf8_lossy(&output.stdout),
33297 String::from_utf8_lossy(&output.stderr)
33298 );
33299 }
33300
33301 #[test]
33302 fn timer_driver_registry_live_result_contains_each_driver_drop() {
33303 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
33304
33305 if std::env::var_os(CHILD_ENV).is_some() {
33306 let registry = TimerDriverRegistry::default();
33307
33308 let first_cancels = Arc::new(AtomicUsize::new(0));
33309 let first_drops = Arc::new(AtomicUsize::new(0));
33310 let first = Arc::new(PanickingCancelDropTimerDriver {
33311 cancels: Arc::clone(&first_cancels),
33312 drops: Arc::clone(&first_drops),
33313 });
33314 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
33315 drop(first);
33316
33317 let second_cancels = Arc::new(AtomicUsize::new(0));
33318 let second_drops = Arc::new(AtomicUsize::new(0));
33319 let second = Arc::new(PanickingCancelDropTimerDriver {
33320 cancels: Arc::clone(&second_cancels),
33321 drops: Arc::clone(&second_drops),
33322 });
33323 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
33324 drop(second);
33325
33326 let live = registry.live();
33327 assert_eq!(live.len(), 2);
33328 registry.unregister(first_id);
33329 registry.unregister(second_id);
33330 drop(live);
33331
33332 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
33333 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
33334 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
33335 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
33336 assert!(
33337 registry
33338 .state
33339 .lock()
33340 .unwrap_or_else(std::sync::PoisonError::into_inner)
33341 .entries
33342 .is_empty()
33343 );
33344 return;
33345 }
33346
33347 let output = std::process::Command::new(std::env::current_exe().unwrap())
33348 .arg("--exact")
33349 .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
33350 .arg("--nocapture")
33351 .env(CHILD_ENV, "1")
33352 .output()
33353 .expect("the hostile live timer-driver snapshot subprocess test must start");
33354 assert!(
33355 output.status.success(),
33356 "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
33357 output.status.code(),
33358 String::from_utf8_lossy(&output.stdout),
33359 String::from_utf8_lossy(&output.stderr)
33360 );
33361 }
33362
33363 #[test]
33364 fn timer_driver_registry_drop_contains_each_driver_destructor() {
33365 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
33366
33367 if std::env::var_os(CHILD_ENV).is_some() {
33368 let first_drops = Arc::new(AtomicUsize::new(0));
33369 let second_drops = Arc::new(AtomicUsize::new(0));
33370 let registry = TimerDriverRegistry::default();
33371 let first = Arc::new(PanickingCancelDropTimerDriver {
33372 cancels: Arc::new(AtomicUsize::new(0)),
33373 drops: Arc::clone(&first_drops),
33374 });
33375 let second = Arc::new(PanickingCancelDropTimerDriver {
33376 cancels: Arc::new(AtomicUsize::new(0)),
33377 drops: Arc::clone(&second_drops),
33378 });
33379 registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
33380 registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
33381 drop(first);
33382 drop(second);
33383
33384 drop(registry);
33385
33386 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
33387 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
33388 return;
33389 }
33390
33391 let output = std::process::Command::new(std::env::current_exe().unwrap())
33392 .arg("--exact")
33393 .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
33394 .arg("--nocapture")
33395 .env(CHILD_ENV, "1")
33396 .output()
33397 .expect("the hostile timer-driver registry drop subprocess test must start");
33398 assert!(
33399 output.status.success(),
33400 "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
33401 output.status.code(),
33402 String::from_utf8_lossy(&output.stdout),
33403 String::from_utf8_lossy(&output.stderr)
33404 );
33405 }
33406
33407 #[test]
33408 fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
33409 const DRIVERS: usize = 512;
33410
33411 let metrics = Arc::new(RuntimeMetrics::default());
33412 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33413 let backend =
33414 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33415 let registry = Arc::new(TimerDriverRegistry::default());
33416 let drivers = (0..DRIVERS)
33417 .map(|_| {
33418 let driver = ManualStubDriver::new();
33419 registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33420 driver
33421 })
33422 .collect::<Vec<_>>();
33423 let mut sleep = make_sleep(
33424 &backend,
33425 ®istry,
33426 Instant::now() + Duration::from_secs(60),
33427 );
33428 let mut cx = Context::from_waker(Waker::noop());
33429
33430 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33431 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33432 let timer_id = drivers[0].registers.lock().unwrap()[0].0;
33433 match &sleep.inner {
33434 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
33435 #[cfg(not(target_family = "wasm"))]
33436 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
33437 }
33438 for driver in &drivers {
33439 assert_eq!(
33440 driver.registers.lock().unwrap().len(),
33441 2,
33442 "a re-poll must refresh each live driver exactly once"
33443 );
33444 }
33445
33446 for driver in drivers.iter().step_by(2) {
33447 driver.kill();
33448 }
33449 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33450 match &sleep.inner {
33451 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
33452 #[cfg(not(target_family = "wasm"))]
33453 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
33454 }
33455 for (index, driver) in drivers.iter().enumerate() {
33456 let registers = driver.registers.lock().unwrap().len();
33457 let cancels = driver.cancels.lock().unwrap().clone();
33458 if index % 2 == 0 {
33459 assert_eq!(registers, 2);
33460 assert_eq!(cancels, vec![timer_id]);
33461 } else {
33462 assert_eq!(registers, 3);
33463 assert!(cancels.is_empty());
33464 }
33465 }
33466
33467 drop(sleep);
33468 for driver in drivers {
33469 assert_eq!(
33470 driver.cancels.lock().unwrap().as_slice(),
33471 &[timer_id],
33472 "every physical arm must be cancelled exactly once"
33473 );
33474 }
33475 assert_eq!(registry.pending_len(), 0);
33476 assert!(!executor.host_timers.has_pending());
33477 }
33478
33479 #[test]
33480 fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
33481 let metrics = Arc::new(RuntimeMetrics::default());
33482 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33483 let backend =
33484 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33485 let healthy = ManualStubDriver::new();
33486 let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
33487 let deadline = Instant::now() + Duration::from_secs(60);
33488 let mut sleep = make_sleep(&backend, ®istry, deadline);
33489 let mut cx = Context::from_waker(Waker::noop());
33490
33491 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33492 let timer_id = healthy.registers.lock().unwrap()[0].0;
33493
33494 let panicking = PanickingRegisterTimerDriver::new();
33495 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
33496 let result = catch_unwind(AssertUnwindSafe(|| {
33497 let _ = Pin::new(&mut sleep).poll(&mut cx);
33498 }));
33499 assert!(
33500 result.is_err(),
33501 "the hostile register panic must retain its normal propagation"
33502 );
33503 assert_eq!(
33504 healthy.registers.lock().unwrap().len(),
33505 2,
33506 "the previously armed driver must have completed its refresh before the later panic"
33507 );
33508 assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
33509 assert!(
33510 panicking.pending.lock().unwrap().contains_key(&timer_id),
33511 "the hostile driver must model an arm committed before it panics"
33512 );
33513
33514 drop(sleep);
33515 assert_eq!(
33516 healthy.cancels.lock().unwrap().as_slice(),
33517 &[timer_id],
33518 "unwind must preserve cancellation ownership for the previously armed driver"
33519 );
33520 assert_eq!(
33521 panicking.cancels.lock().unwrap().as_slice(),
33522 &[timer_id],
33523 "unwind must preserve cancellation ownership for the driver that armed and then panicked"
33524 );
33525 assert!(panicking.pending.lock().unwrap().is_empty());
33526 assert_eq!(registry.pending_len(), 0);
33527 assert!(!executor.host_timers.has_pending());
33528 }
33529
33530 #[test]
33531 fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
33532 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
33533
33534 if std::env::var_os(CHILD_ENV).is_some() {
33535 let metrics = Arc::new(RuntimeMetrics::default());
33536 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33537 let backend =
33538 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33539 let registry = Arc::new(TimerDriverRegistry::default());
33540 let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
33541 registry: Arc::downgrade(®istry),
33542 later: Mutex::new(None),
33543 registers: AtomicUsize::new(0),
33544 });
33545 registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
33546
33547 let later_cancels = Arc::new(AtomicUsize::new(0));
33548 let later_drops = Arc::new(AtomicUsize::new(0));
33549 let later = Arc::new(PanickingCancelDropTimerDriver {
33550 cancels: Arc::clone(&later_cancels),
33551 drops: Arc::clone(&later_drops),
33552 });
33553 let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
33554 *earlier.later.lock().unwrap() = Some(later_id);
33555 drop(later);
33556
33557 let mut sleep = make_sleep(
33558 &backend,
33559 ®istry,
33560 Instant::now() + Duration::from_secs(60),
33561 );
33562 let mut cx = Context::from_waker(Waker::noop());
33563 let result = catch_unwind(AssertUnwindSafe(|| {
33564 let _ = Pin::new(&mut sleep).poll(&mut cx);
33565 }));
33566
33567 assert!(
33568 result.is_err(),
33569 "the earlier driver's register panic must still propagate"
33570 );
33571 assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
33572 assert_eq!(
33573 later_drops.load(Ordering::SeqCst),
33574 0,
33575 "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
33576 );
33577
33578 drop(sleep);
33579 assert_eq!(
33580 later_cancels.load(Ordering::SeqCst),
33581 1,
33582 "the later driver must remain owned for cancellation despite its registry removal"
33583 );
33584 assert_eq!(
33585 later_drops.load(Ordering::SeqCst),
33586 1,
33587 "the later driver's final destructor must run once behind containment"
33588 );
33589 assert_eq!(registry.pending_len(), 0);
33590 assert!(!executor.host_timers.has_pending());
33591 return;
33592 }
33593
33594 let output = std::process::Command::new(std::env::current_exe().unwrap())
33595 .arg("--exact")
33596 .arg(
33597 "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
33598 )
33599 .arg("--nocapture")
33600 .env(CHILD_ENV, "1")
33601 .output()
33602 .expect("the hostile host timer snapshot subprocess test must start");
33603 assert!(
33604 output.status.success(),
33605 "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
33606 output.status.code(),
33607 String::from_utf8_lossy(&output.stdout),
33608 String::from_utf8_lossy(&output.stderr)
33609 );
33610 }
33611
33612 #[test]
33613 fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
33614 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
33615
33616 if std::env::var_os(CHILD_ENV).is_some() {
33617 let metrics = Arc::new(RuntimeMetrics::default());
33618 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33619 let backend =
33620 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33621 let cancels = Arc::new(AtomicUsize::new(0));
33622 let drops = Arc::new(AtomicUsize::new(0));
33623 let hostile = Arc::new(PanickingCancelDropTimerDriver {
33624 cancels: Arc::clone(&cancels),
33625 drops: Arc::clone(&drops),
33626 });
33627 let healthy = ManualStubDriver::new();
33628 let registry = Arc::new(TimerDriverRegistry::default());
33629 let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
33630 registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
33631 let mut sleep = make_sleep(
33632 &backend,
33633 ®istry,
33634 Instant::now() + Duration::from_secs(60),
33635 );
33636 let mut cx = Context::from_waker(Waker::noop());
33637
33638 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33639 let timer_id = healthy.registers.lock().unwrap()[0].0;
33640
33641 registry.unregister(hostile_id);
33644 drop(hostile);
33645 drop(sleep);
33646 assert_eq!(cancels.load(Ordering::SeqCst), 1);
33647 assert_eq!(drops.load(Ordering::SeqCst), 1);
33648 assert_eq!(
33649 healthy.cancels.lock().unwrap().as_slice(),
33650 &[timer_id],
33651 "one hostile cancellation must not skip later armed drivers"
33652 );
33653 assert_eq!(registry.pending_len(), 0);
33654 assert!(!executor.host_timers.has_pending());
33655 return;
33656 }
33657
33658 let output = std::process::Command::new(std::env::current_exe().unwrap())
33659 .arg("--exact")
33660 .arg(
33661 "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
33662 )
33663 .arg("--nocapture")
33664 .env(CHILD_ENV, "1")
33665 .output()
33666 .expect("the hostile timer-driver cancellation subprocess test must start");
33667 assert!(
33668 output.status.success(),
33669 "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
33670 output.status.code(),
33671 String::from_utf8_lossy(&output.stdout),
33672 String::from_utf8_lossy(&output.stderr)
33673 );
33674 }
33675
33676 #[test]
33677 fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
33678 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
33679
33680 if std::env::var_os(CHILD_ENV).is_some() {
33681 let metrics = Arc::new(RuntimeMetrics::default());
33682 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33683 let backend =
33684 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33685 let driver = ManualStubDriver::new();
33686 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33687 let state = Arc::new(TestHostileRawWakerState {
33688 clones: AtomicUsize::new(0),
33689 drop_panics: AtomicUsize::new(0),
33690 panic_on_clone: Some(2),
33691 });
33692
33693 let result = catch_unwind(AssertUnwindSafe(|| {
33694 let mut sleep = make_sleep(
33695 &backend,
33696 ®istry,
33697 Instant::now() + Duration::from_secs(60),
33698 );
33699 let waker = test_hostile_waker(Arc::clone(&state));
33700 let mut cx = Context::from_waker(&waker);
33701 let _ = Pin::new(&mut sleep).poll(&mut cx);
33702 }));
33703 assert!(
33704 result.is_err(),
33705 "the original RawWaker clone panic must still propagate"
33706 );
33707 assert_eq!(state.clones.load(Ordering::SeqCst), 2);
33708 assert_eq!(
33709 state.drop_panics.load(Ordering::SeqCst),
33710 1,
33711 "the first clone must be destroyed once behind a contained boundary"
33712 );
33713 assert!(driver.registers.lock().unwrap().is_empty());
33714 assert_eq!(registry.pending_len(), 0);
33715 assert!(!executor.host_timers.has_pending());
33716 return;
33717 }
33718
33719 let output = std::process::Command::new(std::env::current_exe().unwrap())
33720 .arg("--exact")
33721 .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
33722 .arg("--nocapture")
33723 .env(CHILD_ENV, "1")
33724 .output()
33725 .expect("the hostile RawWaker subprocess test must start");
33726 assert!(
33727 output.status.success(),
33728 "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
33729 output.status.code(),
33730 String::from_utf8_lossy(&output.stdout),
33731 String::from_utf8_lossy(&output.stderr)
33732 );
33733 }
33734
33735 #[test]
33736 fn host_sleep_register_unwind_contains_unretained_waker_drop() {
33737 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
33738
33739 if std::env::var_os(CHILD_ENV).is_some() {
33740 let metrics = Arc::new(RuntimeMetrics::default());
33741 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33742 let backend =
33743 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33744 let driver = Arc::new(PanicBeforeRetainingTimerDriver {
33745 registers: AtomicUsize::new(0),
33746 });
33747 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33748 let state = Arc::new(TestHostileRawWakerState {
33749 clones: AtomicUsize::new(0),
33750 drop_panics: AtomicUsize::new(0),
33751 panic_on_clone: None,
33752 });
33753 let mut sleep = make_sleep(
33754 &backend,
33755 ®istry,
33756 Instant::now() + Duration::from_secs(60),
33757 );
33758 let waker = test_hostile_waker(Arc::clone(&state));
33759 let mut cx = Context::from_waker(&waker);
33760
33761 let result = catch_unwind(AssertUnwindSafe(|| {
33762 let _ = Pin::new(&mut sleep).poll(&mut cx);
33763 }));
33764 assert!(
33765 result.is_err(),
33766 "the timer-driver register panic must still propagate"
33767 );
33768 assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
33769 assert_eq!(state.clones.load(Ordering::SeqCst), 3);
33770 assert_eq!(
33771 state.drop_panics.load(Ordering::SeqCst),
33772 1,
33773 "the unretained register argument must be destroyed once behind the proxy boundary"
33774 );
33775 assert_eq!(registry.pending_len(), 1);
33776 assert!(executor.host_timers.has_pending());
33777
33778 drop(sleep);
33779 assert_eq!(
33780 state.drop_panics.load(Ordering::SeqCst),
33781 3,
33782 "repoll and registry wakers must retain their existing contained cleanup"
33783 );
33784 assert_eq!(registry.pending_len(), 0);
33785 assert!(!executor.host_timers.has_pending());
33786 return;
33787 }
33788
33789 let output = std::process::Command::new(std::env::current_exe().unwrap())
33790 .arg("--exact")
33791 .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
33792 .arg("--nocapture")
33793 .env(CHILD_ENV, "1")
33794 .output()
33795 .expect("the hostile register-waker subprocess test must start");
33796 assert!(
33797 output.status.success(),
33798 "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
33799 output.status.code(),
33800 String::from_utf8_lossy(&output.stdout),
33801 String::from_utf8_lossy(&output.stderr)
33802 );
33803 }
33804
33805 #[test]
33806 fn host_sleep_rearms_on_next_live_driver_after_eviction() {
33807 let metrics = Arc::new(RuntimeMetrics::default());
33813 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33814 let backend =
33815 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33816
33817 let a = ManualStubDriver::new();
33818 let b = ManualStubDriver::new();
33819 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
33820 let deadline = Instant::now() + Duration::from_secs(60);
33821
33822 let (timer_id, b_id) = {
33823 let mut sleep = std::pin::pin!(make_sleep(&backend, ®istry, deadline));
33824 let mut cx = Context::from_waker(Waker::noop());
33825
33826 assert!(sleep.as_mut().poll(&mut cx).is_pending());
33828 let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
33829 assert_eq!(armed_deadline, deadline);
33830 assert!(
33831 executor.host_timers.has_pending(),
33832 "first poll must arm the wake-token"
33833 );
33834
33835 a.kill();
33839 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
33840 assert!(sleep.as_mut().poll(&mut cx).is_pending());
33841 assert_eq!(
33842 b.registers.lock().unwrap().as_slice(),
33843 &[(timer_id, deadline)],
33844 "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
33845 );
33846 assert_eq!(
33847 a.cancels.lock().unwrap().as_slice(),
33848 &[timer_id],
33849 "the dead driver gets a best-effort cancel for the old arm"
33850 );
33851 assert_eq!(
33852 registry.current().map(|(id, _)| id),
33853 Some(b_id),
33854 "the dead driver was evicted"
33855 );
33856 assert!(
33857 executor.host_timers.has_pending(),
33858 "the wake-token survives the re-arm (still exactly this sleep's entry)"
33859 );
33860 (timer_id, b_id)
33861 };
33862
33863 let _ = b_id;
33866 assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
33867 assert!(
33868 !executor.host_timers.has_pending(),
33869 "drop must retire the wake-token"
33870 );
33871 }
33872
33873 #[test]
33874 fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
33875 let metrics = Arc::new(RuntimeMetrics::default());
33876 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33877 let backend =
33878 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33879 let responsive = Arc::new(StubHostTimerDriver::new());
33880 let starved = ManualStubDriver::new();
33881 let registry = Arc::new(TimerDriverRegistry::default());
33882 registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
33883 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
33886
33887 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
33888 let waker = Waker::from(Arc::clone(&wake_count));
33889 let mut cx = Context::from_waker(&waker);
33890 let deadline = Instant::now() + Duration::from_millis(500);
33895 let mut sleep = make_sleep(&backend, ®istry, deadline);
33896 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33897
33898 let responsive_timer = *responsive
33899 .timers
33900 .lock()
33901 .unwrap()
33902 .keys()
33903 .next()
33904 .expect("responsive timer must be armed");
33905 assert_eq!(
33906 starved.registers.lock().unwrap().as_slice(),
33907 &[(responsive_timer, deadline)],
33908 "every live host must receive the same timer identity and absolute deadline"
33909 );
33910
33911 let fire_bound = Instant::now() + Duration::from_secs(30);
33918 while wake_count.0.load(Ordering::SeqCst) == 0 {
33919 assert!(
33920 Instant::now() < fire_bound,
33921 "the responsive host must wake a sleep whose newest host is live but starved"
33922 );
33923 std::thread::sleep(Duration::from_millis(10));
33924 }
33925 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
33928 assert_eq!(
33929 starved.cancels.lock().unwrap().as_slice(),
33930 &[responsive_timer],
33931 "completion through the winning host must cancel the starved losing arm"
33932 );
33933 }
33934
33935 #[test]
33936 fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
33937 use std::sync::mpsc;
33938
33939 struct ReentrantTimerRegistrationWake {
33940 registry: Weak<TimerDriverRegistry>,
33941 wakes: AtomicUsize,
33942 reentered: AtomicBool,
33943 }
33944
33945 impl std::task::Wake for ReentrantTimerRegistrationWake {
33946 fn wake(self: Arc<Self>) {
33947 self.wakes.fetch_add(1, Ordering::SeqCst);
33948 if let Some(registry) = self.registry.upgrade() {
33949 let _ = registry.has_live_driver();
33950 self.reentered.store(true, Ordering::SeqCst);
33951 }
33952 }
33953 }
33954
33955 let metrics = Arc::new(RuntimeMetrics::default());
33956 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33957 let backend =
33958 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33959 let registry = Arc::new(TimerDriverRegistry::default());
33960 let starved = ManualStubDriver::new();
33961 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
33962
33963 let deadline = Instant::now() + Duration::from_secs(2);
33970 let mut first = make_sleep(&backend, ®istry, deadline);
33971 let mut second = make_sleep(&backend, ®istry, deadline);
33972 let first_wake = Arc::new(ReentrantTimerRegistrationWake {
33973 registry: Arc::downgrade(®istry),
33974 wakes: AtomicUsize::new(0),
33975 reentered: AtomicBool::new(false),
33976 });
33977 let second_wake = Arc::new(ReentrantTimerRegistrationWake {
33978 registry: Arc::downgrade(®istry),
33979 wakes: AtomicUsize::new(0),
33980 reentered: AtomicBool::new(false),
33981 });
33982 let first_waker = Waker::from(Arc::clone(&first_wake));
33983 let second_waker = Waker::from(Arc::clone(&second_wake));
33984 let mut first_cx = Context::from_waker(&first_waker);
33985 let mut second_cx = Context::from_waker(&second_waker);
33986
33987 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
33988 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
33989 let initial = starved.registers.lock().unwrap().clone();
33990 let first_id = initial[0].0;
33991 let second_id = initial[1].0;
33992 assert_ne!(first_id, second_id);
33993 assert_eq!(
33994 registry.pending_len(),
33995 2,
33996 "one stable repoll entry is retained per sleep"
33997 );
33998
33999 let responsive = Arc::new(StubHostTimerDriver::new());
34000 let (registered_tx, registered_rx) = mpsc::sync_channel(1);
34001 let registration_registry = Arc::clone(®istry);
34002 let registration_driver = Arc::clone(&responsive);
34003 let registration = std::thread::spawn(move || {
34004 let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
34005 registered_tx.send(id).unwrap();
34006 });
34007 registered_rx
34011 .recv_timeout(Duration::from_secs(10))
34012 .expect("late driver registration or repoll callback re-entry deadlocked");
34013 registration.join().unwrap();
34014
34015 assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
34016 assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
34017 assert!(first_wake.reentered.load(Ordering::SeqCst));
34018 assert!(second_wake.reentered.load(Ordering::SeqCst));
34019 assert!(
34020 responsive.timers.lock().unwrap().is_empty(),
34021 "registration requests bounded re-polls instead of invoking driver callbacks inline"
34022 );
34023
34024 assert!(
34029 Instant::now() < deadline,
34030 "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
34031 this runner is starved beyond the margin this test budgets for"
34032 );
34033 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
34034 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
34035 let responsive_ids = responsive
34036 .timers
34037 .lock()
34038 .unwrap()
34039 .keys()
34040 .copied()
34041 .collect::<FxHashSet<_>>();
34042 assert_eq!(
34043 responsive_ids,
34044 FxHashSet::from_iter([first_id, second_id]),
34045 "every pending sleep must re-arm its existing identity on the new driver"
34046 );
34047 assert_eq!(
34048 starved
34049 .registers
34050 .lock()
34051 .unwrap()
34052 .iter()
34053 .filter(|(id, _)| *id == first_id)
34054 .count(),
34055 2,
34056 "the old host receives only the normal waker refresh on the one re-poll"
34057 );
34058 assert_eq!(
34059 starved
34060 .registers
34061 .lock()
34062 .unwrap()
34063 .iter()
34064 .filter(|(id, _)| *id == second_id)
34065 .count(),
34066 2,
34067 "the old host receives only the normal waker refresh on the one re-poll"
34068 );
34069 assert_eq!(
34070 registry.pending_len(),
34071 2,
34072 "late registration must not duplicate pending state"
34073 );
34074
34075 let fire_bound = Instant::now() + Duration::from_secs(30);
34082 while first_wake.wakes.load(Ordering::SeqCst) < 2
34083 || second_wake.wakes.load(Ordering::SeqCst) < 2
34084 {
34085 assert!(
34086 Instant::now() < fire_bound,
34087 "the late responsive host must fire both timers while the original host remains starved"
34088 );
34089 std::thread::sleep(Duration::from_millis(10));
34090 }
34091 assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
34092 assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
34093
34094 let mut cancelled = starved.cancels.lock().unwrap().clone();
34095 cancelled.sort_unstable();
34096 let mut expected = vec![first_id, second_id];
34097 expected.sort_unstable();
34098 assert_eq!(
34099 cancelled, expected,
34100 "completion must cancel both losing starved-host arms"
34101 );
34102 assert_eq!(
34103 registry.pending_len(),
34104 0,
34105 "completion retires every repoll entry"
34106 );
34107 assert!(!executor.host_timers.has_pending());
34108 }
34109
34110 #[test]
34111 fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
34112 let metrics = Arc::new(RuntimeMetrics::default());
34116 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34117 let backend =
34118 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34119
34120 let a = ManualStubDriver::new();
34121 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
34122 let mut sleep = std::pin::pin!(make_sleep(
34123 &backend,
34124 ®istry,
34125 Instant::now() + Duration::from_secs(60)
34126 ));
34127 let mut cx = Context::from_waker(Waker::noop());
34128 assert!(sleep.as_mut().poll(&mut cx).is_pending());
34129
34130 a.kill();
34131 let payload = catch_unwind(AssertUnwindSafe(|| {
34132 let _ = sleep.as_mut().poll(&mut cx);
34133 }))
34134 .expect_err("a sleep with every driver dead must panic, not pend silently");
34135 let message = payload
34136 .downcast_ref::<String>()
34137 .cloned()
34138 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
34139 .expect("panic payload must be a message");
34140 assert!(
34141 message.contains("lost every live timer driver mid-sleep"),
34142 "the diagnostic must name the mid-flight driver loss, got: {message}"
34143 );
34144 }
34145
34146 #[test]
34147 fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
34148 let metrics = Arc::new(RuntimeMetrics::default());
34155 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34156 let backend =
34157 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34158
34159 let a = ManualStubDriver::new();
34160 let b = ManualStubDriver::new();
34161 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
34162 let mut sleep = std::pin::pin!(make_sleep(
34163 &backend,
34164 ®istry,
34165 Instant::now() + Duration::from_secs(60)
34166 ));
34167
34168 let flag = Arc::new(WakeFlag::default());
34170 let waker = Waker::from(Arc::clone(&flag));
34171 let mut cx = Context::from_waker(&waker);
34172 assert!(sleep.as_mut().poll(&mut cx).is_pending());
34173 assert!(!flag.0.load(Ordering::SeqCst));
34174
34175 a.kill();
34178 registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
34179 let _ = registry.has_live_driver();
34180
34181 assert!(
34182 a.swept.load(Ordering::SeqCst),
34183 "the sweep must notify the swept driver"
34184 );
34185 assert!(
34186 flag.0.load(Ordering::SeqCst),
34187 "a sleep armed on the swept driver must be woken into re-selection, not stranded"
34188 );
34189
34190 assert!(sleep.as_mut().poll(&mut cx).is_pending());
34192 assert_eq!(
34193 b.registers.lock().unwrap().len(),
34194 1,
34195 "the re-poll must re-arm on the live driver"
34196 );
34197 }
34198
34199 #[test]
34200 fn current_thread_sleep_fires_through_stub_host_driver() {
34201 use std::sync::mpsc;
34204
34205 let (done_tx, done_rx) = mpsc::channel();
34206 let runner = std::thread::spawn(move || {
34207 let metrics = Arc::new(RuntimeMetrics::default());
34208 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34209 let backend =
34210 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34211 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34212
34213 let started = Instant::now();
34214 let mut future = std::pin::pin!(async {
34215 make_sleep(
34216 &backend,
34217 ®istry_with(driver),
34218 Instant::now() + Duration::from_millis(60),
34219 )
34220 .await;
34221 });
34222 executor.block_on(future.as_mut());
34223 assert!(started.elapsed() >= Duration::from_millis(60));
34224 assert!(
34225 !executor.host_timers.has_pending(),
34226 "a completed sleep must retire its pending-registry entry"
34227 );
34228 let _ = done_tx.send(());
34229 });
34230
34231 match done_rx.recv_timeout(Duration::from_secs(10)) {
34232 Ok(()) => runner.join().unwrap(),
34233 Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
34234 }
34235 }
34236
34237 #[test]
34238 fn current_thread_timer_shutdown_retires_pending_repoll_state() {
34239 let metrics = Arc::new(RuntimeMetrics::default());
34240 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34241 let backend =
34242 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34243 let drivers = Arc::new(TimerDriverRegistry::default());
34244 let driver = ManualStubDriver::new();
34245 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
34246 let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
34247 let mut cx = Context::from_waker(Waker::noop());
34248
34249 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34250 let timer_id = driver.registers.lock().unwrap()[0].0;
34251 assert_eq!(drivers.pending_len(), 1);
34252
34253 executor.host_timers.shutdown();
34254 assert_eq!(
34255 drivers.pending_len(),
34256 0,
34257 "shutdown must retire the registration-repoll index before the Sleep is dropped"
34258 );
34259 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
34260 assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
34261 }
34262
34263 #[test]
34264 fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
34265 use std::sync::mpsc;
34266
34267 let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
34268 let backend = controller.backend();
34269 let drivers = Arc::new(TimerDriverRegistry::default());
34270 let driver = ManualStubDriver::new();
34271 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
34272 let sleep = make_sleep(
34273 &backend,
34274 &drivers,
34275 Instant::now() + Duration::from_secs(120),
34276 );
34277 drop(backend);
34278
34279 let (polled_tx, polled_rx) = mpsc::channel();
34280 let block_controller = Arc::clone(&controller);
34281 let blocker = std::thread::spawn(move || {
34282 let mut announced = false;
34283 let mut sleep = std::pin::pin!(sleep);
34284 let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
34285 let poll = sleep.as_mut().poll(cx);
34286 if !announced {
34287 announced = true;
34288 polled_tx.send(()).unwrap();
34289 }
34290 poll
34291 }));
34292 block_controller.block_on(future.as_mut());
34293 });
34294 polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34295
34296 let (shutdown_tx, shutdown_rx) = mpsc::channel();
34297 let shutdown_controller = Arc::clone(&controller);
34298 let shutdown = std::thread::spawn(move || {
34299 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
34300 });
34301 shutdown_rx
34302 .recv_timeout(Duration::from_secs(2))
34303 .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
34304 .unwrap();
34305 shutdown.join().unwrap();
34306 blocker.join().unwrap();
34307 assert_eq!(driver.cancels.lock().unwrap().len(), 1);
34308 }
34309
34310 #[test]
34311 fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
34312 use std::sync::mpsc;
34323
34324 let (done_tx, done_rx) = mpsc::channel();
34325 let runner = std::thread::spawn(move || {
34326 let metrics = Arc::new(RuntimeMetrics::default());
34327 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
34331 let backend =
34332 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34333 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34334
34335 let payload = catch_unwind(AssertUnwindSafe(|| {
34336 let mut future = std::pin::pin!(async {
34337 make_sleep(
34338 &backend,
34339 ®istry_with(driver),
34340 Instant::now() + Duration::from_millis(60),
34341 )
34342 .await;
34343 });
34344 executor.block_on(future.as_mut());
34345 }))
34346 .expect_err("a threadless park must self-detect even while a host timer is pending");
34347 let diagnostic = payload
34348 .downcast_ref::<BlockOnDeadlock>()
34349 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
34350 assert_eq!(
34351 diagnostic.kind,
34352 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
34353 );
34354 assert_eq!(
34355 diagnostic.park_deadline, None,
34356 "the certain case carries no deadline"
34357 );
34358 let message = diagnostic.to_string();
34359 assert!(
34360 message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
34361 "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
34362 );
34363 let _ = done_tx.send(());
34364 });
34365
34366 match done_rx.recv_timeout(Duration::from_secs(10)) {
34367 Ok(()) => runner.join().unwrap(),
34368 Err(error) => panic!(
34369 "a pending host timer suppressed the threadless certain-deadlock diagnostic -- block_on parked (or completed via a helper-thread fire that cannot exist on the real target) instead of panicking ({error})"
34370 ),
34371 }
34372 }
34373
34374 #[test]
34375 fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
34376 use std::sync::mpsc;
34385
34386 let (done_tx, done_rx) = mpsc::channel();
34387 let runner = std::thread::spawn(move || {
34388 let metrics = Arc::new(RuntimeMetrics::default());
34389 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
34390 let backend =
34391 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34392 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34393
34394 let _unpolled = make_sleep(
34397 &backend,
34398 ®istry_with(driver),
34399 Instant::now() + Duration::from_secs(60),
34400 );
34401
34402 let payload = catch_unwind(AssertUnwindSafe(|| {
34403 let mut future = std::pin::pin!(NeverReady);
34404 executor.block_on(future.as_mut());
34405 }))
34406 .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
34407 let diagnostic = payload
34408 .downcast_ref::<BlockOnDeadlock>()
34409 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
34410 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
34411 let _ = done_tx.send(());
34412 });
34413
34414 match done_rx.recv_timeout(Duration::from_secs(10)) {
34415 Ok(()) => runner.join().unwrap(),
34416 Err(error) => panic!(
34417 "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
34418 ),
34419 }
34420 }
34421
34422 #[cfg(not(target_family = "wasm"))]
34423 #[test]
34424 fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
34425 use std::sync::mpsc;
34430
34431 let (done_tx, done_rx) = mpsc::channel();
34432 let runner = std::thread::spawn(move || {
34433 let metrics = Arc::new(RuntimeMetrics::default());
34434 let executor = Arc::new(CurrentThreadExecutor::with_detection(
34446 metrics,
34447 false,
34448 Some(Duration::from_secs(2)),
34449 ));
34450 let backend =
34451 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34452 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34453
34454 let started = Instant::now();
34455 let mut future = std::pin::pin!(async {
34456 make_sleep(
34457 &backend,
34458 ®istry_with(driver),
34459 Instant::now() + Duration::from_millis(200),
34460 )
34461 .await;
34462 });
34463 executor.block_on(future.as_mut());
34464 assert!(started.elapsed() >= Duration::from_millis(200));
34465 let _ = done_tx.send(());
34466 });
34467
34468 match done_rx.recv_timeout(Duration::from_secs(30)) {
34469 Ok(()) => runner.join().unwrap(),
34470 Err(error) => {
34471 panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
34472 }
34473 }
34474 }
34475
34476 #[test]
34477 fn host_timer_unrepresentable_grace_remains_live() {
34478 let registry = HostTimerRegistry::default();
34479 let fired = Arc::new(AtomicBool::new(false));
34480 let drivers = Arc::new(TimerDriverRegistry::default());
34481 let repoll = Arc::new(HostTimerRepoll::default());
34482 assert!(
34483 Instant::now().checked_add(Duration::MAX).is_none(),
34484 "Duration::MAX must exercise the unrepresentable-grace path"
34485 );
34486 assert!(
34487 registry.register(
34488 1,
34489 Instant::now(),
34490 futures::task::noop_waker(),
34491 &fired,
34492 &drivers,
34493 &repoll,
34494 ),
34495 "the open registry must accept the timer"
34496 );
34497
34498 assert!(
34499 registry.has_live_wait(Duration::MAX),
34500 "an unrepresentable grace deadline is effectively unbounded"
34501 );
34502 registry.remove(1);
34503 }
34504
34505 #[test]
34506 fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
34507 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
34512 let expected = diagnostic.to_string();
34513 let payload: Box<dyn Any + Send> = Box::new(diagnostic);
34514 assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
34515 }
34516}