1use std::{
5 any::Any,
6 collections::VecDeque,
7 fmt,
8 future::Future,
9 mem::ManuallyDrop,
10 panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
11 pin::Pin,
12 sync::{
13 Arc, Condvar, LazyLock, Mutex, Weak,
14 atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
15 },
16 task::{Context, Poll, Waker},
17 time::{Duration, Instant},
18};
19
20use arc_swap::ArcSwapOption;
21use rustc_hash::{FxHashMap, FxHashSet};
22
23use crate::MAX_ASYNC_RUNTIME_WORKER_THREADS;
24#[cfg(test)]
25use async_task::Task;
26use async_task::{FallibleTask, Runnable};
27use futures::{
28 FutureExt,
29 future::{AbortHandle, AbortRegistration, Abortable},
30};
31
32#[cfg(not(target_family = "wasm"))]
33use crossbeam_deque::{Injector, Steal};
34#[cfg(not(target_family = "wasm"))]
35use futures::channel::oneshot;
36#[cfg(all(test, not(target_family = "wasm")))]
37use rayon::max_num_threads;
38#[cfg(not(target_family = "wasm"))]
39use rayon::{ThreadPool, ThreadPoolBuilder};
40
41fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
44 counter
45 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
46 .expect(exhausted)
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum RuntimeFlavor {
51 CurrentThread,
52 MultiThread,
53}
54
55#[derive(Debug, Clone)]
56pub struct RuntimeOptions {
57 pub flavor: RuntimeFlavor,
58 pub worker_threads: usize,
59 pub max_blocking_tasks: usize,
60 pub thread_name_prefix: String,
61 pub park_deadline: Option<Duration>,
74 pub drain_linger: Duration,
89}
90
91#[derive(Debug, Clone, Copy, Default)]
98pub struct RuntimeOptionsPatch {
99 pub flavor: Option<RuntimeFlavor>,
100 pub worker_threads: Option<usize>,
101 pub max_blocking_tasks: Option<usize>,
102 pub drain_linger: Option<Duration>,
105}
106
107impl RuntimeOptionsPatch {
108 fn apply_to(self, options: &mut RuntimeOptions) {
109 if let Some(flavor) = self.flavor {
110 options.flavor = flavor;
111 }
112 if let Some(worker_threads) = self.worker_threads {
113 options.worker_threads = worker_threads;
114 }
115 if let Some(max_blocking_tasks) = self.max_blocking_tasks {
116 options.max_blocking_tasks = max_blocking_tasks;
117 }
118 if let Some(drain_linger) = self.drain_linger {
119 options.drain_linger = drain_linger;
120 }
121 }
122}
123
124impl Default for RuntimeOptions {
125 fn default() -> Self {
126 let worker_threads = std::thread::available_parallelism()
127 .map_or(1, usize::from)
128 .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
129 Self {
130 flavor: if cfg!(target_family = "wasm") {
131 RuntimeFlavor::CurrentThread
132 } else {
133 RuntimeFlavor::MultiThread
134 },
135 worker_threads,
136 max_blocking_tasks: worker_threads,
137 thread_name_prefix: "napi-async-runtime".to_string(),
138 park_deadline: None,
139 drain_linger: DEFAULT_DRAIN_LINGER,
140 }
141 }
142}
143
144impl RuntimeOptions {
145 fn validate(self) -> Result<Self, RuntimeConfigError> {
146 #[cfg(target_family = "wasm")]
147 let rayon_max_threads = None;
148 #[cfg(not(target_family = "wasm"))]
149 let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
150 self.validate_with_rayon_max_threads(rayon_max_threads)
151 }
152
153 fn validate_with_rayon_max_threads(
154 mut self,
155 rayon_max_threads: Option<usize>,
156 ) -> Result<Self, RuntimeConfigError> {
157 if self.worker_threads == 0 {
158 return Err(RuntimeConfigError(
159 "worker_threads must be greater than zero".to_string(),
160 ));
161 }
162 if self.max_blocking_tasks == 0 {
163 return Err(RuntimeConfigError(
164 "max_blocking_tasks must be greater than zero".to_string(),
165 ));
166 }
167 self.drain_linger = self.drain_linger.min(MAX_DRAIN_LINGER);
174 if self.flavor == RuntimeFlavor::CurrentThread {
175 self.worker_threads = 1;
176 self.max_blocking_tasks = 1;
177 } else {
178 let Some(rayon_max_threads) = rayon_max_threads else {
179 return Err(RuntimeConfigError(
180 "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
181 ));
182 };
183 if rayon_max_threads < 2 {
184 return Err(RuntimeConfigError(
185 "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
186 ));
187 }
188 self.worker_threads = self
196 .worker_threads
197 .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
198 .min(rayon_max_threads);
199 let blocking_capacity = self.worker_threads - 1;
200 self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
201 }
202 Ok(self)
203 }
204}
205
206pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
215
216pub const DRAIN_LINGER_ENV: &str = "NAPI_RUNTIME_DRAIN_LINGER_US";
240
241pub const DEFAULT_DRAIN_LINGER_MICROS: u64 = 500;
245
246pub const MAX_DRAIN_LINGER_MICROS: u64 = 100_000;
253
254const DEFAULT_DRAIN_LINGER: Duration = Duration::from_micros(DEFAULT_DRAIN_LINGER_MICROS);
256
257const MAX_DRAIN_LINGER: Duration = Duration::from_micros(MAX_DRAIN_LINGER_MICROS);
259
260#[cfg(not(target_family = "wasm"))]
274const DRAIN_LINGER_FRAME_FACTOR: u32 = 16;
275
276#[cfg(not(target_family = "wasm"))]
279const MAX_DRAIN_LINGER_FRAME_RESIDENCE: Duration = Duration::from_secs(60);
280
281const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
297
298#[derive(Debug, Clone)]
299pub struct RuntimeConfigError(String);
300
301impl fmt::Display for RuntimeConfigError {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 f.write_str(&self.0)
304 }
305}
306
307impl std::error::Error for RuntimeConfigError {}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum BlockOnDeadlockKind {
312 CurrentThreadCertain,
317 CurrentThreadCertainTimerBacked,
324 CurrentThreadDeadline,
327 MultiThreadCooperativeDeadline,
331}
332
333#[derive(Debug, Clone)]
341pub struct BlockOnDeadlock {
342 pub kind: BlockOnDeadlockKind,
343 pub park_deadline: Option<Duration>,
346}
347
348impl BlockOnDeadlock {
349 fn current_thread_certain() -> Self {
350 Self {
351 kind: BlockOnDeadlockKind::CurrentThreadCertain,
352 park_deadline: None,
353 }
354 }
355
356 fn current_thread_certain_timer_backed() -> Self {
357 Self {
358 kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
359 park_deadline: None,
360 }
361 }
362
363 fn current_thread_deadline(deadline: Duration) -> Self {
364 Self {
365 kind: BlockOnDeadlockKind::CurrentThreadDeadline,
366 park_deadline: Some(deadline),
367 }
368 }
369
370 #[cfg(not(target_family = "wasm"))]
373 fn multi_thread_cooperative(deadline: Duration) -> Self {
374 Self {
375 kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
376 park_deadline: Some(deadline),
377 }
378 }
379}
380
381impl fmt::Display for BlockOnDeadlock {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 let deadline_ms = self
384 .park_deadline
385 .map_or(0, |deadline| deadline.as_millis());
386 match self.kind {
387 BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
388 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
389 park with an empty task queue and no pending wake on a threadless build -- no other \
390 thread exists that could ever deliver the wake, so this park would never end. This is \
391 the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
392 (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
393 dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
394 continuation is the one now parking.",
395 ),
396 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
397 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
398 park with an empty task queue on a threadless build while a host timer is pending. \
399 The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
400 only run on the very thread that is now parking, so it can never fire, and no other \
401 thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
402 hazard: native code called `block_on` on a future that (transitively) awaits a JS \
403 continuation, but the only thread that could run that continuation is the one now \
404 parking.",
405 ),
406 BlockOnDeadlockKind::CurrentThreadDeadline => write!(
407 f,
408 "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
409 {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
410 task queue, no pending wake and ZERO runtime progress across the whole window. This is \
411 the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
412 awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
413 once this thread returns to the event loop. If this park is legitimate, raise or unset \
414 the deadline.",
415 ),
416 BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
417 f,
418 "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
419 parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
420 no runnable or blocking work available and ZERO executor progress across the whole \
421 window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
422 The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
423 If this park is legitimate, raise or unset the deadline.",
424 ),
425 }
426 }
427}
428
429impl std::error::Error for BlockOnDeadlock {}
430
431#[derive(Debug)]
432pub struct JoinError {
433 message: String,
434}
435
436impl JoinError {
437 fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
438 Self {
439 message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
443 deadlock.to_string()
444 } else if let Some(message) = panic.downcast_ref::<&str>() {
445 (*message).to_string()
446 } else if let Some(message) = panic.downcast_ref::<String>() {
447 message.clone()
448 } else {
449 "async runtime task panicked".to_string()
450 },
451 }
452 }
453}
454
455impl fmt::Display for JoinError {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 f.write_str(&self.message)
458 }
459}
460
461impl std::error::Error for JoinError {}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464enum BlockOnOutcome {
465 Completed,
466 Stopped,
467}
468
469fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
470 if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
475 std::mem::forget(nested_payload);
476 }
477}
478
479#[derive(Default)]
480struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
481
482impl RetainedPanicPayload {
483 fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
484 if self.0.is_some() {
485 discard_panic_payload(payload);
486 } else {
487 self.0 = Some(payload);
488 }
489 }
490
491 fn is_some(&self) -> bool {
492 self.0.is_some()
493 }
494
495 fn resume(mut self) -> ! {
496 resume_unwind(
497 self
498 .0
499 .take()
500 .expect("a retained panic payload must remain owned"),
501 )
502 }
503}
504
505impl Drop for RetainedPanicPayload {
506 fn drop(&mut self) {
507 if let Some(payload) = self.0.take() {
508 discard_panic_payload(payload);
509 }
510 }
511}
512
513fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
514 match catch_unwind(AssertUnwindSafe(function)) {
515 Ok(value) => Some(value),
516 Err(payload) => {
517 discard_panic_payload(payload);
518 None
519 }
520 }
521}
522
523struct ContainedArc<T: ?Sized> {
524 value: Option<Arc<T>>,
525}
526
527impl<T: ?Sized> ContainedArc<T> {
528 fn new(value: Arc<T>) -> Self {
529 Self { value: Some(value) }
530 }
531
532 fn get(&self) -> &T {
533 self
534 .value
535 .as_deref()
536 .expect("the contained Arc must remain owned")
537 }
538
539 #[cfg(test)]
540 fn into_inner(mut self) -> Arc<T> {
541 self
542 .value
543 .take()
544 .expect("the contained Arc must remain owned")
545 }
546}
547
548impl<T: ?Sized> Drop for ContainedArc<T> {
549 fn drop(&mut self) {
550 if let Some(value) = self.value.take() {
551 let _ = catch_unwind_contained(|| drop(value));
552 }
553 }
554}
555
556struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
557 values: Vec<(I, Arc<T>)>,
558}
559
560impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
561 fn new(values: Vec<(I, Arc<T>)>) -> Self {
562 Self { values }
563 }
564
565 fn as_slice(&self) -> &[(I, Arc<T>)] {
566 &self.values
567 }
568
569 fn len(&self) -> usize {
570 self.values.len()
571 }
572
573 fn is_empty(&self) -> bool {
574 self.values.is_empty()
575 }
576
577 fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
578 self
579 .values
580 .pop()
581 .map(|(id, value)| (id, ContainedArc::new(value)))
582 }
583}
584
585impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
586 fn drop(&mut self) {
587 while let Some((_, value)) = self.values.pop() {
588 let _ = catch_unwind_contained(|| drop(value));
589 }
590 }
591}
592
593struct PanicContainedWaker {
594 waker: ManuallyDrop<Waker>,
595 generation: Option<u64>,
596}
597
598impl std::task::Wake for PanicContainedWaker {
599 fn wake(self: Arc<Self>) {
600 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
601 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
602 }
603
604 fn wake_by_ref(self: &Arc<Self>) {
605 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
606 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
607 }
608}
609
610impl Drop for PanicContainedWaker {
611 fn drop(&mut self) {
612 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
613 let _ = catch_unwind_contained(|| {
614 unsafe { ManuallyDrop::drop(&mut self.waker) };
616 });
617 }
618}
619
620fn panic_contained_waker(waker: Waker) -> Waker {
621 Waker::from(Arc::new(PanicContainedWaker {
622 waker: ManuallyDrop::new(waker),
623 generation: active_runtime_generation(),
624 }))
625}
626
627struct PanicContainedWakerSource {
628 waker: ManuallyDrop<Waker>,
629 generation: Option<u64>,
630}
631
632impl PanicContainedWakerSource {
633 fn new(waker: Waker) -> Self {
634 Self {
635 waker: ManuallyDrop::new(waker),
636 generation: active_runtime_generation(),
637 }
638 }
639
640 fn as_waker(&self) -> &Waker {
641 &self.waker
642 }
643}
644
645impl Drop for PanicContainedWakerSource {
646 fn drop(&mut self) {
647 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
648 let _ = catch_unwind_contained(|| {
649 unsafe { ManuallyDrop::drop(&mut self.waker) };
651 });
652 }
653}
654
655struct PanicContainedWakerCache {
656 source: PanicContainedWakerSource,
657 contained: Waker,
658}
659
660impl PanicContainedWakerCache {
661 fn new(waker: &Waker) -> Self {
662 let generation = active_runtime_generation();
663 let _generation = RuntimeGenerationGuard::enter_optional(generation);
664 let source = PanicContainedWakerSource::new(waker.clone());
665 let contained = panic_contained_waker(source.as_waker().clone());
666 Self { source, contained }
667 }
668
669 fn update(&mut self, waker: &Waker) {
670 let will_wake = {
671 let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
672 self.source.as_waker().will_wake(waker)
673 };
674 if !will_wake {
675 *self = Self::new(waker);
676 }
677 }
678
679 fn contained(&self) -> &Waker {
680 &self.contained
681 }
682}
683
684fn wake_waker_contained(waker: Waker) {
685 let _ = catch_unwind_contained(|| waker.wake_by_ref());
690 let _ = catch_unwind_contained(|| drop(waker));
691}
692
693fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
694 match catch_unwind(AssertUnwindSafe(function)) {
695 Ok(value) => Ok(value),
696 Err(payload) => {
697 let error = JoinError::from_panic(&*payload);
698 discard_panic_payload(payload);
699 Err(error)
700 }
701 }
702}
703
704fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
705 let error = JoinError::from_panic(&*payload);
706 discard_panic_payload(payload);
707 error
708}
709
710struct ContainedTaskOutput<T> {
711 output: ManuallyDrop<T>,
712 generation: u64,
713}
714
715impl<T> ContainedTaskOutput<T> {
716 fn new(output: T, generation: u64) -> Self {
717 Self {
718 output: ManuallyDrop::new(output),
719 generation,
720 }
721 }
722
723 fn into_inner(mut self) -> T {
724 let output = unsafe { ManuallyDrop::take(&mut self.output) };
727 std::mem::forget(self);
728 output
729 }
730}
731
732impl<T> Drop for ContainedTaskOutput<T> {
733 fn drop(&mut self) {
734 let _generation = RuntimeGenerationGuard::enter(self.generation);
735 let _ = catch_unwind_contained(|| {
736 unsafe { ManuallyDrop::drop(&mut self.output) };
739 });
740 }
741}
742
743#[cfg(not(target_family = "wasm"))]
744#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
745struct BlockingJobId(u64);
746
747#[cfg(not(target_family = "wasm"))]
748static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
749
750#[cfg(all(test, not(target_family = "wasm")))]
751thread_local! {
752 static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
753 const { std::cell::RefCell::new(None) };
754 static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
755 const { std::cell::RefCell::new(None) };
756 static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
757 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
758 const { std::cell::RefCell::new(None) };
759 static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
760 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
761 const { std::cell::RefCell::new(None) };
762 static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
763 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
764 const { std::cell::RefCell::new(None) };
765 static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
766 const { std::cell::RefCell::new(None) };
767 static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
768 const { std::cell::RefCell::new(None) };
769 static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
770 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
771 const { std::cell::RefCell::new(None) };
772 static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
773 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
774 const { std::cell::RefCell::new(None) };
775 static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
776 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
777 const { std::cell::RefCell::new(None) };
778 static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
779 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
780 const { std::cell::RefCell::new(None) };
781 static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
782 const { std::cell::RefCell::new(None) };
783 static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
784 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
785 const { std::cell::RefCell::new(None) };
786 static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
787 const { std::cell::RefCell::new(None) };
788}
789
790#[cfg(all(test, not(target_family = "wasm")))]
791struct WorkerTlsDropProbe {
792 action: Option<WorkerTlsDropAction>,
793}
794
795#[cfg(all(test, not(target_family = "wasm")))]
809enum WorkerTlsDropAction {
810 Record { completed: Arc<AtomicUsize> },
814 ReenterLifecycle {
815 controller: Arc<RuntimeController>,
816 completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
817 },
818}
819
820#[cfg(all(test, not(target_family = "wasm")))]
821impl Drop for WorkerTlsDropProbe {
822 fn drop(&mut self) {
823 match self
824 .action
825 .take()
826 .expect("worker TLS drop probe action already consumed")
827 {
828 WorkerTlsDropAction::Record { completed } => {
829 completed.fetch_add(1, Ordering::SeqCst);
830 }
831 WorkerTlsDropAction::ReenterLifecycle {
836 controller,
837 completed,
838 } => {
839 let start = controller.start().map_err(|error| error.to_string());
840 let shutdown = controller.shutdown().map_err(|error| error.to_string());
841 let _ = completed.send((start, shutdown));
842 }
843 }
844 }
845}
846
847#[cfg(all(test, not(target_family = "wasm")))]
848fn run_dependency_claim_test_hook() {
849 if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
850 hook();
851 }
852}
853
854#[cfg(all(test, not(target_family = "wasm")))]
855fn run_current_thread_dispatch_claim_test_hook() {
856 if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
857 {
858 hook();
859 }
860}
861
862#[cfg(all(test, not(target_family = "wasm")))]
863fn run_current_thread_dispatch_publication_blocked_test_hook() {
864 if let Some(hook) =
865 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
866 {
867 hook();
868 }
869}
870
871#[cfg(all(test, not(target_family = "wasm")))]
872fn run_current_thread_dispatch_recovery_test_hook() {
873 if let Some(hook) =
874 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
875 {
876 hook();
877 }
878}
879
880#[cfg(all(test, not(target_family = "wasm")))]
881fn run_current_thread_failed_publication_retired_test_hook() {
882 if let Some(hook) =
883 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
884 {
885 hook();
886 }
887}
888
889#[cfg(all(test, not(target_family = "wasm")))]
890fn run_current_thread_dispatch_call_test_hook() {
891 if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
892 hook();
893 }
894}
895
896#[cfg(all(test, not(target_family = "wasm")))]
897fn run_current_thread_dispatch_returned_test_hook() {
898 if let Some(hook) =
899 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
900 {
901 hook();
902 }
903}
904
905#[cfg(all(test, not(target_family = "wasm")))]
906fn run_current_thread_blocking_claim_test_hook() {
907 if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
908 {
909 hook();
910 }
911}
912
913#[cfg(all(test, not(target_family = "wasm")))]
914fn run_current_thread_runnable_claim_test_hook() {
915 if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
916 {
917 hook();
918 }
919}
920
921#[cfg(all(test, not(target_family = "wasm")))]
922fn run_multi_thread_work_claimed_test_hook() {
923 if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
924 hook();
925 }
926}
927
928#[cfg(all(test, not(target_family = "wasm")))]
929fn run_multi_thread_work_started_test_hook() {
930 if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
931 hook();
932 }
933}
934
935#[cfg(all(test, not(target_family = "wasm")))]
936fn run_dependency_predicate_test_hook() {
937 if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
938 hook();
939 }
940}
941
942#[cfg(all(test, not(target_family = "wasm")))]
943fn run_before_owned_blocking_queue_claim_test_hook() {
944 if let Some(hook) =
945 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
946 {
947 hook();
948 }
949}
950
951#[cfg(not(target_family = "wasm"))]
952#[derive(Clone, Copy, Debug, PartialEq, Eq)]
953struct BlockingDependency {
954 job: BlockingJobId,
955 owner: Option<BlockingOwnerToken>,
956}
957
958#[cfg(not(target_family = "wasm"))]
959#[derive(Debug)]
960struct DependencyClaim {
961 claimed: AtomicBool,
962 transition: Mutex<()>,
963}
964
965#[cfg(not(target_family = "wasm"))]
966impl DependencyClaim {
967 fn new() -> Self {
968 Self {
969 claimed: AtomicBool::new(false),
970 transition: Mutex::new(()),
971 }
972 }
973
974 fn is_live(&self) -> bool {
975 !self.claimed.load(Ordering::Acquire)
976 }
977
978 fn cancel_link(&self, link: &DependencyLink) {
979 let _transition = self
980 .transition
981 .lock()
982 .unwrap_or_else(std::sync::PoisonError::into_inner);
983 link.cancel();
984 }
985
986 fn try_claim(&self, link: &DependencyLink) -> bool {
987 let _transition = self
988 .transition
989 .lock()
990 .unwrap_or_else(std::sync::PoisonError::into_inner);
991 #[cfg(test)]
992 run_dependency_claim_test_hook();
993 if self.claimed.load(Ordering::Acquire) || !link.is_live() {
994 return false;
995 }
996 self.claimed.store(true, Ordering::Release);
997 true
998 }
999}
1000
1001#[cfg(not(target_family = "wasm"))]
1002#[derive(Debug)]
1003struct DependencyLink {
1004 live: AtomicBool,
1005 parent: Option<Weak<DependencyLink>>,
1006}
1007
1008#[cfg(not(target_family = "wasm"))]
1009impl DependencyLink {
1010 fn root() -> Arc<Self> {
1011 Arc::new(Self {
1012 live: AtomicBool::new(true),
1013 parent: None,
1014 })
1015 }
1016
1017 fn derived(parent: &Arc<Self>) -> Arc<Self> {
1018 Arc::new(Self {
1019 live: AtomicBool::new(true),
1020 parent: Some(Arc::downgrade(parent)),
1021 })
1022 }
1023
1024 fn is_live(&self) -> bool {
1025 if !self.live.load(Ordering::Acquire) {
1026 return false;
1027 }
1028 let Some(parent) = &self.parent else {
1029 return true;
1030 };
1031 let Some(mut current) = parent.upgrade() else {
1032 return false;
1033 };
1034 loop {
1035 if !current.live.load(Ordering::Acquire) {
1036 return false;
1037 }
1038 let Some(parent) = ¤t.parent else {
1039 return true;
1040 };
1041 let Some(next) = parent.upgrade() else {
1042 return false;
1043 };
1044 current = next;
1045 }
1046 }
1047
1048 fn cancel(&self) {
1049 self.live.store(false, Ordering::Release);
1050 }
1051}
1052
1053#[cfg(not(target_family = "wasm"))]
1054#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1055struct DependencyPublicationIdentity {
1056 job: BlockingJobId,
1057 claim: *const DependencyClaim,
1058 link: *const DependencyLink,
1059}
1060
1061#[cfg(not(target_family = "wasm"))]
1062#[derive(Clone, Debug)]
1063struct DependencyPublication {
1064 dependency: BlockingDependency,
1065 claim: Arc<DependencyClaim>,
1066 link: Arc<DependencyLink>,
1067}
1068
1069#[cfg(not(target_family = "wasm"))]
1070impl DependencyPublication {
1071 fn owned(dependency: BlockingDependency) -> Self {
1072 Self {
1073 dependency,
1074 claim: Arc::new(DependencyClaim::new()),
1075 link: DependencyLink::root(),
1076 }
1077 }
1078
1079 fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1080 Self {
1081 dependency: BlockingDependency {
1082 owner: self.dependency.owner.or(owner),
1083 ..self.dependency
1084 },
1085 claim: Arc::clone(&self.claim),
1086 link: DependencyLink::derived(&self.link),
1087 }
1088 }
1089
1090 fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1091 self.dependency.owner = self.dependency.owner.or(owner);
1092 self
1093 }
1094
1095 fn same_as(&self, other: &Self) -> bool {
1096 self.dependency == other.dependency
1097 && Arc::ptr_eq(&self.claim, &other.claim)
1098 && Arc::ptr_eq(&self.link, &other.link)
1099 }
1100
1101 fn identity(&self) -> DependencyPublicationIdentity {
1102 DependencyPublicationIdentity {
1103 job: self.dependency.job,
1104 claim: Arc::as_ptr(&self.claim),
1105 link: Arc::as_ptr(&self.link),
1106 }
1107 }
1108
1109 fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1110 Some(DependencyPublicationIdentity {
1111 job: self.dependency.job,
1112 claim: Arc::as_ptr(&self.claim),
1113 link: self.link.parent.as_ref()?.as_ptr(),
1114 })
1115 }
1116
1117 fn is_derived_from(&self, source: &Self) -> bool {
1118 self.immediate_source_identity() == Some(source.identity())
1119 }
1120
1121 fn is_live(&self) -> bool {
1122 self.claim.is_live() && self.link.is_live()
1123 }
1124
1125 fn cancel(&self) {
1126 self.claim.cancel_link(&self.link);
1127 }
1128
1129 fn try_claim(&self) -> bool {
1130 self.claim.try_claim(&self.link)
1131 }
1132}
1133
1134#[cfg(not(target_family = "wasm"))]
1135struct TaskDependencyEntry {
1136 sequence: u64,
1137 publication: DependencyPublication,
1138}
1139
1140#[cfg(not(target_family = "wasm"))]
1141struct SelectedDependencyPublication {
1142 sequence: u64,
1143 publication: DependencyPublication,
1144}
1145
1146#[cfg(not(target_family = "wasm"))]
1147#[derive(Clone, Copy)]
1148enum OwnerDependencyMode {
1149 Available,
1150 Exact,
1151}
1152
1153#[cfg(not(target_family = "wasm"))]
1154#[derive(Default)]
1155struct TaskDependencyState {
1156 current: VecDeque<TaskDependencyEntry>,
1157 next_sequence: u64,
1158 unowned_sequences: VecDeque<u64>,
1159 owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1160 waiter: Option<Waker>,
1170}
1171
1172#[cfg(not(target_family = "wasm"))]
1173impl TaskDependencyState {
1174 fn append(&mut self, publication: DependencyPublication) -> u64 {
1177 let sequence = self.next_sequence;
1178 self.next_sequence = self
1179 .next_sequence
1180 .checked_add(1)
1181 .expect("task dependency sequence space exhausted");
1182 match publication.dependency.owner {
1183 Some(owner) => self
1184 .owner_sequences
1185 .entry(owner)
1186 .or_default()
1187 .push_back(sequence),
1188 None => self.unowned_sequences.push_back(sequence),
1189 }
1190 self.current.push_back(TaskDependencyEntry {
1191 sequence,
1192 publication,
1193 });
1194 sequence
1195 }
1196
1197 fn entry_in(
1198 current: &VecDeque<TaskDependencyEntry>,
1199 sequence: u64,
1200 ) -> Option<&TaskDependencyEntry> {
1201 let front = current.front()?.sequence;
1202 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1203 let entry = current.get(index)?;
1204 (entry.sequence == sequence).then_some(entry)
1205 }
1206
1207 fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1208 Self::entry_in(&self.current, sequence)
1209 }
1210
1211 fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1212 let front = self.current.front()?.sequence;
1213 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1214 let entry = self.current.get_mut(index)?;
1215 (entry.sequence == sequence).then_some(entry)
1216 }
1217
1218 fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1219 match dependency.owner {
1220 Some(owner) => {
1221 let remove_owner = self
1222 .owner_sequences
1223 .get_mut(&owner)
1224 .is_some_and(|sequences| {
1225 if sequences.front() == Some(&sequence) {
1226 sequences.pop_front();
1227 }
1228 sequences.is_empty()
1229 });
1230 if remove_owner {
1231 self.owner_sequences.remove(&owner);
1232 }
1233 }
1234 None => {
1235 if self.unowned_sequences.front() == Some(&sequence) {
1236 self.unowned_sequences.pop_front();
1237 }
1238 }
1239 }
1240 }
1241
1242 fn clear_indexes(&mut self) {
1243 self.unowned_sequences.clear();
1244 self.owner_sequences.clear();
1245 }
1246}
1247
1248#[cfg(not(target_family = "wasm"))]
1249struct TaskDependency {
1250 state: Mutex<TaskDependencyState>,
1251 owner_hint: Option<BlockingOwnerToken>,
1252 has_current: AtomicBool,
1253 generation: u64,
1254 #[cfg(test)]
1255 entry_visits: AtomicUsize,
1256}
1257
1258#[cfg(not(target_family = "wasm"))]
1259impl Default for TaskDependency {
1260 fn default() -> Self {
1261 Self::new(u64::MAX, None)
1262 }
1263}
1264
1265#[cfg(not(target_family = "wasm"))]
1266impl TaskDependency {
1267 fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1268 Self {
1269 state: Mutex::new(TaskDependencyState::default()),
1270 owner_hint,
1271 has_current: AtomicBool::new(false),
1272 generation,
1273 #[cfg(test)]
1274 entry_visits: AtomicUsize::new(0),
1275 }
1276 }
1277
1278 fn new_task(generation: u64) -> Self {
1279 Self::new(generation, None)
1280 }
1281
1282 fn notify_waiter(&self, waiter: Option<Waker>) {
1283 if let Some(waiter) = waiter {
1284 let _generation = RuntimeGenerationGuard::enter(self.generation);
1285 wake_waker_contained(waiter);
1286 }
1287 }
1288
1289 fn clear_waiter(&self) {
1290 let waiter = self
1291 .state
1292 .lock()
1293 .unwrap_or_else(std::sync::PoisonError::into_inner)
1294 .waiter
1295 .take();
1296 if let Some(waiter) = waiter {
1297 let _generation = RuntimeGenerationGuard::enter(self.generation);
1298 let _ = catch_unwind_contained(|| drop(waiter));
1299 }
1300 }
1301
1302 fn clear(&self) {
1303 let waiter = {
1304 let mut state = self
1305 .state
1306 .lock()
1307 .unwrap_or_else(std::sync::PoisonError::into_inner);
1308 if state.current.is_empty() {
1309 None
1310 } else {
1311 for current in state.current.drain(..) {
1312 current.publication.cancel();
1313 }
1314 state.clear_indexes();
1315 self.has_current.store(false, Ordering::Release);
1316 state.waiter.take()
1317 }
1318 };
1319 self.notify_waiter(waiter);
1320 }
1321
1322 fn retire(&self, sequence: u64) {
1332 let waiter = {
1333 let mut state = self
1334 .state
1335 .lock()
1336 .unwrap_or_else(std::sync::PoisonError::into_inner);
1337 let cancelled = match state.entry_mut(sequence) {
1338 Some(current) => {
1339 self.note_entry_visit();
1340 current.publication.cancel();
1341 true
1342 }
1343 None => false,
1344 };
1345 if cancelled {
1346 self.prune_stale_prefix(&mut state);
1347 state.waiter.take()
1348 } else {
1349 None
1350 }
1351 };
1352 self.notify_waiter(waiter);
1353 }
1354
1355 fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1356 let Some(first_publication) = publications.first() else {
1357 return;
1358 };
1359 let publication_identities = (publications.len() > 1).then(|| {
1360 publications
1361 .iter()
1362 .map(DependencyPublication::identity)
1363 .collect::<FxHashSet<_>>()
1364 });
1365 let waiter = {
1366 let mut state = self
1367 .state
1368 .lock()
1369 .unwrap_or_else(std::sync::PoisonError::into_inner);
1370 let removed = state
1371 .current
1372 .iter_mut()
1373 .filter_map(|current| {
1374 self.note_entry_visit();
1375 let is_derived = publication_identities.as_ref().map_or_else(
1376 || current.publication.is_derived_from(first_publication),
1377 |identities| {
1378 current
1379 .publication
1380 .immediate_source_identity()
1381 .is_some_and(|identity| identities.contains(&identity))
1382 },
1383 );
1384 if !is_derived {
1385 return None;
1386 }
1387 current.publication.cancel();
1388 Some(current.sequence)
1389 })
1390 .collect::<Vec<_>>();
1391 if removed.is_empty() {
1394 None
1395 } else {
1396 self.prune_stale_prefix(&mut state);
1397 state.waiter.take()
1398 }
1399 };
1400 self.notify_waiter(waiter);
1401 }
1402
1403 fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1406 let publication = DependencyPublication::owned(BlockingDependency {
1407 owner: dependency.owner.or(self.owner_hint),
1408 ..dependency
1409 });
1410 self.set_entry(publication)
1411 }
1412
1413 fn holds_live(&self, sequence: u64) -> bool {
1419 let state = self
1420 .state
1421 .lock()
1422 .unwrap_or_else(std::sync::PoisonError::into_inner);
1423 TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1424 self.note_entry_visit();
1425 entry.publication.is_live()
1426 })
1427 }
1428
1429 fn set_borrowed(&self, publication: &DependencyPublication) {
1430 let _sequence = self.set_entry(publication.derived(self.owner_hint));
1431 }
1432
1433 fn set_entry(&self, publication: DependencyPublication) -> u64 {
1434 let (sequence, waiter) = {
1435 let mut state = self
1436 .state
1437 .lock()
1438 .unwrap_or_else(std::sync::PoisonError::into_inner);
1439 let sequence = state.append(publication);
1442 self.has_current.store(true, Ordering::Release);
1443 (sequence, state.waiter.take())
1444 };
1445 self.notify_waiter(waiter);
1446 sequence
1447 }
1448
1449 fn note_entry_visit(&self) {
1450 #[cfg(test)]
1451 self.entry_visits.fetch_add(1, Ordering::Relaxed);
1452 }
1453
1454 fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1455 while state.current.front().is_some_and(|current| {
1456 self.note_entry_visit();
1457 !current.publication.is_live()
1458 }) {
1459 let current = state
1460 .current
1461 .pop_front()
1462 .expect("the stale prefix entry must exist");
1463 state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1464 }
1465 self
1466 .has_current
1467 .store(!state.current.is_empty(), Ordering::Release);
1468 }
1469
1470 fn first_live_sequence(
1471 &self,
1472 current: &VecDeque<TaskDependencyEntry>,
1473 sequences: &mut VecDeque<u64>,
1474 ) -> Option<u64> {
1475 loop {
1476 let sequence = *sequences.front()?;
1477 let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1478 sequences.pop_front();
1479 continue;
1480 };
1481 self.note_entry_visit();
1482 if entry.publication.is_live() {
1483 return Some(sequence);
1484 }
1485 sequences.pop_front();
1486 }
1487 }
1488
1489 fn first_live_owner_sequence(
1490 &self,
1491 state: &mut TaskDependencyState,
1492 owner: BlockingOwnerToken,
1493 ) -> Option<u64> {
1494 let sequence = {
1495 let current = &state.current;
1496 state
1497 .owner_sequences
1498 .get_mut(&owner)
1499 .and_then(|sequences| self.first_live_sequence(current, sequences))
1500 };
1501 if sequence.is_none() {
1502 state.owner_sequences.remove(&owner);
1503 }
1504 sequence
1505 }
1506
1507 fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1508 self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1509 }
1510
1511 #[cfg(test)]
1512 fn get(&self) -> Option<DependencyPublication> {
1513 self
1514 .state
1515 .lock()
1516 .unwrap_or_else(std::sync::PoisonError::into_inner)
1517 .current
1518 .iter()
1519 .find(|current| current.publication.is_live())
1520 .map(|current| current.publication.clone())
1521 }
1522
1523 fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1524 self
1525 .state
1526 .lock()
1527 .unwrap_or_else(std::sync::PoisonError::into_inner)
1528 .current
1529 .iter()
1530 .filter(|current| {
1531 self.note_entry_visit();
1532 current.publication.is_live()
1533 })
1534 .map(|current| current.publication.clone())
1535 .collect()
1536 }
1537
1538 fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1539 let mut state = self
1540 .state
1541 .lock()
1542 .unwrap_or_else(std::sync::PoisonError::into_inner);
1543 self.prune_stale_prefix(&mut state);
1544 let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1545 let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1546 let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1547 (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1548 (Some(_) | None, Some(unowned)) => (unowned, true),
1549 (Some(exact), None) => (exact, false),
1550 (None, None) => return None,
1551 };
1552 if bind_owner {
1553 let removed = state.unowned_sequences.pop_front();
1554 debug_assert_eq!(removed, Some(sequence));
1555 let current = state
1556 .entry_mut(sequence)
1557 .expect("the selected dependency must remain indexed");
1558 debug_assert_eq!(current.publication.dependency.owner, None);
1559 current.publication.dependency.owner = Some(owner);
1560 state
1564 .owner_sequences
1565 .entry(owner)
1566 .or_default()
1567 .push_front(sequence);
1568 }
1569 let publication = state
1570 .entry(sequence)
1571 .expect("the selected dependency must remain indexed")
1572 .publication
1573 .clone();
1574 Some(SelectedDependencyPublication {
1575 sequence,
1576 publication,
1577 })
1578 }
1579
1580 fn has_live_owner_dependency(
1581 &self,
1582 owner: BlockingOwnerToken,
1583 mode: OwnerDependencyMode,
1584 ) -> bool {
1585 #[cfg(all(test, not(target_family = "wasm")))]
1586 run_dependency_predicate_test_hook();
1587 let mut state = self
1588 .state
1589 .lock()
1590 .unwrap_or_else(std::sync::PoisonError::into_inner);
1591 self.prune_stale_prefix(&mut state);
1592 if self.first_live_owner_sequence(&mut state, owner).is_some() {
1593 return true;
1594 }
1595 matches!(mode, OwnerDependencyMode::Available)
1596 && self.first_live_unowned_sequence(&mut state).is_some()
1597 }
1598
1599 #[cfg(test)]
1600 fn get_dependency(&self) -> Option<BlockingDependency> {
1601 self.get().map(|publication| publication.dependency)
1602 }
1603
1604 #[cfg(test)]
1605 fn get_dependencies(&self) -> Vec<BlockingDependency> {
1606 self
1607 .snapshot_all_live()
1608 .into_iter()
1609 .map(|publication| publication.dependency)
1610 .collect()
1611 }
1612
1613 #[cfg(test)]
1614 fn reset_entry_visits(&self) {
1615 self.entry_visits.store(0, Ordering::Relaxed);
1616 }
1617
1618 #[cfg(test)]
1619 fn entry_visits(&self) -> usize {
1620 self.entry_visits.load(Ordering::Relaxed)
1621 }
1622
1623 fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1627 let mut state = self
1628 .state
1629 .lock()
1630 .unwrap_or_else(std::sync::PoisonError::into_inner);
1631 let Some(current) = state.entry(selected.sequence) else {
1632 return false;
1633 };
1634 self.note_entry_visit();
1635 if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1636 return false;
1637 }
1638 let dependency = current.publication.dependency;
1639 state
1640 .entry_mut(selected.sequence)
1641 .expect("the claimed dependency must remain indexed")
1642 .publication
1643 .cancel();
1644 state.remove_sequence_indexes(selected.sequence, dependency);
1645 self.prune_stale_prefix(&mut state);
1646 true
1647 }
1648
1649 fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1650 let _generation = RuntimeGenerationGuard::enter(self.generation);
1651 let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1652 return Vec::new();
1653 };
1654 let (previous, unused, current) = {
1655 let mut state = self
1656 .state
1657 .lock()
1658 .unwrap_or_else(std::sync::PoisonError::into_inner);
1659 let current = state
1660 .current
1661 .iter()
1662 .filter(|current| current.publication.is_live())
1663 .map(|current| current.publication.clone())
1664 .collect();
1665 if state
1666 .waiter
1667 .as_ref()
1668 .is_some_and(|previous| previous.will_wake(&waker))
1669 {
1670 (None, Some(waker), current)
1671 } else {
1672 (state.waiter.replace(waker), None, current)
1673 }
1674 };
1675 if let Some(previous) = previous {
1676 let _ = catch_unwind_contained(|| drop(previous));
1677 }
1678 if let Some(unused) = unused {
1679 let _ = catch_unwind_contained(|| drop(unused));
1680 }
1681 current
1682 }
1683
1684 fn begin_poll(&self) {
1685 if !self.has_current.load(Ordering::Acquire) {
1686 return;
1687 }
1688 let waiter = {
1689 let mut state = self
1690 .state
1691 .lock()
1692 .unwrap_or_else(std::sync::PoisonError::into_inner);
1693 if state.current.is_empty() {
1694 self.has_current.store(false, Ordering::Release);
1695 None
1696 } else {
1697 for previous in state.current.drain(..) {
1698 previous.publication.cancel();
1699 }
1700 state.clear_indexes();
1701 self.has_current.store(false, Ordering::Release);
1702 state.waiter.take()
1703 }
1704 };
1705 self.notify_waiter(waiter);
1706 }
1707
1708 #[cfg(test)]
1709 fn register_waiter(&self, waker: &Waker) {
1710 let _ = self.register_waiter_and_get(waker);
1711 }
1712}
1713
1714#[cfg(not(target_family = "wasm"))]
1715impl Drop for TaskDependency {
1716 fn drop(&mut self) {
1717 let state = self
1718 .state
1719 .get_mut()
1720 .unwrap_or_else(std::sync::PoisonError::into_inner);
1721 for current in state.current.drain(..) {
1722 current.publication.cancel();
1723 }
1724 if let Some(waiter) = state.waiter.take() {
1725 let _generation = RuntimeGenerationGuard::enter(self.generation);
1726 let _ = catch_unwind_contained(|| drop(waiter));
1727 }
1728 }
1729}
1730
1731#[cfg(not(target_family = "wasm"))]
1732thread_local! {
1733 static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1734 const { std::cell::RefCell::new(Vec::new()) };
1735}
1736
1737#[cfg(not(target_family = "wasm"))]
1738struct TaskDependencyGuard(Arc<TaskDependency>);
1739
1740#[cfg(not(target_family = "wasm"))]
1741impl TaskDependencyGuard {
1742 fn enter(dependency: &Arc<TaskDependency>) -> Self {
1743 let dependency = Arc::clone(dependency);
1744 CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1745 Self(dependency)
1746 }
1747}
1748
1749#[cfg(not(target_family = "wasm"))]
1750impl Drop for TaskDependencyGuard {
1751 fn drop(&mut self) {
1752 CURRENT_DEPENDENCY_STACK.with(|stack| {
1753 let current = stack
1754 .borrow_mut()
1755 .pop()
1756 .expect("blocking dependency context underflow");
1757 debug_assert!(Arc::ptr_eq(¤t, &self.0));
1758 });
1759 }
1760}
1761
1762#[cfg(not(target_family = "wasm"))]
1763struct BlockOnDependencyGuard {
1764 dependency: Arc<TaskDependency>,
1765 _context: TaskDependencyGuard,
1766}
1767
1768#[cfg(not(target_family = "wasm"))]
1769impl BlockOnDependencyGuard {
1770 fn enter(generation: u64) -> Self {
1771 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1772 let dependency = Arc::new(TaskDependency::new(generation, owner));
1773 let context = TaskDependencyGuard::enter(&dependency);
1774 Self {
1775 dependency,
1776 _context: context,
1777 }
1778 }
1779
1780 fn clear(&self) {
1781 self.dependency.clear();
1782 }
1783}
1784
1785#[cfg(not(target_family = "wasm"))]
1800struct PublishedBlockingDependency {
1801 state: Weak<TaskDependency>,
1802 sequence: u64,
1803}
1804
1805#[cfg(not(target_family = "wasm"))]
1824#[derive(Default)]
1825struct PublishedBlockingDependencies {
1826 first: Option<PublishedBlockingDependency>,
1827 rest: Vec<PublishedBlockingDependency>,
1828}
1829
1830#[cfg(not(target_family = "wasm"))]
1831impl PublishedBlockingDependency {
1832 fn still_stands(&self) -> bool {
1847 self
1848 .state
1849 .upgrade()
1850 .is_some_and(|state| state.holds_live(self.sequence))
1851 }
1852}
1853
1854#[cfg(not(target_family = "wasm"))]
1855impl PublishedBlockingDependencies {
1856 fn prune(&mut self) {
1881 if self.rest.is_empty() {
1882 if self
1883 .first
1884 .as_ref()
1885 .is_some_and(|first| first.state.strong_count() == 0)
1886 {
1887 self.first = None;
1888 }
1889 return;
1890 }
1891 self.rest.retain(PublishedBlockingDependency::still_stands);
1892 if self
1893 .first
1894 .as_ref()
1895 .is_some_and(|first| !first.still_stands())
1896 {
1897 self.first = self.rest.pop();
1898 }
1899 }
1900
1901 fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1902 self.prune();
1903 let target = Arc::as_ptr(&state);
1904 if let Some(existing) = self
1905 .first
1906 .iter_mut()
1907 .chain(self.rest.iter_mut())
1908 .find(|published| std::ptr::eq(published.state.as_ptr(), target))
1909 {
1910 if state.holds_live(existing.sequence) {
1921 return;
1922 }
1923 existing.sequence = state.set_owned(dependency);
1927 return;
1928 }
1929 let sequence = state.set_owned(dependency);
1930 let published = PublishedBlockingDependency {
1931 state: Arc::downgrade(&state),
1932 sequence,
1933 };
1934 match &mut self.first {
1935 slot @ None => *slot = Some(published),
1936 Some(_) => self.rest.push(published),
1937 }
1938 }
1939
1940 fn retire_all(self) {
1950 for published in self.first.into_iter().chain(self.rest) {
1951 if let Some(state) = published.state.upgrade() {
1952 state.retire(published.sequence);
1953 }
1954 }
1955 }
1956}
1957
1958#[cfg(not(target_family = "wasm"))]
1959fn record_blocking_dependency(
1960 published: &mut PublishedBlockingDependencies,
1961 dependency: BlockingDependency,
1962) {
1963 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1964 if let Some(current) = current {
1965 let dependency = BlockingDependency {
1966 owner: dependency
1967 .owner
1968 .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
1969 ..dependency
1970 };
1971 published.publish(current, dependency);
1972 }
1973}
1974
1975#[cfg(not(target_family = "wasm"))]
1976fn propagate_blocking_dependency(publication: DependencyPublication) {
1977 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1978 if let Some(current) = current {
1979 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1980 let publication = publication.with_owner_if_none(owner);
1981 current.set_borrowed(&publication);
1982 }
1983}
1984
1985#[cfg(not(target_family = "wasm"))]
1986fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
1987 published.retire_all();
1991}
1992
1993#[cfg(not(target_family = "wasm"))]
1994fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
1995 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1996 if let Some(current) = current {
1997 current.clear_if_publications(publications);
1998 }
1999}
2000
2001enum JoinHandleInner<T> {
2002 Task {
2003 task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2004 awaiter: Option<PanicContainedWakerCache>,
2005 #[cfg(not(target_family = "wasm"))]
2006 dependency: Arc<TaskDependency>,
2007 },
2008 #[cfg(not(target_family = "wasm"))]
2009 Blocking {
2010 receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2011 dependency: BlockingDependency,
2012 published: PublishedBlockingDependencies,
2015 },
2016 Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2017}
2018
2019pub struct JoinHandle<T>(JoinHandleInner<T>);
2020
2021impl<T> Unpin for JoinHandle<T> {}
2022
2023impl<T> JoinHandle<T> {
2024 fn detach_task(&mut self) {
2025 let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2026 match inner {
2027 JoinHandleInner::Task {
2028 task,
2029 awaiter: _,
2030 #[cfg(not(target_family = "wasm"))]
2031 dependency,
2032 } => {
2033 #[cfg(not(target_family = "wasm"))]
2034 let current_dependencies = dependency.snapshot_all_live();
2035 #[cfg(not(target_family = "wasm"))]
2036 dependency.clear_waiter();
2037 task.detach();
2038 #[cfg(not(target_family = "wasm"))]
2039 let _ = catch_unwind_contained(|| {
2043 clear_propagated_blocking_dependencies(¤t_dependencies);
2044 });
2045 }
2046 #[cfg(not(target_family = "wasm"))]
2047 JoinHandleInner::Blocking {
2048 receiver,
2049 dependency: _,
2050 published,
2051 } => {
2052 let _ = catch_unwind_contained(|| drop(receiver));
2056 let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2059 }
2060 JoinHandleInner::Ready(result) => {
2061 let _ = catch_unwind_contained(|| drop(result));
2062 }
2063 }
2064 }
2065
2066 pub fn detach(mut self) {
2067 self.detach_task();
2068 }
2069}
2070
2071impl<T> Drop for JoinHandle<T> {
2072 fn drop(&mut self) {
2073 self.detach_task();
2077 }
2078}
2079
2080impl<T> Future for JoinHandle<T> {
2081 type Output = Result<T, JoinError>;
2082
2083 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2084 match &mut self.get_mut().0 {
2085 JoinHandleInner::Task {
2086 task,
2087 awaiter: awaiter_slot,
2088 #[cfg(not(target_family = "wasm"))]
2089 dependency,
2090 } => {
2091 let awaiter = match awaiter_slot {
2092 Some(awaiter) => {
2093 awaiter.update(cx.waker());
2094 awaiter
2095 }
2096 None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2097 };
2098 let contained_waker = awaiter.contained();
2099 let mut contained_cx = Context::from_waker(contained_waker);
2100 let poll = Pin::new(task).poll(&mut contained_cx);
2101 #[cfg(not(target_family = "wasm"))]
2102 if poll.is_pending() {
2103 for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2104 propagate_blocking_dependency(current_dependency);
2105 }
2106 } else {
2107 dependency.clear_waiter();
2108 }
2109 if poll.is_ready() {
2110 *awaiter_slot = None;
2111 }
2112 match poll {
2113 Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2114 Poll::Ready(None) => Poll::Ready(Err(JoinError {
2115 message: "async runtime stopped before the task completed".to_string(),
2116 })),
2117 Poll::Pending => Poll::Pending,
2118 }
2119 }
2120 #[cfg(not(target_family = "wasm"))]
2121 JoinHandleInner::Blocking {
2122 receiver,
2123 dependency,
2124 published,
2125 } => {
2126 let poll = Pin::new(receiver).poll(cx);
2127 if poll.is_pending() {
2128 record_blocking_dependency(published, *dependency);
2129 }
2130 match poll {
2131 Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2132 Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2133 message: "async runtime stopped before the blocking task completed".to_string(),
2134 })),
2135 Poll::Pending => Poll::Pending,
2136 }
2137 }
2138 JoinHandleInner::Ready(result) => Poll::Ready(
2139 result
2140 .take()
2141 .expect("JoinHandle polled after completion")
2142 .map(ContainedTaskOutput::into_inner),
2143 ),
2144 }
2145 }
2146}
2147
2148#[derive(Debug, Clone, Copy)]
2149pub struct RuntimeMetricsSnapshot {
2150 pub flavor: RuntimeFlavor,
2151 pub worker_threads: usize,
2152 pub max_blocking_tasks: usize,
2153 pub tasks_spawned: u64,
2154 pub tasks_completed: u64,
2155 pub tasks_panicked: u64,
2156 pub runnable_schedules: u64,
2157 pub runnable_polls: u64,
2158 pub queued_runnables: u64,
2159 pub max_queued_runnables: u64,
2160 pub active_runnables: u64,
2161 pub max_active_runnables: u64,
2162 pub blocking_tasks_started: u64,
2163 pub blocking_tasks_completed: u64,
2164 pub active_blocking_tasks: u64,
2165 pub max_active_blocking_tasks: u64,
2166}
2167
2168#[derive(Default)]
2169struct RuntimeMetrics {
2170 tasks_spawned: AtomicU64,
2171 tasks_completed: AtomicU64,
2172 tasks_panicked: AtomicU64,
2173 runnable_schedules: AtomicU64,
2174 runnable_polls: AtomicU64,
2175 queued_runnables: AtomicU64,
2176 max_queued_runnables: AtomicU64,
2177 active_runnables: AtomicU64,
2178 max_active_runnables: AtomicU64,
2179 blocking_tasks_scheduled: AtomicU64,
2185 blocking_tasks_started: AtomicU64,
2186 blocking_tasks_completed: AtomicU64,
2187 active_blocking_tasks: AtomicU64,
2188 max_active_blocking_tasks: AtomicU64,
2189 reset_generation: AtomicU64,
2193 reset_lock: Mutex<()>,
2194}
2195
2196#[cfg(test)]
2197thread_local! {
2198 static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2199 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2200 const { std::cell::RefCell::new(None) };
2201}
2202
2203#[cfg(test)]
2204fn run_runtime_metrics_high_water_test_hook() {
2205 if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2206 hook();
2207 }
2208}
2209
2210impl RuntimeMetrics {
2211 fn runnable_scheduled(&self) {
2212 self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2213 let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2214 #[cfg(test)]
2215 run_runtime_metrics_high_water_test_hook();
2216 self
2217 .max_queued_runnables
2218 .fetch_max(queued, Ordering::Relaxed);
2219 }
2220
2221 fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2222 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2223 self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2224 let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2225 #[cfg(test)]
2226 run_runtime_metrics_high_water_test_hook();
2227 self
2228 .max_active_runnables
2229 .fetch_max(active, Ordering::Relaxed);
2230 ActiveRunnableGuard { metrics: self }
2231 }
2232
2233 #[cfg(any(not(target_family = "wasm"), test))]
2234 fn runnable_cancelled(&self) {
2235 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2236 }
2237
2238 fn blocking_scheduled(&self) {
2242 self
2243 .blocking_tasks_scheduled
2244 .fetch_add(1, Ordering::Relaxed);
2245 }
2246
2247 fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2248 self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2249 if count_active_lane {
2250 let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2251 #[cfg(test)]
2252 run_runtime_metrics_high_water_test_hook();
2253 self
2254 .max_active_blocking_tasks
2255 .fetch_max(active, Ordering::Relaxed);
2256 }
2257 BlockingTaskGuard {
2258 metrics: Arc::clone(self),
2259 count_active_lane,
2260 }
2261 }
2262
2263 fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2264 let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2265 let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2266 let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2267 RuntimeMetricsSnapshot {
2268 flavor: options.flavor,
2269 worker_threads: options.worker_threads,
2270 max_blocking_tasks: options.max_blocking_tasks,
2271 tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2272 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2273 tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2274 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2275 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2276 queued_runnables,
2277 max_queued_runnables: self
2278 .max_queued_runnables
2279 .load(Ordering::Relaxed)
2280 .max(queued_runnables),
2281 active_runnables,
2282 max_active_runnables: self
2283 .max_active_runnables
2284 .load(Ordering::Relaxed)
2285 .max(active_runnables),
2286 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2287 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2288 active_blocking_tasks,
2289 max_active_blocking_tasks: self
2290 .max_active_blocking_tasks
2291 .load(Ordering::Relaxed)
2292 .max(active_blocking_tasks),
2293 }
2294 }
2295
2296 fn progress_fingerprint(&self) -> ProgressFingerprint {
2305 loop {
2306 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2307 if !reset_generation.is_multiple_of(2) {
2308 std::hint::spin_loop();
2309 continue;
2310 }
2311 let fingerprint = ProgressFingerprint {
2312 reset_generation,
2313 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2314 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2315 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2316 blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2317 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2318 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2319 };
2320 if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2321 return fingerprint;
2322 }
2323 }
2324 }
2325
2326 fn reset(&self) {
2327 let reset_guard = self
2328 .reset_lock
2329 .lock()
2330 .unwrap_or_else(std::sync::PoisonError::into_inner);
2331 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2339 let Some(resetting_generation) = reset_generation.checked_add(1) else {
2340 drop(reset_guard);
2341 panic!("runtime metrics reset generation space exhausted");
2342 };
2343 let Some(settled_generation) = resetting_generation.checked_add(1) else {
2344 drop(reset_guard);
2345 panic!("runtime metrics reset generation space exhausted");
2346 };
2347 self
2348 .reset_generation
2349 .store(resetting_generation, Ordering::SeqCst);
2350 self.tasks_spawned.store(0, Ordering::Relaxed);
2351 self.tasks_completed.store(0, Ordering::Relaxed);
2352 self.tasks_panicked.store(0, Ordering::Relaxed);
2353 self.runnable_schedules.store(0, Ordering::Relaxed);
2354 self.runnable_polls.store(0, Ordering::Relaxed);
2355 self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2356 self.blocking_tasks_started.store(0, Ordering::Relaxed);
2357 self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2358 self
2359 .reset_generation
2360 .store(settled_generation, Ordering::SeqCst);
2361 }
2362}
2363
2364#[derive(Clone, Copy, PartialEq, Eq)]
2366struct ProgressFingerprint {
2367 reset_generation: u64,
2368 runnable_schedules: u64,
2369 runnable_polls: u64,
2370 tasks_completed: u64,
2371 blocking_tasks_scheduled: u64,
2372 blocking_tasks_started: u64,
2373 blocking_tasks_completed: u64,
2374}
2375
2376struct ActiveRunnableGuard<'a> {
2377 metrics: &'a RuntimeMetrics,
2378}
2379
2380impl Drop for ActiveRunnableGuard<'_> {
2381 fn drop(&mut self) {
2382 self
2383 .metrics
2384 .active_runnables
2385 .fetch_sub(1, Ordering::Relaxed);
2386 }
2387}
2388
2389struct BlockingTaskGuard {
2390 metrics: Arc<RuntimeMetrics>,
2391 count_active_lane: bool,
2392}
2393
2394impl Drop for BlockingTaskGuard {
2395 fn drop(&mut self) {
2396 if self.count_active_lane {
2397 self
2398 .metrics
2399 .active_blocking_tasks
2400 .fetch_sub(1, Ordering::Relaxed);
2401 }
2402 self
2403 .metrics
2404 .blocking_tasks_completed
2405 .fetch_add(1, Ordering::Relaxed);
2406 }
2407}
2408
2409static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2410
2411fn next_runtime_generation() -> u64 {
2412 next_unique_id(
2413 &NEXT_RUNTIME_GENERATION,
2414 "async runtime generation id space exhausted",
2415 )
2416}
2417
2418thread_local! {
2419 static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2420 const { std::cell::Cell::new(None) };
2421}
2422
2423fn active_runtime_generation() -> Option<u64> {
2424 ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2425}
2426
2427struct RuntimeGenerationGuard {
2428 previous: Option<u64>,
2429}
2430
2431impl RuntimeGenerationGuard {
2432 fn enter(generation: u64) -> Self {
2433 Self::enter_optional(Some(generation))
2434 }
2435
2436 fn enter_optional(generation: Option<u64>) -> Self {
2437 Self {
2438 previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2439 }
2440 }
2441}
2442
2443impl Drop for RuntimeGenerationGuard {
2444 fn drop(&mut self) {
2445 ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2446 }
2447}
2448
2449struct ContainedFuture<F> {
2450 future: ManuallyDrop<F>,
2451 generation: u64,
2452}
2453
2454impl<F> ContainedFuture<F> {
2455 fn new(future: F, generation: u64) -> Self {
2456 Self {
2457 future: ManuallyDrop::new(future),
2458 generation,
2459 }
2460 }
2461}
2462
2463impl<F: Future> Future for ContainedFuture<F> {
2464 type Output = F::Output;
2465
2466 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2467 let this = unsafe { self.get_unchecked_mut() };
2469 let _generation = RuntimeGenerationGuard::enter(this.generation);
2470 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2473 }
2474}
2475
2476impl<F> Drop for ContainedFuture<F> {
2477 fn drop(&mut self) {
2478 let _generation = RuntimeGenerationGuard::enter(self.generation);
2479 let _ = catch_unwind_contained(|| {
2480 unsafe { ManuallyDrop::drop(&mut self.future) };
2482 });
2483 }
2484}
2485
2486#[derive(Default)]
2487struct GenerationStop {
2488 stopping: AtomicBool,
2489 publication: Mutex<()>,
2490 parkers: Mutex<Vec<Weak<DriverParker>>>,
2491}
2492
2493impl GenerationStop {
2494 fn is_stopping(&self) -> bool {
2495 self.stopping.load(Ordering::Acquire)
2496 }
2497
2498 fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2499 #[cfg(all(test, not(target_family = "wasm")))]
2500 run_before_generation_stop_publication_lock_test_hook();
2501 self.runnable_claim_guard()
2502 }
2503
2504 fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2505 GenerationStopPublicationGuard {
2506 stop: self,
2507 _publication: self
2508 .publication
2509 .lock()
2510 .unwrap_or_else(std::sync::PoisonError::into_inner),
2511 }
2512 }
2513
2514 fn publish_stopping(&self) {
2515 self.publication_guard().publish_stopping();
2516 }
2517
2518 fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2519 {
2520 let mut parkers = self
2521 .parkers
2522 .lock()
2523 .unwrap_or_else(std::sync::PoisonError::into_inner);
2524 parkers.retain(|candidate| candidate.strong_count() != 0);
2525 parkers.push(Arc::downgrade(parker));
2526 }
2527 if self.is_stopping() {
2528 parker.unpark();
2529 }
2530 GenerationStopGuard {
2531 stop: Arc::clone(self),
2532 parker: Arc::downgrade(parker),
2533 }
2534 }
2535
2536 fn wake_all(&self) -> usize {
2541 let mut count = 0;
2542 let mut registered = self
2543 .parkers
2544 .lock()
2545 .unwrap_or_else(std::sync::PoisonError::into_inner);
2546 registered.retain(|candidate| {
2547 let Some(parker) = candidate.upgrade() else {
2548 return false;
2549 };
2550 count += 1;
2551 parker.unpark();
2554 true
2555 });
2556 count
2557 }
2558
2559 fn begin_shutdown(&self) {
2560 self.publish_stopping();
2561 let parkers = {
2562 let mut registered = self
2563 .parkers
2564 .lock()
2565 .unwrap_or_else(std::sync::PoisonError::into_inner);
2566 let parkers = registered
2567 .iter()
2568 .filter_map(Weak::upgrade)
2569 .collect::<Vec<_>>();
2570 registered.clear();
2571 parkers
2572 };
2573 for parker in parkers {
2574 parker.unpark();
2575 }
2576 }
2577}
2578
2579struct GenerationStopPublicationGuard<'a> {
2580 stop: &'a GenerationStop,
2581 _publication: std::sync::MutexGuard<'a, ()>,
2582}
2583
2584impl GenerationStopPublicationGuard<'_> {
2585 fn is_stopping(&self) -> bool {
2586 self.stop.is_stopping()
2587 }
2588
2589 fn publish_stopping(&self) {
2590 self.stop.stopping.store(true, Ordering::Release);
2591 }
2592}
2593
2594struct GenerationStopGuard {
2595 stop: Arc<GenerationStop>,
2596 parker: Weak<DriverParker>,
2597}
2598
2599impl Drop for GenerationStopGuard {
2600 fn drop(&mut self) {
2601 let Some(parker) = self.parker.upgrade() else {
2602 return;
2603 };
2604 let mut registered = self
2605 .stop
2606 .parkers
2607 .lock()
2608 .unwrap_or_else(std::sync::PoisonError::into_inner);
2609 if let Some(index) = registered.iter().position(|candidate| {
2610 candidate
2611 .upgrade()
2612 .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2613 }) {
2614 registered.swap_remove(index);
2615 }
2616 }
2617}
2618
2619struct GenerationWorkState {
2620 closed: bool,
2621 next_task_id: u64,
2622 active: usize,
2623 abort_handles: FxHashMap<u64, AbortHandle>,
2624}
2625
2626struct GenerationWork {
2627 id: u64,
2628 state: Mutex<GenerationWorkState>,
2629 idle: Condvar,
2630}
2631
2632impl GenerationWork {
2633 fn new() -> Arc<Self> {
2634 Arc::new(Self {
2635 id: next_runtime_generation(),
2636 state: Mutex::new(GenerationWorkState {
2637 closed: false,
2638 next_task_id: 0,
2639 active: 0,
2640 abort_handles: FxHashMap::default(),
2641 }),
2642 idle: Condvar::new(),
2643 })
2644 }
2645
2646 fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2647 let (abort_handle, abort_registration) = AbortHandle::new_pair();
2648 let mut state = self
2649 .state
2650 .lock()
2651 .unwrap_or_else(std::sync::PoisonError::into_inner);
2652 if state.closed {
2653 return None;
2654 }
2655 let task_id = state.next_task_id;
2656 let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2657 drop(state);
2658 panic!("async runtime generation task id space exhausted");
2659 };
2660 state.next_task_id = next_task_id;
2661 state.active += 1;
2662 state.abort_handles.insert(task_id, abort_handle);
2663 Some((
2664 abort_registration,
2665 GenerationWorkGuard {
2666 work: Arc::clone(self),
2667 task_id: Some(task_id),
2668 },
2669 ))
2670 }
2671
2672 fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2673 let mut state = self
2674 .state
2675 .lock()
2676 .unwrap_or_else(std::sync::PoisonError::into_inner);
2677 if state.closed {
2678 return None;
2679 }
2680 state.active += 1;
2681 Some(GenerationWorkGuard {
2682 work: Arc::clone(self),
2683 task_id: None,
2684 })
2685 }
2686
2687 fn close_and_abort(&self) {
2688 let abort_handles = {
2689 let mut state = self
2690 .state
2691 .lock()
2692 .unwrap_or_else(std::sync::PoisonError::into_inner);
2693 state.closed = true;
2694 state.abort_handles.values().cloned().collect::<Vec<_>>()
2695 };
2696 for abort_handle in abort_handles {
2697 abort_handle.abort();
2698 }
2699 }
2700
2701 fn wait_until_idle(&self) {
2702 let mut state = self
2703 .state
2704 .lock()
2705 .unwrap_or_else(std::sync::PoisonError::into_inner);
2706 while state.active != 0 {
2707 state = self
2708 .idle
2709 .wait(state)
2710 .unwrap_or_else(std::sync::PoisonError::into_inner);
2711 }
2712 }
2713}
2714
2715struct GenerationWorkGuard {
2716 work: Arc<GenerationWork>,
2717 task_id: Option<u64>,
2718}
2719
2720impl Drop for GenerationWorkGuard {
2721 fn drop(&mut self) {
2722 let mut state = self
2723 .work
2724 .state
2725 .lock()
2726 .unwrap_or_else(std::sync::PoisonError::into_inner);
2727 if let Some(task_id) = self.task_id {
2728 state.abort_handles.remove(&task_id);
2729 }
2730 state.active = state
2731 .active
2732 .checked_sub(1)
2733 .expect("generation work count underflow");
2734 if state.active == 0 {
2735 self.work.idle.notify_all();
2736 }
2737 }
2738}
2739
2740struct RegisteredTaskFuture<F> {
2746 future: ManuallyDrop<F>,
2747 registration: Option<GenerationWorkGuard>,
2748 generation: u64,
2749}
2750
2751impl<F> RegisteredTaskFuture<F> {
2752 fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2753 Self {
2754 future: ManuallyDrop::new(future),
2755 registration: Some(registration),
2756 generation,
2757 }
2758 }
2759}
2760
2761impl<F: Future> Future for RegisteredTaskFuture<F> {
2762 type Output = F::Output;
2763
2764 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2765 let this = unsafe { self.get_unchecked_mut() };
2767 let _generation = RuntimeGenerationGuard::enter(this.generation);
2768 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2771 }
2772}
2773
2774impl<F> Drop for RegisteredTaskFuture<F> {
2775 fn drop(&mut self) {
2776 let _generation = RuntimeGenerationGuard::enter(self.generation);
2777 let _ = catch_unwind_contained(|| {
2778 unsafe { ManuallyDrop::drop(&mut self.future) };
2780 });
2781 drop(self.registration.take());
2784 }
2785}
2786
2787#[cfg(not(target_family = "wasm"))]
2788struct RegisteredBlockingFunction<F> {
2789 function: Option<F>,
2790 registration: Option<GenerationWorkGuard>,
2791 generation: u64,
2792}
2793
2794#[cfg(not(target_family = "wasm"))]
2795impl<F> RegisteredBlockingFunction<F> {
2796 fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2797 Self {
2798 function: Some(function),
2799 registration: Some(registration),
2800 generation,
2801 }
2802 }
2803
2804 fn run<T>(mut self) -> Result<T, JoinError>
2805 where
2806 F: FnOnce() -> T,
2807 {
2808 let _generation = RuntimeGenerationGuard::enter(self.generation);
2809 let result = catch_unwind_join_error(
2810 self
2811 .function
2812 .take()
2813 .expect("blocking function must be available"),
2814 );
2815 drop(self.registration.take());
2816 result
2817 }
2818}
2819
2820#[cfg(not(target_family = "wasm"))]
2821impl<F> Drop for RegisteredBlockingFunction<F> {
2822 fn drop(&mut self) {
2823 let _generation = RuntimeGenerationGuard::enter(self.generation);
2824 if let Some(function) = self.function.take() {
2825 let _ = catch_unwind_contained(|| drop(function));
2826 }
2827 drop(self.registration.take());
2831 }
2832}
2833
2834fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2835 let _active = metrics.runnable_started();
2836 let _ = catch_unwind_contained(|| runnable.run());
2837}
2838
2839#[cfg(not(target_family = "wasm"))]
2840enum RunnableClaim {
2841 Empty,
2842 Run(Runnable),
2843 Cancel(Runnable),
2844}
2845
2846static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2847static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2848
2849fn next_current_thread_dispatch_id() -> u64 {
2850 next_unique_id(
2851 &NEXT_CURRENT_THREAD_DISPATCH_ID,
2852 "CurrentThread dispatch id space exhausted",
2853 )
2854 .checked_add(1)
2855 .expect("CurrentThread dispatch id space exhausted")
2856}
2857
2858struct CurrentThreadBlockingAdmission {
2859 state: Mutex<CurrentThreadBlockingAdmissionState>,
2860}
2861
2862struct CurrentThreadBlockingAdmissionState {
2863 owner: Option<BlockingOwnerToken>,
2864 closed: bool,
2865 #[cfg(not(target_family = "wasm"))]
2866 queue: BlockingQueue,
2867}
2868
2869struct CurrentThreadExecutor {
2870 id: u64,
2871 generation: u64,
2872 stop: Arc<GenerationStop>,
2873 queue: Mutex<CurrentThreadQueue>,
2874 blocking_admission: CurrentThreadBlockingAdmission,
2875 draining: AtomicBool,
2876 scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2877 scheduler_idle: Condvar,
2878 dispatch_pending: AtomicU64,
2882 recovery_dispatch: AtomicU64,
2886 cancelling_failed_dispatch: AtomicBool,
2889 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2890 task_drivers: &'static CurrentThreadTaskDriverRegistry,
2891 metrics: Arc<RuntimeMetrics>,
2892 threadless: bool,
2900 park_deadline: Option<Duration>,
2901 host_timers: Arc<HostTimerRegistry>,
2911}
2912
2913struct CurrentThreadQueue {
2914 closed: bool,
2915 runnables: VecDeque<Runnable>,
2916}
2917
2918#[derive(Default)]
2919struct CurrentThreadSchedulerState {
2920 active_host_turns: usize,
2921 active_dispatch_calls: usize,
2922 dispatch_publications: Vec<CurrentThreadDispatchPublication>,
2923}
2924
2925struct CurrentThreadDispatchPublication {
2926 dispatch: u64,
2927 republish_requested: bool,
2928}
2929
2930#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2931enum CurrentThreadDispatchRequest {
2932 Accepted,
2933 Coalesced,
2934 Unavailable,
2935}
2936
2937#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2938struct CurrentThreadTaskDispatchFailure {
2939 dispatch: u64,
2940 epoch: u64,
2941}
2942
2943#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2944enum CurrentThreadHostDispatchResult {
2945 Accepted,
2946 Unavailable,
2947 Failed(CurrentThreadTaskDispatchFailure),
2948}
2949
2950enum CurrentThreadDispatchPublicationStep {
2951 Republish,
2952 Complete(Option<CurrentThreadHostDispatchResult>),
2953}
2954
2955struct CurrentThreadHostTurn {
2956 executor: Arc<CurrentThreadExecutor>,
2957 draining: bool,
2958 _generation: RuntimeGenerationGuard,
2959}
2960
2961impl CurrentThreadHostTurn {
2962 fn drive(&mut self) {
2963 let executor = Arc::clone(&self.executor);
2964 executor.drive_admitted_host_turn(self);
2965 }
2966
2967 fn release_draining(&mut self) {
2968 if !self.draining {
2969 return;
2970 }
2971 let _scheduler = self
2972 .executor
2973 .scheduler_idle_lock
2974 .lock()
2975 .unwrap_or_else(std::sync::PoisonError::into_inner);
2976 self.executor.draining.store(false, Ordering::Release);
2977 self.draining = false;
2978 }
2979}
2980
2981impl Drop for CurrentThreadHostTurn {
2982 fn drop(&mut self) {
2983 let mut scheduler = self
2984 .executor
2985 .scheduler_idle_lock
2986 .lock()
2987 .unwrap_or_else(std::sync::PoisonError::into_inner);
2988 if self.draining {
2989 self.executor.draining.store(false, Ordering::Release);
2990 self.draining = false;
2991 }
2992 scheduler.active_host_turns = scheduler
2993 .active_host_turns
2994 .checked_sub(1)
2995 .expect("CurrentThread active host-turn count underflow");
2996 self.executor.scheduler_idle.notify_all();
2997 }
2998}
2999
3000#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3007pub struct CurrentThreadTaskCallbackLease {
3008 _host_turn: CurrentThreadHostTurn,
3009 _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3010}
3011
3012impl CurrentThreadTaskCallbackLease {
3013 fn new(host_turn: CurrentThreadHostTurn) -> Self {
3014 Self {
3015 _host_turn: host_turn,
3016 _not_send: std::marker::PhantomData,
3017 }
3018 }
3019}
3020
3021struct CurrentThreadDispatchCall<'a> {
3022 executor: &'a CurrentThreadExecutor,
3023 publication: Option<u64>,
3024 _generation: RuntimeGenerationGuard,
3025}
3026
3027impl CurrentThreadDispatchCall<'_> {
3028 fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3029 let dispatch = self
3030 .publication
3031 .expect("the CurrentThread dispatch call must own a publication");
3032 let index = scheduler
3033 .dispatch_publications
3034 .iter()
3035 .position(|publication| publication.dispatch == dispatch)
3036 .expect("the CurrentThread dispatch publication must remain registered");
3037 scheduler.dispatch_publications.swap_remove(index);
3038 self.publication = None;
3039 }
3040}
3041
3042impl Drop for CurrentThreadDispatchCall<'_> {
3043 fn drop(&mut self) {
3044 let mut scheduler = self
3045 .executor
3046 .scheduler_idle_lock
3047 .lock()
3048 .unwrap_or_else(std::sync::PoisonError::into_inner);
3049 if let Some(dispatch) = self.publication.take()
3050 && let Some(index) = scheduler
3051 .dispatch_publications
3052 .iter()
3053 .position(|publication| publication.dispatch == dispatch)
3054 {
3055 scheduler.dispatch_publications.swap_remove(index);
3056 }
3057 scheduler.active_dispatch_calls = scheduler
3058 .active_dispatch_calls
3059 .checked_sub(1)
3060 .expect("CurrentThread active dispatch-call count underflow");
3061 self.executor.scheduler_idle.notify_all();
3062 }
3063}
3064
3065struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3066
3067impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3068 fn drop(&mut self) {
3069 self
3070 .0
3071 .cancelling_failed_dispatch
3072 .store(false, Ordering::Release);
3073 }
3074}
3075
3076enum CurrentThreadDispatchFailureAction<'a> {
3077 None,
3078 Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3079 Publish {
3080 dispatch: u64,
3081 call: CurrentThreadDispatchCall<'a>,
3082 },
3083}
3084
3085struct CurrentThreadDispatchFailureRecovery<'a> {
3086 action: CurrentThreadDispatchFailureAction<'a>,
3087 _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3088}
3089
3090struct CurrentThreadRegistryCancellationGuard<'a> {
3091 registry: &'a CurrentThreadTaskDriverRegistry,
3092 failure: Option<CurrentThreadTaskDispatchFailure>,
3093}
3094
3095impl CurrentThreadRegistryCancellationGuard<'_> {
3096 fn new(
3097 registry: &CurrentThreadTaskDriverRegistry,
3098 failure: CurrentThreadTaskDispatchFailure,
3099 ) -> CurrentThreadRegistryCancellationGuard<'_> {
3100 CurrentThreadRegistryCancellationGuard {
3101 registry,
3102 failure: Some(failure),
3103 }
3104 }
3105}
3106
3107impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3108 fn drop(&mut self) {
3109 if let Some(failure) = self.failure.take() {
3110 let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3111 }
3112 }
3113}
3114
3115struct CurrentThreadBlockingGuard<'a> {
3116 executor: &'a CurrentThreadExecutor,
3117 owner: BlockingOwnerToken,
3118 previous_owner: Option<BlockingOwnerToken>,
3119 owns_lane: bool,
3120}
3121
3122impl CurrentThreadBlockingGuard<'_> {
3123 fn counts_active_lane(&self) -> bool {
3124 self.owns_lane
3125 }
3126}
3127
3128impl Drop for CurrentThreadBlockingGuard<'_> {
3129 fn drop(&mut self) {
3130 BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3131 if !self.owns_lane {
3132 return;
3133 }
3134 let mut state = self
3135 .executor
3136 .blocking_admission
3137 .state
3138 .lock()
3139 .unwrap_or_else(std::sync::PoisonError::into_inner);
3140 debug_assert_eq!(state.owner, Some(self.owner));
3141 state.owner = None;
3142 drop(state);
3143 self.executor.stop.wake_all();
3144 }
3145}
3146
3147impl CurrentThreadExecutor {
3148 const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3151 #[cfg(not(target_family = "wasm"))]
3153 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3154
3155 #[cfg(test)]
3159 fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3160 Self::with_detection(metrics, THREADLESS_BUILD, None)
3161 }
3162
3163 #[cfg(test)]
3169 fn with_detection(
3170 metrics: Arc<RuntimeMetrics>,
3171 threadless: bool,
3172 park_deadline: Option<Duration>,
3173 ) -> Self {
3174 Self::with_generation(
3175 metrics,
3176 threadless,
3177 park_deadline,
3178 u64::MAX,
3179 Arc::new(GenerationStop::default()),
3180 )
3181 }
3182
3183 fn with_generation(
3184 metrics: Arc<RuntimeMetrics>,
3185 threadless: bool,
3186 park_deadline: Option<Duration>,
3187 generation: u64,
3188 stop: Arc<GenerationStop>,
3189 ) -> Self {
3190 Self {
3191 id: next_unique_id(
3192 &NEXT_RUNTIME_EXECUTOR_ID,
3193 "async runtime executor id space exhausted",
3194 ),
3195 generation,
3196 stop,
3197 queue: Mutex::new(CurrentThreadQueue {
3198 closed: false,
3199 runnables: VecDeque::new(),
3200 }),
3201 blocking_admission: CurrentThreadBlockingAdmission {
3202 state: Mutex::new(CurrentThreadBlockingAdmissionState {
3203 owner: None,
3204 closed: false,
3205 #[cfg(not(target_family = "wasm"))]
3206 queue: BlockingQueue {
3207 closed: false,
3208 head: None,
3209 tail: None,
3210 jobs: FxHashMap::default(),
3211 },
3212 }),
3213 },
3214 draining: AtomicBool::new(false),
3215 scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3216 scheduler_idle: Condvar::new(),
3217 dispatch_pending: AtomicU64::new(0),
3218 recovery_dispatch: AtomicU64::new(0),
3219 cancelling_failed_dispatch: AtomicBool::new(false),
3220 task_dispatch: dispatch_current_thread_tasks,
3221 task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3222 metrics,
3223 threadless,
3224 park_deadline,
3225 host_timers: Arc::new(HostTimerRegistry::default()),
3226 }
3227 }
3228
3229 fn fresh_owner_token(&self) -> BlockingOwnerToken {
3230 BlockingOwnerToken {
3231 executor_id: self.id,
3232 frame: next_unique_id(
3233 &NEXT_BLOCKING_FRAME,
3234 "blocking owner frame id space exhausted",
3235 ),
3236 }
3237 }
3238
3239 fn enter_blocking_owner(
3240 &self,
3241 owner: BlockingOwnerToken,
3242 owns_lane: bool,
3243 ) -> CurrentThreadBlockingGuard<'_> {
3244 let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3245 CurrentThreadBlockingGuard {
3246 executor: self,
3247 owner,
3248 previous_owner,
3249 owns_lane,
3250 }
3251 }
3252
3253 fn schedule_blocking<F, T>(
3254 self: &Arc<Self>,
3255 function: F,
3256 registration: GenerationWorkGuard,
3257 ) -> JoinHandle<T>
3258 where
3259 F: FnOnce() -> T + Send + 'static,
3260 T: Send + 'static,
3261 {
3262 self.metrics.blocking_scheduled();
3264 let mut function = Some(function);
3265 let mut registration = Some(registration);
3266 let ambient_owner = BLOCKING_OWNER
3267 .with(std::cell::Cell::get)
3268 .filter(|owner| owner.executor_id == self.id);
3269 let mut run_owner = None;
3270 let mut owns_lane = false;
3271 let mut rejected = false;
3272
3273 #[cfg(not(target_family = "wasm"))]
3274 let (sender, receiver) = oneshot::channel();
3275 #[cfg(not(target_family = "wasm"))]
3276 let dependency = BlockingDependency {
3277 job: BlockingJobId(next_unique_id(
3278 &NEXT_BLOCKING_JOB_ID,
3279 "blocking job id space exhausted",
3280 )),
3281 owner: ambient_owner,
3282 };
3283 #[cfg(not(target_family = "wasm"))]
3284 let mut queued = false;
3285
3286 {
3287 let mut state = self
3288 .blocking_admission
3289 .state
3290 .lock()
3291 .unwrap_or_else(std::sync::PoisonError::into_inner);
3292 #[cfg(all(test, not(target_family = "wasm")))]
3293 run_current_thread_blocking_claim_test_hook();
3294 if self.stop.is_stopping()
3298 || state.closed
3299 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3300 {
3301 rejected = true;
3302 } else if ambient_owner.is_some() && state.owner == ambient_owner {
3303 run_owner = ambient_owner;
3304 } else {
3305 #[cfg(not(target_family = "wasm"))]
3306 let queue_empty = state.queue.is_empty();
3307 #[cfg(target_family = "wasm")]
3308 let queue_empty = true;
3309
3310 if state.owner.is_none() && queue_empty {
3311 let owner = self.fresh_owner_token();
3312 state.owner = Some(owner);
3313 run_owner = Some(owner);
3314 owns_lane = true;
3315 } else {
3316 #[cfg(not(target_family = "wasm"))]
3317 {
3318 let function = function
3319 .take()
3320 .expect("blocking function must be available");
3321 let registration = registration
3322 .take()
3323 .expect("blocking registration must be available");
3324 let generation = self.generation;
3325 let function = RegisteredBlockingFunction::new(function, registration, generation);
3326 state.queue.push(QueuedBlocking {
3327 id: dependency.job,
3328 run: Box::new(move || {
3329 let _generation = RuntimeGenerationGuard::enter(generation);
3330 let result = function
3331 .run()
3332 .map(|output| ContainedTaskOutput::new(output, generation));
3333 let _ = sender.send(result);
3334 }),
3335 });
3336 queued = true;
3337 }
3338 #[cfg(target_family = "wasm")]
3339 {
3340 rejected = true;
3344 }
3345 }
3346 }
3347 }
3348
3349 #[cfg(not(target_family = "wasm"))]
3350 if queued {
3351 self.stop.wake_all();
3352 if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3353 self.request_drain();
3354 }
3355 return JoinHandle(JoinHandleInner::Blocking {
3356 receiver,
3357 dependency,
3358 published: PublishedBlockingDependencies::default(),
3359 });
3360 }
3361
3362 if rejected {
3363 let _generation = RuntimeGenerationGuard::enter(self.generation);
3364 let _ = catch_unwind_contained(|| drop(function.take()));
3365 drop(registration.take());
3366 return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3367 message: "the async runtime stopped before the blocking task could start".to_string(),
3368 }))));
3369 }
3370
3371 let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3372 let blocking = self.enter_blocking_owner(owner, owns_lane);
3373 let _registration = registration
3374 .take()
3375 .expect("blocking registration must be available");
3376 let _generation = RuntimeGenerationGuard::enter(self.generation);
3377 let result = {
3378 let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3379 catch_unwind_join_error(
3380 function
3381 .take()
3382 .expect("blocking function must be available"),
3383 )
3384 }
3385 .map(|output| ContainedTaskOutput::new(output, self.generation));
3386 drop(blocking);
3387 #[cfg(not(target_family = "wasm"))]
3388 if owns_lane && !self.draining.load(Ordering::Acquire) {
3389 self.service_queued_blocking_work();
3393 }
3394 JoinHandle(JoinHandleInner::Ready(Some(result)))
3395 }
3396
3397 #[cfg(not(target_family = "wasm"))]
3398 fn take_queued_blocking(
3399 &self,
3400 ) -> Option<(
3401 Box<dyn FnOnce() + Send + 'static>,
3402 CurrentThreadBlockingGuard<'_>,
3403 )> {
3404 let (job, owner) = {
3405 let mut state = self
3406 .blocking_admission
3407 .state
3408 .lock()
3409 .unwrap_or_else(std::sync::PoisonError::into_inner);
3410 #[cfg(all(test, not(target_family = "wasm")))]
3411 run_current_thread_blocking_claim_test_hook();
3412 if self.stop.is_stopping()
3413 || state.closed
3414 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3415 || state.owner.is_some()
3416 {
3417 return None;
3418 }
3419 let job = state.queue.pop_front()?;
3420 let owner = self.fresh_owner_token();
3421 state.owner = Some(owner);
3422 (job, owner)
3423 };
3424 Some((job, self.enter_blocking_owner(owner, true)))
3425 }
3426
3427 #[cfg(not(target_family = "wasm"))]
3428 fn run_one_blocking(&self) -> bool {
3429 let Some((job, blocking)) = self.take_queued_blocking() else {
3430 return false;
3431 };
3432 let _generation = RuntimeGenerationGuard::enter(self.generation);
3433 {
3434 let _task = self.metrics.blocking_started(true);
3435 let _ = catch_unwind_contained(job);
3436 }
3437 drop(blocking);
3438 true
3439 }
3440
3441 #[cfg(not(target_family = "wasm"))]
3442 fn service_queued_blocking_work(&self) {
3443 while self.run_one_blocking() {}
3444 }
3445
3446 #[cfg(not(target_family = "wasm"))]
3447 fn has_serviceable_blocking_work(&self) -> bool {
3448 let state = self
3449 .blocking_admission
3450 .state
3451 .lock()
3452 .unwrap_or_else(std::sync::PoisonError::into_inner);
3453 !state.closed && state.owner.is_none() && !state.queue.is_empty()
3454 }
3455
3456 #[cfg(not(target_family = "wasm"))]
3457 fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3458 let Some(ambient_owner) = BLOCKING_OWNER
3459 .with(std::cell::Cell::get)
3460 .filter(|owner| owner.executor_id == self.id)
3461 else {
3462 return false;
3463 };
3464 let Some(current) = dependency.select_for_owner(ambient_owner) else {
3465 return false;
3466 };
3467 let job = {
3468 let mut state = self
3469 .blocking_admission
3470 .state
3471 .lock()
3472 .unwrap_or_else(std::sync::PoisonError::into_inner);
3473 #[cfg(all(test, not(target_family = "wasm")))]
3474 run_current_thread_blocking_claim_test_hook();
3475 if self.stop.is_stopping()
3476 || state.closed
3477 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3478 || state.owner != Some(ambient_owner)
3479 {
3480 return false;
3481 }
3482 if dependency.try_claim(¤t) {
3483 state.queue.remove(current.publication.dependency.job)
3484 } else {
3485 None
3486 }
3487 };
3488 let Some(job) = job else {
3489 return false;
3490 };
3491 let _generation = RuntimeGenerationGuard::enter(self.generation);
3492 let _task = self.metrics.blocking_started(false);
3493 let _ = catch_unwind_contained(job);
3494 true
3495 }
3496
3497 fn close_blocking_admission(&self) {
3498 #[cfg(not(target_family = "wasm"))]
3499 let queued = {
3500 let mut state = self
3501 .blocking_admission
3502 .state
3503 .lock()
3504 .unwrap_or_else(std::sync::PoisonError::into_inner);
3505 state.closed = true;
3506 state.queue.closed = true;
3507 state.queue.take_all()
3508 };
3509 #[cfg(target_family = "wasm")]
3510 {
3511 let mut state = self
3512 .blocking_admission
3513 .state
3514 .lock()
3515 .unwrap_or_else(std::sync::PoisonError::into_inner);
3516 state.closed = true;
3517 }
3518 #[cfg(not(target_family = "wasm"))]
3519 for job in queued {
3520 let _ = catch_unwind_contained(|| drop(job));
3521 }
3522 }
3523
3524 #[cfg(test)]
3525 fn with_task_dispatch(
3526 metrics: Arc<RuntimeMetrics>,
3527 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3528 ) -> Self {
3529 Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
3530 }
3531
3532 #[cfg(test)]
3533 fn with_task_dispatch_registry(
3534 metrics: Arc<RuntimeMetrics>,
3535 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3536 task_drivers: &'static CurrentThreadTaskDriverRegistry,
3537 ) -> Self {
3538 let mut executor = Self::new(metrics);
3539 executor.task_dispatch = task_dispatch;
3540 executor.task_drivers = task_drivers;
3541 executor
3542 }
3543
3544 fn schedule(self: &Arc<Self>, runnable: Runnable) {
3545 self.metrics.runnable_scheduled();
3546 let rejected = {
3547 let mut queue = self
3548 .queue
3549 .lock()
3550 .unwrap_or_else(std::sync::PoisonError::into_inner);
3551 if queue.closed || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3552 Some(runnable)
3553 } else {
3554 queue.runnables.push_back(runnable);
3555 None
3556 }
3557 };
3558 if let Some(runnable) = rejected {
3559 self
3560 .metrics
3561 .queued_runnables
3562 .fetch_sub(1, Ordering::Relaxed);
3563 let _generation = RuntimeGenerationGuard::enter(self.generation);
3564 let _ = catch_unwind_contained(|| drop(runnable));
3565 return;
3566 }
3567 self.stop.wake_all();
3571 if !self.draining.load(Ordering::Acquire) {
3572 self.request_drain();
3573 }
3574 }
3575
3576 fn request_drain(self: &Arc<Self>) {
3585 let _ = self.request_drain_with(next_current_thread_dispatch_id);
3586 }
3587
3588 fn lock_scheduler_for_publication(
3589 &self,
3590 ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
3591 #[cfg(all(test, not(target_family = "wasm")))]
3592 match self.scheduler_idle_lock.try_lock() {
3593 Ok(scheduler) => scheduler,
3594 Err(std::sync::TryLockError::WouldBlock) => {
3595 run_current_thread_dispatch_publication_blocked_test_hook();
3596 self
3597 .scheduler_idle_lock
3598 .lock()
3599 .unwrap_or_else(std::sync::PoisonError::into_inner)
3600 }
3601 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
3602 }
3603 #[cfg(any(not(test), target_family = "wasm"))]
3604 self
3605 .scheduler_idle_lock
3606 .lock()
3607 .unwrap_or_else(std::sync::PoisonError::into_inner)
3608 }
3609
3610 fn request_drain_with(
3611 self: &Arc<Self>,
3612 next_dispatch: impl FnOnce() -> u64,
3613 ) -> CurrentThreadDispatchRequest {
3614 let mut scheduler = self.lock_scheduler_for_publication();
3615 if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3616 return CurrentThreadDispatchRequest::Unavailable;
3617 }
3618 if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
3619 return CurrentThreadDispatchRequest::Coalesced;
3620 }
3621 let dispatch = next_dispatch();
3622 if self
3623 .dispatch_pending
3624 .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
3625 .is_err()
3626 {
3627 return CurrentThreadDispatchRequest::Coalesced;
3628 }
3629 let dispatch_call = self
3630 .begin_host_dispatch_publication(&mut scheduler, dispatch)
3631 .expect("a fresh CurrentThread dispatch must own its publication");
3632 drop(scheduler);
3633 match self.publish_host_dispatch(dispatch, dispatch_call) {
3634 CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
3635 CurrentThreadDispatchRequest::Accepted
3636 }
3637 CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
3638 }
3639 }
3640
3641 fn claim_dispatch_call<'a>(
3642 &'a self,
3643 scheduler: &mut CurrentThreadSchedulerState,
3644 ) -> CurrentThreadDispatchCall<'a> {
3645 let generation = RuntimeGenerationGuard::enter(self.generation);
3646 let active_dispatch_calls = scheduler
3647 .active_dispatch_calls
3648 .checked_add(1)
3649 .expect("CurrentThread active dispatch-call count exhausted");
3650 scheduler.active_dispatch_calls = active_dispatch_calls;
3651 CurrentThreadDispatchCall {
3652 executor: self,
3653 publication: None,
3654 _generation: generation,
3655 }
3656 }
3657
3658 fn begin_host_dispatch_publication<'a>(
3659 &'a self,
3660 scheduler: &mut CurrentThreadSchedulerState,
3661 dispatch: u64,
3662 ) -> Option<CurrentThreadDispatchCall<'a>> {
3663 if let Some(publication) = scheduler
3665 .dispatch_publications
3666 .iter_mut()
3667 .find(|publication| publication.dispatch == dispatch)
3668 {
3669 publication.republish_requested = true;
3670 return None;
3671 }
3672 let mut dispatch_call = self.claim_dispatch_call(scheduler);
3673 scheduler
3674 .dispatch_publications
3675 .push(CurrentThreadDispatchPublication {
3676 dispatch,
3677 republish_requested: false,
3678 });
3679 dispatch_call.publication = Some(dispatch);
3680 Some(dispatch_call)
3681 }
3682
3683 fn publish_host_dispatch(
3684 self: &Arc<Self>,
3685 dispatch: u64,
3686 mut dispatch_call: CurrentThreadDispatchCall<'_>,
3687 ) -> CurrentThreadHostDispatchResult {
3688 let mut panic_payload = RetainedPanicPayload::default();
3689 loop {
3690 let result = match catch_unwind(AssertUnwindSafe(|| {
3691 #[cfg(all(test, not(target_family = "wasm")))]
3692 run_current_thread_dispatch_call_test_hook();
3693 let result = (self.task_dispatch)(dispatch);
3694 #[cfg(all(test, not(target_family = "wasm")))]
3695 run_current_thread_dispatch_returned_test_hook();
3696 result
3697 })) {
3698 Ok(result) => Some(result),
3699 Err(payload) => {
3700 panic_payload.capture(payload);
3701 None
3702 }
3703 };
3704
3705 let step = catch_unwind(AssertUnwindSafe(|| {
3706 let (republish, cancelling, failure, result) = {
3707 let mut scheduler = self
3708 .scheduler_idle_lock
3709 .lock()
3710 .unwrap_or_else(std::sync::PoisonError::into_inner);
3711 let index = scheduler
3712 .dispatch_publications
3713 .iter()
3714 .position(|publication| publication.dispatch == dispatch)
3715 .expect("the CurrentThread dispatch publication must remain registered");
3716 let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
3717 let republish = dispatch_remains_pending
3718 && !self.stop.is_stopping()
3719 && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
3720 && scheduler.dispatch_publications[index].republish_requested;
3721 if republish {
3722 if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3723 let _ = self.task_drivers.rearm_failed_publication(failure);
3724 }
3725 scheduler.dispatch_publications[index].republish_requested = false;
3726 (true, None, None, result)
3727 } else {
3728 dispatch_call.finish_publication(&mut scheduler);
3729 let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3730 #[cfg(all(test, not(target_family = "wasm")))]
3731 run_current_thread_failed_publication_retired_test_hook();
3732 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3733 } else {
3734 None
3735 };
3736 let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
3737 && dispatch_remains_pending)
3738 .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
3739 .flatten();
3740 let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
3741 && !dispatch_remains_pending
3742 {
3743 Some(CurrentThreadHostDispatchResult::Accepted)
3746 } else {
3747 result
3748 };
3749 (false, cancelling, failure, result)
3750 }
3751 };
3752
3753 if republish {
3754 return CurrentThreadDispatchPublicationStep::Republish;
3755 }
3756 if let Some(cancelling) = cancelling {
3757 self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
3758 }
3759 if let Some(failure) = failure {
3760 self.run_host_dispatch_failure(failure.action);
3761 }
3762 CurrentThreadDispatchPublicationStep::Complete(result)
3763 }));
3764
3765 match step {
3766 Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
3767 Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
3768 if panic_payload.is_some() {
3769 panic_payload.resume();
3770 }
3771 return result
3772 .expect("a CurrentThread host dispatch without a result must resume its panic");
3773 }
3774 Err(payload) => {
3775 panic_payload.capture(payload);
3776 panic_payload.resume();
3777 }
3778 }
3779 }
3780 }
3781
3782 fn claim_host_turn(
3783 self: &Arc<Self>,
3784 scheduler: &mut CurrentThreadSchedulerState,
3785 ) -> CurrentThreadHostTurn {
3786 scheduler.active_host_turns = scheduler
3787 .active_host_turns
3788 .checked_add(1)
3789 .expect("CurrentThread active host-turn count exhausted");
3790 self.draining.store(true, Ordering::Release);
3791 CurrentThreadHostTurn {
3792 executor: Arc::clone(self),
3793 draining: true,
3794 _generation: RuntimeGenerationGuard::enter(self.generation),
3795 }
3796 }
3797
3798 fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
3799 let mut scheduler = self
3800 .scheduler_idle_lock
3801 .lock()
3802 .unwrap_or_else(std::sync::PoisonError::into_inner);
3803 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3804 return None;
3805 }
3806 if dispatch == 0
3807 || self
3808 .dispatch_pending
3809 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3810 .is_err()
3811 {
3812 return None;
3813 }
3814 #[cfg(all(test, not(target_family = "wasm")))]
3815 run_current_thread_dispatch_claim_test_hook();
3816 let _ =
3817 self
3818 .recovery_dispatch
3819 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
3820 Some(self.claim_host_turn(&mut scheduler))
3821 }
3822
3823 #[cfg(test)]
3824 fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
3825 let mut scheduler = self
3826 .scheduler_idle_lock
3827 .lock()
3828 .unwrap_or_else(std::sync::PoisonError::into_inner);
3829 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3830 return None;
3831 }
3832 Some(self.claim_host_turn(&mut scheduler))
3833 }
3834
3835 #[cfg(test)]
3836 fn drive_host_turn(self: &Arc<Self>) {
3837 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3838 let host_turn = if dispatch == 0 {
3839 self.try_admit_host_turn_without_dispatch()
3840 } else {
3841 self.try_admit_host_turn(dispatch)
3842 };
3843 if let Some(mut host_turn) = host_turn {
3844 host_turn.drive();
3845 }
3846 }
3847
3848 #[cfg(test)]
3849 fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
3850 if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
3851 host_turn.drive();
3852 }
3853 }
3854
3855 fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
3859 #[cfg(not(target_family = "wasm"))]
3860 let mut runnable_streak = 0usize;
3861 for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
3862 #[cfg(not(target_family = "wasm"))]
3863 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
3864 runnable_streak = 0;
3865 if self.run_one_blocking() {
3866 continue;
3867 }
3868 }
3869 if self.drain_one() {
3870 #[cfg(not(target_family = "wasm"))]
3871 {
3872 runnable_streak += 1;
3873 }
3874 continue;
3875 }
3876 #[cfg(not(target_family = "wasm"))]
3877 if self.run_one_blocking() {
3878 runnable_streak = 0;
3879 continue;
3880 }
3881 break;
3882 }
3883 host_turn.release_draining();
3888 self.request_drain_if_work_remains();
3889 }
3890
3891 fn request_drain_if_queued(self: &Arc<Self>) {
3892 if !self.has_serviceable_work() {
3893 return;
3894 }
3895 let mut scheduler = self.lock_scheduler_for_publication();
3896 if self.stop.is_stopping()
3897 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3898 || self.draining.load(Ordering::Acquire)
3899 {
3900 return;
3901 }
3902 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3903 if dispatch == 0 {
3904 drop(scheduler);
3905 self.request_drain_if_work_remains();
3906 return;
3907 }
3908
3909 let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
3913 return;
3914 };
3915 drop(scheduler);
3916 let _ = self.publish_host_dispatch(dispatch, dispatch_call);
3917 }
3918
3919 fn retire_unavailable_host_dispatch_locked(
3920 &self,
3921 dispatch: u64,
3922 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
3923 if self
3924 .dispatch_pending
3925 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3926 .is_err()
3927 {
3928 return None;
3929 }
3930 if self
3931 .recovery_dispatch
3932 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3933 .is_ok()
3934 {
3935 self.begin_failed_dispatch_cancellation()
3936 } else {
3937 None
3938 }
3939 }
3940
3941 fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
3942 let failure = {
3944 let mut scheduler = self
3945 .scheduler_idle_lock
3946 .lock()
3947 .unwrap_or_else(std::sync::PoisonError::into_inner);
3948 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3949 };
3950 if let Some(failure) = failure {
3951 self.run_host_dispatch_failure(failure.action);
3952 }
3953 }
3954
3955 fn begin_host_dispatch_failure_locked<'a>(
3956 &'a self,
3957 scheduler: &mut CurrentThreadSchedulerState,
3958 failure: CurrentThreadTaskDispatchFailure,
3959 ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
3960 if !self.task_drivers.is_current_failure(failure) {
3961 return None;
3962 }
3963 let registry_cancellation =
3964 CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
3965 let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
3966 Some(CurrentThreadDispatchFailureRecovery {
3967 action,
3968 _registry_cancellation: registry_cancellation,
3969 })
3970 }
3971
3972 fn begin_host_dispatch_failure_transition_locked<'a>(
3973 &'a self,
3974 scheduler: &mut CurrentThreadSchedulerState,
3975 dispatch: u64,
3976 ) -> CurrentThreadDispatchFailureAction<'a> {
3977 if dispatch == 0
3978 || self
3979 .dispatch_pending
3980 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3981 .is_err()
3982 {
3983 return CurrentThreadDispatchFailureAction::None;
3984 }
3985 #[cfg(all(test, not(target_family = "wasm")))]
3986 run_current_thread_dispatch_recovery_test_hook();
3987
3988 if self
3989 .recovery_dispatch
3990 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3991 .is_ok()
3992 {
3993 return self.begin_failed_dispatch_cancellation().map_or(
3997 CurrentThreadDispatchFailureAction::None,
3998 CurrentThreadDispatchFailureAction::Cancel,
3999 );
4000 }
4001
4002 if self.stop.is_stopping()
4003 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4004 || self.draining.load(Ordering::Acquire)
4005 || !self.has_serviceable_work()
4006 {
4007 return CurrentThreadDispatchFailureAction::None;
4008 }
4009
4010 let replacement = next_current_thread_dispatch_id();
4014 self.recovery_dispatch.store(replacement, Ordering::Release);
4015 self.dispatch_pending.store(replacement, Ordering::Release);
4016 let dispatch_call = self
4017 .begin_host_dispatch_publication(scheduler, replacement)
4018 .expect("a reserved CurrentThread replacement must own its publication");
4019 CurrentThreadDispatchFailureAction::Publish {
4020 dispatch: replacement,
4021 call: dispatch_call,
4022 }
4023 }
4024
4025 #[cfg(test)]
4026 fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4027 let failure = {
4028 let mut scheduler = self
4029 .scheduler_idle_lock
4030 .lock()
4031 .unwrap_or_else(std::sync::PoisonError::into_inner);
4032 self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4033 };
4034 self.run_host_dispatch_failure(failure);
4035 }
4036
4037 fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4038 match failure {
4039 CurrentThreadDispatchFailureAction::None => {}
4040 CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4041 self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
4042 }
4043 CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4044 let _ = self.publish_host_dispatch(dispatch, call);
4045 }
4046 }
4047 }
4048
4049 fn has_serviceable_work(&self) -> bool {
4050 let has_queued_runnable = {
4051 let queue = self
4052 .queue
4053 .lock()
4054 .unwrap_or_else(std::sync::PoisonError::into_inner);
4055 !queue.closed && !queue.runnables.is_empty()
4056 };
4057 #[cfg(not(target_family = "wasm"))]
4058 let has_queued_blocking = self.has_serviceable_blocking_work();
4059 #[cfg(target_family = "wasm")]
4060 let has_queued_blocking = false;
4061 has_queued_runnable || has_queued_blocking
4062 }
4063
4064 fn request_drain_if_work_remains(self: &Arc<Self>) {
4065 if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4066 self.request_drain();
4067 }
4068 }
4069
4070 fn begin_failed_dispatch_cancellation(
4071 &self,
4072 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4073 if self
4074 .cancelling_failed_dispatch
4075 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4076 .is_err()
4077 {
4078 return None;
4079 }
4080 self.dispatch_pending.store(0, Ordering::Release);
4081 self.recovery_dispatch.store(0, Ordering::Release);
4082 Some(CurrentThreadDispatchCancellationGuard(self))
4083 }
4084
4085 fn cancel_work_after_host_dispatch_failure_claimed(
4086 &self,
4087 _cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4088 ) {
4089 let queued = {
4090 let mut queue = self
4091 .queue
4092 .lock()
4093 .unwrap_or_else(std::sync::PoisonError::into_inner);
4094 std::mem::take(&mut queue.runnables)
4095 };
4096 #[cfg(not(target_family = "wasm"))]
4097 let queued_blocking = {
4098 let mut state = self
4099 .blocking_admission
4100 .state
4101 .lock()
4102 .unwrap_or_else(std::sync::PoisonError::into_inner);
4103 state.queue.take_all()
4104 };
4105
4106 let _generation = RuntimeGenerationGuard::enter(self.generation);
4107 for runnable in queued {
4108 self
4109 .metrics
4110 .queued_runnables
4111 .fetch_sub(1, Ordering::Relaxed);
4112 let _ = catch_unwind_contained(|| drop(runnable));
4113 }
4114 #[cfg(not(target_family = "wasm"))]
4115 for job in queued_blocking {
4116 let _ = catch_unwind_contained(|| drop(job));
4117 }
4118 }
4119
4120 fn take_runnable(&self) -> Option<Runnable> {
4121 let scheduler = self
4126 .scheduler_idle_lock
4127 .lock()
4128 .unwrap_or_else(std::sync::PoisonError::into_inner);
4129 #[cfg(all(test, not(target_family = "wasm")))]
4130 run_current_thread_runnable_claim_test_hook();
4131 let stop_publication = self.stop.publication_guard();
4132 if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4133 return None;
4134 }
4135 let runnable = {
4136 let mut queue = self
4137 .queue
4138 .lock()
4139 .unwrap_or_else(std::sync::PoisonError::into_inner);
4140 if queue.closed {
4141 None
4142 } else {
4143 queue.runnables.pop_front()
4144 }
4145 };
4146 drop(stop_publication);
4147 drop(scheduler);
4148 runnable
4149 }
4150
4151 fn drain_one(&self) -> bool {
4152 let runnable = self.take_runnable();
4153 if let Some(runnable) = runnable {
4154 #[cfg(not(target_family = "wasm"))]
4155 let _non_owner = BlockingOwnerGuard::enter(None);
4156 run_runnable(&self.metrics, runnable);
4157 true
4158 } else {
4159 false
4160 }
4161 }
4162
4163 fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4185 let parker = Arc::new(DriverParker::default());
4186 let _stop_registration = self.stop.register(&parker);
4187 #[cfg(not(target_family = "wasm"))]
4188 let dependency = BlockOnDependencyGuard::enter(self.generation);
4189 let waker = std::task::Waker::from(Arc::clone(&parker));
4190 let mut cx = Context::from_waker(&waker);
4191 #[cfg(not(target_family = "wasm"))]
4192 let mut runnable_streak = 0usize;
4193 loop {
4194 if self.stop.is_stopping() {
4195 return BlockOnOutcome::Stopped;
4196 }
4197 #[cfg(not(target_family = "wasm"))]
4198 dependency.clear();
4199 if future.as_mut().poll(&mut cx).is_ready() {
4200 return BlockOnOutcome::Completed;
4201 }
4202 if self.stop.is_stopping() {
4206 return BlockOnOutcome::Stopped;
4207 }
4208 #[cfg(not(target_family = "wasm"))]
4209 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4210 runnable_streak = 0;
4211 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4212 continue;
4213 }
4214 }
4215 if self.drain_one() {
4216 #[cfg(not(target_family = "wasm"))]
4217 {
4218 runnable_streak += 1;
4219 }
4220 continue;
4221 }
4222 #[cfg(not(target_family = "wasm"))]
4223 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4224 runnable_streak = 0;
4225 continue;
4226 }
4227 #[cfg(not(target_family = "wasm"))]
4228 {
4229 runnable_streak = 0;
4230 }
4231 if parker.consume_permit() {
4235 continue;
4236 }
4237 if self.threadless {
4249 if self.host_timers.has_pending() {
4250 std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
4251 }
4252 std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
4253 }
4254 match self.park_deadline {
4255 None => parker.park(),
4259 Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
4260 DeadlineParkOutcome::Woken => {}
4261 DeadlineParkOutcome::Expired(armed) => {
4262 if parker.consume_permit()
4277 || self.metrics.progress_fingerprint() != armed
4278 || self.host_timers.has_live_wait(deadline)
4279 {
4280 continue;
4281 }
4282 std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
4283 }
4284 },
4285 }
4286 }
4287 }
4288
4289 fn begin_shutdown(&self) {
4290 let _generation = RuntimeGenerationGuard::enter(self.generation);
4291 self.stop.begin_shutdown();
4292 self.close_blocking_admission();
4293 let queued = {
4294 let mut queue = self
4295 .queue
4296 .lock()
4297 .unwrap_or_else(std::sync::PoisonError::into_inner);
4298 queue.closed = true;
4299 std::mem::take(&mut queue.runnables)
4300 };
4301 self.dispatch_pending.store(0, Ordering::Release);
4302 self.recovery_dispatch.store(0, Ordering::Release);
4303 self.task_drivers.retire_for_shutdown();
4304 for runnable in queued {
4305 self
4306 .metrics
4307 .queued_runnables
4308 .fetch_sub(1, Ordering::Relaxed);
4309 let _ = catch_unwind_contained(|| drop(runnable));
4312 }
4313 self.host_timers.shutdown();
4314 }
4315
4316 fn wait_until_scheduler_idle(&self) {
4317 let mut idle = self
4318 .scheduler_idle_lock
4319 .lock()
4320 .unwrap_or_else(std::sync::PoisonError::into_inner);
4321 while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
4322 idle = self
4323 .scheduler_idle
4324 .wait(idle)
4325 .unwrap_or_else(std::sync::PoisonError::into_inner);
4326 }
4327 debug_assert!(
4328 idle.dispatch_publications.is_empty(),
4329 "an idle CurrentThread scheduler must not retain dispatch publications"
4330 );
4331 }
4332}
4333
4334#[cfg(not(target_family = "wasm"))]
4347thread_local! {
4348 static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4349 static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4353}
4354
4355#[cfg(not(target_family = "wasm"))]
4361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4362struct SchedulerDriverRole {
4363 executor_id: u64,
4364 can_run_blocking: bool,
4365}
4366
4367#[cfg(not(target_family = "wasm"))]
4368thread_local! {
4369 static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
4370 const { std::cell::Cell::new(None) };
4371}
4372
4373#[cfg(not(target_family = "wasm"))]
4390thread_local! {
4391 static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
4392 const { std::cell::Cell::new(None) };
4393}
4394
4395#[cfg(not(target_family = "wasm"))]
4396struct OnPoolWorkerGuard {
4397 previous_worker: Option<u64>,
4398 previous_driver: Option<SchedulerDriverRole>,
4399}
4400
4401#[cfg(not(target_family = "wasm"))]
4402impl OnPoolWorkerGuard {
4403 fn enter(id: u64) -> Self {
4407 Self::enter_with_role(id, true)
4408 }
4409
4410 fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
4411 Self {
4412 previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
4413 previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
4414 flag.replace(Some(SchedulerDriverRole {
4415 executor_id: id,
4416 can_run_blocking,
4417 }))
4418 }),
4419 }
4420 }
4421}
4422
4423#[cfg(not(target_family = "wasm"))]
4424impl Drop for OnPoolWorkerGuard {
4425 fn drop(&mut self) {
4426 IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
4427 ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
4428 }
4429}
4430
4431#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
4444struct BlockingOwnerToken {
4445 executor_id: u64,
4446 frame: u64,
4447}
4448
4449#[cfg(not(target_family = "wasm"))]
4450enum OwnedBlockingOutcome {
4451 Ineligible,
4452 OwnerBusy(BlockingOwnerToken),
4453 ReservationReleased {
4454 owner: BlockingOwnerToken,
4455 ran: bool,
4456 },
4457}
4458
4459static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
4462#[cfg(not(target_family = "wasm"))]
4463static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
4464
4465thread_local! {
4468 static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
4469 const { std::cell::Cell::new(None) };
4470}
4471
4472#[cfg(not(target_family = "wasm"))]
4479struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
4480
4481#[cfg(not(target_family = "wasm"))]
4482impl BlockingOwnerGuard {
4483 fn enter(token: Option<BlockingOwnerToken>) -> Self {
4484 Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
4485 }
4486}
4487
4488#[cfg(not(target_family = "wasm"))]
4489impl Drop for BlockingOwnerGuard {
4490 fn drop(&mut self) {
4491 BLOCKING_OWNER.with(|cell| cell.set(self.0));
4492 }
4493}
4494
4495#[cfg(not(target_family = "wasm"))]
4496#[derive(Default)]
4497struct ActiveBlockingOwners {
4498 owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
4502}
4503
4504#[cfg(not(target_family = "wasm"))]
4505impl ActiveBlockingOwners {
4506 fn register(&self, owner: BlockingOwnerToken) {
4507 let previous = self
4508 .owners
4509 .lock()
4510 .unwrap_or_else(std::sync::PoisonError::into_inner)
4511 .insert(owner, None);
4512 debug_assert!(previous.is_none());
4513 }
4514
4515 fn unregister(&self, owner: BlockingOwnerToken) {
4516 let removed = self
4517 .owners
4518 .lock()
4519 .unwrap_or_else(std::sync::PoisonError::into_inner)
4520 .remove(&owner);
4521 debug_assert!(removed.is_some());
4522 }
4523
4524 fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
4525 let mut owners = self
4526 .owners
4527 .lock()
4528 .unwrap_or_else(std::sync::PoisonError::into_inner);
4529 let reservation = owners.get_mut(&owner)?;
4530 if reservation.is_some() {
4531 return None;
4532 }
4533 let id = next_unique_id(
4534 &NEXT_OWNER_RESERVATION,
4535 "blocking owner reservation id space exhausted",
4536 );
4537 *reservation = Some(id);
4538 Some(id)
4539 }
4540
4541 fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
4542 let mut owners = self
4543 .owners
4544 .lock()
4545 .unwrap_or_else(std::sync::PoisonError::into_inner);
4546 if let Some(current) = owners.get_mut(&owner)
4547 && *current == Some(reservation)
4548 {
4549 *current = None;
4550 }
4551 }
4552
4553 fn is_available(&self, owner: BlockingOwnerToken) -> bool {
4554 self
4555 .owners
4556 .lock()
4557 .unwrap_or_else(std::sync::PoisonError::into_inner)
4558 .get(&owner)
4559 .is_some_and(Option::is_none)
4560 }
4561}
4562
4563#[cfg(not(target_family = "wasm"))]
4564struct ActiveBlockingOwnerGuard<'a> {
4565 owners: &'a ActiveBlockingOwners,
4566 owner: BlockingOwnerToken,
4567}
4568
4569#[cfg(not(target_family = "wasm"))]
4570impl<'a> ActiveBlockingOwnerGuard<'a> {
4571 fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
4572 owners.register(owner);
4573 Self { owners, owner }
4574 }
4575}
4576
4577#[cfg(not(target_family = "wasm"))]
4578impl Drop for ActiveBlockingOwnerGuard<'_> {
4579 fn drop(&mut self) {
4580 self.owners.unregister(self.owner);
4581 }
4582}
4583
4584#[cfg(not(target_family = "wasm"))]
4585struct OwnerLaneReservation<'a> {
4586 owners: &'a ActiveBlockingOwners,
4587 owner: BlockingOwnerToken,
4588 reservation: u64,
4589}
4590
4591#[cfg(not(target_family = "wasm"))]
4592impl<'a> OwnerLaneReservation<'a> {
4593 fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
4594 let reservation = owners.reserve(owner)?;
4595 Some(Self {
4596 owners,
4597 owner,
4598 reservation,
4599 })
4600 }
4601}
4602
4603#[cfg(not(target_family = "wasm"))]
4604impl Drop for OwnerLaneReservation<'_> {
4605 fn drop(&mut self) {
4606 self.owners.release(self.owner, self.reservation);
4607 }
4608}
4609
4610#[cfg(not(target_family = "wasm"))]
4614type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
4615
4616#[cfg(not(target_family = "wasm"))]
4617struct QueuedBlocking {
4618 id: BlockingJobId,
4619 run: BlockingClosure,
4620}
4621
4622#[cfg(not(target_family = "wasm"))]
4623struct BlockingQueueNode {
4624 previous: Option<BlockingJobId>,
4625 next: Option<BlockingJobId>,
4626 run: Box<dyn FnOnce() + Send + 'static>,
4627}
4628
4629#[cfg(not(target_family = "wasm"))]
4630struct BlockingQueue {
4631 closed: bool,
4632 head: Option<BlockingJobId>,
4633 tail: Option<BlockingJobId>,
4634 jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
4635}
4636
4637#[cfg(not(target_family = "wasm"))]
4638impl BlockingQueue {
4639 fn push(&mut self, job: QueuedBlocking) {
4640 let previous = self.tail;
4641 let replaced = self.jobs.insert(
4642 job.id,
4643 BlockingQueueNode {
4644 previous,
4645 next: None,
4646 run: job.run,
4647 },
4648 );
4649 debug_assert!(replaced.is_none());
4650 if let Some(previous) = previous {
4651 self
4652 .jobs
4653 .get_mut(&previous)
4654 .expect("blocking queue tail must be indexed")
4655 .next = Some(job.id);
4656 } else {
4657 self.head = Some(job.id);
4658 }
4659 self.tail = Some(job.id);
4660 }
4661
4662 fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4663 self.head.and_then(|id| self.remove(id))
4664 }
4665
4666 fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4667 let node = self.jobs.remove(&id)?;
4668 if let Some(previous) = node.previous {
4669 self
4670 .jobs
4671 .get_mut(&previous)
4672 .expect("blocking queue predecessor must be indexed")
4673 .next = node.next;
4674 } else {
4675 self.head = node.next;
4676 }
4677 if let Some(next) = node.next {
4678 self
4679 .jobs
4680 .get_mut(&next)
4681 .expect("blocking queue successor must be indexed")
4682 .previous = node.previous;
4683 } else {
4684 self.tail = node.previous;
4685 }
4686 Some(node.run)
4687 }
4688
4689 fn is_empty(&self) -> bool {
4690 debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
4691 debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
4692 self.jobs.is_empty()
4693 }
4694
4695 fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
4696 self.head = None;
4697 self.tail = None;
4698 std::mem::take(&mut self.jobs)
4699 .into_values()
4700 .map(|node| node.run)
4701 .collect()
4702 }
4703}
4704
4705#[cfg(not(target_family = "wasm"))]
4706struct WorkerLifecycle {
4707 remaining: Mutex<usize>,
4708 handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
4709}
4710
4711#[cfg(not(target_family = "wasm"))]
4712impl WorkerLifecycle {
4713 fn new(worker_threads: usize) -> Arc<Self> {
4714 Arc::new(Self {
4715 remaining: Mutex::new(worker_threads),
4716 handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
4717 })
4718 }
4719
4720 fn retain(&self, handle: std::thread::JoinHandle<()>) {
4721 self
4722 .handles
4723 .lock()
4724 .unwrap_or_else(std::sync::PoisonError::into_inner)
4725 .as_mut()
4726 .expect("runtime worker handles already joined")
4727 .push(handle);
4728 }
4729
4730 fn worker_exited(&self) {
4731 let mut remaining = self
4732 .remaining
4733 .lock()
4734 .unwrap_or_else(std::sync::PoisonError::into_inner);
4735 *remaining = remaining
4736 .checked_sub(1)
4737 .expect("runtime worker exit count underflow");
4738 }
4739
4740 fn wait_for_all_workers(&self) {
4741 let handles = self
4742 .handles
4743 .lock()
4744 .unwrap_or_else(std::sync::PoisonError::into_inner)
4745 .take()
4746 .expect("runtime worker handles already joined");
4747 for handle in handles {
4748 if let Err(payload) = handle.join() {
4749 discard_panic_payload(payload);
4750 }
4751 }
4752 }
4753
4754 #[cfg(test)]
4755 fn remaining(&self) -> usize {
4756 *self
4757 .remaining
4758 .lock()
4759 .unwrap_or_else(std::sync::PoisonError::into_inner)
4760 }
4761}
4762
4763#[cfg(not(target_family = "wasm"))]
4764struct MultiThreadExecutor {
4765 generation: u64,
4766 stop: Arc<GenerationStop>,
4767 id: u64,
4770 pool: ThreadPool,
4771 injector: Injector<Runnable>,
4778 blocking_queue: Mutex<BlockingQueue>,
4779 active_drainers: AtomicUsize,
4780 active_blocking: AtomicUsize,
4781 scheduler_idle_lock: Mutex<usize>,
4789 scheduler_idle: Condvar,
4790 worker_lifecycle: Arc<WorkerLifecycle>,
4791 parked_drivers: ParkedDrivers,
4799 active_blocking_owners: ActiveBlockingOwners,
4800 #[cfg(test)]
4801 owner_lending_attempts: AtomicUsize,
4802 #[cfg(test)]
4812 drain_linger_registrations: AtomicUsize,
4813 #[cfg(test)]
4814 drain_linger_expiries: AtomicUsize,
4815 max_drainers: usize,
4816 max_blocking: usize,
4817 park_deadline: Option<Duration>,
4824 drain_linger: Option<Duration>,
4829 deadlock_state: Arc<MultiThreadDeadlockState>,
4833 timers: TimerHeap,
4836 metrics: Arc<RuntimeMetrics>,
4837}
4838
4839#[cfg(not(target_family = "wasm"))]
4840impl MultiThreadExecutor {
4841 const RUNNABLE_BUDGET: usize = 64;
4844 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
4847
4848 #[cfg(test)]
4849 fn new(
4850 options: &RuntimeOptions,
4851 metrics: Arc<RuntimeMetrics>,
4852 ) -> Result<Self, RuntimeConfigError> {
4853 Self::new_for_generation(
4854 options,
4855 metrics,
4856 u64::MAX,
4857 Arc::new(GenerationStop::default()),
4858 )
4859 }
4860
4861 fn new_for_generation(
4862 options: &RuntimeOptions,
4863 metrics: Arc<RuntimeMetrics>,
4864 generation: u64,
4865 stop: Arc<GenerationStop>,
4866 ) -> Result<Self, RuntimeConfigError> {
4867 let id = next_unique_id(
4868 &NEXT_RUNTIME_EXECUTOR_ID,
4869 "async runtime executor id space exhausted",
4870 );
4871 let thread_name_prefix = options.thread_name_prefix.clone();
4872 let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
4873 options.park_deadline.is_some(),
4874 ));
4875 let timers = TimerHeap::new(
4876 generation,
4877 id,
4878 format!("{thread_name_prefix}-timer"),
4879 Arc::clone(&deadlock_state),
4880 );
4881 let pool_threads = options.worker_threads;
4885 let worker_lifecycle = WorkerLifecycle::new(pool_threads);
4886 let exit_lifecycle = Arc::clone(&worker_lifecycle);
4887 let spawn_lifecycle = Arc::clone(&worker_lifecycle);
4888 let pool = ThreadPoolBuilder::new()
4889 .num_threads(pool_threads)
4890 .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
4891 .start_handler(move |_| {
4892 ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
4893 })
4894 .exit_handler(move |_| {
4895 IN_SCHEDULER_DRIVER.with(|driver| {
4896 if driver.get().is_some_and(|role| role.executor_id == id) {
4897 driver.set(None);
4898 }
4899 });
4900 exit_lifecycle.worker_exited();
4906 })
4907 .spawn_handler(move |thread| {
4908 let mut builder = std::thread::Builder::new();
4909 if let Some(name) = thread.name() {
4910 builder = builder.name(name.to_string());
4911 }
4912 if let Some(stack_size) = thread.stack_size() {
4913 builder = builder.stack_size(stack_size);
4914 }
4915 let handle = builder.spawn(move || thread.run())?;
4916 spawn_lifecycle.retain(handle);
4917 Ok(())
4918 })
4919 .build()
4920 .map_err(|error| {
4921 worker_lifecycle.wait_for_all_workers();
4922 RuntimeConfigError(format!("failed to create runtime workers: {error}"))
4923 })?;
4924 Ok(Self {
4925 generation,
4926 stop,
4927 id,
4928 pool,
4929 injector: Injector::new(),
4930 blocking_queue: Mutex::new(BlockingQueue {
4931 closed: false,
4932 head: None,
4933 tail: None,
4934 jobs: FxHashMap::default(),
4935 }),
4936 active_drainers: AtomicUsize::new(0),
4937 active_blocking: AtomicUsize::new(0),
4938 scheduler_idle_lock: Mutex::new(0),
4939 scheduler_idle: Condvar::new(),
4940 worker_lifecycle,
4941 parked_drivers: ParkedDrivers::default(),
4942 active_blocking_owners: ActiveBlockingOwners::default(),
4943 #[cfg(test)]
4944 owner_lending_attempts: AtomicUsize::new(0),
4945 #[cfg(test)]
4946 drain_linger_registrations: AtomicUsize::new(0),
4947 #[cfg(test)]
4948 drain_linger_expiries: AtomicUsize::new(0),
4949 max_drainers: pool_threads,
4950 max_blocking: options.max_blocking_tasks,
4951 park_deadline: options.park_deadline,
4952 drain_linger: {
4953 let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
4957 (!budget.is_zero()).then_some(budget)
4958 },
4959 deadlock_state,
4960 timers,
4961 metrics,
4962 })
4963 }
4964
4965 fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
4966 self.deadlock_state.begin_admission()
4967 }
4968
4969 fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
4970 self.deadlock_state.try_begin_verdict()
4971 }
4972
4973 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
4974 let _publication = self.deadlock_state.owner_handoff_publication_guard();
4976 self.parked_drivers.wake_one_owner_dependency(owner)
4977 }
4978
4979 fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
4980 let owner = reservation.owner;
4981 let _publication = self.deadlock_state.owner_handoff_publication_guard();
4985 drop(reservation);
4986 self.parked_drivers.wake_one_owner_dependency(owner)
4987 }
4988
4989 fn schedule(self: &Arc<Self>, runnable: Runnable) {
4990 let mut admission = self.begin_deadlock_admission();
4991 self.metrics.runnable_scheduled();
4994 if IN_SCHEDULER_DRIVER
5014 .with(std::cell::Cell::get)
5015 .is_some_and(|role| role.executor_id == self.id)
5016 && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5017 {
5018 match LIFO_SLOT.with(std::cell::Cell::take) {
5019 None => {
5020 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5021 if let Some(admission) = admission.as_mut() {
5022 admission.mark_published();
5023 }
5024 return;
5025 }
5026 Some((id, displaced)) if id == self.id => {
5030 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5031 self.push_to_queue_and_wake(displaced);
5032 if let Some(admission) = admission.as_mut() {
5033 admission.mark_published();
5034 }
5035 return;
5036 }
5037 Some(foreign) => {
5044 LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5045 }
5046 }
5047 }
5048 self.push_to_queue_and_wake(runnable);
5049 if let Some(admission) = admission.as_mut() {
5050 admission.mark_published();
5051 }
5052 }
5053
5054 fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5058 self.injector.push(runnable);
5059 std::sync::atomic::fence(Ordering::SeqCst);
5068 self.wake_for_new_work();
5069 }
5070
5071 fn wake_for_new_work(self: &Arc<Self>) {
5080 if !self.parked_drivers.wake_one() {
5081 self.ensure_drainer();
5082 }
5083 }
5084
5085 fn wake_for_blocking_work(self: &Arc<Self>) {
5086 if !self.parked_drivers.wake_one_blocking() {
5089 self.ensure_drainer();
5090 }
5091 }
5092
5093 #[cfg(test)]
5094 fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5095 where
5096 F: FnOnce() -> T + Send + 'static,
5097 T: Send + 'static,
5098 {
5099 self.schedule_blocking_result(move || catch_unwind_join_error(function))
5100 }
5101
5102 fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5103 where
5104 F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5105 T: Send + 'static,
5106 {
5107 let mut admission = self.begin_deadlock_admission();
5108 let (sender, receiver) = oneshot::channel();
5109 let generation = self.generation;
5110 let dependency = BlockingDependency {
5111 job: BlockingJobId(next_unique_id(
5112 &NEXT_BLOCKING_JOB_ID,
5113 "blocking job id space exhausted",
5114 )),
5115 owner: BLOCKING_OWNER
5116 .with(std::cell::Cell::get)
5117 .filter(|owner| owner.executor_id == self.id),
5118 };
5119 self.metrics.blocking_scheduled();
5124 let queued = QueuedBlocking {
5125 id: dependency.job,
5126 run: Box::new(move || {
5127 let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5128 let _ = sender.send(result);
5129 }),
5130 };
5131 let rejected = {
5132 let mut queue = self
5133 .blocking_queue
5134 .lock()
5135 .unwrap_or_else(std::sync::PoisonError::into_inner);
5136 if queue.closed {
5137 Some(queued)
5138 } else {
5139 queue.push(queued);
5140 None
5141 }
5142 };
5143 if let Some(rejected) = rejected {
5144 let _generation = RuntimeGenerationGuard::enter(self.generation);
5149 let _ = catch_unwind_contained(|| drop(rejected));
5150 } else {
5151 self.wake_for_blocking_work();
5153 if let Some(admission) = admission.as_mut() {
5154 admission.mark_published();
5155 }
5156 }
5157 JoinHandle(JoinHandleInner::Blocking {
5158 receiver,
5159 dependency,
5160 published: PublishedBlockingDependencies::default(),
5161 })
5162 }
5163
5164 fn ensure_drainer(self: &Arc<Self>) {
5165 loop {
5166 let active = self.active_drainers.load(Ordering::Acquire);
5167 if active >= self.max_drainers {
5168 return;
5169 }
5170 if self
5171 .active_drainers
5172 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5173 .is_ok()
5174 {
5175 let executor = Arc::clone(self);
5176 self.pool.spawn_fifo(move || executor.drain());
5177 return;
5178 }
5179 }
5180 }
5181
5182 fn drain(self: Arc<Self>) {
5183 let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5189 let _on_pool = OnPoolWorkerGuard::enter(self.id);
5192 let _slot_backstop = LifoSlotFlushGuard(&self);
5198
5199 let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5200 let mut linger: Option<DrainLinger> = None;
5203 let mut idle_deadline: Option<Instant> = None;
5209 let mut frame_deadline: Option<Instant> = None;
5215
5216 'frame: loop {
5217 let mut runnable_streak = 0usize;
5218 for _ in 0..Self::RUNNABLE_BUDGET {
5219 if self.run_one_fair(&mut runnable_streak) {
5220 idle_deadline = None;
5221 continue;
5222 }
5223 self.flush_lifo_slot();
5231 let Some(budget) = linger_budget else {
5253 break 'frame;
5254 };
5255 if self.stop.is_stopping() {
5256 break 'frame;
5257 }
5258 let now = Instant::now();
5259 let frame = *frame_deadline.get_or_insert_with(|| {
5265 now
5266 + budget
5267 .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
5268 .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
5269 });
5270 let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
5271 if now >= deadline {
5272 #[cfg(test)]
5279 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5280 break 'frame;
5281 }
5282 let state = match &linger {
5283 Some(state) => state,
5284 None => linger.insert(DrainLinger::new(&self)),
5285 };
5286 self
5287 .parked_drivers
5288 .register_with_role(&state.parker, true, None);
5289 #[cfg(test)]
5290 self
5291 .drain_linger_registrations
5292 .fetch_add(1, Ordering::SeqCst);
5293 if self.has_queued_work_for_role(true) {
5294 self.parked_drivers.deregister(&state.parker);
5295 continue 'frame;
5296 }
5297 if self.stop.is_stopping() {
5302 self.parked_drivers.deregister(&state.parker);
5303 break 'frame;
5304 }
5305 let woken = state.parker.park_timeout(deadline - now);
5306 self.parked_drivers.deregister(&state.parker);
5307 if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
5308 continue 'frame;
5309 }
5310 #[cfg(test)]
5315 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5316 break 'frame;
5317 }
5318 break 'frame;
5323 }
5324
5325 self.flush_lifo_slot();
5328 self.finish_draining();
5329 }
5330
5331 fn pop_lifo_slot(&self) -> Option<Runnable> {
5335 LIFO_SLOT.with(|slot| match slot.take() {
5336 Some((id, runnable)) if id == self.id => Some(runnable),
5337 other => {
5338 slot.set(other);
5339 None
5340 }
5341 })
5342 }
5343
5344 fn flush_lifo_slot(self: &Arc<Self>) {
5351 let mut admission = self.begin_deadlock_admission();
5352 if let Some(runnable) = self.pop_lifo_slot() {
5353 self.push_to_queue_and_wake(runnable);
5354 if let Some(admission) = admission.as_mut() {
5355 admission.mark_published();
5356 }
5357 }
5358 }
5359
5360 fn fresh_owner_token(&self) -> BlockingOwnerToken {
5364 BlockingOwnerToken {
5365 executor_id: self.id,
5366 frame: next_unique_id(
5367 &NEXT_BLOCKING_FRAME,
5368 "blocking owner frame id space exhausted",
5369 ),
5370 }
5371 }
5372
5373 fn finish_draining(self: &Arc<Self>) {
5374 self.active_drainers.fetch_sub(1, Ordering::AcqRel);
5384 let has_runnable = self.has_queued_runnable();
5385 let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5386 && !self
5387 .blocking_queue
5388 .lock()
5389 .unwrap_or_else(std::sync::PoisonError::into_inner)
5390 .is_empty();
5391 if has_runnable || has_blocking {
5392 self.ensure_drainer();
5393 }
5394 let idle = self
5400 .scheduler_idle_lock
5401 .lock()
5402 .unwrap_or_else(std::sync::PoisonError::into_inner);
5403 if *idle > 0 {
5404 self.scheduler_idle.notify_all();
5405 }
5406 }
5407
5408 fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
5409 if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
5410 self.cooperative_block_on(future)
5418 } else {
5419 self.parking_block_on(future)
5423 }
5424 }
5425
5426 fn parking_block_on(
5427 self: &Arc<Self>,
5428 mut future: Pin<&mut dyn Future<Output = ()>>,
5429 ) -> BlockOnOutcome {
5430 let parker = Arc::new(DriverParker::default());
5431 let _stop_registration = self.stop.register(&parker);
5432 let waker = Waker::from(Arc::clone(&parker));
5433 let mut cx = Context::from_waker(&waker);
5434 loop {
5435 if self.stop.is_stopping() {
5436 return BlockOnOutcome::Stopped;
5437 }
5438 if future.as_mut().poll(&mut cx).is_ready() {
5439 return BlockOnOutcome::Completed;
5440 }
5441 if self.stop.is_stopping() {
5442 return BlockOnOutcome::Stopped;
5443 }
5444 parker.park();
5445 }
5446 }
5447
5448 fn run_runnable(
5449 self: &Arc<Self>,
5450 runnable: Runnable,
5451 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5452 ) {
5453 let _non_owner = BlockingOwnerGuard::enter(None);
5456 let _active = self.metrics.runnable_started();
5457 #[cfg(test)]
5458 run_multi_thread_work_started_test_hook();
5459 drop(admission);
5463 let _ = catch_unwind_contained(|| runnable.run());
5464 }
5465
5466 fn cancel_runnable(
5467 &self,
5468 runnable: Runnable,
5469 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5470 ) {
5471 self.metrics.runnable_cancelled();
5472 drop(admission);
5475 let _generation = RuntimeGenerationGuard::enter(self.generation);
5476 let _ = catch_unwind_contained(|| drop(runnable));
5477 }
5478
5479 fn finish_runnable_claim(
5480 self: &Arc<Self>,
5481 claim: RunnableClaim,
5482 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5483 ) -> bool {
5484 match claim {
5485 RunnableClaim::Empty => {
5486 drop(admission);
5487 false
5488 }
5489 RunnableClaim::Run(runnable) => {
5490 #[cfg(test)]
5491 run_multi_thread_work_claimed_test_hook();
5492 self.run_runnable(runnable, admission);
5493 true
5494 }
5495 RunnableClaim::Cancel(runnable) => {
5496 #[cfg(test)]
5497 run_multi_thread_work_claimed_test_hook();
5498 self.cancel_runnable(runnable, admission);
5499 true
5500 }
5501 }
5502 }
5503
5504 fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
5505 match runnable {
5506 Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
5507 Some(runnable) => RunnableClaim::Run(runnable),
5508 None => RunnableClaim::Empty,
5509 }
5510 }
5511
5512 fn steal_one(&self) -> Option<Runnable> {
5516 loop {
5517 match self.injector.steal() {
5518 Steal::Success(runnable) => return Some(runnable),
5519 Steal::Empty => return None,
5520 Steal::Retry => continue,
5521 }
5522 }
5523 }
5524
5525 #[cfg(test)]
5532 fn push_fifo_raw(&self, runnable: Runnable) {
5533 self.injector.push(runnable);
5534 std::sync::atomic::fence(Ordering::SeqCst);
5535 }
5536
5537 fn claim_fifo_runnable(&self) -> RunnableClaim {
5538 let stop_publication = self.stop.runnable_claim_guard();
5539 let runnable = self.steal_one();
5540 let stopping = stop_publication.is_stopping();
5541 drop(stop_publication);
5542 Self::runnable_claim(stopping, runnable)
5543 }
5544
5545 fn claim_runnable(&self) -> RunnableClaim {
5546 let stop_publication = self.stop.runnable_claim_guard();
5547 let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
5548 let stopping = stop_publication.is_stopping();
5549 drop(stop_publication);
5550 Self::runnable_claim(stopping, runnable)
5551 }
5552
5553 fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
5554 let admission = self.begin_deadlock_admission();
5555 let claim = self.claim_fifo_runnable();
5556 self.finish_runnable_claim(claim, admission)
5557 }
5558
5559 fn run_one_runnable(self: &Arc<Self>) -> bool {
5561 let admission = self.begin_deadlock_admission();
5562 let claim = self.claim_runnable();
5565 self.finish_runnable_claim(claim, admission)
5566 }
5567
5568 fn run_one_blocking(self: &Arc<Self>) -> bool {
5569 if let Some((blocking, admission)) = self.take_blocking() {
5570 let _generation = RuntimeGenerationGuard::enter(self.generation);
5571 {
5572 let _task = self.metrics.blocking_started(true);
5573 #[cfg(test)]
5574 run_multi_thread_work_started_test_hook();
5575 drop(admission);
5578 let owner = self.fresh_owner_token();
5579 let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
5580 let _owner = BlockingOwnerGuard::enter(Some(owner));
5581 let _ = catch_unwind_contained(blocking);
5586 }
5587 self.active_blocking.fetch_sub(1, Ordering::Release);
5588 return true;
5589 }
5590 false
5591 }
5592
5593 fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
5597 self.run_one_fair_with_exact(runnable_streak, None)
5598 }
5599
5600 fn run_one_fair_with_exact(
5604 self: &Arc<Self>,
5605 runnable_streak: &mut usize,
5606 exact_dependency: Option<&TaskDependency>,
5607 ) -> bool {
5608 let mut exact_attempted = false;
5609 if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
5610 *runnable_streak = 0;
5611 if let Some(dependency) = exact_dependency {
5612 exact_attempted = true;
5613 if self.try_owned_blocking_over_cap(dependency) {
5614 return true;
5615 }
5616 }
5617 if self.run_one_blocking() {
5618 return true;
5619 }
5620 }
5621 if self.run_one_runnable() {
5622 *runnable_streak += 1;
5623 return true;
5624 }
5625 if !exact_attempted
5626 && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
5627 {
5628 *runnable_streak = 0;
5629 return true;
5630 }
5631 if self.run_one_blocking() {
5632 *runnable_streak = 0;
5633 return true;
5634 }
5635 false
5636 }
5637
5638 #[cfg(test)]
5639 fn run_one(self: &Arc<Self>) -> bool {
5640 let mut runnable_streak = 0;
5641 self.run_one_fair(&mut runnable_streak)
5642 }
5643
5644 fn run_owned_blocking_over_cap(
5654 self: &Arc<Self>,
5655 dependency: &TaskDependency,
5656 ) -> OwnedBlockingOutcome {
5657 #[cfg(test)]
5658 self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
5659
5660 let Some(ambient_owner) = BLOCKING_OWNER
5661 .with(std::cell::Cell::get)
5662 .filter(|owner| owner.executor_id == self.id)
5663 else {
5664 return OwnedBlockingOutcome::Ineligible;
5665 };
5666 let Some(current) = dependency.select_for_owner(ambient_owner) else {
5667 return OwnedBlockingOutcome::Ineligible;
5668 };
5669 let admission = self.begin_deadlock_admission();
5670 let owner = current
5671 .publication
5672 .dependency
5673 .owner
5674 .expect("selected dependency must carry its owner");
5675 let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
5676 else {
5677 return OwnedBlockingOutcome::OwnerBusy(owner);
5678 };
5679 #[cfg(test)]
5680 run_before_owned_blocking_queue_claim_test_hook();
5681 let job = {
5682 let mut queue = self
5683 .blocking_queue
5684 .lock()
5685 .unwrap_or_else(std::sync::PoisonError::into_inner);
5686 if queue.closed || self.stop.is_stopping() {
5690 None
5691 } else if dependency.try_claim(¤t) {
5695 queue.remove(current.publication.dependency.job)
5696 } else {
5697 None
5698 }
5699 };
5700 let ran = if let Some(job) = job {
5701 #[cfg(test)]
5702 run_multi_thread_work_claimed_test_hook();
5703 let _generation = RuntimeGenerationGuard::enter(self.generation);
5704 {
5705 let _task = self.metrics.blocking_started(false);
5706 #[cfg(test)]
5707 run_multi_thread_work_started_test_hook();
5708 drop(admission);
5711 let nested_owner = self.fresh_owner_token();
5712 let _active_owner =
5713 ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
5714 let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
5715 let _ = catch_unwind_contained(job);
5716 }
5717 true
5718 } else {
5719 drop(admission);
5720 false
5721 };
5722 self.release_owner_lane_and_wake(reservation);
5725 OwnedBlockingOutcome::ReservationReleased { owner, ran }
5726 }
5727
5728 fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
5729 matches!(
5730 self.run_owned_blocking_over_cap(dependency),
5731 OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
5732 )
5733 }
5734
5735 fn service_owner_handoff(
5736 self: &Arc<Self>,
5737 parker: &DriverParker,
5738 dependency: &TaskDependency,
5739 can_run_blocking: bool,
5740 ) -> Option<bool> {
5741 let handoff_owner = parker.take_owner_handoff()?;
5742 if !can_run_blocking {
5743 self.wake_one_owner_dependency(handoff_owner);
5744 return Some(false);
5745 }
5746 match self.run_owned_blocking_over_cap(dependency) {
5747 OwnedBlockingOutcome::Ineligible => {
5748 self.wake_one_owner_dependency(handoff_owner);
5749 Some(false)
5750 }
5751 OwnedBlockingOutcome::OwnerBusy(owner) => {
5752 if owner != handoff_owner {
5753 self.wake_one_owner_dependency(handoff_owner);
5754 }
5755 Some(false)
5756 }
5757 OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
5758 if owner != handoff_owner {
5759 self.wake_one_owner_dependency(handoff_owner);
5760 }
5761 Some(ran)
5762 }
5763 }
5764 }
5765
5766 fn forward_owner_handoff(&self, parker: &DriverParker) {
5767 if let Some(owner) = parker.take_owner_handoff() {
5768 self.wake_one_owner_dependency(owner);
5769 }
5770 }
5771
5772 fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
5773 let Some(ambient_owner) = BLOCKING_OWNER
5774 .with(std::cell::Cell::get)
5775 .filter(|owner| owner.executor_id == self.id)
5776 else {
5777 return false;
5778 };
5779 self.active_blocking_owners.is_available(ambient_owner)
5780 && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
5781 }
5782
5783 fn has_exact_owner_work_for_role(
5784 &self,
5785 parker: &DriverParker,
5786 dependency: &TaskDependency,
5787 can_run_blocking: bool,
5788 ) -> bool {
5789 parker.has_owner_handoff()
5790 || (can_run_blocking && self.owner_dependency_lane_available(dependency))
5791 }
5792
5793 fn cooperative_block_on(
5802 self: &Arc<Self>,
5803 mut future: Pin<&mut dyn Future<Output = ()>>,
5804 ) -> BlockOnOutcome {
5805 let parker = Arc::new(DriverParker::default());
5810 let _stop_registration = self.stop.register(&parker);
5811 let dependency = BlockOnDependencyGuard::enter(self.generation);
5812 let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
5813 let can_run_blocking =
5814 driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
5815 let _exit = CooperativeDriverExitGuard {
5816 executor: self,
5817 parker: &parker,
5818 can_run_blocking,
5819 };
5820 let waker = std::task::Waker::from(Arc::clone(&parker));
5825 let mut cx = Context::from_waker(&waker);
5826 let mut runnable_streak = 0usize;
5834 let mut work_streak = 0usize;
5835 let mut force_fifo = false;
5836 loop {
5837 if self.stop.is_stopping() {
5838 return BlockOnOutcome::Stopped;
5839 }
5840 dependency.clear();
5841 if future.as_mut().poll(&mut cx).is_ready() {
5842 return BlockOnOutcome::Completed;
5843 }
5844 if self.stop.is_stopping() {
5845 return BlockOnOutcome::Stopped;
5846 }
5847 if self
5848 .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
5849 .is_some_and(|ran| ran)
5850 {
5851 continue;
5852 }
5853 if force_fifo {
5854 force_fifo = false;
5855 if self.run_one_fifo_runnable() {
5856 runnable_streak += 1;
5857 work_streak += 1;
5858 continue;
5859 }
5860 }
5861 let ran_work = if can_run_blocking {
5862 self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
5863 } else if self.run_one_runnable() {
5864 runnable_streak += 1;
5865 true
5866 } else {
5867 false
5868 };
5869 if ran_work {
5870 work_streak += 1;
5871 if work_streak >= Self::RUNNABLE_BUDGET {
5872 self.flush_lifo_slot();
5877 work_streak = 0;
5878 force_fifo = true;
5879 }
5880 continue;
5881 }
5882 runnable_streak = 0;
5883 work_streak = 0;
5884 self.flush_lifo_slot();
5889 self.parked_drivers.register_with_role(
5893 &parker,
5894 can_run_blocking,
5895 Some(Arc::clone(&dependency.dependency)),
5896 );
5897 if self.has_queued_work_for_role(can_run_blocking)
5898 || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
5899 {
5900 self.parked_drivers.deregister(&parker);
5901 continue;
5902 }
5903 if self.stop.is_stopping() {
5904 return BlockOnOutcome::Stopped;
5905 }
5906 if let Some(next) = self.next_timer_deadline() {
5921 let now = Instant::now();
5922 if next > now {
5923 parker.park_timeout(next - now);
5924 }
5925 self.parked_drivers.deregister(&parker);
5926 self.fire_due_timers();
5927 continue;
5928 }
5929 match self.park_deadline {
5930 None => parker.park(),
5931 Some(deadline) => {
5932 let deadlock_publications = self.deadlock_state.publications();
5938 match park_with_deadline(&parker, deadline, &self.metrics) {
5939 DeadlineParkOutcome::Woken => {}
5940 DeadlineParkOutcome::Expired(armed) => {
5941 #[cfg(test)]
5942 run_deadline_expiry_test_hook();
5943 self.parked_drivers.deregister(&parker);
5980 if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
5981 continue;
5982 }
5983 #[cfg(test)]
5986 run_deadline_verdict_test_hook();
5987 if self.metrics.progress_fingerprint() != armed {
5988 continue;
5989 }
5990 if self.next_timer_deadline().is_some() {
5999 continue;
6000 }
6001 #[cfg(test)]
6004 run_deadline_final_verdict_test_hook();
6005 let Some(verdict) = self.try_begin_deadlock_verdict() else {
6006 continue;
6007 };
6008 #[cfg(test)]
6009 run_deadline_gated_verdict_test_hook();
6010 let stop_publication = self.stop.publication_guard();
6015 #[cfg(test)]
6016 run_deadline_synchronized_stop_test_hook();
6017 if stop_publication.is_stopping() {
6018 return BlockOnOutcome::Stopped;
6019 }
6020 let owner_handoff_publication = self
6021 .deadlock_state
6022 .owner_handoff_publication_guard()
6023 .expect("a deadline verdict requires exact-owner publication");
6024 #[cfg(test)]
6025 run_deadline_exact_owner_verdict_test_hook(&parker);
6026 if self.has_exact_owner_work_for_role(
6034 &parker,
6035 &dependency.dependency,
6036 can_run_blocking,
6037 ) || parker.consume_permit()
6038 || self.has_queued_work_for_role(can_run_blocking)
6039 || self.deadlock_state.publications() != deadlock_publications
6040 || self.metrics.progress_fingerprint() != armed
6041 || self.next_timer_deadline().is_some()
6042 {
6043 continue;
6044 }
6045 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6046 drop(owner_handoff_publication);
6051 drop(stop_publication);
6052 drop(verdict);
6053 std::panic::panic_any(diagnostic);
6054 }
6055 }
6056 }
6057 }
6058 self.parked_drivers.deregister(&parker);
6061 }
6062 }
6063
6064 fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6070 if self.has_queued_runnable() {
6071 return true;
6072 }
6073 can_run_blocking
6074 && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6075 && !self
6076 .blocking_queue
6077 .lock()
6078 .unwrap_or_else(std::sync::PoisonError::into_inner)
6079 .is_empty()
6080 }
6081
6082 fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6083 if self.has_queued_runnable() {
6084 self.wake_for_new_work();
6085 }
6086 if self.stop.is_stopping() {
6090 return;
6091 }
6092 if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6093 && !self
6094 .blocking_queue
6095 .lock()
6096 .unwrap_or_else(std::sync::PoisonError::into_inner)
6097 .is_empty()
6098 {
6099 let consumed = can_run_blocking && self.run_one_blocking();
6107 let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6108 && !self
6109 .blocking_queue
6110 .lock()
6111 .unwrap_or_else(std::sync::PoisonError::into_inner)
6112 .is_empty();
6113 if !self.stop.is_stopping() && (!consumed || has_residue) {
6114 self.wake_for_blocking_work();
6115 }
6116 }
6117 }
6118
6119 fn has_queued_runnable(&self) -> bool {
6120 std::sync::atomic::fence(Ordering::SeqCst);
6127 !self.injector.is_empty()
6128 }
6129
6130 fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6134 let admission = self.begin_deadlock_admission();
6135 loop {
6136 let active = self.active_blocking.load(Ordering::Acquire);
6137 if active >= self.max_blocking {
6138 return None;
6139 }
6140 let mut queue = self
6141 .blocking_queue
6142 .lock()
6143 .unwrap_or_else(std::sync::PoisonError::into_inner);
6144 if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6145 return None;
6146 }
6147 if self
6148 .active_blocking
6149 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6150 .is_ok()
6151 {
6152 let blocking = queue
6153 .pop_front()
6154 .expect("a non-empty blocking queue must have a FIFO head");
6155 drop(queue);
6156 #[cfg(test)]
6157 run_multi_thread_work_claimed_test_hook();
6158 return Some((blocking, admission));
6159 }
6160 }
6161 }
6162}
6163
6164#[cfg(not(target_family = "wasm"))]
6165struct MultiThreadDeadlockState {
6166 enabled: bool,
6167 verdict_closed: AtomicBool,
6168 admissions: AtomicUsize,
6169 publications: AtomicU64,
6170 owner_handoff_publication: Mutex<()>,
6171}
6172
6173#[cfg(not(target_family = "wasm"))]
6174impl MultiThreadDeadlockState {
6175 fn new(enabled: bool) -> Self {
6176 Self {
6177 enabled,
6178 verdict_closed: AtomicBool::new(false),
6179 admissions: AtomicUsize::new(0),
6180 publications: AtomicU64::new(0),
6181 owner_handoff_publication: Mutex::new(()),
6182 }
6183 }
6184
6185 fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6186 if !self.enabled {
6187 return None;
6188 }
6189 loop {
6190 while self.verdict_closed.load(Ordering::SeqCst) {
6191 #[cfg(test)]
6192 run_deadlock_closed_gate_admission_test_hook();
6193 std::hint::spin_loop();
6194 }
6195 self.admissions.fetch_add(1, Ordering::SeqCst);
6196 if !self.verdict_closed.load(Ordering::SeqCst) {
6197 return Some(MultiThreadDeadlockAdmissionGuard {
6198 state: Arc::clone(self),
6199 published: false,
6200 });
6201 }
6202 self.admissions.fetch_sub(1, Ordering::SeqCst);
6203 }
6204 }
6205
6206 fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6207 debug_assert!(
6208 self.enabled,
6209 "a deadline verdict requires an enabled admission state"
6210 );
6211 if self
6212 .verdict_closed
6213 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6214 .is_err()
6215 {
6216 return None;
6217 }
6218 if self.admissions.load(Ordering::SeqCst) != 0 {
6219 self.verdict_closed.store(false, Ordering::SeqCst);
6220 return None;
6221 }
6222 Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6223 }
6224
6225 fn publications(&self) -> u64 {
6226 self.publications.load(Ordering::SeqCst)
6227 }
6228
6229 fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
6230 self.enabled.then(|| {
6231 self
6232 .owner_handoff_publication
6233 .lock()
6234 .unwrap_or_else(std::sync::PoisonError::into_inner)
6235 })
6236 }
6237}
6238
6239#[cfg(not(target_family = "wasm"))]
6240struct MultiThreadDeadlockAdmissionGuard {
6241 state: Arc<MultiThreadDeadlockState>,
6242 published: bool,
6243}
6244
6245#[cfg(not(target_family = "wasm"))]
6246impl MultiThreadDeadlockAdmissionGuard {
6247 fn mark_published(&mut self) {
6248 self.published = true;
6249 }
6250}
6251
6252#[cfg(not(target_family = "wasm"))]
6253impl Drop for MultiThreadDeadlockAdmissionGuard {
6254 fn drop(&mut self) {
6255 if self.published {
6256 self.state.publications.fetch_add(1, Ordering::SeqCst);
6257 }
6258 self.state.admissions.fetch_sub(1, Ordering::SeqCst);
6261 }
6262}
6263
6264#[cfg(not(target_family = "wasm"))]
6265struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
6266
6267#[cfg(not(target_family = "wasm"))]
6268impl Drop for MultiThreadDeadlockVerdictGuard {
6269 fn drop(&mut self) {
6270 self.0.verdict_closed.store(false, Ordering::SeqCst);
6271 }
6272}
6273
6274#[cfg(not(target_family = "wasm"))]
6280struct CooperativeDriverExitGuard<'a> {
6281 executor: &'a Arc<MultiThreadExecutor>,
6282 parker: &'a Arc<DriverParker>,
6283 can_run_blocking: bool,
6284}
6285
6286#[cfg(not(target_family = "wasm"))]
6287impl Drop for CooperativeDriverExitGuard<'_> {
6288 fn drop(&mut self) {
6289 let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
6290 let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
6291 let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
6292 let _ = catch_unwind_contained(|| {
6293 self
6294 .executor
6295 .compensate_queued_work_wake(self.can_run_blocking);
6296 });
6297 }
6298}
6299
6300#[cfg(not(target_family = "wasm"))]
6306struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
6307
6308#[cfg(not(target_family = "wasm"))]
6309impl Drop for LifoSlotFlushGuard<'_> {
6310 fn drop(&mut self) {
6311 self.0.flush_lifo_slot();
6312 }
6313}
6314
6315#[cfg(not(target_family = "wasm"))]
6322struct DrainLinger {
6323 parker: Arc<DriverParker>,
6324 _stop_registration: GenerationStopGuard,
6325}
6326
6327#[cfg(not(target_family = "wasm"))]
6328impl DrainLinger {
6329 fn new(executor: &MultiThreadExecutor) -> Self {
6330 let parker = Arc::new(DriverParker::default());
6331 let stop_registration = executor.stop.register(&parker);
6332 Self {
6333 parker,
6334 _stop_registration: stop_registration,
6335 }
6336 }
6337}
6338
6339const PARKER_EMPTY: usize = 0;
6361const PARKER_NOTIFIED: usize = 1;
6363const PARKER_SLEEPING: usize = 2;
6365
6366#[derive(Default)]
6370struct DriverParker {
6371 state: AtomicUsize,
6372 lock: Mutex<()>,
6373 condvar: std::sync::Condvar,
6374 #[cfg(not(target_family = "wasm"))]
6375 owner_handoff_pending: AtomicBool,
6376 #[cfg(not(target_family = "wasm"))]
6377 owner_handoff: Mutex<Option<BlockingOwnerToken>>,
6378}
6379
6380impl DriverParker {
6381 #[cfg(not(target_family = "wasm"))]
6382 fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
6383 let mut pending = self
6384 .owner_handoff
6385 .lock()
6386 .unwrap_or_else(std::sync::PoisonError::into_inner);
6387 debug_assert!(pending.is_none());
6388 *pending = Some(owner);
6389 self.owner_handoff_pending.store(true, Ordering::Release);
6390 }
6391
6392 #[cfg(not(target_family = "wasm"))]
6393 fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
6394 if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
6395 return None;
6396 }
6397 self
6398 .owner_handoff
6399 .lock()
6400 .unwrap_or_else(std::sync::PoisonError::into_inner)
6401 .take()
6402 }
6403
6404 #[cfg(not(target_family = "wasm"))]
6405 fn has_owner_handoff(&self) -> bool {
6406 self.owner_handoff_pending.load(Ordering::Acquire)
6407 }
6408
6409 fn unpark(&self) {
6416 if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
6417 drop(
6422 self
6423 .lock
6424 .lock()
6425 .unwrap_or_else(std::sync::PoisonError::into_inner),
6426 );
6427 self.condvar.notify_one();
6428 }
6429 }
6430
6431 fn consume_permit(&self) -> bool {
6436 self
6437 .state
6438 .compare_exchange(
6439 PARKER_NOTIFIED,
6440 PARKER_EMPTY,
6441 Ordering::SeqCst,
6442 Ordering::SeqCst,
6443 )
6444 .is_ok()
6445 }
6446
6447 fn park(&self) {
6451 if self.consume_permit() {
6453 return;
6454 }
6455 let mut guard = self
6456 .lock
6457 .lock()
6458 .unwrap_or_else(std::sync::PoisonError::into_inner);
6459 match self.state.compare_exchange(
6460 PARKER_EMPTY,
6461 PARKER_SLEEPING,
6462 Ordering::SeqCst,
6463 Ordering::SeqCst,
6464 ) {
6465 Ok(_) => {}
6466 Err(_) => {
6470 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6471 return;
6472 }
6473 }
6474 loop {
6475 guard = self
6476 .condvar
6477 .wait(guard)
6478 .unwrap_or_else(std::sync::PoisonError::into_inner);
6479 if self
6480 .state
6481 .compare_exchange(
6482 PARKER_NOTIFIED,
6483 PARKER_EMPTY,
6484 Ordering::SeqCst,
6485 Ordering::SeqCst,
6486 )
6487 .is_ok()
6488 {
6489 return;
6490 }
6491 }
6493 }
6494
6495 fn park_timeout(&self, timeout: Duration) -> bool {
6501 if self.consume_permit() {
6502 return true;
6503 }
6504 let mut guard = self
6505 .lock
6506 .lock()
6507 .unwrap_or_else(std::sync::PoisonError::into_inner);
6508 match self.state.compare_exchange(
6509 PARKER_EMPTY,
6510 PARKER_SLEEPING,
6511 Ordering::SeqCst,
6512 Ordering::SeqCst,
6513 ) {
6514 Ok(_) => {}
6515 Err(_) => {
6519 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6520 return true;
6521 }
6522 }
6523 let Some(deadline) = Instant::now().checked_add(timeout) else {
6524 loop {
6528 guard = self
6529 .condvar
6530 .wait(guard)
6531 .unwrap_or_else(std::sync::PoisonError::into_inner);
6532 if self
6533 .state
6534 .compare_exchange(
6535 PARKER_NOTIFIED,
6536 PARKER_EMPTY,
6537 Ordering::SeqCst,
6538 Ordering::SeqCst,
6539 )
6540 .is_ok()
6541 {
6542 return true;
6543 }
6544 }
6545 };
6546 loop {
6547 if self
6548 .state
6549 .compare_exchange(
6550 PARKER_NOTIFIED,
6551 PARKER_EMPTY,
6552 Ordering::SeqCst,
6553 Ordering::SeqCst,
6554 )
6555 .is_ok()
6556 {
6557 return true;
6558 }
6559 let now = Instant::now();
6560 if now >= deadline {
6561 return match self.state.compare_exchange(
6566 PARKER_SLEEPING,
6567 PARKER_EMPTY,
6568 Ordering::SeqCst,
6569 Ordering::SeqCst,
6570 ) {
6571 Ok(_) => false,
6572 Err(_) => {
6573 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6574 true
6575 }
6576 };
6577 }
6578 let (returned_guard, _timed_out) = self
6579 .condvar
6580 .wait_timeout(guard, deadline - now)
6581 .unwrap_or_else(std::sync::PoisonError::into_inner);
6582 guard = returned_guard;
6583 }
6586 }
6587}
6588
6589impl std::task::Wake for DriverParker {
6590 fn wake(self: Arc<Self>) {
6591 self.unpark();
6592 }
6593
6594 fn wake_by_ref(self: &Arc<Self>) {
6595 self.unpark();
6596 }
6597}
6598
6599enum DeadlineParkOutcome {
6601 Woken,
6603 Expired(ProgressFingerprint),
6612}
6613
6614fn park_with_deadline(
6623 parker: &DriverParker,
6624 deadline: Duration,
6625 metrics: &RuntimeMetrics,
6626) -> DeadlineParkOutcome {
6627 let mut armed = metrics.progress_fingerprint();
6628 loop {
6629 if parker.park_timeout(deadline) {
6630 return DeadlineParkOutcome::Woken;
6631 }
6632 let current = metrics.progress_fingerprint();
6633 if current != armed {
6634 armed = current;
6635 continue;
6636 }
6637 return DeadlineParkOutcome::Expired(armed);
6638 }
6639}
6640
6641#[cfg(all(test, not(target_family = "wasm")))]
6649thread_local! {
6650 static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6651 const { std::cell::RefCell::new(None) };
6652}
6653
6654#[cfg(all(test, not(target_family = "wasm")))]
6655fn run_deadline_expiry_test_hook() {
6656 if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6657 hook();
6658 }
6659}
6660
6661#[cfg(all(test, not(target_family = "wasm")))]
6668thread_local! {
6669 static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6670 const { std::cell::RefCell::new(None) };
6671}
6672
6673#[cfg(all(test, not(target_family = "wasm")))]
6674fn run_deadline_verdict_test_hook() {
6675 if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6676 hook();
6677 }
6678}
6679
6680#[cfg(all(test, not(target_family = "wasm")))]
6685thread_local! {
6686 static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6687 const { std::cell::RefCell::new(None) };
6688}
6689
6690#[cfg(all(test, not(target_family = "wasm")))]
6691fn run_deadline_final_verdict_test_hook() {
6692 if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6693 hook();
6694 }
6695}
6696
6697#[cfg(all(test, not(target_family = "wasm")))]
6702thread_local! {
6703 static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6704 const { std::cell::RefCell::new(None) };
6705}
6706
6707#[cfg(all(test, not(target_family = "wasm")))]
6708fn run_deadline_gated_verdict_test_hook() {
6709 if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6710 hook();
6711 }
6712}
6713
6714#[cfg(all(test, not(target_family = "wasm")))]
6718thread_local! {
6719 static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6720 const { std::cell::RefCell::new(None) };
6721}
6722
6723#[cfg(all(test, not(target_family = "wasm")))]
6724fn run_deadline_synchronized_stop_test_hook() {
6725 if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6726 hook();
6727 }
6728}
6729
6730#[cfg(all(test, not(target_family = "wasm")))]
6734type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
6735
6736#[cfg(all(test, not(target_family = "wasm")))]
6737thread_local! {
6738 static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
6739 std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
6740 const { std::cell::RefCell::new(None) };
6741}
6742
6743#[cfg(all(test, not(target_family = "wasm")))]
6744fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
6745 if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6746 hook(parker);
6747 }
6748}
6749
6750#[cfg(all(test, not(target_family = "wasm")))]
6754thread_local! {
6755 static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
6756 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6757 const { std::cell::RefCell::new(None) };
6758}
6759
6760#[cfg(all(test, not(target_family = "wasm")))]
6761fn run_before_generation_stop_publication_lock_test_hook() {
6762 if let Some(hook) =
6763 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6764 {
6765 hook();
6766 }
6767}
6768
6769#[cfg(all(test, not(target_family = "wasm")))]
6773thread_local! {
6774 static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
6775 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6776 const { std::cell::RefCell::new(None) };
6777}
6778
6779#[cfg(all(test, not(target_family = "wasm")))]
6780fn run_deadlock_closed_gate_admission_test_hook() {
6781 if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6782 {
6783 hook();
6784 }
6785}
6786
6787#[cfg(all(test, not(target_family = "wasm")))]
6791thread_local! {
6792 static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6793 const { std::cell::RefCell::new(None) };
6794}
6795
6796#[cfg(all(test, not(target_family = "wasm")))]
6797fn run_timer_registration_admission_test_hook() {
6798 if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6799 hook();
6800 }
6801}
6802
6803#[cfg(all(test, not(target_family = "wasm")))]
6804thread_local! {
6805 static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6806 const { std::cell::RefCell::new(None) };
6807}
6808
6809#[cfg(all(test, not(target_family = "wasm")))]
6810fn run_owner_handoff_selected_test_hook() {
6811 if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6812 hook();
6813 }
6814}
6815
6816#[cfg(not(target_family = "wasm"))]
6819#[derive(Default)]
6820struct ParkedDrivers {
6821 parked: Mutex<Vec<ParkedDriver>>,
6822 count: AtomicUsize,
6825}
6826
6827#[cfg(not(target_family = "wasm"))]
6828struct ParkedDriver {
6829 parker: Arc<DriverParker>,
6830 can_run_blocking: bool,
6831 dependency: Option<Arc<TaskDependency>>,
6832}
6833
6834#[cfg(not(target_family = "wasm"))]
6835impl ParkedDrivers {
6836 #[cfg(test)]
6839 fn register(&self, parker: &Arc<DriverParker>) {
6840 self.register_with_role(parker, true, None);
6841 }
6842
6843 fn register_with_role(
6844 &self,
6845 parker: &Arc<DriverParker>,
6846 can_run_blocking: bool,
6847 dependency: Option<Arc<TaskDependency>>,
6848 ) {
6849 let mut parked = self
6850 .parked
6851 .lock()
6852 .unwrap_or_else(std::sync::PoisonError::into_inner);
6853 parked.push(ParkedDriver {
6854 parker: Arc::clone(parker),
6855 can_run_blocking,
6856 dependency,
6857 });
6858 self.count.store(parked.len(), Ordering::SeqCst);
6859 }
6860
6861 fn deregister(&self, parker: &Arc<DriverParker>) {
6865 let removed = {
6866 let mut parked = self
6867 .parked
6868 .lock()
6869 .unwrap_or_else(std::sync::PoisonError::into_inner);
6870 let removed = parked
6871 .iter()
6872 .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
6873 .map(|index| parked.swap_remove(index));
6874 self.count.store(parked.len(), Ordering::SeqCst);
6875 removed
6876 };
6877 drop(removed);
6878 }
6879
6880 fn wake_one(&self) -> bool {
6903 if self.count.load(Ordering::SeqCst) == 0 {
6904 return false;
6908 }
6909 let driver = {
6910 let mut parked = self
6911 .parked
6912 .lock()
6913 .unwrap_or_else(std::sync::PoisonError::into_inner);
6914 let driver = parked.pop();
6915 self.count.store(parked.len(), Ordering::SeqCst);
6916 driver
6917 };
6918 match driver {
6919 Some(driver) => {
6920 driver.parker.unpark();
6921 true
6922 }
6923 None => false,
6924 }
6925 }
6926
6927 fn wake_one_blocking(&self) -> bool {
6930 if self.count.load(Ordering::SeqCst) == 0 {
6931 return false;
6932 }
6933 let driver = {
6934 let mut parked = self
6935 .parked
6936 .lock()
6937 .unwrap_or_else(std::sync::PoisonError::into_inner);
6938 let driver = parked
6939 .iter()
6940 .rposition(|driver| driver.can_run_blocking)
6941 .map(|index| parked.swap_remove(index));
6942 self.count.store(parked.len(), Ordering::SeqCst);
6943 driver
6944 };
6945 match driver {
6946 Some(driver) => {
6947 driver.parker.unpark();
6948 true
6949 }
6950 None => false,
6951 }
6952 }
6953
6954 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
6959 loop {
6960 if self.count.load(Ordering::SeqCst) == 0 {
6961 return false;
6962 }
6963 let candidates = {
6964 let parked = self
6965 .parked
6966 .lock()
6967 .unwrap_or_else(std::sync::PoisonError::into_inner);
6968 parked
6969 .iter()
6970 .rev()
6971 .filter_map(|driver| {
6972 if !driver.can_run_blocking {
6973 return None;
6974 }
6975 driver
6976 .dependency
6977 .as_ref()
6978 .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
6979 })
6980 .collect::<Vec<_>>()
6981 };
6982 let selected = candidates.into_iter().find(|(_, dependency)| {
6983 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
6984 });
6985 let Some((selected_parker, selected_dependency)) = selected else {
6986 return false;
6987 };
6988 let driver = {
6989 let mut parked = self
6990 .parked
6991 .lock()
6992 .unwrap_or_else(std::sync::PoisonError::into_inner);
6993 let driver = parked
6994 .iter()
6995 .rposition(|driver| {
6996 Arc::ptr_eq(&driver.parker, &selected_parker)
6997 && driver
6998 .dependency
6999 .as_ref()
7000 .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7001 })
7002 .map(|index| parked.swap_remove(index));
7003 if let Some(driver) = &driver {
7008 driver.parker.grant_owner_handoff(owner);
7009 }
7010 self.count.store(parked.len(), Ordering::SeqCst);
7011 driver
7012 };
7013 if let Some(driver) = driver {
7014 #[cfg(test)]
7015 run_owner_handoff_selected_test_hook();
7016 driver.parker.unpark();
7017 return true;
7018 }
7019 }
7020 }
7021
7022 fn wake_all(&self) {
7025 if self.count.load(Ordering::SeqCst) == 0 {
7026 return;
7027 }
7028 let drivers = {
7029 let mut parked = self
7030 .parked
7031 .lock()
7032 .unwrap_or_else(std::sync::PoisonError::into_inner);
7033 let drivers = parked.drain(..).collect::<Vec<_>>();
7034 self.count.store(0, Ordering::SeqCst);
7035 drivers
7036 };
7037 for driver in drivers {
7038 driver.parker.unpark();
7039 }
7040 }
7041}
7042
7043pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7088 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7101
7102 fn is_live(&self) -> bool {
7103 true
7104 }
7105
7106 fn on_swept(&self) {}
7109}
7110
7111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7112pub struct CurrentThreadTaskDriverId(u64);
7113
7114type CurrentThreadTaskDriverSnapshot =
7115 ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7116
7117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7118pub struct CurrentThreadTaskDelivery {
7119 driver: CurrentThreadTaskDriverId,
7120 capability: u64,
7121 dispatch: u64,
7122}
7123
7124impl CurrentThreadTaskDelivery {
7125 pub fn capability(self) -> u64 {
7127 self.capability
7128 }
7129}
7130
7131struct CurrentThreadTaskDeliveryState {
7132 delivery: CurrentThreadTaskDelivery,
7133 claimed: bool,
7134 accepted: bool,
7135}
7136
7137struct CurrentThreadTaskDriverEntry {
7138 driver: Arc<dyn CurrentThreadTaskDriver>,
7139 delivery: Option<CurrentThreadTaskDeliveryState>,
7140 pending: Option<u64>,
7141 previous: Option<CurrentThreadTaskDriverId>,
7142 next: Option<CurrentThreadTaskDriverId>,
7143}
7144
7145#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7146enum CurrentThreadDispatchPhase {
7147 #[default]
7148 Pending,
7149 Serviced,
7150 Cancelling,
7151}
7152
7153#[derive(Default)]
7154struct CurrentThreadDispatchState {
7155 phase: CurrentThreadDispatchPhase,
7156 epoch: u64,
7157 publications: usize,
7158 references: usize,
7159 accepted: bool,
7160 accepted_failure: bool,
7161}
7162
7163impl CurrentThreadDispatchState {
7164 fn is_serviced(&self) -> bool {
7165 self.phase == CurrentThreadDispatchPhase::Serviced
7166 }
7167
7168 fn is_cancelling(&self) -> bool {
7169 self.phase == CurrentThreadDispatchPhase::Cancelling
7170 }
7171
7172 fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7173 CurrentThreadTaskDispatchFailure {
7174 dispatch,
7175 epoch: self.epoch,
7176 }
7177 }
7178}
7179
7180#[derive(Default)]
7181struct CurrentThreadTaskDriverRegistryState {
7182 entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7183 oldest: Option<CurrentThreadTaskDriverId>,
7184 newest: Option<CurrentThreadTaskDriverId>,
7185 delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7186 dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7187}
7188
7189impl CurrentThreadTaskDriverRegistryState {
7190 fn insert_driver(
7191 &mut self,
7192 id: CurrentThreadTaskDriverId,
7193 driver: Arc<dyn CurrentThreadTaskDriver>,
7194 ) {
7195 let previous = self.newest;
7196 let replaced = self.entries.insert(
7197 id,
7198 CurrentThreadTaskDriverEntry {
7199 driver,
7200 delivery: None,
7201 pending: None,
7202 previous,
7203 next: None,
7204 },
7205 );
7206 assert!(
7207 replaced.is_none(),
7208 "CurrentThread task driver id must be unique"
7209 );
7210 if let Some(previous) = previous {
7211 self
7212 .entries
7213 .get_mut(&previous)
7214 .expect("the newest CurrentThread task driver must remain registered")
7215 .next = Some(id);
7216 } else {
7217 self.oldest = Some(id);
7218 }
7219 self.newest = Some(id);
7220 }
7221
7222 fn remove_driver(
7223 &mut self,
7224 id: CurrentThreadTaskDriverId,
7225 ) -> Option<CurrentThreadTaskDriverEntry> {
7226 let entry = self.entries.remove(&id)?;
7227 if let Some(previous) = entry.previous {
7228 self
7229 .entries
7230 .get_mut(&previous)
7231 .expect("the previous CurrentThread task driver must remain registered")
7232 .next = entry.next;
7233 } else {
7234 self.oldest = entry.next;
7235 }
7236 if let Some(next) = entry.next {
7237 self
7238 .entries
7239 .get_mut(&next)
7240 .expect("the next CurrentThread task driver must remain registered")
7241 .previous = entry.previous;
7242 } else {
7243 self.newest = entry.previous;
7244 }
7245 if let Some(delivery) = &entry.delivery {
7246 let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
7247 debug_assert_eq!(
7248 indexed,
7249 Some(id),
7250 "delivery capability index must match its driver"
7251 );
7252 }
7253 Some(entry)
7254 }
7255
7256 fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7257 let mut snapshot = Vec::with_capacity(self.entries.len());
7258 let mut current = self.oldest;
7259 while let Some(id) = current {
7260 let entry = self
7261 .entries
7262 .get(&id)
7263 .expect("CurrentThread task driver order must reference a live entry");
7264 snapshot.push((id, Arc::clone(&entry.driver)));
7265 current = entry.next;
7266 }
7267 debug_assert_eq!(
7268 snapshot.len(),
7269 self.entries.len(),
7270 "CurrentThread task driver order must contain every entry exactly once"
7271 );
7272 snapshot
7273 }
7274
7275 fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
7276 if self.entries.len() != expected.len() {
7277 return false;
7278 }
7279 let mut current = self.oldest;
7280 for expected in expected {
7281 if current != Some(*expected) {
7282 return false;
7283 }
7284 current = self
7285 .entries
7286 .get(expected)
7287 .expect("CurrentThread task driver order must reference a live entry")
7288 .next;
7289 }
7290 current.is_none()
7291 }
7292}
7293
7294struct CurrentThreadTaskDriverSelection {
7295 live: CurrentThreadTaskDriverSnapshot,
7296 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7297}
7298
7299#[derive(Default)]
7300struct CurrentThreadTaskDeliveryCompletion {
7301 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7302 redispatch: bool,
7303}
7304
7305struct CurrentThreadTaskDispatchCompletion {
7306 result: CurrentThreadHostDispatchResult,
7307 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7308}
7309
7310struct CurrentThreadTaskDispatchPublicationGuard<'a> {
7311 registry: &'a CurrentThreadTaskDriverRegistry,
7312 dispatch: Option<u64>,
7313}
7314
7315impl CurrentThreadTaskDispatchPublicationGuard<'_> {
7316 fn finish(mut self) -> CurrentThreadHostDispatchResult {
7317 let dispatch = self
7318 .dispatch
7319 .expect("the CurrentThread task dispatch publication must remain armed");
7320 let result = self.registry.finish_publication(dispatch);
7321 self.dispatch = None;
7322 result
7323 }
7324}
7325
7326impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
7327 fn drop(&mut self) {
7328 if let Some(dispatch) = self.dispatch.take() {
7329 self.registry.abort_publication(dispatch);
7330 }
7331 }
7332}
7333
7334#[derive(Default)]
7335struct CurrentThreadTaskDriverRegistry {
7336 state: Mutex<CurrentThreadTaskDriverRegistryState>,
7337 next_id: AtomicU64,
7338 next_delivery: AtomicU64,
7339}
7340
7341impl CurrentThreadTaskDriverRegistry {
7342 fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
7343 let id = next_unique_id(
7344 &self.next_id,
7345 "CurrentThread task driver id space exhausted",
7346 );
7347 let id = CurrentThreadTaskDriverId(id);
7348 self
7349 .state
7350 .lock()
7351 .unwrap_or_else(std::sync::PoisonError::into_inner)
7352 .insert_driver(id, driver);
7353 id
7354 }
7355
7356 fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
7357 let (removed, failed_dispatches) = {
7358 let mut state = self
7359 .state
7360 .lock()
7361 .unwrap_or_else(std::sync::PoisonError::into_inner);
7362 let removed = state.remove_driver(id);
7363 let mut failed_dispatches = Vec::new();
7364 if let Some(entry) = &removed {
7365 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
7366 }
7367 (removed, failed_dispatches)
7368 };
7369 let _ = catch_unwind_contained(|| drop(removed));
7370 CurrentThreadTaskDeliveryCompletion {
7371 failed_dispatches,
7372 redispatch: false,
7373 }
7374 }
7375
7376 fn live(&self) -> CurrentThreadTaskDriverSelection {
7377 let mut failed_dispatches = Vec::new();
7378 loop {
7379 let snapshot = CurrentThreadTaskDriverSnapshot::new({
7380 let state = self
7381 .state
7382 .lock()
7383 .unwrap_or_else(std::sync::PoisonError::into_inner);
7384 state.ordered_snapshot()
7385 });
7386 let mut dead_ids = FxHashSet::default();
7387 let mut live_ids = Vec::new();
7388 for (id, driver) in snapshot.as_slice() {
7389 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
7390 live_ids.push(*id);
7391 } else {
7392 dead_ids.insert(*id);
7393 }
7394 }
7395
7396 let (live, swept, retry) = {
7397 let mut state = self
7398 .state
7399 .lock()
7400 .unwrap_or_else(std::sync::PoisonError::into_inner);
7401 let mut swept = Vec::new();
7402 let mut current = state.oldest;
7403 while let Some(id) = current {
7404 let next = state
7405 .entries
7406 .get(&id)
7407 .expect("CurrentThread task driver order must reference a live entry")
7408 .next;
7409 if dead_ids.contains(&id)
7410 && let Some(entry) = state.remove_driver(id)
7411 {
7412 swept.push(entry);
7413 }
7414 current = next;
7415 }
7416 for entry in &swept {
7417 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
7418 }
7419 let retry = !state.ordered_ids_match(&live_ids);
7420 let live = if retry {
7421 Vec::new()
7422 } else {
7423 state.ordered_snapshot()
7424 };
7425 (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
7426 };
7427
7428 drop(snapshot);
7431 for entry in swept {
7432 let _ = catch_unwind_contained(|| entry.driver.on_swept());
7433 let _ = catch_unwind_contained(|| drop(entry));
7434 }
7435 if !retry {
7436 return CurrentThreadTaskDriverSelection {
7437 live,
7438 failed_dispatches,
7439 };
7440 }
7441 }
7442 }
7443
7444 #[cfg(test)]
7445 fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7446 let mut live = self.live().live;
7447 live.pop().map(|(id, driver)| (id, driver.into_inner()))
7448 }
7449
7450 fn next_delivery(
7451 &self,
7452 driver: CurrentThreadTaskDriverId,
7453 dispatch: u64,
7454 ) -> CurrentThreadTaskDelivery {
7455 let capability = next_unique_id(
7456 &self.next_delivery,
7457 "CurrentThread task delivery id space exhausted",
7458 )
7459 .checked_add(1)
7460 .expect("CurrentThread task delivery id space exhausted");
7461 CurrentThreadTaskDelivery {
7462 driver,
7463 capability,
7464 dispatch,
7465 }
7466 }
7467
7468 fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
7469 let dispatch_state = state
7470 .dispatches
7471 .get_mut(&dispatch)
7472 .expect("a referenced CurrentThread dispatch must remain registered");
7473 dispatch_state.references = dispatch_state
7474 .references
7475 .checked_add(1)
7476 .expect("CurrentThread dispatch reference count exhausted");
7477 }
7478
7479 fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
7480 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7481 return;
7482 };
7483 dispatch_state.references = dispatch_state
7484 .references
7485 .checked_sub(1)
7486 .expect("CurrentThread dispatch reference count underflow");
7487 }
7488
7489 fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
7490 let publication = {
7491 let mut state = self
7492 .state
7493 .lock()
7494 .unwrap_or_else(std::sync::PoisonError::into_inner);
7495 let dispatch_state = state.dispatches.entry(dispatch).or_default();
7496 if dispatch_state.is_serviced() {
7497 return CurrentThreadTaskDispatchCompletion {
7498 result: CurrentThreadHostDispatchResult::Accepted,
7499 failed_dispatches: Vec::new(),
7500 };
7501 }
7502 if dispatch_state.is_cancelling() {
7503 return CurrentThreadTaskDispatchCompletion {
7504 result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
7505 failed_dispatches: Vec::new(),
7506 };
7507 }
7508 dispatch_state.publications = dispatch_state
7509 .publications
7510 .checked_add(1)
7511 .expect("CurrentThread dispatch publication count exhausted");
7512 CurrentThreadTaskDispatchPublicationGuard {
7513 registry: self,
7514 dispatch: Some(dispatch),
7515 }
7516 };
7517
7518 let CurrentThreadTaskDriverSelection {
7519 mut live,
7520 mut failed_dispatches,
7521 } = self.live();
7522 let planned_deliveries = {
7523 let mut state = self
7524 .state
7525 .lock()
7526 .unwrap_or_else(std::sync::PoisonError::into_inner);
7527 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
7528 return CurrentThreadTaskDispatchCompletion {
7529 result: CurrentThreadHostDispatchResult::Unavailable,
7530 failed_dispatches,
7531 };
7532 };
7533 if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
7534 Vec::new()
7535 } else {
7536 let mut reserved_deliveries = Vec::with_capacity(live.len());
7540 for (id, _) in live.as_slice().iter().rev() {
7541 let Some(entry) = state.entries.get(id) else {
7542 continue;
7543 };
7544 if entry.delivery.is_none() {
7545 reserved_deliveries.push(self.next_delivery(*id, dispatch));
7546 }
7547 }
7548 state.delivery_drivers.reserve(reserved_deliveries.len());
7549 let mut reserved_deliveries = reserved_deliveries.into_iter();
7550 let mut planned_deliveries = Vec::with_capacity(live.len());
7551 let mut retired = Vec::with_capacity(live.len());
7552 let mut accepted_reservation = false;
7553 for (id, _) in live.as_slice().iter().rev() {
7554 let Some(entry) = state.entries.get(id) else {
7555 continue;
7556 };
7557 if let Some(delivery) = &entry.delivery
7558 && delivery.delivery.dispatch == dispatch
7559 {
7560 accepted_reservation |= delivery.accepted;
7561 continue;
7562 }
7563 if entry.delivery.is_some() {
7564 let previous = state
7565 .entries
7566 .get_mut(id)
7567 .expect("the CurrentThread task driver must remain registered")
7568 .pending
7569 .replace(dispatch);
7570 if previous != Some(dispatch) {
7571 if let Some(previous) = previous {
7572 Self::remove_dispatch_reference(&mut state, previous);
7573 }
7574 Self::add_dispatch_reference(&mut state, dispatch);
7575 }
7576 retired.push(previous);
7577 accepted_reservation = true;
7578 continue;
7579 }
7580 let delivery = reserved_deliveries
7581 .next()
7582 .expect("every available CurrentThread host must have a reserved delivery");
7583 let pending = state
7584 .entries
7585 .get_mut(id)
7586 .expect("the CurrentThread task driver must remain registered")
7587 .pending
7588 .take();
7589 let accepted = pending == Some(dispatch);
7590 if !accepted && pending.is_some() {
7591 retired.push(pending);
7592 }
7593 match pending {
7594 Some(pending) if pending == dispatch => {}
7595 Some(pending) => {
7596 Self::remove_dispatch_reference(&mut state, pending);
7597 Self::add_dispatch_reference(&mut state, dispatch);
7598 }
7599 None => Self::add_dispatch_reference(&mut state, dispatch),
7600 }
7601 let indexed = state.delivery_drivers.insert(delivery.capability, *id);
7602 assert!(
7603 indexed.is_none(),
7604 "CurrentThread delivery capability must be unique"
7605 );
7606 state
7607 .entries
7608 .get_mut(id)
7609 .expect("the CurrentThread task driver must remain registered")
7610 .delivery = Some(CurrentThreadTaskDeliveryState {
7611 delivery,
7612 claimed: false,
7613 accepted,
7614 });
7615 accepted_reservation |= accepted;
7616 planned_deliveries.push((*id, delivery));
7617 }
7618 let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
7619 debug_assert!(
7620 all_reserved_deliveries_installed,
7621 "every reserved CurrentThread delivery must be installed"
7622 );
7623 if accepted_reservation {
7624 state
7625 .dispatches
7626 .get_mut(&dispatch)
7627 .expect("the publishing dispatch state must remain registered")
7628 .accepted = true;
7629 }
7630 for dispatch in retired.into_iter().flatten() {
7631 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7632 }
7633 planned_deliveries
7634 }
7635 };
7636
7637 let mut deliveries = Vec::with_capacity(planned_deliveries.len());
7642 let mut planned_deliveries = planned_deliveries.into_iter();
7643 let mut next_delivery = planned_deliveries.next();
7644 while let Some((id, driver)) = live.pop() {
7645 let Some((planned_id, delivery)) = next_delivery else {
7646 break;
7647 };
7648 if planned_id == id {
7649 deliveries.push((id, driver, delivery));
7650 next_delivery = planned_deliveries.next();
7651 } else {
7652 next_delivery = Some((planned_id, delivery));
7653 }
7654 }
7655 debug_assert!(
7656 next_delivery.is_none() && planned_deliveries.len() == 0,
7657 "every planned CurrentThread delivery must retain its selected driver"
7658 );
7659
7660 for (id, driver, delivery) in deliveries {
7661 if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
7662 self.accept_delivery(id, delivery);
7663 } else {
7664 let (entry, failures) = self.reject_delivery(id, delivery);
7665 failed_dispatches.extend(failures);
7666 if let Some(entry) = entry {
7667 let _ = catch_unwind_contained(|| entry.driver.on_swept());
7668 let _ = catch_unwind_contained(|| drop(entry));
7669 }
7670 }
7671 }
7672
7673 let result = publication.finish();
7674 debug_assert!(
7675 !failed_dispatches
7676 .iter()
7677 .any(|failure| failure.dispatch == dispatch),
7678 "the active publication must report its own terminal outcome directly"
7679 );
7680 CurrentThreadTaskDispatchCompletion {
7681 result,
7682 failed_dispatches,
7683 }
7684 }
7685
7686 #[cfg(test)]
7687 fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7688 self.dispatch_with_completion(dispatch).result
7689 }
7690
7691 fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
7692 let mut state = self
7693 .state
7694 .lock()
7695 .unwrap_or_else(std::sync::PoisonError::into_inner);
7696 let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
7697 entry
7698 .delivery
7699 .as_ref()
7700 .is_some_and(|current| current.delivery == delivery)
7701 }) {
7702 state
7703 .entries
7704 .get_mut(&id)
7705 .expect("the matching CurrentThread task driver must remain registered")
7706 .delivery
7707 .as_mut()
7708 .expect("the matching delivery must remain installed")
7709 .accepted = true;
7710 true
7711 } else {
7712 false
7717 };
7718 if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
7719 dispatch_state.accepted = true;
7720 dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
7721 }
7722 }
7723
7724 fn reject_delivery(
7725 &self,
7726 id: CurrentThreadTaskDriverId,
7727 delivery: CurrentThreadTaskDelivery,
7728 ) -> (
7729 Option<CurrentThreadTaskDriverEntry>,
7730 Vec<CurrentThreadTaskDispatchFailure>,
7731 ) {
7732 let mut state = self
7733 .state
7734 .lock()
7735 .unwrap_or_else(std::sync::PoisonError::into_inner);
7736 if !state.entries.get(&id).is_some_and(|entry| {
7737 entry
7738 .delivery
7739 .as_ref()
7740 .is_some_and(|current| current.delivery == delivery)
7741 }) {
7742 return (None, Vec::new());
7743 }
7744 let entry = state
7745 .remove_driver(id)
7746 .expect("the matching CurrentThread task driver must remain registered");
7747 let mut failures = Vec::new();
7748 Self::finish_entry_references(&mut state, &entry, &mut failures);
7749 (Some(entry), failures)
7750 }
7751
7752 fn finish_delivery(
7753 &self,
7754 delivery: CurrentThreadTaskDelivery,
7755 failed: bool,
7756 ) -> CurrentThreadTaskDeliveryCompletion {
7757 let mut state = self
7758 .state
7759 .lock()
7760 .unwrap_or_else(std::sync::PoisonError::into_inner);
7761 if !state.entries.get(&delivery.driver).is_some_and(|entry| {
7762 entry
7763 .delivery
7764 .as_ref()
7765 .is_some_and(|current| current.delivery == delivery)
7766 }) {
7767 return CurrentThreadTaskDeliveryCompletion::default();
7768 }
7769 let current = state
7770 .entries
7771 .get_mut(&delivery.driver)
7772 .expect("the matching CurrentThread task driver must remain registered")
7773 .delivery
7774 .take()
7775 .expect("the matching delivery must remain installed");
7776 let indexed = state.delivery_drivers.remove(&delivery.capability);
7777 debug_assert_eq!(
7778 indexed,
7779 Some(delivery.driver),
7780 "delivery capability index must match its driver"
7781 );
7782 let redispatch = state
7783 .entries
7784 .get(&delivery.driver)
7785 .expect("the matching CurrentThread task driver must remain registered")
7786 .pending
7787 .is_some_and(|dispatch| {
7788 state
7789 .dispatches
7790 .get(&dispatch)
7791 .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
7792 });
7793 let mut failed_dispatches = Vec::new();
7794 Self::finish_dispatch_reference(
7799 &mut state,
7800 delivery.dispatch,
7801 true,
7802 failed || !current.claimed,
7803 &mut failed_dispatches,
7804 );
7805 CurrentThreadTaskDeliveryCompletion {
7806 failed_dispatches,
7807 redispatch,
7808 }
7809 }
7810
7811 fn claim_delivery(&self, capability: u64) -> Option<u64> {
7812 let mut state = self
7813 .state
7814 .lock()
7815 .unwrap_or_else(std::sync::PoisonError::into_inner);
7816 let driver = *state.delivery_drivers.get(&capability)?;
7817 let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
7818 if delivery.capability != capability {
7819 return None;
7820 }
7821 let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
7822 if dispatch_state.is_serviced()
7823 || dispatch_state.is_cancelling()
7824 || state.entries.get(&driver)?.delivery.as_ref()?.claimed
7825 {
7826 return None;
7827 }
7828 let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
7829 current.claimed = true;
7830 current.accepted = true;
7831 state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
7832 Some(delivery.dispatch)
7833 }
7834
7835 fn mark_serviced(&self, dispatch: u64) {
7836 let mut state = self
7837 .state
7838 .lock()
7839 .unwrap_or_else(std::sync::PoisonError::into_inner);
7840 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7841 return;
7842 };
7843 dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
7844 let mut removed_references = 0usize;
7845 for entry in state.entries.values_mut() {
7846 if entry.pending == Some(dispatch) {
7847 entry.pending = None;
7848 removed_references = removed_references
7849 .checked_add(1)
7850 .expect("CurrentThread dispatch reference count exhausted");
7851 }
7852 }
7853 let dispatch_state = state
7854 .dispatches
7855 .get_mut(&dispatch)
7856 .expect("the serviced CurrentThread dispatch must remain registered");
7857 dispatch_state.references = dispatch_state
7858 .references
7859 .checked_sub(removed_references)
7860 .expect("CurrentThread dispatch reference count underflow");
7861 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7862 }
7863
7864 fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
7865 let delivery = {
7866 let state = self
7867 .state
7868 .lock()
7869 .unwrap_or_else(std::sync::PoisonError::into_inner);
7870 state
7871 .delivery_drivers
7872 .get(&capability)
7873 .and_then(|driver| state.entries.get(driver))
7874 .and_then(|entry| entry.delivery.as_ref())
7875 .map(|delivery| delivery.delivery)
7876 .filter(|delivery| delivery.capability == capability)
7877 };
7878 delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
7879 self.finish_delivery(delivery, true)
7880 })
7881 }
7882
7883 fn finish_dispatch_reference(
7884 state: &mut CurrentThreadTaskDriverRegistryState,
7885 dispatch: u64,
7886 accepted: bool,
7887 failed: bool,
7888 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7889 ) {
7890 let (publications, references) = {
7891 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7892 return;
7893 };
7894 dispatch_state.references = dispatch_state
7895 .references
7896 .checked_sub(1)
7897 .expect("CurrentThread dispatch reference count underflow");
7898 dispatch_state.accepted |= accepted;
7899 dispatch_state.accepted_failure |= accepted && failed;
7900 (dispatch_state.publications, dispatch_state.references)
7901 };
7902 if references != 0 || publications != 0 {
7903 return;
7904 }
7905 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7906 return;
7907 };
7908 if dispatch_state.accepted_failure
7909 && !dispatch_state.is_serviced()
7910 && !dispatch_state.is_cancelling()
7911 {
7912 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7913 failures.push(dispatch_state.failure(dispatch));
7914 } else if !dispatch_state.is_cancelling() {
7915 state.dispatches.remove(&dispatch);
7916 }
7917 }
7918
7919 fn finish_entry_references(
7920 state: &mut CurrentThreadTaskDriverRegistryState,
7921 entry: &CurrentThreadTaskDriverEntry,
7922 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7923 ) {
7924 if let Some(delivery) = &entry.delivery {
7925 Self::finish_dispatch_reference(
7926 state,
7927 delivery.delivery.dispatch,
7928 delivery.accepted,
7929 delivery.accepted,
7930 failures,
7931 );
7932 }
7933 if let Some(pending) = entry.pending {
7934 Self::finish_dispatch_reference(state, pending, true, true, failures);
7935 }
7936 }
7937
7938 fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7939 let mut state = self
7940 .state
7941 .lock()
7942 .unwrap_or_else(std::sync::PoisonError::into_inner);
7943 let publications = {
7944 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7945 return CurrentThreadHostDispatchResult::Unavailable;
7946 };
7947 dispatch_state.publications = dispatch_state
7948 .publications
7949 .checked_sub(1)
7950 .expect("CurrentThread dispatch publication count underflow");
7951 dispatch_state.publications
7952 };
7953 if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
7954 return CurrentThreadHostDispatchResult::Accepted;
7955 }
7956 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7957 return CurrentThreadHostDispatchResult::Unavailable;
7958 };
7959 if dispatch_state.is_cancelling() {
7960 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7961 }
7962 if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
7963 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7964 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7965 }
7966 let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
7967 state.dispatches.remove(&dispatch);
7968 if accepted {
7969 CurrentThreadHostDispatchResult::Accepted
7970 } else {
7971 CurrentThreadHostDispatchResult::Unavailable
7972 }
7973 }
7974
7975 fn abort_publication(&self, dispatch: u64) {
7976 let mut state = self
7977 .state
7978 .lock()
7979 .unwrap_or_else(std::sync::PoisonError::into_inner);
7980 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7981 return;
7982 };
7983 let Some(publications) = dispatch_state.publications.checked_sub(1) else {
7984 return;
7985 };
7986 dispatch_state.publications = publications;
7987 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7988 }
7989
7990 fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
7991 let mut state = self
7992 .state
7993 .lock()
7994 .unwrap_or_else(std::sync::PoisonError::into_inner);
7995 let can_rearm = state
7996 .dispatches
7997 .get(&failure.dispatch)
7998 .is_some_and(|dispatch_state| {
7999 dispatch_state.is_cancelling()
8000 && dispatch_state.epoch == failure.epoch
8001 && dispatch_state.publications == 0
8002 })
8003 && !Self::has_dispatch_reference(&state, failure.dispatch);
8004 if !can_rearm {
8005 return false;
8006 }
8007 let dispatch_state = state
8008 .dispatches
8009 .get_mut(&failure.dispatch)
8010 .expect("the exact failed CurrentThread dispatch must remain registered");
8011 let epoch = dispatch_state
8012 .epoch
8013 .checked_add(1)
8014 .expect("CurrentThread dispatch failure epoch space exhausted");
8015 *dispatch_state = CurrentThreadDispatchState {
8016 epoch,
8017 ..CurrentThreadDispatchState::default()
8018 };
8019 true
8020 }
8021
8022 fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8023 self
8024 .state
8025 .lock()
8026 .unwrap_or_else(std::sync::PoisonError::into_inner)
8027 .dispatches
8028 .get(&failure.dispatch)
8029 .is_some_and(|dispatch_state| {
8030 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8031 })
8032 }
8033
8034 fn retire_unreferenced_dispatch(
8035 state: &mut CurrentThreadTaskDriverRegistryState,
8036 dispatch: Option<u64>,
8037 ) {
8038 let Some(dispatch) = dispatch else {
8039 return;
8040 };
8041 if Self::has_dispatch_reference(state, dispatch) {
8042 return;
8043 }
8044 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8045 return;
8046 };
8047 if dispatch_state.publications == 0
8048 && !dispatch_state.is_cancelling()
8049 && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8050 {
8051 state.dispatches.remove(&dispatch);
8052 }
8053 }
8054
8055 fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8056 state
8057 .dispatches
8058 .get(&dispatch)
8059 .is_some_and(|dispatch_state| dispatch_state.references != 0)
8060 }
8061
8062 fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8063 let mut state = self
8064 .state
8065 .lock()
8066 .unwrap_or_else(std::sync::PoisonError::into_inner);
8067 if state
8068 .dispatches
8069 .get(&failure.dispatch)
8070 .is_some_and(|dispatch_state| {
8071 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8072 })
8073 {
8074 state.dispatches.remove(&failure.dispatch);
8075 }
8076 }
8077
8078 fn retire_for_shutdown(&self) {
8079 let mut state = self
8080 .state
8081 .lock()
8082 .unwrap_or_else(std::sync::PoisonError::into_inner);
8083 state.dispatches.clear();
8084 for entry in state.entries.values_mut() {
8085 entry.pending = None;
8086 }
8087 }
8088}
8089
8090static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8091 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8092
8093pub fn register_current_thread_task_driver(
8094 driver: Arc<dyn CurrentThreadTaskDriver>,
8095) -> CurrentThreadTaskDriverId {
8096 CURRENT_THREAD_TASK_DRIVERS.register(driver)
8097}
8098
8099pub fn request_current_thread_task_drain() {
8104 RUNTIME.request_current_thread_drain();
8105}
8106
8107pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8108 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8109}
8110
8111fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8112 let CurrentThreadTaskDispatchCompletion {
8113 result,
8114 failed_dispatches,
8115 } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8116 complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8117 failed_dispatches,
8118 redispatch: false,
8119 });
8120 result
8121}
8122
8123pub type TimerId = u64;
8124
8125static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8127
8128pub trait TimerDriver: Send + Sync + 'static {
8134 fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8138 fn cancel(&self, id: TimerId);
8141 fn is_live(&self) -> bool {
8146 true
8147 }
8148 fn on_swept(&self) {}
8158}
8159
8160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8164pub struct TimerDriverId(u64);
8165
8166type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8167
8168struct TimerDriverEntry {
8169 driver: Arc<dyn TimerDriver>,
8170 previous: Option<TimerDriverId>,
8171 next: Option<TimerDriverId>,
8172}
8173
8174#[derive(Default)]
8175struct TimerDriverRegistryState {
8176 entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8177 oldest: Option<TimerDriverId>,
8178 newest: Option<TimerDriverId>,
8179}
8180
8181impl TimerDriverRegistryState {
8182 fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8183 let previous = self.newest;
8184 let replaced = self.entries.insert(
8185 id,
8186 TimerDriverEntry {
8187 driver,
8188 previous,
8189 next: None,
8190 },
8191 );
8192 assert!(replaced.is_none(), "timer driver id must be unique");
8193 if let Some(previous) = previous {
8194 self
8195 .entries
8196 .get_mut(&previous)
8197 .expect("the newest timer driver must remain registered")
8198 .next = Some(id);
8199 } else {
8200 self.oldest = Some(id);
8201 }
8202 self.newest = Some(id);
8203 }
8204
8205 fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
8206 let entry = self.entries.remove(&id)?;
8207 if let Some(previous) = entry.previous {
8208 self
8209 .entries
8210 .get_mut(&previous)
8211 .expect("the previous timer driver must remain registered")
8212 .next = entry.next;
8213 } else {
8214 self.oldest = entry.next;
8215 }
8216 if let Some(next) = entry.next {
8217 self
8218 .entries
8219 .get_mut(&next)
8220 .expect("the next timer driver must remain registered")
8221 .previous = entry.previous;
8222 } else {
8223 self.newest = entry.previous;
8224 }
8225 Some(entry)
8226 }
8227
8228 fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
8229 let mut snapshot = Vec::with_capacity(self.entries.len());
8230 let mut current = self.oldest;
8231 while let Some(id) = current {
8232 let entry = self
8233 .entries
8234 .get(&id)
8235 .expect("timer driver order must reference a live entry");
8236 snapshot.push((id, Arc::clone(&entry.driver)));
8237 current = entry.next;
8238 }
8239 debug_assert_eq!(
8240 snapshot.len(),
8241 self.entries.len(),
8242 "timer driver order must contain every entry exactly once"
8243 );
8244 snapshot
8245 }
8246
8247 fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
8248 if self.entries.len() != expected.len() {
8249 return false;
8250 }
8251 let mut current = self.oldest;
8252 for expected in expected {
8253 if current != Some(*expected) {
8254 return false;
8255 }
8256 current = self
8257 .entries
8258 .get(expected)
8259 .expect("timer driver order must reference a live entry")
8260 .next;
8261 }
8262 current.is_none()
8263 }
8264}
8265
8266#[derive(Default)]
8284pub struct TimerDriverRegistry {
8285 state: Mutex<TimerDriverRegistryState>,
8289 pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
8294 next_id: AtomicU64,
8295}
8296
8297impl Drop for TimerDriverRegistry {
8298 fn drop(&mut self) {
8299 let state = self
8300 .state
8301 .get_mut()
8302 .unwrap_or_else(std::sync::PoisonError::into_inner);
8303 state.oldest = None;
8304 state.newest = None;
8305 let entries = std::mem::take(&mut state.entries);
8306 for (_, entry) in entries {
8307 let _ = catch_unwind_contained(|| drop(entry));
8308 }
8309 }
8310}
8311
8312impl TimerDriverRegistry {
8313 pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8315 let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
8316 let id = TimerDriverId(id);
8317 self
8318 .state
8319 .lock()
8320 .unwrap_or_else(std::sync::PoisonError::into_inner)
8321 .insert_driver(id, driver);
8322 let pending = {
8327 let mut entries = self
8328 .pending
8329 .lock()
8330 .unwrap_or_else(std::sync::PoisonError::into_inner);
8331 let mut pending = Vec::with_capacity(entries.len());
8332 entries.retain(|_, repoll| {
8333 if let Some(repoll) = repoll.upgrade() {
8334 pending.push(repoll);
8335 true
8336 } else {
8337 false
8338 }
8339 });
8340 pending
8341 };
8342 for repoll in pending {
8343 repoll.wake();
8344 }
8345 id
8346 }
8347
8348 pub fn unregister(&self, id: TimerDriverId) {
8350 let removed = self
8351 .state
8352 .lock()
8353 .unwrap_or_else(std::sync::PoisonError::into_inner)
8354 .remove_driver(id);
8355 let _ = catch_unwind_contained(|| drop(removed));
8358 }
8359
8360 fn live(&self) -> HostTimerDriverSnapshot {
8371 loop {
8372 let snapshot = HostTimerDriverSnapshot::new({
8373 let state = self
8374 .state
8375 .lock()
8376 .unwrap_or_else(std::sync::PoisonError::into_inner);
8377 state.ordered_snapshot()
8378 });
8379 let mut dead_ids = FxHashSet::default();
8380 let mut live_ids = Vec::new();
8381 for (id, driver) in snapshot.as_slice() {
8382 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8383 live_ids.push(*id);
8384 } else {
8385 dead_ids.insert(*id);
8386 }
8387 }
8388
8389 let (live, swept, retry) = {
8390 let mut state = self
8391 .state
8392 .lock()
8393 .unwrap_or_else(std::sync::PoisonError::into_inner);
8394 let mut swept = Vec::new();
8395 let mut current = state.oldest;
8396 while let Some(id) = current {
8397 let next = state
8398 .entries
8399 .get(&id)
8400 .expect("timer driver order must reference a live entry")
8401 .next;
8402 if dead_ids.contains(&id)
8403 && let Some(entry) = state.remove_driver(id)
8404 {
8405 swept.push(entry);
8406 }
8407 current = next;
8408 }
8409 let retry = !state.ordered_ids_match(&live_ids);
8410 let live = if retry {
8411 Vec::new()
8412 } else {
8413 state.ordered_snapshot()
8414 };
8415 (HostTimerDriverSnapshot::new(live), swept, retry)
8416 };
8417
8418 drop(snapshot);
8421 for entry in swept {
8422 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8423 let _ = catch_unwind_contained(|| drop(entry));
8424 }
8425 if !retry {
8426 return live;
8427 }
8428 }
8429 }
8430
8431 #[cfg(test)]
8432 fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
8433 let mut live = self.live();
8434 live.pop().map(|(id, driver)| (id, driver.into_inner()))
8435 }
8436
8437 pub fn has_live_driver(&self) -> bool {
8439 !self.live().is_empty()
8440 }
8441
8442 fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
8443 let replaced = self
8444 .pending
8445 .lock()
8446 .unwrap_or_else(std::sync::PoisonError::into_inner)
8447 .insert(id, Arc::downgrade(repoll));
8448 assert!(
8449 replaced.is_none(),
8450 "host timer {id} registered its repoll signal twice"
8451 );
8452 if repoll.is_closed() {
8455 self.remove_pending(id);
8456 }
8457 }
8458
8459 fn remove_pending(&self, id: TimerId) {
8460 let removed = self
8461 .pending
8462 .lock()
8463 .unwrap_or_else(std::sync::PoisonError::into_inner)
8464 .remove(&id);
8465 let _ = catch_unwind_contained(|| drop(removed));
8466 }
8467
8468 #[cfg(test)]
8469 fn pending_len(&self) -> usize {
8470 self
8471 .pending
8472 .lock()
8473 .unwrap_or_else(std::sync::PoisonError::into_inner)
8474 .len()
8475 }
8476}
8477
8478static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
8481 LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
8482
8483pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8489 TIMER_DRIVERS.register(driver)
8490}
8491
8492pub fn unregister_timer_driver(id: TimerDriverId) {
8496 TIMER_DRIVERS.unregister(id);
8497}
8498
8499pub fn has_live_timer_driver() -> bool {
8506 TIMER_DRIVERS.has_live_driver()
8507}
8508
8509#[derive(Default)]
8513struct HostTimerRepoll {
8514 state: Mutex<HostTimerRepollState>,
8515}
8516
8517#[derive(Default)]
8518struct HostTimerRepollState {
8519 closed: bool,
8520 notified: bool,
8521 waker: Option<Waker>,
8522}
8523
8524impl HostTimerRepoll {
8525 fn register(&self, waker: Waker) {
8526 let (replaced, wake) = {
8527 let mut state = self
8528 .state
8529 .lock()
8530 .unwrap_or_else(std::sync::PoisonError::into_inner);
8531 if state.closed {
8532 (Some(waker), None)
8533 } else if state.notified {
8534 state.notified = false;
8535 (state.waker.take(), Some(waker))
8536 } else {
8537 (state.waker.replace(waker), None)
8538 }
8539 };
8540 let _ = catch_unwind_contained(|| drop(replaced));
8541 if let Some(waker) = wake {
8542 wake_waker_contained(waker);
8543 }
8544 }
8545
8546 fn wake(&self) {
8547 let waker = {
8548 let mut state = self
8549 .state
8550 .lock()
8551 .unwrap_or_else(std::sync::PoisonError::into_inner);
8552 if state.closed {
8553 return;
8554 }
8555 if let Some(waker) = state.waker.take() {
8556 Some(waker)
8557 } else {
8558 state.notified = true;
8559 None
8560 }
8561 };
8562 if let Some(waker) = waker {
8563 wake_waker_contained(waker);
8564 }
8565 }
8566
8567 fn close(&self) {
8568 let waker = {
8569 let mut state = self
8570 .state
8571 .lock()
8572 .unwrap_or_else(std::sync::PoisonError::into_inner);
8573 state.closed = true;
8574 state.notified = false;
8575 state.waker.take()
8576 };
8577 let _ = catch_unwind_contained(|| drop(waker));
8578 }
8579
8580 fn is_closed(&self) -> bool {
8581 self
8582 .state
8583 .lock()
8584 .unwrap_or_else(std::sync::PoisonError::into_inner)
8585 .closed
8586 }
8587}
8588
8589#[derive(Default)]
8596struct HostTimerRegistry {
8597 state: Mutex<HostTimerRegistryState>,
8598}
8599
8600#[derive(Default)]
8601struct HostTimerRegistryState {
8602 closed: bool,
8603 timers: FxHashMap<TimerId, HostTimerEntry>,
8604}
8605
8606struct HostTimerEntry {
8607 deadline: Instant,
8608 waker: Waker,
8609 fired: Arc<AtomicBool>,
8610 drivers: Weak<TimerDriverRegistry>,
8611 repoll: Arc<HostTimerRepoll>,
8612}
8613
8614impl HostTimerRegistry {
8615 fn register(
8616 &self,
8617 id: TimerId,
8618 deadline: Instant,
8619 waker: Waker,
8620 fired: &Arc<AtomicBool>,
8621 drivers: &Arc<TimerDriverRegistry>,
8622 repoll: &Arc<HostTimerRepoll>,
8623 ) -> bool {
8624 let replaced = {
8625 let mut state = self
8626 .state
8627 .lock()
8628 .unwrap_or_else(std::sync::PoisonError::into_inner);
8629 if state.closed || fired.load(Ordering::SeqCst) {
8630 fired.store(true, Ordering::SeqCst);
8631 Err(waker)
8632 } else {
8633 Ok(state.timers.insert(
8634 id,
8635 HostTimerEntry {
8636 deadline,
8637 waker,
8638 fired: Arc::clone(fired),
8639 drivers: Arc::downgrade(drivers),
8640 repoll: Arc::clone(repoll),
8641 },
8642 ))
8643 }
8644 };
8645 match replaced {
8646 Ok(Some(entry)) => {
8647 let _ = catch_unwind_contained(|| drop(entry));
8648 true
8649 }
8650 Ok(None) => true,
8651 Err(waker) => {
8652 let _ = catch_unwind_contained(|| drop(waker));
8653 false
8654 }
8655 }
8656 }
8657
8658 fn remove(&self, id: TimerId) {
8659 let removed = self
8660 .state
8661 .lock()
8662 .unwrap_or_else(std::sync::PoisonError::into_inner)
8663 .timers
8664 .remove(&id);
8665 if let Some(entry) = removed {
8666 let _ = catch_unwind_contained(|| drop(entry));
8667 }
8668 }
8669
8670 fn shutdown(&self) {
8671 let timers = {
8672 let mut state = self
8673 .state
8674 .lock()
8675 .unwrap_or_else(std::sync::PoisonError::into_inner);
8676 state.closed = true;
8677 state
8678 .timers
8679 .drain()
8680 .map(|(id, entry)| {
8681 entry.fired.store(true, Ordering::SeqCst);
8682 (id, entry)
8683 })
8684 .collect::<Vec<_>>()
8685 };
8686 for (id, entry) in timers {
8687 entry.repoll.close();
8688 if let Some(drivers) = entry.drivers.upgrade() {
8689 drivers.remove_pending(id);
8690 }
8691 wake_waker_contained(entry.waker);
8692 }
8693 }
8694
8695 fn has_pending(&self) -> bool {
8700 !self
8701 .state
8702 .lock()
8703 .unwrap_or_else(std::sync::PoisonError::into_inner)
8704 .timers
8705 .is_empty()
8706 }
8707
8708 fn has_live_wait(&self, grace: Duration) -> bool {
8717 let now = Instant::now();
8718 self
8719 .state
8720 .lock()
8721 .unwrap_or_else(std::sync::PoisonError::into_inner)
8722 .timers
8723 .values()
8724 .any(|entry| {
8725 entry
8726 .deadline
8727 .checked_add(grace)
8728 .is_none_or(|deadline| deadline > now)
8729 })
8730 }
8731}
8732
8733struct HostTimerPendingGuard {
8737 registry: Arc<HostTimerRegistry>,
8738 drivers: Arc<TimerDriverRegistry>,
8739 repoll: Arc<HostTimerRepoll>,
8740 id: TimerId,
8741}
8742
8743impl Drop for HostTimerPendingGuard {
8744 fn drop(&mut self) {
8745 self.repoll.close();
8746 self.registry.remove(self.id);
8747 self.drivers.remove_pending(self.id);
8748 }
8749}
8750
8751#[cfg(not(target_family = "wasm"))]
8757struct TimerHeap {
8758 inner: Arc<Mutex<TimerHeapInner>>,
8759 parker: Arc<DriverParker>,
8760 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
8761 deadlock_state: Arc<MultiThreadDeadlockState>,
8762 #[cfg(test)]
8763 fail_next_start: AtomicBool,
8764 generation: u64,
8765 executor_id: u64,
8766 thread_name: String,
8767}
8768
8769#[cfg(not(target_family = "wasm"))]
8770#[derive(Default)]
8771struct TimerHeapInner {
8772 queue: TimerDeadlineQueue,
8776 entries: FxHashMap<TimerId, HeapTimerEntry>,
8777 timekeeper_parker: Option<Arc<DriverParker>>,
8781 closed: bool,
8785}
8786
8787#[cfg(not(target_family = "wasm"))]
8788struct HeapTimerEntry {
8789 waker: Waker,
8790 fired: Arc<AtomicBool>,
8795}
8796
8797#[cfg(not(target_family = "wasm"))]
8798#[derive(Default)]
8799struct TimerDeadlineQueue {
8800 nodes: Vec<(Instant, TimerId)>,
8801 positions: FxHashMap<TimerId, usize>,
8802}
8803
8804#[cfg(not(target_family = "wasm"))]
8805impl TimerDeadlineQueue {
8806 #[cfg(test)]
8807 fn len(&self) -> usize {
8808 self.nodes.len()
8809 }
8810
8811 #[cfg(test)]
8812 fn is_empty(&self) -> bool {
8813 self.nodes.is_empty()
8814 }
8815
8816 fn clear(&mut self) {
8817 self.nodes.clear();
8818 self.positions.clear();
8819 }
8820
8821 fn peek(&self) -> Option<(Instant, TimerId)> {
8822 self.nodes.first().copied()
8823 }
8824
8825 fn push(&mut self, deadline: Instant, id: TimerId) {
8826 let index = self.nodes.len();
8827 assert!(
8828 self.positions.insert(id, index).is_none(),
8829 "timer deadline queue received duplicate id {id}"
8830 );
8831 self.nodes.push((deadline, id));
8832 self.sift_up(index);
8833 }
8834
8835 fn pop(&mut self) -> Option<(Instant, TimerId)> {
8836 let first = self.nodes.first().copied()?;
8837 self.positions.remove(&first.1);
8838 let last = self
8839 .nodes
8840 .pop()
8841 .expect("the non-empty timer heap must have a final node");
8842 if !self.nodes.is_empty() {
8843 self.nodes[0] = last;
8844 self.positions.insert(last.1, 0);
8845 self.sift_down(0);
8846 }
8847 Some(first)
8848 }
8849
8850 fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
8851 let index = self.positions.remove(&id)?;
8852 let removed = self.nodes[index];
8853 let last = self.nodes.pop().expect("an indexed timer node must exist");
8854 if index < self.nodes.len() {
8855 self.nodes[index] = last;
8856 self.positions.insert(last.1, index);
8857 if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
8858 self.sift_up(index);
8859 } else {
8860 self.sift_down(index);
8861 }
8862 }
8863 Some(removed)
8864 }
8865
8866 fn sift_up(&mut self, mut index: usize) {
8867 while index > 0 {
8868 let parent = (index - 1) / 2;
8869 if self.nodes[parent] <= self.nodes[index] {
8870 break;
8871 }
8872 self.swap(parent, index);
8873 index = parent;
8874 }
8875 }
8876
8877 fn sift_down(&mut self, mut index: usize) {
8878 loop {
8879 let left = index * 2 + 1;
8880 if left >= self.nodes.len() {
8881 return;
8882 }
8883 let right = left + 1;
8884 let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
8885 right
8886 } else {
8887 left
8888 };
8889 if self.nodes[index] <= self.nodes[child] {
8890 return;
8891 }
8892 self.swap(index, child);
8893 index = child;
8894 }
8895 }
8896
8897 fn swap(&mut self, left: usize, right: usize) {
8898 self.nodes.swap(left, right);
8899 self.positions.insert(self.nodes[left].1, left);
8900 self.positions.insert(self.nodes[right].1, right);
8901 }
8902}
8903
8904#[cfg(not(target_family = "wasm"))]
8905impl TimerHeapInner {
8906 fn next_deadline(&self) -> Option<Instant> {
8907 self.queue.peek().map(|(deadline, _)| deadline)
8908 }
8909}
8910
8911#[cfg(not(target_family = "wasm"))]
8912impl TimerHeap {
8913 fn new(
8914 generation: u64,
8915 executor_id: u64,
8916 thread_name: String,
8917 deadlock_state: Arc<MultiThreadDeadlockState>,
8918 ) -> Self {
8919 Self {
8920 inner: Arc::new(Mutex::new(TimerHeapInner::default())),
8921 parker: Arc::new(DriverParker::default()),
8922 thread: Mutex::new(None),
8923 deadlock_state,
8924 #[cfg(test)]
8925 fail_next_start: AtomicBool::new(false),
8926 generation,
8927 executor_id,
8928 thread_name,
8929 }
8930 }
8931
8932 fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
8933 if self
8934 .inner
8935 .lock()
8936 .unwrap_or_else(std::sync::PoisonError::into_inner)
8937 .closed
8938 {
8939 return Ok(());
8940 }
8941 let current_thread = std::thread::current().id();
8942 loop {
8943 let finished = {
8944 let mut thread = self
8945 .thread
8946 .lock()
8947 .unwrap_or_else(std::sync::PoisonError::into_inner);
8948 match thread.as_ref() {
8949 Some(handle) if handle.thread().id() == current_thread => return Ok(()),
8950 Some(handle) if !handle.is_finished() => return Ok(()),
8951 Some(_) => thread.take(),
8952 None => {
8953 if self
8956 .inner
8957 .lock()
8958 .unwrap_or_else(std::sync::PoisonError::into_inner)
8959 .closed
8960 {
8961 return Ok(());
8962 }
8963 #[cfg(test)]
8964 if self.fail_next_start.swap(false, Ordering::SeqCst) {
8965 return Err(RuntimeConfigError(
8966 "injected runtime timer thread creation failure".to_string(),
8967 ));
8968 }
8969
8970 let inner = Arc::clone(&self.inner);
8971 let parker = Arc::clone(&self.parker);
8972 let deadlock_state = Arc::clone(&self.deadlock_state);
8973 let generation = self.generation;
8974 let executor_id = self.executor_id;
8975 let handle = std::thread::Builder::new()
8976 .name(self.thread_name.clone())
8977 .spawn(move || {
8978 ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
8981 let _generation = RuntimeGenerationGuard::enter(generation);
8982 timer_timekeeper_main(&inner, &parker, &deadlock_state);
8983 })
8984 .map_err(|error| {
8985 RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
8986 })?;
8987 *thread = Some(handle);
8988 return Ok(());
8989 }
8990 }
8991 };
8992
8993 if let Some(finished) = finished
8997 && finished.thread().id() != current_thread
8998 && let Err(payload) = finished.join()
8999 {
9000 discard_panic_payload(payload);
9001 }
9002 if self
9003 .inner
9004 .lock()
9005 .unwrap_or_else(std::sync::PoisonError::into_inner)
9006 .closed
9007 {
9008 return Ok(());
9009 }
9010 }
9011 }
9012
9013 fn close(&self) -> Vec<Waker> {
9014 let mut inner = self
9015 .inner
9016 .lock()
9017 .unwrap_or_else(std::sync::PoisonError::into_inner);
9018 inner.closed = true;
9019 inner.queue.clear();
9020 inner.timekeeper_parker = None;
9021 inner
9022 .entries
9023 .drain()
9024 .map(|(_, entry)| {
9025 entry.fired.store(true, Ordering::SeqCst);
9026 entry.waker
9027 })
9028 .collect()
9029 }
9030
9031 fn join(&self) {
9032 let handle = self
9033 .thread
9034 .lock()
9035 .unwrap_or_else(std::sync::PoisonError::into_inner)
9036 .take();
9037 if let Some(handle) = handle {
9038 if handle.thread().id() == std::thread::current().id() {
9039 drop(handle);
9043 return;
9044 }
9045 if let Err(payload) = handle.join() {
9046 discard_panic_payload(payload);
9047 }
9048 }
9049 }
9050}
9051
9052#[cfg(not(target_family = "wasm"))]
9053impl Drop for TimerHeap {
9054 fn drop(&mut self) {
9055 let mut admission = self.deadlock_state.begin_admission();
9056 let wakers = self.close();
9057 if !wakers.is_empty()
9058 && let Some(admission) = admission.as_mut()
9059 {
9060 admission.mark_published();
9061 }
9062 self.parker.unpark();
9063 for waker in wakers {
9064 wake_waker_contained(waker);
9065 }
9066 drop(admission);
9067 self.join();
9068 }
9069}
9070
9071#[cfg(not(target_family = "wasm"))]
9072fn timer_timekeeper_main(
9073 inner: &Mutex<TimerHeapInner>,
9074 parker: &Arc<DriverParker>,
9075 deadlock_state: &Arc<MultiThreadDeadlockState>,
9076) {
9077 loop {
9078 let mut admission = deadlock_state.begin_admission();
9082 let (due, deadline) = {
9083 let mut inner = inner
9084 .lock()
9085 .unwrap_or_else(std::sync::PoisonError::into_inner);
9086 if inner.closed {
9087 inner.timekeeper_parker = None;
9088 return;
9089 }
9090 let now = Instant::now();
9091 let mut due = Vec::new();
9092 while let Some((deadline, id)) = inner.queue.peek() {
9093 if deadline > now {
9094 break;
9095 }
9096 inner.queue.pop();
9097 if let Some(entry) = inner.entries.remove(&id) {
9098 entry.fired.store(true, Ordering::SeqCst);
9099 due.push(entry.waker);
9100 }
9101 }
9102 let deadline = inner.next_deadline();
9103 inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9104 (due, deadline)
9105 };
9106
9107 if !due.is_empty() {
9108 if let Some(admission) = admission.as_mut() {
9109 admission.mark_published();
9110 }
9111 for waker in due {
9112 wake_waker_contained(waker);
9113 }
9114 continue;
9115 }
9116 drop(admission);
9119
9120 match deadline {
9121 Some(deadline) => {
9122 let now = Instant::now();
9123 if deadline > now {
9124 parker.park_timeout(deadline - now);
9125 }
9126 }
9127 None => parker.park(),
9128 }
9129 }
9130}
9131
9132#[cfg(not(target_family = "wasm"))]
9133impl MultiThreadExecutor {
9134 fn register_timer(
9140 self: &Arc<Self>,
9141 id: TimerId,
9142 deadline: Instant,
9143 waker: Waker,
9144 fired: &Arc<AtomicBool>,
9145 ) {
9146 let mut admission = self.begin_deadlock_admission();
9147 #[cfg(test)]
9148 run_timer_registration_admission_test_hook();
9149 if let Err(error) = self.timers.ensure_started() {
9153 let _ = catch_unwind_contained(|| drop(waker));
9154 panic!("{error}");
9155 }
9156 let (became_earliest, published, discarded_waker) = {
9157 let mut inner = self
9158 .timers
9159 .inner
9160 .lock()
9161 .unwrap_or_else(std::sync::PoisonError::into_inner);
9162 if inner.closed || fired.load(Ordering::SeqCst) {
9163 fired.store(true, Ordering::SeqCst);
9164 (false, false, Some(waker))
9165 } else if let Some(entry) = inner.entries.get_mut(&id) {
9166 (
9168 false,
9169 false,
9170 Some(std::mem::replace(&mut entry.waker, waker)),
9171 )
9172 } else {
9173 let previous = inner.next_deadline();
9174 inner.entries.insert(
9175 id,
9176 HeapTimerEntry {
9177 waker,
9178 fired: Arc::clone(fired),
9179 },
9180 );
9181 inner.queue.push(deadline, id);
9182 (
9183 previous.is_none_or(|previous| deadline < previous),
9184 true,
9185 None,
9186 )
9187 }
9188 };
9189 if published && let Some(admission) = admission.as_mut() {
9190 admission.mark_published();
9191 }
9192 if let Some(waker) = discarded_waker {
9193 let _ = catch_unwind_contained(|| drop(waker));
9196 }
9197 if became_earliest {
9198 self.note_earlier_timer();
9200 }
9201 }
9202
9203 fn cancel_timer(&self, id: TimerId) {
9207 let (removed, earliest_changed) = {
9208 let mut inner = self
9209 .timers
9210 .inner
9211 .lock()
9212 .unwrap_or_else(std::sync::PoisonError::into_inner);
9213 let previous = inner.next_deadline();
9214 let removed = inner.entries.remove(&id);
9215 if removed.is_some() {
9216 let queued = inner.queue.remove(id);
9217 debug_assert!(
9218 queued.is_some(),
9219 "a live timer entry must have one indexed deadline node"
9220 );
9221 }
9222 let current = inner.next_deadline();
9223 (removed, previous != current)
9224 };
9225 if let Some(entry) = removed {
9226 let _ = catch_unwind_contained(|| drop(entry));
9229 }
9230 if earliest_changed {
9231 self.timers.parker.unpark();
9232 self.parked_drivers.wake_all();
9233 }
9234 }
9235
9236 fn next_timer_deadline(&self) -> Option<Instant> {
9238 self
9239 .timers
9240 .inner
9241 .lock()
9242 .unwrap_or_else(std::sync::PoisonError::into_inner)
9243 .next_deadline()
9244 }
9245
9246 fn fire_due_timers(&self) {
9251 let mut admission = self.begin_deadlock_admission();
9252 let due: Vec<Waker> = {
9253 let mut inner = self
9254 .timers
9255 .inner
9256 .lock()
9257 .unwrap_or_else(std::sync::PoisonError::into_inner);
9258 let now = Instant::now();
9259 let mut due = Vec::new();
9260 while let Some((deadline, id)) = inner.queue.peek() {
9261 if deadline > now {
9262 break;
9263 }
9264 inner.queue.pop();
9265 if let Some(entry) = inner.entries.remove(&id) {
9266 entry.fired.store(true, Ordering::SeqCst);
9267 due.push(entry.waker);
9268 }
9269 }
9270 due
9271 };
9272 if !due.is_empty()
9273 && let Some(admission) = admission.as_mut()
9274 {
9275 admission.mark_published();
9276 }
9277 for waker in due {
9278 wake_waker_contained(waker);
9281 }
9282 }
9283
9284 fn note_earlier_timer(self: &Arc<Self>) {
9288 self.timers.parker.unpark();
9289 self.parked_drivers.wake_one();
9290 }
9291
9292 fn shutdown_timers(&self) {
9298 let mut admission = self.begin_deadlock_admission();
9299 let wakers = self.timers.close();
9300 if !wakers.is_empty()
9301 && let Some(admission) = admission.as_mut()
9302 {
9303 admission.mark_published();
9304 }
9305 for waker in wakers {
9306 wake_waker_contained(waker);
9310 }
9311 self.timers.parker.unpark();
9312 }
9313
9314 fn begin_shutdown(&self) {
9315 let _generation = RuntimeGenerationGuard::enter(self.generation);
9316 self.stop.begin_shutdown();
9321 let queued = {
9322 let mut queue = self
9323 .blocking_queue
9324 .lock()
9325 .unwrap_or_else(std::sync::PoisonError::into_inner);
9326 queue.closed = true;
9329 queue.take_all()
9330 };
9331 for queued in queued {
9336 let _ = catch_unwind_contained(|| drop(queued));
9337 }
9338 self.shutdown_timers();
9339 }
9340
9341 fn wait_until_scheduler_idle(&self) {
9342 self.timers.join();
9343 let mut idle = self
9344 .scheduler_idle_lock
9345 .lock()
9346 .unwrap_or_else(std::sync::PoisonError::into_inner);
9347 *idle += 1;
9353 while self.active_drainers.load(Ordering::Acquire) != 0 {
9354 idle = self
9355 .scheduler_idle
9356 .wait(idle)
9357 .unwrap_or_else(std::sync::PoisonError::into_inner);
9358 }
9359 *idle -= 1;
9360 }
9361}
9362
9363pub struct Sleep {
9368 deadline: Instant,
9369 inner: SleepInner,
9370}
9371
9372enum SleepInner {
9373 #[cfg(not(target_family = "wasm"))]
9374 Heap(HeapSleep),
9375 Host(HostSleep),
9376}
9377
9378#[cfg(not(target_family = "wasm"))]
9379struct HeapSleep {
9380 id: TimerId,
9381 executor: Weak<MultiThreadExecutor>,
9382 fired: Arc<AtomicBool>,
9383 registered: bool,
9384}
9385
9386struct HostSleep {
9387 id: TimerId,
9388 drivers: Arc<TimerDriverRegistry>,
9392 armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
9396 registry: Arc<HostTimerRegistry>,
9399 fired: Arc<AtomicBool>,
9400 repoll: Arc<HostTimerRepoll>,
9404 pending: Option<HostTimerPendingGuard>,
9409}
9410
9411fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
9412 let _ = catch_unwind_contained(|| driver.cancel(id));
9413 let _ = catch_unwind_contained(|| drop(driver));
9414}
9415
9416fn cancel_host_timer_drivers(
9417 id: TimerId,
9418 armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
9419) {
9420 for (_, driver) in armed.drain() {
9421 cancel_host_timer_driver(id, driver);
9422 }
9423}
9424
9425impl Future for Sleep {
9426 type Output = ();
9427
9428 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
9429 let deadline = self.deadline;
9430 let this = self.get_mut();
9431 match &mut this.inner {
9432 #[cfg(not(target_family = "wasm"))]
9433 SleepInner::Heap(heap) => {
9434 if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
9435 if heap.registered {
9436 if let Some(executor) = heap.executor.upgrade() {
9439 executor.cancel_timer(heap.id);
9440 }
9441 heap.registered = false;
9442 }
9443 return Poll::Ready(());
9444 }
9445 let Some(executor) = heap.executor.upgrade() else {
9446 heap.fired.store(true, Ordering::SeqCst);
9447 heap.registered = false;
9448 return Poll::Ready(());
9449 };
9450 executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
9451 heap.registered = true;
9452 if heap.fired.load(Ordering::SeqCst) {
9456 heap.registered = false;
9457 return Poll::Ready(());
9458 }
9459 Poll::Pending
9460 }
9461 SleepInner::Host(host) => {
9462 if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
9463 cancel_host_timer_drivers(host.id, &mut host.armed);
9464 host.pending.take();
9465 return Poll::Ready(());
9466 }
9467 let repoll_waker = cx.waker().clone();
9472 let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
9473 Ok(waker) => waker,
9474 Err(payload) => {
9475 let _ = catch_unwind_contained(|| drop(repoll_waker));
9476 resume_unwind(payload);
9477 }
9478 };
9479 host.repoll.register(repoll_waker);
9480 if !host.registry.register(
9481 host.id,
9482 deadline,
9483 registry_waker,
9484 &host.fired,
9485 &host.drivers,
9486 &host.repoll,
9487 ) {
9488 cancel_host_timer_drivers(host.id, &mut host.armed);
9489 if host.pending.take().is_none() {
9490 host.repoll.close();
9491 }
9492 return Poll::Ready(());
9493 }
9494 if host.pending.is_none() {
9495 host.drivers.register_pending(host.id, &host.repoll);
9503 host.pending = Some(HostTimerPendingGuard {
9504 registry: Arc::clone(&host.registry),
9505 drivers: Arc::clone(&host.drivers),
9506 repoll: Arc::clone(&host.repoll),
9507 id: host.id,
9508 });
9509 }
9510 let current = host.drivers.live();
9514 assert!(
9515 !current.as_slice().is_empty(),
9516 "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
9517 that registered timer drivers (via \
9518 `napi_async_runtime::register_timer_driver`; the napi adapter registers \
9519 one per importing env through `registerTimerHost`) have all been torn down, so this \
9520 `sleep_until` can never be woken."
9521 );
9522 let missing = current
9528 .as_slice()
9529 .iter()
9530 .filter(|(current_id, _)| !host.armed.contains_key(current_id))
9531 .count();
9532 host.armed.reserve(missing);
9533 for (current_id, current_driver) in current.as_slice() {
9534 host
9535 .armed
9536 .entry(*current_id)
9537 .or_insert_with(|| Arc::clone(current_driver));
9538 }
9539 for (current_id, _) in current.as_slice() {
9540 let armed_driver = host
9541 .armed
9542 .get(current_id)
9543 .expect("every live timer driver must have cancellation ownership before callbacks");
9544 armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
9545 }
9546 let current_ids = current
9547 .as_slice()
9548 .iter()
9549 .map(|(current_id, _)| *current_id)
9550 .collect::<FxHashSet<_>>();
9551 let retired = host
9552 .armed
9553 .keys()
9554 .filter(|armed_id| !current_ids.contains(armed_id))
9555 .copied()
9556 .collect::<Vec<_>>();
9557 for retired in retired {
9558 let old_driver = host
9559 .armed
9560 .remove(&retired)
9561 .expect("the retired timer driver must remain armed");
9562 cancel_host_timer_driver(host.id, old_driver);
9563 }
9564 Poll::Pending
9565 }
9566 }
9567 }
9568}
9569
9570impl Drop for Sleep {
9571 fn drop(&mut self) {
9572 match &mut self.inner {
9573 #[cfg(not(target_family = "wasm"))]
9574 SleepInner::Heap(heap) => {
9575 if heap.registered
9576 && let Some(executor) = heap.executor.upgrade()
9577 {
9578 executor.cancel_timer(heap.id);
9579 }
9580 }
9581 SleepInner::Host(host) => {
9582 host.repoll.close();
9585 cancel_host_timer_drivers(host.id, &mut host.armed);
9586 }
9588 }
9589 }
9590}
9591
9592pub fn sleep_until(deadline: Instant) -> Sleep {
9597 make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
9598}
9599
9600fn make_sleep(
9604 backend: &RuntimeBackend,
9605 host_drivers: &Arc<TimerDriverRegistry>,
9606 deadline: Instant,
9607) -> Sleep {
9608 let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
9609 match &backend.executor {
9610 #[cfg(not(target_family = "wasm"))]
9611 RuntimeExecutor::MultiThread(executor) => Sleep {
9612 deadline,
9613 inner: SleepInner::Heap(HeapSleep {
9614 id,
9615 executor: Arc::downgrade(executor),
9616 fired: Arc::new(AtomicBool::new(false)),
9617 registered: false,
9618 }),
9619 },
9620 RuntimeExecutor::CurrentThread(executor) => {
9621 assert!(
9629 host_drivers.has_live_driver(),
9630 "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
9631 single-thread flavor delegates timers to the host event loop. Install one via \
9632 `napi_async_runtime::register_timer_driver` (the napi adapter registers \
9633 a setTimeout-based driver per importing env through `registerTimerHost` at import)."
9634 );
9635 Sleep {
9642 deadline,
9643 inner: SleepInner::Host(HostSleep {
9644 id,
9645 drivers: Arc::clone(host_drivers),
9646 armed: FxHashMap::default(),
9647 registry: Arc::clone(&executor.host_timers),
9648 fired: Arc::new(AtomicBool::new(false)),
9649 repoll: Arc::new(HostTimerRepoll::default()),
9650 pending: None,
9651 }),
9652 }
9653 }
9654 }
9655}
9656
9657#[derive(Clone)]
9658enum RuntimeExecutor {
9659 CurrentThread(Arc<CurrentThreadExecutor>),
9660 #[cfg(not(target_family = "wasm"))]
9661 MultiThread(Arc<MultiThreadExecutor>),
9662}
9663
9664enum WeakRuntimeExecutor {
9665 CurrentThread(Weak<CurrentThreadExecutor>),
9666 #[cfg(not(target_family = "wasm"))]
9667 MultiThread(Weak<MultiThreadExecutor>),
9668}
9669
9670impl WeakRuntimeExecutor {
9671 fn schedule(&self, runnable: Runnable) {
9672 match self {
9673 Self::CurrentThread(executor) => {
9674 if let Some(executor) = executor.upgrade() {
9675 executor.schedule(runnable);
9676 }
9677 }
9678 #[cfg(not(target_family = "wasm"))]
9679 Self::MultiThread(executor) => {
9680 if let Some(executor) = executor.upgrade() {
9681 executor.schedule(runnable);
9682 }
9683 }
9684 }
9685 }
9686}
9687
9688#[derive(Clone)]
9689struct RuntimeBackend {
9690 work: Arc<GenerationWork>,
9691 executor: RuntimeExecutor,
9692}
9693
9694impl RuntimeBackend {
9695 fn new(
9696 options: &RuntimeOptions,
9697 metrics: Arc<RuntimeMetrics>,
9698 ) -> Result<Self, RuntimeConfigError> {
9699 #[cfg(test)]
9700 if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
9701 return Err(RuntimeConfigError(
9702 "injected async runtime backend creation failure".to_string(),
9703 ));
9704 }
9705
9706 let work = GenerationWork::new();
9707 let stop = Arc::new(GenerationStop::default());
9708 let executor = match options.flavor {
9709 RuntimeFlavor::CurrentThread => {
9710 RuntimeExecutor::CurrentThread(Arc::new(CurrentThreadExecutor::with_generation(
9711 metrics,
9712 THREADLESS_BUILD,
9713 options.park_deadline,
9714 work.id,
9715 Arc::clone(&stop),
9716 )))
9717 }
9718 RuntimeFlavor::MultiThread => {
9719 #[cfg(not(target_family = "wasm"))]
9720 {
9721 RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
9722 options, metrics, work.id, stop,
9723 )?))
9724 }
9725 #[cfg(target_family = "wasm")]
9726 {
9727 let _ = metrics;
9728 return Err(RuntimeConfigError(
9729 "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
9730 ));
9731 }
9732 }
9733 };
9734 Ok(Self { work, executor })
9735 }
9736
9737 #[cfg(test)]
9738 fn from_executor(executor: RuntimeExecutor) -> Self {
9739 Self {
9740 work: GenerationWork::new(),
9741 executor,
9742 }
9743 }
9744
9745 fn generation(&self) -> u64 {
9746 self.work.id
9747 }
9748
9749 fn downgrade_executor(&self) -> WeakRuntimeExecutor {
9750 match &self.executor {
9751 RuntimeExecutor::CurrentThread(executor) => {
9752 WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
9753 }
9754 #[cfg(not(target_family = "wasm"))]
9755 RuntimeExecutor::MultiThread(executor) => {
9756 WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
9757 }
9758 }
9759 }
9760
9761 fn schedule(&self, runnable: Runnable) {
9762 match &self.executor {
9763 RuntimeExecutor::CurrentThread(executor) => executor.schedule(runnable),
9764 #[cfg(not(target_family = "wasm"))]
9765 RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
9766 }
9767 }
9768
9769 fn spawn_registered_blocking<F, T>(
9770 &self,
9771 function: F,
9772 registration: GenerationWorkGuard,
9773 metrics: &Arc<RuntimeMetrics>,
9774 ) -> JoinHandle<T>
9775 where
9776 F: FnOnce() -> T + Send + 'static,
9777 T: Send + 'static,
9778 {
9779 match &self.executor {
9780 RuntimeExecutor::CurrentThread(executor) => {
9781 let _ = metrics;
9782 executor.schedule_blocking(function, registration)
9783 }
9784 #[cfg(not(target_family = "wasm"))]
9785 RuntimeExecutor::MultiThread(executor) => {
9786 let generation = self.generation();
9787 let _ = metrics;
9788 let function = RegisteredBlockingFunction::new(function, registration, generation);
9789 executor.schedule_blocking_result(move || function.run())
9790 }
9791 }
9792 }
9793
9794 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
9795 match &self.executor {
9796 RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
9797 #[cfg(not(target_family = "wasm"))]
9798 RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
9799 }
9800 }
9801
9802 fn generation_stop(&self) -> Arc<GenerationStop> {
9803 match &self.executor {
9804 RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
9805 #[cfg(not(target_family = "wasm"))]
9806 RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
9807 }
9808 }
9809
9810 fn begin_shutdown(&self) {
9811 let _generation = RuntimeGenerationGuard::enter(self.generation());
9812 self.work.close_and_abort();
9813 match &self.executor {
9814 RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
9815 #[cfg(not(target_family = "wasm"))]
9816 RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
9817 }
9818 }
9819
9820 fn wait_until_idle(&self) {
9821 self.work.wait_until_idle();
9822 match &self.executor {
9823 RuntimeExecutor::CurrentThread(executor) => executor.wait_until_scheduler_idle(),
9824 #[cfg(not(target_family = "wasm"))]
9825 RuntimeExecutor::MultiThread(executor) => executor.wait_until_scheduler_idle(),
9826 }
9827 }
9828
9829 #[cfg(not(target_family = "wasm"))]
9830 fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
9831 match &self.executor {
9832 RuntimeExecutor::CurrentThread(_) => None,
9833 RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
9834 }
9835 }
9836
9837 fn stop_identity(&self) -> RuntimeStopIdentity {
9838 RuntimeStopIdentity {
9839 generation: self.generation(),
9840 #[cfg(not(target_family = "wasm"))]
9841 executor_id: match &self.executor {
9842 RuntimeExecutor::CurrentThread(_) => None,
9843 RuntimeExecutor::MultiThread(executor) => Some(executor.id),
9844 },
9845 }
9846 }
9847
9848 fn is_current(&self) -> bool {
9849 self.stop_identity().is_current()
9850 }
9851}
9852
9853#[derive(Clone, Copy)]
9854struct RuntimeStopIdentity {
9855 generation: u64,
9856 #[cfg(not(target_family = "wasm"))]
9857 executor_id: Option<u64>,
9858}
9859
9860impl RuntimeStopIdentity {
9861 fn is_current(self) -> bool {
9862 if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
9863 return true;
9864 }
9865 #[cfg(not(target_family = "wasm"))]
9866 if let Some(executor_id) = self.executor_id
9867 && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
9868 || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
9869 {
9870 return true;
9871 }
9872 false
9873 }
9874}
9875
9876enum RuntimeLifecycle {
9877 Initial,
9878 StoppedBeforeFirstUse,
9879 Running(RuntimeBackend),
9880 Stopping(RuntimeStopIdentity),
9881 StoppingWithoutBackend(ZeroBackendShutdownTarget),
9882 Stopped,
9883}
9884
9885#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9886enum ZeroBackendShutdownTarget {
9887 StoppedBeforeFirstUse,
9888 Stopped,
9889}
9890
9891impl ZeroBackendShutdownTarget {
9892 fn into_lifecycle(self) -> RuntimeLifecycle {
9893 match self {
9894 Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
9895 Self::Stopped => RuntimeLifecycle::Stopped,
9896 }
9897 }
9898}
9899
9900struct RuntimeState {
9901 options: RuntimeOptions,
9902 lifecycle: RuntimeLifecycle,
9903 rejected_drops: usize,
9904}
9905
9906#[cfg(test)]
9907thread_local! {
9908 static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
9909 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9910 static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
9911 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9912 static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
9913 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9914 static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
9915 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9916 static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
9917 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9918 static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
9919 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9920 static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
9921 std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
9922}
9923
9924#[cfg(test)]
9925fn run_before_runtime_submission_lock_test_hook() {
9926 if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9927 {
9928 hook();
9929 }
9930}
9931
9932#[cfg(test)]
9933fn run_after_async_work_registration_test_hook() {
9934 if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9935 {
9936 hook();
9937 }
9938}
9939
9940#[cfg(test)]
9941fn run_after_blocking_work_registration_test_hook() {
9942 if let Some(hook) =
9943 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9944 {
9945 hook();
9946 }
9947}
9948
9949#[cfg(test)]
9950fn run_after_current_thread_host_admission_test_hook() {
9951 if let Some(hook) =
9952 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9953 {
9954 hook();
9955 }
9956}
9957
9958#[cfg(test)]
9959fn run_before_initial_start_wait_test_hook() {
9960 if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
9961 hook();
9962 }
9963}
9964
9965#[cfg(test)]
9966fn run_after_generation_stop_publication_test_hook() {
9967 if let Some(hook) =
9968 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9969 {
9970 hook();
9971 }
9972}
9973
9974thread_local! {
9975 static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
9976 const { std::cell::Cell::new(0) };
9977}
9978
9979struct RejectedSubmissionDropContext;
9980
9981impl RejectedSubmissionDropContext {
9982 fn enter() -> Self {
9983 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
9984 Self
9985 }
9986
9987 fn is_current() -> bool {
9988 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
9989 }
9990}
9991
9992impl Drop for RejectedSubmissionDropContext {
9993 fn drop(&mut self) {
9994 IN_REJECTED_SUBMISSION_DROP.with(|depth| {
9995 depth.set(
9996 depth
9997 .get()
9998 .checked_sub(1)
9999 .expect("rejected submission drop depth underflow"),
10000 );
10001 });
10002 }
10003}
10004
10005struct RuntimeController {
10006 state: Mutex<RuntimeState>,
10007 lifecycle_changed: Condvar,
10008 metrics: Arc<RuntimeMetrics>,
10009 current_backend: ArcSwapOption<RuntimeBackend>,
10015}
10016
10017struct RejectedSubmissionGuard<'a> {
10018 controller: &'a RuntimeController,
10019}
10020
10021impl Drop for RejectedSubmissionGuard<'_> {
10022 fn drop(&mut self) {
10023 let mut state = self
10024 .controller
10025 .state
10026 .lock()
10027 .unwrap_or_else(std::sync::PoisonError::into_inner);
10028 state.rejected_drops = state
10029 .rejected_drops
10030 .checked_sub(1)
10031 .expect("rejected submission drop count underflow");
10032 self.controller.lifecycle_changed.notify_all();
10033 }
10034}
10035
10036impl RuntimeController {
10037 fn new() -> Self {
10038 let options = RuntimeOptions::default()
10039 .validate()
10040 .expect("default async runtime options must be valid");
10041 Self {
10042 state: Mutex::new(RuntimeState {
10043 options,
10044 lifecycle: RuntimeLifecycle::Initial,
10045 rejected_drops: 0,
10046 }),
10047 lifecycle_changed: Condvar::new(),
10048 metrics: Arc::new(RuntimeMetrics::default()),
10049 current_backend: ArcSwapOption::empty(),
10050 }
10051 }
10052
10053 fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10054 let options = options.validate()?;
10055 let mut state = self
10056 .state
10057 .lock()
10058 .unwrap_or_else(std::sync::PoisonError::into_inner);
10059 Self::ensure_configuration_mutable(&state)?;
10060 state.options = options;
10061 Ok(())
10062 }
10063
10064 fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10065 self.configure_partial_inner(patch, |_| {})
10066 }
10067
10068 fn configure_partial_inner(
10069 &self,
10070 patch: RuntimeOptionsPatch,
10071 after_merge: impl FnOnce(&RuntimeOptions),
10072 ) -> Result<(), RuntimeConfigError> {
10073 let mut state = self
10074 .state
10075 .lock()
10076 .unwrap_or_else(std::sync::PoisonError::into_inner);
10077 Self::ensure_configuration_mutable(&state)?;
10078
10079 let mut options = state.options.clone();
10080 patch.apply_to(&mut options);
10081 after_merge(&options);
10082 let options = options.validate()?;
10083 state.options = options;
10084 Ok(())
10085 }
10086
10087 fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10088 if matches!(
10089 &state.lifecycle,
10090 RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
10091 ) {
10092 return Ok(());
10093 }
10094 Err(RuntimeConfigError(
10095 "the async runtime configuration is frozen; configure it before the first async call"
10096 .to_string(),
10097 ))
10098 }
10099
10100 fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10101 let Some(active_generation) = active_runtime_generation() else {
10102 return Ok(());
10103 };
10104 let lifecycle_generation = match &state.lifecycle {
10105 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10106 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10107 RuntimeLifecycle::Initial
10108 | RuntimeLifecycle::StoppedBeforeFirstUse
10109 | RuntimeLifecycle::StoppingWithoutBackend(_)
10110 | RuntimeLifecycle::Stopped => None,
10111 };
10112 if lifecycle_generation == Some(active_generation) {
10113 return Ok(());
10114 }
10115 Err(RuntimeConfigError(
10116 "cannot access or control the async runtime from work owned by a retired generation"
10117 .to_string(),
10118 ))
10119 }
10120
10121 fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
10122 Self::ensure_active_generation_current(state)?;
10123 match &state.lifecycle {
10124 RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
10125 RuntimeLifecycle::StoppedBeforeFirstUse
10126 | RuntimeLifecycle::Stopping(_)
10127 | RuntimeLifecycle::StoppingWithoutBackend(_)
10128 | RuntimeLifecycle::Stopped => {
10129 return Err(RuntimeConfigError(
10130 "the async runtime is stopped; call start before submitting work".to_string(),
10131 ));
10132 }
10133 RuntimeLifecycle::Initial => {}
10134 }
10135 if state.rejected_drops != 0 {
10136 return Err(RuntimeConfigError(
10137 "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
10138 ));
10139 }
10140 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10141 state.lifecycle = RuntimeLifecycle::Running(backend.clone());
10142 self.current_backend.store(Some(Arc::new(backend.clone())));
10143 Ok(backend)
10144 }
10145
10146 fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
10147 let mut state = self
10148 .state
10149 .lock()
10150 .unwrap_or_else(std::sync::PoisonError::into_inner);
10151 self.backend_locked(&mut state)
10152 }
10153
10154 fn backend(&self) -> RuntimeBackend {
10155 self.try_backend().unwrap_or_else(|error| panic!("{error}"))
10156 }
10157
10158 fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
10159 let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
10160 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10161 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10162 RuntimeLifecycle::Initial
10163 | RuntimeLifecycle::StoppedBeforeFirstUse
10164 | RuntimeLifecycle::StoppingWithoutBackend(_)
10165 | RuntimeLifecycle::Stopped => None,
10166 });
10167 state.rejected_drops += 1;
10168 generation
10169 }
10170
10171 #[inline]
10179 fn fast_register<R>(
10180 &self,
10181 register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
10182 ) -> Option<(Arc<RuntimeBackend>, R)> {
10183 let guard = self.current_backend.load();
10184 let backend = guard.as_ref()?; if let Some(active) = active_runtime_generation() {
10186 if active != backend.generation() {
10187 return None; }
10189 }
10190 let registration = register(&backend.work)?; Some((Arc::clone(backend), registration))
10194 }
10195
10196 fn try_spawn_tracked<F, T>(
10197 &self,
10198 future: F,
10199 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10200 where
10201 F: Future<Output = T> + Send + 'static,
10202 T: Send + 'static,
10203 {
10204 #[cfg(test)]
10205 run_before_runtime_submission_lock_test_hook();
10206
10207 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
10208 #[cfg(test)]
10209 run_after_async_work_registration_test_hook();
10210 return Ok(spawn_registered(
10211 &backend,
10212 Arc::clone(&self.metrics),
10213 future,
10214 registration,
10215 ));
10216 }
10217
10218 let (backend, registration) = {
10219 let mut state = self
10220 .state
10221 .lock()
10222 .unwrap_or_else(std::sync::PoisonError::into_inner);
10223 let backend = match self.backend_locked(&mut state) {
10224 Ok(backend) => backend,
10225 Err(error) => {
10226 let generation = Self::begin_rejected_drop(&mut state);
10227 return Err((error, future, generation));
10228 }
10229 };
10230 let Some(registration) = backend.work.try_register_async() else {
10231 let generation = Self::begin_rejected_drop(&mut state);
10232 return Err((
10233 RuntimeConfigError(
10234 "the async runtime is stopped; call start before submitting work".to_string(),
10235 ),
10236 future,
10237 generation,
10238 ));
10239 };
10240 (backend, registration)
10241 };
10242 #[cfg(test)]
10243 run_after_async_work_registration_test_hook();
10244 Ok(spawn_registered(
10245 &backend,
10246 Arc::clone(&self.metrics),
10247 future,
10248 registration,
10249 ))
10250 }
10251
10252 fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10253 where
10254 F: Future<Output = T> + Send + 'static,
10255 T: Send + 'static,
10256 {
10257 match self.try_spawn_tracked(future) {
10258 Ok(handle) => Ok(handle),
10259 Err((error, future, _generation)) => {
10260 drop(RejectedSubmissionGuard { controller: self });
10261 Err((error, future))
10262 }
10263 }
10264 }
10265
10266 fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
10267 where
10268 F: Future<Output = T> + Send + 'static,
10269 T: Send + 'static,
10270 {
10271 match self.try_spawn_tracked(future) {
10272 Ok(handle) => handle,
10273 Err((error, future, generation)) => {
10274 let _rejection = RejectedSubmissionGuard { controller: self };
10275 let _drop_context = RejectedSubmissionDropContext::enter();
10276 let _generation = generation.map(RuntimeGenerationGuard::enter);
10277 let _ = catch_unwind_contained(|| drop(future));
10278 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10279 message: error.to_string(),
10280 }))))
10281 }
10282 }
10283 }
10284
10285 fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
10286 where
10287 F: Future<Output = ()> + Send + 'static,
10288 {
10289 match self.try_spawn(future) {
10290 Ok(handle) => {
10291 handle.detach();
10292 Ok(())
10293 }
10294 Err((_, future)) => Err(future),
10295 }
10296 }
10297
10298 fn try_spawn_blocking_tracked<F, T>(
10299 &self,
10300 function: F,
10301 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10302 where
10303 F: FnOnce() -> T + Send + 'static,
10304 T: Send + 'static,
10305 {
10306 #[cfg(test)]
10307 run_before_runtime_submission_lock_test_hook();
10308
10309 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
10310 #[cfg(test)]
10311 run_after_blocking_work_registration_test_hook();
10312 return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
10313 }
10314
10315 let (backend, registration) = {
10316 let mut state = self
10317 .state
10318 .lock()
10319 .unwrap_or_else(std::sync::PoisonError::into_inner);
10320 let backend = match self.backend_locked(&mut state) {
10321 Ok(backend) => backend,
10322 Err(error) => {
10323 let generation = Self::begin_rejected_drop(&mut state);
10324 return Err((error, function, generation));
10325 }
10326 };
10327 let Some(registration) = backend.work.try_register_work() else {
10328 let generation = Self::begin_rejected_drop(&mut state);
10329 return Err((
10330 RuntimeConfigError(
10331 "the async runtime is stopped; call start before submitting work".to_string(),
10332 ),
10333 function,
10334 generation,
10335 ));
10336 };
10337 (backend, registration)
10338 };
10339 #[cfg(test)]
10340 run_after_blocking_work_registration_test_hook();
10341 Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
10342 }
10343
10344 fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10345 where
10346 F: FnOnce() -> T + Send + 'static,
10347 T: Send + 'static,
10348 {
10349 match self.try_spawn_blocking_tracked(function) {
10350 Ok(handle) => Ok(handle),
10351 Err((error, function, _generation)) => {
10352 drop(RejectedSubmissionGuard { controller: self });
10353 Err((error, function))
10354 }
10355 }
10356 }
10357
10358 fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
10359 where
10360 F: FnOnce() -> T + Send + 'static,
10361 T: Send + 'static,
10362 {
10363 match self.try_spawn_blocking_tracked(function) {
10364 Ok(handle) => handle,
10365 Err((error, function, generation)) => {
10366 let _rejection = RejectedSubmissionGuard { controller: self };
10367 let _drop_context = RejectedSubmissionDropContext::enter();
10368 let _generation = generation.map(RuntimeGenerationGuard::enter);
10369 let _ = catch_unwind_contained(|| drop(function));
10370 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10371 message: error.to_string(),
10372 }))))
10373 }
10374 }
10375 }
10376
10377 #[cfg(test)]
10378 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10379 self
10380 .try_block_on(future)
10381 .unwrap_or_else(|error| panic!("{error}"))
10382 }
10383
10384 fn try_block_on_scope(
10385 &self,
10386 ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
10387 let mut state = self
10388 .state
10389 .lock()
10390 .unwrap_or_else(std::sync::PoisonError::into_inner);
10391 let backend = match self.backend_locked(&mut state) {
10392 Ok(backend) => backend,
10393 Err(error) => {
10394 let generation = Self::begin_rejected_drop(&mut state);
10395 return Err((error, generation));
10396 }
10397 };
10398 let Some(registration) = backend.work.try_register_work() else {
10399 let generation = Self::begin_rejected_drop(&mut state);
10400 return Err((
10401 RuntimeConfigError(
10402 "the async runtime is stopped; call start before submitting work".to_string(),
10403 ),
10404 generation,
10405 ));
10406 };
10407 Ok((backend, registration))
10408 }
10409
10410 fn try_block_on(
10411 &self,
10412 future: Pin<&mut dyn Future<Output = ()>>,
10413 ) -> Result<BlockOnOutcome, RuntimeConfigError> {
10414 let (backend, registration) = match self.try_block_on_scope() {
10415 Ok(scope) => scope,
10416 Err((error, _generation)) => {
10417 drop(RejectedSubmissionGuard { controller: self });
10418 return Err(error);
10419 }
10420 };
10421 let _registration = registration;
10422 let _generation = RuntimeGenerationGuard::enter(backend.generation());
10423 Ok(backend.block_on(future))
10424 }
10425
10426 fn start(&self) -> Result<(), RuntimeConfigError> {
10427 let mut state = self
10428 .state
10429 .lock()
10430 .unwrap_or_else(std::sync::PoisonError::into_inner);
10431 loop {
10432 Self::ensure_active_generation_current(&state)?;
10433 match &state.lifecycle {
10434 RuntimeLifecycle::Initial => {
10438 if state.rejected_drops == 0 {
10439 return Ok(());
10440 }
10441 if RejectedSubmissionDropContext::is_current() {
10442 return Err(RuntimeConfigError(
10443 "cannot start the async runtime while a rejected submission is being destroyed"
10444 .to_string(),
10445 ));
10446 }
10447 #[cfg(test)]
10448 run_before_initial_start_wait_test_hook();
10449 state = self
10450 .lifecycle_changed
10451 .wait(state)
10452 .unwrap_or_else(std::sync::PoisonError::into_inner);
10453 }
10454 RuntimeLifecycle::Running(_) => return Ok(()),
10455 RuntimeLifecycle::Stopping(identity) => {
10456 if identity.is_current() {
10457 return Err(RuntimeConfigError(
10458 "cannot restart the async runtime from work in the generation being stopped"
10459 .to_string(),
10460 ));
10461 }
10462 state = self
10463 .lifecycle_changed
10464 .wait(state)
10465 .unwrap_or_else(std::sync::PoisonError::into_inner);
10466 }
10467 RuntimeLifecycle::StoppingWithoutBackend(_) => {
10468 if RejectedSubmissionDropContext::is_current() {
10469 return Err(RuntimeConfigError(
10470 "cannot restart the async runtime while a rejected submission is being destroyed"
10471 .to_string(),
10472 ));
10473 }
10474 state = self
10475 .lifecycle_changed
10476 .wait(state)
10477 .unwrap_or_else(std::sync::PoisonError::into_inner);
10478 }
10479 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10480 if state.rejected_drops == 0 {
10481 if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10482 state.lifecycle = RuntimeLifecycle::Initial;
10483 return Ok(());
10484 }
10485 break;
10486 }
10487 if RejectedSubmissionDropContext::is_current() {
10488 return Err(RuntimeConfigError(
10489 "cannot restart the async runtime while a rejected submission is being destroyed"
10490 .to_string(),
10491 ));
10492 }
10493 state = self
10494 .lifecycle_changed
10495 .wait(state)
10496 .unwrap_or_else(std::sync::PoisonError::into_inner);
10497 }
10498 }
10499 }
10500 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10501 self.current_backend.store(Some(Arc::new(backend.clone())));
10502 state.lifecycle = RuntimeLifecycle::Running(backend);
10503 Ok(())
10504 }
10505
10506 fn options(&self) -> RuntimeOptions {
10507 self
10508 .state
10509 .lock()
10510 .unwrap_or_else(std::sync::PoisonError::into_inner)
10511 .options
10512 .clone()
10513 }
10514
10515 fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
10516 let state = self
10517 .state
10518 .lock()
10519 .unwrap_or_else(std::sync::PoisonError::into_inner);
10520 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10521 return None;
10522 };
10523 match &backend.executor {
10524 RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
10525 #[cfg(not(target_family = "wasm"))]
10526 RuntimeExecutor::MultiThread(_) => None,
10527 }
10528 }
10529
10530 fn request_current_thread_drain(&self) {
10531 if let Some(executor) = self.running_current_thread_executor() {
10532 executor.request_drain_if_queued();
10533 }
10534 }
10535
10536 fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
10537 let executor = {
10538 let state = self
10539 .state
10540 .lock()
10541 .unwrap_or_else(std::sync::PoisonError::into_inner);
10542 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10543 return;
10544 };
10545 match &backend.executor {
10546 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
10547 #[cfg(not(target_family = "wasm"))]
10548 RuntimeExecutor::MultiThread(_) => return,
10549 }
10550 };
10551 executor.cancel_host_dispatch(failure);
10552 }
10553
10554 #[cfg(test)]
10555 fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
10556 let executor = {
10557 let state = self
10558 .state
10559 .lock()
10560 .unwrap_or_else(std::sync::PoisonError::into_inner);
10561 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10562 return;
10563 };
10564 match &backend.executor {
10565 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
10566 #[cfg(not(target_family = "wasm"))]
10567 RuntimeExecutor::MultiThread(_) => return,
10568 }
10569 };
10570 executor.cancel_host_dispatch_for_test(dispatch);
10571 }
10572
10573 fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
10574 let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
10575 self.drive_current_thread_dispatch_with_lease(dispatch, true)
10576 }
10577
10578 fn drive_current_thread_dispatch_with_lease(
10579 &self,
10580 dispatch: u64,
10581 mark_serviced: bool,
10582 ) -> Option<CurrentThreadTaskCallbackLease> {
10583 let mut host_turn = {
10584 let state = self
10585 .state
10586 .lock()
10587 .unwrap_or_else(std::sync::PoisonError::into_inner);
10588 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10589 return None;
10590 };
10591 let executor = match &backend.executor {
10592 RuntimeExecutor::CurrentThread(executor) => executor,
10593 #[cfg(not(target_family = "wasm"))]
10594 RuntimeExecutor::MultiThread(_) => return None,
10595 };
10596 let host_turn = executor.try_admit_host_turn(dispatch)?;
10597 if mark_serviced {
10598 CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
10599 }
10600 host_turn
10601 };
10602 #[cfg(test)]
10603 run_after_current_thread_host_admission_test_hook();
10604 host_turn.drive();
10605 Some(CurrentThreadTaskCallbackLease::new(host_turn))
10606 }
10607
10608 #[cfg(test)]
10609 fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
10610 self
10611 .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
10612 .is_some()
10613 }
10614
10615 fn shutdown(&self) -> Result<(), RuntimeConfigError> {
10616 let backend = {
10617 let mut state = self
10618 .state
10619 .lock()
10620 .unwrap_or_else(std::sync::PoisonError::into_inner);
10621 loop {
10622 Self::ensure_active_generation_current(&state)?;
10623 match &state.lifecycle {
10624 RuntimeLifecycle::Initial => {
10625 if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
10626 return Err(RuntimeConfigError(
10627 "cannot shut down the async runtime while a rejected submission is being destroyed"
10628 .to_string(),
10629 ));
10630 }
10631 let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
10632 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10633 self.lifecycle_changed.notify_all();
10634 while state.rejected_drops != 0 {
10635 state = self
10636 .lifecycle_changed
10637 .wait(state)
10638 .unwrap_or_else(std::sync::PoisonError::into_inner);
10639 }
10640 debug_assert!(matches!(
10641 state.lifecycle,
10642 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10643 ));
10644 state.lifecycle = target.into_lifecycle();
10645 self.lifecycle_changed.notify_all();
10646 return Ok(());
10647 }
10648 RuntimeLifecycle::Running(backend) => {
10649 if backend.is_current() {
10650 return Err(RuntimeConfigError(
10651 "cannot shut down the async runtime from work running on that runtime".to_string(),
10652 ));
10653 }
10654 let identity = backend.stop_identity();
10655 let stop = backend.generation_stop();
10656 let stop_publication = stop.publication_guard();
10661 stop_publication.publish_stopping();
10662 let RuntimeLifecycle::Running(backend) =
10663 std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
10664 else {
10665 unreachable!();
10666 };
10667 self.current_backend.store(None);
10668 drop(stop_publication);
10672 break backend;
10673 }
10674 RuntimeLifecycle::Stopping(identity) => {
10675 if identity.is_current() {
10676 return Err(RuntimeConfigError(
10677 "cannot wait for async runtime shutdown from work in the generation being stopped"
10678 .to_string(),
10679 ));
10680 }
10681 state = self
10682 .lifecycle_changed
10683 .wait(state)
10684 .unwrap_or_else(std::sync::PoisonError::into_inner);
10685 }
10686 RuntimeLifecycle::StoppingWithoutBackend(_) => {
10687 if RejectedSubmissionDropContext::is_current() {
10688 return Err(RuntimeConfigError(
10689 "cannot shut down the async runtime while a rejected submission is being destroyed"
10690 .to_string(),
10691 ));
10692 }
10693 state = self
10694 .lifecycle_changed
10695 .wait(state)
10696 .unwrap_or_else(std::sync::PoisonError::into_inner);
10697 }
10698 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10699 if state.rejected_drops == 0 {
10700 return Ok(());
10701 }
10702 if RejectedSubmissionDropContext::is_current() {
10703 return Err(RuntimeConfigError(
10704 "cannot shut down the async runtime while a rejected submission is being destroyed"
10705 .to_string(),
10706 ));
10707 }
10708 let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10709 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
10710 } else {
10711 ZeroBackendShutdownTarget::Stopped
10712 };
10713 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10714 self.lifecycle_changed.notify_all();
10715 while state.rejected_drops != 0 {
10716 state = self
10717 .lifecycle_changed
10718 .wait(state)
10719 .unwrap_or_else(std::sync::PoisonError::into_inner);
10720 }
10721 debug_assert!(matches!(
10722 state.lifecycle,
10723 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10724 ));
10725 state.lifecycle = target.into_lifecycle();
10726 self.lifecycle_changed.notify_all();
10727 return Ok(());
10728 }
10729 }
10730 }
10731 };
10732
10733 #[cfg(test)]
10734 run_after_generation_stop_publication_test_hook();
10735 backend.begin_shutdown();
10736 backend.wait_until_idle();
10737
10738 #[cfg(not(target_family = "wasm"))]
10739 let worker_lifecycle = backend.worker_lifecycle();
10740 drop(backend);
10741
10742 #[cfg(not(target_family = "wasm"))]
10743 if let Some(worker_lifecycle) = worker_lifecycle {
10744 worker_lifecycle.wait_for_all_workers();
10745 }
10746
10747 let mut state = self
10748 .state
10749 .lock()
10750 .unwrap_or_else(std::sync::PoisonError::into_inner);
10751 while state.rejected_drops != 0 {
10752 state = self
10753 .lifecycle_changed
10754 .wait(state)
10755 .unwrap_or_else(std::sync::PoisonError::into_inner);
10756 }
10757 debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
10758 state.lifecycle = RuntimeLifecycle::Stopped;
10759 self.lifecycle_changed.notify_all();
10760 Ok(())
10761 }
10762}
10763
10764static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
10765
10766pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10767 RUNTIME.configure(options)
10768}
10769
10770pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10771 RUNTIME.configure_partial(patch)
10772}
10773
10774pub fn configured_options() -> RuntimeOptions {
10775 RUNTIME.options()
10776}
10777
10778pub fn is_multi_threaded() -> bool {
10779 RUNTIME.options().flavor == RuntimeFlavor::MultiThread
10780}
10781
10782fn spawn_registered<F, T>(
10783 backend: &RuntimeBackend,
10784 metrics: Arc<RuntimeMetrics>,
10785 future: F,
10786 registration: (AbortRegistration, GenerationWorkGuard),
10787) -> JoinHandle<T>
10788where
10789 F: Future<Output = T> + Send + 'static,
10790 T: Send + 'static,
10791{
10792 metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
10793 let generation = backend.generation();
10794 let (abort_registration, work_registration) = registration;
10795 #[cfg(not(target_family = "wasm"))]
10796 let dependency = Arc::new(TaskDependency::new_task(generation));
10797 #[cfg(not(target_family = "wasm"))]
10798 let task_dependency = Arc::clone(&dependency);
10799 let wrapped = async move {
10800 let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
10801 let mut abortable = std::pin::pin!(abortable);
10802 let polled = futures::future::poll_fn(|cx| {
10803 #[cfg(not(target_family = "wasm"))]
10804 task_dependency.begin_poll();
10805 #[cfg(not(target_family = "wasm"))]
10806 let _dependency = TaskDependencyGuard::enter(&task_dependency);
10807 abortable.as_mut().poll(cx)
10808 });
10809 match AssertUnwindSafe(polled).catch_unwind().await {
10810 Ok(Ok(output)) => {
10811 metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
10812 Ok(ContainedTaskOutput::new(output, generation))
10813 }
10814 Ok(Err(_)) => Err(JoinError {
10815 message: "async runtime stopped before the task completed".to_string(),
10816 }),
10817 Err(panic) => {
10818 metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
10819 Err(panic_payload_to_join_error(panic))
10820 }
10821 }
10822 };
10823 let scheduler = backend.downgrade_executor();
10824 let (runnable, task) = async_task::spawn(
10828 RegisteredTaskFuture::new(wrapped, work_registration, generation),
10829 move |runnable| {
10830 scheduler.schedule(runnable);
10831 },
10832 );
10833 backend.schedule(runnable);
10834 JoinHandle(JoinHandleInner::Task {
10835 task: task.fallible(),
10836 awaiter: None,
10837 #[cfg(not(target_family = "wasm"))]
10838 dependency,
10839 })
10840}
10841
10842pub fn spawn<F, T>(future: F) -> JoinHandle<T>
10843where
10844 F: Future<Output = T> + Send + 'static,
10845 T: Send + 'static,
10846{
10847 RUNTIME.spawn(future)
10848}
10849
10850pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10855where
10856 F: Future<Output = T> + Send + 'static,
10857 T: Send + 'static,
10858{
10859 RUNTIME.try_spawn(future)
10860}
10861
10862pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
10863where
10864 F: Future<Output = ()> + Send + 'static,
10865{
10866 RUNTIME.try_spawn_detached(future)
10867}
10868
10869pub fn spawn_detached<F>(future: F)
10870where
10871 F: Future<Output = ()> + Send + 'static,
10872{
10873 spawn(future).detach();
10874}
10875
10876pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
10877where
10878 F: FnOnce() -> T + Send + 'static,
10879 T: Send + 'static,
10880{
10881 RUNTIME.spawn_blocking(function)
10882}
10883
10884pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10889where
10890 F: FnOnce() -> T + Send + 'static,
10891 T: Send + 'static,
10892{
10893 RUNTIME.try_spawn_blocking(function)
10894}
10895
10896fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
10897 let (backend, registration) = match controller.try_block_on_scope() {
10898 Ok(scope) => scope,
10899 Err((error, generation)) => {
10900 let _rejection = RejectedSubmissionGuard { controller };
10901 let _drop_context = RejectedSubmissionDropContext::enter();
10902 let _generation = generation.map(RuntimeGenerationGuard::enter);
10903 let _ = catch_unwind_contained(|| drop(future));
10904 panic!("{error}");
10905 }
10906 };
10907 let generation = backend.generation();
10908 let _registration = registration;
10909 let _generation = RuntimeGenerationGuard::enter(generation);
10910 let mut output = None;
10911 {
10912 let future = ContainedFuture::new(future, generation);
10913 let mut erased = std::pin::pin!(async {
10914 output = Some(future.await);
10915 });
10916 let _ = backend.block_on(erased.as_mut());
10917 }
10922 output.expect("async runtime returned before the future completed")
10923}
10924
10925pub fn block_on<F: Future>(future: F) -> F::Output {
10926 block_on_with_controller(&RUNTIME, future)
10927}
10928
10929pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
10930 try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
10931}
10932
10933pub fn try_block_on_dyn(
10935 future: Pin<&mut dyn Future<Output = ()>>,
10936) -> Result<(), RuntimeConfigError> {
10937 match RUNTIME.try_block_on(future)? {
10938 BlockOnOutcome::Completed => Ok(()),
10939 BlockOnOutcome::Stopped => Err(RuntimeConfigError(
10940 "the async runtime stopped before block_on completed its future".to_string(),
10941 )),
10942 }
10943}
10944
10945pub fn start() -> Result<(), RuntimeConfigError> {
10946 RUNTIME.start()
10947}
10948
10949pub fn shutdown() -> Result<(), RuntimeConfigError> {
10950 RUNTIME.shutdown()
10951}
10952
10953fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
10954 for failure in completion.failed_dispatches {
10955 RUNTIME.cancel_current_thread_dispatch(failure);
10956 }
10957 if completion.redispatch {
10958 RUNTIME.request_current_thread_drain();
10959 }
10960}
10961
10962pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10971 complete_current_thread_task_delivery(
10972 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
10973 );
10974}
10975
10976pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10982 complete_current_thread_task_delivery(
10983 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
10984 );
10985}
10986
10987pub fn cancel_current_thread_task_dispatch(delivery: u64) {
10994 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
10995}
10996
10997pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11006 RUNTIME.drive_current_thread_tasks(delivery)
11007}
11008
11009pub fn reset_metrics() {
11010 RUNTIME.metrics.reset();
11011}
11012
11013pub fn metrics() -> RuntimeMetricsSnapshot {
11014 let options = RUNTIME.options();
11015 RUNTIME.metrics.snapshot(&options)
11016}
11017
11018#[cfg(test)]
11019mod tests {
11020 use super::*;
11021
11022 static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11023 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11024 LazyLock::new(Mutex::default);
11025 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11026 static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11027 static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11028 static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11029 LazyLock::new(Mutex::default);
11030 static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11031 static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11032 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11033 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11034 LazyLock::new(Mutex::default);
11035 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
11036 static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
11037 CurrentThreadTaskDriverRegistry,
11038 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11039 static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11040 static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11041 static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11042 LazyLock::new(Mutex::default);
11043 static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11044 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11045 static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11046 LazyLock::new(Mutex::default);
11047 static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11048 static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11049 LazyLock::new(Mutex::default);
11050 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11051 LazyLock::new(Mutex::default);
11052 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
11053 CurrentThreadTaskDriverRegistry,
11054 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11055 static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11056 LazyLock::new(Mutex::default);
11057 static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11058 static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11059 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11060 static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11061
11062 thread_local! {
11063 static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
11064 const { std::cell::Cell::new(0) };
11065 static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
11066 const { std::cell::Cell::new(0) };
11067 }
11068
11069 #[cfg(not(target_family = "wasm"))]
11070 struct StopPollProbe {
11071 polls: Arc<AtomicUsize>,
11072 first_poll: Option<std::sync::mpsc::Sender<()>>,
11073 }
11074
11075 #[cfg(not(target_family = "wasm"))]
11076 impl Future for StopPollProbe {
11077 type Output = ();
11078
11079 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
11080 self.polls.fetch_add(1, Ordering::SeqCst);
11081 if let Some(first_poll) = self.first_poll.take() {
11082 first_poll.send(()).unwrap();
11083 }
11084 Poll::Pending
11085 }
11086 }
11087
11088 #[cfg(not(target_family = "wasm"))]
11089 struct PanickingJoinAwaiter {
11090 wakes: Arc<AtomicUsize>,
11091 drops: Arc<AtomicUsize>,
11092 }
11093
11094 #[cfg(not(target_family = "wasm"))]
11095 impl std::task::Wake for PanickingJoinAwaiter {
11096 fn wake(self: Arc<Self>) {
11097 self.wakes.fetch_add(1, Ordering::SeqCst);
11098 panic!("intentional JoinHandle awaiter wake panic");
11099 }
11100
11101 fn wake_by_ref(self: &Arc<Self>) {
11102 self.wakes.fetch_add(1, Ordering::SeqCst);
11103 panic!("intentional JoinHandle awaiter wake-by-ref panic");
11104 }
11105 }
11106
11107 #[cfg(not(target_family = "wasm"))]
11108 impl Drop for PanickingJoinAwaiter {
11109 fn drop(&mut self) {
11110 self.drops.fetch_add(1, Ordering::SeqCst);
11111 panic!("intentional JoinHandle awaiter destructor panic");
11112 }
11113 }
11114
11115 #[cfg(not(target_family = "wasm"))]
11116 fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
11117 Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
11118 }
11119
11120 #[cfg(not(target_family = "wasm"))]
11121 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11122 enum JoinAwaiterWakerEventKind {
11123 Clone,
11124 Wake,
11125 WakeByRef,
11126 Drop,
11127 }
11128
11129 #[cfg(not(target_family = "wasm"))]
11130 struct JoinAwaiterWakerState {
11131 events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
11132 }
11133
11134 #[cfg(not(target_family = "wasm"))]
11135 impl JoinAwaiterWakerState {
11136 fn record(&self, kind: JoinAwaiterWakerEventKind) {
11137 self
11138 .events
11139 .lock()
11140 .unwrap_or_else(std::sync::PoisonError::into_inner)
11141 .push((kind, active_runtime_generation()));
11142 }
11143
11144 fn clear(&self) {
11145 self
11146 .events
11147 .lock()
11148 .unwrap_or_else(std::sync::PoisonError::into_inner)
11149 .clear();
11150 }
11151
11152 fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
11153 self
11154 .events
11155 .lock()
11156 .unwrap_or_else(std::sync::PoisonError::into_inner)
11157 .clone()
11158 }
11159 }
11160
11161 #[cfg(not(target_family = "wasm"))]
11162 struct JoinAwaiterWakerToken {
11163 state: Arc<JoinAwaiterWakerState>,
11164 }
11165
11166 #[cfg(not(target_family = "wasm"))]
11167 unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
11168 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11169 token.state.record(JoinAwaiterWakerEventKind::Clone);
11170 let clone = Box::new(JoinAwaiterWakerToken {
11171 state: Arc::clone(&token.state),
11172 });
11173 std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
11174 }
11175
11176 #[cfg(not(target_family = "wasm"))]
11177 unsafe fn wake_join_awaiter_waker(data: *const ()) {
11178 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11179 token.state.record(JoinAwaiterWakerEventKind::Wake);
11180 }
11181
11182 #[cfg(not(target_family = "wasm"))]
11183 unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
11184 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11185 token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
11186 }
11187
11188 #[cfg(not(target_family = "wasm"))]
11189 unsafe fn drop_join_awaiter_waker(data: *const ()) {
11190 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11191 token.state.record(JoinAwaiterWakerEventKind::Drop);
11192 }
11193
11194 #[cfg(not(target_family = "wasm"))]
11195 static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
11196 clone_join_awaiter_waker,
11197 wake_join_awaiter_waker,
11198 wake_join_awaiter_waker_by_ref,
11199 drop_join_awaiter_waker,
11200 );
11201
11202 #[cfg(not(target_family = "wasm"))]
11203 fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
11204 let token = Box::new(JoinAwaiterWakerToken { state });
11205 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
11206 unsafe { Waker::from_raw(raw) }
11207 }
11208
11209 #[cfg(not(target_family = "wasm"))]
11210 fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
11211 wait_until("the block_on driver to sleep", || {
11212 stop
11213 .parkers
11214 .lock()
11215 .unwrap_or_else(std::sync::PoisonError::into_inner)
11216 .iter()
11217 .filter_map(Weak::upgrade)
11218 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11219 });
11220 }
11221
11222 #[test]
11223 fn unique_ids_fail_before_wraparound() {
11224 let counter = AtomicU64::new(u64::MAX - 1);
11225 assert_eq!(
11226 next_unique_id(&counter, "test id space exhausted"),
11227 u64::MAX - 1
11228 );
11229
11230 let exhausted = catch_unwind(AssertUnwindSafe(|| {
11231 next_unique_id(&counter, "test id space exhausted");
11232 }));
11233 assert!(exhausted.is_err());
11234 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
11235 }
11236
11237 #[test]
11238 fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
11239 let work = GenerationWork::new();
11240 {
11241 let mut state = work.state.lock().unwrap();
11242 state.next_task_id = u64::MAX;
11243 }
11244
11245 let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
11246 assert!(exhausted.is_err());
11247
11248 let state = work.state.lock().unwrap();
11249 assert_eq!(state.next_task_id, u64::MAX);
11250 assert_eq!(state.active, 0);
11251 assert!(state.abort_handles.is_empty());
11252 }
11253
11254 #[test]
11255 fn caught_panic_payload_destructor_runs() {
11256 struct DropSignal(Arc<AtomicBool>);
11257
11258 impl Drop for DropSignal {
11259 fn drop(&mut self) {
11260 self.0.store(true, Ordering::SeqCst);
11261 }
11262 }
11263
11264 let dropped = Arc::new(AtomicBool::new(false));
11265 let payload = DropSignal(Arc::clone(&dropped));
11266
11267 assert!(
11268 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
11269 "the original panic must remain contained"
11270 );
11271 assert!(
11272 dropped.load(Ordering::SeqCst),
11273 "an ordinary caught panic payload must be destroyed"
11274 );
11275 }
11276
11277 #[test]
11278 fn hostile_caught_panic_payload_destructor_remains_contained() {
11279 struct PanicOnDrop(Arc<AtomicBool>);
11280
11281 impl Drop for PanicOnDrop {
11282 fn drop(&mut self) {
11283 self.0.store(true, Ordering::SeqCst);
11284 panic!("caught panic payload destructor escaped");
11285 }
11286 }
11287
11288 let dropped = Arc::new(AtomicBool::new(false));
11289 let payload = PanicOnDrop(Arc::clone(&dropped));
11290 let result = catch_unwind(AssertUnwindSafe(|| {
11291 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
11292 }));
11293
11294 assert!(
11295 result.is_ok(),
11296 "the payload destructor's nested panic must remain contained"
11297 );
11298 assert!(
11299 result.unwrap().is_none(),
11300 "the original panic must remain contained"
11301 );
11302 assert!(
11303 dropped.load(Ordering::SeqCst),
11304 "the hostile payload destructor must be attempted"
11305 );
11306 }
11307
11308 fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11309 CurrentThreadHostDispatchResult::Accepted
11310 }
11311
11312 fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11313 CurrentThreadHostDispatchResult::Unavailable
11314 }
11315
11316 fn count_current_thread_recovery_host_dispatch(
11317 _dispatch: u64,
11318 ) -> CurrentThreadHostDispatchResult {
11319 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
11320 CurrentThreadHostDispatchResult::Accepted
11321 }
11322
11323 fn reset_current_thread_recovery_host_dispatches() {
11324 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
11325 }
11326
11327 fn current_thread_recovery_host_dispatches() -> usize {
11328 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
11329 }
11330
11331 fn accept_only_first_current_thread_host_dispatch(
11332 _dispatch: u64,
11333 ) -> CurrentThreadHostDispatchResult {
11334 if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
11335 let previous = dispatches.get();
11336 dispatches.set(previous + 1);
11337 previous == 0
11338 }) {
11339 CurrentThreadHostDispatchResult::Accepted
11340 } else {
11341 CurrentThreadHostDispatchResult::Unavailable
11342 }
11343 }
11344
11345 fn reset_current_thread_one_shot_host_dispatches() {
11346 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
11347 }
11348
11349 fn current_thread_one_shot_host_dispatches() -> usize {
11350 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
11351 }
11352
11353 fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11354 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11355 CurrentThreadHostDispatchResult::Accepted
11356 }
11357
11358 fn count_current_thread_blocking_budget_host_dispatch(
11359 _dispatch: u64,
11360 ) -> CurrentThreadHostDispatchResult {
11361 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11362 CurrentThreadHostDispatchResult::Accepted
11363 }
11364
11365 fn count_current_thread_admission_host_dispatch(
11366 _dispatch: u64,
11367 ) -> CurrentThreadHostDispatchResult {
11368 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11369 CurrentThreadHostDispatchResult::Accepted
11370 }
11371
11372 fn count_current_thread_interleaved_recovery_host_dispatch(
11373 _dispatch: u64,
11374 ) -> CurrentThreadHostDispatchResult {
11375 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11376 CurrentThreadHostDispatchResult::Accepted
11377 }
11378
11379 fn dispatch_late_registration_current_thread_tasks(
11380 dispatch: u64,
11381 ) -> CurrentThreadHostDispatchResult {
11382 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
11383 }
11384
11385 fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
11386 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
11387 .lock()
11388 .unwrap()
11389 .push(dispatch);
11390 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
11391 }
11392
11393 fn dispatch_authoritative_panic_current_thread_tasks(
11394 dispatch: u64,
11395 ) -> CurrentThreadHostDispatchResult {
11396 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
11397 .lock()
11398 .unwrap()
11399 .push(dispatch);
11400 match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
11401 0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
11402 2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
11403 _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
11404 }
11405 }
11406
11407 fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
11408 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
11409 }
11410
11411 fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
11412 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
11413 .lock()
11414 .unwrap()
11415 .push(dispatch);
11416 assert!(
11417 !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
11418 "injected CurrentThread host dispatch panic"
11419 );
11420 CurrentThreadHostDispatchResult::Accepted
11421 }
11422
11423 fn panic_once_current_thread_armed_republish_host_dispatch(
11424 dispatch: u64,
11425 ) -> CurrentThreadHostDispatchResult {
11426 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
11427 .lock()
11428 .unwrap()
11429 .push(dispatch);
11430 assert!(
11431 !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
11432 "injected armed CurrentThread host dispatch panic"
11433 );
11434 CurrentThreadHostDispatchResult::Accepted
11435 }
11436
11437 fn record_current_thread_republish_rearm_dispatch(
11438 dispatch: u64,
11439 ) -> CurrentThreadHostDispatchResult {
11440 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
11441 .lock()
11442 .unwrap()
11443 .push(dispatch);
11444 CurrentThreadHostDispatchResult::Accepted
11445 }
11446
11447 fn reject_initial_current_thread_publication_once(
11448 dispatch: u64,
11449 ) -> CurrentThreadHostDispatchResult {
11450 let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
11451 .lock()
11452 .unwrap();
11453 dispatches.push(dispatch);
11454 if dispatches.len() == 1 {
11455 CurrentThreadHostDispatchResult::Unavailable
11456 } else {
11457 CurrentThreadHostDispatchResult::Accepted
11458 }
11459 }
11460
11461 fn reject_current_thread_replacement_publication_once(
11462 dispatch: u64,
11463 ) -> CurrentThreadHostDispatchResult {
11464 let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
11465 .lock()
11466 .unwrap();
11467 dispatches.push(dispatch);
11468 if dispatches.len() == 2 {
11469 CurrentThreadHostDispatchResult::Unavailable
11470 } else {
11471 CurrentThreadHostDispatchResult::Accepted
11472 }
11473 }
11474
11475 fn fail_current_thread_replacement_publication_once(
11476 dispatch: u64,
11477 ) -> CurrentThreadHostDispatchResult {
11478 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
11479 .lock()
11480 .unwrap()
11481 .push(dispatch);
11482 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
11483 }
11484
11485 fn count_current_thread_shutdown_republish_host_dispatch(
11486 _dispatch: u64,
11487 ) -> CurrentThreadHostDispatchResult {
11488 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11489 CurrentThreadHostDispatchResult::Accepted
11490 }
11491
11492 fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11493 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11494 CurrentThreadHostDispatchResult::Accepted
11495 }
11496
11497 fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
11498 let pending = executor.dispatch_pending.load(Ordering::Acquire);
11499 if pending != 0 {
11500 return pending;
11501 }
11502 let dispatch = next_current_thread_dispatch_id();
11503 match executor.dispatch_pending.compare_exchange(
11504 0,
11505 dispatch,
11506 Ordering::AcqRel,
11507 Ordering::Acquire,
11508 ) {
11509 Ok(_) => dispatch,
11510 Err(pending) => pending,
11511 }
11512 }
11513
11514 fn current_thread_dispatch_failure(
11515 dispatch: u64,
11516 epoch: u64,
11517 ) -> CurrentThreadTaskDispatchFailure {
11518 CurrentThreadTaskDispatchFailure { dispatch, epoch }
11519 }
11520
11521 fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
11522 let executor = controller
11523 .running_current_thread_executor()
11524 .expect("the test controller must have a running CurrentThread executor");
11525 current_thread_dispatch(&executor)
11526 }
11527
11528 #[test]
11529 fn current_thread_executor_drives_spawned_tasks() {
11530 let metrics = Arc::new(RuntimeMetrics::default());
11531 let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
11532 let scheduler = Arc::clone(&executor);
11533 let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
11534 scheduler.schedule(runnable);
11535 });
11536
11537 executor.schedule(runnable);
11538
11539 let mut output = None;
11540 {
11541 let mut future = std::pin::pin!(async {
11542 output = Some(task.await);
11543 });
11544 executor.block_on(future.as_mut());
11545 }
11546 assert_eq!(output, Some(42));
11547 assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
11548 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
11549 }
11550
11551 #[cfg(not(target_family = "wasm"))]
11552 #[test]
11553 fn current_thread_shutdown_wake_does_not_repoll_user_future() {
11554 use std::sync::mpsc;
11555
11556 let executor = Arc::new(CurrentThreadExecutor::with_detection(
11557 Arc::new(RuntimeMetrics::default()),
11558 false,
11559 None,
11560 ));
11561 let polls = Arc::new(AtomicUsize::new(0));
11562 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11563 let (outcome_tx, outcome_rx) = mpsc::channel();
11564 let driver_executor = Arc::clone(&executor);
11565 let driver_polls = Arc::clone(&polls);
11566 let driver = std::thread::spawn(move || {
11567 let mut future = std::pin::pin!(StopPollProbe {
11568 polls: driver_polls,
11569 first_poll: Some(first_poll_tx)
11570 });
11571 outcome_tx
11572 .send(driver_executor.block_on(future.as_mut()))
11573 .unwrap();
11574 });
11575
11576 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11577 wait_for_generation_parker_to_sleep(&executor.stop);
11578 executor.stop.begin_shutdown();
11579
11580 assert_eq!(
11581 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11582 BlockOnOutcome::Stopped
11583 );
11584 driver.join().unwrap();
11585 assert_eq!(
11586 polls.load(Ordering::SeqCst),
11587 1,
11588 "a shutdown wake must be checked before the next CurrentThread user poll"
11589 );
11590 }
11591
11592 #[cfg(not(target_family = "wasm"))]
11593 #[test]
11594 fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
11595 use std::sync::mpsc;
11596
11597 let metrics = Arc::new(RuntimeMetrics::default());
11598 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11599 Arc::clone(&metrics),
11600 accept_current_thread_host_dispatch,
11601 ));
11602 let polls = Arc::new(AtomicUsize::new(0));
11603 let task_polls = Arc::clone(&polls);
11604 let (runnable, task) = async_task::spawn(
11605 async move {
11606 task_polls.fetch_add(1, Ordering::SeqCst);
11607 },
11608 |_| {},
11609 );
11610 executor.schedule(runnable);
11611 let dispatch = current_thread_dispatch(&executor);
11612
11613 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
11614 let (release_claim_tx, release_claim_rx) = mpsc::channel();
11615 let driver_executor = Arc::clone(&executor);
11616 let driver = std::thread::spawn(move || {
11617 CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
11618 *slot.borrow_mut() = Some(Box::new(move || {
11619 claim_locked_tx.send(()).unwrap();
11620 release_claim_rx.recv().unwrap();
11621 }));
11622 });
11623 driver_executor.drive_host_turn_for_dispatch(dispatch);
11624 });
11625 claim_locked_rx
11626 .recv_timeout(Duration::from_secs(2))
11627 .expect("the admitted host turn must pause before its runnable claim");
11628
11629 executor.stop.publish_stopping();
11630 release_claim_tx.send(()).unwrap();
11631 driver.join().unwrap();
11632
11633 assert_eq!(
11634 polls.load(Ordering::SeqCst),
11635 0,
11636 "stop publication must outrank a later runnable claim by an already-admitted host turn"
11637 );
11638 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11639
11640 executor.begin_shutdown();
11641 assert!(
11642 futures::executor::block_on(task.fallible()).is_none(),
11643 "shutdown must cancel the runnable rejected by the stopped claim"
11644 );
11645 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11646 }
11647
11648 #[cfg(not(target_family = "wasm"))]
11649 #[test]
11650 fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
11651 use std::sync::mpsc;
11652
11653 let metrics = Arc::new(RuntimeMetrics::default());
11654 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11655 Arc::clone(&metrics),
11656 accept_current_thread_host_dispatch,
11657 ));
11658 let polls = Arc::new(AtomicUsize::new(0));
11659 let task_polls = Arc::clone(&polls);
11660 let (runnable, task) = async_task::spawn(
11661 async move {
11662 task_polls.fetch_add(1, Ordering::SeqCst);
11663 },
11664 |_| {},
11665 );
11666 executor.schedule(runnable);
11667
11668 let queue = executor
11669 .queue
11670 .lock()
11671 .unwrap_or_else(std::sync::PoisonError::into_inner);
11672 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
11673 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
11674 let cancelling_executor = Arc::clone(&executor);
11675 let cancellation = std::thread::spawn(move || {
11676 let scheduler = cancelling_executor
11677 .scheduler_idle_lock
11678 .lock()
11679 .unwrap_or_else(std::sync::PoisonError::into_inner);
11680 let cancelling = cancelling_executor
11681 .begin_failed_dispatch_cancellation()
11682 .expect("the test must publish terminal dispatch cancellation");
11683 drop(scheduler);
11684 cancellation_published_tx.send(()).unwrap();
11685 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
11686 cancellation_done_tx.send(()).unwrap();
11687 });
11688 cancellation_published_rx
11689 .recv_timeout(Duration::from_secs(2))
11690 .expect("terminal dispatch cancellation must publish before queue draining");
11691
11692 let claimant_executor = Arc::clone(&executor);
11693 let claimant = std::thread::spawn(move || claimant_executor.drain_one());
11694 assert!(
11695 !claimant.join().unwrap(),
11696 "a runnable claim after terminal cancellation publication must be rejected"
11697 );
11698 assert_eq!(polls.load(Ordering::SeqCst), 0);
11699 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11700 assert!(
11701 matches!(
11702 cancellation_done_rx.try_recv(),
11703 Err(mpsc::TryRecvError::Empty)
11704 ),
11705 "terminal cancellation must still be waiting to drain the queued runnable"
11706 );
11707
11708 drop(queue);
11709 cancellation_done_rx
11710 .recv_timeout(Duration::from_secs(2))
11711 .expect("terminal cancellation must drain the rejected runnable");
11712 cancellation.join().unwrap();
11713 assert!(
11714 futures::executor::block_on(task.fallible()).is_none(),
11715 "terminal cancellation must cancel the runnable it prevented from starting"
11716 );
11717 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11718 }
11719
11720 #[cfg(not(target_family = "wasm"))]
11721 #[test]
11722 fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
11723 use std::sync::mpsc;
11724
11725 let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
11726 let polls = Arc::new(AtomicUsize::new(0));
11727 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11728 let (outcome_tx, outcome_rx) = mpsc::channel();
11729 let driver_executor = Arc::clone(&executor);
11730 let driver_polls = Arc::clone(&polls);
11731 let driver = std::thread::spawn(move || {
11732 let mut future = std::pin::pin!(StopPollProbe {
11733 polls: driver_polls,
11734 first_poll: Some(first_poll_tx)
11735 });
11736 outcome_tx
11737 .send(driver_executor.block_on(future.as_mut()))
11738 .unwrap();
11739 });
11740
11741 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11742 wait_for_generation_parker_to_sleep(&executor.stop);
11743 executor.stop.begin_shutdown();
11744
11745 assert_eq!(
11746 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11747 BlockOnOutcome::Stopped
11748 );
11749 driver.join().unwrap();
11750 assert_eq!(
11751 polls.load(Ordering::SeqCst),
11752 1,
11753 "a shutdown wake must be checked before the next parking block_on user poll"
11754 );
11755 }
11756
11757 #[cfg(not(target_family = "wasm"))]
11758 #[test]
11759 fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
11760 use std::sync::mpsc;
11761
11762 let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
11763 let polls = Arc::new(AtomicUsize::new(0));
11764 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11765 let (outcome_tx, outcome_rx) = mpsc::channel();
11766 let driver_executor = Arc::clone(&executor);
11767 let driver_polls = Arc::clone(&polls);
11768 let driver = std::thread::spawn(move || {
11769 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
11770 let mut future = std::pin::pin!(StopPollProbe {
11771 polls: driver_polls,
11772 first_poll: Some(first_poll_tx)
11773 });
11774 outcome_tx
11775 .send(driver_executor.block_on(future.as_mut()))
11776 .unwrap();
11777 });
11778
11779 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11780 wait_for_generation_parker_to_sleep(&executor.stop);
11781 executor.stop.begin_shutdown();
11782
11783 assert_eq!(
11784 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11785 BlockOnOutcome::Stopped
11786 );
11787 driver.join().unwrap();
11788 assert_eq!(
11789 polls.load(Ordering::SeqCst),
11790 1,
11791 "a shutdown wake must be checked before the next cooperative user poll"
11792 );
11793 }
11794
11795 #[test]
11796 fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
11797 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11798 Arc::new(RuntimeMetrics::default()),
11799 accept_current_thread_host_dispatch,
11800 ));
11801 let pending = u64::MAX - 1;
11802 executor.dispatch_pending.store(pending, Ordering::Release);
11803 let mut allocations = 0;
11804
11805 executor.request_drain_with(|| {
11806 allocations += 1;
11807 u64::MAX
11808 });
11809
11810 assert_eq!(allocations, 0);
11811 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
11812 }
11813
11814 #[cfg(not(target_family = "wasm"))]
11815 #[test]
11816 fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
11817 use std::sync::mpsc;
11818
11819 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11820 Arc::new(RuntimeMetrics::default()),
11821 accept_current_thread_host_dispatch,
11822 ));
11823 let dispatch = current_thread_dispatch(&executor);
11824 let scheduler = executor
11825 .scheduler_idle_lock
11826 .lock()
11827 .unwrap_or_else(std::sync::PoisonError::into_inner);
11828 let (claiming_tx, claiming_rx) = mpsc::channel();
11829 let (claimed_tx, claimed_rx) = mpsc::channel();
11830 let claiming_executor = Arc::clone(&executor);
11831 let claimant = std::thread::spawn(move || {
11832 claiming_tx.send(()).unwrap();
11833 claimed_tx
11834 .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
11835 .unwrap();
11836 });
11837 claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11838
11839 executor.stop.begin_shutdown();
11840 drop(scheduler);
11841
11842 assert!(
11843 !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11844 "an old-generation dispatch must not claim a host turn after stop becomes visible"
11845 );
11846 claimant.join().unwrap();
11847 assert_eq!(
11848 executor.dispatch_pending.load(Ordering::Acquire),
11849 dispatch,
11850 "rejecting the stopped claim must not consume its stale capability"
11851 );
11852 }
11853
11854 #[cfg(not(target_family = "wasm"))]
11855 #[test]
11856 fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
11857 use std::sync::mpsc;
11858
11859 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11860 let metrics = Arc::new(RuntimeMetrics::default());
11861 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11862 Arc::clone(&metrics),
11863 count_current_thread_stopped_host_dispatch,
11864 ));
11865 let scheduler = executor
11866 .scheduler_idle_lock
11867 .lock()
11868 .unwrap_or_else(std::sync::PoisonError::into_inner);
11869 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
11870 let scheduling_executor = Arc::clone(&executor);
11871 let scheduling = std::thread::spawn(move || {
11872 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
11873 *slot.borrow_mut() = Some(Box::new(move || {
11874 publication_blocked_tx.send(()).unwrap();
11875 }));
11876 });
11877 let scheduler = Arc::clone(&scheduling_executor);
11878 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
11879 scheduler.schedule(runnable);
11880 });
11881 task.detach();
11882 scheduling_executor.schedule(runnable);
11883 });
11884
11885 publication_blocked_rx
11886 .recv_timeout(Duration::from_secs(2))
11887 .expect("dispatch publication must block behind the scheduler-idle mutex");
11888 executor.begin_shutdown();
11889 drop(scheduler);
11890 scheduling.join().unwrap();
11891 executor.wait_until_scheduler_idle();
11892
11893 assert_eq!(
11894 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
11895 0,
11896 "a publication that reaches the scheduler-idle barrier after stop must not call the host"
11897 );
11898 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
11899 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11900 }
11901
11902 #[test]
11903 fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
11904 use std::{sync::mpsc, time::Duration};
11905
11906 let metrics = Arc::new(RuntimeMetrics::default());
11907 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11908 Arc::clone(&metrics),
11909 accept_current_thread_host_dispatch,
11910 ));
11911 let scheduler = Arc::clone(&executor);
11912 let completed = Arc::new(AtomicBool::new(false));
11913 let completed_task = Arc::clone(&completed);
11914 let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
11915 let shared = async move {
11916 wake_rx.await.expect("wake sender dropped");
11917 }
11918 .boxed()
11919 .shared();
11920 let (runnable, task) = async_task::spawn(
11921 async move {
11922 shared.await;
11923 completed_task.store(true, Ordering::SeqCst);
11924 },
11925 move |runnable| scheduler.schedule(runnable),
11926 );
11927
11928 executor.schedule(runnable);
11929 assert!(
11930 !completed.load(Ordering::SeqCst),
11931 "accepted host dispatch must defer the first poll"
11932 );
11933 executor.drive_host_turn();
11934
11935 let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
11936 std::thread::spawn(move || {
11937 wake_tx.send(()).expect("shared wake receiver dropped");
11938 send_returned_tx.send(()).unwrap();
11939 });
11940 send_returned_rx
11941 .recv_timeout(Duration::from_secs(1))
11942 .expect("Shared wake re-entered its own waker mutex through inline polling");
11943 assert!(
11944 !completed.load(Ordering::SeqCst),
11945 "the wake must remain queued until a fresh host turn"
11946 );
11947
11948 executor.drive_host_turn();
11949 futures::executor::block_on(task);
11950 assert!(completed.load(Ordering::SeqCst));
11951 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11952 }
11953
11954 #[test]
11955 fn current_thread_hostless_wake_is_enqueue_only() {
11956 use std::{sync::mpsc, time::Duration};
11957
11958 struct LockOnSecondPoll {
11959 gate: Arc<Mutex<()>>,
11960 first: bool,
11961 waker: Option<mpsc::Sender<Waker>>,
11962 completed: Arc<AtomicBool>,
11963 }
11964
11965 impl Future for LockOnSecondPoll {
11966 type Output = ();
11967
11968 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
11969 if self.first {
11970 self.first = false;
11971 self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
11972 return Poll::Pending;
11973 }
11974 drop(self.gate.lock().unwrap());
11975 self.completed.store(true, Ordering::SeqCst);
11976 Poll::Ready(())
11977 }
11978 }
11979
11980 let metrics = Arc::new(RuntimeMetrics::default());
11981 let executor = Arc::new(CurrentThreadExecutor::new(metrics));
11982 let gate = Arc::new(Mutex::new(()));
11983 let completed = Arc::new(AtomicBool::new(false));
11984 let (waker_tx, waker_rx) = mpsc::channel();
11985 let scheduler = Arc::clone(&executor);
11986 let (runnable, task) = async_task::spawn(
11987 LockOnSecondPoll {
11988 gate: Arc::clone(&gate),
11989 first: true,
11990 waker: Some(waker_tx),
11991 completed: Arc::clone(&completed),
11992 },
11993 move |runnable| scheduler.schedule(runnable),
11994 );
11995 executor.schedule(runnable);
11996 executor.drive_host_turn();
11997 let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
11998
11999 let (returned_tx, returned_rx) = mpsc::channel();
12000 let wake_gate = Arc::clone(&gate);
12001 let waking = std::thread::spawn(move || {
12002 let _held = wake_gate.lock().unwrap();
12003 waker.wake();
12004 returned_tx.send(()).unwrap();
12005 });
12006 returned_rx
12007 .recv_timeout(Duration::from_secs(1))
12008 .expect("hostless wake must enqueue without polling under the caller's lock");
12009 waking.join().unwrap();
12010 assert!(!completed.load(Ordering::SeqCst));
12011
12012 executor.drive_host_turn();
12013 futures::executor::block_on(task);
12014 assert!(completed.load(Ordering::SeqCst));
12015 }
12016
12017 #[cfg(not(target_family = "wasm"))]
12018 #[test]
12019 fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
12020 use std::sync::mpsc;
12021
12022 let controller = Arc::new(current_thread_controller(
12023 "current-thread-hostless-late-work",
12024 ));
12025 let backend = controller.backend();
12026 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
12027 panic!("configured CurrentThread backend must create a CurrentThread executor");
12028 };
12029 let executor = Arc::clone(executor);
12030 drop(backend);
12031
12032 let (value_tx, value_rx) = oneshot::channel();
12033 let (done_tx, done_rx) = mpsc::channel();
12034 let block_on_controller = Arc::clone(&controller);
12035 let runner = std::thread::spawn(move || {
12036 let mut output = None;
12037 let outcome = {
12038 let mut future = std::pin::pin!(async {
12039 output = Some(value_rx.await.expect("scheduled sender must deliver"));
12040 });
12041 block_on_controller.block_on(future.as_mut())
12042 };
12043 done_tx.send((outcome, output)).unwrap();
12044 });
12045
12046 wait_until(
12047 "the hostless CurrentThread block_on driver to sleep",
12048 || {
12049 executor
12050 .stop
12051 .parkers
12052 .lock()
12053 .unwrap_or_else(std::sync::PoisonError::into_inner)
12054 .iter()
12055 .filter_map(Weak::upgrade)
12056 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12057 },
12058 );
12059
12060 controller
12061 .try_spawn_detached(async move {
12062 value_tx
12063 .send(37usize)
12064 .expect("block_on receiver must remain alive");
12065 })
12066 .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
12067
12068 let (outcome, output) = done_rx
12069 .recv_timeout(Duration::from_secs(2))
12070 .expect("queue publication must wake the sleeping explicit driver");
12071 assert_eq!(outcome, BlockOnOutcome::Completed);
12072 assert_eq!(output, Some(37));
12073 runner.join().unwrap();
12074 controller.shutdown().unwrap();
12075 }
12076
12077 #[cfg(not(target_family = "wasm"))]
12078 #[test]
12079 fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
12080 use std::sync::mpsc;
12081
12082 struct BlockingPoll {
12083 entered: Option<mpsc::Sender<()>>,
12084 release: mpsc::Receiver<()>,
12085 }
12086
12087 impl Future for BlockingPoll {
12088 type Output = ();
12089
12090 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12091 self.entered.take().unwrap().send(()).unwrap();
12092 self.release.recv().unwrap();
12093 Poll::Ready(())
12094 }
12095 }
12096
12097 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12098 Arc::new(RuntimeMetrics::default()),
12099 reject_current_thread_host_dispatch,
12100 ));
12101 let (value_tx, value_rx) = oneshot::channel();
12102 let (older_done_tx, older_done_rx) = mpsc::channel();
12103 let older_executor = Arc::clone(&executor);
12104 let older = std::thread::spawn(move || {
12105 let mut value = None;
12106 {
12107 let mut future = std::pin::pin!(async {
12108 value = Some(value_rx.await.unwrap());
12109 });
12110 assert_eq!(
12111 older_executor.block_on(future.as_mut()),
12112 BlockOnOutcome::Completed
12113 );
12114 }
12115 older_done_tx.send(value.unwrap()).unwrap();
12116 });
12117 wait_until("the older CurrentThread driver to sleep", || {
12118 executor
12119 .stop
12120 .parkers
12121 .lock()
12122 .unwrap_or_else(std::sync::PoisonError::into_inner)
12123 .iter()
12124 .filter_map(Weak::upgrade)
12125 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12126 });
12127
12128 let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
12129 let (release_newer_tx, release_newer_rx) = mpsc::channel();
12130 let newer_executor = Arc::clone(&executor);
12131 let newer = std::thread::spawn(move || {
12132 let mut future = std::pin::pin!(BlockingPoll {
12133 entered: Some(newer_entered_tx),
12134 release: release_newer_rx
12135 });
12136 assert_eq!(
12137 newer_executor.block_on(future.as_mut()),
12138 BlockOnOutcome::Completed
12139 );
12140 });
12141 newer_entered_rx
12142 .recv_timeout(Duration::from_secs(1))
12143 .expect("the newer driver must be blocked inside its poll");
12144
12145 let scheduler = Arc::clone(&executor);
12146 let (runnable, task) = async_task::spawn(
12147 async move {
12148 value_tx.send(41usize).unwrap();
12149 },
12150 move |runnable| scheduler.schedule(runnable),
12151 );
12152 task.detach();
12153 executor.schedule(runnable);
12154
12155 assert_eq!(
12156 older_done_rx
12157 .recv_timeout(Duration::from_secs(2))
12158 .expect("queue publication must also wake the older sleeping driver"),
12159 41
12160 );
12161 release_newer_tx.send(()).unwrap();
12162 older.join().unwrap();
12163 newer.join().unwrap();
12164 }
12165
12166 #[test]
12167 fn current_thread_shutdown_cancels_host_queued_task() {
12168 use std::sync::mpsc;
12169
12170 struct DropSignal(mpsc::Sender<()>);
12171
12172 impl Drop for DropSignal {
12173 fn drop(&mut self) {
12174 self.0.send(()).unwrap();
12175 }
12176 }
12177
12178 let metrics = Arc::new(RuntimeMetrics::default());
12179 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12180 Arc::clone(&metrics),
12181 accept_current_thread_host_dispatch,
12182 ));
12183 let scheduler = Arc::clone(&executor);
12184 let (dropped_tx, dropped_rx) = mpsc::channel();
12185 let signal = DropSignal(dropped_tx);
12186 let (runnable, task) = async_task::spawn(
12187 async move {
12188 let _signal = signal;
12189 futures::future::pending::<()>().await;
12190 },
12191 move |runnable| scheduler.schedule(runnable),
12192 );
12193
12194 executor.schedule(runnable);
12195 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12196 executor.begin_shutdown();
12197
12198 dropped_rx
12199 .recv_timeout(Duration::from_secs(1))
12200 .expect("shutdown must drop a task queued behind a host turn");
12201 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12202 assert!(
12203 futures::executor::block_on(task.fallible()).is_none(),
12204 "the queued task handle must resolve as cancelled"
12205 );
12206 }
12207
12208 #[test]
12209 fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
12210 reset_current_thread_recovery_host_dispatches();
12211 let metrics = Arc::new(RuntimeMetrics::default());
12212 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12213 Arc::clone(&metrics),
12214 count_current_thread_recovery_host_dispatch,
12215 ));
12216 let scheduler = Arc::clone(&executor);
12217 let completed = Arc::new(AtomicBool::new(false));
12218 let completed_task = Arc::clone(&completed);
12219 let (runnable, task) = async_task::spawn(
12220 async move {
12221 completed_task.store(true, Ordering::SeqCst);
12222 },
12223 move |runnable| scheduler.schedule(runnable),
12224 );
12225
12226 executor.schedule(runnable);
12227 assert_eq!(current_thread_recovery_host_dispatches(), 1);
12228 let lost_dispatch = current_thread_dispatch(&executor);
12229
12230 executor.request_drain_if_queued();
12234 assert_eq!(
12235 current_thread_recovery_host_dispatches(),
12236 2,
12237 "the newly registered host must receive the pending executor dispatch"
12238 );
12239 assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
12240 executor.drive_host_turn_for_dispatch(lost_dispatch);
12241 futures::executor::block_on(task);
12242 assert!(completed.load(Ordering::SeqCst));
12243 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12244
12245 let second_completed = Arc::new(AtomicBool::new(false));
12246 let second_completed_task = Arc::clone(&second_completed);
12247 let second_scheduler = Arc::clone(&executor);
12248 let (second_runnable, second_task) = async_task::spawn(
12249 async move {
12250 second_completed_task.store(true, Ordering::SeqCst);
12251 },
12252 move |runnable| second_scheduler.schedule(runnable),
12253 );
12254 executor.schedule(second_runnable);
12255 assert_eq!(
12256 current_thread_recovery_host_dispatches(),
12257 3,
12258 "work queued after the stale callback must receive a new dispatch"
12259 );
12260 let third_dispatch = current_thread_dispatch(&executor);
12261
12262 executor.drive_host_turn_for_dispatch(lost_dispatch);
12265 assert!(!second_completed.load(Ordering::SeqCst));
12266 executor.drive_host_turn_for_dispatch(third_dispatch);
12267 futures::executor::block_on(second_task);
12268 assert!(second_completed.load(Ordering::SeqCst));
12269
12270 let third_scheduler = Arc::clone(&executor);
12271 let (third_runnable, third_task) =
12272 async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
12273 executor.schedule(third_runnable);
12274 assert_eq!(
12275 current_thread_recovery_host_dispatches(),
12276 4,
12277 "a stale callback must not leave the dispatch latch stuck"
12278 );
12279 executor.drive_host_turn();
12280 futures::executor::block_on(third_task);
12281 }
12282
12283 #[test]
12284 fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
12285 let metrics = Arc::new(RuntimeMetrics::default());
12286 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12287 Arc::clone(&metrics),
12288 accept_current_thread_host_dispatch,
12289 ));
12290 let scheduler = Arc::clone(&executor);
12291 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12292 scheduler.schedule(runnable)
12293 });
12294
12295 executor.schedule(runnable);
12296 let failed_dispatch = current_thread_dispatch(&executor);
12297 executor.cancel_host_dispatch_for_test(failed_dispatch);
12298 let recovery_dispatch = current_thread_dispatch(&executor);
12299 assert_eq!(
12300 executor.recovery_dispatch.load(Ordering::Acquire),
12301 recovery_dispatch
12302 );
12303
12304 executor.request_drain_if_queued();
12305 let new_host_dispatch = current_thread_dispatch(&executor);
12306 assert_eq!(new_host_dispatch, recovery_dispatch);
12307 assert_eq!(
12308 executor.recovery_dispatch.load(Ordering::Acquire),
12309 recovery_dispatch
12310 );
12311 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12312
12313 executor.drive_host_turn_for_dispatch(new_host_dispatch);
12314 assert_eq!(futures::executor::block_on(task), 17);
12315 }
12316
12317 #[cfg(not(target_family = "wasm"))]
12318 #[test]
12319 fn current_thread_host_admission_serializes_dispatch_publication() {
12320 use std::sync::mpsc;
12321
12322 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12323 let metrics = Arc::new(RuntimeMetrics::default());
12324 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12325 Arc::clone(&metrics),
12326 count_current_thread_admission_host_dispatch,
12327 ));
12328 let first_scheduler = Arc::clone(&executor);
12329 let (first_runnable, first_task) =
12330 async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
12331 first_task.detach();
12332 executor.schedule(first_runnable);
12333 let dispatch = current_thread_dispatch(&executor);
12334 assert_eq!(
12335 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
12336 1
12337 );
12338
12339 let (claimed_tx, claimed_rx) = mpsc::channel();
12340 let (release_tx, release_rx) = mpsc::channel();
12341 let driver_executor = Arc::clone(&executor);
12342 let driver = std::thread::spawn(move || {
12343 CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
12344 *slot.borrow_mut() = Some(Box::new(move || {
12345 claimed_tx.send(()).unwrap();
12346 release_rx.recv().unwrap();
12347 }));
12348 });
12349 driver_executor.drive_host_turn_for_dispatch(dispatch);
12350 });
12351 claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12352
12353 let second_ran = Arc::new(AtomicBool::new(false));
12354 let second_ran_task = Arc::clone(&second_ran);
12355 let scheduling_executor = Arc::clone(&executor);
12356 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
12357 let (scheduled_tx, scheduled_rx) = mpsc::channel();
12358 let scheduling = std::thread::spawn(move || {
12359 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
12360 *slot.borrow_mut() = Some(Box::new(move || {
12361 publication_blocked_tx.send(()).unwrap();
12362 }));
12363 });
12364 let scheduler = Arc::clone(&scheduling_executor);
12365 let (runnable, task) = async_task::spawn(
12366 async move {
12367 second_ran_task.store(true, Ordering::SeqCst);
12368 },
12369 move |runnable| scheduler.schedule(runnable),
12370 );
12371 task.detach();
12372 scheduling_executor.schedule(runnable);
12373 scheduled_tx.send(()).unwrap();
12374 });
12375
12376 publication_blocked_rx
12377 .recv_timeout(Duration::from_secs(2))
12378 .unwrap();
12379 assert!(
12380 matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
12381 "dispatch publication must wait until host admission publishes its draining role"
12382 );
12383 assert_eq!(
12384 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
12385 1,
12386 "the admission gap must not publish a duplicate host dispatch"
12387 );
12388
12389 release_tx.send(()).unwrap();
12390 scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12391 scheduling.join().unwrap();
12392 driver.join().unwrap();
12393 if !second_ran.load(Ordering::SeqCst) {
12394 executor.drive_host_turn();
12395 }
12396 assert!(second_ran.load(Ordering::SeqCst));
12397 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12398 }
12399
12400 #[test]
12401 fn current_thread_host_turn_yields_after_runnable_budget() {
12402 struct SelfWakingFuture {
12403 remaining: usize,
12404 }
12405
12406 impl Future for SelfWakingFuture {
12407 type Output = ();
12408
12409 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12410 if self.remaining == 0 {
12411 return Poll::Ready(());
12412 }
12413 self.remaining -= 1;
12414 cx.waker().wake_by_ref();
12415 Poll::Pending
12416 }
12417 }
12418
12419 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12420 let metrics = Arc::new(RuntimeMetrics::default());
12421 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12422 Arc::clone(&metrics),
12423 count_current_thread_budget_host_dispatch,
12424 ));
12425 let scheduler = Arc::clone(&executor);
12426 let (runnable, task) = async_task::spawn(
12427 SelfWakingFuture {
12428 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12429 },
12430 move |runnable| scheduler.schedule(runnable),
12431 );
12432
12433 executor.schedule(runnable);
12434 executor.drive_host_turn();
12435
12436 assert_eq!(
12437 metrics.runnable_polls.load(Ordering::Relaxed),
12438 CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
12439 );
12440 assert_eq!(
12441 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
12442 2,
12443 "remaining hot work must continue through a fresh host turn"
12444 );
12445
12446 executor.drive_host_turn();
12447 futures::executor::block_on(task);
12448 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12449 }
12450
12451 #[cfg(not(target_family = "wasm"))]
12452 #[test]
12453 fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
12454 const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
12455
12456 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12457 let metrics = Arc::new(RuntimeMetrics::default());
12458 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12459 Arc::clone(&metrics),
12460 count_current_thread_blocking_budget_host_dispatch,
12461 ));
12462 let work = GenerationWork::new();
12463 let observed = Arc::new(Mutex::new(Vec::new()));
12464
12465 let task_executor = Arc::clone(&executor);
12466 let wake_executor = Arc::clone(&executor);
12467 let producer_executor = Arc::clone(&executor);
12468 let producer_work = Arc::clone(&work);
12469 let producer_observed = Arc::clone(&observed);
12470 let outer_registration = work
12471 .try_register_work()
12472 .expect("the outer blocking job must be accepted");
12473 let (runnable, task) = async_task::spawn(
12474 async move {
12475 let queued = task_executor
12476 .schedule_blocking(
12477 move || {
12478 std::thread::spawn(move || {
12479 (0..QUEUED_BLOCKING_JOBS)
12480 .map(|index| {
12481 let registration = producer_work
12482 .try_register_work()
12483 .expect("queued blocking work must be accepted");
12484 let observed = Arc::clone(&producer_observed);
12485 producer_executor.schedule_blocking(
12486 move || {
12487 observed
12488 .lock()
12489 .unwrap_or_else(std::sync::PoisonError::into_inner)
12490 .push(index);
12491 },
12492 registration,
12493 )
12494 })
12495 .collect::<Vec<_>>()
12496 })
12497 .join()
12498 .expect("the blocking producer must finish")
12499 },
12500 outer_registration,
12501 )
12502 .await
12503 .expect("the inline blocking job must finish");
12504 for handle in queued {
12505 handle.await.expect("queued blocking work must finish");
12506 }
12507 },
12508 move |runnable| wake_executor.schedule(runnable),
12509 );
12510
12511 executor.schedule(runnable);
12512 executor.drive_host_turn();
12513
12514 let first_turn = observed
12515 .lock()
12516 .unwrap_or_else(std::sync::PoisonError::into_inner)
12517 .clone();
12518 assert!(
12519 !first_turn.is_empty(),
12520 "the first host turn must service queued blocking work"
12521 );
12522 assert!(
12523 first_turn.len() < QUEUED_BLOCKING_JOBS,
12524 "inline lane release must not drain the entire blocking FIFO in one host turn"
12525 );
12526 assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
12527 assert_eq!(
12528 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
12529 2,
12530 "remaining blocking work must continue through a fresh host turn"
12531 );
12532
12533 let mut continuation_turns = 0;
12534 while observed
12535 .lock()
12536 .unwrap_or_else(std::sync::PoisonError::into_inner)
12537 .len()
12538 < QUEUED_BLOCKING_JOBS
12539 {
12540 continuation_turns += 1;
12541 assert!(
12542 continuation_turns <= QUEUED_BLOCKING_JOBS,
12543 "queued blocking work failed to make bounded host-turn progress"
12544 );
12545 executor.drive_host_turn();
12546 }
12547 executor.drive_host_turn();
12548 futures::executor::block_on(task);
12549
12550 assert_eq!(
12551 *observed
12552 .lock()
12553 .unwrap_or_else(std::sync::PoisonError::into_inner),
12554 (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
12555 "blocking jobs must preserve FIFO order across host turns"
12556 );
12557 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12558 }
12559
12560 #[cfg(not(target_family = "wasm"))]
12561 #[test]
12562 fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
12563 use std::sync::mpsc;
12564
12565 struct SelfWakingFuture {
12566 remaining: usize,
12567 }
12568
12569 impl Future for SelfWakingFuture {
12570 type Output = ();
12571
12572 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12573 if self.remaining == 0 {
12574 return Poll::Ready(());
12575 }
12576 self.remaining -= 1;
12577 cx.waker().wake_by_ref();
12578 Poll::Pending
12579 }
12580 }
12581
12582 let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
12583 let old_generation = controller.backend().generation();
12584 controller
12585 .try_spawn_detached(SelfWakingFuture {
12586 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12587 })
12588 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
12589 let dispatch = controller_current_thread_dispatch(&controller);
12590
12591 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12592 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12593 let driver_controller = Arc::clone(&controller);
12594 let driver = std::thread::spawn(move || {
12595 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12596 *slot.borrow_mut() = Some(Box::new(move || {
12597 dispatch_entered_tx.send(()).unwrap();
12598 release_dispatch_rx.recv().unwrap();
12599 }));
12600 });
12601 driver_controller.drive_current_thread_dispatch(dispatch, false);
12602 });
12603 dispatch_entered_rx
12604 .recv_timeout(Duration::from_secs(2))
12605 .expect("the bounded host turn must begin publishing its continuation");
12606
12607 let (shutdown_tx, shutdown_rx) = mpsc::channel();
12608 let shutdown_controller = Arc::clone(&controller);
12609 let shutdown = std::thread::spawn(move || {
12610 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12611 });
12612 wait_until("CurrentThread shutdown to leave Running", || {
12613 let state = controller
12614 .state
12615 .lock()
12616 .unwrap_or_else(std::sync::PoisonError::into_inner);
12617 !matches!(
12618 &state.lifecycle,
12619 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12620 )
12621 });
12622
12623 let (restart_tx, restart_rx) = mpsc::channel();
12624 let restart_controller = Arc::clone(&controller);
12625 let restart = std::thread::spawn(move || {
12626 let result = restart_controller
12627 .start()
12628 .map(|()| restart_controller.backend().generation());
12629 restart_tx.send(result).unwrap();
12630 });
12631
12632 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12633 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12634 let shutdown_overlapped = early_shutdown.is_some();
12635 let restart_overlapped = early_restart.is_some();
12636 release_dispatch_tx.send(()).unwrap();
12637 driver.join().unwrap();
12638
12639 let shutdown_result = early_shutdown.unwrap_or_else(|| {
12640 shutdown_rx
12641 .recv_timeout(Duration::from_secs(2))
12642 .expect("shutdown must finish after continuation publication retires")
12643 });
12644 shutdown_result.unwrap();
12645 let new_generation = early_restart.unwrap_or_else(|| {
12646 restart_rx
12647 .recv_timeout(Duration::from_secs(2))
12648 .expect("restart must follow continuation publication and shutdown")
12649 });
12650 let new_generation = new_generation.unwrap();
12651 shutdown.join().unwrap();
12652 restart.join().unwrap();
12653
12654 assert!(
12655 !shutdown_overlapped,
12656 "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
12657 );
12658 assert!(
12659 !restart_overlapped,
12660 "restart must not overlap an old-generation continuation dispatch"
12661 );
12662 assert_ne!(new_generation, old_generation);
12663 controller.shutdown().unwrap();
12664 }
12665
12666 #[cfg(not(target_family = "wasm"))]
12667 #[test]
12668 fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
12669 use std::sync::mpsc;
12670
12671 let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
12672 let old_generation = controller.backend().generation();
12673 let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
12674 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12675 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12676 let submit_controller = Arc::clone(&controller);
12677 let reentrant_controller = Arc::clone(&controller);
12678 let submitter = std::thread::spawn(move || {
12679 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12680 *slot.borrow_mut() = Some(Box::new(move || {
12681 reentrant_shutdown_tx
12682 .send(reentrant_controller.shutdown())
12683 .unwrap();
12684 dispatch_entered_tx.send(()).unwrap();
12685 release_dispatch_rx.recv().unwrap();
12686 }));
12687 });
12688 submit_controller
12689 .try_spawn_detached(std::future::pending::<()>())
12690 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
12691 });
12692
12693 let reentrant_shutdown = reentrant_shutdown_rx
12694 .recv_timeout(Duration::from_secs(2))
12695 .expect("dispatch publication must not self-deadlock on reentrant shutdown");
12696 dispatch_entered_rx
12697 .recv_timeout(Duration::from_secs(2))
12698 .expect("the initial host dispatch publication must reach the injected pause");
12699
12700 let (shutdown_tx, shutdown_rx) = mpsc::channel();
12701 let shutdown_controller = Arc::clone(&controller);
12702 let shutdown = std::thread::spawn(move || {
12703 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12704 });
12705 wait_until("CurrentThread shutdown to leave Running", || {
12706 let state = controller
12707 .state
12708 .lock()
12709 .unwrap_or_else(std::sync::PoisonError::into_inner);
12710 !matches!(
12711 &state.lifecycle,
12712 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12713 )
12714 });
12715
12716 let (restart_tx, restart_rx) = mpsc::channel();
12717 let restart_controller = Arc::clone(&controller);
12718 let restart = std::thread::spawn(move || {
12719 let result = restart_controller
12720 .start()
12721 .map(|()| restart_controller.backend().generation());
12722 restart_tx.send(result).unwrap();
12723 });
12724
12725 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12726 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12727 let shutdown_overlapped = early_shutdown.is_some();
12728 let restart_overlapped = early_restart.is_some();
12729 release_dispatch_tx.send(()).unwrap();
12730 submitter.join().unwrap();
12731
12732 let shutdown_result = early_shutdown.unwrap_or_else(|| {
12733 shutdown_rx
12734 .recv_timeout(Duration::from_secs(2))
12735 .expect("shutdown must finish after initial dispatch publication retires")
12736 });
12737 shutdown_result.unwrap();
12738 let new_generation = early_restart.unwrap_or_else(|| {
12739 restart_rx
12740 .recv_timeout(Duration::from_secs(2))
12741 .expect("restart must follow initial dispatch publication and shutdown")
12742 });
12743 let new_generation = new_generation.unwrap();
12744 shutdown.join().unwrap();
12745 restart.join().unwrap();
12746
12747 let reentrant_error = reentrant_shutdown
12748 .expect_err("dispatch publication must be generation-scoped scheduler work");
12749 assert!(
12750 reentrant_error
12751 .to_string()
12752 .contains("work running on that runtime")
12753 );
12754 assert!(
12755 !shutdown_overlapped,
12756 "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
12757 );
12758 assert!(
12759 !restart_overlapped,
12760 "restart must not overlap an old-generation host dispatch call"
12761 );
12762 assert_ne!(new_generation, old_generation);
12763 controller.shutdown().unwrap();
12764 }
12765
12766 #[test]
12767 fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
12768 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12769 .lock()
12770 .unwrap()
12771 .clear();
12772 CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12773 let metrics = Arc::new(RuntimeMetrics::default());
12774 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12775 Arc::clone(&metrics),
12776 panic_once_current_thread_host_dispatch,
12777 ));
12778 let scheduler = Arc::clone(&executor);
12779 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12780 scheduler.schedule(runnable)
12781 });
12782
12783 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
12784 assert!(
12785 publication.is_err(),
12786 "the injected host dispatch panic must unwind"
12787 );
12788 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12789 {
12790 let scheduler = executor
12791 .scheduler_idle_lock
12792 .lock()
12793 .unwrap_or_else(std::sync::PoisonError::into_inner);
12794 assert_eq!(scheduler.active_dispatch_calls, 0);
12795 assert!(scheduler.dispatch_publications.is_empty());
12796 }
12797
12798 executor.request_drain_if_queued();
12799
12800 assert_ne!(dispatch, 0);
12801 assert_eq!(
12802 *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12803 .lock()
12804 .unwrap(),
12805 vec![dispatch, dispatch],
12806 "a later host registration must retry the same pending capability"
12807 );
12808 {
12809 let scheduler = executor
12810 .scheduler_idle_lock
12811 .lock()
12812 .unwrap_or_else(std::sync::PoisonError::into_inner);
12813 assert_eq!(scheduler.active_dispatch_calls, 0);
12814 assert!(scheduler.dispatch_publications.is_empty());
12815 }
12816 executor.drive_host_turn_for_dispatch(dispatch);
12817 assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
12818 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12819 }
12820
12821 #[cfg(not(target_family = "wasm"))]
12822 #[test]
12823 fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
12824 use std::sync::mpsc;
12825
12826 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12827 .lock()
12828 .unwrap()
12829 .clear();
12830 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12831 let metrics = Arc::new(RuntimeMetrics::default());
12832 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12833 Arc::clone(&metrics),
12834 panic_once_current_thread_armed_republish_host_dispatch,
12835 ));
12836 let scheduler = Arc::clone(&executor);
12837 let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
12838 scheduler.schedule(runnable)
12839 });
12840
12841 let (called_tx, called_rx) = mpsc::channel();
12842 let (release_tx, release_rx) = mpsc::channel();
12843 let publishing_executor = Arc::clone(&executor);
12844 let publication = std::thread::spawn(move || {
12845 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12846 *slot.borrow_mut() = Some(Box::new(move || {
12847 called_tx.send(()).unwrap();
12848 release_rx.recv().unwrap();
12849 }));
12850 });
12851 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12852 });
12853 called_rx
12854 .recv_timeout(Duration::from_secs(2))
12855 .expect("the first publication must pause before calling the host dispatcher");
12856
12857 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12858 executor.request_drain_if_queued();
12859 let publication_state = {
12860 let scheduler = executor
12861 .scheduler_idle_lock
12862 .lock()
12863 .unwrap_or_else(std::sync::PoisonError::into_inner);
12864 (
12865 scheduler.active_dispatch_calls,
12866 scheduler.dispatch_publications.len(),
12867 scheduler.dispatch_publications[0].dispatch,
12868 scheduler.dispatch_publications[0].republish_requested,
12869 )
12870 };
12871 release_tx.send(()).unwrap();
12872
12873 let publication = publication.join().unwrap();
12874 assert!(
12875 publication.is_err(),
12876 "the original host-dispatch panic must resume"
12877 );
12878 assert_ne!(dispatch, 0);
12879 assert_eq!(publication_state, (1, 1, dispatch, true));
12880 assert_eq!(
12881 *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12882 .lock()
12883 .unwrap(),
12884 vec![dispatch, dispatch],
12885 "the armed request must retry automatically before the original panic resumes"
12886 );
12887 {
12888 let scheduler = executor
12889 .scheduler_idle_lock
12890 .lock()
12891 .unwrap_or_else(std::sync::PoisonError::into_inner);
12892 assert_eq!(scheduler.active_dispatch_calls, 0);
12893 assert!(scheduler.dispatch_publications.is_empty());
12894 }
12895 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
12896 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12897
12898 executor.drive_host_turn_for_dispatch(dispatch);
12899 assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
12900 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12901 }
12902
12903 #[cfg(not(target_family = "wasm"))]
12904 #[test]
12905 fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
12906 use std::sync::mpsc;
12907
12908 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12909 .lock()
12910 .unwrap()
12911 .clear();
12912 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
12913 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12914 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12915 {
12916 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12917 .state
12918 .lock()
12919 .unwrap_or_else(std::sync::PoisonError::into_inner);
12920 assert!(state.entries.is_empty());
12921 assert!(state.dispatches.is_empty());
12922 }
12923 let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12924 .register(Arc::new(FailDuringAuthoritativePanicPublication));
12925 let metrics = Arc::new(RuntimeMetrics::default());
12926 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12927 Arc::clone(&metrics),
12928 dispatch_authoritative_panic_current_thread_tasks,
12929 &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
12930 ));
12931 let scheduler = Arc::clone(&executor);
12932 let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
12933 scheduler.schedule(runnable)
12934 });
12935
12936 let (called_tx, called_rx) = mpsc::channel();
12937 let (release_tx, release_rx) = mpsc::channel();
12938 let publishing_executor = Arc::clone(&executor);
12939 let publication = std::thread::spawn(move || {
12940 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12941 *slot.borrow_mut() = Some(Box::new(move || {
12942 called_tx.send(()).unwrap();
12943 release_rx.recv().unwrap();
12944 }));
12945 });
12946 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12947 });
12948 called_rx
12949 .recv_timeout(Duration::from_secs(2))
12950 .expect("the first publication must pause before raising its panic");
12951
12952 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12953 executor.request_drain_if_queued();
12954 release_tx.send(()).unwrap();
12955
12956 let payload = publication
12957 .join()
12958 .unwrap()
12959 .expect_err("the first host-dispatch panic must resume");
12960 assert!(
12961 payload.is::<FirstHostileCurrentThreadPanic>(),
12962 "the first panic must remain authoritative over nested recovery"
12963 );
12964 assert_eq!(
12965 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12966 1,
12967 "the later hostile payload must be discarded under containment"
12968 );
12969 assert_eq!(
12970 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12971 0,
12972 "the authoritative payload must remain owned by the resumed unwind"
12973 );
12974 discard_panic_payload(payload);
12975 assert_eq!(
12976 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12977 1
12978 );
12979
12980 let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12981 .lock()
12982 .unwrap()
12983 .clone();
12984 assert_eq!(dispatches.len(), 3);
12985 let replacement_dispatch = dispatches[2];
12986 assert_eq!(dispatches[0], failed_dispatch);
12987 assert_eq!(dispatches[1], failed_dispatch);
12988 assert_ne!(replacement_dispatch, failed_dispatch);
12989 assert_eq!(
12990 executor.dispatch_pending.load(Ordering::Acquire),
12991 replacement_dispatch
12992 );
12993 assert_eq!(
12994 executor.recovery_dispatch.load(Ordering::Acquire),
12995 replacement_dispatch
12996 );
12997 {
12998 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12999 .state
13000 .lock()
13001 .unwrap_or_else(std::sync::PoisonError::into_inner);
13002 assert!(
13003 !state.dispatches.contains_key(&failed_dispatch),
13004 "nested recovery unwind must retire the exact failed epoch"
13005 );
13006 assert!(!state.dispatches.contains_key(&replacement_dispatch));
13007 }
13008
13009 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
13010 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13011 let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13012 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13013 executor.request_drain_if_queued();
13014
13015 assert_eq!(
13016 *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13017 .lock()
13018 .unwrap(),
13019 vec![
13020 failed_dispatch,
13021 failed_dispatch,
13022 replacement_dispatch,
13023 replacement_dispatch
13024 ],
13025 "retry must retain the replacement reserved before the nested panic"
13026 );
13027 let delivery = healthy.deliveries()[0];
13028 assert_eq!(
13029 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
13030 Some(replacement_dispatch)
13031 );
13032 let mut host_turn = executor
13033 .try_admit_host_turn(replacement_dispatch)
13034 .expect("the retried replacement delivery must be admitted");
13035 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13036 host_turn.drive();
13037 assert!(
13038 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13039 .finish_delivery(delivery, false)
13040 .failed_dispatches
13041 .is_empty()
13042 );
13043 assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
13044 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13045
13046 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
13047 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13048 .state
13049 .lock()
13050 .unwrap_or_else(std::sync::PoisonError::into_inner);
13051 assert!(state.entries.is_empty());
13052 assert!(state.dispatches.is_empty());
13053 }
13054
13055 #[cfg(not(target_family = "wasm"))]
13056 #[test]
13057 fn current_thread_shutdown_suppresses_queued_republish() {
13058 use std::sync::mpsc;
13059
13060 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13061 let metrics = Arc::new(RuntimeMetrics::default());
13062 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13063 Arc::clone(&metrics),
13064 count_current_thread_shutdown_republish_host_dispatch,
13065 ));
13066 let scheduler = Arc::clone(&executor);
13067 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13068 scheduler.schedule(runnable)
13069 });
13070
13071 let (returned_tx, returned_rx) = mpsc::channel();
13072 let (release_tx, release_rx) = mpsc::channel();
13073 let publishing_executor = Arc::clone(&executor);
13074 let publication = std::thread::spawn(move || {
13075 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13076 *slot.borrow_mut() = Some(Box::new(move || {
13077 returned_tx.send(()).unwrap();
13078 release_rx.recv().unwrap();
13079 }));
13080 });
13081 publishing_executor.schedule(runnable);
13082 });
13083 returned_rx
13084 .recv_timeout(Duration::from_secs(2))
13085 .expect("the initial publication must reach its completion barrier");
13086
13087 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13088 executor.request_drain_if_queued();
13089 let publication_state = {
13090 let scheduler = executor
13091 .scheduler_idle_lock
13092 .lock()
13093 .unwrap_or_else(std::sync::PoisonError::into_inner);
13094 (
13095 scheduler.active_dispatch_calls,
13096 scheduler.dispatch_publications.len(),
13097 scheduler.dispatch_publications[0].dispatch,
13098 scheduler.dispatch_publications[0].republish_requested,
13099 )
13100 };
13101 executor.begin_shutdown();
13102 release_tx.send(()).unwrap();
13103 publication.join().unwrap();
13104 executor.wait_until_scheduler_idle();
13105
13106 assert_ne!(dispatch, 0);
13107 assert_eq!(publication_state, (1, 1, dispatch, true));
13108 assert_eq!(
13109 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
13110 1
13111 );
13112 {
13113 let scheduler = executor
13114 .scheduler_idle_lock
13115 .lock()
13116 .unwrap_or_else(std::sync::PoisonError::into_inner);
13117 assert_eq!(scheduler.active_dispatch_calls, 0);
13118 assert!(scheduler.dispatch_publications.is_empty());
13119 }
13120 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13121 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13122 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13123 assert_eq!(futures::executor::block_on(task.fallible()), None);
13124 }
13125
13126 #[cfg(not(target_family = "wasm"))]
13127 #[test]
13128 fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
13129 use std::sync::mpsc;
13130
13131 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13132 .lock()
13133 .unwrap()
13134 .clear();
13135 let metrics = Arc::new(RuntimeMetrics::default());
13136 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13137 Arc::clone(&metrics),
13138 record_current_thread_republish_rearm_dispatch,
13139 ));
13140 let scheduler = Arc::clone(&executor);
13141 let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
13142 scheduler.schedule(runnable)
13143 });
13144
13145 let (first_returned_tx, first_returned_rx) = mpsc::channel();
13146 let (release_first_tx, release_first_rx) = mpsc::channel();
13147 let (second_returned_tx, second_returned_rx) = mpsc::channel();
13148 let (release_second_tx, release_second_rx) = mpsc::channel();
13149 let publishing_executor = Arc::clone(&executor);
13150 let publication = std::thread::spawn(move || {
13151 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13152 *slot.borrow_mut() = Some(Box::new(move || {
13153 first_returned_tx.send(()).unwrap();
13154 release_first_rx.recv().unwrap();
13155 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13156 *slot.borrow_mut() = Some(Box::new(move || {
13157 second_returned_tx.send(()).unwrap();
13158 release_second_rx.recv().unwrap();
13159 }));
13160 });
13161 }));
13162 });
13163 publishing_executor.schedule(runnable);
13164 });
13165 first_returned_rx
13166 .recv_timeout(Duration::from_secs(2))
13167 .expect("the first publication must reach its completion barrier");
13168
13169 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13170 executor.request_drain_if_queued();
13171 executor.request_drain_if_queued();
13172 let first_publication_state = {
13173 let scheduler = executor
13174 .scheduler_idle_lock
13175 .lock()
13176 .unwrap_or_else(std::sync::PoisonError::into_inner);
13177 (
13178 scheduler.active_dispatch_calls,
13179 scheduler.dispatch_publications.len(),
13180 scheduler.dispatch_publications[0].dispatch,
13181 scheduler.dispatch_publications[0].republish_requested,
13182 )
13183 };
13184 release_first_tx.send(()).unwrap();
13185 second_returned_rx
13186 .recv_timeout(Duration::from_secs(2))
13187 .expect("the second publication must reach its completion barrier");
13188
13189 executor.request_drain_if_queued();
13190 executor.request_drain_if_queued();
13191 let second_publication_state = {
13192 let scheduler = executor
13193 .scheduler_idle_lock
13194 .lock()
13195 .unwrap_or_else(std::sync::PoisonError::into_inner);
13196 (
13197 scheduler.active_dispatch_calls,
13198 scheduler.dispatch_publications.len(),
13199 scheduler.dispatch_publications[0].dispatch,
13200 scheduler.dispatch_publications[0].republish_requested,
13201 )
13202 };
13203 release_second_tx.send(()).unwrap();
13204 publication.join().unwrap();
13205
13206 assert_ne!(dispatch, 0);
13207 assert_eq!(first_publication_state, (1, 1, dispatch, true));
13208 assert_eq!(second_publication_state, (1, 1, dispatch, true));
13209 assert_eq!(
13210 *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
13211 vec![dispatch, dispatch, dispatch],
13212 "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
13213 );
13214 {
13215 let scheduler = executor
13216 .scheduler_idle_lock
13217 .lock()
13218 .unwrap_or_else(std::sync::PoisonError::into_inner);
13219 assert_eq!(scheduler.active_dispatch_calls, 0);
13220 assert!(scheduler.dispatch_publications.is_empty());
13221 }
13222 executor.drive_host_turn_for_dispatch(dispatch);
13223 assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
13224 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13225 }
13226
13227 #[cfg(not(target_family = "wasm"))]
13228 #[test]
13229 fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
13230 use std::sync::mpsc;
13231
13232 CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13233 .lock()
13234 .unwrap()
13235 .clear();
13236 let metrics = Arc::new(RuntimeMetrics::default());
13237 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13238 Arc::clone(&metrics),
13239 reject_initial_current_thread_publication_once,
13240 ));
13241 let scheduler = Arc::clone(&executor);
13242 let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
13243 scheduler.schedule(runnable)
13244 });
13245
13246 let (returned_tx, returned_rx) = mpsc::channel();
13247 let (release_tx, release_rx) = mpsc::channel();
13248 let publishing_executor = Arc::clone(&executor);
13249 let publication = std::thread::spawn(move || {
13250 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13251 *slot.borrow_mut() = Some(Box::new(move || {
13252 returned_tx.send(()).unwrap();
13253 release_rx.recv().unwrap();
13254 }));
13255 });
13256 publishing_executor.schedule(runnable);
13257 });
13258 returned_rx
13259 .recv_timeout(Duration::from_secs(2))
13260 .expect("the unavailable initial publication must reach its completion barrier");
13261
13262 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13263 executor.request_drain_if_queued();
13264 let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13265 .lock()
13266 .unwrap()
13267 .len();
13268
13269 release_tx.send(()).unwrap();
13270 publication.join().unwrap();
13271
13272 let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13273 .lock()
13274 .unwrap()
13275 .clone();
13276 assert_ne!(dispatch, 0);
13277 assert_eq!(
13278 attempts_while_unavailable_is_paused, 1,
13279 "a racing host request must join the in-flight publication instead of retiring it independently"
13280 );
13281 assert_eq!(
13282 dispatches,
13283 vec![dispatch, dispatch],
13284 "the publication owner must retry the same internal capability after the new host joins"
13285 );
13286 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
13287 executor.drive_host_turn_for_dispatch(dispatch);
13288 assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
13289 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13290 }
13291
13292 #[cfg(not(target_family = "wasm"))]
13293 #[test]
13294 fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
13295 use std::sync::mpsc;
13296
13297 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13298 .lock()
13299 .unwrap()
13300 .clear();
13301 let metrics = Arc::new(RuntimeMetrics::default());
13302 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13303 Arc::clone(&metrics),
13304 reject_current_thread_replacement_publication_once,
13305 ));
13306 let scheduler = Arc::clone(&executor);
13307 let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
13308 scheduler.schedule(runnable)
13309 });
13310 executor.schedule(runnable);
13311 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13312 assert_ne!(failed_dispatch, 0);
13313
13314 let (returned_tx, returned_rx) = mpsc::channel();
13315 let (release_tx, release_rx) = mpsc::channel();
13316 let cancelling_executor = Arc::clone(&executor);
13317 let cancellation = std::thread::spawn(move || {
13318 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13319 *slot.borrow_mut() = Some(Box::new(move || {
13320 returned_tx.send(()).unwrap();
13321 release_rx.recv().unwrap();
13322 }));
13323 });
13324 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
13325 });
13326 returned_rx
13327 .recv_timeout(Duration::from_secs(2))
13328 .expect("the unavailable replacement publication must reach its completion barrier");
13329
13330 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13331 executor.request_drain_if_queued();
13332 let attempts_while_unavailable_is_paused =
13333 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13334 .lock()
13335 .unwrap()
13336 .len();
13337
13338 release_tx.send(()).unwrap();
13339 cancellation.join().unwrap();
13340
13341 let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13342 .lock()
13343 .unwrap()
13344 .clone();
13345 assert_ne!(replacement_dispatch, 0);
13346 assert_ne!(replacement_dispatch, failed_dispatch);
13347 assert_eq!(
13348 attempts_while_unavailable_is_paused, 2,
13349 "a racing host request must join the in-flight replacement publication"
13350 );
13351 assert_eq!(
13352 dispatches,
13353 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13354 "republication must reuse the sole replacement capability instead of allocating another"
13355 );
13356 assert_eq!(
13357 executor.dispatch_pending.load(Ordering::Acquire),
13358 replacement_dispatch
13359 );
13360 assert_eq!(
13361 executor.recovery_dispatch.load(Ordering::Acquire),
13362 replacement_dispatch
13363 );
13364 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13365
13366 executor.drive_host_turn_for_dispatch(replacement_dispatch);
13367 assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
13368 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13369 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13370 }
13371
13372 #[cfg(not(target_family = "wasm"))]
13373 #[test]
13374 fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
13375 use std::sync::mpsc;
13376
13377 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13378 .lock()
13379 .unwrap()
13380 .clear();
13381 {
13382 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13383 .state
13384 .lock()
13385 .unwrap_or_else(std::sync::PoisonError::into_inner);
13386 assert!(state.entries.is_empty());
13387 assert!(state.dispatches.is_empty());
13388 }
13389 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
13390 let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13391 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
13392 let metrics = Arc::new(RuntimeMetrics::default());
13393 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13394 Arc::clone(&metrics),
13395 fail_current_thread_replacement_publication_once,
13396 &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
13397 ));
13398 let scheduler = Arc::clone(&executor);
13399 let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
13400 scheduler.schedule(runnable)
13401 });
13402 executor.schedule(runnable);
13403 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13404 assert_ne!(failed_dispatch, 0);
13405 let initial_delivery = initial.deliveries()[0];
13406 let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13407 .finish_delivery(initial_delivery, true);
13408 let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
13409 panic!("the initial accepted delivery must report one exact failure");
13410 };
13411 assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
13412 let failed_dispatch_failure = *failed_dispatch_failure;
13413 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
13414 let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13415 .register(Arc::new(FailDuringReplacementRepublishPublication));
13416
13417 let (returned_tx, returned_rx) = mpsc::channel();
13418 let (release_tx, release_rx) = mpsc::channel();
13419 let cancelling_executor = Arc::clone(&executor);
13420 let cancellation = std::thread::spawn(move || {
13421 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13422 *slot.borrow_mut() = Some(Box::new(move || {
13423 returned_tx.send(()).unwrap();
13424 release_rx.recv().unwrap();
13425 }));
13426 });
13427 cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
13428 });
13429 returned_rx
13430 .recv_timeout(Duration::from_secs(2))
13431 .expect("the failed replacement publication must reach its completion barrier");
13432
13433 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13434 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13435 let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13436 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13437 executor.request_drain_if_queued();
13438 let publication_state = {
13439 let scheduler = executor
13440 .scheduler_idle_lock
13441 .lock()
13442 .unwrap_or_else(std::sync::PoisonError::into_inner);
13443 (
13444 scheduler.active_dispatch_calls,
13445 scheduler.dispatch_publications.len(),
13446 scheduler.dispatch_publications[0].dispatch,
13447 scheduler.dispatch_publications[0].republish_requested,
13448 )
13449 };
13450 release_tx.send(()).unwrap();
13451 cancellation.join().unwrap();
13452
13453 assert_ne!(replacement_dispatch, 0);
13454 assert_ne!(replacement_dispatch, failed_dispatch);
13455 assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
13456 assert_eq!(
13457 *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13458 .lock()
13459 .unwrap(),
13460 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13461 "the newly armed host request must receive the exact replacement before cancellation"
13462 );
13463 assert_eq!(
13464 executor.dispatch_pending.load(Ordering::Acquire),
13465 replacement_dispatch
13466 );
13467 assert_eq!(
13468 executor.recovery_dispatch.load(Ordering::Acquire),
13469 replacement_dispatch
13470 );
13471 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13472
13473 let delivery = healthy.deliveries()[0];
13474 assert_eq!(delivery.dispatch, replacement_dispatch);
13475 assert_eq!(
13476 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13477 .claim_delivery(delivery.capability()),
13478 Some(replacement_dispatch)
13479 );
13480 let mut host_turn = executor
13481 .try_admit_host_turn(replacement_dispatch)
13482 .expect("the rearmed replacement delivery must be admitted");
13483 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13484 host_turn.drive();
13485 assert!(
13486 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13487 .finish_delivery(delivery, false)
13488 .failed_dispatches
13489 .is_empty()
13490 );
13491 assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
13492 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13493 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13494
13495 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
13496 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
13497 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13498 .state
13499 .lock()
13500 .unwrap_or_else(std::sync::PoisonError::into_inner);
13501 assert!(state.entries.is_empty());
13502 assert!(state.dispatches.is_empty());
13503 }
13504
13505 #[cfg(not(target_family = "wasm"))]
13506 #[test]
13507 fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
13508 use std::sync::mpsc;
13509
13510 {
13511 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13512 .state
13513 .lock()
13514 .unwrap_or_else(std::sync::PoisonError::into_inner);
13515 assert!(state.entries.is_empty());
13516 assert!(state.dispatches.is_empty());
13517 }
13518 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
13519 let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13520 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
13521 let metrics = Arc::new(RuntimeMetrics::default());
13522 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13523 Arc::clone(&metrics),
13524 dispatch_stale_failure_current_thread_tasks,
13525 &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
13526 ));
13527 let scheduler = Arc::clone(&executor);
13528 let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
13529 scheduler.schedule(runnable)
13530 });
13531 executor.schedule(runnable);
13532
13533 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13534 let initial_delivery = initial.deliveries()[0];
13535 let delayed_completion =
13536 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
13537 let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
13538 panic!("the initial accepted delivery must report one exact failure");
13539 };
13540 assert_eq!(
13541 *delayed_failure,
13542 current_thread_dispatch_failure(dispatch, 0)
13543 );
13544 let delayed_failure = *delayed_failure;
13545
13546 let (returned_tx, returned_rx) = mpsc::channel();
13547 let (release_tx, release_rx) = mpsc::channel();
13548 let publishing_executor = Arc::clone(&executor);
13549 let publication = std::thread::spawn(move || {
13550 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13551 *slot.borrow_mut() = Some(Box::new(move || {
13552 returned_tx.send(()).unwrap();
13553 release_rx.recv().unwrap();
13554 }));
13555 });
13556 publishing_executor.request_drain_if_queued();
13557 });
13558 returned_rx
13559 .recv_timeout(Duration::from_secs(2))
13560 .expect("the old failed epoch must pause before executor recovery");
13561
13562 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13563 let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13564 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13565 executor.request_drain_if_queued();
13566 release_tx.send(()).unwrap();
13567 publication.join().unwrap();
13568
13569 let healthy_delivery = healthy.deliveries()[0];
13570 let delayed_initial_delivery = initial.deliveries()[1];
13571 {
13572 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13573 .state
13574 .lock()
13575 .unwrap_or_else(std::sync::PoisonError::into_inner);
13576 let dispatch_state = state
13577 .dispatches
13578 .get(&dispatch)
13579 .expect("the rearmed dispatch must remain registered");
13580 assert_eq!(dispatch_state.epoch, 1);
13581 assert!(!dispatch_state.is_cancelling());
13582 }
13583
13584 executor.cancel_host_dispatch(delayed_failure);
13585
13586 assert_eq!(
13587 executor.dispatch_pending.load(Ordering::Acquire),
13588 dispatch,
13589 "the delayed old-epoch failure must not consume the rearmed executor capability"
13590 );
13591 assert_eq!(
13592 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13593 .state
13594 .lock()
13595 .unwrap_or_else(std::sync::PoisonError::into_inner)
13596 .dispatches
13597 .get(&dispatch)
13598 .map(|state| (state.epoch, state.phase)),
13599 Some((1, CurrentThreadDispatchPhase::Pending)),
13600 "the delayed old-epoch cancellation must not remove the accepted rearm"
13601 );
13602
13603 assert_eq!(
13604 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
13605 Some(dispatch)
13606 );
13607 let mut host_turn = executor
13608 .try_admit_host_turn(dispatch)
13609 .expect("the current-epoch delivery must be admitted");
13610 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
13611 host_turn.drive();
13612 assert!(
13613 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13614 .finish_delivery(healthy_delivery, false)
13615 .failed_dispatches
13616 .is_empty()
13617 );
13618 assert!(
13619 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13620 .finish_delivery(delayed_initial_delivery, false)
13621 .failed_dispatches
13622 .is_empty()
13623 );
13624 assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
13625 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13626
13627 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
13628 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
13629 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13630 .state
13631 .lock()
13632 .unwrap_or_else(std::sync::PoisonError::into_inner);
13633 assert!(state.entries.is_empty());
13634 assert!(state.dispatches.is_empty());
13635 }
13636
13637 #[cfg(not(target_family = "wasm"))]
13638 #[test]
13639 fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
13640 use std::sync::mpsc;
13641
13642 {
13643 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13644 .state
13645 .lock()
13646 .unwrap_or_else(std::sync::PoisonError::into_inner);
13647 assert!(state.entries.is_empty());
13648 assert!(state.dispatches.is_empty());
13649 }
13650 let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13651 .register(Arc::new(FailDuringLateRegistrationPublication));
13652
13653 let metrics = Arc::new(RuntimeMetrics::default());
13654 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13655 Arc::clone(&metrics),
13656 dispatch_late_registration_current_thread_tasks,
13657 &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
13658 ));
13659 let scheduler = Arc::clone(&executor);
13660 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13661 scheduler.schedule(runnable)
13662 });
13663 metrics.runnable_scheduled();
13664 executor
13665 .queue
13666 .lock()
13667 .unwrap_or_else(std::sync::PoisonError::into_inner)
13668 .runnables
13669 .push_back(runnable);
13670 let replacement_dispatch = next_current_thread_dispatch_id();
13671 executor
13672 .recovery_dispatch
13673 .store(replacement_dispatch, Ordering::Release);
13674 executor
13675 .dispatch_pending
13676 .store(replacement_dispatch, Ordering::Release);
13677
13678 let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
13679 let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
13680 let publishing_executor = Arc::clone(&executor);
13681 let publication = std::thread::spawn(move || {
13682 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13683 *slot.borrow_mut() = Some(Box::new(move || {
13684 owner_retired_tx.send(()).unwrap();
13685 request_blocked_rx
13686 .recv_timeout(Duration::from_secs(2))
13687 .expect("the binding-like drain request must wait for the failure transition");
13688 }));
13689 });
13690 publishing_executor.request_drain_if_queued();
13691 });
13692 owner_retired_rx
13693 .recv_timeout(Duration::from_secs(2))
13694 .expect("the failed replacement publication must retire its owner");
13695
13696 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13699 let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13700 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13701 let requesting_executor = Arc::clone(&executor);
13702 let request = std::thread::spawn(move || {
13703 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13704 *slot.borrow_mut() = Some(Box::new(move || {
13705 request_blocked_tx.send(()).unwrap();
13706 }));
13707 });
13708 requesting_executor.request_drain_if_queued();
13709 });
13710
13711 publication.join().unwrap();
13712 request.join().unwrap();
13713
13714 assert!(
13715 healthy.deliveries().is_empty(),
13716 "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
13717 );
13718 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13719 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13720 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13721 assert_eq!(futures::executor::block_on(task.fallible()), None);
13722 {
13723 let scheduler = executor
13724 .scheduler_idle_lock
13725 .lock()
13726 .unwrap_or_else(std::sync::PoisonError::into_inner);
13727 assert_eq!(scheduler.active_dispatch_calls, 0);
13728 assert!(scheduler.dispatch_publications.is_empty());
13729 }
13730
13731 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
13732
13733 let scheduler = Arc::clone(&executor);
13734 let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
13735 scheduler.schedule(runnable)
13736 });
13737 executor.schedule(later_runnable);
13738 let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13739 let later_delivery = healthy.deliveries()[0];
13740 assert_eq!(
13741 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
13742 Some(later_dispatch)
13743 );
13744 let mut host_turn = executor
13745 .try_admit_host_turn(later_dispatch)
13746 .expect("the later exact delivery must be admitted");
13747 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
13748 host_turn.drive();
13749 assert!(
13750 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13751 .finish_delivery(later_delivery, false)
13752 .failed_dispatches
13753 .is_empty()
13754 );
13755 assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
13756 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13757
13758 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
13759 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13760 .state
13761 .lock()
13762 .unwrap_or_else(std::sync::PoisonError::into_inner);
13763 assert!(state.entries.is_empty());
13764 assert!(state.dispatches.is_empty());
13765 }
13766
13767 #[cfg(not(target_family = "wasm"))]
13768 #[test]
13769 fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
13770 {
13771 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13772 .lock()
13773 .unwrap()
13774 .clear();
13775 {
13776 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13777 .state
13778 .lock()
13779 .unwrap_or_else(std::sync::PoisonError::into_inner);
13780 assert!(state.entries.is_empty());
13781 assert!(state.dispatches.is_empty());
13782 }
13783 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13784 .next_delivery
13785 .store(0, Ordering::Relaxed);
13786 let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13787 .register(Arc::new(FailDuringNestedUnwindPublication));
13788
13789 let metrics = Arc::new(RuntimeMetrics::default());
13790 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13791 Arc::clone(&metrics),
13792 dispatch_nested_unwind_current_thread_tasks,
13793 &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
13794 ));
13795 let scheduler = Arc::clone(&executor);
13796 let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
13797 scheduler.schedule(runnable)
13798 });
13799
13800 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13801 *slot.borrow_mut() = Some(Box::new(|| {
13802 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13803 .next_delivery
13804 .store(u64::MAX, Ordering::Relaxed);
13805 }));
13806 });
13807 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
13808 assert!(
13809 publication.is_err(),
13810 "replacement delivery-id exhaustion must unwind through the outer failed publication"
13811 );
13812
13813 let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13814 .lock()
13815 .unwrap()
13816 .clone();
13817 assert_eq!(dispatches.len(), 2);
13818 let failed_dispatch = dispatches[0];
13819 let replacement_dispatch = dispatches[1];
13820 assert_ne!(failed_dispatch, replacement_dispatch);
13821 assert_eq!(
13822 executor.dispatch_pending.load(Ordering::Acquire),
13823 replacement_dispatch
13824 );
13825 assert_eq!(
13826 executor.recovery_dispatch.load(Ordering::Acquire),
13827 replacement_dispatch
13828 );
13829 {
13830 let scheduler = executor
13831 .scheduler_idle_lock
13832 .lock()
13833 .unwrap_or_else(std::sync::PoisonError::into_inner);
13834 assert_eq!(scheduler.active_dispatch_calls, 0);
13835 assert!(scheduler.dispatch_publications.is_empty());
13836 }
13837 {
13838 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13839 .state
13840 .lock()
13841 .unwrap_or_else(std::sync::PoisonError::into_inner);
13842 assert!(
13843 !state.dispatches.contains_key(&failed_dispatch),
13844 "outer unwind cleanup must retire the exact old cancelling dispatch"
13845 );
13846 assert!(
13847 !state.dispatches.contains_key(&replacement_dispatch),
13848 "the replacement publication guard must release its aborted registry state"
13849 );
13850 }
13851
13852 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13853 .next_delivery
13854 .store(1, Ordering::Relaxed);
13855 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
13856 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13857 let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13858 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13859
13860 executor.request_drain_if_queued();
13861
13862 assert_eq!(
13863 *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
13864 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13865 "retry must reuse the replacement reserved before the nested unwind"
13866 );
13867 let delivery = healthy.deliveries()[0];
13868 assert_eq!(delivery.dispatch, replacement_dispatch);
13869 assert_eq!(
13870 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
13871 Some(replacement_dispatch)
13872 );
13873 let mut host_turn = executor
13874 .try_admit_host_turn(replacement_dispatch)
13875 .expect("the retried replacement delivery must be admitted");
13876 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13877 host_turn.drive();
13878 assert!(
13879 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13880 .finish_delivery(delivery, false)
13881 .failed_dispatches
13882 .is_empty()
13883 );
13884 assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
13885 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13886
13887 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
13888 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13889 .state
13890 .lock()
13891 .unwrap_or_else(std::sync::PoisonError::into_inner);
13892 assert!(state.entries.is_empty());
13893 assert!(state.dispatches.is_empty());
13894 }
13895
13896 #[test]
13897 fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
13898 reset_current_thread_recovery_host_dispatches();
13899 let metrics = Arc::new(RuntimeMetrics::default());
13900 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13901 Arc::clone(&metrics),
13902 count_current_thread_recovery_host_dispatch,
13903 ));
13904
13905 let scheduler = Arc::clone(&executor);
13906 let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
13907 scheduler.schedule(runnable)
13908 });
13909 executor.schedule(runnable);
13910 assert_eq!(current_thread_recovery_host_dispatches(), 1);
13911 let failed_dispatch = current_thread_dispatch(&executor);
13912
13913 executor.cancel_host_dispatch_for_test(failed_dispatch);
13916 assert_eq!(
13917 current_thread_recovery_host_dispatches(),
13918 2,
13919 "cancellation must request one replacement without another submission"
13920 );
13921 let replacement_dispatch = current_thread_dispatch(&executor);
13922 assert_ne!(replacement_dispatch, failed_dispatch);
13923 assert_eq!(
13924 executor.recovery_dispatch.load(Ordering::Acquire),
13925 replacement_dispatch
13926 );
13927
13928 executor.drive_host_turn_for_dispatch(replacement_dispatch);
13929 assert_eq!(futures::executor::block_on(task), 1);
13930 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13931 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13932 }
13933
13934 #[test]
13935 fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
13936 reset_current_thread_recovery_host_dispatches();
13937 let metrics = Arc::new(RuntimeMetrics::default());
13938 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13939 Arc::clone(&metrics),
13940 count_current_thread_recovery_host_dispatch,
13941 ));
13942 let backend =
13943 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13944
13945 let registration = backend
13946 .work
13947 .try_register_async()
13948 .expect("the backend must accept work");
13949 let task = spawn_registered(
13950 &backend,
13951 Arc::clone(&metrics),
13952 std::future::pending::<()>(),
13953 registration,
13954 );
13955 let failed_dispatch = current_thread_dispatch(&executor);
13956 executor.cancel_host_dispatch_for_test(failed_dispatch);
13957 let replacement_dispatch = current_thread_dispatch(&executor);
13958
13959 executor.cancel_host_dispatch_for_test(replacement_dispatch);
13960
13961 assert_eq!(
13962 current_thread_recovery_host_dispatches(),
13963 2,
13964 "a second scheduler failure must not create an unbounded retry loop"
13965 );
13966 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13967 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13968 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13969 assert_eq!(
13970 futures::executor::block_on(task).unwrap_err().to_string(),
13971 "async runtime stopped before the task completed",
13972 "terminal host failure must settle the original operation as cancelled"
13973 );
13974 assert_eq!(backend.work.state.lock().unwrap().active, 0);
13975
13976 let later_registration = backend
13977 .work
13978 .try_register_async()
13979 .expect("terminal cancellation must reopen acceptance");
13980 let later_task = spawn_registered(
13981 &backend,
13982 Arc::clone(&metrics),
13983 async { 2usize },
13984 later_registration,
13985 );
13986 assert_eq!(
13987 current_thread_recovery_host_dispatches(),
13988 3,
13989 "terminal cancellation must not permanently close the executor"
13990 );
13991 executor.drive_host_turn();
13992 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
13993 }
13994
13995 #[cfg(not(target_family = "wasm"))]
13996 #[test]
13997 fn current_thread_recovery_reservation_blocks_new_host_publication() {
13998 use std::sync::mpsc;
13999
14000 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14001 let metrics = Arc::new(RuntimeMetrics::default());
14002 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14003 Arc::clone(&metrics),
14004 count_current_thread_interleaved_recovery_host_dispatch,
14005 ));
14006 let backend =
14007 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14008
14009 let registration = backend
14010 .work
14011 .try_register_async()
14012 .expect("the backend must accept work");
14013 let task = spawn_registered(
14014 &backend,
14015 Arc::clone(&metrics),
14016 std::future::pending::<()>(),
14017 registration,
14018 );
14019 let failed_dispatch = current_thread_dispatch(&executor);
14020 assert_eq!(
14021 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14022 1
14023 );
14024
14025 let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
14026 let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
14027 let cancelling_executor = Arc::clone(&executor);
14028 let cancellation = std::thread::spawn(move || {
14029 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
14030 *slot.borrow_mut() = Some(Box::new(move || {
14031 recovery_cleared_tx.send(()).unwrap();
14032 release_recovery_rx.recv().unwrap();
14033 }));
14034 });
14035 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14036 });
14037 recovery_cleared_rx
14038 .recv_timeout(Duration::from_secs(2))
14039 .expect("failed dispatch cancellation must pause after consuming the old capability");
14040
14041 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
14042 let (publication_done_tx, publication_done_rx) = mpsc::channel();
14043 let publishing_executor = Arc::clone(&executor);
14044 let publication = std::thread::spawn(move || {
14045 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14046 *slot.borrow_mut() = Some(Box::new(move || {
14047 publication_blocked_tx.send(()).unwrap();
14048 }));
14049 });
14050 publishing_executor.request_drain_if_queued();
14051 publication_done_tx.send(()).unwrap();
14052 });
14053
14054 if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
14055 release_recovery_tx.send(()).unwrap();
14056 cancellation.join().unwrap();
14057 publication.join().unwrap();
14058 panic!("a newly registered host must wait for recovery reservation: {error}");
14059 }
14060 assert!(
14061 matches!(
14062 publication_done_rx.try_recv(),
14063 Err(mpsc::TryRecvError::Empty)
14064 ),
14065 "the new host must not publish in the consumed-to-recovery gap"
14066 );
14067 release_recovery_tx.send(()).unwrap();
14068
14069 cancellation.join().unwrap();
14070 publication_done_rx
14071 .recv_timeout(Duration::from_secs(2))
14072 .unwrap();
14073 publication.join().unwrap();
14074
14075 let replacement_dispatch = current_thread_dispatch(&executor);
14076 assert_ne!(replacement_dispatch, failed_dispatch);
14077 assert_eq!(
14078 executor.recovery_dispatch.load(Ordering::Acquire),
14079 replacement_dispatch
14080 );
14081 assert_eq!(
14082 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14083 3,
14084 "the original publication and two host attempts for its one replacement must run"
14085 );
14086
14087 executor.cancel_host_dispatch_for_test(replacement_dispatch);
14088
14089 assert_eq!(
14090 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14091 3,
14092 "failure of the tagged replacement must cancel work without a second replacement"
14093 );
14094 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14095 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14096 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14097 assert_eq!(
14098 futures::executor::block_on(task).unwrap_err().to_string(),
14099 "async runtime stopped before the task completed"
14100 );
14101
14102 let later_registration = backend
14103 .work
14104 .try_register_async()
14105 .expect("terminal cancellation must reopen acceptance");
14106 let later_task = spawn_registered(
14107 &backend,
14108 Arc::clone(&metrics),
14109 async { 2usize },
14110 later_registration,
14111 );
14112 assert_eq!(
14113 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14114 4
14115 );
14116 executor.drive_host_turn();
14117 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
14118 }
14119
14120 #[cfg(not(target_family = "wasm"))]
14121 #[test]
14122 fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
14123 use std::sync::mpsc;
14124
14125 struct BlockingDrop {
14126 entered: mpsc::Sender<()>,
14127 release: mpsc::Receiver<()>,
14128 }
14129
14130 impl Drop for BlockingDrop {
14131 fn drop(&mut self) {
14132 self.entered.send(()).unwrap();
14133 self.release.recv().unwrap();
14134 }
14135 }
14136
14137 reset_current_thread_recovery_host_dispatches();
14138 let metrics = Arc::new(RuntimeMetrics::default());
14139 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14140 Arc::clone(&metrics),
14141 count_current_thread_recovery_host_dispatch,
14142 ));
14143 let backend =
14144 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14145
14146 let holder_registration = backend
14147 .work
14148 .try_register_work()
14149 .expect("the backend must accept the blocking holder");
14150 let (holder_started_tx, holder_started_rx) = mpsc::channel();
14151 let (release_holder_tx, release_holder_rx) = mpsc::channel();
14152 let holder_executor = Arc::clone(&executor);
14153 let holder = std::thread::spawn(move || {
14154 let handle = holder_executor.schedule_blocking(
14155 move || {
14156 holder_started_tx.send(()).unwrap();
14157 release_holder_rx.recv().unwrap();
14158 },
14159 holder_registration,
14160 );
14161 futures::executor::block_on(handle).unwrap();
14162 });
14163 holder_started_rx
14164 .recv_timeout(Duration::from_secs(2))
14165 .expect("the first blocking call must hold the CurrentThread lane");
14166
14167 let cancelled_registration = backend
14168 .work
14169 .try_register_work()
14170 .expect("the backend must accept queued blocking work");
14171 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
14172 let (release_drop_tx, release_drop_rx) = mpsc::channel();
14173 let blocking_drop = BlockingDrop {
14174 entered: drop_entered_tx,
14175 release: release_drop_rx,
14176 };
14177 let cancelled = executor.schedule_blocking(
14178 move || {
14179 let _blocking_drop = blocking_drop;
14180 },
14181 cancelled_registration,
14182 );
14183
14184 let async_registration = backend
14185 .work
14186 .try_register_async()
14187 .expect("the backend must accept queued async work");
14188 let pending = spawn_registered(
14189 &backend,
14190 Arc::clone(&metrics),
14191 std::future::pending::<()>(),
14192 async_registration,
14193 );
14194 let failed_dispatch = current_thread_dispatch(&executor);
14195 executor.cancel_host_dispatch_for_test(failed_dispatch);
14196 let replacement_dispatch = current_thread_dispatch(&executor);
14197
14198 let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
14199 let cancelling_executor = Arc::clone(&executor);
14200 let cancellation = std::thread::spawn(move || {
14201 cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
14202 cancel_done_tx.send(()).unwrap();
14203 });
14204 drop_entered_rx
14205 .recv_timeout(Duration::from_secs(2))
14206 .expect("terminal dispatch cancellation must destroy the queued blocking capture");
14207 let active_during_drop = backend.work.state.lock().unwrap().active;
14208 let cancellation_blocked = cancel_done_rx.try_recv().is_err();
14209
14210 release_drop_tx.send(()).unwrap();
14211 cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14212 cancellation.join().unwrap();
14213
14214 assert_eq!(
14215 active_during_drop, 2,
14216 "the blocking capture's work registration must remain active until its destructor returns"
14217 );
14218 assert!(
14219 cancellation_blocked,
14220 "terminal cancellation must wait for capture destruction"
14221 );
14222 assert!(futures::executor::block_on(cancelled).is_err());
14223 assert!(futures::executor::block_on(pending).is_err());
14224 assert_eq!(backend.work.state.lock().unwrap().active, 1);
14225
14226 release_holder_tx.send(()).unwrap();
14227 holder.join().unwrap();
14228 assert_eq!(backend.work.state.lock().unwrap().active, 0);
14229 }
14230
14231 #[test]
14232 fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
14233 reset_current_thread_one_shot_host_dispatches();
14234 let metrics = Arc::new(RuntimeMetrics::default());
14235 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14236 Arc::clone(&metrics),
14237 accept_only_first_current_thread_host_dispatch,
14238 ));
14239
14240 let scheduler = Arc::clone(&executor);
14241 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14242 scheduler.schedule(runnable)
14243 });
14244 executor.schedule(runnable);
14245 let failed_dispatch = current_thread_dispatch(&executor);
14246
14247 executor.cancel_host_dispatch_for_test(failed_dispatch);
14248
14249 assert_eq!(
14250 current_thread_one_shot_host_dispatches(),
14251 2,
14252 "the original and its one bounded replacement must be attempted"
14253 );
14254 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14255 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14256 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14257 assert!(
14258 futures::executor::block_on(task.fallible()).is_none(),
14259 "an unavailable replacement host must settle the original task as cancelled"
14260 );
14261 }
14262
14263 enum TestCurrentThreadTaskDriverBehavior {
14264 Healthy,
14265 PanicInLivenessAndSweep,
14266 PanicInDispatchAndSweep,
14267 }
14268
14269 struct TestCurrentThreadTaskDriver {
14270 behavior: TestCurrentThreadTaskDriverBehavior,
14271 dispatched: AtomicBool,
14272 }
14273
14274 #[derive(Default)]
14275 struct ManualCurrentThreadTaskDriver {
14276 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
14277 }
14278
14279 struct FailDuringAuthoritativePanicPublication;
14280 struct FailDuringLateRegistrationPublication;
14281 struct FailDuringNestedUnwindPublication;
14282 struct FailDuringReplacementRepublishPublication;
14283 struct FirstHostileCurrentThreadPanic;
14284 struct LaterHostileCurrentThreadPanic;
14285
14286 impl Drop for FirstHostileCurrentThreadPanic {
14287 fn drop(&mut self) {
14288 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
14289 panic!("the first retained CurrentThread panic payload was dropped");
14290 }
14291 }
14292
14293 impl Drop for LaterHostileCurrentThreadPanic {
14294 fn drop(&mut self) {
14295 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
14296 panic!("a later CurrentThread panic payload escaped containment");
14297 }
14298 }
14299
14300 impl ManualCurrentThreadTaskDriver {
14301 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
14302 self.deliveries.lock().unwrap().clone()
14303 }
14304 }
14305
14306 impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
14307 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14308 self.deliveries.lock().unwrap().push(delivery);
14309 true
14310 }
14311 }
14312
14313 impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
14314 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14315 let completion =
14316 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
14317 assert!(
14318 completion.failed_dispatches.is_empty(),
14319 "the failed delivery must remain provisional until its publication retires"
14320 );
14321 true
14322 }
14323 }
14324
14325 impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
14326 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14327 let completion =
14328 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
14329 assert!(
14330 completion.failed_dispatches.is_empty(),
14331 "the failed delivery must remain provisional until its publication retires"
14332 );
14333 true
14334 }
14335 }
14336
14337 impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
14338 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14339 let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
14340 assert!(
14341 completion.failed_dispatches.is_empty(),
14342 "the failed delivery must remain provisional until its publication retires"
14343 );
14344 true
14345 }
14346 }
14347
14348 impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
14349 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14350 let completion =
14351 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
14352 assert!(
14353 completion.failed_dispatches.is_empty(),
14354 "the failed delivery must remain provisional until its publication retires"
14355 );
14356 true
14357 }
14358 }
14359
14360 struct ReentrantCurrentThreadTaskDriver {
14361 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14362 registration: Mutex<Option<CurrentThreadTaskDriverId>>,
14363 }
14364
14365 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
14366 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14367 true
14368 }
14369
14370 fn is_live(&self) -> bool {
14371 let registration = *self.registration.lock().unwrap();
14372 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
14373 registry.unregister(id);
14374 }
14375 false
14376 }
14377 }
14378
14379 struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
14380 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14381 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
14382 }
14383
14384 impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
14385 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14386 true
14387 }
14388
14389 fn is_live(&self) -> bool {
14390 if let Some(registry) = self.registry.upgrade() {
14391 for registration in self.registrations.lock().unwrap().iter().copied() {
14392 let completion = registry.unregister(registration);
14393 assert!(completion.failed_dispatches.is_empty());
14394 assert!(!completion.redispatch);
14395 }
14396 }
14397 false
14398 }
14399 }
14400
14401 struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14402 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14403 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
14404 drops: Arc<AtomicUsize>,
14405 }
14406
14407 impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14408 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14409 if let Some(registry) = self.registry.upgrade() {
14410 for registration in self.registrations.lock().unwrap().iter().copied() {
14411 let completion = registry.unregister(registration);
14412 assert!(completion.failed_dispatches.is_empty());
14413 assert!(!completion.redispatch);
14414 }
14415 }
14416 true
14417 }
14418 }
14419
14420 impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14421 fn drop(&mut self) {
14422 self.drops.fetch_add(1, Ordering::SeqCst);
14423 panic!("intentional reentrant CurrentThread task-driver destructor panic");
14424 }
14425 }
14426
14427 struct PanickingDropCurrentThreadTaskDriver {
14428 drops: Arc<AtomicUsize>,
14429 }
14430
14431 impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
14432 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14433 true
14434 }
14435 }
14436
14437 impl Drop for PanickingDropCurrentThreadTaskDriver {
14438 fn drop(&mut self) {
14439 self.drops.fetch_add(1, Ordering::SeqCst);
14440 panic!("intentional CurrentThread task-driver destructor panic");
14441 }
14442 }
14443
14444 struct ReentrantCurrentThreadTaskDriverDrop {
14445 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14446 fallback: CurrentThreadTaskDriverId,
14447 }
14448
14449 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
14450 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14451 true
14452 }
14453 }
14454
14455 impl Drop for ReentrantCurrentThreadTaskDriverDrop {
14456 fn drop(&mut self) {
14457 if let Some(registry) = self.registry.upgrade() {
14458 registry.unregister(self.fallback);
14459 }
14460 }
14461 }
14462
14463 impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
14464 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14465 assert!(
14466 !matches!(
14467 self.behavior,
14468 TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
14469 ),
14470 "intentional task-driver dispatch panic"
14471 );
14472 self.dispatched.store(true, Ordering::SeqCst);
14473 true
14474 }
14475
14476 fn is_live(&self) -> bool {
14477 assert!(
14478 !matches!(
14479 self.behavior,
14480 TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
14481 ),
14482 "intentional task-driver liveness panic"
14483 );
14484 true
14485 }
14486
14487 fn on_swept(&self) {
14488 assert!(
14489 matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
14490 "intentional task-driver sweep panic"
14491 );
14492 }
14493 }
14494
14495 #[test]
14496 fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
14497 let registry = CurrentThreadTaskDriverRegistry::default();
14498 let fallback = Arc::new(TestCurrentThreadTaskDriver {
14499 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
14500 dispatched: AtomicBool::new(false),
14501 });
14502 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
14503 registry.register(Arc::new(TestCurrentThreadTaskDriver {
14504 behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
14505 dispatched: AtomicBool::new(false),
14506 }));
14507 registry.register(Arc::new(TestCurrentThreadTaskDriver {
14508 behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
14509 dispatched: AtomicBool::new(false),
14510 }));
14511
14512 assert_eq!(
14513 registry.dispatch(37),
14514 CurrentThreadHostDispatchResult::Accepted,
14515 "a panicking newest host must fall back to a live host"
14516 );
14517 assert!(fallback.dispatched.load(Ordering::SeqCst));
14518 }
14519
14520 #[test]
14521 fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
14522 let registry = CurrentThreadTaskDriverRegistry::default();
14523 let first = Arc::new(ManualCurrentThreadTaskDriver::default());
14524 let second = Arc::new(ManualCurrentThreadTaskDriver::default());
14525 registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
14526 registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
14527 registry
14528 .next_delivery
14529 .store(u64::MAX - 1, Ordering::Relaxed);
14530
14531 let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
14532 assert!(
14533 publication.is_err(),
14534 "delivery-id exhaustion on the second host must unwind the publication"
14535 );
14536 {
14537 let state = registry
14538 .state
14539 .lock()
14540 .unwrap_or_else(std::sync::PoisonError::into_inner);
14541 assert!(
14542 !state.dispatches.contains_key(&38),
14543 "unwind cleanup must release the exact registry publication barrier"
14544 );
14545 assert!(
14546 state
14547 .entries
14548 .values()
14549 .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
14550 "failed transactional reservation must leave every physical host slot clean"
14551 );
14552 }
14553 assert!(first.deliveries().is_empty());
14554 assert!(second.deliveries().is_empty());
14555
14556 registry.next_delivery.store(0, Ordering::Relaxed);
14557 assert_eq!(
14558 registry.dispatch(38),
14559 CurrentThreadHostDispatchResult::Accepted
14560 );
14561 let first_delivery = first.deliveries()[0];
14562 let second_delivery = second.deliveries()[0];
14563 assert_eq!(
14564 registry.claim_delivery(first_delivery.capability()),
14565 Some(38)
14566 );
14567 assert_eq!(
14568 registry.claim_delivery(second_delivery.capability()),
14569 Some(38)
14570 );
14571 registry.mark_serviced(38);
14572 assert!(
14573 registry
14574 .finish_delivery(first_delivery, false)
14575 .failed_dispatches
14576 .is_empty()
14577 );
14578 assert!(
14579 registry
14580 .finish_delivery(second_delivery, false)
14581 .failed_dispatches
14582 .is_empty()
14583 );
14584
14585 assert_eq!(
14586 registry.dispatch(39),
14587 CurrentThreadHostDispatchResult::Accepted
14588 );
14589 let first_later = first.deliveries()[1];
14590 let second_later = second.deliveries()[1];
14591 assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
14592 assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
14593 registry.mark_serviced(39);
14594 assert!(
14595 registry
14596 .finish_delivery(first_later, false)
14597 .failed_dispatches
14598 .is_empty()
14599 );
14600 assert!(
14601 registry
14602 .finish_delivery(second_later, false)
14603 .failed_dispatches
14604 .is_empty()
14605 );
14606 assert!(
14607 registry
14608 .state
14609 .lock()
14610 .unwrap_or_else(std::sync::PoisonError::into_inner)
14611 .dispatches
14612 .is_empty()
14613 );
14614 let state = registry
14615 .state
14616 .lock()
14617 .unwrap_or_else(std::sync::PoisonError::into_inner);
14618 assert!(
14619 state
14620 .entries
14621 .values()
14622 .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
14623 );
14624 }
14625
14626 #[test]
14627 fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
14628 let registry = CurrentThreadTaskDriverRegistry::default();
14629 let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
14630 let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
14631 let dispatch = 40;
14632
14633 assert_eq!(
14634 registry.dispatch(dispatch),
14635 CurrentThreadHostDispatchResult::Accepted
14636 );
14637 let failed_delivery = failed.deliveries()[0];
14638 let failure = current_thread_dispatch_failure(dispatch, 0);
14639 assert_eq!(
14640 registry
14641 .finish_delivery(failed_delivery, true)
14642 .failed_dispatches,
14643 vec![failure]
14644 );
14645 registry.unregister(failed_id);
14646
14647 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
14648 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
14649 assert!(
14650 registry.rearm_failed_publication(failure),
14651 "a closed unreferenced dispatch must reopen for its armed publication owner"
14652 );
14653 assert_eq!(
14654 registry.dispatch(dispatch),
14655 CurrentThreadHostDispatchResult::Accepted
14656 );
14657 let replacement_delivery = replacement.deliveries()[0];
14658 assert_eq!(replacement_delivery.dispatch, dispatch);
14659 assert_eq!(
14660 registry.claim_delivery(replacement_delivery.capability()),
14661 Some(dispatch)
14662 );
14663 registry.mark_serviced(dispatch);
14664 assert!(
14665 registry
14666 .finish_delivery(replacement_delivery, false)
14667 .failed_dispatches
14668 .is_empty()
14669 );
14670 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14671 }
14672
14673 #[test]
14674 fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
14675 struct RacingTaskDriver {
14676 executor: Arc<CurrentThreadExecutor>,
14677 drives: bool,
14678 dispatches: Mutex<Vec<u64>>,
14679 }
14680
14681 impl CurrentThreadTaskDriver for RacingTaskDriver {
14682 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14683 self.dispatches.lock().unwrap().push(delivery.dispatch);
14684 if self.drives {
14685 self
14686 .executor
14687 .drive_host_turn_for_dispatch(delivery.dispatch);
14688 }
14689 true
14690 }
14691 }
14692
14693 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14694 Arc::new(RuntimeMetrics::default()),
14695 accept_current_thread_host_dispatch,
14696 ));
14697 let polls = Arc::new(AtomicUsize::new(0));
14698 let task_polls = Arc::clone(&polls);
14699 let scheduler = Arc::clone(&executor);
14700 let (runnable, task) = async_task::spawn(
14701 async move {
14702 task_polls.fetch_add(1, Ordering::SeqCst);
14703 },
14704 move |runnable| scheduler.schedule(runnable),
14705 );
14706 executor.schedule(runnable);
14707 let dispatch = current_thread_dispatch(&executor);
14708
14709 let registry = CurrentThreadTaskDriverRegistry::default();
14710 let responsive = Arc::new(RacingTaskDriver {
14711 executor: Arc::clone(&executor),
14712 drives: true,
14713 dispatches: Mutex::new(Vec::new()),
14714 });
14715 let starved = Arc::new(RacingTaskDriver {
14716 executor: Arc::clone(&executor),
14717 drives: false,
14718 dispatches: Mutex::new(Vec::new()),
14719 });
14720 registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
14721 registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
14723
14724 assert_eq!(
14725 registry.dispatch(dispatch),
14726 CurrentThreadHostDispatchResult::Accepted
14727 );
14728 assert_eq!(
14729 responsive.dispatches.lock().unwrap().as_slice(),
14730 &[dispatch]
14731 );
14732 assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
14733 assert_eq!(
14734 polls.load(Ordering::SeqCst),
14735 1,
14736 "the responsive older host must win even when the newest host is starved"
14737 );
14738
14739 executor.drive_host_turn_for_dispatch(dispatch);
14742 assert_eq!(polls.load(Ordering::SeqCst), 1);
14743 futures::executor::block_on(task);
14744 }
14745
14746 #[test]
14747 fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
14748 const HOSTS: usize = 1_024;
14749
14750 let registry = CurrentThreadTaskDriverRegistry::default();
14751 let mut ids = Vec::with_capacity(HOSTS);
14752 let mut drivers = Vec::with_capacity(HOSTS);
14753 for _ in 0..HOSTS {
14754 let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
14755 ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
14756 drivers.push(driver);
14757 }
14758
14759 let dispatch = u64::MAX - 300;
14760 assert_eq!(
14761 registry.dispatch(dispatch),
14762 CurrentThreadHostDispatchResult::Accepted
14763 );
14764 let deliveries = drivers
14765 .iter()
14766 .map(|driver| driver.deliveries()[0])
14767 .collect::<Vec<_>>();
14768 {
14769 let state = registry
14770 .state
14771 .lock()
14772 .unwrap_or_else(std::sync::PoisonError::into_inner);
14773 assert_eq!(state.delivery_drivers.len(), HOSTS);
14774 assert_eq!(
14775 state
14776 .dispatches
14777 .get(&dispatch)
14778 .map(|state| state.references),
14779 Some(HOSTS)
14780 );
14781 assert_eq!(
14782 state
14783 .ordered_snapshot()
14784 .iter()
14785 .map(|(id, _)| *id)
14786 .collect::<Vec<_>>(),
14787 ids,
14788 "keyed driver lookup must retain registration order"
14789 );
14790 }
14791
14792 for delivery in &deliveries {
14793 assert_eq!(
14794 registry.claim_delivery(delivery.capability()),
14795 Some(dispatch)
14796 );
14797 }
14798 registry.mark_serviced(dispatch);
14799 for delivery in deliveries {
14800 assert!(
14801 registry
14802 .finish_delivery(delivery, false)
14803 .failed_dispatches
14804 .is_empty()
14805 );
14806 }
14807 {
14808 let state = registry
14809 .state
14810 .lock()
14811 .unwrap_or_else(std::sync::PoisonError::into_inner);
14812 assert!(state.delivery_drivers.is_empty());
14813 assert!(state.dispatches.is_empty());
14814 }
14815
14816 let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
14817 for id in &removed {
14818 let completion = registry.unregister(*id);
14819 assert!(completion.failed_dispatches.is_empty());
14820 assert!(!completion.redispatch);
14821 }
14822 let expected = ids
14823 .iter()
14824 .copied()
14825 .filter(|id| !removed.contains(id))
14826 .collect::<Vec<_>>();
14827 let live = registry.live().live;
14828 assert_eq!(
14829 live
14830 .as_slice()
14831 .iter()
14832 .map(|(id, _)| *id)
14833 .collect::<Vec<_>>(),
14834 expected,
14835 "O(1) removals must preserve the relative order of surviving host registrations"
14836 );
14837 drop(live);
14838
14839 for id in expected {
14840 registry.unregister(id);
14841 }
14842 let state = registry
14843 .state
14844 .lock()
14845 .unwrap_or_else(std::sync::PoisonError::into_inner);
14846 assert!(state.entries.is_empty());
14847 assert!(state.oldest.is_none());
14848 assert!(state.newest.is_none());
14849 }
14850
14851 #[test]
14852 fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
14853 let registry = CurrentThreadTaskDriverRegistry::default();
14854 let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
14855 let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
14856 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14857 registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
14858 registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
14859 registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14860
14861 let dispatch = 41;
14862 assert_eq!(
14863 registry.dispatch(dispatch),
14864 CurrentThreadHostDispatchResult::Accepted
14865 );
14866 let failing_delivery = failing.deliveries()[0];
14867 let delayed_delivery = delayed.deliveries()[0];
14868 let healthy_delivery = healthy.deliveries()[0];
14869 assert_eq!(failing_delivery.dispatch, dispatch);
14870 assert_eq!(delayed_delivery.dispatch, dispatch);
14871 assert_eq!(healthy_delivery.dispatch, dispatch);
14872
14873 assert!(
14874 registry
14875 .finish_delivery(failing_delivery, true)
14876 .failed_dispatches
14877 .is_empty(),
14878 "one host failure must not recover while other accepted attempts remain"
14879 );
14880 assert_eq!(
14881 registry.claim_delivery(healthy_delivery.capability()),
14882 Some(dispatch)
14883 );
14884 registry.mark_serviced(dispatch);
14885 assert!(
14886 registry
14887 .finish_delivery(healthy_delivery, false)
14888 .failed_dispatches
14889 .is_empty()
14890 );
14891 assert_eq!(
14892 registry.claim_delivery(delayed_delivery.capability()),
14893 None,
14894 "a delayed losing delivery must become stale after another host services the dispatch"
14895 );
14896 assert!(
14897 registry
14898 .finish_delivery(delayed_delivery, true)
14899 .failed_dispatches
14900 .is_empty(),
14901 "a stale losing failure must not recover or cancel a serviced dispatch"
14902 );
14903 assert!(
14904 registry.state.lock().unwrap().dispatches.is_empty(),
14905 "all per-dispatch acknowledgement state must retire"
14906 );
14907 }
14908
14909 #[test]
14910 fn current_thread_unclaimed_callback_success_is_delivery_failure() {
14911 let registry = CurrentThreadTaskDriverRegistry::default();
14912 let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
14913 registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
14914
14915 let dispatch = 56;
14916 assert_eq!(
14917 registry.dispatch(dispatch),
14918 CurrentThreadHostDispatchResult::Accepted
14919 );
14920 let delivery = no_op.deliveries()[0];
14921 let completion = registry.finish_delivery(delivery, false);
14922 let failure = current_thread_dispatch_failure(dispatch, 0);
14923 assert_eq!(
14924 completion.failed_dispatches,
14925 vec![failure],
14926 "a callback return without an exact capability claim must enter recovery"
14927 );
14928 assert!(
14929 registry
14930 .state
14931 .lock()
14932 .unwrap()
14933 .dispatches
14934 .get(&dispatch)
14935 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14936 );
14937 registry.finish_cancellation(failure);
14938 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14939 }
14940
14941 #[test]
14942 fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
14943 struct FailDuringPublication {
14944 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14945 }
14946
14947 impl CurrentThreadTaskDriver for FailDuringPublication {
14948 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14949 let completion = self
14950 .registry
14951 .upgrade()
14952 .expect("the registry must outlive publication")
14953 .finish_delivery(delivery, true);
14954 assert!(
14955 completion.failed_dispatches.is_empty(),
14956 "failure must remain provisional until the broadcast closes"
14957 );
14958 true
14959 }
14960 }
14961
14962 struct RejectProvisional;
14963
14964 impl CurrentThreadTaskDriver for RejectProvisional {
14965 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14966 false
14967 }
14968 }
14969
14970 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14971 registry.register(Arc::new(RejectProvisional));
14972 registry.register(Arc::new(FailDuringPublication {
14975 registry: Arc::downgrade(®istry),
14976 }));
14977
14978 let failure = current_thread_dispatch_failure(57, 0);
14979 assert_eq!(
14980 registry.dispatch(57),
14981 CurrentThreadHostDispatchResult::Failed(failure),
14982 "the final provisional rejection must expose the earlier accepted failure"
14983 );
14984 assert!(
14985 registry
14986 .state
14987 .lock()
14988 .unwrap()
14989 .dispatches
14990 .get(&57)
14991 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14992 );
14993 assert_eq!(
14994 registry.dispatch(57),
14995 CurrentThreadHostDispatchResult::Failed(failure),
14996 "a racing replacement publication must preserve terminal failure until cancellation runs"
14997 );
14998 registry.finish_cancellation(failure);
14999 }
15000
15001 #[test]
15002 fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
15003 struct RejectProvisional;
15004
15005 impl CurrentThreadTaskDriver for RejectProvisional {
15006 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
15007 false
15008 }
15009 }
15010
15011 let registry = CurrentThreadTaskDriverRegistry::default();
15012 registry.register(Arc::new(RejectProvisional));
15013 registry.register(Arc::new(RejectProvisional));
15014
15015 assert_eq!(
15016 registry.dispatch(58),
15017 CurrentThreadHostDispatchResult::Unavailable
15018 );
15019 assert!(
15020 registry.state.lock().unwrap().dispatches.is_empty(),
15021 "a broadcast with no accepted attempt must not enter failure recovery"
15022 );
15023 }
15024
15025 #[test]
15026 fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
15027 let registry = CurrentThreadTaskDriverRegistry::default();
15028 let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
15029 let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
15030
15031 assert_eq!(
15032 registry.dispatch(1),
15033 CurrentThreadHostDispatchResult::Accepted
15034 );
15035 let first = blocked.deliveries()[0];
15036 for dispatch in 2..=256 {
15037 assert_eq!(
15038 registry.dispatch(dispatch),
15039 CurrentThreadHostDispatchResult::Accepted
15040 );
15041 assert_eq!(
15042 blocked.deliveries().len(),
15043 1,
15044 "a blocked host must never receive a second queued or in-flight callback"
15045 );
15046 }
15047 {
15048 let state = registry.state.lock().unwrap();
15049 let entry = state
15050 .entries
15051 .get(&blocked_id)
15052 .expect("the blocked host must remain registered");
15053 assert_eq!(entry.pending, Some(256));
15054 assert_eq!(
15055 state.dispatches.len(),
15056 2,
15057 "only the in-flight and latest pending dispatches may retain registry state"
15058 );
15059 }
15060
15061 assert_eq!(registry.claim_delivery(first.capability()), Some(1));
15062 registry.mark_serviced(1);
15063 let completion = registry.finish_delivery(first, false);
15064 assert!(completion.failed_dispatches.is_empty());
15065 assert!(completion.redispatch);
15066 assert_eq!(
15067 registry.dispatch(256),
15068 CurrentThreadHostDispatchResult::Accepted
15069 );
15070 let deliveries = blocked.deliveries();
15071 assert_eq!(deliveries.len(), 2);
15072 assert_eq!(
15073 deliveries[1].dispatch, 256,
15074 "completion must schedule exactly the latest coalesced capability"
15075 );
15076 assert_eq!(registry.claim_delivery(first.capability()), None);
15077 assert_eq!(
15078 registry.claim_delivery(deliveries[1].capability()),
15079 Some(256)
15080 );
15081 registry.mark_serviced(256);
15082 assert!(
15083 registry
15084 .finish_delivery(deliveries[1], false)
15085 .failed_dispatches
15086 .is_empty()
15087 );
15088 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15089 }
15090
15091 #[test]
15092 fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
15093 struct RejectAfterFirst {
15094 calls: AtomicUsize,
15095 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
15096 }
15097
15098 impl CurrentThreadTaskDriver for RejectAfterFirst {
15099 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
15100 self.deliveries.lock().unwrap().push(delivery);
15101 self.calls.fetch_add(1, Ordering::SeqCst) == 0
15102 }
15103 }
15104
15105 let registry = CurrentThreadTaskDriverRegistry::default();
15106 let host = Arc::new(RejectAfterFirst {
15107 calls: AtomicUsize::new(0),
15108 deliveries: Mutex::new(Vec::new()),
15109 });
15110 registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
15111 assert_eq!(
15112 registry.dispatch(11),
15113 CurrentThreadHostDispatchResult::Accepted
15114 );
15115 let first = host.deliveries.lock().unwrap()[0];
15116 assert_eq!(
15117 registry.dispatch(12),
15118 CurrentThreadHostDispatchResult::Accepted
15119 );
15120
15121 assert_eq!(registry.claim_delivery(first.capability()), Some(11));
15122 registry.mark_serviced(11);
15123 let completion = registry.finish_delivery(first, false);
15124 assert!(completion.redispatch);
15125 let failure = current_thread_dispatch_failure(12, 0);
15126 assert_eq!(
15127 registry.dispatch(12),
15128 CurrentThreadHostDispatchResult::Failed(failure),
15129 "an accepted coalesced capability must report failure if its eventual host queue rejects it"
15130 );
15131 assert!(
15132 registry
15133 .state
15134 .lock()
15135 .unwrap()
15136 .dispatches
15137 .get(&12)
15138 .is_some_and(CurrentThreadDispatchState::is_cancelling)
15139 );
15140 registry.finish_cancellation(failure);
15141 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15142 }
15143
15144 #[test]
15145 fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
15146 let registry = CurrentThreadTaskDriverRegistry::default();
15147 let old = Arc::new(ManualCurrentThreadTaskDriver::default());
15148 let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
15149 assert_eq!(
15150 registry.dispatch(73),
15151 CurrentThreadHostDispatchResult::Accepted
15152 );
15153 let old_delivery = old.deliveries()[0];
15154
15155 let completion = registry.unregister(old_id);
15156 let failure = current_thread_dispatch_failure(73, 0);
15157 assert_eq!(
15158 completion.failed_dispatches,
15159 vec![failure],
15160 "unregistering the final accepting host must retire its exact dispatch"
15161 );
15162 registry.finish_cancellation(failure);
15163 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
15164 let replacement_id =
15165 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
15166 assert_ne!(replacement_id, old_id);
15167 assert_eq!(
15168 registry.dispatch(74),
15169 CurrentThreadHostDispatchResult::Accepted
15170 );
15171 let replacement_delivery = replacement.deliveries()[0];
15172 assert_ne!(replacement_delivery.capability(), old_delivery.capability());
15173
15174 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
15175 assert!(
15176 registry
15177 .finish_delivery(old_delivery, true)
15178 .failed_dispatches
15179 .is_empty()
15180 );
15181 assert!(
15182 registry
15183 .fail_delivery(old_delivery.capability())
15184 .failed_dispatches
15185 .is_empty()
15186 );
15187 assert_eq!(
15188 registry.claim_delivery(replacement_delivery.capability()),
15189 Some(74)
15190 );
15191 registry.mark_serviced(74);
15192 assert!(
15193 registry
15194 .finish_delivery(replacement_delivery, false)
15195 .failed_dispatches
15196 .is_empty()
15197 );
15198 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15199 }
15200
15201 #[test]
15202 fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
15203 struct QueuePendingThenReject {
15204 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
15205 }
15206
15207 impl CurrentThreadTaskDriver for QueuePendingThenReject {
15208 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
15209 assert_eq!(delivery.dispatch, 81);
15210 let completion = self
15211 .registry
15212 .upgrade()
15213 .expect("the registry must outlive publication")
15214 .dispatch_with_completion(82);
15215 assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
15216 assert!(completion.failed_dispatches.is_empty());
15217 false
15218 }
15219 }
15220
15221 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15222 registry.register(Arc::new(QueuePendingThenReject {
15223 registry: Arc::downgrade(®istry),
15224 }));
15225
15226 let completion = registry.dispatch_with_completion(81);
15227 assert_eq!(
15228 completion.result,
15229 CurrentThreadHostDispatchResult::Unavailable
15230 );
15231 let failure = current_thread_dispatch_failure(82, 0);
15232 assert_eq!(
15233 completion.failed_dispatches,
15234 vec![failure],
15235 "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
15236 );
15237 assert!(
15238 registry
15239 .state
15240 .lock()
15241 .unwrap()
15242 .dispatches
15243 .get(&82)
15244 .is_some_and(CurrentThreadDispatchState::is_cancelling)
15245 );
15246 registry.finish_cancellation(failure);
15247 assert!(registry.state.lock().unwrap().dispatches.is_empty());
15248 }
15249
15250 #[test]
15251 fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
15252 let registry = CurrentThreadTaskDriverRegistry::default();
15253 let host = Arc::new(ManualCurrentThreadTaskDriver::default());
15254 let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
15255 assert_eq!(
15256 registry.dispatch(91),
15257 CurrentThreadHostDispatchResult::Accepted
15258 );
15259 let old_delivery = host.deliveries()[0];
15260 assert_eq!(
15261 registry.dispatch(92),
15262 CurrentThreadHostDispatchResult::Accepted
15263 );
15264
15265 registry.retire_for_shutdown();
15266 {
15267 let state = registry.state.lock().unwrap();
15268 assert!(state.dispatches.is_empty());
15269 let entry = state
15270 .entries
15271 .get(&host_id)
15272 .expect("the host must remain registered");
15273 assert_eq!(entry.pending, None);
15274 assert!(
15275 entry.delivery.is_some(),
15276 "the physical callback remains single-flight until it actually retires"
15277 );
15278 }
15279 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
15280
15281 assert_eq!(
15282 registry.dispatch(93),
15283 CurrentThreadHostDispatchResult::Accepted
15284 );
15285 assert_eq!(host.deliveries().len(), 1);
15286 let completion = registry.finish_delivery(old_delivery, true);
15287 assert!(
15288 completion.failed_dispatches.is_empty(),
15289 "a retired-generation failure must not cancel restarted work"
15290 );
15291 assert!(completion.redispatch);
15292 assert_eq!(
15293 registry.dispatch(93),
15294 CurrentThreadHostDispatchResult::Accepted
15295 );
15296 let restarted_delivery = host.deliveries()[1];
15297 assert_eq!(restarted_delivery.dispatch, 93);
15298 assert_eq!(
15299 registry.claim_delivery(restarted_delivery.capability()),
15300 Some(93)
15301 );
15302 registry.mark_serviced(93);
15303 assert!(
15304 registry
15305 .finish_delivery(restarted_delivery, false)
15306 .failed_dispatches
15307 .is_empty()
15308 );
15309
15310 registry.unregister(host_id);
15311 let state = registry.state.lock().unwrap();
15312 assert!(state.entries.is_empty());
15313 assert!(state.dispatches.is_empty());
15314 }
15315
15316 #[test]
15317 fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
15318 use std::{sync::mpsc, time::Duration};
15319
15320 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15321 let fallback = Arc::new(TestCurrentThreadTaskDriver {
15322 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
15323 dispatched: AtomicBool::new(false),
15324 });
15325 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
15326 let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
15327 registry: Arc::downgrade(®istry),
15328 registration: Mutex::new(None),
15329 });
15330 let reentrant_id =
15331 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15332 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
15333
15334 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
15335 let selection_registry = Arc::clone(®istry);
15336 let selection_thread = std::thread::spawn(move || {
15337 selection_tx
15338 .send(selection_registry.current().map(|(id, _)| id))
15339 .unwrap();
15340 });
15341 assert_eq!(
15342 selection_rx
15343 .recv_timeout(Duration::from_secs(10))
15344 .expect("is_live re-entry deadlocked"),
15345 Some(fallback_id)
15346 );
15347 selection_thread.join().unwrap();
15348
15349 let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
15350 Arc::new(ReentrantCurrentThreadTaskDriverDrop {
15351 registry: Arc::downgrade(®istry),
15352 fallback: fallback_id,
15353 });
15354 let drop_id = registry.register(Arc::clone(&drop_driver));
15355 drop(drop_driver);
15356
15357 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
15358 let drop_registry = Arc::clone(®istry);
15359 let drop_thread = std::thread::spawn(move || {
15360 drop_registry.unregister(drop_id);
15361 drop_tx.send(()).unwrap();
15362 });
15363 drop_rx
15364 .recv_timeout(Duration::from_secs(10))
15365 .expect("driver destructor re-entry deadlocked");
15366 drop_thread.join().unwrap();
15367 assert!(
15368 registry.current().is_none(),
15369 "the destructor must unregister the fallback"
15370 );
15371 }
15372
15373 #[test]
15374 fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
15375 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
15376
15377 if std::env::var_os(CHILD_ENV).is_some() {
15378 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15379 let fallback = Arc::new(TestCurrentThreadTaskDriver {
15380 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
15381 dispatched: AtomicBool::new(false),
15382 });
15383 let fallback_id =
15384 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
15385 let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
15386 registry: Arc::downgrade(®istry),
15387 registrations: Mutex::new(Vec::new()),
15388 });
15389 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15390
15391 let first_drops = Arc::new(AtomicUsize::new(0));
15392 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
15393 drops: Arc::clone(&first_drops),
15394 });
15395 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
15396 drop(first);
15397
15398 let second_drops = Arc::new(AtomicUsize::new(0));
15399 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
15400 drops: Arc::clone(&second_drops),
15401 });
15402 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
15403 drop(second);
15404 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
15405
15406 assert_eq!(
15407 registry.current().map(|(id, _)| id),
15408 Some(fallback_id),
15409 "selection must retry after reentrant removals and preserve the healthy fallback"
15410 );
15411 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
15412 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
15413 let state = registry
15414 .state
15415 .lock()
15416 .unwrap_or_else(std::sync::PoisonError::into_inner);
15417 assert_eq!(state.entries.len(), 1);
15418 assert!(state.entries.contains_key(&fallback_id));
15419 assert!(state.dispatches.is_empty());
15420 return;
15421 }
15422
15423 let output = std::process::Command::new(std::env::current_exe().unwrap())
15424 .arg("--exact")
15425 .arg(
15426 "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
15427 )
15428 .arg("--nocapture")
15429 .env(CHILD_ENV, "1")
15430 .output()
15431 .expect("the hostile task-driver snapshot subprocess test must start");
15432 assert!(
15433 output.status.success(),
15434 "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
15435 output.status.code(),
15436 String::from_utf8_lossy(&output.stdout),
15437 String::from_utf8_lossy(&output.stderr)
15438 );
15439 }
15440
15441 #[test]
15442 fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
15443 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
15444
15445 if std::env::var_os(CHILD_ENV).is_some() {
15446 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15447
15448 let first_drops = Arc::new(AtomicUsize::new(0));
15449 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
15450 drops: Arc::clone(&first_drops),
15451 });
15452 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
15453 drop(first);
15454
15455 let second_drops = Arc::new(AtomicUsize::new(0));
15456 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
15457 drops: Arc::clone(&second_drops),
15458 });
15459 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
15460 drop(second);
15461
15462 let reentrant_drops = Arc::new(AtomicUsize::new(0));
15463 let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
15464 registry: Arc::downgrade(®istry),
15465 registrations: Mutex::new(Vec::new()),
15466 drops: Arc::clone(&reentrant_drops),
15467 });
15468 let reentrant_id =
15469 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15470 *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
15471 drop(reentrant);
15472
15473 let dispatch = 59;
15474 let failure = current_thread_dispatch_failure(dispatch, 0);
15475 assert_eq!(
15476 registry.dispatch(dispatch),
15477 CurrentThreadHostDispatchResult::Failed(failure),
15478 "reentrant removal of every provisional host must report accepted delivery failure"
15479 );
15480 assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
15481 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
15482 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
15483 registry.finish_cancellation(failure);
15484 let state = registry
15485 .state
15486 .lock()
15487 .unwrap_or_else(std::sync::PoisonError::into_inner);
15488 assert!(state.entries.is_empty());
15489 assert!(state.dispatches.is_empty());
15490 return;
15491 }
15492
15493 let output = std::process::Command::new(std::env::current_exe().unwrap())
15494 .arg("--exact")
15495 .arg(
15496 "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
15497 )
15498 .arg("--nocapture")
15499 .env(CHILD_ENV, "1")
15500 .output()
15501 .expect("the hostile task-driver delivery subprocess test must start");
15502 assert!(
15503 output.status.success(),
15504 "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
15505 output.status.code(),
15506 String::from_utf8_lossy(&output.stdout),
15507 String::from_utf8_lossy(&output.stderr)
15508 );
15509 }
15510
15511 #[test]
15512 fn dropping_join_handle_detaches_task_like_tokio() {
15513 let completed = Arc::new(AtomicBool::new(false));
15514 let completed_task = Arc::clone(&completed);
15515 let (runnable, task) = async_task::spawn(
15516 async move {
15517 completed_task.store(true, Ordering::SeqCst);
15518 Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
15519 },
15520 |_| {},
15521 );
15522 let handle = JoinHandle(JoinHandleInner::Task {
15523 task: task.fallible(),
15524 awaiter: None,
15525 dependency: Arc::new(TaskDependency::default()),
15526 });
15527
15528 drop(handle);
15529 runnable.run();
15530 assert!(completed.load(Ordering::SeqCst));
15531 }
15532
15533 #[cfg(not(target_family = "wasm"))]
15534 #[test]
15535 fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
15536 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
15537
15538 if std::env::var_os(CHILD_ENV).is_some() {
15539 let wakes = Arc::new(AtomicUsize::new(0));
15540 let drops = Arc::new(AtomicUsize::new(0));
15541 let (runnable, task) = async_task::spawn(
15542 async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
15543 |_| {},
15544 );
15545 let mut handle = JoinHandle(JoinHandleInner::Task {
15546 task: task.fallible(),
15547 awaiter: None,
15548 dependency: Arc::new(TaskDependency::default()),
15549 });
15550 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15551 {
15552 let mut cx = Context::from_waker(&waker);
15553 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15554 }
15555 drop(waker);
15556
15557 runnable.run();
15558
15559 let waker = futures::task::noop_waker();
15560 let mut cx = Context::from_waker(&waker);
15561 assert!(matches!(
15562 Pin::new(&mut handle).poll(&mut cx),
15563 Poll::Ready(Ok(()))
15564 ));
15565 assert_eq!(
15566 drops.load(Ordering::SeqCst),
15567 1,
15568 "a completed handle must not retain the caller's awaiter"
15569 );
15570 drop(handle);
15571 assert!(wakes.load(Ordering::SeqCst) >= 1);
15572 assert_eq!(drops.load(Ordering::SeqCst), 1);
15573 return;
15574 }
15575
15576 let output = std::process::Command::new(std::env::current_exe().unwrap())
15577 .arg("--exact")
15578 .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
15579 .arg("--nocapture")
15580 .env(CHILD_ENV, "1")
15581 .output()
15582 .expect("the JoinHandle completion awaiter subprocess must start");
15583 assert!(
15584 output.status.success(),
15585 "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15586 output.status.code(),
15587 String::from_utf8_lossy(&output.stdout),
15588 String::from_utf8_lossy(&output.stderr)
15589 );
15590 }
15591
15592 #[cfg(not(target_family = "wasm"))]
15593 #[test]
15594 fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
15595 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
15596
15597 if std::env::var_os(CHILD_ENV).is_some() {
15598 let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
15599 let wakes = Arc::new(AtomicUsize::new(0));
15600 let drops = Arc::new(AtomicUsize::new(0));
15601 let mut handle = controller.spawn(std::future::pending::<()>());
15602 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15603 {
15604 let mut cx = Context::from_waker(&waker);
15605 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15606 }
15607 drop(waker);
15608
15609 controller
15610 .shutdown()
15611 .expect("shutdown must survive the hostile awaiter");
15612 drop(handle);
15613 assert!(wakes.load(Ordering::SeqCst) >= 1);
15614 assert_eq!(drops.load(Ordering::SeqCst), 1);
15615 return;
15616 }
15617
15618 let output = std::process::Command::new(std::env::current_exe().unwrap())
15619 .arg("--exact")
15620 .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
15621 .arg("--nocapture")
15622 .env(CHILD_ENV, "1")
15623 .output()
15624 .expect("the JoinHandle shutdown awaiter subprocess must start");
15625 assert!(
15626 output.status.success(),
15627 "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15628 output.status.code(),
15629 String::from_utf8_lossy(&output.stdout),
15630 String::from_utf8_lossy(&output.stderr)
15631 );
15632 }
15633
15634 #[cfg(not(target_family = "wasm"))]
15635 #[test]
15636 fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
15637 let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
15638 let mut handle = controller.spawn(std::future::pending::<()>());
15639 let old_generation = controller.backend().generation();
15640 let state = Arc::new(JoinAwaiterWakerState {
15641 events: Mutex::new(Vec::new()),
15642 });
15643 let waker = {
15644 let _generation = RuntimeGenerationGuard::enter(old_generation);
15645 join_awaiter_waker(Arc::clone(&state))
15646 };
15647 {
15648 let _generation = RuntimeGenerationGuard::enter(old_generation);
15649 let mut cx = Context::from_waker(&waker);
15650 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15651 }
15652 {
15653 let _generation = RuntimeGenerationGuard::enter(old_generation);
15654 drop(waker);
15655 }
15656
15657 controller
15658 .shutdown()
15659 .expect("the first generation must stop");
15660 controller.start().expect("the runtime must restart");
15661 let new_generation = controller.backend().generation();
15662 assert_ne!(new_generation, old_generation);
15663 state.clear();
15664
15665 let replacement = futures::task::noop_waker();
15666 {
15667 let _generation = RuntimeGenerationGuard::enter(new_generation);
15668 let mut cx = Context::from_waker(&replacement);
15669 assert!(matches!(
15670 Pin::new(&mut handle).poll(&mut cx),
15671 Poll::Ready(Err(_))
15672 ));
15673 }
15674
15675 let events = state.snapshot();
15676 assert!(
15677 !events.is_empty(),
15678 "completion must retire the registered awaiter waker"
15679 );
15680 assert!(
15681 events.iter().all(|(kind, generation)| {
15682 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15683 }),
15684 "old-generation awaiter retirement escaped its generation: {events:?}"
15685 );
15686 controller
15687 .shutdown()
15688 .expect("the replacement generation must stop");
15689 }
15690
15691 #[cfg(not(target_family = "wasm"))]
15692 #[test]
15693 fn dropping_join_handle_retains_awaiter_generation_after_restart() {
15694 let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
15695 let mut handle = controller.spawn(std::future::pending::<()>());
15696 let old_generation = controller.backend().generation();
15697 let state = Arc::new(JoinAwaiterWakerState {
15698 events: Mutex::new(Vec::new()),
15699 });
15700 let waker = {
15701 let _generation = RuntimeGenerationGuard::enter(old_generation);
15702 join_awaiter_waker(Arc::clone(&state))
15703 };
15704 {
15705 let _generation = RuntimeGenerationGuard::enter(old_generation);
15706 let mut cx = Context::from_waker(&waker);
15707 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15708 }
15709 {
15710 let _generation = RuntimeGenerationGuard::enter(old_generation);
15711 drop(waker);
15712 }
15713
15714 controller
15715 .shutdown()
15716 .expect("the first generation must stop");
15717 controller.start().expect("the runtime must restart");
15718 let new_generation = controller.backend().generation();
15719 assert_ne!(new_generation, old_generation);
15720 state.clear();
15721
15722 {
15723 let _generation = RuntimeGenerationGuard::enter(new_generation);
15724 drop(handle);
15725 }
15726
15727 let events = state.snapshot();
15728 assert!(
15729 !events.is_empty(),
15730 "handle drop must retire the registered awaiter waker"
15731 );
15732 assert!(
15733 events.iter().all(|(kind, generation)| {
15734 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15735 }),
15736 "old-generation awaiter destruction escaped its generation: {events:?}"
15737 );
15738 controller
15739 .shutdown()
15740 .expect("the replacement generation must stop");
15741 }
15742
15743 #[cfg(not(target_family = "wasm"))]
15744 #[test]
15745 fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
15746 use std::{sync::mpsc, time::Duration};
15747
15748 let options = RuntimeOptions {
15749 flavor: RuntimeFlavor::MultiThread,
15750 worker_threads: 2,
15751 max_blocking_tasks: 1,
15752 thread_name_prefix: "worker-classification".to_string(),
15753 park_deadline: None,
15754 drain_linger: DEFAULT_DRAIN_LINGER,
15755 };
15756 let executor =
15757 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
15758 let id = executor.id;
15759 let (tx, rx) = mpsc::sync_channel(1);
15760
15761 executor.pool.spawn(move || {
15762 tx.send((
15763 ON_POOL_WORKER.with(std::cell::Cell::get),
15764 IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
15765 ))
15766 .unwrap();
15767 });
15768
15769 let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
15770 assert_eq!(
15771 worker,
15772 Some(id),
15773 "nested Rayon work must use cooperative block_on"
15774 );
15775 assert_eq!(
15776 driver, None,
15777 "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
15778 );
15779 }
15780
15781 #[cfg(not(target_family = "wasm"))]
15782 #[test]
15783 fn multi_thread_block_on_does_not_park_pool_worker() {
15784 use std::sync::{Barrier, mpsc};
15789 use std::time::Duration;
15790
15791 let (done_tx, done_rx) = mpsc::channel();
15792 let runner = std::thread::spawn(move || {
15793 let metrics = Arc::new(RuntimeMetrics::default());
15794 let options = RuntimeOptions {
15795 flavor: RuntimeFlavor::MultiThread,
15796 worker_threads: 2,
15797 max_blocking_tasks: 2,
15798 thread_name_prefix: "rd1-test".to_string(),
15799 park_deadline: None,
15800 drain_linger: DEFAULT_DRAIN_LINGER,
15801 };
15802 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15803
15804 let task_count = 2usize;
15805 let barrier = Arc::new(Barrier::new(task_count));
15808
15809 let mut tasks = Vec::new();
15810 for _ in 0..task_count {
15811 let exec = Arc::clone(&executor);
15812 let barrier = Arc::clone(&barrier);
15813 let body = async move {
15814 barrier.wait();
15815 let inner_exec = Arc::clone(&exec);
15816 let mut inner = std::pin::pin!(async move {
15817 let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
15818 assert_eq!(value, 7);
15819 });
15820 exec.block_on(inner.as_mut());
15822 };
15823 let scheduler = Arc::clone(&executor);
15824 let (runnable, task) =
15825 async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
15826 executor.schedule(runnable);
15827 tasks.push(task);
15828 }
15829
15830 for task in tasks {
15831 futures::executor::block_on(task);
15832 }
15833 let _ = done_tx.send(());
15834 });
15835
15836 match done_rx.recv_timeout(Duration::from_secs(10)) {
15837 Ok(()) => runner.join().unwrap(),
15838 Err(error) => panic!(
15839 "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
15840 ),
15841 }
15842 }
15843
15844 #[cfg(not(target_family = "wasm"))]
15845 #[test]
15846 fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
15847 use std::sync::mpsc;
15866
15867 for round in 0..3 {
15868 let metrics = Arc::new(RuntimeMetrics::default());
15869 let options = RuntimeOptions {
15870 flavor: RuntimeFlavor::MultiThread,
15871 worker_threads: 2,
15872 max_blocking_tasks: 1,
15873 thread_name_prefix: format!("linger-starve-{round}"),
15874 park_deadline: None,
15875 drain_linger: DEFAULT_DRAIN_LINGER,
15876 };
15877 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15878
15879 let r_done = Arc::new(AtomicBool::new(false));
15880 let give_up = Arc::new(AtomicBool::new(false));
15881 let (done_tx, done_rx) = mpsc::channel::<Duration>();
15882
15883 let t1_executor = Arc::clone(&executor);
15889 let t1_started = Arc::new(AtomicBool::new(false));
15890 let t1_started_setter = Arc::clone(&t1_started);
15891 let t1_body = async move {
15892 t1_started_setter.store(true, Ordering::SeqCst);
15893 let deadline = Instant::now() + Duration::from_secs(10);
15894 while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
15895 if Instant::now() >= deadline {
15896 return;
15898 }
15899 std::hint::spin_loop();
15900 }
15901 };
15902 let t1_scheduler = Arc::clone(&executor);
15903 let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
15904 executor.schedule(t1_runnable);
15905 wait_until("the worker-pinning task to start", || {
15906 t1_started.load(Ordering::SeqCst)
15907 });
15908
15909 let blocking_executor = Arc::clone(&executor);
15911 let r_done_setter = Arc::clone(&r_done);
15912 let started = Instant::now();
15913 let handle = executor.schedule_blocking(move || {
15914 let (r_tx, r_rx) = oneshot::channel::<()>();
15915 blocking_executor.pool.spawn(move || {
15916 r_done_setter.store(true, Ordering::SeqCst);
15917 let _ = r_tx.send(());
15918 });
15919 let mut wait_for_r = std::pin::pin!(async move {
15920 let _ = r_rx.await;
15921 });
15922 blocking_executor.block_on(wait_for_r.as_mut());
15923 let _ = done_tx.send(started.elapsed());
15924 });
15925
15926 let arm_deadline = Instant::now() + Duration::from_secs(15);
15930 while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
15931 && !r_done.load(Ordering::SeqCst)
15932 {
15933 assert!(
15934 Instant::now() < arm_deadline,
15935 "timed out arming the linger-starvation interleaving"
15936 );
15937 std::thread::yield_now();
15938 }
15939
15940 let producer_executor = Arc::clone(&executor);
15943 let producer_r_done = Arc::clone(&r_done);
15944 let producer_give_up = Arc::clone(&give_up);
15945 let producer = std::thread::spawn(move || {
15946 let cadence = Duration::from_micros(200);
15947 let mut next_tick = Instant::now();
15948 while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
15949 let scheduler = Arc::clone(&producer_executor);
15950 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
15951 producer_executor.schedule(runnable);
15952 task.detach();
15953 next_tick += cadence;
15954 while Instant::now() < next_tick {
15955 std::hint::spin_loop();
15956 }
15957 }
15958 });
15959
15960 let outcome = done_rx.recv_timeout(Duration::from_secs(5));
15961 give_up.store(true, Ordering::SeqCst);
15962 producer.join().unwrap();
15963 match outcome {
15964 Ok(elapsed) => {
15965 futures::executor::block_on(handle).expect("blocking closure join");
15966 drop(t1_task);
15967 eprintln!("round {round}: completed in {elapsed:?}");
15968 }
15969 Err(error) => panic!(
15970 "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
15971 sub-budget periodic work must not keep a drain frame resident forever"
15972 ),
15973 }
15974 }
15975 }
15976
15977 #[cfg(not(target_family = "wasm"))]
15978 #[test]
15979 #[ignore = "microbenchmark: run explicitly in release mode"]
15980 fn parked_drivers_registry_cycle_microbench() {
15981 for &n in &[8usize, 64, 256] {
15988 let registry = ParkedDrivers::default();
15989 let parkers: Vec<Arc<DriverParker>> =
15990 (0..n).map(|_| Arc::new(DriverParker::default())).collect();
15991 for parker in &parkers {
15992 registry.register_with_role(parker, true, None);
15993 }
15994 let hot = &parkers[n - 1];
15995
15996 let iters = 2_000_000u32;
15997 let start = Instant::now();
15998 for _ in 0..iters {
15999 assert!(registry.wake_one());
16000 registry.deregister(hot);
16001 assert!(hot.consume_permit());
16002 registry.register_with_role(hot, true, None);
16003 }
16004 let wake_cycle = start.elapsed() / iters;
16005
16006 let start = Instant::now();
16007 for _ in 0..iters {
16008 registry.deregister(hot);
16009 registry.register_with_role(hot, true, None);
16010 }
16011 let timeout_cycle = start.elapsed() / iters;
16012
16013 let owner = BlockingOwnerToken {
16014 executor_id: u64::MAX,
16015 frame: u64::MAX,
16016 };
16017 let start = Instant::now();
16018 for _ in 0..iters {
16019 assert!(!registry.wake_one_owner_dependency(owner));
16020 }
16021 let owner_sweep = start.elapsed() / iters;
16022
16023 eprintln!(
16024 "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
16025 timeout-deregister+re-register {timeout_cycle:?}, \
16026 owner-dependency sweep {owner_sweep:?}"
16027 );
16028 }
16029 }
16030
16031 #[cfg(not(target_family = "wasm"))]
16032 #[test]
16033 fn multi_thread_block_on_from_caller_thread_still_parks() {
16034 let metrics = Arc::new(RuntimeMetrics::default());
16037 let options = RuntimeOptions {
16038 flavor: RuntimeFlavor::MultiThread,
16039 worker_threads: 3,
16040 max_blocking_tasks: 2,
16041 thread_name_prefix: "rd1-caller".to_string(),
16042 park_deadline: None,
16043 drain_linger: DEFAULT_DRAIN_LINGER,
16044 };
16045 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16046
16047 let mut output = None;
16048 let mut future = std::pin::pin!(async {
16049 output = Some(41usize + 1);
16050 });
16051 executor.block_on(future.as_mut());
16052 assert_eq!(output, Some(42));
16053 }
16054
16055 #[cfg(not(target_family = "wasm"))]
16056 #[test]
16057 fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
16058 use std::sync::mpsc;
16081 use std::time::Duration;
16082
16083 struct CallerProbe {
16088 task: Task<usize>,
16089 parked_tx: Option<mpsc::Sender<()>>,
16090 }
16091 impl Future for CallerProbe {
16092 type Output = usize;
16093 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
16094 let poll = Pin::new(&mut self.task).poll(cx);
16096 if poll.is_pending()
16097 && let Some(tx) = self.parked_tx.take()
16098 {
16099 let _ = tx.send(());
16101 }
16102 poll
16103 }
16104 }
16105
16106 let (done_tx, done_rx) = mpsc::channel();
16107 let runner = std::thread::spawn(move || {
16108 let metrics = Arc::new(RuntimeMetrics::default());
16109 let options = RuntimeOptions {
16110 flavor: RuntimeFlavor::MultiThread,
16111 worker_threads: 2,
16112 max_blocking_tasks: 2,
16113 thread_name_prefix: "rd10-caller".to_string(),
16114 park_deadline: None,
16115 drain_linger: DEFAULT_DRAIN_LINGER,
16116 };
16117 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16118
16119 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
16124 let scheduler = Arc::clone(&executor);
16125 let (runnable, task) = async_task::spawn(
16126 async move { gate_rx.await.expect("gate sender dropped") },
16127 move |r| {
16128 scheduler.schedule(r);
16129 },
16130 );
16131 executor.schedule(runnable);
16132
16133 let (parked_tx, parked_rx) = mpsc::channel::<()>();
16137 let releaser = std::thread::spawn(move || {
16138 if parked_rx.recv().is_ok() {
16139 let _ = gate_tx.send(42usize);
16140 }
16141 });
16142
16143 let mut output = None;
16146 {
16147 let mut future = std::pin::pin!(async {
16148 output = Some(
16149 CallerProbe {
16150 task,
16151 parked_tx: Some(parked_tx),
16152 }
16153 .await,
16154 );
16155 });
16156 executor.block_on(future.as_mut());
16157 }
16158
16159 releaser.join().unwrap();
16160 assert_eq!(
16161 output,
16162 Some(42usize),
16163 "block_on must return the pool-computed value"
16164 );
16165 let _ = done_tx.send(());
16166 });
16167
16168 match done_rx.recv_timeout(Duration::from_secs(10)) {
16169 Ok(()) => runner.join().unwrap(),
16170 Err(error) => panic!(
16171 "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
16172 ),
16173 }
16174 }
16175
16176 #[cfg(not(target_family = "wasm"))]
16177 #[test]
16178 fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
16179 use std::sync::{Barrier, mpsc};
16185 use std::time::Duration;
16186
16187 let (done_tx, done_rx) = mpsc::channel();
16188 let runner = std::thread::spawn(move || {
16189 let metrics = Arc::new(RuntimeMetrics::default());
16190 let options = RuntimeOptions {
16191 flavor: RuntimeFlavor::MultiThread,
16192 worker_threads: 2,
16193 max_blocking_tasks: 2,
16194 thread_name_prefix: "rd1-park".to_string(),
16195 park_deadline: None,
16196 drain_linger: DEFAULT_DRAIN_LINGER,
16197 };
16198 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16199
16200 let task_count = 2usize;
16201 let barrier = Arc::new(Barrier::new(task_count));
16202
16203 let mut senders = Vec::new();
16204 let mut tasks = Vec::new();
16205 for _ in 0..task_count {
16206 let (tx, rx) = oneshot::channel::<usize>();
16207 senders.push(tx);
16208 let exec = Arc::clone(&executor);
16209 let barrier = Arc::clone(&barrier);
16210 let body = async move {
16211 barrier.wait();
16213 let child_exec = Arc::clone(&exec);
16216 let (child_runnable, child_task) =
16217 async_task::spawn(async move { rx.await.unwrap() }, move |r| {
16218 child_exec.schedule(r)
16219 });
16220 exec.schedule(child_runnable);
16221 let mut inner = std::pin::pin!(async move {
16222 assert_eq!(child_task.await, 9usize);
16223 });
16224 exec.block_on(inner.as_mut());
16225 };
16226 let scheduler = Arc::clone(&executor);
16227 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
16228 executor.schedule(runnable);
16229 tasks.push(task);
16230 }
16231
16232 std::thread::sleep(Duration::from_millis(200));
16234 for tx in senders {
16235 let _ = tx.send(9usize);
16236 }
16237
16238 for task in tasks {
16239 futures::executor::block_on(task);
16240 }
16241 let _ = done_tx.send(());
16242 });
16243
16244 match done_rx.recv_timeout(Duration::from_secs(10)) {
16245 Ok(()) => runner.join().unwrap(),
16246 Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
16247 }
16248 }
16249
16250 #[cfg(not(target_family = "wasm"))]
16256 fn wait_until(what: &str, condition: impl Fn() -> bool) {
16257 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
16258 while !condition() {
16259 assert!(
16260 std::time::Instant::now() < deadline,
16261 "timed out waiting for: {what}"
16262 );
16263 std::thread::yield_now();
16264 }
16265 }
16266
16267 #[cfg(not(target_family = "wasm"))]
16276 fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
16277 let deadline = std::time::Instant::now() + timeout;
16278 while !handle.is_finished() {
16279 assert!(
16280 std::time::Instant::now() < deadline,
16281 "thread wedged past its {timeout:?} bound while: {what}"
16282 );
16283 std::thread::sleep(Duration::from_millis(10));
16284 }
16285 handle.join().unwrap();
16286 }
16287
16288 #[cfg(not(target_family = "wasm"))]
16289 fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
16290 executor: &Arc<MultiThreadExecutor>,
16291 start_counter: &AtomicU64,
16292 claim: impl FnOnce() -> T + Send + 'static,
16293 ) -> T {
16294 use std::sync::mpsc;
16295
16296 let (claimed_tx, claimed_rx) = mpsc::channel();
16297 let (release_claim_tx, release_claim_rx) = mpsc::channel();
16298 let (started_tx, started_rx) = mpsc::channel();
16299 let (release_start_tx, release_start_rx) = mpsc::channel();
16300 let starts_before = start_counter.load(Ordering::SeqCst);
16301 let worker = std::thread::spawn(move || {
16302 MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
16303 *slot.borrow_mut() = Some(Box::new(move || {
16304 claimed_tx.send(()).unwrap();
16305 release_claim_rx.recv().unwrap();
16306 }));
16307 });
16308 MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
16309 *slot.borrow_mut() = Some(Box::new(move || {
16310 started_tx.send(()).unwrap();
16311 release_start_rx.recv().unwrap();
16312 }));
16313 });
16314 claim()
16315 });
16316 claimed_rx
16317 .recv_timeout(Duration::from_secs(2))
16318 .expect("the scheduler claim must pause after removing work");
16319
16320 let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16321 let verdict = executor.try_begin_deadlock_verdict();
16322 let claim_verdict_rejected = verdict.is_none();
16323 drop(verdict);
16324 release_claim_tx.send(()).unwrap();
16325
16326 started_rx
16327 .recv_timeout(Duration::from_secs(2))
16328 .expect("the scheduler claim must pause after publishing its start");
16329 let admission_active_after_start =
16330 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16331 let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
16332 let verdict = executor.try_begin_deadlock_verdict();
16333 let start_verdict_rejected = verdict.is_none();
16334 drop(verdict);
16335 release_start_tx.send(()).unwrap();
16336 let output = worker.join().unwrap();
16337
16338 assert!(
16339 admission_active,
16340 "removing scheduler work must retain a detector admission until start accounting"
16341 );
16342 assert!(
16343 claim_verdict_rejected,
16344 "a deadline verdict must not close while removed work has not published its start"
16345 );
16346 assert!(
16347 start_published,
16348 "work start accounting must publish before admission retires"
16349 );
16350 assert!(
16351 admission_active_after_start,
16352 "the detector admission must remain active until after start accounting"
16353 );
16354 assert!(
16355 start_verdict_rejected,
16356 "a deadline verdict must not close between start accounting and admission retirement"
16357 );
16358 output
16359 }
16360
16361 #[cfg(not(target_family = "wasm"))]
16362 #[test]
16363 fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
16364 let executor =
16365 multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
16366
16367 let fifo_ran = Arc::new(AtomicBool::new(false));
16368 let fifo_task_ran = Arc::clone(&fifo_ran);
16369 let (fifo, fifo_task) = async_task::spawn(
16370 async move {
16371 fifo_task_ran.store(true, Ordering::SeqCst);
16372 },
16373 |_| {},
16374 );
16375 executor.metrics.runnable_scheduled();
16376 executor.push_fifo_raw(fifo);
16377 let fifo_executor = Arc::clone(&executor);
16378 assert!(run_claim_with_deadlock_gate_assertion(
16379 &executor,
16380 &executor.metrics.runnable_polls,
16381 move || {
16382 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16383 fifo_executor.run_one_fifo_runnable()
16384 }
16385 ));
16386 futures::executor::block_on(fifo_task);
16387 assert!(fifo_ran.load(Ordering::SeqCst));
16388
16389 let fifo_order = Arc::new(Mutex::new(Vec::new()));
16390 let mut fifo_tasks = Vec::new();
16391 for name in ["first", "second"] {
16392 let task_order = Arc::clone(&fifo_order);
16393 let (runnable, task) = async_task::spawn(
16394 async move {
16395 task_order.lock().unwrap().push(name);
16396 },
16397 |_| {},
16398 );
16399 executor.metrics.runnable_scheduled();
16400 executor.push_fifo_raw(runnable);
16401 fifo_tasks.push(task);
16402 }
16403 for _ in 0..2 {
16404 let fifo_executor = Arc::clone(&executor);
16405 assert!(run_claim_with_deadlock_gate_assertion(
16406 &executor,
16407 &executor.metrics.runnable_polls,
16408 move || {
16409 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16410 fifo_executor.run_one_runnable()
16411 }
16412 ));
16413 }
16414 for task in fifo_tasks {
16415 futures::executor::block_on(task);
16416 }
16417 assert_eq!(
16418 *fifo_order.lock().unwrap(),
16419 ["first", "second"],
16420 "the detector-enabled normal fallback must preserve FIFO order"
16421 );
16422
16423 let lifo_ran = Arc::new(AtomicBool::new(false));
16424 let lifo_task_ran = Arc::clone(&lifo_ran);
16425 let (lifo, lifo_task) = async_task::spawn(
16426 async move {
16427 lifo_task_ran.store(true, Ordering::SeqCst);
16428 },
16429 |_| {},
16430 );
16431 executor.metrics.runnable_scheduled();
16432 let lifo_executor = Arc::clone(&executor);
16433 assert!(run_claim_with_deadlock_gate_assertion(
16434 &executor,
16435 &executor.metrics.runnable_polls,
16436 move || {
16437 let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
16438 LIFO_SLOT.with(|slot| {
16439 assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
16440 });
16441 lifo_executor.run_one_runnable()
16442 }
16443 ));
16444 futures::executor::block_on(lifo_task);
16445 assert!(lifo_ran.load(Ordering::SeqCst));
16446
16447 executor.begin_shutdown();
16448 executor.wait_until_scheduler_idle();
16449 }
16450
16451 #[cfg(not(target_family = "wasm"))]
16452 #[test]
16453 fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
16454 let executor =
16455 multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
16456
16457 let fifo_ran = Arc::new(AtomicBool::new(false));
16458 let fifo_job_ran = Arc::clone(&fifo_ran);
16459 executor.metrics.blocking_scheduled();
16460 executor
16461 .blocking_queue
16462 .lock()
16463 .unwrap()
16464 .push(QueuedBlocking {
16465 id: BlockingJobId(u64::MAX - 90),
16466 run: Box::new(move || {
16467 fifo_job_ran.store(true, Ordering::SeqCst);
16468 }),
16469 });
16470 let fifo_executor = Arc::clone(&executor);
16471 assert!(run_claim_with_deadlock_gate_assertion(
16472 &executor,
16473 &executor.metrics.blocking_tasks_started,
16474 move || {
16475 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16476 fifo_executor.run_one_blocking()
16477 }
16478 ));
16479 assert!(fifo_ran.load(Ordering::SeqCst));
16480
16481 let owner = executor.fresh_owner_token();
16482 let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
16483 let exact_job = BlockingJobId(u64::MAX - 91);
16484 let exact_ran = Arc::new(AtomicBool::new(false));
16485 let exact_job_ran = Arc::clone(&exact_ran);
16486 executor.metrics.blocking_scheduled();
16487 executor
16488 .blocking_queue
16489 .lock()
16490 .unwrap()
16491 .push(QueuedBlocking {
16492 id: exact_job,
16493 run: Box::new(move || {
16494 exact_job_ran.store(true, Ordering::SeqCst);
16495 }),
16496 });
16497 let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
16498 dependency.set_owned(BlockingDependency {
16499 job: exact_job,
16500 owner: Some(owner),
16501 });
16502 let exact_executor = Arc::clone(&executor);
16503 assert!(run_claim_with_deadlock_gate_assertion(
16504 &executor,
16505 &executor.metrics.blocking_tasks_started,
16506 move || {
16507 let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
16508 let _owner = BlockingOwnerGuard::enter(Some(owner));
16509 exact_executor.try_owned_blocking_over_cap(&dependency)
16510 }
16511 ));
16512 assert!(exact_ran.load(Ordering::SeqCst));
16513
16514 drop(active_owner);
16515 executor.begin_shutdown();
16516 executor.wait_until_scheduler_idle();
16517 }
16518
16519 #[cfg(not(target_family = "wasm"))]
16520 #[test]
16521 fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
16522 use std::sync::mpsc;
16530 use std::time::Duration;
16531
16532 let registry = Arc::new(ParkedDrivers::default());
16533
16534 let guard = registry.parked.lock().unwrap();
16536 let (done_tx, done_rx) = mpsc::channel();
16537 let waker_thread = {
16538 let registry = Arc::clone(®istry);
16539 std::thread::spawn(move || {
16540 done_tx.send(registry.wake_one()).unwrap();
16541 })
16542 };
16543 let woke = done_rx
16544 .recv_timeout(Duration::from_secs(5))
16545 .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
16546 assert!(!woke, "an empty registry has nobody to wake");
16547 waker_thread.join().unwrap();
16548 drop(guard);
16549
16550 let parker = Arc::new(DriverParker::default());
16554 registry.register(&parker);
16555 let guard = registry.parked.lock().unwrap();
16556 let (done_tx, done_rx) = mpsc::channel();
16557 let waker_thread = {
16558 let registry = Arc::clone(®istry);
16559 std::thread::spawn(move || {
16560 done_tx.send(registry.wake_one()).unwrap();
16561 })
16562 };
16563 assert!(
16564 done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16565 "wake_one with a registered parker must take the slow path (block on the held lock)"
16566 );
16567 drop(guard);
16568 let woke = done_rx
16569 .recv_timeout(Duration::from_secs(5))
16570 .expect("slow-path wake_one must complete once the lock is released");
16571 assert!(
16572 woke,
16573 "the slow path must pop and wake the registered parker"
16574 );
16575 waker_thread.join().unwrap();
16576 parker.park();
16579 assert_eq!(
16580 registry.count.load(Ordering::SeqCst),
16581 0,
16582 "wake_one deregisters what it pops"
16583 );
16584 }
16585
16586 #[cfg(not(target_family = "wasm"))]
16587 #[test]
16588 fn driver_parker_permit_granted_while_running_is_not_lost() {
16589 use std::sync::mpsc;
16593 use std::time::Duration;
16594
16595 let parker = Arc::new(DriverParker::default());
16596 parker.unpark();
16598 parker.unpark();
16599
16600 let (tx, rx) = mpsc::channel();
16601 let driver = {
16602 let parker = Arc::clone(&parker);
16603 std::thread::spawn(move || {
16604 parker.park(); tx.send(1).unwrap();
16606 parker.park(); tx.send(2).unwrap();
16608 })
16609 };
16610
16611 assert_eq!(
16612 rx.recv_timeout(Duration::from_secs(5))
16613 .expect("a stored permit must not be lost"),
16614 1
16615 );
16616 assert!(
16617 rx.recv_timeout(Duration::from_millis(300)).is_err(),
16618 "two pre-park wakes must coalesce into one permit (second park must sleep)"
16619 );
16620 parker.unpark();
16621 assert_eq!(
16622 rx.recv_timeout(Duration::from_secs(5))
16623 .expect("unpark must wake a sleeping driver"),
16624 2
16625 );
16626 driver.join().unwrap();
16627 }
16628
16629 #[cfg(not(target_family = "wasm"))]
16630 #[test]
16631 fn driver_parker_unrepresentable_timeout_remains_wakeable() {
16632 use std::sync::mpsc;
16633
16634 let parker = Arc::new(DriverParker::default());
16635 assert!(
16636 Instant::now().checked_add(Duration::MAX).is_none(),
16637 "Duration::MAX must exercise the unrepresentable-deadline path"
16638 );
16639 let (done_tx, done_rx) = mpsc::channel();
16640 let driver = {
16641 let parker = Arc::clone(&parker);
16642 std::thread::spawn(move || {
16643 done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
16644 })
16645 };
16646 wait_until("the unbounded timeout driver to sleep", || {
16647 parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
16648 });
16649 assert!(
16650 done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
16651 "an unrepresentable timeout must not expire immediately"
16652 );
16653
16654 let unparker = {
16655 let parker = Arc::clone(&parker);
16656 std::thread::spawn(move || parker.unpark())
16657 };
16658 assert!(
16659 done_rx
16660 .recv_timeout(Duration::from_secs(1))
16661 .expect("an unrepresentable timeout must remain wakeable"),
16662 "a permit must be reported as a wake"
16663 );
16664 unparker.join().unwrap();
16665 driver.join().unwrap();
16666 }
16667
16668 #[cfg(not(target_family = "wasm"))]
16669 #[test]
16670 fn parked_drivers_wake_one_wakes_exactly_one() {
16671 use std::sync::mpsc;
16675 use std::time::Duration;
16676
16677 let registry = Arc::new(ParkedDrivers::default());
16678 let (woke_tx, woke_rx) = mpsc::channel();
16679 let mut drivers = Vec::new();
16680 for index in 0..2 {
16681 let registry = Arc::clone(®istry);
16682 let woke_tx = woke_tx.clone();
16683 drivers.push(std::thread::spawn(move || {
16684 let parker = Arc::new(DriverParker::default());
16685 registry.register(&parker);
16686 parker.park();
16687 registry.deregister(&parker);
16688 woke_tx.send(index).unwrap();
16689 }));
16690 }
16691 wait_until("both drivers registered as parked", || {
16692 registry.count.load(Ordering::SeqCst) == 2
16693 });
16694
16695 assert!(registry.wake_one(), "wake_one must wake a parked driver");
16696 woke_rx
16697 .recv_timeout(Duration::from_secs(5))
16698 .expect("exactly one driver must wake");
16699 assert!(
16700 woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16701 "the second driver must stay parked after a single wake_one"
16702 );
16703 assert_eq!(
16704 registry.count.load(Ordering::SeqCst),
16705 1,
16706 "one driver must remain registered"
16707 );
16708
16709 assert!(
16710 registry.wake_one(),
16711 "a second wake_one must wake the remaining driver"
16712 );
16713 woke_rx
16714 .recv_timeout(Duration::from_secs(5))
16715 .expect("the second driver must wake");
16716 for driver in drivers {
16717 driver.join().unwrap();
16718 }
16719 }
16720
16721 #[cfg(not(target_family = "wasm"))]
16722 #[test]
16723 fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
16724 use std::sync::mpsc;
16725
16726 struct ReenterRegistryOnDrop {
16727 registry: Arc<ParkedDrivers>,
16728 dropped: mpsc::Sender<()>,
16729 }
16730
16731 #[expect(
16732 clippy::manual_noop_waker,
16733 reason = "the test observes the waker payload's Drop"
16734 )]
16735 impl std::task::Wake for ReenterRegistryOnDrop {
16736 fn wake(self: Arc<Self>) {}
16737 }
16738
16739 impl Drop for ReenterRegistryOnDrop {
16740 fn drop(&mut self) {
16741 let parker = Arc::new(DriverParker::default());
16742 self.registry.register(&parker);
16743 self.registry.deregister(&parker);
16744 self.dropped.send(()).unwrap();
16745 }
16746 }
16747
16748 let registry = Arc::new(ParkedDrivers::default());
16749 let dependency = Arc::new(TaskDependency::default());
16750 let (dropped_tx, dropped_rx) = mpsc::channel();
16751 let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
16752 registry: Arc::clone(®istry),
16753 dropped: dropped_tx,
16754 }));
16755 dependency.register_waiter(&waiter);
16756 drop(waiter);
16757
16758 let parker = Arc::new(DriverParker::default());
16759 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16760 drop(dependency);
16761
16762 let retiring_registry = Arc::clone(®istry);
16763 let retiring_parker = Arc::clone(&parker);
16764 let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
16765 dropped_rx
16766 .recv_timeout(Duration::from_secs(1))
16767 .expect("retained dependency waiters must be destroyed after releasing the registry lock");
16768 retiring.join().unwrap();
16769 }
16770
16771 #[cfg(not(target_family = "wasm"))]
16772 #[test]
16773 fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
16774 use std::sync::mpsc;
16775
16776 let registry = Arc::new(ParkedDrivers::default());
16777 let owner = BlockingOwnerToken {
16778 executor_id: u64::MAX - 70,
16779 frame: u64::MAX - 71,
16780 };
16781 let dependency = Arc::new(TaskDependency::default());
16782 dependency.set_owned(BlockingDependency {
16783 job: BlockingJobId(u64::MAX - 72),
16784 owner: Some(owner),
16785 });
16786 let parker = Arc::new(DriverParker::default());
16787 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16788
16789 let (predicate_tx, predicate_rx) = mpsc::channel();
16790 let (release_tx, release_rx) = mpsc::channel();
16791 let selecting_registry = Arc::clone(®istry);
16792 let selector = std::thread::spawn(move || {
16793 DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
16794 *slot.borrow_mut() = Some(Box::new(move || {
16795 predicate_tx.send(()).unwrap();
16796 release_rx.recv().unwrap();
16797 }));
16798 });
16799 selecting_registry.wake_one_owner_dependency(owner)
16800 });
16801
16802 predicate_rx
16803 .recv_timeout(Duration::from_secs(2))
16804 .expect("owner selection must reach the dependency predicate");
16805 let registry_was_unlocked = registry.parked.try_lock().is_ok();
16806 release_tx.send(()).unwrap();
16807 assert!(
16808 selector.join().unwrap(),
16809 "the exact-owner parker must still be selected"
16810 );
16811
16812 assert!(
16813 registry_was_unlocked,
16814 "owner dependency predicates must not run while the parked-driver registry is locked"
16815 );
16816 assert!(parker.consume_permit());
16817 assert_eq!(parker.take_owner_handoff(), Some(owner));
16818 }
16819
16820 #[cfg(not(target_family = "wasm"))]
16821 #[test]
16822 fn owner_handoff_skips_newer_untagged_dependency() {
16823 let registry = ParkedDrivers::default();
16824 let owner = BlockingOwnerToken {
16825 executor_id: u64::MAX - 1,
16826 frame: u64::MAX - 2,
16827 };
16828 let owned_dependency = Arc::new(TaskDependency::default());
16829 owned_dependency.set_owned(BlockingDependency {
16830 job: BlockingJobId(u64::MAX - 3),
16831 owner: Some(owner),
16832 });
16833 let untagged_dependency = Arc::new(TaskDependency::default());
16834 untagged_dependency.set_owned(BlockingDependency {
16835 job: BlockingJobId(u64::MAX - 4),
16836 owner: None,
16837 });
16838 let owned = Arc::new(DriverParker::default());
16839 let untagged = Arc::new(DriverParker::default());
16840
16841 registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
16842 registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
16845
16846 assert!(registry.wake_one_owner_dependency(owner));
16847 assert!(
16848 owned.consume_permit(),
16849 "the exact owner dependency must receive the retry"
16850 );
16851 assert!(
16852 !untagged.consume_permit(),
16853 "an untagged dependency must not consume an exact owner handoff"
16854 );
16855 registry.deregister(&untagged);
16856 }
16857
16858 #[cfg(not(target_family = "wasm"))]
16859 #[test]
16860 fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
16861 let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
16862 let owner = executor.fresh_owner_token();
16863 let valid_dependency = Arc::new(TaskDependency::default());
16864 valid_dependency.set_owned(BlockingDependency {
16865 job: BlockingJobId(u64::MAX - 45),
16866 owner: Some(owner),
16867 });
16868 let withdrawn_dependency = Arc::new(TaskDependency::default());
16869 withdrawn_dependency.set_owned(BlockingDependency {
16870 job: BlockingJobId(u64::MAX - 46),
16871 owner: Some(owner),
16872 });
16873 let valid = Arc::new(DriverParker::default());
16874 let withdrawn = Arc::new(DriverParker::default());
16875
16876 executor
16877 .parked_drivers
16878 .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
16879 executor.parked_drivers.register_with_role(
16880 &withdrawn,
16881 true,
16882 Some(Arc::clone(&withdrawn_dependency)),
16883 );
16884 assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
16885 withdrawn_dependency.clear();
16886
16887 assert_eq!(
16888 executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
16889 Some(false),
16890 "a selected dependency withdrawn before retry must forward the owner handoff"
16891 );
16892 assert!(
16893 valid.consume_permit(),
16894 "the next live same-owner dependency must receive the forwarded wake"
16895 );
16896 assert_eq!(
16897 valid.take_owner_handoff(),
16898 Some(owner),
16899 "the forwarded wake must retain the exact owner identity"
16900 );
16901 }
16902
16903 #[cfg(not(target_family = "wasm"))]
16904 #[test]
16905 fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
16906 use std::sync::mpsc;
16907
16908 let executor = multi_thread_executor(2, None, "owner-handoff-publication");
16909 let owner = executor.fresh_owner_token();
16910 let fallback_dependency = Arc::new(TaskDependency::default());
16911 fallback_dependency.set_owned(BlockingDependency {
16912 job: BlockingJobId(u64::MAX - 47),
16913 owner: Some(owner),
16914 });
16915 let exiting_dependency = Arc::new(TaskDependency::default());
16916 exiting_dependency.set_owned(BlockingDependency {
16917 job: BlockingJobId(u64::MAX - 48),
16918 owner: Some(owner),
16919 });
16920 let fallback = Arc::new(DriverParker::default());
16921 let exiting = Arc::new(DriverParker::default());
16922
16923 executor.parked_drivers.register_with_role(
16924 &fallback,
16925 true,
16926 Some(Arc::clone(&fallback_dependency)),
16927 );
16928 executor.parked_drivers.register_with_role(
16929 &exiting,
16930 true,
16931 Some(Arc::clone(&exiting_dependency)),
16932 );
16933
16934 let (selected_tx, selected_rx) = mpsc::channel();
16935 let (release_selection_tx, release_selection_rx) = mpsc::channel();
16936 let selector_executor = Arc::clone(&executor);
16937 let selector = std::thread::spawn(move || {
16938 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
16939 *slot.borrow_mut() = Some(Box::new(move || {
16940 selected_tx.send(()).unwrap();
16941 release_selection_rx.recv().unwrap();
16942 }));
16943 });
16944 assert!(
16945 selector_executor
16946 .parked_drivers
16947 .wake_one_owner_dependency(owner)
16948 );
16949 });
16950 selected_rx
16951 .recv_timeout(Duration::from_secs(2))
16952 .expect("selection must pause after removing the exact-owner parker");
16953
16954 let (exited_tx, exited_rx) = mpsc::channel();
16955 let exiting_executor = Arc::clone(&executor);
16956 let exiting_driver = Arc::clone(&exiting);
16957 let driver = std::thread::spawn(move || {
16958 exiting_driver.park();
16959 exiting_executor.parked_drivers.deregister(&exiting_driver);
16960 exiting_executor.forward_owner_handoff(&exiting_driver);
16961 exited_tx.send(()).unwrap();
16962 });
16963 exiting.unpark();
16964 exited_rx
16965 .recv_timeout(Duration::from_secs(2))
16966 .expect("the direct future wake must let the selected driver exit");
16967
16968 release_selection_tx.send(()).unwrap();
16969 selector.join().unwrap();
16970 driver.join().unwrap();
16971
16972 assert!(
16973 fallback.consume_permit(),
16974 "an exiting selected driver must forward the owner handoff to the next dependency"
16975 );
16976 assert_eq!(
16977 fallback.take_owner_handoff(),
16978 Some(owner),
16979 "the forwarded handoff must retain the exact owner identity"
16980 );
16981 }
16982
16983 #[cfg(not(target_family = "wasm"))]
16984 #[test]
16985 fn cooperative_panic_forwards_pending_owner_handoff() {
16986 use std::sync::mpsc;
16987
16988 struct PanicAfterWake {
16989 dependency: BlockingDependency,
16990 first_poll: Option<mpsc::Sender<()>>,
16991 }
16992
16993 impl Future for PanicAfterWake {
16994 type Output = ();
16995
16996 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16997 if let Some(first_poll) = self.first_poll.take() {
16998 let mut published = PublishedBlockingDependencies::default();
17001 record_blocking_dependency(&mut published, self.dependency);
17002 first_poll.send(()).unwrap();
17003 return Poll::Pending;
17004 }
17005 panic!("intentional cooperative driver panic after owner handoff");
17006 }
17007 }
17008
17009 let executor = multi_thread_executor(2, None, "panic-owner-handoff");
17010 let owner = executor.fresh_owner_token();
17011 let fallback_dependency = Arc::new(TaskDependency::default());
17012 fallback_dependency.set_owned(BlockingDependency {
17013 job: BlockingJobId(u64::MAX - 49),
17014 owner: Some(owner),
17015 });
17016 let fallback = Arc::new(DriverParker::default());
17017 executor.parked_drivers.register_with_role(
17018 &fallback,
17019 true,
17020 Some(Arc::clone(&fallback_dependency)),
17021 );
17022
17023 let (first_poll_tx, first_poll_rx) = mpsc::channel();
17024 let (panicked_tx, panicked_rx) = mpsc::channel();
17025 let driver_executor = Arc::clone(&executor);
17026 let driver = std::thread::spawn(move || {
17027 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17028 let mut future = std::pin::pin!(PanicAfterWake {
17029 dependency: BlockingDependency {
17030 job: BlockingJobId(u64::MAX - 50),
17031 owner: Some(owner)
17032 },
17033 first_poll: Some(first_poll_tx),
17034 });
17035 let panicked = catch_unwind(AssertUnwindSafe(|| {
17036 driver_executor.block_on(future.as_mut());
17037 }))
17038 .is_err();
17039 panicked_tx.send(panicked).unwrap();
17040 });
17041
17042 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17043 wait_until("the panicking cooperative driver to park", || {
17044 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17045 });
17046 assert!(
17047 executor.parked_drivers.wake_one_owner_dependency(owner),
17048 "the exact owner handoff must select the cooperative driver"
17049 );
17050 assert!(
17051 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17052 "the test future must unwind from its second poll"
17053 );
17054 driver.join().unwrap();
17055
17056 assert!(
17057 fallback.consume_permit(),
17058 "panic cleanup must forward the unconsumed owner handoff"
17059 );
17060 assert_eq!(
17061 fallback.take_owner_handoff(),
17062 Some(owner),
17063 "panic cleanup must preserve the exact owner identity"
17064 );
17065 }
17066
17067 #[cfg(not(target_family = "wasm"))]
17068 #[test]
17069 fn cooperative_panic_flushes_lifo_slot() {
17070 struct ScheduleThenPanic {
17071 executor: Arc<MultiThreadExecutor>,
17072 ran: Arc<AtomicBool>,
17073 }
17074
17075 impl Future for ScheduleThenPanic {
17076 type Output = ();
17077
17078 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17079 let ran = Arc::clone(&self.ran);
17080 let scheduler = Arc::clone(&self.executor);
17081 let (runnable, task) = async_task::spawn(
17082 async move {
17083 ran.store(true, Ordering::SeqCst);
17084 },
17085 move |runnable| scheduler.schedule(runnable),
17086 );
17087 self.executor.schedule(runnable);
17088 task.detach();
17089 panic!("intentional cooperative driver panic with a populated LIFO slot");
17090 }
17091 }
17092
17093 let executor = multi_thread_executor(2, None, "panic-lifo-flush");
17094 executor
17095 .active_drainers
17096 .store(executor.max_drainers, Ordering::SeqCst);
17097 let ran = Arc::new(AtomicBool::new(false));
17098 let panicked = {
17099 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17100 let mut future = std::pin::pin!(ScheduleThenPanic {
17101 executor: Arc::clone(&executor),
17102 ran: Arc::clone(&ran),
17103 });
17104 catch_unwind(AssertUnwindSafe(|| {
17105 executor.block_on(future.as_mut());
17106 }))
17107 .is_err()
17108 };
17109
17110 assert!(panicked, "the test future must unwind from its first poll");
17111 assert!(
17112 executor.pop_lifo_slot().is_none(),
17113 "panic cleanup must empty the LIFO slot"
17114 );
17115 assert_eq!(
17116 executor.injector.len(),
17117 1,
17118 "panic cleanup must move the slot runnable to the shared FIFO"
17119 );
17120 assert!(
17121 executor.run_one(),
17122 "the flushed runnable must remain executable"
17123 );
17124 assert!(ran.load(Ordering::SeqCst));
17125 executor.active_drainers.store(0, Ordering::SeqCst);
17126 }
17127
17128 #[cfg(not(target_family = "wasm"))]
17129 #[test]
17130 fn cooperative_panic_compensates_absorbed_queue_wake() {
17131 use std::sync::mpsc;
17132
17133 struct PanicAfterWake {
17134 first_poll: Option<mpsc::Sender<()>>,
17135 }
17136
17137 impl Future for PanicAfterWake {
17138 type Output = ();
17139
17140 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17141 if let Some(first_poll) = self.first_poll.take() {
17142 first_poll.send(()).unwrap();
17143 return Poll::Pending;
17144 }
17145 panic!("intentional cooperative driver panic after a queue wake");
17146 }
17147 }
17148
17149 let executor = multi_thread_executor(2, None, "panic-wake-compensation");
17150 executor
17151 .active_drainers
17152 .store(executor.max_drainers, Ordering::SeqCst);
17153 let fallback = Arc::new(DriverParker::default());
17154 executor.parked_drivers.register(&fallback);
17155 let (first_poll_tx, first_poll_rx) = mpsc::channel();
17156 let (panicked_tx, panicked_rx) = mpsc::channel();
17157 let driver_executor = Arc::clone(&executor);
17158 let driver = std::thread::spawn(move || {
17159 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17160 let mut future = std::pin::pin!(PanicAfterWake {
17161 first_poll: Some(first_poll_tx)
17162 });
17163 let panicked = catch_unwind(AssertUnwindSafe(|| {
17164 driver_executor.block_on(future.as_mut());
17165 }))
17166 .is_err();
17167 panicked_tx.send(panicked).unwrap();
17168 });
17169
17170 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17171 wait_until(
17172 "the panicking cooperative driver to park after the fallback",
17173 || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
17174 );
17175
17176 let ran = Arc::new(AtomicBool::new(false));
17177 let ran_task = Arc::clone(&ran);
17178 let scheduler = Arc::clone(&executor);
17179 let (runnable, task) = async_task::spawn(
17180 async move {
17181 ran_task.store(true, Ordering::SeqCst);
17182 },
17183 move |runnable| scheduler.schedule(runnable),
17184 );
17185 task.detach();
17186 executor.metrics.runnable_scheduled();
17187 executor.push_fifo_raw(runnable);
17188 assert!(
17189 executor.parked_drivers.wake_one(),
17190 "the queued runnable's wake must be absorbed by the cooperative driver"
17191 );
17192
17193 assert!(
17194 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17195 "the test future must unwind after absorbing the queue wake"
17196 );
17197 driver.join().unwrap();
17198 assert!(
17199 fallback.consume_permit(),
17200 "panic cleanup must compensate the absorbed queue wake to the fallback driver"
17201 );
17202 assert!(
17203 executor.run_one(),
17204 "the queued runnable must remain executable"
17205 );
17206 assert!(ran.load(Ordering::SeqCst));
17207 executor.active_drainers.store(0, Ordering::SeqCst);
17208 }
17209
17210 #[cfg(not(target_family = "wasm"))]
17211 #[test]
17212 fn multi_thread_future_wake_targets_its_own_parked_driver() {
17213 use std::sync::mpsc;
17221 use std::time::Duration;
17222
17223 let (done_tx, done_rx) = mpsc::channel();
17224 let runner = std::thread::spawn(move || {
17225 let metrics = Arc::new(RuntimeMetrics::default());
17226 let options = RuntimeOptions {
17227 flavor: RuntimeFlavor::MultiThread,
17228 worker_threads: 2,
17229 max_blocking_tasks: 2,
17230 thread_name_prefix: "wake-target".to_string(),
17231 park_deadline: None,
17232 drain_linger: DEFAULT_DRAIN_LINGER,
17233 };
17234 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17235
17236 let (tx1, rx1) = oneshot::channel::<usize>();
17237 let (tx2, rx2) = oneshot::channel::<usize>();
17238 let t1_done = Arc::new(AtomicBool::new(false));
17239 let t2_done = Arc::new(AtomicBool::new(false));
17240
17241 let t1_exec = Arc::clone(&executor);
17243 let t1_flag = Arc::clone(&t1_done);
17244 let t1_body = async move {
17245 let mut inner = std::pin::pin!(async move {
17246 assert_eq!(rx1.await.unwrap(), 7usize);
17247 });
17248 t1_exec.block_on(inner.as_mut());
17249 t1_flag.store(true, Ordering::SeqCst);
17250 };
17251 let sched1 = Arc::clone(&executor);
17252 let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
17253 executor.schedule(runnable1);
17254 wait_until("driver 1 parked", || {
17255 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17256 });
17257
17258 let t2_exec = Arc::clone(&executor);
17262 let t2_flag = Arc::clone(&t2_done);
17263 let t2_body = async move {
17264 let mut inner = std::pin::pin!(async move {
17265 assert_eq!(rx2.await.unwrap(), 9usize);
17266 });
17267 t2_exec.block_on(inner.as_mut());
17268 t2_flag.store(true, Ordering::SeqCst);
17269 };
17270 let sched2 = Arc::clone(&executor);
17271 let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
17272 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(runnable2);
17274 executor.ensure_drainer();
17275 wait_until("driver 2 parked", || {
17276 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17277 });
17278
17279 tx1.send(7usize).unwrap();
17282 wait_until("T1 completed via its own driver's wake", || {
17283 t1_done.load(Ordering::SeqCst)
17284 });
17285 futures::executor::block_on(task1);
17286 assert!(
17287 !t2_done.load(Ordering::SeqCst),
17288 "D2 must still be parked: its future was never completed"
17289 );
17290 wait_until(
17307 "the retiring drain frame registered its linger parker",
17308 || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
17309 );
17310 wait_until("the lingering drainer's idle budget expired", || {
17311 executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
17312 });
17313 assert_eq!(
17314 executor.parked_drivers.count.load(Ordering::SeqCst),
17315 1,
17316 "exactly the untargeted driver must remain parked after the linger expired"
17317 );
17318 assert!(
17319 !t2_done.load(Ordering::SeqCst),
17320 "D2 must still be parked after the lingering drainer expired"
17321 );
17322
17323 tx2.send(9usize).unwrap();
17325 futures::executor::block_on(task2);
17326 assert!(t2_done.load(Ordering::SeqCst));
17327 let _ = done_tx.send(());
17328 });
17329
17330 match done_rx.recv_timeout(Duration::from_secs(10)) {
17331 Ok(()) => runner.join().unwrap(),
17332 Err(error) => panic!(
17333 "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
17334 ),
17335 }
17336 }
17337
17338 #[cfg(not(target_family = "wasm"))]
17339 #[test]
17340 fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
17341 use std::sync::mpsc;
17349 use std::time::Duration;
17350
17351 let (done_tx, done_rx) = mpsc::channel();
17352 let runner = std::thread::spawn(move || {
17353 let metrics = Arc::new(RuntimeMetrics::default());
17354 let options = RuntimeOptions {
17355 flavor: RuntimeFlavor::MultiThread,
17356 worker_threads: 1,
17357 max_blocking_tasks: 1,
17358 thread_name_prefix: "wake-compensate".to_string(),
17359 park_deadline: None,
17360 drain_linger: DEFAULT_DRAIN_LINGER,
17361 };
17362 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17363
17364 let (gate_tx, gate_rx) = oneshot::channel::<()>();
17366 let td_exec = Arc::clone(&executor);
17367 let td_body = async move {
17368 let mut inner = std::pin::pin!(async move {
17369 gate_rx.await.unwrap();
17370 });
17371 td_exec.block_on(inner.as_mut());
17372 };
17373 let sched = Arc::clone(&executor);
17374 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
17375 executor.schedule(td_runnable);
17376 wait_until("the sole driver parked", || {
17377 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17378 });
17379
17380 let (ran_tx, ran_rx) = mpsc::channel::<()>();
17383 let sched_stranded = Arc::clone(&executor);
17384 let (stranded_runnable, stranded_task) = async_task::spawn(
17385 async move {
17386 ran_tx.send(()).unwrap();
17387 },
17388 move |r| sched_stranded.schedule(r),
17389 );
17390 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(stranded_runnable);
17392 stranded_task.detach();
17393
17394 {
17399 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17400 let mut ready = std::pin::pin!(async {});
17401 executor.block_on(ready.as_mut());
17402 }
17403
17404 ran_rx.recv_timeout(Duration::from_secs(5)).expect(
17405 "exit compensation must wake the parked driver to run the stranded queued runnable",
17406 );
17407
17408 gate_tx.send(()).unwrap();
17410 futures::executor::block_on(td_task);
17411 let _ = done_tx.send(());
17412 });
17413
17414 match done_rx.recv_timeout(Duration::from_secs(10)) {
17415 Ok(()) => runner.join().unwrap(),
17416 Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
17417 }
17418 }
17419
17420 #[cfg(not(target_family = "wasm"))]
17421 #[test]
17422 fn cooperative_exit_compensates_blocking_only_residue() {
17423 use std::sync::mpsc;
17424
17425 struct Controlled {
17426 ready: Arc<AtomicBool>,
17427 entered: Option<mpsc::Sender<()>>,
17428 }
17429
17430 impl Future for Controlled {
17431 type Output = ();
17432
17433 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17434 if let Some(entered) = self.entered.take() {
17435 entered.send(()).unwrap();
17436 }
17437 if self.ready.load(Ordering::SeqCst) {
17438 Poll::Ready(())
17439 } else {
17440 Poll::Pending
17441 }
17442 }
17443 }
17444
17445 let options = RuntimeOptions {
17446 flavor: RuntimeFlavor::MultiThread,
17447 worker_threads: 2,
17448 max_blocking_tasks: 1,
17449 thread_name_prefix: "blocking-exit-compensation".to_string(),
17450 park_deadline: None,
17451 drain_linger: DEFAULT_DRAIN_LINGER,
17452 };
17453 let executor =
17454 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17455 let ready = Arc::new(AtomicBool::new(false));
17456 let (entered_tx, entered_rx) = mpsc::channel();
17457 let (returned_tx, returned_rx) = mpsc::channel();
17458 let (release_tx, release_rx) = mpsc::channel();
17459 let task_exec = Arc::clone(&executor);
17460 let task_ready = Arc::clone(&ready);
17461 let scheduler = Arc::clone(&executor);
17462 let (runnable, task) = async_task::spawn(
17463 async move {
17464 let mut future = std::pin::pin!(Controlled {
17465 ready: task_ready,
17466 entered: Some(entered_tx)
17467 });
17468 task_exec.block_on(future.as_mut());
17469 returned_tx.send(()).unwrap();
17470 release_rx.recv().unwrap();
17471 },
17472 move |runnable| scheduler.schedule(runnable),
17473 );
17474 executor.schedule(runnable);
17475 entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
17476
17477 wait_until("the cooperative frame parked", || {
17487 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17488 });
17489
17490 let timer_exec = Arc::clone(&executor);
17491 let timer_scheduler = Arc::clone(&executor);
17492 let (timer_runnable, timer_task) = async_task::spawn(
17493 async move {
17494 heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
17495 },
17496 move |runnable| timer_scheduler.schedule(runnable),
17497 );
17498 executor.schedule(timer_runnable);
17499 wait_until("the dedicated timekeeper parked", || {
17500 executor
17501 .timers
17502 .inner
17503 .lock()
17504 .unwrap()
17505 .timekeeper_parker
17506 .is_some()
17507 });
17508 wait_until(
17513 "the cooperative frame re-parked in its timer-bounded park",
17514 || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
17515 );
17516
17517 ready.store(true, Ordering::SeqCst);
17518 let (blocking_tx, blocking_rx) = mpsc::channel();
17519 let blocking = executor.schedule_blocking(move || {
17520 blocking_tx.send(()).unwrap();
17521 });
17522 blocking_rx
17525 .recv_timeout(Duration::from_secs(10))
17526 .expect("blocking-only residue must be serviced by a Rayon worker");
17527 returned_rx
17528 .recv_timeout(Duration::from_secs(10))
17529 .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
17530 release_tx.send(()).unwrap();
17531 futures::executor::block_on(task);
17532 futures::executor::block_on(blocking).unwrap();
17533 executor.shutdown_timers();
17534 futures::executor::block_on(timer_task);
17535 }
17536
17537 #[cfg(not(target_family = "wasm"))]
17538 #[test]
17539 fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
17540 let options = RuntimeOptions {
17541 flavor: RuntimeFlavor::MultiThread,
17542 worker_threads: 2,
17543 max_blocking_tasks: 1,
17544 thread_name_prefix: "stopped-exit-compensation".to_string(),
17545 park_deadline: None,
17546 drain_linger: DEFAULT_DRAIN_LINGER,
17547 };
17548 let executor =
17549 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17550 let ran = Arc::new(AtomicBool::new(false));
17551 let blocking_ran = Arc::clone(&ran);
17552
17553 executor
17557 .active_drainers
17558 .store(executor.max_drainers, Ordering::SeqCst);
17559 let blocking = executor.schedule_blocking(move || {
17560 blocking_ran.store(true, Ordering::SeqCst);
17561 });
17562 assert!(!executor.blocking_queue.lock().unwrap().is_empty());
17563
17564 executor.stop.begin_shutdown();
17565 executor.compensate_queued_work_wake(true);
17566 let ran_after_stop = ran.load(Ordering::SeqCst);
17567
17568 executor.active_drainers.store(0, Ordering::SeqCst);
17569 executor.begin_shutdown();
17570 let blocking_result = futures::executor::block_on(blocking);
17571
17572 assert!(
17573 !ran_after_stop,
17574 "exit compensation started queued blocking work after stop"
17575 );
17576 assert!(
17577 blocking_result.is_err(),
17578 "shutdown must cancel the queued blocking job"
17579 );
17580 }
17581
17582 #[cfg(not(target_family = "wasm"))]
17583 #[test]
17584 fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
17585 use std::sync::mpsc;
17594 use std::time::Duration;
17595
17596 const ROUNDS: usize = 20_000;
17597
17598 let (done_tx, done_rx) = mpsc::channel();
17599 let registry = Arc::new(ParkedDrivers::default());
17600 let work = Arc::new(AtomicBool::new(false));
17601
17602 let consumer = {
17603 let registry = Arc::clone(®istry);
17604 let work = Arc::clone(&work);
17605 std::thread::spawn(move || {
17606 let parker = Arc::new(DriverParker::default());
17607 for _ in 0..ROUNDS {
17608 loop {
17609 if work.swap(false, Ordering::SeqCst) {
17610 break;
17611 }
17612 registry.register(&parker);
17615 if work.load(Ordering::SeqCst) {
17616 registry.deregister(&parker);
17617 continue;
17618 }
17619 parker.park();
17620 registry.deregister(&parker);
17621 }
17622 }
17623 done_tx.send(()).unwrap();
17624 })
17625 };
17626
17627 for _ in 0..ROUNDS {
17628 work.store(true, Ordering::SeqCst);
17629 let _ = registry.wake_one();
17633 while work.load(Ordering::SeqCst) {
17634 std::hint::spin_loop();
17635 }
17636 }
17637
17638 done_rx
17639 .recv_timeout(Duration::from_secs(30))
17640 .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
17641 consumer.join().unwrap();
17642 }
17643
17644 #[cfg(not(target_family = "wasm"))]
17645 #[test]
17646 fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
17647 let options = RuntimeOptions {
17651 flavor: RuntimeFlavor::MultiThread,
17652 worker_threads: 2,
17653 max_blocking_tasks: 2,
17654 thread_name_prefix: "lifo-order".to_string(),
17655 park_deadline: None,
17656 drain_linger: DEFAULT_DRAIN_LINGER,
17657 };
17658 let executor =
17659 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17660 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17661
17662 let old_order = Arc::clone(&order);
17665 let sched = Arc::clone(&executor);
17666 let (old_runnable, old_task) = async_task::spawn(
17667 async move {
17668 old_order.lock().unwrap().push("old");
17669 },
17670 move |r| sched.schedule(r),
17671 );
17672 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(old_runnable);
17674 old_task.detach();
17675
17676 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17679 let new_order = Arc::clone(&order);
17680 let sched = Arc::clone(&executor);
17681 let (new_runnable, new_task) = async_task::spawn(
17682 async move {
17683 new_order.lock().unwrap().push("new");
17684 },
17685 move |r| sched.schedule(r),
17686 );
17687 executor.schedule(new_runnable);
17688 new_task.detach();
17689 assert_eq!(
17690 executor.injector.len(),
17691 1,
17692 "the slot-path schedule must not push to the shared FIFO"
17693 );
17694
17695 assert!(executor.run_one(), "run_one must pop the slot first");
17696 assert!(executor.run_one(), "run_one must then pop the FIFO");
17697 assert!(!executor.run_one(), "no work must remain");
17698 assert_eq!(
17699 *order.lock().unwrap(),
17700 vec!["new", "old"],
17701 "the slot runnable (newest) must run before older FIFO work"
17702 );
17703 }
17704
17705 #[cfg(not(target_family = "wasm"))]
17706 #[test]
17707 fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
17708 let options = RuntimeOptions {
17709 flavor: RuntimeFlavor::MultiThread,
17710 worker_threads: 2,
17711 max_blocking_tasks: 1,
17712 thread_name_prefix: "fifo-owner".to_string(),
17713 park_deadline: None,
17714 drain_linger: DEFAULT_DRAIN_LINGER,
17715 };
17716 let executor =
17717 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17718 let observed_owner = Arc::new(Mutex::new(None));
17719 let observed = Arc::clone(&observed_owner);
17720 let (runnable, task) = async_task::spawn(
17721 async move {
17722 *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
17723 },
17724 |_| {},
17725 );
17726 task.detach();
17727 executor.metrics.runnable_scheduled();
17728 executor.push_fifo_raw(runnable);
17729 executor.active_drainers.store(1, Ordering::Release);
17730
17731 let owner = executor.fresh_owner_token();
17732 let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
17733 Arc::clone(&executor).drain();
17734
17735 assert_eq!(
17736 *observed_owner.lock().unwrap(),
17737 Some(None),
17738 "FIFO runnables must not borrow a blocking closure's over-cap privilege"
17739 );
17740 assert_eq!(
17741 BLOCKING_OWNER.with(std::cell::Cell::get),
17742 Some(owner),
17743 "the ambient blocking owner must be restored after the runnable"
17744 );
17745 }
17746
17747 #[cfg(not(target_family = "wasm"))]
17748 #[test]
17749 fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
17750 use std::sync::mpsc;
17754 use std::time::Duration;
17755
17756 let (done_tx, done_rx) = mpsc::channel();
17757 let runner = std::thread::spawn(move || {
17758 let options = RuntimeOptions {
17759 flavor: RuntimeFlavor::MultiThread,
17760 worker_threads: 1,
17761 max_blocking_tasks: 1,
17762 thread_name_prefix: "lifo-drain".to_string(),
17763 park_deadline: None,
17764 drain_linger: DEFAULT_DRAIN_LINGER,
17765 };
17766 let executor =
17767 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17768 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17769
17770 let p_exec = Arc::clone(&executor);
17771 let p_order = Arc::clone(&order);
17772 let p_body = async move {
17773 p_order.lock().unwrap().push("P");
17774 let c_order = Arc::clone(&p_order);
17776 let sched = Arc::clone(&p_exec);
17777 let (c_runnable, c_task) = async_task::spawn(
17778 async move {
17779 c_order.lock().unwrap().push("C");
17780 },
17781 move |r| sched.schedule(r),
17782 );
17783 p_exec.schedule(c_runnable);
17784 c_task.detach();
17785 };
17786
17787 let mut tasks = Vec::new();
17791 let sched = Arc::clone(&executor);
17792 let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
17793 executor.metrics.runnable_scheduled();
17794 executor.push_fifo_raw(p_runnable);
17795 tasks.push(p_task);
17796 for name in ["T1", "T2"] {
17797 let t_order = Arc::clone(&order);
17798 let sched = Arc::clone(&executor);
17799 let (t_runnable, t_task) = async_task::spawn(
17800 async move {
17801 t_order.lock().unwrap().push(name);
17802 },
17803 move |r| sched.schedule(r),
17804 );
17805 executor.metrics.runnable_scheduled();
17806 executor.push_fifo_raw(t_runnable);
17807 tasks.push(t_task);
17808 }
17809 executor.ensure_drainer();
17810
17811 for task in tasks {
17812 futures::executor::block_on(task);
17813 }
17814 wait_until("the detached child C ran", || {
17815 order.lock().unwrap().len() == 4
17816 });
17817 assert_eq!(
17818 *order.lock().unwrap(),
17819 vec!["P", "C", "T1", "T2"],
17820 "the slot child C must run right after its scheduler P, ahead of older FIFO work"
17821 );
17822 let _ = done_tx.send(());
17823 });
17824
17825 match done_rx.recv_timeout(Duration::from_secs(10)) {
17826 Ok(()) => runner.join().unwrap(),
17827 Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
17828 }
17829 }
17830
17831 #[cfg(not(target_family = "wasm"))]
17832 #[test]
17833 fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
17834 let options = RuntimeOptions {
17840 flavor: RuntimeFlavor::MultiThread,
17841 worker_threads: 2,
17842 max_blocking_tasks: 2,
17843 thread_name_prefix: "lifo-displace".to_string(),
17844 park_deadline: None,
17845 drain_linger: DEFAULT_DRAIN_LINGER,
17846 };
17847 let executor =
17848 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17849 executor
17850 .active_drainers
17851 .store(executor.max_drainers, Ordering::SeqCst);
17852 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17853
17854 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17855 for name in ["a", "b"] {
17856 let n_order = Arc::clone(&order);
17857 let sched = Arc::clone(&executor);
17858 let (runnable, task) = async_task::spawn(
17859 async move {
17860 n_order.lock().unwrap().push(name);
17861 },
17862 move |r| sched.schedule(r),
17863 );
17864 executor.schedule(runnable);
17865 task.detach();
17866 }
17867 assert_eq!(
17868 executor.injector.len(),
17869 1,
17870 "the displaced occupant (a) must have been pushed to the shared FIFO"
17871 );
17872
17873 assert!(executor.run_one(), "run_one must pop the slot (b) first");
17874 assert!(
17875 executor.run_one(),
17876 "run_one must then pop the displaced (a) from the FIFO"
17877 );
17878 assert!(!executor.run_one(), "no work must remain");
17879 assert_eq!(
17880 *order.lock().unwrap(),
17881 vec!["b", "a"],
17882 "the newest schedule must win the slot; the displaced one must survive via the FIFO"
17883 );
17884 executor.active_drainers.store(0, Ordering::SeqCst);
17885 }
17886
17887 #[cfg(not(target_family = "wasm"))]
17888 #[test]
17889 fn lifo_slot_is_executor_scoped() {
17890 let options = RuntimeOptions {
17895 flavor: RuntimeFlavor::MultiThread,
17896 worker_threads: 2,
17897 max_blocking_tasks: 2,
17898 thread_name_prefix: "lifo-scope".to_string(),
17899 park_deadline: None,
17900 drain_linger: DEFAULT_DRAIN_LINGER,
17901 };
17902 let exec1 =
17903 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17904 let exec2 =
17905 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17906 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
17907 exec1
17908 .active_drainers
17909 .store(exec1.max_drainers, Ordering::SeqCst);
17910 exec2
17911 .active_drainers
17912 .store(exec2.max_drainers, Ordering::SeqCst);
17913 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17914
17915 let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
17917 let r1_order = Arc::clone(&order);
17918 let sched = Arc::clone(&exec1);
17919 let (r1, t1) = async_task::spawn(
17920 async move {
17921 r1_order.lock().unwrap().push("one");
17922 },
17923 move |r| sched.schedule(r),
17924 );
17925 exec1.schedule(r1);
17926 t1.detach();
17927 assert!(
17928 exec1.injector.is_empty(),
17929 "r1 must sit in the slot, not exec1's FIFO"
17930 );
17931
17932 {
17933 let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
17936 let r2_order = Arc::clone(&order);
17937 let sched = Arc::clone(&exec2);
17938 let (r2, t2) = async_task::spawn(
17939 async move {
17940 r2_order.lock().unwrap().push("two");
17941 },
17942 move |r| sched.schedule(r),
17943 );
17944 exec2.schedule(r2);
17945 t2.detach();
17946 assert_eq!(
17947 exec2.injector.len(),
17948 1,
17949 "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
17950 );
17951 assert!(
17952 exec2.pop_lifo_slot().is_none(),
17953 "exec2 must not pop exec1's slot entry (executor-scoped pop)"
17954 );
17955 assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
17956 }
17957
17958 assert!(
17960 exec1.run_one(),
17961 "exec1's slot entry must have survived the foreign activity"
17962 );
17963 assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
17964 exec1.active_drainers.store(0, Ordering::SeqCst);
17965 exec2.active_drainers.store(0, Ordering::SeqCst);
17966 }
17967
17968 #[cfg(not(target_family = "wasm"))]
17969 #[test]
17970 fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
17971 use std::sync::mpsc;
17978 use std::time::Duration;
17979
17980 const CHAIN: u64 = 200; fn spawn_link(
17983 executor: &Arc<MultiThreadExecutor>,
17984 remaining: u64,
17985 counter: Arc<AtomicU64>,
17986 done: mpsc::Sender<()>,
17987 ) {
17988 let body_exec = Arc::clone(executor);
17989 let body = async move {
17990 counter.fetch_add(1, Ordering::SeqCst);
17991 if remaining > 0 {
17992 spawn_link(&body_exec, remaining - 1, counter, done);
17993 } else {
17994 done.send(()).unwrap();
17995 }
17996 };
17997 let sched = Arc::clone(executor);
17998 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17999 executor.schedule(runnable);
18000 task.detach();
18001 }
18002
18003 let (done_tx, done_rx) = mpsc::channel();
18004 let runner = std::thread::spawn(move || {
18005 let metrics = Arc::new(RuntimeMetrics::default());
18006 let options = RuntimeOptions {
18007 flavor: RuntimeFlavor::MultiThread,
18008 worker_threads: 1,
18009 max_blocking_tasks: 1,
18010 thread_name_prefix: "lifo-budget".to_string(),
18011 park_deadline: None,
18012 drain_linger: DEFAULT_DRAIN_LINGER,
18013 };
18014 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
18015
18016 let counter = Arc::new(AtomicU64::new(0));
18017 let (chain_tx, chain_rx) = mpsc::channel();
18018 spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
18019
18020 chain_rx
18021 .recv_timeout(Duration::from_secs(10))
18022 .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
18023 assert_eq!(
18024 counter.load(Ordering::SeqCst),
18025 CHAIN,
18026 "every chain link must have run"
18027 );
18028 wait_until("runnable counters settle to zero (no slot leak)", || {
18029 metrics.queued_runnables.load(Ordering::Relaxed) == 0
18030 && metrics.active_runnables.load(Ordering::Relaxed) == 0
18031 });
18032 let _ = done_tx.send(());
18033 });
18034
18035 match done_rx.recv_timeout(Duration::from_secs(15)) {
18036 Ok(()) => runner.join().unwrap(),
18037 Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
18038 }
18039 }
18040
18041 #[cfg(not(target_family = "wasm"))]
18042 #[test]
18043 fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
18044 let options = RuntimeOptions {
18051 flavor: RuntimeFlavor::MultiThread,
18052 worker_threads: 2,
18053 max_blocking_tasks: 2,
18054 thread_name_prefix: "lifo-flush".to_string(),
18055 park_deadline: None,
18056 drain_linger: DEFAULT_DRAIN_LINGER,
18057 };
18058 let executor =
18059 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18060 executor
18061 .active_drainers
18062 .store(executor.max_drainers, Ordering::SeqCst);
18063 let ran = Arc::new(AtomicBool::new(false));
18064
18065 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18066 let ran_flag = Arc::clone(&ran);
18067 let sched = Arc::clone(&executor);
18068 let (runnable, task) = async_task::spawn(
18069 async move {
18070 ran_flag.store(true, Ordering::SeqCst);
18071 },
18072 move |r| sched.schedule(r),
18073 );
18074 executor.schedule(runnable);
18075 task.detach();
18076 assert!(
18077 executor.injector.is_empty(),
18078 "the runnable must start in the slot"
18079 );
18080
18081 executor.flush_lifo_slot();
18082 assert!(
18083 executor.pop_lifo_slot().is_none(),
18084 "the slot must be empty after a flush"
18085 );
18086 assert_eq!(
18087 executor.injector.len(),
18088 1,
18089 "the flushed runnable must be on the shared FIFO"
18090 );
18091 assert!(executor.run_one(), "the flushed runnable must still run");
18092 assert!(
18093 ran.load(Ordering::SeqCst),
18094 "the flushed task must complete, not be dropped"
18095 );
18096 executor.active_drainers.store(0, Ordering::SeqCst);
18097 }
18098
18099 #[cfg(not(target_family = "wasm"))]
18100 #[test]
18101 fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
18102 let options = RuntimeOptions {
18107 flavor: RuntimeFlavor::MultiThread,
18108 worker_threads: 2,
18109 max_blocking_tasks: 2,
18110 thread_name_prefix: "lifo-owner".to_string(),
18111 park_deadline: None,
18112 drain_linger: DEFAULT_DRAIN_LINGER,
18113 };
18114 let executor =
18115 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18116 let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
18117
18118 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18119
18120 let observed_slot = Arc::clone(&observed);
18124 let sched = Arc::clone(&executor);
18125 let (runnable, task) = async_task::spawn(
18126 async move {
18127 *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
18128 },
18129 move |r| sched.schedule(r),
18130 );
18131 executor.schedule(runnable);
18132 task.detach();
18133 assert!(
18134 executor.injector.is_empty(),
18135 "the runnable must sit in the slot"
18136 );
18137
18138 let token = executor.fresh_owner_token();
18142 let _owner = BlockingOwnerGuard::enter(Some(token));
18143 assert!(executor.run_one(), "the slot runnable must run");
18144 assert_eq!(
18145 *observed.lock().unwrap(),
18146 Some(None),
18147 "a slot runnable must observe a CLEARED blocking owner"
18148 );
18149 assert_eq!(
18150 BLOCKING_OWNER.with(std::cell::Cell::get),
18151 Some(token),
18152 "the ambient owner frame must be restored after the slot runnable"
18153 );
18154 }
18155
18156 #[cfg(not(target_family = "wasm"))]
18162 struct PingPongShared {
18163 count: AtomicU64,
18164 stop: AtomicBool,
18165 stop_after: u64,
18166 wakers: Mutex<[Option<std::task::Waker>; 2]>,
18167 }
18168
18169 #[cfg(not(target_family = "wasm"))]
18170 struct PingPong {
18171 shared: Arc<PingPongShared>,
18172 me: usize,
18173 }
18174
18175 #[cfg(not(target_family = "wasm"))]
18176 impl Future for PingPong {
18177 type Output = ();
18178 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18179 let shared = &self.shared;
18180 let polls = shared.count.fetch_add(1, Ordering::SeqCst);
18181 if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
18182 let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
18183 if let Some(partner) = partner {
18184 partner.wake();
18185 }
18186 return Poll::Ready(());
18187 }
18188 let mut wakers = shared.wakers.lock().unwrap();
18189 wakers[self.me] = Some(cx.waker().clone());
18190 if let Some(partner) = wakers[1 - self.me].take() {
18191 drop(wakers);
18192 partner.wake();
18193 }
18194 Poll::Pending
18195 }
18196 }
18197
18198 #[cfg(not(target_family = "wasm"))]
18199 fn spawn_ping_pong_pair(
18200 executor: &Arc<MultiThreadExecutor>,
18201 shared: &Arc<PingPongShared>,
18202 ) -> Vec<Task<()>> {
18203 let mut tasks = Vec::new();
18204 for me in 0..2 {
18205 let sched = Arc::clone(executor);
18206 let (runnable, task) = async_task::spawn(
18207 PingPong {
18208 shared: Arc::clone(shared),
18209 me,
18210 },
18211 move |r| sched.schedule(r),
18212 );
18213 executor.schedule(runnable);
18214 tasks.push(task);
18215 }
18216 tasks
18217 }
18218
18219 #[cfg(not(target_family = "wasm"))]
18220 #[test]
18221 fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
18222 use std::sync::mpsc;
18229 use std::time::Duration;
18230
18231 let (done_tx, done_rx) = mpsc::channel();
18232 let runner = std::thread::spawn(move || {
18233 let options = RuntimeOptions {
18234 flavor: RuntimeFlavor::MultiThread,
18235 worker_threads: 1,
18236 max_blocking_tasks: 0,
18237 thread_name_prefix: "lifo-streak".to_string(),
18238 park_deadline: None,
18239 drain_linger: DEFAULT_DRAIN_LINGER,
18240 };
18241 let executor =
18242 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18243
18244 let shared = Arc::new(PingPongShared {
18245 count: AtomicU64::new(0),
18246 stop: AtomicBool::new(false),
18247 stop_after: u64::MAX,
18248 wakers: Mutex::new([None, None]),
18249 });
18250 let pair = spawn_ping_pong_pair(&executor, &shared);
18251
18252 let (t3_tx, t3_rx) = mpsc::channel::<()>();
18254 let sched = Arc::clone(&executor);
18255 let (t3_runnable, t3_task) = async_task::spawn(
18256 async move {
18257 t3_tx.send(()).unwrap();
18258 },
18259 move |r| sched.schedule(r),
18260 );
18261 executor.schedule(t3_runnable);
18262
18263 t3_rx
18264 .recv_timeout(Duration::from_secs(10))
18265 .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
18266 assert!(
18267 shared.count.load(Ordering::SeqCst) >= 32,
18268 "the pair must have been bouncing through the slot before T3 got its turn"
18269 );
18270
18271 shared.stop.store(true, Ordering::SeqCst);
18273 let stored: Vec<_> = shared
18274 .wakers
18275 .lock()
18276 .unwrap()
18277 .iter_mut()
18278 .filter_map(Option::take)
18279 .collect();
18280 for waker in stored {
18281 waker.wake();
18282 }
18283 for task in pair {
18284 futures::executor::block_on(task);
18285 }
18286 futures::executor::block_on(t3_task);
18287 let _ = done_tx.send(());
18288 });
18289
18290 match done_rx.recv_timeout(Duration::from_secs(15)) {
18291 Ok(()) => runner.join().unwrap(),
18292 Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
18293 }
18294 }
18295
18296 #[cfg(not(target_family = "wasm"))]
18297 #[test]
18298 fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
18299 use std::sync::mpsc;
18306 use std::time::Duration;
18307
18308 const BOUNCES: u64 = 2_000; let (done_tx, done_rx) = mpsc::channel();
18311 let runner = std::thread::spawn(move || {
18312 let options = RuntimeOptions {
18313 flavor: RuntimeFlavor::MultiThread,
18314 worker_threads: 1,
18315 max_blocking_tasks: 1,
18316 thread_name_prefix: "lifo-blocking".to_string(),
18317 park_deadline: None,
18318 drain_linger: DEFAULT_DRAIN_LINGER,
18319 };
18320 let executor =
18321 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18322
18323 let shared = Arc::new(PingPongShared {
18324 count: AtomicU64::new(0),
18325 stop: AtomicBool::new(false),
18326 stop_after: BOUNCES,
18327 wakers: Mutex::new([None, None]),
18328 });
18329 let pair = spawn_ping_pong_pair(&executor, &shared);
18330
18331 let blocking = executor.schedule_blocking(|| 11usize);
18333
18334 assert_eq!(
18335 futures::executor::block_on(blocking).unwrap(),
18336 11,
18337 "the queued blocking job must complete despite the hot LIFO pair"
18338 );
18339 for task in pair {
18340 futures::executor::block_on(task);
18341 }
18342 assert!(
18343 shared.count.load(Ordering::SeqCst) >= BOUNCES,
18344 "the pair must have actually run its full bounce quota"
18345 );
18346 let _ = done_tx.send(());
18347 });
18348
18349 match done_rx.recv_timeout(Duration::from_secs(15)) {
18350 Ok(()) => runner.join().unwrap(),
18351 Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
18352 }
18353 }
18354
18355 #[cfg(not(target_family = "wasm"))]
18356 #[test]
18357 fn infinite_hot_runnable_yields_to_blocking_fifo() {
18358 use std::sync::mpsc;
18359
18360 struct HotFuture {
18361 stop: Arc<AtomicBool>,
18362 }
18363
18364 impl Future for HotFuture {
18365 type Output = ();
18366
18367 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18368 if self.stop.load(Ordering::SeqCst) {
18369 Poll::Ready(())
18370 } else {
18371 cx.waker().wake_by_ref();
18372 Poll::Pending
18373 }
18374 }
18375 }
18376
18377 let options = RuntimeOptions {
18378 flavor: RuntimeFlavor::MultiThread,
18379 worker_threads: 2,
18380 max_blocking_tasks: 1,
18381 thread_name_prefix: "blocking-fairness".to_string(),
18382 park_deadline: None,
18383 drain_linger: DEFAULT_DRAIN_LINGER,
18384 };
18385 let executor =
18386 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18387 let stop = Arc::new(AtomicBool::new(false));
18388 let scheduler = Arc::clone(&executor);
18389 let (runnable, task) = async_task::spawn(
18390 HotFuture {
18391 stop: Arc::clone(&stop),
18392 },
18393 move |r| scheduler.schedule(r),
18394 );
18395 task.detach();
18396 let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
18397 executor
18398 .blocking_queue
18399 .lock()
18400 .unwrap()
18401 .push(QueuedBlocking {
18402 id: BlockingJobId(u64::MAX),
18403 run: Box::new(move || blocking_tx.send(()).unwrap()),
18404 });
18405
18406 let _driver = OnPoolWorkerGuard::enter(executor.id);
18407 executor.schedule(runnable);
18408 let mut runnable_streak = 0;
18409 for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
18410 assert!(executor.run_one_fair(&mut runnable_streak));
18411 }
18412 blocking_rx
18413 .try_recv()
18414 .expect("an infinite self-waker must yield a bounded quantum to blocking work");
18415
18416 stop.store(true, Ordering::SeqCst);
18417 assert!(executor.run_one_fair(&mut runnable_streak));
18418 }
18419
18420 #[cfg(not(target_family = "wasm"))]
18421 #[test]
18422 fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
18423 use std::sync::mpsc;
18424
18425 struct HotFuture {
18426 stop: Arc<AtomicBool>,
18427 }
18428
18429 impl Future for HotFuture {
18430 type Output = ();
18431
18432 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18433 if self.stop.load(Ordering::SeqCst) {
18434 Poll::Ready(())
18435 } else {
18436 cx.waker().wake_by_ref();
18437 Poll::Pending
18438 }
18439 }
18440 }
18441
18442 let options = RuntimeOptions {
18443 flavor: RuntimeFlavor::MultiThread,
18444 worker_threads: 1,
18445 max_blocking_tasks: 1,
18446 thread_name_prefix: "exact-dependency-fairness".to_string(),
18447 park_deadline: None,
18448 drain_linger: DEFAULT_DRAIN_LINGER,
18449 };
18450 let executor =
18451 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18452 let stop = Arc::new(AtomicBool::new(false));
18453 let (queued_tx, queued_rx) = mpsc::channel();
18454 let (exact_tx, exact_rx) = mpsc::channel();
18455 let owner_executor = Arc::clone(&executor);
18456 let owner_stop = Arc::clone(&stop);
18457 let owner = executor.schedule_blocking(move || {
18458 let scheduler = Arc::clone(&owner_executor);
18459 let hot_stop = Arc::clone(&owner_stop);
18460 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
18461 scheduler.schedule(runnable);
18462 });
18463 owner_executor.schedule(runnable);
18464
18465 let exact_stop = Arc::clone(&owner_stop);
18466 let exact = owner_executor.schedule_blocking(move || {
18467 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
18468 41usize
18469 });
18470 queued_tx.send(()).unwrap();
18471
18472 let mut value = None;
18473 {
18474 let mut future = std::pin::pin!(async {
18475 value = Some(exact.await.unwrap());
18476 hot_task.await;
18477 });
18478 assert_eq!(
18479 owner_executor.block_on(future.as_mut()),
18480 BlockOnOutcome::Completed
18481 );
18482 }
18483 value.unwrap()
18484 });
18485
18486 queued_rx
18487 .recv_timeout(Duration::from_secs(2))
18488 .expect("the exact dependency must be queued behind the saturated owner");
18489 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
18490 stop.store(true, Ordering::SeqCst);
18491 assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
18492 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
18493 assert_eq!(
18494 exact_before_stop,
18495 Some(true),
18496 "a hot local runnable must yield to the exact dependency before it is stopped"
18497 );
18498 }
18499
18500 #[cfg(not(target_family = "wasm"))]
18501 #[test]
18502 fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
18503 use std::sync::mpsc;
18504
18505 struct BlockingPoll {
18506 started: Option<mpsc::Sender<()>>,
18507 released: Arc<(Mutex<bool>, Condvar)>,
18508 }
18509
18510 impl Future for BlockingPoll {
18511 type Output = ();
18512
18513 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18514 if let Some(started) = self.started.take() {
18515 started.send(()).unwrap();
18516 }
18517 let (released, changed) = &*self.released;
18518 let mut released = released
18519 .lock()
18520 .unwrap_or_else(std::sync::PoisonError::into_inner);
18521 while !*released {
18522 released = changed
18523 .wait(released)
18524 .unwrap_or_else(std::sync::PoisonError::into_inner);
18525 }
18526 Poll::Ready(())
18527 }
18528 }
18529
18530 let options = RuntimeOptions {
18531 flavor: RuntimeFlavor::MultiThread,
18532 worker_threads: 3,
18533 max_blocking_tasks: 2,
18534 thread_name_prefix: "exact-before-last-slot".to_string(),
18535 park_deadline: None,
18536 drain_linger: DEFAULT_DRAIN_LINGER,
18537 };
18538 let executor =
18539 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18540
18541 let released = Arc::new((Mutex::new(false), Condvar::new()));
18542 let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
18543 let mut blockers = Vec::new();
18544 for _ in 0..2 {
18545 let scheduler = Arc::clone(&executor);
18546 let blocker_released = Arc::clone(&released);
18547 let started = blocker_started_tx.clone();
18548 let (runnable, task) = async_task::spawn(
18549 BlockingPoll {
18550 started: Some(started),
18551 released: blocker_released,
18552 },
18553 move |runnable| scheduler.schedule(runnable),
18554 );
18555 executor.schedule(runnable);
18556 blockers.push(task);
18557 }
18558 drop(blocker_started_tx);
18559 for _ in 0..2 {
18560 blocker_started_rx
18561 .recv_timeout(Duration::from_secs(2))
18562 .expect("two workers must be gated before the owner starts");
18563 }
18564
18565 let dependency_started = Arc::new(AtomicBool::new(false));
18566 let owner_executor = Arc::clone(&executor);
18567 let owner_dependency_started = Arc::clone(&dependency_started);
18568 let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
18569 let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
18570 let owner_signal = Arc::clone(&dependency_signal);
18571 let owner = executor.schedule_blocking(move || {
18572 let unrelated_signal = Arc::clone(&owner_signal);
18573 let unrelated_started = Arc::clone(&owner_dependency_started);
18574 let unrelated = owner_executor.schedule_blocking(move || {
18575 let observed = unrelated_started.load(Ordering::SeqCst);
18576 unrelated_observed_tx.send(observed).unwrap();
18577 let (ready, changed) = &*unrelated_signal;
18578 let mut ready = ready
18579 .lock()
18580 .unwrap_or_else(std::sync::PoisonError::into_inner);
18581 while !*ready {
18582 ready = changed
18583 .wait(ready)
18584 .unwrap_or_else(std::sync::PoisonError::into_inner);
18585 }
18586 });
18587
18588 let exact_signal = Arc::clone(&owner_signal);
18589 let exact_started = Arc::clone(&owner_dependency_started);
18590 let exact = owner_executor.schedule_blocking(move || {
18591 exact_started.store(true, Ordering::SeqCst);
18592 let (ready, changed) = &*exact_signal;
18593 *ready
18594 .lock()
18595 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18596 changed.notify_all();
18597 41usize
18598 });
18599
18600 let mut output = None;
18601 {
18602 let mut future = std::pin::pin!(async {
18603 output = Some(exact.await.unwrap());
18604 unrelated.await.unwrap();
18605 });
18606 assert_eq!(
18607 owner_executor.block_on(future.as_mut()),
18608 BlockOnOutcome::Completed
18609 );
18610 }
18611 output.unwrap()
18612 });
18613
18614 let (owner_done_tx, owner_done_rx) = mpsc::channel();
18615 let owner_waiter = std::thread::spawn(move || {
18616 owner_done_tx
18617 .send(futures::executor::block_on(owner))
18618 .unwrap();
18619 });
18620 let (completed_while_gated, owner_result) =
18621 match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
18622 Ok(result) => (true, result),
18623 Err(_) => {
18624 let (released, changed) = &*released;
18627 *released
18628 .lock()
18629 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18630 changed.notify_all();
18631 (
18632 false,
18633 owner_done_rx
18634 .recv_timeout(Duration::from_secs(2))
18635 .expect("releasing the other workers must let the broken ordering recover"),
18636 )
18637 }
18638 };
18639 let unrelated_observed = unrelated_observed_rx
18640 .recv_timeout(Duration::from_secs(2))
18641 .expect("the unrelated FIFO job must eventually run");
18642
18643 {
18644 let (released, changed) = &*released;
18645 *released
18646 .lock()
18647 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18648 changed.notify_all();
18649 }
18650 owner_waiter.join().unwrap();
18651 for blocker in blockers {
18652 futures::executor::block_on(blocker);
18653 }
18654 executor.begin_shutdown();
18655 executor.wait_until_scheduler_idle();
18656
18657 assert!(
18658 completed_while_gated,
18659 "the exact dependency must complete while unrelated workers remain gated"
18660 );
18661 assert_eq!(owner_result.unwrap(), 41);
18662 assert!(
18663 unrelated_observed,
18664 "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
18665 );
18666 }
18667
18668 #[cfg(not(target_family = "wasm"))]
18669 #[test]
18670 fn blocking_saturation_preserves_a_runnable_execution_lane() {
18671 use std::{sync::mpsc, time::Duration};
18672
18673 let options = RuntimeOptions {
18674 flavor: RuntimeFlavor::MultiThread,
18675 worker_threads: 2,
18676 max_blocking_tasks: 2,
18677 thread_name_prefix: "blocking-reserve".to_string(),
18678 park_deadline: None,
18679 drain_linger: DEFAULT_DRAIN_LINGER,
18680 }
18681 .validate()
18682 .expect("production options must reserve a runnable lane");
18683 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
18684 let executor =
18685 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18686 let (release_tx, release_rx) = mpsc::channel();
18687 let (started_tx, started_rx) = mpsc::channel();
18688 let first = executor.schedule_blocking(move || {
18689 started_tx.send(()).unwrap();
18690 release_rx.recv().unwrap();
18691 });
18692 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
18693
18694 let (queued_tx, queued_rx) = mpsc::channel();
18695 let second = executor.schedule_blocking(move || {
18696 queued_tx.send(()).unwrap();
18697 });
18698 assert!(
18699 queued_rx.try_recv().is_err(),
18700 "the second blocking job must remain queued while the sole blocking slot is occupied"
18701 );
18702
18703 let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
18704 let scheduler = Arc::clone(&executor);
18705 let (runnable, task) = async_task::spawn(
18706 async move {
18707 runnable_tx.send(()).unwrap();
18708 },
18709 move |r| scheduler.schedule(r),
18710 );
18711 task.detach();
18712 executor.schedule(runnable);
18713 runnable_rx
18714 .recv_timeout(Duration::from_secs(1))
18715 .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
18716
18717 release_tx.send(()).unwrap();
18718 futures::executor::block_on(first).unwrap();
18719 futures::executor::block_on(second).unwrap();
18720 queued_rx
18721 .recv_timeout(Duration::from_secs(1))
18722 .expect("queued blocking work must run after the occupied slot is released");
18723 }
18724
18725 #[cfg(not(target_family = "wasm"))]
18726 #[test]
18727 fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
18728 use std::sync::mpsc;
18736 use std::time::Duration;
18737
18738 struct SpawnOnReady {
18739 executor: Arc<MultiThreadExecutor>,
18740 child_tx: Option<mpsc::Sender<u64>>,
18741 }
18742 impl Future for SpawnOnReady {
18743 type Output = ();
18744 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18745 let child_tx = self.child_tx.take().expect("polled once");
18746 let sched = Arc::clone(&self.executor);
18747 let (runnable, task) = async_task::spawn(
18748 async move {
18749 child_tx.send(21).unwrap();
18750 },
18751 move |r| sched.schedule(r),
18752 );
18753 self.executor.schedule(runnable);
18755 task.detach();
18756 Poll::Ready(())
18757 }
18758 }
18759
18760 let (done_tx, done_rx) = mpsc::channel();
18761 let runner = std::thread::spawn(move || {
18762 let options = RuntimeOptions {
18763 flavor: RuntimeFlavor::MultiThread,
18764 worker_threads: 2,
18765 max_blocking_tasks: 2,
18766 thread_name_prefix: "exit-flush".to_string(),
18767 park_deadline: None,
18768 drain_linger: DEFAULT_DRAIN_LINGER,
18769 };
18770 let executor =
18771 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18772
18773 let (child_tx, child_rx) = mpsc::channel::<u64>();
18774 {
18775 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18778 let mut future = std::pin::pin!(SpawnOnReady {
18779 executor: Arc::clone(&executor),
18780 child_tx: Some(child_tx)
18781 });
18782 executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
18783 }
18784
18785 let value = child_rx
18789 .recv_timeout(Duration::from_secs(5))
18790 .expect("the child scheduled by the final poll was stranded in the LIFO slot");
18791 assert_eq!(value, 21);
18792 let _ = done_tx.send(());
18793 });
18794
18795 match done_rx.recv_timeout(Duration::from_secs(10)) {
18796 Ok(()) => runner.join().unwrap(),
18797 Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
18798 }
18799 }
18800
18801 #[cfg(not(target_family = "wasm"))]
18805 fn spawn_hot_chain(
18806 executor: &Arc<MultiThreadExecutor>,
18807 stop: Arc<AtomicBool>,
18808 hops: Arc<AtomicU64>,
18809 ) {
18810 if stop.load(Ordering::SeqCst) {
18811 return;
18812 }
18813 let link_exec = Arc::clone(executor);
18814 let body = async move {
18815 hops.fetch_add(1, Ordering::SeqCst);
18816 spawn_hot_chain(&link_exec, stop, hops);
18817 };
18818 let sched = Arc::clone(executor);
18819 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
18820 executor.schedule(runnable);
18821 task.detach();
18822 }
18823
18824 #[cfg(not(target_family = "wasm"))]
18825 #[test]
18826 fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
18827 use std::sync::mpsc;
18836 use std::time::Duration;
18837
18838 let (done_tx, done_rx) = mpsc::channel();
18839 let runner = std::thread::spawn(move || {
18840 let options = RuntimeOptions {
18841 flavor: RuntimeFlavor::MultiThread,
18842 worker_threads: 1,
18843 max_blocking_tasks: 0,
18846 thread_name_prefix: "coop-budget".to_string(),
18847 park_deadline: None,
18848 drain_linger: DEFAULT_DRAIN_LINGER,
18849 };
18850 let executor =
18851 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18852
18853 let stop = Arc::new(AtomicBool::new(false));
18854 let hops = Arc::new(AtomicU64::new(0));
18855
18856 let m_exec = Arc::clone(&executor);
18860 let m_stop = Arc::clone(&stop);
18861 let m_hops = Arc::clone(&hops);
18862 let m_body = async move {
18863 let (tx, rx) = oneshot::channel::<u64>();
18864 let sched_f = Arc::clone(&m_exec);
18866 let (f_runnable, f_task) = async_task::spawn(
18867 async move {
18868 let _ = tx.send(7);
18869 },
18870 move |r| sched_f.schedule(r),
18871 );
18872 m_exec.schedule(f_runnable);
18873 f_task.detach();
18874 spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
18877
18878 let mut inner = std::pin::pin!(async move {
18879 assert_eq!(
18880 rx.await.unwrap(),
18881 7,
18882 "F must run and complete the awaited future"
18883 );
18884 });
18885 m_exec.block_on(inner.as_mut());
18886 m_stop.store(true, Ordering::SeqCst);
18887 };
18888 let sched_m = Arc::clone(&executor);
18889 let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
18890 executor.schedule(m_runnable);
18891
18892 futures::executor::block_on(m_task);
18893 assert!(
18894 hops.load(Ordering::SeqCst) >= 64,
18895 "the chain must have been genuinely hot (>= one budget window) before F ran"
18896 );
18897 let _ = done_tx.send(());
18898 });
18899
18900 match done_rx.recv_timeout(Duration::from_secs(10)) {
18901 Ok(()) => runner.join().unwrap(),
18902 Err(error) => panic!(
18903 "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
18904 ),
18905 }
18906 }
18907
18908 #[cfg(not(target_family = "wasm"))]
18909 #[test]
18910 fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
18911 use std::sync::mpsc;
18912
18913 struct RefillLifo {
18914 executor: Arc<MultiThreadExecutor>,
18915 fifo_ran: Arc<AtomicBool>,
18916 }
18917
18918 impl Future for RefillLifo {
18919 type Output = ();
18920
18921 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18922 if self.fifo_ran.load(Ordering::SeqCst) {
18923 return Poll::Ready(());
18924 }
18925 let scheduler = Arc::clone(&self.executor);
18926 let (runnable, task) =
18927 async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
18928 self.executor.schedule(runnable);
18929 task.detach();
18930 Poll::Pending
18931 }
18932 }
18933
18934 let (done_tx, done_rx) = mpsc::channel();
18935 let runner = std::thread::spawn(move || {
18936 let options = RuntimeOptions {
18937 flavor: RuntimeFlavor::MultiThread,
18938 worker_threads: 1,
18939 max_blocking_tasks: 0,
18940 thread_name_prefix: "forced-fifo".to_string(),
18941 park_deadline: None,
18942 drain_linger: DEFAULT_DRAIN_LINGER,
18943 };
18944 let executor =
18945 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18946 executor
18947 .active_drainers
18948 .store(executor.max_drainers, Ordering::SeqCst);
18949
18950 let fifo_ran = Arc::new(AtomicBool::new(false));
18951 let fifo_flag = Arc::clone(&fifo_ran);
18952 let scheduler = Arc::clone(&executor);
18953 let (fifo, task) = async_task::spawn(
18954 async move {
18955 fifo_flag.store(true, Ordering::SeqCst);
18956 },
18957 move |runnable| scheduler.schedule(runnable),
18958 );
18959 task.detach();
18960 executor.metrics.runnable_scheduled();
18961 executor.push_fifo_raw(fifo);
18962
18963 {
18964 let _driver = OnPoolWorkerGuard::enter(executor.id);
18965 let mut future = std::pin::pin!(RefillLifo {
18966 executor: Arc::clone(&executor),
18967 fifo_ran: Arc::clone(&fifo_ran)
18968 });
18969 executor.block_on(future.as_mut());
18970 while executor.run_one() {}
18971 }
18972 executor.active_drainers.store(0, Ordering::SeqCst);
18973 assert!(fifo_ran.load(Ordering::SeqCst));
18974 done_tx.send(()).unwrap();
18975 });
18976 done_rx
18977 .recv_timeout(Duration::from_secs(3))
18978 .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
18979 runner.join().unwrap();
18980 }
18981
18982 #[cfg(not(target_family = "wasm"))]
18983 #[test]
18984 fn blocking_closure_spawn_is_not_stranded_in_slot() {
18985 use std::sync::mpsc;
18992 use std::time::Duration;
18993
18994 let (done_tx, done_rx) = mpsc::channel();
18995 let runner = std::thread::spawn(move || {
18996 let options = RuntimeOptions {
18997 flavor: RuntimeFlavor::MultiThread,
18998 worker_threads: 2,
18999 max_blocking_tasks: 2,
19000 thread_name_prefix: "blk-spawn-wait".to_string(),
19001 park_deadline: None,
19002 drain_linger: DEFAULT_DRAIN_LINGER,
19003 };
19004 let executor =
19005 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19006
19007 let blk_exec = Arc::clone(&executor);
19010 let handle = executor.schedule_blocking(move || {
19011 let (tx, rx) = mpsc::channel::<u64>();
19012 let sched = Arc::clone(&blk_exec);
19013 let (runnable, task) = async_task::spawn(
19014 async move {
19015 tx.send(21).unwrap();
19016 },
19017 move |r| sched.schedule(r),
19018 );
19019 blk_exec.schedule(runnable);
19020 task.detach();
19021 rx.recv_timeout(Duration::from_secs(5))
19022 .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
19023 });
19024 assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
19025
19026 let (v_tx, v_rx) = mpsc::channel::<u64>();
19029 let v_exec = Arc::clone(&executor);
19030 let v_handle = executor.schedule_blocking(move || {
19031 let sched = Arc::clone(&v_exec);
19032 let (runnable, task) = async_task::spawn(
19033 async move {
19034 v_tx.send(42).unwrap();
19035 },
19036 move |r| sched.schedule(r),
19037 );
19038 v_exec.schedule(runnable);
19039 task.detach();
19040 1u64
19041 });
19042 assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
19043 assert_eq!(
19044 v_rx
19045 .recv_timeout(Duration::from_secs(5))
19046 .expect("fire-and-forget child must complete"),
19047 42
19048 );
19049 let _ = done_tx.send(());
19050 });
19051
19052 match done_rx.recv_timeout(Duration::from_secs(15)) {
19053 Ok(()) => runner.join().unwrap(),
19054 Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
19055 }
19056 }
19057
19058 #[cfg(not(target_family = "wasm"))]
19059 #[test]
19060 fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
19061 use std::sync::{Barrier, mpsc};
19067 use std::time::Duration;
19068
19069 let (done_tx, done_rx) = mpsc::channel();
19070 let runner = std::thread::spawn(move || {
19071 let metrics = Arc::new(RuntimeMetrics::default());
19072 let options = RuntimeOptions {
19073 flavor: RuntimeFlavor::MultiThread,
19074 worker_threads: 2,
19075 max_blocking_tasks: 2,
19076 thread_name_prefix: "rd1-nested-blk".to_string(),
19077 park_deadline: None,
19078 drain_linger: DEFAULT_DRAIN_LINGER,
19079 };
19080 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19081
19082 let outer_count = 2usize; let barrier = Arc::new(Barrier::new(outer_count));
19084
19085 let mut handles = Vec::new();
19086 for _ in 0..outer_count {
19087 let exec = Arc::clone(&executor);
19088 let barrier = Arc::clone(&barrier);
19089 let handle = executor.schedule_blocking(move || {
19090 barrier.wait();
19092 let inner_exec = Arc::clone(&exec);
19093 let mut value = 0usize;
19094 {
19095 let mut inner = std::pin::pin!(async {
19096 value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19097 });
19098 exec.block_on(inner.as_mut());
19099 }
19100 value
19101 });
19102 handles.push(handle);
19103 }
19104
19105 for handle in handles {
19106 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
19107 }
19108 let _ = done_tx.send(());
19109 });
19110
19111 match done_rx.recv_timeout(Duration::from_secs(10)) {
19112 Ok(()) => runner.join().unwrap(),
19113 Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
19114 }
19115 }
19116
19117 #[cfg(not(target_family = "wasm"))]
19118 #[test]
19119 fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
19120 let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
19121 let owner = executor.fresh_owner_token();
19122 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19123 let job = BlockingJobId(u64::MAX - 5);
19124 let dependency = TaskDependency::default();
19125 dependency.set_owned(BlockingDependency { job, owner: None });
19126 let ran = Arc::new(AtomicBool::new(false));
19127 let job_ran = Arc::clone(&ran);
19128 executor
19129 .blocking_queue
19130 .lock()
19131 .unwrap()
19132 .push(QueuedBlocking {
19133 id: job,
19134 run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
19135 });
19136
19137 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19138 assert!(
19139 !executor.try_owned_blocking_over_cap(&dependency),
19140 "an untagged worker must not infer ancestry from a sole active owner"
19141 );
19142 assert!(!ran.load(Ordering::SeqCst));
19143 assert!(
19144 executor
19145 .blocking_queue
19146 .lock()
19147 .unwrap()
19148 .jobs
19149 .contains_key(&job),
19150 "rejected owner inference must leave the exact job queued"
19151 );
19152 }
19153
19154 #[cfg(not(target_family = "wasm"))]
19155 #[test]
19156 fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
19157 use std::sync::{Barrier, mpsc};
19158
19159 let options = RuntimeOptions {
19160 flavor: RuntimeFlavor::MultiThread,
19161 worker_threads: 2,
19162 max_blocking_tasks: 2,
19163 thread_name_prefix: "exact-owner-frame".to_string(),
19164 park_deadline: None,
19165 drain_linger: DEFAULT_DRAIN_LINGER,
19166 };
19167 let executor =
19168 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19169 let owners_saturated = Arc::new(Barrier::new(3));
19170 let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
19171 let begin_lending = Arc::new(Barrier::new(3));
19172 let release_dependencies = Arc::new(Barrier::new(3));
19173 let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
19174 let mut owners = Vec::new();
19175
19176 for owner_index in 0..2 {
19177 let owner_executor = Arc::clone(&executor);
19178 let owners_saturated = Arc::clone(&owners_saturated);
19179 let dependency_queued_tx = dependency_queued_tx.clone();
19180 let begin_lending = Arc::clone(&begin_lending);
19181 let release_dependencies = Arc::clone(&release_dependencies);
19182 let dependency_started_tx = dependency_started_tx.clone();
19183 owners.push(executor.schedule_blocking(move || {
19184 let owner_thread = std::thread::current().id();
19185 owners_saturated.wait();
19186 let child_started = dependency_started_tx.clone();
19187 let child_release = Arc::clone(&release_dependencies);
19188 let child = owner_executor.schedule_blocking(move || {
19189 assert_eq!(
19190 std::thread::current().id(),
19191 owner_thread,
19192 "an owner frame must execute only its own exact dependency on its lent lane"
19193 );
19194 child_started.send(owner_index).unwrap();
19195 child_release.wait();
19196 owner_index
19197 });
19198 dependency_queued_tx.send(()).unwrap();
19199 begin_lending.wait();
19200
19201 let mut result = None;
19202 {
19203 let mut future = std::pin::pin!(async {
19204 result = Some(child.await.unwrap());
19205 });
19206 assert_eq!(
19207 owner_executor.block_on(future.as_mut()),
19208 BlockOnOutcome::Completed
19209 );
19210 }
19211 result.unwrap()
19212 }));
19213 }
19214 drop(dependency_queued_tx);
19215 drop(dependency_started_tx);
19216 owners_saturated.wait();
19217
19218 for _ in 0..2 {
19219 dependency_queued_rx
19220 .recv_timeout(Duration::from_secs(2))
19221 .expect("both owner-scoped dependencies must be queued while the cap is saturated");
19222 }
19223 let (unrelated_tx, unrelated_rx) = mpsc::channel();
19224 let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
19225 begin_lending.wait();
19226
19227 let mut started = Vec::new();
19228 for _ in 0..2 {
19229 started.push(
19230 dependency_started_rx
19231 .recv_timeout(Duration::from_secs(2))
19232 .expect("each owner must service its own exact dependency"),
19233 );
19234 }
19235 started.sort_unstable();
19236 assert_eq!(started, vec![0, 1]);
19237 assert!(
19238 unrelated_rx.try_recv().is_err(),
19239 "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
19240 );
19241
19242 release_dependencies.wait();
19243 for (owner_index, owner) in owners.into_iter().enumerate() {
19244 assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
19245 }
19246 unrelated_rx
19247 .recv_timeout(Duration::from_secs(2))
19248 .expect("the unrelated job must run after an owner releases a counted lane");
19249 futures::executor::block_on(unrelated).unwrap();
19250 }
19251
19252 #[cfg(not(target_family = "wasm"))]
19253 #[test]
19254 fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
19255 use std::sync::{Mutex, mpsc};
19256
19257 const DEPENDENCY_COUNT: usize = 4;
19258
19259 let options = RuntimeOptions {
19260 flavor: RuntimeFlavor::MultiThread,
19261 worker_threads: 3,
19262 max_blocking_tasks: 1,
19263 thread_name_prefix: "parked-unrelated-worker".to_string(),
19264 park_deadline: None,
19265 drain_linger: DEFAULT_DRAIN_LINGER,
19266 };
19267 let executor =
19268 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19269 let (parked_tx, parked_rx) = mpsc::channel();
19270 let (release_parked_tx, release_parked_rx) = mpsc::channel();
19271 executor.pool.spawn(move || {
19272 parked_tx.send(()).unwrap();
19273 release_parked_rx.recv().unwrap();
19274 });
19275 parked_rx
19276 .recv_timeout(Duration::from_secs(2))
19277 .expect("the unrelated Rayon worker must remain occupied");
19278
19279 let owner_executor = Arc::clone(&executor);
19280 let owner = executor.schedule_blocking(move || {
19281 let owner = BLOCKING_OWNER
19282 .with(std::cell::Cell::get)
19283 .expect("the blocking closure must carry its exact owner frame");
19284 let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
19285 let results = Arc::new(Mutex::new(Vec::new()));
19286 let descendant_results = Arc::clone(&results);
19287 let descendant_executor = Arc::clone(&owner_executor);
19288 rayon::scope(move |scope| {
19289 scope.spawn(move |_| {
19290 assert_eq!(
19291 BLOCKING_OWNER.with(std::cell::Cell::get),
19292 None,
19293 "the parked owner must force its descendant onto the remaining worker"
19294 );
19295 let _owner = BlockingOwnerGuard::enter(Some(owner));
19296 descendant_started_tx.send(()).unwrap();
19297 for value in 0..DEPENDENCY_COUNT {
19298 let handle = descendant_executor.schedule_blocking(move || value);
19299 let mut result = None;
19300 {
19301 let mut future = std::pin::pin!(async {
19302 result = Some(handle.await.unwrap());
19303 });
19304 assert_eq!(
19305 descendant_executor.block_on(future.as_mut()),
19306 BlockOnOutcome::Completed
19307 );
19308 }
19309 descendant_results.lock().unwrap().push(result.unwrap());
19310 }
19311 });
19312 descendant_started_rx
19313 .recv_timeout(Duration::from_secs(2))
19314 .expect("the nested descendant must start on the only unoccupied worker");
19315 });
19316 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19317 });
19318
19319 assert_eq!(
19320 futures::executor::block_on(owner).unwrap(),
19321 (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
19322 );
19323
19324 let (idle_tx, idle_rx) = mpsc::channel();
19325 let idle_executor = Arc::clone(&executor);
19326 let idle_waiter = std::thread::spawn(move || {
19327 idle_executor.begin_shutdown();
19328 idle_executor.wait_until_scheduler_idle();
19329 idle_tx.send(()).unwrap();
19330 });
19331 idle_rx
19332 .recv_timeout(Duration::from_secs(2))
19333 .expect("an unrelated parked worker must not retain owner-probe idle accounting");
19334
19335 release_parked_tx.send(()).unwrap();
19336 idle_waiter.join().unwrap();
19337 }
19338
19339 #[cfg(not(target_family = "wasm"))]
19340 #[test]
19341 fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
19342 use std::sync::{Condvar, Mutex, mpsc};
19343
19344 let options = RuntimeOptions {
19345 flavor: RuntimeFlavor::MultiThread,
19346 worker_threads: 3,
19347 max_blocking_tasks: 1,
19348 thread_name_prefix: "same-owner-rearm".to_string(),
19349 park_deadline: None,
19350 drain_linger: DEFAULT_DRAIN_LINGER,
19351 };
19352 let executor =
19353 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19354 let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
19355 let (job_started_tx, job_started_rx) = mpsc::channel();
19356 let owner_executor = Arc::clone(&executor);
19357 let owner_releases = Arc::clone(&releases);
19358 let owner = executor.schedule_blocking(move || {
19359 let owner = BLOCKING_OWNER
19360 .with(std::cell::Cell::get)
19361 .expect("the blocking closure must carry its exact owner frame");
19362 let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
19363 let results = Arc::new(Mutex::new(Vec::new()));
19364 let scope_results = Arc::clone(&results);
19365 rayon::scope(move |scope| {
19366 for index in 0..2 {
19367 let descendant_ready_tx = descendant_ready_tx.clone();
19368 let descendant_executor = Arc::clone(&owner_executor);
19369 let descendant_results = Arc::clone(&scope_results);
19370 let job_started_tx = job_started_tx.clone();
19371 let releases = Arc::clone(&owner_releases);
19372 scope.spawn(move |_| {
19373 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19374 let _owner = BlockingOwnerGuard::enter(Some(owner));
19375 descendant_ready_tx.send(()).unwrap();
19376 let handle = descendant_executor.schedule_blocking(move || {
19377 job_started_tx.send(index).unwrap();
19378 let (released, changed) = &*releases;
19379 let mut state = released.lock().unwrap();
19380 while !state[index] {
19381 state = changed.wait(state).unwrap();
19382 }
19383 index
19384 });
19385 let mut result = None;
19386 {
19387 let mut future = std::pin::pin!(async {
19388 result = Some(handle.await.unwrap());
19389 });
19390 assert_eq!(
19391 descendant_executor.block_on(future.as_mut()),
19392 BlockOnOutcome::Completed
19393 );
19394 }
19395 descendant_results.lock().unwrap().push(result.unwrap());
19396 });
19397 }
19398 drop(descendant_ready_tx);
19399 for _ in 0..2 {
19400 descendant_ready_rx
19401 .recv_timeout(Duration::from_secs(2))
19402 .expect("both descendants must be stolen before the owner leaves its Rayon scope");
19403 }
19404 });
19405 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19406 });
19407
19408 let first = job_started_rx
19409 .recv_timeout(Duration::from_secs(2))
19410 .expect("one exact dependency must borrow the owner lane");
19411 assert!(
19412 job_started_rx
19413 .recv_timeout(Duration::from_millis(100))
19414 .is_err(),
19415 "one owner frame must not service two dependencies concurrently"
19416 );
19417 let unrelated_parker = Arc::new(DriverParker::default());
19418 executor
19419 .parked_drivers
19420 .register_with_role(&unrelated_parker, true, None);
19421 {
19422 let (released, changed) = &*releases;
19423 released.lock().unwrap()[first] = true;
19424 changed.notify_all();
19425 }
19426
19427 let second = job_started_rx
19428 .recv_timeout(Duration::from_secs(2))
19429 .expect("completion must rearm one parked same-owner dependency");
19430 assert_ne!(second, first);
19431 assert!(
19432 !unrelated_parker.consume_permit(),
19433 "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
19434 );
19435 executor.parked_drivers.deregister(&unrelated_parker);
19436 {
19437 let (released, changed) = &*releases;
19438 released.lock().unwrap()[second] = true;
19439 changed.notify_all();
19440 }
19441
19442 let mut results = futures::executor::block_on(owner).unwrap();
19443 results.sort_unstable();
19444 assert_eq!(results, vec![0, 1]);
19445 }
19446
19447 #[cfg(not(target_family = "wasm"))]
19448 #[test]
19449 fn failed_exact_claim_rearms_parked_same_owner_dependency() {
19450 let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
19451 let owner = executor.fresh_owner_token();
19452 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19453 let failed_dependency = Arc::new(TaskDependency::default());
19454 failed_dependency.set_owned(BlockingDependency {
19455 job: BlockingJobId(u64::MAX - 21),
19456 owner: Some(owner),
19457 });
19458 let parked_dependency = Arc::new(TaskDependency::default());
19459 parked_dependency.set_owned(BlockingDependency {
19460 job: BlockingJobId(u64::MAX - 22),
19461 owner: Some(owner),
19462 });
19463 let parker = Arc::new(DriverParker::default());
19464
19465 let queue = executor.blocking_queue.lock().unwrap();
19466 let claimant_executor = Arc::clone(&executor);
19467 let claimant_dependency = Arc::clone(&failed_dependency);
19468 let claimant = std::thread::spawn(move || {
19469 let _owner = BlockingOwnerGuard::enter(Some(owner));
19470 claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
19471 });
19472 wait_until("the failed claimant to reserve the owner lane", || {
19473 !executor.active_blocking_owners.is_available(owner)
19474 });
19475 executor
19476 .parked_drivers
19477 .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
19478 failed_dependency.clear();
19479 drop(queue);
19480
19481 assert!(
19482 !claimant.join().unwrap(),
19483 "the cancelled dependency must not execute"
19484 );
19485 assert!(
19486 parker.consume_permit(),
19487 "releasing a failed exact claim must hand the owner lane to another parked dependency"
19488 );
19489 }
19490
19491 #[cfg(not(target_family = "wasm"))]
19492 #[test]
19493 fn exact_owner_lending_does_not_start_blocking_after_stop() {
19494 use std::sync::mpsc;
19495
19496 let options = RuntimeOptions {
19497 flavor: RuntimeFlavor::MultiThread,
19498 worker_threads: 2,
19499 max_blocking_tasks: 1,
19500 thread_name_prefix: "stopped-exact-owner".to_string(),
19501 park_deadline: None,
19502 drain_linger: DEFAULT_DRAIN_LINGER,
19503 };
19504 let executor =
19505 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19506 let ran = Arc::new(AtomicBool::new(false));
19507 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
19508 let (release_claim_tx, release_claim_rx) = mpsc::channel();
19509 let owner_executor = Arc::clone(&executor);
19510 let child_ran = Arc::clone(&ran);
19511 let owner = executor.schedule_blocking(move || {
19512 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
19513 *slot.borrow_mut() = Some(Box::new(move || {
19514 claim_entered_tx.send(()).unwrap();
19515 release_claim_rx.recv().unwrap();
19516 }));
19517 });
19518 let child = owner_executor.schedule_blocking(move || {
19519 child_ran.store(true, Ordering::SeqCst);
19520 });
19521 let mut future = std::pin::pin!(async move {
19522 let _ = child.await;
19523 });
19524 owner_executor.block_on(future.as_mut())
19525 });
19526
19527 claim_entered_rx
19528 .recv_timeout(Duration::from_secs(2))
19529 .expect("the cooperative driver must pause before the exact-owner queue claim");
19530 assert_eq!(
19531 executor.blocking_queue.lock().unwrap().jobs.len(),
19532 1,
19533 "the exact dependency must remain queued before stop publication"
19534 );
19535
19536 executor.stop.begin_shutdown();
19539 release_claim_tx.send(()).unwrap();
19540
19541 assert_eq!(
19542 futures::executor::block_on(owner).unwrap(),
19543 BlockOnOutcome::Stopped,
19544 "the cooperative driver must stop instead of executing its queued dependency"
19545 );
19546 assert!(
19547 !ran.load(Ordering::SeqCst),
19548 "exact-owner lending started blocking work after stop"
19549 );
19550 assert_eq!(
19551 executor.blocking_queue.lock().unwrap().jobs.len(),
19552 1,
19553 "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
19554 );
19555
19556 executor.begin_shutdown();
19557 executor.wait_until_scheduler_idle();
19558 assert!(executor.blocking_queue.lock().unwrap().is_empty());
19559 assert!(!ran.load(Ordering::SeqCst));
19560 }
19561
19562 #[cfg(not(target_family = "wasm"))]
19563 #[test]
19564 fn exact_owner_lending_attempts_scale_linearly_with_topology() {
19565 const DEPENDENCY_COUNT: usize = 1024;
19566
19567 let options = RuntimeOptions {
19568 flavor: RuntimeFlavor::MultiThread,
19569 worker_threads: 2,
19570 max_blocking_tasks: 1,
19571 thread_name_prefix: "linear-owner-lending".to_string(),
19572 park_deadline: None,
19573 drain_linger: DEFAULT_DRAIN_LINGER,
19574 };
19575 let executor =
19576 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19577 let completed = Arc::new(AtomicUsize::new(0));
19578
19579 for index in 0..DEPENDENCY_COUNT {
19580 let owner = executor.fresh_owner_token();
19581 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19582 let dependency = TaskDependency::new(u64::MAX, Some(owner));
19583 let blocking = BlockingDependency {
19584 job: BlockingJobId(u64::MAX - index as u64),
19585 owner: Some(owner),
19586 };
19587 dependency.set_owned(blocking);
19588 let completed = Arc::clone(&completed);
19589 executor
19590 .blocking_queue
19591 .lock()
19592 .unwrap()
19593 .push(QueuedBlocking {
19594 id: blocking.job,
19595 run: Box::new(move || {
19596 completed.fetch_add(1, Ordering::SeqCst);
19597 }),
19598 });
19599
19600 let _owner = BlockingOwnerGuard::enter(Some(owner));
19601 assert!(executor.try_owned_blocking_over_cap(&dependency));
19602 }
19603
19604 assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
19605 assert_eq!(
19606 executor.owner_lending_attempts.load(Ordering::Relaxed),
19607 DEPENDENCY_COUNT,
19608 "targeted lending must perform one O(1) claim attempt per exact dependency"
19609 );
19610 let queue = executor.blocking_queue.lock().unwrap();
19611 assert!(queue.is_empty());
19612 assert_eq!(queue.head, None);
19613 assert_eq!(queue.tail, None);
19614 }
19615
19616 #[cfg(not(target_family = "wasm"))]
19617 #[test]
19618 fn one_dependency_interspersed_owner_publications_stays_linear() {
19619 const PUBLICATION_COUNT: usize = 1024;
19620
19621 let executor = multi_thread_executor(2, None, "linear-publication-lending");
19622 let owner = executor.fresh_owner_token();
19623 let wrong_owner = executor.fresh_owner_token();
19624 let dependency = TaskDependency::new(u64::MAX, Some(owner));
19625 let completed = Arc::new(AtomicUsize::new(0));
19626 for index in 0..PUBLICATION_COUNT {
19627 let cancelled_source = TaskDependency::default();
19628 cancelled_source.set_owned(BlockingDependency {
19629 job: BlockingJobId(u64::MAX - 100_000 - index as u64),
19630 owner: Some(owner),
19631 });
19632 dependency.set_borrowed(
19633 &cancelled_source
19634 .get()
19635 .expect("the source publication must initially be live"),
19636 );
19637 cancelled_source.clear();
19638
19639 dependency.set_owned(BlockingDependency {
19640 job: BlockingJobId(u64::MAX - 200_000 - index as u64),
19641 owner: Some(wrong_owner),
19642 });
19643 let blocking = BlockingDependency {
19644 job: BlockingJobId(u64::MAX - 1_000 - index as u64),
19645 owner: Some(owner),
19646 };
19647 dependency.set_owned(blocking);
19648 let completed = Arc::clone(&completed);
19649 executor
19650 .blocking_queue
19651 .lock()
19652 .unwrap()
19653 .push(QueuedBlocking {
19654 id: blocking.job,
19655 run: Box::new(move || {
19656 completed.fetch_add(1, Ordering::SeqCst);
19657 }),
19658 });
19659 }
19660 dependency.reset_entry_visits();
19661
19662 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19663 let _owner = BlockingOwnerGuard::enter(Some(owner));
19664 for _ in 0..PUBLICATION_COUNT {
19665 assert!(
19666 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19667 "the owner-handoff predicate must find the next exact publication"
19668 );
19669 assert!(
19670 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19671 "the park-availability predicate must find the next serviceable publication"
19672 );
19673 assert!(executor.try_owned_blocking_over_cap(&dependency));
19674 }
19675
19676 assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
19677 assert!(
19678 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19679 "wrong-owner publications must not satisfy an exact handoff"
19680 );
19681 assert!(
19682 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19683 "wrong-owner publications must not make the owner's lane appear serviceable"
19684 );
19685 assert!(
19686 dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
19687 "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
19688 dependency.entry_visits()
19689 );
19690 assert!(executor.blocking_queue.lock().unwrap().is_empty());
19691 }
19692
19693 #[cfg(not(target_family = "wasm"))]
19694 #[test]
19695 fn binding_unowned_dependency_updates_exact_owner_predicate() {
19696 let owner = BlockingOwnerToken {
19697 executor_id: u64::MAX - 200,
19698 frame: u64::MAX - 201,
19699 };
19700 let dependency = TaskDependency::default();
19701 let blocking = BlockingDependency {
19702 job: BlockingJobId(u64::MAX - 202),
19703 owner: None,
19704 };
19705 dependency.set_owned(blocking);
19706
19707 assert!(
19708 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19709 "an unowned publication must not satisfy an exact owner handoff"
19710 );
19711 assert!(
19712 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19713 "an unowned publication is available to the ambient owner"
19714 );
19715
19716 let selected = dependency
19717 .select_for_owner(owner)
19718 .expect("the owner must bind the unowned publication");
19719 assert_eq!(selected.publication.dependency.owner, Some(owner));
19720 assert!(
19721 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19722 "binding must immediately publish the entry to exact-owner predicates"
19723 );
19724 assert!(dependency.try_claim(&selected));
19725 assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
19726 }
19727
19728 #[cfg(not(target_family = "wasm"))]
19729 #[test]
19730 fn owner_selection_preserves_unowned_and_exact_publication_order() {
19731 let owner = BlockingOwnerToken {
19732 executor_id: u64::MAX - 210,
19733 frame: u64::MAX - 211,
19734 };
19735 let dependency = TaskDependency::default();
19736 let first = BlockingDependency {
19737 job: BlockingJobId(u64::MAX - 212),
19738 owner: None,
19739 };
19740 let second = BlockingDependency {
19741 job: BlockingJobId(u64::MAX - 213),
19742 owner: Some(owner),
19743 };
19744 dependency.set_owned(first);
19745 dependency.set_owned(second);
19746
19747 let selected = dependency
19748 .select_for_owner(owner)
19749 .expect("the first publication must be found");
19750 assert_eq!(
19751 selected.publication.dependency,
19752 BlockingDependency {
19753 owner: Some(owner),
19754 ..first
19755 }
19756 );
19757 assert!(dependency.try_claim(&selected));
19758
19759 let selected = dependency
19760 .select_for_owner(owner)
19761 .expect("the second publication must remain eligible");
19762 assert_eq!(selected.publication.dependency, second);
19763 }
19764
19765 #[cfg(not(target_family = "wasm"))]
19766 #[test]
19767 fn blocking_queue_exact_removal_reclaims_fifo_links() {
19768 const JOB_COUNT: u64 = 4096;
19769
19770 let mut queue = BlockingQueue {
19771 closed: false,
19772 head: None,
19773 tail: None,
19774 jobs: FxHashMap::default(),
19775 };
19776 for id in 1..=JOB_COUNT {
19777 queue.push(QueuedBlocking {
19778 id: BlockingJobId(id),
19779 run: Box::new(|| {}),
19780 });
19781 }
19782 for id in (2..=JOB_COUNT).step_by(2) {
19783 drop(
19784 queue
19785 .remove(BlockingJobId(id))
19786 .expect("even job must remain indexed"),
19787 );
19788 }
19789 for id in (1..=JOB_COUNT).step_by(2) {
19790 drop(
19791 queue
19792 .remove(BlockingJobId(id))
19793 .expect("odd job must remain indexed"),
19794 );
19795 }
19796 assert!(queue.jobs.is_empty());
19797 assert_eq!(queue.head, None);
19798 assert_eq!(queue.tail, None);
19799
19800 let order = Arc::new(Mutex::new(Vec::new()));
19801 for id in 1..=3 {
19802 let order = Arc::clone(&order);
19803 queue.push(QueuedBlocking {
19804 id: BlockingJobId(id),
19805 run: Box::new(move || order.lock().unwrap().push(id)),
19806 });
19807 }
19808 drop(
19809 queue
19810 .remove(BlockingJobId(2))
19811 .expect("middle job must be removable"),
19812 );
19813 queue.pop_front().expect("FIFO head must remain linked")();
19814 queue.pop_front().expect("FIFO tail must remain linked")();
19815 assert!(queue.pop_front().is_none());
19816 assert_eq!(*order.lock().unwrap(), vec![1, 3]);
19817 assert_eq!(queue.head, None);
19818 assert_eq!(queue.tail, None);
19819 }
19820
19821 #[cfg(not(target_family = "wasm"))]
19822 #[test]
19823 fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
19824 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
19825
19826 if std::env::var_os(CHILD_ENV).is_some() {
19827 configure(RuntimeOptions {
19828 flavor: RuntimeFlavor::MultiThread,
19829 worker_threads: 2,
19830 max_blocking_tasks: 1,
19831 thread_name_prefix: "dependency-lending-metrics".to_string(),
19832 park_deadline: None,
19833 drain_linger: DEFAULT_DRAIN_LINGER,
19834 })
19835 .unwrap();
19836
19837 let outer = spawn_blocking(|| {
19838 let inner = spawn_blocking(|| 42usize);
19839 block_on(inner).unwrap()
19840 });
19841 assert_eq!(block_on(outer).unwrap(), 42);
19842
19843 shutdown().unwrap();
19848 let snapshot = metrics();
19849 assert_eq!(snapshot.blocking_tasks_started, 2);
19850 assert_eq!(snapshot.blocking_tasks_completed, 2);
19851 assert_eq!(snapshot.active_blocking_tasks, 0);
19852 assert_eq!(
19853 snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
19854 "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
19855 );
19856 return;
19857 }
19858
19859 let output = std::process::Command::new(std::env::current_exe().unwrap())
19860 .arg("--exact")
19861 .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
19862 .arg("--nocapture")
19863 .env(CHILD_ENV, "1")
19864 .output()
19865 .expect("the dependency-lending metrics subprocess must start");
19866 assert!(
19867 output.status.success(),
19868 "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
19869 output.status.code(),
19870 String::from_utf8_lossy(&output.stdout),
19871 String::from_utf8_lossy(&output.stderr)
19872 );
19873 }
19874
19875 #[cfg(not(target_family = "wasm"))]
19876 #[test]
19877 fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
19878 use std::sync::mpsc;
19879
19880 let (done_tx, done_rx) = mpsc::channel();
19881 let runner = std::thread::spawn(move || {
19882 let options = RuntimeOptions {
19883 flavor: RuntimeFlavor::MultiThread,
19884 worker_threads: 2,
19885 max_blocking_tasks: 1,
19886 thread_name_prefix: "exact-blocking-job".to_string(),
19887 park_deadline: None,
19888 drain_linger: DEFAULT_DRAIN_LINGER,
19889 };
19890 let executor =
19891 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19892 let outer_exec = Arc::clone(&executor);
19893 let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
19894 let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
19895 let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
19896 let outer = executor.schedule_blocking(move || {
19897 let sibling = outer_exec.schedule_blocking(move || {
19898 sibling_started_tx.send(()).unwrap();
19899 release_sibling_rx.recv().unwrap();
19900 });
19901 let awaited = outer_exec.schedule_blocking(move || {
19902 awaited_ran_tx.send(()).unwrap();
19903 17usize
19904 });
19905 let mut value = None;
19906 {
19907 let mut future = std::pin::pin!(async {
19908 value = Some(awaited.await.unwrap());
19909 });
19910 outer_exec.block_on(future.as_mut());
19911 }
19912 (sibling, value.unwrap())
19913 });
19914
19915 awaited_ran_rx
19916 .recv_timeout(Duration::from_secs(2))
19917 .expect("the exact awaited job must bypass the earlier detached sibling");
19918 let (sibling, value) = futures::executor::block_on(outer).unwrap();
19919 assert_eq!(value, 17);
19920 sibling_started_rx
19921 .recv_timeout(Duration::from_secs(2))
19922 .expect("the sibling must run after the owner releases its counted slot");
19923 release_sibling_tx.send(()).unwrap();
19924 futures::executor::block_on(sibling).unwrap();
19925 done_tx.send(()).unwrap();
19926 });
19927 done_rx
19928 .recv_timeout(Duration::from_secs(5))
19929 .expect("same-owner sibling selection deadlocked the exact awaited job");
19930 runner.join().unwrap();
19931 }
19932
19933 #[cfg(not(target_family = "wasm"))]
19934 #[test]
19935 fn blocking_dependency_propagates_through_async_join_lineage() {
19936 use std::sync::mpsc;
19937
19938 let (done_tx, done_rx) = mpsc::channel();
19939 let runner = std::thread::spawn(move || {
19940 let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
19941 let owner_controller = Arc::clone(&controller);
19942 let (child_tx, child_rx) = mpsc::channel();
19943 let outer = controller
19944 .try_spawn_blocking(move || {
19945 let async_controller = Arc::clone(&owner_controller);
19946 let async_child = owner_controller
19947 .try_spawn(async move {
19948 async_controller
19949 .try_spawn_blocking(move || {
19950 child_tx.send(()).unwrap();
19951 23usize
19952 })
19953 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
19954 .await
19955 .unwrap()
19956 })
19957 .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
19958 let mut value = None;
19959 {
19960 let mut future = std::pin::pin!(async {
19961 value = Some(async_child.await.unwrap());
19962 });
19963 owner_controller.block_on(future.as_mut());
19964 }
19965 value.unwrap()
19966 })
19967 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19968
19969 child_rx
19970 .recv_timeout(Duration::from_secs(2))
19971 .expect("the async descendant's exact blocking dependency must inherit the owner lane");
19972 assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
19973 controller.shutdown().unwrap();
19974 done_tx.send(()).unwrap();
19975 });
19976 done_rx
19977 .recv_timeout(Duration::from_secs(5))
19978 .expect("blocking dependency lineage deadlocked");
19979 runner.join().unwrap();
19980 }
19981
19982 #[cfg(not(target_family = "wasm"))]
19983 #[test]
19984 fn blocking_dependency_does_not_escape_a_serviced_runnable() {
19985 use std::sync::mpsc;
19986
19987 let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
19988 let outer_controller = Arc::clone(&controller);
19989 let (child_queued_tx, child_queued_rx) = mpsc::channel();
19990 let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
19991 let (release_outer_tx, release_outer_rx) = oneshot::channel();
19992 let outer = controller
19993 .try_spawn_blocking(move || {
19994 let child_controller = Arc::clone(&outer_controller);
19995 let child = outer_controller
19996 .try_spawn(async move {
19997 let blocking = child_controller
19998 .try_spawn_blocking(move || {
19999 unrelated_ran_tx.send(()).unwrap();
20000 31usize
20001 })
20002 .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
20003 child_queued_tx.send(()).unwrap();
20004 blocking.await.unwrap()
20005 })
20006 .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
20007 let mut released = false;
20008 {
20009 let mut future = std::pin::pin!(async {
20010 release_outer_rx.await.unwrap();
20011 released = true;
20012 });
20013 outer_controller.block_on(future.as_mut());
20014 }
20015 assert!(released);
20016 child
20017 })
20018 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
20019
20020 child_queued_rx
20021 .recv_timeout(Duration::from_secs(2))
20022 .expect("the cooperative driver must service the unrelated async runnable");
20023 assert!(
20024 unrelated_ran_rx
20025 .recv_timeout(Duration::from_millis(200))
20026 .is_err(),
20027 "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
20028 );
20029
20030 release_outer_tx.send(()).unwrap();
20031 let child = futures::executor::block_on(outer).unwrap();
20032 unrelated_ran_rx
20033 .recv_timeout(Duration::from_secs(2))
20034 .expect("the unrelated blocking job must run after the owner releases its counted slot");
20035 assert_eq!(futures::executor::block_on(child).unwrap(), 31);
20036 controller.shutdown().unwrap();
20037 }
20038
20039 #[cfg(not(target_family = "wasm"))]
20040 #[test]
20041 fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
20042 let parent = Arc::new(TaskDependency::default());
20043 let _context = TaskDependencyGuard::enter(&parent);
20044 let dependency = BlockingDependency {
20045 job: BlockingJobId(u64::MAX),
20046 owner: None,
20047 };
20048 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20049 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20050 receiver,
20051 dependency,
20052 published: PublishedBlockingDependencies::default(),
20053 });
20054 let waker = futures::task::noop_waker();
20055 let mut cx = Context::from_waker(&waker);
20056
20057 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20058 assert_eq!(parent.get_dependency(), Some(dependency));
20059 drop(handle);
20060 assert_eq!(
20061 parent.get_dependency(),
20062 None,
20063 "an abandoned JoinHandle must clear stale dependency lineage"
20064 );
20065 drop(sender);
20066 }
20067
20068 #[cfg(not(target_family = "wasm"))]
20069 #[test]
20070 fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
20071 let owner = BlockingOwnerToken {
20072 executor_id: u64::MAX - 40,
20073 frame: u64::MAX - 41,
20074 };
20075 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20076 let _context = TaskDependencyGuard::enter(&parent);
20077 let dependency = BlockingDependency {
20078 job: BlockingJobId(u64::MAX - 42),
20079 owner: None,
20080 };
20081 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20082 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20083 receiver,
20084 dependency,
20085 published: PublishedBlockingDependencies::default(),
20086 });
20087 let waker = futures::task::noop_waker();
20088 let mut cx = Context::from_waker(&waker);
20089
20090 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20091 assert_eq!(
20092 parent.get_dependency(),
20093 Some(BlockingDependency {
20094 owner: Some(owner),
20095 ..dependency
20096 }),
20097 "the ambient task owner must enrich an untagged direct blocking dependency"
20098 );
20099 drop(handle);
20100 assert_eq!(
20101 parent.get_dependency(),
20102 None,
20103 "detach cleanup must match the stable job id after owner enrichment"
20104 );
20105 drop(sender);
20106 }
20107
20108 #[cfg(not(target_family = "wasm"))]
20109 #[test]
20110 fn one_poll_preserves_every_observed_blocking_dependency() {
20111 let parent = Arc::new(TaskDependency::default());
20112 let _context = TaskDependencyGuard::enter(&parent);
20113 let first = BlockingDependency {
20114 job: BlockingJobId(u64::MAX - 60),
20115 owner: None,
20116 };
20117 let second = BlockingDependency {
20118 job: BlockingJobId(u64::MAX - 61),
20119 owner: None,
20120 };
20121 let (first_sender, first_receiver) =
20122 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20123 let (second_sender, second_receiver) =
20124 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20125 let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
20126 receiver: first_receiver,
20127 dependency: first,
20128 published: PublishedBlockingDependencies::default(),
20129 });
20130 let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
20131 receiver: second_receiver,
20132 dependency: second,
20133 published: PublishedBlockingDependencies::default(),
20134 });
20135 let waker = futures::task::noop_waker();
20136 let mut cx = Context::from_waker(&waker);
20137
20138 assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
20139 assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
20140 assert_eq!(
20141 parent.get_dependencies(),
20142 vec![first, second],
20143 "publishing a later dependency in one user poll must not replace an earlier dependency"
20144 );
20145
20146 drop(first_handle);
20147 drop(second_handle);
20148 drop(first_sender);
20149 drop(second_sender);
20150 }
20151
20152 #[cfg(not(target_family = "wasm"))]
20153 #[test]
20154 fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
20155 let owner = BlockingOwnerToken {
20156 executor_id: u64::MAX - 900,
20157 frame: u64::MAX - 901,
20158 };
20159 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20160 let _context = TaskDependencyGuard::enter(&parent);
20161 let waker = futures::task::noop_waker();
20162 let mut cx = Context::from_waker(&waker);
20163
20164 let j1 = BlockingDependency {
20165 job: BlockingJobId(u64::MAX - 910),
20166 owner: Some(owner),
20167 };
20168 let j2 = BlockingDependency {
20169 job: BlockingJobId(u64::MAX - 911),
20170 owner: Some(owner),
20171 };
20172
20173 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20174 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20175
20176 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20177 receiver: r1,
20178 dependency: j1,
20179 published: PublishedBlockingDependencies::default(),
20180 });
20181 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20184 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20185 let entries_after_double_poll = parent
20186 .state
20187 .lock()
20188 .unwrap_or_else(std::sync::PoisonError::into_inner)
20189 .current
20190 .len();
20191 drop(h1);
20192
20193 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20194 receiver: r2,
20195 dependency: j2,
20196 published: PublishedBlockingDependencies::default(),
20197 });
20198 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20199
20200 assert_eq!(
20201 entries_after_double_poll, 1,
20202 "a repeat Pending poll must supersede the handle's own previous publication"
20203 );
20204 let selected = parent
20205 .select_for_owner(owner)
20206 .expect("a live dependency must be selectable");
20207 assert_eq!(
20208 selected.publication.dependency.job, j2.job,
20209 "a DETACHED job must not be selected for owner lending ahead of the live one"
20210 );
20211 drop((s1, s2));
20212 }
20213
20214 #[cfg(not(target_family = "wasm"))]
20215 #[test]
20216 fn handle_polled_under_a_nested_frame_retires_both_publications() {
20217 let owner = BlockingOwnerToken {
20218 executor_id: u64::MAX - 920,
20219 frame: u64::MAX - 921,
20220 };
20221 let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20222 let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20223 let waker = futures::task::noop_waker();
20224 let mut cx = Context::from_waker(&waker);
20225
20226 let j1 = BlockingDependency {
20227 job: BlockingJobId(u64::MAX - 930),
20228 owner: Some(owner),
20229 };
20230 let j2 = BlockingDependency {
20231 job: BlockingJobId(u64::MAX - 931),
20232 owner: Some(owner),
20233 };
20234 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20235 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20236 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20237 receiver: r1,
20238 dependency: j1,
20239 published: Default::default(),
20240 });
20241
20242 {
20243 let _outer_ctx = TaskDependencyGuard::enter(&outer);
20244 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20248 {
20249 let _inner_ctx = TaskDependencyGuard::enter(&inner);
20250 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20251 }
20252 drop(h1);
20253 assert!(
20254 outer.get_dependencies().is_empty(),
20255 "detaching must retire the publication made in the outer frame too"
20256 );
20257 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20258 receiver: r2,
20259 dependency: j2,
20260 published: Default::default(),
20261 });
20262 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20263 let selected = outer.select_for_owner(owner).expect("selectable");
20264 assert_eq!(
20265 selected.publication.dependency.job, j2.job,
20266 "a detached job published in an outer frame must not outrank a live one"
20267 );
20268 drop(h2);
20269 }
20270 drop((s1, s2));
20271 }
20272
20273 #[cfg(not(target_family = "wasm"))]
20274 #[test]
20275 fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
20276 const FRAME_COUNT: usize = 64;
20277 let waker = futures::task::noop_waker();
20278 let mut cx = Context::from_waker(&waker);
20279 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20280 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20281 receiver,
20282 dependency: BlockingDependency {
20283 job: BlockingJobId(u64::MAX - 940),
20284 owner: None,
20285 },
20286 published: PublishedBlockingDependencies::default(),
20287 });
20288 let mut frames = Vec::new();
20289 for _ in 0..FRAME_COUNT {
20290 let frame = Arc::new(TaskDependency::default());
20291 frames.push(Arc::downgrade(&frame));
20292 let _context = TaskDependencyGuard::enter(&frame);
20293 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20294 }
20295 let retained = frames
20300 .iter()
20301 .filter(|frame| frame.strong_count() > 0)
20302 .count();
20303 assert_eq!(
20304 retained, 0,
20305 "a completed frame must not be kept alive by a pending handle's publication history"
20306 );
20307 drop(handle);
20308 drop(sender);
20309 }
20310
20311 #[cfg(not(target_family = "wasm"))]
20312 #[test]
20313 fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
20314 let owner = BlockingOwnerToken {
20319 executor_id: u64::MAX - 950,
20320 frame: u64::MAX - 951,
20321 };
20322 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20323 let _context = TaskDependencyGuard::enter(&parent);
20324 let waker = futures::task::noop_waker();
20325 let mut cx = Context::from_waker(&waker);
20326
20327 let j1 = BlockingDependency {
20328 job: BlockingJobId(u64::MAX - 960),
20329 owner: Some(owner),
20330 };
20331 let j2 = BlockingDependency {
20332 job: BlockingJobId(u64::MAX - 961),
20333 owner: Some(owner),
20334 };
20335 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20336 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20337 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20338 receiver: r1,
20339 dependency: j1,
20340 published: PublishedBlockingDependencies::default(),
20341 });
20342 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20343 receiver: r2,
20344 dependency: j2,
20345 published: PublishedBlockingDependencies::default(),
20346 });
20347
20348 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20349 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20350 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20351
20352 let selected = parent
20353 .select_for_owner(owner)
20354 .expect("a live dependency must be selectable");
20355 assert_eq!(
20356 selected.publication.dependency.job, j1.job,
20357 "re-polling a handle must not push its dependency behind one published later"
20358 );
20359 drop((h1, h2, s1, s2));
20360 }
20361
20362 #[cfg(not(target_family = "wasm"))]
20363 #[test]
20364 fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
20365 const FRAME_COUNT: usize = 256;
20372 let waker = futures::task::noop_waker();
20373 let mut cx = Context::from_waker(&waker);
20374 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20375 let mut handle = JoinHandle(JoinHandleInner::Blocking {
20376 receiver,
20377 dependency: BlockingDependency {
20378 job: BlockingJobId(u64::MAX - 970),
20379 owner: None,
20380 },
20381 published: PublishedBlockingDependencies::default(),
20382 });
20383 let mut retained = Vec::new();
20384 for _ in 0..FRAME_COUNT {
20385 let frame = Arc::new(TaskDependency::default());
20386 retained.push(Arc::clone(&frame));
20387 let _context = TaskDependencyGuard::enter(&frame);
20388 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20389 frame.begin_poll();
20391 }
20392 let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
20393 panic!("the handle must still be a blocking handle");
20394 };
20395 let remembered = usize::from(published.first.is_some()) + published.rest.len();
20396 assert!(
20397 remembered <= 2,
20398 "a drained frame must not stay in the publication history, remembered={remembered}"
20399 );
20400 drop(handle);
20401 drop(sender);
20402 }
20403
20404 #[cfg(not(target_family = "wasm"))]
20405 #[test]
20406 fn mass_detach_of_pending_blocking_handles_stays_linear() {
20407 const HANDLE_COUNT: usize = 512;
20418
20419 let parent = Arc::new(TaskDependency::default());
20420 let _context = TaskDependencyGuard::enter(&parent);
20421 let waker = futures::task::noop_waker();
20422 let mut cx = Context::from_waker(&waker);
20423
20424 let mut senders = Vec::with_capacity(HANDLE_COUNT);
20425 let mut handles = Vec::with_capacity(HANDLE_COUNT);
20426 for index in 0..HANDLE_COUNT {
20427 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20428 senders.push(sender);
20429 handles.push(JoinHandle(JoinHandleInner::Blocking {
20430 receiver,
20431 dependency: BlockingDependency {
20432 job: BlockingJobId(u64::MAX - 300_000 - index as u64),
20433 owner: None,
20434 },
20435 published: PublishedBlockingDependencies::default(),
20436 }));
20437 }
20438 for handle in &mut handles {
20439 assert!(Pin::new(handle).poll(&mut cx).is_pending());
20440 }
20441 assert_eq!(
20442 parent
20443 .state
20444 .lock()
20445 .unwrap_or_else(std::sync::PoisonError::into_inner)
20446 .current
20447 .len(),
20448 HANDLE_COUNT,
20449 "every handle must be registered and still pending before the detach"
20450 );
20451
20452 parent.reset_entry_visits();
20453 drop(handles);
20454
20455 assert!(
20456 parent.entry_visits() <= HANDLE_COUNT * 4,
20457 "mass-detach must visit O(n) entries across n handles, visits={}",
20458 parent.entry_visits()
20459 );
20460 assert_eq!(
20461 parent.get_dependency(),
20462 None,
20463 "every detached handle must have retired its own publication"
20464 );
20465 drop(senders);
20466 }
20467
20468 #[cfg(not(target_family = "wasm"))]
20469 #[test]
20470 fn dependency_tls_borrow_is_released_before_reentrant_wake() {
20471 use std::sync::mpsc;
20472
20473 struct ReenterDependencyContext {
20474 dependency: Arc<TaskDependency>,
20475 woke: mpsc::Sender<()>,
20476 }
20477
20478 impl std::task::Wake for ReenterDependencyContext {
20479 fn wake(self: Arc<Self>) {
20480 let _context = TaskDependencyGuard::enter(&self.dependency);
20481 self.woke.send(()).unwrap();
20482 }
20483 }
20484
20485 let parent = Arc::new(TaskDependency::default());
20486 let nested = Arc::new(TaskDependency::default());
20487 let (woke_tx, woke_rx) = mpsc::channel();
20488 let waiter = Waker::from(Arc::new(ReenterDependencyContext {
20489 dependency: Arc::clone(&nested),
20490 woke: woke_tx,
20491 }));
20492 parent.register_waiter(&waiter);
20493 let blocking = BlockingDependency {
20494 job: BlockingJobId(u64::MAX - 62),
20495 owner: None,
20496 };
20497
20498 let _context = TaskDependencyGuard::enter(&parent);
20499 let mut published = PublishedBlockingDependencies::default();
20500 record_blocking_dependency(&mut published, blocking);
20501 woke_rx
20502 .recv_timeout(Duration::from_secs(1))
20503 .expect("dependency notification must permit reentrant TLS context access");
20504
20505 parent.register_waiter(&waiter);
20506 clear_blocking_dependency(published);
20507 woke_rx
20508 .recv_timeout(Duration::from_secs(1))
20509 .expect("dependency clearing must permit reentrant TLS context access");
20510 }
20511
20512 #[cfg(not(target_family = "wasm"))]
20513 #[test]
20514 fn dependency_free_begin_poll_does_not_lock_state() {
20515 use std::sync::mpsc;
20516
20517 let dependency = Arc::new(TaskDependency::default());
20518 let state = dependency.state.lock().unwrap();
20519 let (completed_tx, completed_rx) = mpsc::channel();
20520 let polling_dependency = Arc::clone(&dependency);
20521 let polling = std::thread::spawn(move || {
20522 polling_dependency.begin_poll();
20523 completed_tx.send(()).unwrap();
20524 });
20525
20526 let completed_without_lock = completed_rx
20527 .recv_timeout(Duration::from_millis(200))
20528 .is_ok();
20529 drop(state);
20530 polling.join().unwrap();
20531 assert!(
20532 completed_without_lock,
20533 "a task with no published dependency must skip the dependency-state mutex"
20534 );
20535 }
20536
20537 #[cfg(not(target_family = "wasm"))]
20538 #[test]
20539 fn deep_dependency_liveness_chain_is_iterative() {
20540 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
20541
20542 if std::env::var_os(CHILD_ENV).is_some() {
20543 const DEPTH: usize = 100_000;
20544
20545 let mut links = Vec::with_capacity(DEPTH + 1);
20546 links.push(DependencyLink::root());
20547 for _ in 0..DEPTH {
20548 links.push(DependencyLink::derived(links.last().unwrap()));
20549 }
20550 assert!(links.last().unwrap().is_live());
20551 links[0].cancel();
20552 assert!(!links.last().unwrap().is_live());
20553 return;
20554 }
20555
20556 let output = std::process::Command::new(std::env::current_exe().unwrap())
20557 .arg("--exact")
20558 .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
20559 .arg("--nocapture")
20560 .env(CHILD_ENV, "1")
20561 .output()
20562 .expect("the subprocess test must start");
20563 assert!(
20564 output.status.success(),
20565 "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
20566 output.status.code(),
20567 String::from_utf8_lossy(&output.stdout),
20568 String::from_utf8_lossy(&output.stderr)
20569 );
20570 }
20571
20572 #[cfg(not(target_family = "wasm"))]
20573 #[test]
20574 fn task_dependency_set_contains_panicking_waiter() {
20575 struct PanicWake;
20576 impl std::task::Wake for PanicWake {
20577 fn wake(self: Arc<Self>) {
20578 panic!("intentional TaskDependency::set waiter panic");
20579 }
20580 }
20581
20582 let dependency = TaskDependency::default();
20583 let blocking = BlockingDependency {
20584 job: BlockingJobId(u64::MAX),
20585 owner: None,
20586 };
20587 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20588
20589 let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
20590 assert!(
20591 result.is_ok(),
20592 "set notification must not unwind its caller"
20593 );
20594 assert_eq!(
20595 dependency.get_dependency(),
20596 Some(blocking),
20597 "set must commit before notifying its waiter"
20598 );
20599 }
20600
20601 #[cfg(not(target_family = "wasm"))]
20602 #[test]
20603 fn task_dependency_clear_contains_panicking_waiter() {
20604 struct PanicWake;
20605 impl std::task::Wake for PanicWake {
20606 fn wake(self: Arc<Self>) {
20607 panic!("intentional TaskDependency::clear waiter panic");
20608 }
20609 }
20610
20611 let dependency = TaskDependency::default();
20612 dependency.set_owned(BlockingDependency {
20613 job: BlockingJobId(u64::MAX),
20614 owner: None,
20615 });
20616 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20617
20618 let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
20619 assert!(
20620 result.is_ok(),
20621 "clear notification must not unwind its caller"
20622 );
20623 assert_eq!(
20624 dependency.get_dependency(),
20625 None,
20626 "clear must commit before notifying its waiter"
20627 );
20628 }
20629
20630 #[cfg(not(target_family = "wasm"))]
20631 #[test]
20632 fn task_dependency_clear_if_contains_panicking_waiter() {
20633 struct PanicWake;
20634 impl std::task::Wake for PanicWake {
20635 fn wake(self: Arc<Self>) {
20636 panic!("intentional TaskDependency::clear_if waiter panic");
20637 }
20638 }
20639
20640 let dependency = TaskDependency::default();
20641 let blocking = BlockingDependency {
20642 job: BlockingJobId(u64::MAX),
20643 owner: None,
20644 };
20645 let sequence = dependency.set_owned(blocking);
20646 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20647
20648 let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
20649 assert!(
20650 result.is_ok(),
20651 "retire notification must not unwind its caller"
20652 );
20653 assert_eq!(
20654 dependency.get_dependency(),
20655 None,
20656 "retire must commit before notifying its waiter"
20657 );
20658 }
20659
20660 #[cfg(not(target_family = "wasm"))]
20661 #[test]
20662 fn task_dependency_waiter_replacement_contains_panicking_drop() {
20663 use std::sync::mpsc;
20664
20665 const GENERATION: u64 = u64::MAX - 20;
20666
20667 struct PanicOnDropWake {
20668 dropped: mpsc::Sender<Option<u64>>,
20669 }
20670
20671 #[expect(
20672 clippy::manual_noop_waker,
20673 reason = "the test observes the waker payload's Drop"
20674 )]
20675 impl std::task::Wake for PanicOnDropWake {
20676 fn wake(self: Arc<Self>) {}
20677 }
20678
20679 impl Drop for PanicOnDropWake {
20680 fn drop(&mut self) {
20681 self
20682 .dropped
20683 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20684 .unwrap();
20685 panic!("intentional replaced dependency waiter destructor panic");
20686 }
20687 }
20688
20689 let dependency = TaskDependency::new(GENERATION, None);
20690 let (dropped_tx, dropped_rx) = mpsc::channel();
20691 let waiter = Waker::from(Arc::new(PanicOnDropWake {
20692 dropped: dropped_tx,
20693 }));
20694 dependency.register_waiter(&waiter);
20695 drop(waiter);
20696
20697 let replacement = futures::task::noop_waker();
20698 let result = catch_unwind(AssertUnwindSafe(|| {
20699 dependency.register_waiter(&replacement)
20700 }));
20701 assert!(
20702 result.is_ok(),
20703 "replacing a hostile waiter must not unwind registration"
20704 );
20705 assert_eq!(
20706 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20707 Some(GENERATION),
20708 "the replaced waiter must be destroyed under its runtime generation"
20709 );
20710 }
20711
20712 #[cfg(not(target_family = "wasm"))]
20713 #[test]
20714 fn task_dependency_retirement_contains_stored_waiter_drop() {
20715 use std::sync::mpsc;
20716
20717 const GENERATION: u64 = u64::MAX - 21;
20718
20719 struct PanicOnDropWake {
20720 dropped: mpsc::Sender<Option<u64>>,
20721 }
20722
20723 #[expect(
20724 clippy::manual_noop_waker,
20725 reason = "the test observes the waker payload's Drop"
20726 )]
20727 impl std::task::Wake for PanicOnDropWake {
20728 fn wake(self: Arc<Self>) {}
20729 }
20730
20731 impl Drop for PanicOnDropWake {
20732 fn drop(&mut self) {
20733 self
20734 .dropped
20735 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20736 .unwrap();
20737 panic!("intentional stored dependency waiter destructor panic");
20738 }
20739 }
20740
20741 let dependency = TaskDependency::new(GENERATION, None);
20742 let (dropped_tx, dropped_rx) = mpsc::channel();
20743 let waiter = Waker::from(Arc::new(PanicOnDropWake {
20744 dropped: dropped_tx,
20745 }));
20746 dependency.register_waiter(&waiter);
20747 drop(waiter);
20748
20749 let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
20750 assert!(
20751 result.is_ok(),
20752 "retiring a hostile stored waiter must not unwind"
20753 );
20754 assert_eq!(
20755 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20756 Some(GENERATION),
20757 "the retained waiter must be destroyed under its runtime generation"
20758 );
20759 }
20760
20761 #[cfg(not(target_family = "wasm"))]
20762 #[test]
20763 fn detaching_task_clears_dependency_waiter_parent_retention() {
20764 struct RetainingWake {
20765 retained_parent: Arc<()>,
20766 }
20767
20768 impl std::task::Wake for RetainingWake {
20769 fn wake(self: Arc<Self>) {
20770 let _ = &self.retained_parent;
20771 }
20772 }
20773
20774 let dependency = Arc::new(TaskDependency::default());
20775 let retained_parent = Arc::new(());
20776 let retained_parent_weak = Arc::downgrade(&retained_parent);
20777 let waiter = Waker::from(Arc::new(RetainingWake {
20778 retained_parent: Arc::clone(&retained_parent),
20779 }));
20780 dependency.register_waiter(&waiter);
20781 drop(waiter);
20782 drop(retained_parent);
20783 assert!(
20784 retained_parent_weak.upgrade().is_some(),
20785 "the stored dependency waiter must retain its parent before detachment"
20786 );
20787
20788 let (runnable, task) = async_task::spawn(
20789 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20790 |_| {},
20791 );
20792 drop(JoinHandle(JoinHandleInner::Task {
20793 task: task.fallible(),
20794 awaiter: None,
20795 dependency: Arc::clone(&dependency),
20796 }));
20797 assert!(
20798 retained_parent_weak.upgrade().is_none(),
20799 "detachment must clear the dependency waiter instead of retaining its parent task"
20800 );
20801 drop(runnable);
20802 }
20803
20804 #[cfg(not(target_family = "wasm"))]
20805 #[test]
20806 fn detaching_task_clears_many_parent_publications_linearly() {
20807 const PUBLICATION_COUNT: usize = 1024;
20808
20809 struct CountWake(Arc<AtomicUsize>);
20810
20811 impl std::task::Wake for CountWake {
20812 fn wake(self: Arc<Self>) {
20813 self.0.fetch_add(1, Ordering::SeqCst);
20814 }
20815 }
20816
20817 fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
20818 dependency
20819 .state
20820 .lock()
20821 .unwrap_or_else(std::sync::PoisonError::into_inner)
20822 .current
20823 .back()
20824 .expect("the sentinel publication must be appended")
20825 .publication
20826 .clone()
20827 }
20828
20829 let owner = BlockingOwnerToken {
20830 executor_id: u64::MAX - 300,
20831 frame: u64::MAX - 301,
20832 };
20833 let other_owner = BlockingOwnerToken {
20834 executor_id: u64::MAX - 302,
20835 frame: u64::MAX - 303,
20836 };
20837 let child = Arc::new(TaskDependency::default());
20838 for index in 0..PUBLICATION_COUNT {
20839 child.set_owned(BlockingDependency {
20840 job: BlockingJobId(1_000_000 + index as u64),
20841 owner: (index % 2 == 0).then_some(owner),
20842 });
20843 }
20844 let child_publications = child.snapshot_all_live();
20845 let source = child_publications[0].clone();
20846
20847 let parent = Arc::new(TaskDependency::default());
20848 let _context = TaskDependencyGuard::enter(&parent);
20849 let (runnable, task) = async_task::spawn(
20850 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20851 |_| {},
20852 );
20853 let mut handle = JoinHandle(JoinHandleInner::Task {
20854 task: task.fallible(),
20855 awaiter: None,
20856 dependency: Arc::clone(&child),
20857 });
20858 let waker = futures::task::noop_waker();
20859 let mut cx = Context::from_waker(&waker);
20860 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20861 assert_eq!(
20862 parent.get_dependencies().len(),
20863 PUBLICATION_COUNT,
20864 "polling the child handle must propagate every current publication"
20865 );
20866
20867 let mut wrong_claim_source = source.clone();
20868 wrong_claim_source.claim = Arc::new(DependencyClaim::new());
20869 parent.set_borrowed(&wrong_claim_source);
20870 let wrong_claim_publication = latest_publication(&parent);
20871
20872 let mut wrong_job_source = source.clone();
20873 wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
20874 parent.set_borrowed(&wrong_job_source);
20875 let wrong_job_publication = latest_publication(&parent);
20876
20877 let wrong_immediate_source = source.derived(None);
20878 parent.set_borrowed(&wrong_immediate_source);
20879 let wrong_immediate_publication = latest_publication(&parent);
20880
20881 let wakes = Arc::new(AtomicUsize::new(0));
20882 parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
20883 parent.reset_entry_visits();
20884 drop(handle);
20885
20886 assert_eq!(
20887 wakes.load(Ordering::SeqCst),
20888 1,
20889 "one batched parent cleanup must emit one waiter notification"
20890 );
20891 assert!(
20892 parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
20893 "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
20894 parent.entry_visits()
20895 );
20896 assert!(
20897 wrong_claim_publication.is_live(),
20898 "claim identity must remain part of the match"
20899 );
20900 assert!(
20901 wrong_job_publication.is_live(),
20902 "job identity must remain part of the match"
20903 );
20904 assert!(
20905 wrong_immediate_publication.is_live(),
20906 "only an exact immediate-parent link may be retired"
20907 );
20908
20909 {
20910 let state = parent
20911 .state
20912 .lock()
20913 .unwrap_or_else(std::sync::PoisonError::into_inner);
20914 assert_eq!(state.current.len(), 3);
20915 assert!(
20922 state
20923 .current
20924 .iter()
20925 .all(|entry| entry.publication.is_live()),
20926 "only the three non-matching sentinels may remain, all still live"
20927 );
20928 assert!(state.unowned_sequences.is_empty());
20929 assert_eq!(state.owner_sequences.len(), 1);
20930 assert_eq!(
20931 state.owner_sequences.get(&owner).map(VecDeque::len),
20932 Some(3)
20933 );
20934 }
20935 assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
20936 assert!(
20937 !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
20938 "cancelled unowned publications must not survive in availability predicates"
20939 );
20940 drop(runnable);
20941 }
20942
20943 #[cfg(not(target_family = "wasm"))]
20944 #[test]
20945 fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
20946 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
20947
20948 if std::env::var_os(CHILD_ENV).is_some() {
20949 struct PanicWake;
20950 impl std::task::Wake for PanicWake {
20951 fn wake(self: Arc<Self>) {
20952 panic!("intentional dependency waker panic");
20953 }
20954 }
20955
20956 struct PanicOnDrop;
20957 impl Drop for PanicOnDrop {
20958 fn drop(&mut self) {
20959 panic!("intentional buffered blocking result destructor panic");
20960 }
20961 }
20962
20963 let parent = Arc::new(TaskDependency::default());
20964 let dependency = BlockingDependency {
20965 job: BlockingJobId(u64::MAX - 1),
20966 owner: None,
20967 };
20968 let sequence = parent.set_owned(dependency);
20972 parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
20973 let _context = TaskDependencyGuard::enter(&parent);
20974 let (sender, receiver) = oneshot::channel();
20975 let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
20976 ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
20977 ));
20978 drop(JoinHandle(JoinHandleInner::Blocking {
20979 receiver,
20980 dependency,
20981 published: PublishedBlockingDependencies {
20982 first: Some(PublishedBlockingDependency {
20983 state: Arc::downgrade(&parent),
20984 sequence,
20985 }),
20986 rest: Vec::new(),
20987 },
20988 }));
20989 assert_eq!(parent.get_dependency(), None);
20990 return;
20991 }
20992
20993 let output = std::process::Command::new(std::env::current_exe().unwrap())
20994 .arg("--exact")
20995 .arg(
20996 "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
20997 )
20998 .arg("--nocapture")
20999 .env(CHILD_ENV, "1")
21000 .output()
21001 .expect("the subprocess test must start");
21002 assert!(
21003 output.status.success(),
21004 "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
21005 output.status.code(),
21006 String::from_utf8_lossy(&output.stdout),
21007 String::from_utf8_lossy(&output.stderr)
21008 );
21009 }
21010
21011 #[cfg(not(target_family = "wasm"))]
21012 #[test]
21013 fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
21014 use std::sync::{Barrier, mpsc};
21023 use std::time::Duration;
21024
21025 let (done_tx, done_rx) = mpsc::channel();
21026 let peak_out = Arc::new(Mutex::new(None));
21027 let peak_slot = Arc::clone(&peak_out);
21028 let runner = std::thread::spawn(move || {
21029 let metrics = Arc::new(RuntimeMetrics::default());
21030 let options = RuntimeOptions {
21031 flavor: RuntimeFlavor::MultiThread,
21032 worker_threads: 4,
21033 max_blocking_tasks: 2,
21034 thread_name_prefix: "rd1-cap".to_string(),
21035 park_deadline: None,
21036 drain_linger: DEFAULT_DRAIN_LINGER,
21037 };
21038 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21039
21040 let holder_count = 2usize; let driver_count = 2usize;
21042
21043 let (held_tx, held_rx) = mpsc::channel();
21045 let release = Arc::new(Barrier::new(holder_count + 1));
21046
21047 let mut holder_handles = Vec::new();
21048 for _ in 0..holder_count {
21049 let held_tx = held_tx.clone();
21050 let release = Arc::clone(&release);
21051 let handle = executor.schedule_blocking(move || {
21052 held_tx.send(()).unwrap();
21053 release.wait();
21054 0usize
21055 });
21056 holder_handles.push(handle);
21057 }
21058 for _ in 0..holder_count {
21060 held_rx.recv().unwrap();
21061 }
21062
21063 let mut driver_tasks = Vec::new();
21066 for _ in 0..driver_count {
21067 let exec = Arc::clone(&executor);
21068 let body = async move {
21069 let inner_exec = Arc::clone(&exec);
21070 let mut inner = std::pin::pin!(async move {
21071 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21072 assert_eq!(v, 5);
21073 });
21074 exec.block_on(inner.as_mut());
21075 };
21076 let scheduler = Arc::clone(&executor);
21077 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
21078 executor.schedule(runnable);
21079 driver_tasks.push(task);
21080 }
21081
21082 std::thread::sleep(Duration::from_millis(300));
21085
21086 release.wait();
21088
21089 for task in driver_tasks {
21090 futures::executor::block_on(task);
21091 }
21092 for handle in holder_handles {
21093 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21094 }
21095
21096 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21097 let _ = done_tx.send(());
21098 });
21099
21100 match done_rx.recv_timeout(Duration::from_secs(10)) {
21101 Ok(()) => runner.join().unwrap(),
21102 Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
21103 }
21104 let peak = peak_out.lock().unwrap().expect("peak captured");
21105 assert!(
21106 peak <= 2,
21107 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21108 );
21109 }
21110
21111 #[cfg(not(target_family = "wasm"))]
21112 #[test]
21113 fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
21114 use std::sync::{Barrier, mpsc};
21130 use std::time::Duration;
21131
21132 let (done_tx, done_rx) = mpsc::channel();
21133 let peak_out = Arc::new(Mutex::new(None));
21134 let peak_slot = Arc::clone(&peak_out);
21135 let runner = std::thread::spawn(move || {
21136 let metrics = Arc::new(RuntimeMetrics::default());
21137 let options = RuntimeOptions {
21138 flavor: RuntimeFlavor::MultiThread,
21139 worker_threads: 2,
21140 max_blocking_tasks: 2,
21141 thread_name_prefix: "rd1-owner-runnable".to_string(),
21142 park_deadline: None,
21143 drain_linger: DEFAULT_DRAIN_LINGER,
21144 };
21145 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21146
21147 let saturated = Arc::new(Barrier::new(2));
21148 let release = Arc::new(Barrier::new(2));
21149
21150 let h2_saturated = Arc::clone(&saturated);
21152 let h2_release = Arc::clone(&release);
21153 let h2 = executor.schedule_blocking(move || {
21154 h2_saturated.wait();
21155 h2_release.wait();
21156 0usize
21157 });
21158
21159 let o_exec = Arc::clone(&executor);
21162 let o_saturated = Arc::clone(&saturated);
21163 let o = executor.schedule_blocking(move || {
21164 o_saturated.wait(); let r_exec = Arc::clone(&o_exec);
21167 let body = async move {
21168 let inner_exec = Arc::clone(&r_exec);
21169 let mut inner = std::pin::pin!(async move {
21170 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21171 assert_eq!(v, 5);
21172 });
21173 r_exec.block_on(inner.as_mut());
21174 };
21175 let sched = Arc::clone(&o_exec);
21176 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21177 o_exec.schedule(runnable);
21178
21179 let mut wait_r = std::pin::pin!(async move {
21180 task.await;
21181 });
21182 o_exec.block_on(wait_r.as_mut());
21183 1usize
21184 });
21185
21186 std::thread::sleep(Duration::from_millis(300));
21189 release.wait();
21191
21192 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21193 assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
21194
21195 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21196 let _ = done_tx.send(());
21197 });
21198
21199 match done_rx.recv_timeout(Duration::from_secs(10)) {
21200 Ok(()) => runner.join().unwrap(),
21201 Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
21202 }
21203 let peak = peak_out.lock().unwrap().expect("peak captured");
21204 assert!(
21205 peak <= 2,
21206 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21207 );
21208 }
21209
21210 #[cfg(not(target_family = "wasm"))]
21211 #[test]
21212 fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
21213 use std::sync::{Barrier, mpsc};
21222
21223 let (done_tx, done_rx) = mpsc::channel();
21224 let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21225 let order_read = Arc::clone(&order_out);
21226 let runner = std::thread::spawn(move || {
21227 let metrics = Arc::new(RuntimeMetrics::default());
21228 let options = RuntimeOptions {
21229 flavor: RuntimeFlavor::MultiThread,
21230 worker_threads: 2,
21231 max_blocking_tasks: 2,
21232 thread_name_prefix: "rd1-unrelated".to_string(),
21233 park_deadline: None,
21234 drain_linger: DEFAULT_DRAIN_LINGER,
21235 };
21236 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21237
21238 let (held_tx, held_rx) = mpsc::channel();
21241 let u_ready = Arc::new(Barrier::new(2)); let h_release = Arc::new(Barrier::new(2)); let h_held = held_tx.clone();
21246 let h_rel = Arc::clone(&h_release);
21247 let h = executor.schedule_blocking(move || {
21248 h_held.send(()).unwrap();
21249 h_rel.wait();
21250 0usize
21251 });
21252
21253 let o_exec = Arc::clone(&executor);
21256 let o_ready = Arc::clone(&u_ready);
21257 let o_order = Arc::clone(&order_out);
21258 let o = executor.schedule_blocking(move || {
21259 held_tx.send(()).unwrap();
21260 o_ready.wait(); let inner_exec = Arc::clone(&o_exec);
21262 let d_order = Arc::clone(&o_order);
21263 let mut inner = std::pin::pin!(async move {
21264 let v = inner_exec
21265 .schedule_blocking(move || {
21266 d_order.lock().unwrap().push("D");
21267 5usize
21268 })
21269 .await
21270 .unwrap();
21271 assert_eq!(v, 5);
21272 });
21273 o_exec.block_on(inner.as_mut());
21274 1usize
21275 });
21276
21277 held_rx.recv().unwrap();
21279 held_rx.recv().unwrap();
21280
21281 let u_order = Arc::clone(&order_out);
21283 let u = executor.schedule_blocking(move || {
21284 u_order.lock().unwrap().push("U");
21285 2usize
21286 });
21287
21288 u_ready.wait();
21290
21291 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21293 assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
21294
21295 h_release.wait();
21297 assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
21298
21299 let _ = done_tx.send(());
21300 });
21301
21302 match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
21303 Ok(()) => runner.join().unwrap(),
21304 Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
21305 }
21306 let order = order_read.lock().unwrap().clone();
21307 assert_eq!(
21308 order,
21309 vec!["D", "U"],
21310 "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
21311 );
21312 }
21313
21314 #[cfg(not(target_family = "wasm"))]
21315 #[test]
21316 fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
21317 use std::sync::{Barrier, mpsc};
21318
21319 let opts = RuntimeOptions {
21320 flavor: RuntimeFlavor::MultiThread,
21321 worker_threads: 2,
21322 max_blocking_tasks: 2,
21323 thread_name_prefix: "rd1-scope".to_string(),
21324 park_deadline: None,
21325 drain_linger: DEFAULT_DRAIN_LINGER,
21326 };
21327 let exec1 =
21328 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21329 let exec2 =
21330 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21331 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21332
21333 let holder_count = opts.max_blocking_tasks;
21334 let (held_tx, held_rx) = mpsc::channel();
21335 let release = Arc::new(Barrier::new(holder_count + 1));
21336 let mut holder_handles = Vec::new();
21337 for _ in 0..holder_count {
21338 let held_tx = held_tx.clone();
21339 let release = Arc::clone(&release);
21340 holder_handles.push(exec2.schedule_blocking(move || {
21341 held_tx.send(()).unwrap();
21342 release.wait();
21343 0usize
21344 }));
21345 }
21346 for _ in 0..holder_count {
21347 held_rx.recv().unwrap();
21348 }
21349 assert_eq!(
21350 exec2.active_blocking.load(Ordering::Acquire),
21351 opts.max_blocking_tasks
21352 );
21353
21354 let exact = BlockingJobId(u64::MAX);
21355 let ran = Arc::new(AtomicBool::new(false));
21356 let ran_job = Arc::clone(&ran);
21357 exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
21358 id: exact,
21359 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21360 });
21361 let dependency = TaskDependency::default();
21362
21363 let token1 = exec1.fresh_owner_token();
21364 {
21365 let _owner = BlockingOwnerGuard::enter(Some(token1));
21366 dependency.set_owned(BlockingDependency {
21367 job: exact,
21368 owner: Some(token1),
21369 });
21370 assert!(
21371 !exec2.try_owned_blocking_over_cap(&dependency),
21372 "a foreign-executor ambient token must fail the executor gate"
21373 );
21374 }
21375
21376 let token2 = exec2.fresh_owner_token();
21377 {
21378 let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
21379 let _owner = BlockingOwnerGuard::enter(Some(token2));
21380 dependency.set_owned(BlockingDependency {
21381 job: BlockingJobId(u64::MAX - 1),
21382 owner: Some(token2),
21383 });
21384 assert!(
21385 !exec2.try_owned_blocking_over_cap(&dependency),
21386 "a foreign or unrelated dependency id must fail the exact-job gate"
21387 );
21388 dependency.set_owned(BlockingDependency {
21389 job: BlockingJobId(exact.0 - 2),
21390 owner: Some(token2),
21391 });
21392 assert!(
21393 !exec2.try_owned_blocking_over_cap(&dependency),
21394 "a different job id must not consume the queued dependency"
21395 );
21396 dependency.set_owned(BlockingDependency {
21397 job: exact,
21398 owner: Some(token2),
21399 });
21400 assert!(
21401 exec2.try_owned_blocking_over_cap(&dependency),
21402 "the exact awaited job must run over cap"
21403 );
21404 }
21405 assert!(
21406 ran.load(Ordering::SeqCst),
21407 "the exact dependency must have run"
21408 );
21409
21410 release.wait();
21411 for handle in holder_handles {
21412 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21413 }
21414 }
21415
21416 #[cfg(not(target_family = "wasm"))]
21417 #[test]
21418 fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
21419 let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
21420 let first_owner = executor.fresh_owner_token();
21421 let second_owner = executor.fresh_owner_token();
21422 let _first_owner =
21423 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
21424 let _second_owner =
21425 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
21426 let exact = BlockingJobId(u64::MAX - 30);
21427 let dependency = TaskDependency::default();
21428 dependency.set_owned(BlockingDependency {
21429 job: exact,
21430 owner: None,
21431 });
21432 let ran = Arc::new(AtomicBool::new(false));
21433 let ran_job = Arc::clone(&ran);
21434 executor
21435 .blocking_queue
21436 .lock()
21437 .unwrap()
21438 .push(QueuedBlocking {
21439 id: exact,
21440 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21441 });
21442
21443 assert!(
21444 !executor.try_owned_blocking_over_cap(&dependency),
21445 "an untagged driver must not choose between multiple active owner frames"
21446 );
21447 assert!(!ran.load(Ordering::SeqCst));
21448 let queued = executor.blocking_queue.lock().unwrap().remove(exact);
21449 assert!(
21450 queued.is_some(),
21451 "ambiguous ownership must leave the exact job queued"
21452 );
21453 }
21454
21455 #[cfg(not(target_family = "wasm"))]
21456 #[test]
21457 fn owner_lending_claims_live_dependency_not_stale_snapshot() {
21458 let executor = multi_thread_executor(2, None, "live-parked-dependency");
21459 let owner = executor.fresh_owner_token();
21460 let dependency = TaskDependency::new(u64::MAX, Some(owner));
21461 let stale = BlockingJobId(u64::MAX - 10);
21462 let live = BlockingJobId(u64::MAX - 9);
21463 dependency.set_owned(BlockingDependency {
21464 job: stale,
21465 owner: Some(owner),
21466 });
21467 dependency.begin_poll();
21468 dependency.set_owned(BlockingDependency {
21469 job: live,
21470 owner: Some(owner),
21471 });
21472
21473 let stale_ran = Arc::new(AtomicBool::new(false));
21474 let stale_job_ran = Arc::clone(&stale_ran);
21475 let live_ran = Arc::new(AtomicBool::new(false));
21476 let live_job_ran = Arc::clone(&live_ran);
21477 {
21478 let mut queue = executor
21479 .blocking_queue
21480 .lock()
21481 .unwrap_or_else(std::sync::PoisonError::into_inner);
21482 queue.push(QueuedBlocking {
21483 id: stale,
21484 run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
21485 });
21486 queue.push(QueuedBlocking {
21487 id: live,
21488 run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
21489 });
21490 }
21491
21492 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21493 let _owner = BlockingOwnerGuard::enter(Some(owner));
21494 assert!(
21495 executor.try_owned_blocking_over_cap(&dependency),
21496 "the owner lane must claim the currently published dependency"
21497 );
21498 assert!(
21499 live_ran.load(Ordering::SeqCst),
21500 "the live dependency must run"
21501 );
21502 assert!(
21503 !stale_ran.load(Ordering::SeqCst),
21504 "the stale registered id must remain queued"
21505 );
21506 let remaining = executor
21507 .blocking_queue
21508 .lock()
21509 .unwrap_or_else(std::sync::PoisonError::into_inner)
21510 .jobs
21511 .keys()
21512 .copied()
21513 .collect::<Vec<_>>();
21514 assert_eq!(remaining, vec![stale]);
21515 }
21516
21517 #[cfg(not(target_family = "wasm"))]
21518 #[test]
21519 fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
21520 let executor = multi_thread_executor(2, None, "cancelled-child-publication");
21521 let owner = executor.fresh_owner_token();
21522 let child = TaskDependency::new_task(u64::MAX);
21523 let parent = TaskDependency::new(u64::MAX, Some(owner));
21524 let exact = BlockingDependency {
21525 job: BlockingJobId(u64::MAX - 31),
21526 owner: Some(owner),
21527 };
21528
21529 child.set_owned(exact);
21530 let stale = child
21531 .get()
21532 .expect("the child must publish its direct dependency");
21533 child.begin_poll();
21534 parent.set_borrowed(&stale);
21535
21536 let ran = Arc::new(AtomicBool::new(false));
21537 let ran_job = Arc::clone(&ran);
21538 executor
21539 .blocking_queue
21540 .lock()
21541 .unwrap()
21542 .push(QueuedBlocking {
21543 id: exact.job,
21544 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21545 });
21546 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21547 let _owner = BlockingOwnerGuard::enter(Some(owner));
21548 assert!(
21549 !executor.try_owned_blocking_over_cap(&parent),
21550 "a publication copied before child cancellation must remain cancelled after parent install"
21551 );
21552 assert!(!ran.load(Ordering::SeqCst));
21553
21554 child.set_owned(exact);
21555 parent.set_borrowed(
21556 &child
21557 .get()
21558 .expect("the replacement publication must be live"),
21559 );
21560 assert!(
21561 executor.try_owned_blocking_over_cap(&parent),
21562 "a fresh child publication must still authorize its exact queued job"
21563 );
21564 assert!(ran.load(Ordering::SeqCst));
21565 }
21566
21567 #[cfg(not(target_family = "wasm"))]
21568 #[test]
21569 fn intermediate_repoll_does_not_cancel_child_source_claim() {
21570 let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
21571 let owner = executor.fresh_owner_token();
21572 let child = TaskDependency::new_task(u64::MAX);
21573 let intermediate = TaskDependency::new_task(u64::MAX);
21574 let parent = TaskDependency::new(u64::MAX, Some(owner));
21575 let exact = BlockingDependency {
21576 job: BlockingJobId(u64::MAX - 43),
21577 owner: Some(owner),
21578 };
21579
21580 child.set_owned(exact);
21581 intermediate.set_borrowed(&child.get().expect("the child source must be live"));
21582 parent.set_borrowed(
21583 &intermediate
21584 .get()
21585 .expect("the intermediate publication must be live"),
21586 );
21587
21588 intermediate.begin_poll();
21589 assert!(
21590 child.get().is_some(),
21591 "withdrawing an intermediate hop must not cancel the child's source claim"
21592 );
21593 assert!(
21594 parent.get().is_none(),
21595 "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
21596 );
21597
21598 intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
21599 parent.set_borrowed(
21600 &intermediate
21601 .get()
21602 .expect("the intermediate must republish the still-live source"),
21603 );
21604 let ran = Arc::new(AtomicBool::new(false));
21605 let ran_job = Arc::clone(&ran);
21606 executor
21607 .blocking_queue
21608 .lock()
21609 .unwrap()
21610 .push(QueuedBlocking {
21611 id: exact.job,
21612 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21613 });
21614
21615 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21616 let _owner = BlockingOwnerGuard::enter(Some(owner));
21617 assert!(
21618 executor.try_owned_blocking_over_cap(&parent),
21619 "a fresh chain through the repolled intermediate must retain the exact source claim"
21620 );
21621 assert!(ran.load(Ordering::SeqCst));
21622 }
21623
21624 #[cfg(not(target_family = "wasm"))]
21625 #[test]
21626 fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
21627 use std::sync::mpsc;
21628
21629 let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
21630 let owner = executor.fresh_owner_token();
21631 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21632 let child = Arc::new(TaskDependency::new_task(u64::MAX));
21633 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21634 let exact = BlockingDependency {
21635 job: BlockingJobId(u64::MAX - 44),
21636 owner: Some(owner),
21637 };
21638 child.set_owned(exact);
21639 parent.set_borrowed(&child.get().expect("the child source must be live"));
21640
21641 let ran = Arc::new(AtomicBool::new(false));
21642 let ran_job = Arc::clone(&ran);
21643 executor
21644 .blocking_queue
21645 .lock()
21646 .unwrap()
21647 .push(QueuedBlocking {
21648 id: exact.job,
21649 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21650 });
21651
21652 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
21653 let (release_claim_tx, release_claim_rx) = mpsc::channel();
21654 let claimant_executor = Arc::clone(&executor);
21655 let claimant_parent = Arc::clone(&parent);
21656 let claimant = std::thread::spawn(move || {
21657 DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
21658 *slot.borrow_mut() = Some(Box::new(move || {
21659 claim_entered_tx.send(()).unwrap();
21660 release_claim_rx.recv().unwrap();
21661 }));
21662 });
21663 let _owner = BlockingOwnerGuard::enter(Some(owner));
21664 claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
21665 });
21666 claim_entered_rx
21667 .recv_timeout(Duration::from_secs(2))
21668 .expect("the exact claimant must hold the shared transition lock");
21669
21670 let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
21671 let withdrawing_child = Arc::clone(&child);
21672 let withdrawal = std::thread::spawn(move || {
21673 withdrawing_child.begin_poll();
21674 withdrawn_tx.send(()).unwrap();
21675 });
21676 let withdrew_before_claim = withdrawn_rx
21677 .recv_timeout(Duration::from_millis(200))
21678 .is_ok();
21679 release_claim_tx.send(()).unwrap();
21680
21681 assert!(
21682 claimant.join().unwrap(),
21683 "the claim that linearized first must execute the exact job"
21684 );
21685 withdrawal.join().unwrap();
21686 assert!(
21687 !withdrew_before_claim,
21688 "source withdrawal must not interleave after claim validation but before claim commit"
21689 );
21690 assert!(ran.load(Ordering::SeqCst));
21691 }
21692
21693 #[cfg(not(target_family = "wasm"))]
21694 #[test]
21695 fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
21696 struct PendThenReady {
21717 polls: usize,
21718 }
21719 impl Future for PendThenReady {
21720 type Output = ();
21721 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21722 self.polls += 1;
21723 if self.polls >= 2 {
21724 Poll::Ready(())
21725 } else {
21726 cx.waker().wake_by_ref();
21730 Poll::Pending
21731 }
21732 }
21733 }
21734
21735 fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
21738 let scheduler = Arc::clone(target);
21739 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
21740 target.push_fifo_raw(runnable);
21741 task.detach();
21742 }
21743
21744 let opts = RuntimeOptions {
21745 flavor: RuntimeFlavor::MultiThread,
21746 worker_threads: 2,
21747 max_blocking_tasks: 2,
21748 thread_name_prefix: "rd-onpool-scope".to_string(),
21749 park_deadline: None,
21750 drain_linger: DEFAULT_DRAIN_LINGER,
21751 };
21752 let exec1 =
21753 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21754 let exec2 =
21755 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21756 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21757
21758 let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
21760
21761 enqueue_runnable(&exec2);
21765 {
21766 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21767 exec2.block_on(probe.as_mut());
21768 }
21769 assert_eq!(
21770 exec2.metrics.runnable_polls.load(Ordering::Relaxed),
21771 0,
21772 "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
21773 );
21774 assert_eq!(
21775 exec2.injector.len(),
21776 1,
21777 "the foreign call must leave exec2's runnable untouched on its queue"
21778 );
21779
21780 enqueue_runnable(&exec1);
21784 {
21785 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21786 exec1.block_on(probe.as_mut());
21787 }
21788 assert_eq!(
21789 exec1.metrics.runnable_polls.load(Ordering::Relaxed),
21790 1,
21791 "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
21792 );
21793 assert!(
21794 exec1.injector.is_empty(),
21795 "the cooperative branch must have drained exec1's queued runnable"
21796 );
21797
21798 while exec2.steal_one().is_some() {}
21801 }
21802
21803 #[cfg(not(target_family = "wasm"))]
21804 #[test]
21805 fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
21806 use std::sync::mpsc;
21810 use std::time::Duration;
21811
21812 const PRODUCER_COUNT: usize = 8;
21813
21814 fn schedule_pool_workload(
21815 executor: &Arc<MultiThreadExecutor>,
21816 tx: &mpsc::Sender<Option<usize>>,
21817 count: usize,
21818 ) {
21819 for i in 0..count {
21820 let tx = tx.clone();
21821 let scheduler = Arc::clone(executor);
21822 let (runnable, task) =
21823 async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
21824 scheduler.schedule(r);
21825 });
21826 executor.schedule(runnable);
21827 task.detach();
21828 }
21829 let tx = tx.clone();
21830 let scheduler = Arc::clone(executor);
21831 let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
21832 scheduler.schedule(r)
21833 });
21834 executor.schedule(runnable);
21835 task.detach();
21836 }
21837
21838 let expected_sum: usize = (0..PRODUCER_COUNT).sum();
21839 let (done_tx, done_rx) = mpsc::sync_channel(1);
21840 let runner = std::thread::spawn(move || {
21841 let options = RuntimeOptions {
21842 flavor: RuntimeFlavor::MultiThread,
21843 worker_threads: 1,
21844 max_blocking_tasks: 1,
21845 thread_name_prefix: "rd2-pre-fix".to_string(),
21846 park_deadline: None,
21847 drain_linger: DEFAULT_DRAIN_LINGER,
21848 }
21849 .validate()
21850 .expect("production MultiThread options must provide a reserve lane");
21851 let executor =
21852 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21853
21854 let (tx, rx) = mpsc::channel::<Option<usize>>();
21855 let (started_tx, started_rx) = mpsc::sync_channel(1);
21856 let handle = executor.schedule_blocking(move || {
21857 started_tx.send(()).unwrap();
21858 let mut sum = 0usize;
21859 while let Ok(msg) = rx.recv() {
21860 match msg {
21861 Some(value) => sum += value,
21862 None => break,
21863 }
21864 }
21865 sum
21866 });
21867 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
21868 schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
21869 let sum = futures::executor::block_on(handle).unwrap();
21870 done_tx.send(sum).unwrap();
21871 });
21872
21873 match done_rx.recv_timeout(Duration::from_secs(10)) {
21874 Ok(sum) => {
21875 runner.join().unwrap();
21876 assert_eq!(sum, expected_sum);
21877 }
21878 Err(error) => {
21879 panic!("a saturated blocking lane starved the async producers that feed it ({error})")
21880 }
21881 }
21882 }
21883
21884 #[test]
21885 fn current_thread_blocking_work_completes_inline() {
21886 let metrics = Arc::new(RuntimeMetrics::default());
21887 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
21888 CurrentThreadExecutor::new(Arc::clone(&metrics)),
21889 )));
21890 let registration = backend.work.try_register_work().unwrap();
21891
21892 let value =
21893 futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
21894 .expect("blocking task should complete");
21895
21896 assert_eq!(value, 7);
21897 assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
21898 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
21899 }
21900
21901 #[cfg(not(target_family = "wasm"))]
21902 #[test]
21903 fn current_thread_blocking_work_obeys_single_lane_across_callers() {
21904 use std::sync::mpsc;
21905
21906 let controller = Arc::new(current_thread_controller(
21907 "current-thread-blocking-admission",
21908 ));
21909 let backend = controller.backend();
21910 let (first_started_tx, first_started_rx) = mpsc::channel();
21911 let (release_first_tx, release_first_rx) = mpsc::channel();
21912 let first_controller = Arc::clone(&controller);
21913 let first = std::thread::spawn(move || {
21914 let handle = first_controller
21915 .try_spawn_blocking(move || {
21916 first_started_tx.send(()).unwrap();
21917 release_first_rx.recv().unwrap();
21918 1usize
21919 })
21920 .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
21921 futures::executor::block_on(handle).unwrap()
21922 });
21923 first_started_rx
21924 .recv_timeout(Duration::from_secs(1))
21925 .unwrap();
21926
21927 let (second_started_tx, second_started_rx) = mpsc::channel();
21928 let second_controller = Arc::clone(&controller);
21929 let second = std::thread::spawn(move || {
21930 let handle = second_controller
21931 .try_spawn_blocking(move || {
21932 second_started_tx.send(()).unwrap();
21933 2usize
21934 })
21935 .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
21936 futures::executor::block_on(handle).unwrap()
21937 });
21938
21939 wait_until("both CurrentThread blocking calls to be accepted", || {
21940 backend
21941 .work
21942 .state
21943 .lock()
21944 .unwrap_or_else(std::sync::PoisonError::into_inner)
21945 .active
21946 == 2
21947 });
21948 assert!(
21949 second_started_rx
21950 .recv_timeout(Duration::from_millis(100))
21951 .is_err(),
21952 "CurrentThread must not execute two blocking closures concurrently"
21953 );
21954
21955 release_first_tx.send(()).unwrap();
21956 second_started_rx
21957 .recv_timeout(Duration::from_secs(1))
21958 .expect("the waiting blocking call must acquire the released lane");
21959 assert_eq!(first.join().unwrap(), 1);
21960 assert_eq!(second.join().unwrap(), 2);
21961 assert_eq!(
21962 controller
21963 .metrics
21964 .blocking_tasks_started
21965 .load(Ordering::Relaxed),
21966 2
21967 );
21968 assert_eq!(
21969 controller
21970 .metrics
21971 .blocking_tasks_completed
21972 .load(Ordering::Relaxed),
21973 2
21974 );
21975 assert_eq!(
21976 controller
21977 .metrics
21978 .active_blocking_tasks
21979 .load(Ordering::Relaxed),
21980 0
21981 );
21982 assert_eq!(
21983 controller
21984 .metrics
21985 .max_active_blocking_tasks
21986 .load(Ordering::Relaxed),
21987 1,
21988 "the public active-lane metric must remain within CurrentThread's reported capacity"
21989 );
21990 controller.shutdown().unwrap();
21991 }
21992
21993 #[cfg(not(target_family = "wasm"))]
21994 #[test]
21995 fn current_thread_nested_blocking_reuses_the_active_lane() {
21996 use std::sync::mpsc;
21997
21998 let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
21999 let (done_tx, done_rx) = mpsc::channel();
22000 let runner_controller = Arc::clone(&controller);
22001 let runner = std::thread::spawn(move || {
22002 let nested_controller = Arc::clone(&runner_controller);
22003 let outer = runner_controller
22004 .try_spawn_blocking(move || {
22005 let inner = nested_controller
22006 .try_spawn_blocking(|| 41usize)
22007 .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
22008 futures::executor::block_on(inner).unwrap() + 1
22009 })
22010 .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
22011 done_tx
22012 .send(futures::executor::block_on(outer).unwrap())
22013 .unwrap();
22014 });
22015
22016 assert_eq!(
22017 done_rx
22018 .recv_timeout(Duration::from_secs(2))
22019 .expect("a nested CurrentThread blocking call must not wait on its own lane"),
22020 42
22021 );
22022 runner.join().unwrap();
22023 assert_eq!(
22024 controller
22025 .metrics
22026 .blocking_tasks_started
22027 .load(Ordering::Relaxed),
22028 2
22029 );
22030 assert_eq!(
22031 controller
22032 .metrics
22033 .blocking_tasks_completed
22034 .load(Ordering::Relaxed),
22035 2
22036 );
22037 assert_eq!(
22038 controller
22039 .metrics
22040 .active_blocking_tasks
22041 .load(Ordering::Relaxed),
22042 0
22043 );
22044 assert_eq!(
22045 controller
22046 .metrics
22047 .max_active_blocking_tasks
22048 .load(Ordering::Relaxed),
22049 1,
22050 "nested work borrows the outer lane instead of reporting a second physical lane"
22051 );
22052 controller.shutdown().unwrap();
22053 }
22054
22055 #[cfg(not(target_family = "wasm"))]
22056 #[test]
22057 fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22058 use std::sync::mpsc;
22059
22060 struct HotFuture {
22061 stop: Arc<AtomicBool>,
22062 }
22063
22064 impl Future for HotFuture {
22065 type Output = ();
22066
22067 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22068 if self.stop.load(Ordering::SeqCst) {
22069 Poll::Ready(())
22070 } else {
22071 cx.waker().wake_by_ref();
22072 Poll::Pending
22073 }
22074 }
22075 }
22076
22077 let stop = Arc::new(AtomicBool::new(false));
22078 let (ready_tx, ready_rx) = mpsc::channel();
22079 let (exact_tx, exact_rx) = mpsc::channel();
22080 let (done_tx, done_rx) = mpsc::channel();
22081 let runner_stop = Arc::clone(&stop);
22082 let runner = std::thread::spawn(move || {
22083 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22084 RuntimeMetrics::default(),
22085 )));
22086 let owner = executor.fresh_owner_token();
22087 let job = BlockingJobId(u64::MAX - 70);
22088 let (result_tx, result_rx) = oneshot::channel();
22089 {
22090 let mut state = executor
22091 .blocking_admission
22092 .state
22093 .lock()
22094 .unwrap_or_else(std::sync::PoisonError::into_inner);
22095 state.owner = Some(owner);
22096 let exact_stop = Arc::clone(&runner_stop);
22097 state.queue.push(QueuedBlocking {
22098 id: job,
22099 run: Box::new(move || {
22100 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22101 let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
22102 }),
22103 });
22104 }
22105 let exact = JoinHandle(JoinHandleInner::Blocking {
22106 receiver: result_rx,
22107 dependency: BlockingDependency {
22108 job,
22109 owner: Some(owner),
22110 },
22111 published: PublishedBlockingDependencies::default(),
22112 });
22113 let _owner = executor.enter_blocking_owner(owner, false);
22114
22115 let scheduler = Arc::clone(&executor);
22116 let hot_stop = Arc::clone(&runner_stop);
22117 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22118 scheduler.schedule(runnable);
22119 });
22120 executor.schedule(runnable);
22121 ready_tx.send(()).unwrap();
22122
22123 let mut value = None;
22124 {
22125 let mut future = std::pin::pin!(async {
22126 value = Some(exact.await.unwrap());
22127 hot_task.await;
22128 });
22129 assert_eq!(
22130 executor.block_on(future.as_mut()),
22131 BlockOnOutcome::Completed
22132 );
22133 }
22134 executor
22135 .blocking_admission
22136 .state
22137 .lock()
22138 .unwrap_or_else(std::sync::PoisonError::into_inner)
22139 .owner = None;
22140 done_tx.send(value.unwrap()).unwrap();
22141 });
22142
22143 ready_rx
22144 .recv_timeout(Duration::from_secs(2))
22145 .expect("the fairness driver must start");
22146 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22147 stop.store(true, Ordering::SeqCst);
22148 assert_eq!(
22149 done_rx
22150 .recv_timeout(Duration::from_secs(2))
22151 .expect("the CurrentThread driver must retire"),
22152 41
22153 );
22154 runner.join().unwrap();
22155 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22156 assert_eq!(
22157 exact_before_stop,
22158 Some(true),
22159 "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
22160 );
22161 }
22162
22163 #[cfg(not(target_family = "wasm"))]
22164 #[test]
22165 fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
22166 use std::sync::mpsc;
22167
22168 let controller = Arc::new(current_thread_controller(
22169 "current-thread-cross-driver-blocking",
22170 ));
22171 let backend = controller.backend();
22172 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22173 panic!("configured CurrentThread backend must create a CurrentThread executor");
22174 };
22175 let executor = Arc::clone(executor);
22176 drop(backend);
22177
22178 let (release_driver_tx, release_driver_rx) = oneshot::channel();
22179 let (driver_started_tx, driver_started_rx) = mpsc::channel();
22180 let driver_controller = Arc::clone(&controller);
22181 let driver = std::thread::spawn(move || {
22182 driver_started_tx.send(std::thread::current().id()).unwrap();
22183 block_on_with_controller(&driver_controller, async {
22184 release_driver_rx
22185 .await
22186 .expect("driver release sender must remain alive");
22187 });
22188 });
22189 let driver_thread = driver_started_rx
22190 .recv_timeout(Duration::from_secs(1))
22191 .unwrap();
22192 wait_until("the secondary CurrentThread driver to park", || {
22193 executor
22194 .stop
22195 .parkers
22196 .lock()
22197 .unwrap_or_else(std::sync::PoisonError::into_inner)
22198 .iter()
22199 .filter_map(Weak::upgrade)
22200 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22201 });
22202
22203 let (child_polled_tx, child_polled_rx) = mpsc::channel();
22204 let (release_child_tx, release_child_rx) = mpsc::channel();
22205 let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
22206 let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
22207 let (owner_done_tx, owner_done_rx) = mpsc::channel();
22208 let owner_controller = Arc::clone(&controller);
22209 let owner = std::thread::spawn(move || {
22210 let owner_thread = std::thread::current().id();
22211 let await_controller = Arc::clone(&owner_controller);
22212 let child_controller = Arc::clone(&owner_controller);
22213 let outer = owner_controller
22214 .try_spawn_blocking(move || {
22215 let spawn_controller = Arc::clone(&child_controller);
22216 let child = spawn_controller
22217 .try_spawn(async move {
22218 child_polled_tx.send(std::thread::current().id()).unwrap();
22219 release_child_rx.recv().unwrap();
22220 child_controller
22221 .try_spawn_blocking(move || {
22222 blocking_ran_tx.send(std::thread::current().id()).unwrap();
22223 41usize
22224 })
22225 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
22226 .await
22227 .unwrap()
22228 + 1
22229 })
22230 .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
22231
22232 assert_eq!(
22233 child_polled_rx
22234 .recv_timeout(Duration::from_secs(1))
22235 .unwrap(),
22236 driver_thread,
22237 "the secondary driver must own the child poll that reaches blocking admission"
22238 );
22239 owner_awaiting_tx.send(owner_thread).unwrap();
22240 block_on_with_controller(&await_controller, child).unwrap()
22241 })
22242 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22243 owner_done_tx
22244 .send(futures::executor::block_on(outer))
22245 .unwrap();
22246 });
22247
22248 let owner_thread = owner_awaiting_rx
22249 .recv_timeout(Duration::from_secs(1))
22250 .unwrap();
22251 wait_until(
22252 "the blocking owner to park while awaiting the cross-driver child",
22253 || {
22254 executor
22255 .stop
22256 .parkers
22257 .lock()
22258 .unwrap_or_else(std::sync::PoisonError::into_inner)
22259 .iter()
22260 .filter_map(Weak::upgrade)
22261 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22262 },
22263 );
22264 release_child_tx.send(()).unwrap();
22265
22266 assert_eq!(
22267 blocking_ran_rx
22268 .recv_timeout(Duration::from_secs(2))
22269 .unwrap(),
22270 owner_thread,
22271 "the exact descendant must be serviced by the lane-owning driver"
22272 );
22273 assert_eq!(
22274 owner_done_rx
22275 .recv_timeout(Duration::from_secs(2))
22276 .expect("cross-driver blocking dependency must not deadlock")
22277 .unwrap(),
22278 42
22279 );
22280 release_driver_tx.send(()).unwrap();
22281 owner.join().unwrap();
22282 driver.join().unwrap();
22283 controller.shutdown().unwrap();
22284 assert_eq!(
22285 controller
22286 .metrics
22287 .blocking_tasks_started
22288 .load(Ordering::Relaxed),
22289 2
22290 );
22291 assert_eq!(
22292 controller
22293 .metrics
22294 .blocking_tasks_completed
22295 .load(Ordering::Relaxed),
22296 2
22297 );
22298 assert_eq!(
22299 controller
22300 .metrics
22301 .active_blocking_tasks
22302 .load(Ordering::Relaxed),
22303 0
22304 );
22305 assert_eq!(
22306 controller
22307 .metrics
22308 .max_active_blocking_tasks
22309 .load(Ordering::Relaxed),
22310 1,
22311 "cross-driver dependency service must reuse the owner's physical lane"
22312 );
22313 }
22314
22315 #[cfg(not(target_family = "wasm"))]
22316 #[test]
22317 fn current_thread_blocking_admission_rechecks_stop_under_lock() {
22318 use std::sync::mpsc;
22319
22320 let controller = Arc::new(current_thread_controller(
22321 "current-thread-blocking-stop-race",
22322 ));
22323 let backend = controller.backend();
22324 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22325 panic!("configured CurrentThread backend must create a CurrentThread executor");
22326 };
22327 let executor = Arc::clone(executor);
22328 drop(backend);
22329
22330 let ran = Arc::new(AtomicBool::new(false));
22331 let blocking_ran = Arc::clone(&ran);
22332 let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
22333 let (release_admission_tx, release_admission_rx) = mpsc::channel();
22334 let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
22335 let submit_controller = Arc::clone(&controller);
22336 let submitter = std::thread::spawn(move || {
22337 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22338 *slot.borrow_mut() = Some(Box::new(move || {
22339 admission_locked_tx.send(()).unwrap();
22340 release_admission_rx.recv().unwrap();
22341 }));
22342 });
22343 let handle = submit_controller
22344 .try_spawn_blocking(move || {
22345 blocking_ran.store(true, Ordering::SeqCst);
22346 })
22347 .unwrap_or_else(|_| {
22348 panic!("the submission linearized before shutdown and must be accepted")
22349 });
22350 blocking_result_tx
22351 .send(futures::executor::block_on(handle))
22352 .unwrap();
22353 });
22354 admission_locked_rx
22355 .recv_timeout(Duration::from_secs(2))
22356 .expect("the blocking submitter must pause while holding the admission lock");
22357
22358 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
22359 let shutdown_controller = Arc::clone(&controller);
22360 let shutdown = std::thread::spawn(move || {
22361 shutdown_result_tx
22362 .send(shutdown_controller.shutdown())
22363 .unwrap();
22364 });
22365 wait_until("CurrentThread shutdown to publish generation stop", || {
22366 executor.stop.is_stopping()
22367 });
22368 release_admission_tx.send(()).unwrap();
22369
22370 let blocking_error = blocking_result_rx
22371 .recv_timeout(Duration::from_secs(2))
22372 .expect("the admitted blocking handle must resolve")
22373 .expect_err("stop publication must reject the blocking lane claim");
22374 assert_eq!(
22375 blocking_error.to_string(),
22376 "the async runtime stopped before the blocking task could start"
22377 );
22378 assert!(
22379 !ran.load(Ordering::SeqCst),
22380 "blocking work must not start after stop publication"
22381 );
22382 shutdown_result_rx
22383 .recv_timeout(Duration::from_secs(2))
22384 .expect("shutdown must finish after the rejected admission retires")
22385 .unwrap();
22386 submitter.join().unwrap();
22387 shutdown.join().unwrap();
22388 }
22389
22390 #[cfg(not(target_family = "wasm"))]
22391 #[test]
22392 fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
22393 use std::sync::mpsc;
22394
22395 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22396 RuntimeMetrics::default(),
22397 )));
22398 let ran = Arc::new(AtomicBool::new(false));
22399 let job_ran = Arc::clone(&ran);
22400 {
22401 let mut state = executor
22402 .blocking_admission
22403 .state
22404 .lock()
22405 .unwrap_or_else(std::sync::PoisonError::into_inner);
22406 state.queue.push(QueuedBlocking {
22407 id: BlockingJobId(u64::MAX - 71),
22408 run: Box::new(move || {
22409 job_ran.store(true, Ordering::SeqCst);
22410 }),
22411 });
22412 }
22413
22414 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22415 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22416 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22417 let claim_executor = Arc::clone(&executor);
22418 let claimant = std::thread::spawn(move || {
22419 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22420 *slot.borrow_mut() = Some(Box::new(move || {
22421 claim_locked_tx.send(()).unwrap();
22422 release_claim_rx.recv().unwrap();
22423 }));
22424 });
22425 claim_result_tx
22426 .send(claim_executor.run_one_blocking())
22427 .unwrap();
22428 });
22429 claim_locked_rx
22430 .recv_timeout(Duration::from_secs(2))
22431 .expect("the FIFO claimant must pause while holding the admission lock");
22432
22433 executor.stop.begin_shutdown();
22434 release_claim_tx.send(()).unwrap();
22435 assert!(
22436 !claim_result_rx
22437 .recv_timeout(Duration::from_secs(2))
22438 .expect("the stopped FIFO claim must finish"),
22439 "stop publication must reject the queued FIFO claim"
22440 );
22441 claimant.join().unwrap();
22442 assert!(
22443 !ran.load(Ordering::SeqCst),
22444 "queued FIFO work must not start after stop publication"
22445 );
22446 assert!(
22447 !executor
22448 .blocking_admission
22449 .state
22450 .lock()
22451 .unwrap_or_else(std::sync::PoisonError::into_inner)
22452 .queue
22453 .is_empty(),
22454 "the rejected FIFO job must remain queued for shutdown cancellation"
22455 );
22456
22457 executor.close_blocking_admission();
22458 assert!(
22459 executor
22460 .blocking_admission
22461 .state
22462 .lock()
22463 .unwrap_or_else(std::sync::PoisonError::into_inner)
22464 .queue
22465 .is_empty()
22466 );
22467 }
22468
22469 #[cfg(not(target_family = "wasm"))]
22470 #[test]
22471 fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22472 use std::sync::mpsc;
22473
22474 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22475 RuntimeMetrics::default(),
22476 )));
22477 let ran = Arc::new(AtomicBool::new(false));
22478 let job_ran = Arc::clone(&ran);
22479 {
22480 let mut state = executor
22481 .blocking_admission
22482 .state
22483 .lock()
22484 .unwrap_or_else(std::sync::PoisonError::into_inner);
22485 state.queue.push(QueuedBlocking {
22486 id: BlockingJobId(u64::MAX - 73),
22487 run: Box::new(move || {
22488 job_ran.store(true, Ordering::SeqCst);
22489 }),
22490 });
22491 }
22492
22493 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22494 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22495 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22496 let claim_executor = Arc::clone(&executor);
22497 let claimant = std::thread::spawn(move || {
22498 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22499 *slot.borrow_mut() = Some(Box::new(move || {
22500 claim_locked_tx.send(()).unwrap();
22501 release_claim_rx.recv().unwrap();
22502 }));
22503 });
22504 claim_result_tx
22505 .send(claim_executor.run_one_blocking())
22506 .unwrap();
22507 });
22508 claim_locked_rx
22509 .recv_timeout(Duration::from_secs(2))
22510 .expect("the FIFO claimant must pause while holding the admission lock");
22511
22512 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22513 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22514 let cancelling_executor = Arc::clone(&executor);
22515 let cancellation = std::thread::spawn(move || {
22516 let cancelling = cancelling_executor
22517 .begin_failed_dispatch_cancellation()
22518 .expect("the test must publish terminal dispatch cancellation");
22519 cancellation_published_tx.send(()).unwrap();
22520 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22521 cancellation_done_tx.send(()).unwrap();
22522 });
22523 cancellation_published_rx
22524 .recv_timeout(Duration::from_secs(2))
22525 .expect("terminal dispatch cancellation must publish before queue cancellation");
22526 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22527 assert!(
22528 matches!(
22529 cancellation_done_rx.try_recv(),
22530 Err(mpsc::TryRecvError::Empty)
22531 ),
22532 "terminal cancellation must wait for the blocking-admission lock"
22533 );
22534
22535 release_claim_tx.send(()).unwrap();
22536 assert!(
22537 !claim_result_rx
22538 .recv_timeout(Duration::from_secs(2))
22539 .expect("the cancelled FIFO claim must finish"),
22540 "terminal cancellation publication must reject the queued FIFO claim"
22541 );
22542 cancellation_done_rx
22543 .recv_timeout(Duration::from_secs(2))
22544 .expect("terminal cancellation must drain the rejected FIFO job");
22545 claimant.join().unwrap();
22546 cancellation.join().unwrap();
22547
22548 assert!(
22549 !ran.load(Ordering::SeqCst),
22550 "terminally cancelled FIFO work must not start"
22551 );
22552 assert!(
22553 executor
22554 .blocking_admission
22555 .state
22556 .lock()
22557 .unwrap_or_else(std::sync::PoisonError::into_inner)
22558 .queue
22559 .is_empty(),
22560 "terminal cancellation must drain the rejected FIFO job"
22561 );
22562 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22563 }
22564
22565 #[cfg(not(target_family = "wasm"))]
22566 #[test]
22567 fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
22568 use std::sync::mpsc;
22569
22570 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22571 RuntimeMetrics::default(),
22572 )));
22573 let owner = executor.fresh_owner_token();
22574 let job = BlockingJobId(u64::MAX - 72);
22575 let ran = Arc::new(AtomicBool::new(false));
22576 let job_ran = Arc::clone(&ran);
22577 {
22578 let mut state = executor
22579 .blocking_admission
22580 .state
22581 .lock()
22582 .unwrap_or_else(std::sync::PoisonError::into_inner);
22583 state.owner = Some(owner);
22584 state.queue.push(QueuedBlocking {
22585 id: job,
22586 run: Box::new(move || {
22587 job_ran.store(true, Ordering::SeqCst);
22588 }),
22589 });
22590 }
22591 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22592 dependency.set_owned(BlockingDependency {
22593 job,
22594 owner: Some(owner),
22595 });
22596
22597 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22598 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22599 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22600 let claim_executor = Arc::clone(&executor);
22601 let claim_dependency = Arc::clone(&dependency);
22602 let claimant = std::thread::spawn(move || {
22603 let _owner = claim_executor.enter_blocking_owner(owner, false);
22604 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22605 *slot.borrow_mut() = Some(Box::new(move || {
22606 claim_locked_tx.send(()).unwrap();
22607 release_claim_rx.recv().unwrap();
22608 }));
22609 });
22610 claim_result_tx
22611 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22612 .unwrap();
22613 });
22614 claim_locked_rx
22615 .recv_timeout(Duration::from_secs(2))
22616 .expect("the owner claimant must pause while holding the admission lock");
22617
22618 executor.stop.begin_shutdown();
22619 release_claim_tx.send(()).unwrap();
22620 assert!(
22621 !claim_result_rx
22622 .recv_timeout(Duration::from_secs(2))
22623 .expect("the stopped owner claim must finish"),
22624 "stop publication must reject the exact-owner claim"
22625 );
22626 claimant.join().unwrap();
22627 assert!(
22628 !ran.load(Ordering::SeqCst),
22629 "queued exact-owner work must not start after stop publication"
22630 );
22631 assert!(
22632 !executor
22633 .blocking_admission
22634 .state
22635 .lock()
22636 .unwrap_or_else(std::sync::PoisonError::into_inner)
22637 .queue
22638 .is_empty(),
22639 "the rejected exact-owner job must remain queued for shutdown cancellation"
22640 );
22641
22642 executor
22643 .blocking_admission
22644 .state
22645 .lock()
22646 .unwrap_or_else(std::sync::PoisonError::into_inner)
22647 .owner = None;
22648 executor.close_blocking_admission();
22649 assert!(
22650 executor
22651 .blocking_admission
22652 .state
22653 .lock()
22654 .unwrap_or_else(std::sync::PoisonError::into_inner)
22655 .queue
22656 .is_empty()
22657 );
22658 }
22659
22660 #[cfg(not(target_family = "wasm"))]
22661 #[test]
22662 fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22663 use std::sync::mpsc;
22664
22665 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22666 RuntimeMetrics::default(),
22667 )));
22668 let owner = executor.fresh_owner_token();
22669 let job = BlockingJobId(u64::MAX - 74);
22670 let ran = Arc::new(AtomicBool::new(false));
22671 let job_ran = Arc::clone(&ran);
22672 {
22673 let mut state = executor
22674 .blocking_admission
22675 .state
22676 .lock()
22677 .unwrap_or_else(std::sync::PoisonError::into_inner);
22678 state.owner = Some(owner);
22679 state.queue.push(QueuedBlocking {
22680 id: job,
22681 run: Box::new(move || {
22682 job_ran.store(true, Ordering::SeqCst);
22683 }),
22684 });
22685 }
22686 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22687 dependency.set_owned(BlockingDependency {
22688 job,
22689 owner: Some(owner),
22690 });
22691
22692 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22693 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22694 let (claim_result_tx, claim_result_rx) = mpsc::channel();
22695 let claim_executor = Arc::clone(&executor);
22696 let claim_dependency = Arc::clone(&dependency);
22697 let claimant = std::thread::spawn(move || {
22698 let _owner = claim_executor.enter_blocking_owner(owner, false);
22699 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22700 *slot.borrow_mut() = Some(Box::new(move || {
22701 claim_locked_tx.send(()).unwrap();
22702 release_claim_rx.recv().unwrap();
22703 }));
22704 });
22705 claim_result_tx
22706 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22707 .unwrap();
22708 });
22709 claim_locked_rx
22710 .recv_timeout(Duration::from_secs(2))
22711 .expect("the owner claimant must pause while holding the admission lock");
22712
22713 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22714 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22715 let cancelling_executor = Arc::clone(&executor);
22716 let cancellation = std::thread::spawn(move || {
22717 let cancelling = cancelling_executor
22718 .begin_failed_dispatch_cancellation()
22719 .expect("the test must publish terminal dispatch cancellation");
22720 cancellation_published_tx.send(()).unwrap();
22721 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22722 cancellation_done_tx.send(()).unwrap();
22723 });
22724 cancellation_published_rx
22725 .recv_timeout(Duration::from_secs(2))
22726 .expect("terminal dispatch cancellation must publish before queue cancellation");
22727 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22728 assert!(
22729 matches!(
22730 cancellation_done_rx.try_recv(),
22731 Err(mpsc::TryRecvError::Empty)
22732 ),
22733 "terminal cancellation must wait for the blocking-admission lock"
22734 );
22735
22736 release_claim_tx.send(()).unwrap();
22737 assert!(
22738 !claim_result_rx
22739 .recv_timeout(Duration::from_secs(2))
22740 .expect("the cancelled owner claim must finish"),
22741 "terminal cancellation publication must reject the exact-owner claim"
22742 );
22743 cancellation_done_rx
22744 .recv_timeout(Duration::from_secs(2))
22745 .expect("terminal cancellation must drain the rejected exact-owner job");
22746 claimant.join().unwrap();
22747 cancellation.join().unwrap();
22748
22749 assert!(
22750 !ran.load(Ordering::SeqCst),
22751 "terminally cancelled exact-owner work must not start"
22752 );
22753 assert!(
22754 executor
22755 .blocking_admission
22756 .state
22757 .lock()
22758 .unwrap_or_else(std::sync::PoisonError::into_inner)
22759 .queue
22760 .is_empty(),
22761 "terminal cancellation must drain the rejected exact-owner job"
22762 );
22763 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22764 }
22765
22766 #[cfg(not(target_family = "wasm"))]
22767 #[test]
22768 fn current_thread_shutdown_cancels_waiting_blocking_work() {
22769 use std::sync::mpsc;
22770
22771 struct DropSignal(mpsc::Sender<()>);
22772
22773 impl Drop for DropSignal {
22774 fn drop(&mut self) {
22775 let _ = self.0.send(());
22776 }
22777 }
22778
22779 let controller = Arc::new(current_thread_controller(
22780 "current-thread-blocking-shutdown",
22781 ));
22782 let backend = controller.backend();
22783 let (first_started_tx, first_started_rx) = mpsc::channel();
22784 let (release_first_tx, release_first_rx) = mpsc::channel();
22785 let first_controller = Arc::clone(&controller);
22786 let first = std::thread::spawn(move || {
22787 let handle = first_controller
22788 .try_spawn_blocking(move || {
22789 first_started_tx.send(()).unwrap();
22790 release_first_rx.recv().unwrap();
22791 })
22792 .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
22793 futures::executor::block_on(handle)
22794 });
22795 first_started_rx
22796 .recv_timeout(Duration::from_secs(1))
22797 .unwrap();
22798
22799 let waiting_ran = Arc::new(AtomicBool::new(false));
22800 let waiting_ran_task = Arc::clone(&waiting_ran);
22801 let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
22802 let waiting_drop = DropSignal(waiting_dropped_tx);
22803 let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
22804 let waiting_controller = Arc::clone(&controller);
22805 let waiting = std::thread::spawn(move || {
22806 let handle = waiting_controller
22807 .try_spawn_blocking(move || {
22808 let _waiting_drop = waiting_drop;
22809 waiting_ran_task.store(true, Ordering::SeqCst);
22810 })
22811 .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
22812 waiting_result_tx
22813 .send(futures::executor::block_on(handle))
22814 .unwrap();
22815 });
22816 wait_until("both CurrentThread blocking calls to be accepted", || {
22817 backend
22818 .work
22819 .state
22820 .lock()
22821 .unwrap_or_else(std::sync::PoisonError::into_inner)
22822 .active
22823 == 2
22824 });
22825
22826 let (shutdown_tx, shutdown_rx) = mpsc::channel();
22827 let shutdown_controller = Arc::clone(&controller);
22828 let shutdown = std::thread::spawn(move || {
22829 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22830 });
22831 waiting_dropped_rx
22832 .recv_timeout(Duration::from_secs(1))
22833 .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
22834 assert!(
22835 !waiting_ran.load(Ordering::SeqCst),
22836 "waiting blocking work must not start"
22837 );
22838 assert!(
22839 waiting_result_rx
22840 .recv_timeout(Duration::from_secs(1))
22841 .unwrap()
22842 .is_err(),
22843 "the waiting blocking handle must resolve as cancelled"
22844 );
22845 assert!(
22846 shutdown_rx
22847 .recv_timeout(Duration::from_millis(100))
22848 .is_err(),
22849 "shutdown must still wait for the blocking closure that already owns the lane"
22850 );
22851
22852 release_first_tx.send(()).unwrap();
22853 first.join().unwrap().unwrap();
22854 waiting.join().unwrap();
22855 shutdown_rx
22856 .recv_timeout(Duration::from_secs(1))
22857 .unwrap()
22858 .unwrap();
22859 shutdown.join().unwrap();
22860 assert_eq!(
22861 controller
22862 .metrics
22863 .blocking_tasks_started
22864 .load(Ordering::Relaxed),
22865 1
22866 );
22867 assert_eq!(
22868 controller
22869 .metrics
22870 .blocking_tasks_completed
22871 .load(Ordering::Relaxed),
22872 1
22873 );
22874 assert_eq!(
22875 controller
22876 .metrics
22877 .active_blocking_tasks
22878 .load(Ordering::Relaxed),
22879 0
22880 );
22881 }
22882
22883 #[cfg(not(target_family = "wasm"))]
22884 #[test]
22885 fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
22886 use std::{panic::panic_any, sync::mpsc};
22887
22888 struct ReentrantPayload {
22889 controller: Arc<RuntimeController>,
22890 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
22891 }
22892
22893 impl Drop for ReentrantPayload {
22894 fn drop(&mut self) {
22895 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
22896 let shutdown = self.controller.shutdown();
22897 self.result.send((generation, shutdown)).unwrap();
22898 }
22899 }
22900
22901 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
22902
22903 impl Drop for PanicPayloadOnDrop {
22904 fn drop(&mut self) {
22905 panic_any(self.0.take().expect("the result must panic only once"));
22906 }
22907 }
22908
22909 let controller = Arc::new(current_thread_controller(
22910 "current-thread-panic-payload-generation",
22911 ));
22912 let backend = controller.backend();
22913 let generation = backend.generation();
22914 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22915 panic!("configured CurrentThread backend must create a CurrentThread executor");
22916 };
22917 let executor = Arc::clone(executor);
22918 drop(backend);
22919
22920 let owner = executor.fresh_owner_token();
22923 executor
22924 .blocking_admission
22925 .state
22926 .lock()
22927 .unwrap_or_else(std::sync::PoisonError::into_inner)
22928 .owner = Some(owner);
22929 let (result_tx, result_rx) = mpsc::channel();
22930 let payload_controller = Arc::clone(&controller);
22931 let handle = controller
22932 .try_spawn_blocking(move || {
22933 PanicPayloadOnDrop(Some(ReentrantPayload {
22934 controller: payload_controller,
22935 result: result_tx,
22936 }))
22937 })
22938 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22939 drop(handle);
22940 executor
22941 .blocking_admission
22942 .state
22943 .lock()
22944 .unwrap_or_else(std::sync::PoisonError::into_inner)
22945 .owner = None;
22946 let dispatch = current_thread_dispatch(&executor);
22947 controller.drive_current_thread_dispatch(dispatch, false);
22948
22949 let (observed_generation, shutdown) = result_rx
22950 .recv_timeout(Duration::from_secs(1))
22951 .expect("the caught result panic payload must be destroyed in the scheduler turn");
22952 assert_eq!(observed_generation, Some(generation));
22953 let error = shutdown.expect_err("payload destruction is still work in the active generation");
22954 assert!(error.to_string().contains("work running on that runtime"));
22955 controller.shutdown().unwrap();
22956 }
22957
22958 #[test]
22959 fn current_thread_detached_blocking_result_drop_is_contained() {
22960 use std::sync::mpsc;
22961
22962 struct PanicOnDrop(mpsc::Sender<()>);
22963
22964 impl Drop for PanicOnDrop {
22965 fn drop(&mut self) {
22966 self.0.send(()).unwrap();
22967 panic!("intentional CurrentThread blocking result destructor panic");
22968 }
22969 }
22970
22971 let controller = current_thread_controller("current-thread-blocking-result-drop");
22972 let (dropped_tx, dropped_rx) = mpsc::channel();
22973 let handle = controller
22974 .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
22975 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22976
22977 let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
22978 dropped_rx
22979 .recv_timeout(Duration::from_secs(1))
22980 .expect("dropping the detached handle must reclaim its blocking result");
22981 assert!(
22982 result.is_ok(),
22983 "the blocking result destructor panic must be contained"
22984 );
22985 controller.shutdown().unwrap();
22986 }
22987
22988 #[test]
22993 fn validate_rejects_zero_worker_threads() {
22994 let error = RuntimeOptions {
22995 flavor: RuntimeFlavor::MultiThread,
22996 worker_threads: 0,
22997 max_blocking_tasks: 1,
22998 thread_name_prefix: "rd8".to_string(),
22999 park_deadline: None,
23000 drain_linger: DEFAULT_DRAIN_LINGER,
23001 }
23002 .validate()
23003 .expect_err("worker_threads == 0 must be rejected");
23004 assert_eq!(
23005 error.to_string(),
23006 "worker_threads must be greater than zero"
23007 );
23008 }
23009
23010 #[test]
23011 fn validate_rejects_zero_max_blocking_tasks() {
23012 let error = RuntimeOptions {
23013 flavor: RuntimeFlavor::MultiThread,
23014 worker_threads: 2,
23015 max_blocking_tasks: 0,
23016 thread_name_prefix: "rd8".to_string(),
23017 park_deadline: None,
23018 drain_linger: DEFAULT_DRAIN_LINGER,
23019 }
23020 .validate()
23021 .expect_err("max_blocking_tasks == 0 must be rejected");
23022 assert_eq!(
23023 error.to_string(),
23024 "max_blocking_tasks must be greater than zero"
23025 );
23026 }
23027
23028 #[test]
23029 fn validate_current_thread_coerces_worker_threads_to_one() {
23030 let validated = RuntimeOptions {
23033 flavor: RuntimeFlavor::CurrentThread,
23034 worker_threads: 8,
23035 max_blocking_tasks: 8,
23036 thread_name_prefix: "rd8".to_string(),
23037 park_deadline: None,
23038 drain_linger: DEFAULT_DRAIN_LINGER,
23039 }
23040 .validate()
23041 .expect("CurrentThread options must validate");
23042 assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
23043 assert_eq!(validated.worker_threads, 1);
23044 assert_eq!(validated.max_blocking_tasks, 1);
23045 }
23046
23047 #[cfg(not(target_family = "wasm"))]
23050 #[test]
23051 fn validate_reserves_one_worker_from_blocking_admission() {
23052 let validated = RuntimeOptions {
23053 flavor: RuntimeFlavor::MultiThread,
23054 worker_threads: 2,
23055 max_blocking_tasks: 8,
23056 thread_name_prefix: "rd8".to_string(),
23057 park_deadline: None,
23058 drain_linger: DEFAULT_DRAIN_LINGER,
23059 }
23060 .validate()
23061 .expect("MultiThread options must validate");
23062 assert_eq!(validated.worker_threads, 2);
23063 assert_eq!(validated.max_blocking_tasks, 1);
23064 }
23065
23066 #[cfg(not(target_family = "wasm"))]
23067 #[test]
23068 fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
23069 let validated = RuntimeOptions {
23070 flavor: RuntimeFlavor::MultiThread,
23071 worker_threads: 1,
23072 max_blocking_tasks: 8,
23073 thread_name_prefix: "rd8-minimum".to_string(),
23074 park_deadline: None,
23075 drain_linger: DEFAULT_DRAIN_LINGER,
23076 }
23077 .validate()
23078 .expect("MultiThread options must validate");
23079 assert_eq!(validated.worker_threads, 2);
23080 assert_eq!(validated.max_blocking_tasks, 1);
23081 }
23082
23083 #[cfg(not(target_family = "wasm"))]
23084 #[test]
23085 fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
23086 let simulated_32_bit = RuntimeOptions {
23087 flavor: RuntimeFlavor::MultiThread,
23088 worker_threads: 256,
23089 max_blocking_tasks: 256,
23090 thread_name_prefix: "rd8-rayon-cap-32".to_string(),
23091 park_deadline: None,
23092 drain_linger: DEFAULT_DRAIN_LINGER,
23093 }
23094 .validate_with_rayon_max_threads(Some(255))
23095 .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
23096 assert_eq!(simulated_32_bit.worker_threads, 255);
23097 assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
23098
23099 let rayon_max_threads = max_num_threads();
23100 let requested_threads = rayon_max_threads
23101 .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
23102 .checked_add(1)
23103 .expect("supported worker counts must leave room for an oversized request");
23104 let controller = RuntimeController::new();
23105 controller
23106 .configure(RuntimeOptions {
23107 flavor: RuntimeFlavor::MultiThread,
23108 worker_threads: requested_threads,
23109 max_blocking_tasks: requested_threads,
23110 thread_name_prefix: "rd8-rayon-cap".to_string(),
23111 park_deadline: None,
23112 drain_linger: DEFAULT_DRAIN_LINGER,
23113 })
23114 .expect("an oversized request must clamp to the production worker ceiling");
23115
23116 let reported = controller.options();
23117 let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
23118 assert_eq!(reported.worker_threads, expected_workers);
23119 assert_eq!(
23120 reported.max_blocking_tasks,
23121 expected_workers - 1,
23122 "the blocking cap must reserve a lane from the physically realizable worker count"
23123 );
23124 }
23125
23126 #[cfg(not(target_family = "wasm"))]
23127 #[test]
23128 fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
23129 let extreme = Duration::from_micros(u64::MAX);
23136 let validated = RuntimeOptions {
23137 flavor: RuntimeFlavor::MultiThread,
23138 worker_threads: 2,
23139 max_blocking_tasks: 1,
23140 thread_name_prefix: "linger-clamp".to_string(),
23141 park_deadline: None,
23142 drain_linger: extreme,
23143 }
23144 .validate_with_rayon_max_threads(Some(2))
23145 .expect("an extreme linger budget must clamp, not fail validation");
23146 assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
23147
23148 let controller = RuntimeController::new();
23149 controller
23150 .configure_partial(RuntimeOptionsPatch {
23151 drain_linger: Some(extreme),
23152 ..RuntimeOptionsPatch::default()
23153 })
23154 .expect("an extreme linger patch must clamp, not fail validation");
23155 assert_eq!(
23156 controller.options().drain_linger,
23157 MAX_DRAIN_LINGER,
23158 "introspection must report the clamped budget, not the requested one"
23159 );
23160 controller
23161 .configure_partial(RuntimeOptionsPatch {
23162 drain_linger: Some(Duration::ZERO),
23163 ..RuntimeOptionsPatch::default()
23164 })
23165 .expect("ZERO (lingering disabled) must remain a valid budget");
23166 assert_eq!(controller.options().drain_linger, Duration::ZERO);
23167
23168 let metrics = Arc::new(RuntimeMetrics::default());
23169 let unvalidated = RuntimeOptions {
23170 flavor: RuntimeFlavor::MultiThread,
23171 worker_threads: 2,
23172 max_blocking_tasks: 2,
23173 thread_name_prefix: "linger-clamp-exec".to_string(),
23174 park_deadline: None,
23175 drain_linger: extreme,
23176 };
23177 let executor = MultiThreadExecutor::new(&unvalidated, metrics)
23178 .expect("the executor must construct with an extreme unvalidated budget");
23179 assert_eq!(
23180 executor.drain_linger,
23181 Some(MAX_DRAIN_LINGER),
23182 "the constructor's defensive clamp must bound unvalidated budgets"
23183 );
23184
23185 let metrics = Arc::new(RuntimeMetrics::default());
23186 let disabled = RuntimeOptions {
23187 flavor: RuntimeFlavor::MultiThread,
23188 worker_threads: 2,
23189 max_blocking_tasks: 2,
23190 thread_name_prefix: "linger-clamp-off".to_string(),
23191 park_deadline: None,
23192 drain_linger: Duration::ZERO,
23193 };
23194 let executor = MultiThreadExecutor::new(&disabled, metrics)
23195 .expect("the executor must construct with lingering disabled");
23196 assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
23197 }
23198
23199 #[cfg(not(target_family = "wasm"))]
23200 #[test]
23201 fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
23202 let controller = RuntimeController::new();
23203 let options = controller.options();
23204 assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
23205 assert_eq!(
23206 options.max_blocking_tasks,
23207 options.worker_threads.saturating_sub(1).max(1),
23208 "the unconfigured Rust API must use the same validated reserve as the binding"
23209 );
23210 }
23211
23212 #[cfg(not(target_family = "wasm"))]
23213 #[test]
23214 fn concurrent_partial_configuration_merges_from_latest_committed_options() {
23215 use std::{sync::mpsc, time::Duration};
23216
23217 let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
23218 let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
23219 let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
23220 let first_controller = Arc::clone(&controller);
23221 let first = std::thread::spawn(move || {
23222 first_controller
23223 .configure_partial_inner(
23224 RuntimeOptionsPatch {
23225 max_blocking_tasks: Some(3),
23226 ..RuntimeOptionsPatch::default()
23227 },
23228 |candidate| {
23229 assert_eq!(
23230 (candidate.worker_threads, candidate.max_blocking_tasks),
23231 (8, 3)
23232 );
23233 first_merged_tx
23234 .send(())
23235 .expect("the race coordinator must still be listening");
23236 release_first_rx
23237 .recv_timeout(Duration::from_secs(5))
23238 .expect("the first partial configuration must be released");
23239 },
23240 )
23241 .expect("the first partial configuration must commit");
23242 });
23243 first_merged_rx
23244 .recv_timeout(Duration::from_secs(5))
23245 .expect("the first partial configuration must hold the controller lock after merging");
23246
23247 let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
23248 let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
23249 let second_controller = Arc::clone(&controller);
23250 let second = std::thread::spawn(move || {
23251 second_started_tx
23252 .send(())
23253 .expect("the race coordinator must still be listening");
23254 second_controller
23255 .configure_partial_inner(
23256 RuntimeOptionsPatch {
23257 worker_threads: Some(6),
23258 ..RuntimeOptionsPatch::default()
23259 },
23260 |candidate| {
23261 second_candidate_tx
23262 .send((candidate.worker_threads, candidate.max_blocking_tasks))
23263 .expect("the race coordinator must still be listening");
23264 },
23265 )
23266 .expect("the second partial configuration must commit");
23267 });
23268 second_started_rx
23269 .recv_timeout(Duration::from_secs(5))
23270 .expect("the second partial configuration thread must start");
23271 release_first_tx
23272 .send(())
23273 .expect("the first partial configuration must still be waiting");
23274
23275 first
23276 .join()
23277 .expect("the first configuration thread must not panic");
23278 second
23279 .join()
23280 .expect("the second configuration thread must not panic");
23281 assert_eq!(
23282 second_candidate_rx
23283 .recv_timeout(Duration::from_secs(5))
23284 .expect("the second partial configuration must expose its merged candidate"),
23285 (6, 3),
23286 "the second caller must merge against the first caller's committed options"
23287 );
23288
23289 let options = controller.options();
23290 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
23291 assert_eq!(options.thread_name_prefix, "partial-config-race");
23292 }
23293
23294 #[cfg(not(target_family = "wasm"))]
23295 #[test]
23296 fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
23297 use std::{sync::mpsc, time::Duration};
23298
23299 let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
23300 let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
23301 let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
23302 let invalid_controller = Arc::clone(&controller);
23303 let invalid = std::thread::spawn(move || {
23304 invalid_controller
23305 .configure_partial_inner(
23306 RuntimeOptionsPatch {
23307 worker_threads: Some(0),
23308 ..RuntimeOptionsPatch::default()
23309 },
23310 |candidate| {
23311 assert_eq!(
23312 (candidate.worker_threads, candidate.max_blocking_tasks),
23313 (0, 2)
23314 );
23315 invalid_merged_tx
23316 .send(())
23317 .expect("the race coordinator must still be listening");
23318 release_invalid_rx
23319 .recv_timeout(Duration::from_secs(5))
23320 .expect("the invalid partial configuration must be released");
23321 },
23322 )
23323 .expect_err("the invalid partial configuration must be rejected")
23324 .to_string()
23325 });
23326 invalid_merged_rx
23327 .recv_timeout(Duration::from_secs(5))
23328 .expect("the invalid partial configuration must hold the lock after merging");
23329
23330 let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
23331 let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
23332 let valid_controller = Arc::clone(&controller);
23333 let valid = std::thread::spawn(move || {
23334 valid_started_tx
23335 .send(())
23336 .expect("the race coordinator must still be listening");
23337 valid_controller
23338 .configure_partial_inner(
23339 RuntimeOptionsPatch {
23340 max_blocking_tasks: Some(4),
23341 ..RuntimeOptionsPatch::default()
23342 },
23343 |candidate| {
23344 valid_candidate_tx
23345 .send((candidate.worker_threads, candidate.max_blocking_tasks))
23346 .expect("the race coordinator must still be listening");
23347 },
23348 )
23349 .expect("the valid partial configuration must commit");
23350 });
23351 valid_started_rx
23352 .recv_timeout(Duration::from_secs(5))
23353 .expect("the valid partial configuration thread must start");
23354 release_invalid_tx
23355 .send(())
23356 .expect("the invalid partial configuration must still be waiting");
23357
23358 assert_eq!(
23359 invalid
23360 .join()
23361 .expect("the invalid configuration thread must not panic"),
23362 "worker_threads must be greater than zero"
23363 );
23364 valid
23365 .join()
23366 .expect("the valid configuration thread must not panic");
23367 assert_eq!(
23368 valid_candidate_rx
23369 .recv_timeout(Duration::from_secs(5))
23370 .expect("the valid partial configuration must expose its merged candidate"),
23371 (6, 4),
23372 "the valid caller must merge against the last committed options, not the rejected candidate"
23373 );
23374
23375 let options = controller.options();
23376 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
23377 assert_eq!(options.thread_name_prefix, "partial-config-rollback");
23378 }
23379
23380 #[test]
23381 fn configure_after_backend_started_is_rejected() {
23382 let controller = RuntimeController::new();
23385 controller
23386 .configure(RuntimeOptions {
23387 flavor: RuntimeFlavor::CurrentThread,
23388 worker_threads: 1,
23389 max_blocking_tasks: 1,
23390 thread_name_prefix: "rd8".to_string(),
23391 park_deadline: None,
23392 drain_linger: DEFAULT_DRAIN_LINGER,
23393 })
23394 .expect("first configure before the backend exists must succeed");
23395
23396 let _backend = controller.backend();
23398
23399 let error = controller
23400 .configure(RuntimeOptions::default())
23401 .expect_err("configure after the backend started must be rejected");
23402 assert_eq!(
23403 error.to_string(),
23404 "the async runtime configuration is frozen; configure it before the first async call"
23405 );
23406 }
23407
23408 #[test]
23409 fn partial_configuration_after_backend_started_is_rejected() {
23410 let controller = current_thread_controller("partial-config-frozen");
23411 let _backend = controller.backend();
23412
23413 let error = controller
23414 .configure_partial(RuntimeOptionsPatch {
23415 worker_threads: Some(2),
23416 ..RuntimeOptionsPatch::default()
23417 })
23418 .expect_err("partial configuration after the backend started must be rejected");
23419 assert_eq!(
23420 error.to_string(),
23421 "the async runtime configuration is frozen; configure it before the first async call"
23422 );
23423 }
23424
23425 fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
23426 let controller = RuntimeController::new();
23427 controller
23428 .configure(RuntimeOptions {
23429 flavor: RuntimeFlavor::CurrentThread,
23430 worker_threads: 1,
23431 max_blocking_tasks: 1,
23432 thread_name_prefix: thread_name_prefix.to_string(),
23433 park_deadline: None,
23434 drain_linger: DEFAULT_DRAIN_LINGER,
23435 })
23436 .expect("CurrentThread test configuration must be accepted");
23437 controller
23438 }
23439
23440 #[cfg(not(target_family = "wasm"))]
23441 fn multi_thread_controller(
23442 thread_name_prefix: &str,
23443 worker_threads: usize,
23444 max_blocking_tasks: usize,
23445 ) -> RuntimeController {
23446 let controller = RuntimeController::new();
23447 controller
23448 .configure(RuntimeOptions {
23449 flavor: RuntimeFlavor::MultiThread,
23450 worker_threads,
23451 max_blocking_tasks,
23452 thread_name_prefix: thread_name_prefix.to_string(),
23453 park_deadline: None,
23454 drain_linger: DEFAULT_DRAIN_LINGER,
23455 })
23456 .expect("MultiThread test configuration must be accepted");
23457 controller
23458 }
23459
23460 #[test]
23461 fn blocking_backend_creation_failure_preserves_diagnostic() {
23462 let controller = current_thread_controller("blocking-backend-creation-failure");
23463 let ran = Arc::new(AtomicBool::new(false));
23464 let work_ran = Arc::clone(&ran);
23465
23466 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23467 let Err((error, work)) = controller.try_spawn_blocking(move || {
23468 work_ran.store(true, Ordering::SeqCst);
23469 17usize
23470 }) else {
23471 panic!("the injected backend creation failure must reject blocking work");
23472 };
23473 assert_eq!(
23474 error.to_string(),
23475 "injected async runtime backend creation failure"
23476 );
23477 assert!(
23478 !ran.load(Ordering::SeqCst),
23479 "rejected blocking work must not run"
23480 );
23481 assert_eq!(
23482 work(),
23483 17,
23484 "the rejected blocking closure must remain usable"
23485 );
23486 assert!(ran.load(Ordering::SeqCst));
23487
23488 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23489 let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
23490 .expect_err("infallible blocking submission must return a failed join handle");
23491 assert_eq!(
23492 error.to_string(),
23493 "injected async runtime backend creation failure"
23494 );
23495
23496 let handle = controller
23497 .try_spawn_blocking(|| 31usize)
23498 .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
23499 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
23500 controller.shutdown().unwrap();
23501 }
23502
23503 #[cfg(not(target_family = "wasm"))]
23504 #[test]
23505 fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
23506 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
23507
23508 if std::env::var_os(CHILD_ENV).is_some() {
23509 struct PollProbe {
23510 polls: Arc<AtomicUsize>,
23511 }
23512
23513 impl Future for PollProbe {
23514 type Output = ();
23515
23516 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23517 self.polls.fetch_add(1, Ordering::SeqCst);
23518 Poll::Ready(())
23519 }
23520 }
23521
23522 shutdown().expect("the isolated runtime must stop before block_on admission");
23523 let polls = Arc::new(AtomicUsize::new(0));
23524 let mut future = std::pin::pin!(PollProbe {
23525 polls: Arc::clone(&polls)
23526 });
23527 let error = try_block_on_dyn(future.as_mut())
23528 .expect_err("the stopped runtime must reject the borrowed future");
23529 assert_eq!(
23530 error.to_string(),
23531 "the async runtime is stopped; call start before submitting work"
23532 );
23533 assert_eq!(
23534 polls.load(Ordering::SeqCst),
23535 0,
23536 "rejected block_on must not poll its future"
23537 );
23538
23539 start().expect("the isolated runtime must restart");
23540 try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
23541 assert_eq!(polls.load(Ordering::SeqCst), 1);
23542 shutdown().expect("the restarted isolated runtime must stop");
23543 return;
23544 }
23545
23546 let output = std::process::Command::new(std::env::current_exe().unwrap())
23547 .arg("--exact")
23548 .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
23549 .arg("--nocapture")
23550 .env(CHILD_ENV, "1")
23551 .output()
23552 .expect("the stopped block_on subprocess must start");
23553 assert!(
23554 output.status.success(),
23555 "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
23556 output.status.code(),
23557 String::from_utf8_lossy(&output.stdout),
23558 String::from_utf8_lossy(&output.stderr)
23559 );
23560 }
23561
23562 #[cfg(not(target_family = "wasm"))]
23563 #[test]
23564 fn try_block_on_dyn_reports_concurrent_shutdown() {
23565 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
23566
23567 if std::env::var_os(CHILD_ENV).is_some() {
23568 use std::sync::mpsc;
23569
23570 struct PendingProbe {
23571 entered: Option<mpsc::Sender<()>>,
23572 complete: Arc<AtomicBool>,
23573 }
23574
23575 impl Future for PendingProbe {
23576 type Output = ();
23577
23578 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23579 if let Some(entered) = self.entered.take() {
23580 entered.send(()).unwrap();
23581 }
23582 if self.complete.load(Ordering::SeqCst) {
23583 Poll::Ready(())
23584 } else {
23585 Poll::Pending
23586 }
23587 }
23588 }
23589
23590 configure(RuntimeOptions {
23591 flavor: RuntimeFlavor::CurrentThread,
23592 worker_threads: 1,
23593 max_blocking_tasks: 1,
23594 thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
23595 park_deadline: None,
23596 drain_linger: DEFAULT_DRAIN_LINGER,
23597 })
23598 .expect("the isolated runtime must accept CurrentThread configuration");
23599 start().expect("the isolated runtime must start");
23600
23601 let (entered_tx, entered_rx) = mpsc::channel();
23602 let (error_tx, error_rx) = mpsc::channel();
23603 let (retry_tx, retry_rx) = mpsc::channel();
23604 let complete = Arc::new(AtomicBool::new(false));
23605 let future_complete = Arc::clone(&complete);
23606 let runner = std::thread::spawn(move || {
23607 let mut future = std::pin::pin!(PendingProbe {
23608 entered: Some(entered_tx),
23609 complete: future_complete
23610 });
23611 let error = try_block_on_dyn(future.as_mut())
23612 .expect_err("concurrent shutdown must interrupt the borrowed future")
23613 .to_string();
23614 error_tx.send(error).unwrap();
23615 retry_rx.recv().unwrap();
23616 try_block_on_dyn(future.as_mut())
23617 .expect("restart must accept the same interrupted borrowed future");
23618 });
23619 entered_rx
23620 .recv_timeout(Duration::from_secs(2))
23621 .expect("the borrowed future must be polled before shutdown");
23622 shutdown().expect("shutdown must wait for the active block_on scope to retire");
23623 assert_eq!(
23624 error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
23625 "the async runtime stopped before block_on completed its future"
23626 );
23627 start().expect("the isolated runtime must restart after the interrupted drive");
23628 complete.store(true, Ordering::SeqCst);
23629 retry_tx.send(()).unwrap();
23630 runner.join().unwrap();
23631 shutdown().expect("the retried isolated runtime must stop");
23632 return;
23633 }
23634
23635 let output = std::process::Command::new(std::env::current_exe().unwrap())
23636 .arg("--exact")
23637 .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
23638 .arg("--nocapture")
23639 .env(CHILD_ENV, "1")
23640 .output()
23641 .expect("the concurrent block_on shutdown subprocess must start");
23642 assert!(
23643 output.status.success(),
23644 "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
23645 output.status.code(),
23646 String::from_utf8_lossy(&output.stdout),
23647 String::from_utf8_lossy(&output.stderr)
23648 );
23649 }
23650
23651 #[test]
23652 fn initial_lifecycle_start_preserves_lazy_configuration() {
23653 let controller = RuntimeController::new();
23654 controller
23655 .start()
23656 .expect("initial lifecycle start must succeed");
23657 controller
23658 .configure(RuntimeOptions {
23659 flavor: RuntimeFlavor::CurrentThread,
23660 worker_threads: 1,
23661 max_blocking_tasks: 1,
23662 thread_name_prefix: "lifecycle-initial-start".to_string(),
23663 park_deadline: None,
23664 drain_linger: DEFAULT_DRAIN_LINGER,
23665 })
23666 .expect("module-registration start must not consume the configuration window");
23667
23668 let state = controller
23669 .state
23670 .lock()
23671 .unwrap_or_else(std::sync::PoisonError::into_inner);
23672 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23673 }
23674
23675 #[test]
23676 fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
23677 let controller = RuntimeController::new();
23678
23679 for _ in 0..3 {
23680 controller
23681 .start()
23682 .expect("zero-work lifecycle start must succeed");
23683 assert!(matches!(
23684 &controller
23685 .state
23686 .lock()
23687 .unwrap_or_else(std::sync::PoisonError::into_inner)
23688 .lifecycle,
23689 RuntimeLifecycle::Initial
23690 ));
23691
23692 controller
23693 .shutdown()
23694 .expect("zero-work lifecycle shutdown must succeed");
23695 assert!(matches!(
23696 &controller
23697 .state
23698 .lock()
23699 .unwrap_or_else(std::sync::PoisonError::into_inner)
23700 .lifecycle,
23701 RuntimeLifecycle::StoppedBeforeFirstUse
23702 ));
23703 }
23704
23705 controller
23706 .start()
23707 .expect("a later zero-work restart must remain lazy");
23708 controller
23709 .configure(RuntimeOptions {
23710 flavor: RuntimeFlavor::CurrentThread,
23711 worker_threads: 1,
23712 max_blocking_tasks: 1,
23713 thread_name_prefix: "lifecycle-zero-work-config".to_string(),
23714 park_deadline: None,
23715 drain_linger: DEFAULT_DRAIN_LINGER,
23716 })
23717 .expect("zero-work lifecycle cycles must not consume the configuration window");
23718
23719 let state = controller
23720 .state
23721 .lock()
23722 .unwrap_or_else(std::sync::PoisonError::into_inner);
23723 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23724 }
23725
23726 #[cfg(not(target_family = "wasm"))]
23727 #[test]
23728 fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
23729 use std::sync::mpsc;
23730
23731 struct BlockingDropFuture {
23732 entered: mpsc::Sender<()>,
23733 release: mpsc::Receiver<()>,
23734 }
23735
23736 impl Future for BlockingDropFuture {
23737 type Output = ();
23738
23739 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23740 Poll::Pending
23741 }
23742 }
23743
23744 impl Drop for BlockingDropFuture {
23745 fn drop(&mut self) {
23746 self.entered.send(()).unwrap();
23747 self.release.recv().unwrap();
23748 }
23749 }
23750
23751 let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
23752 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23753 let (release_drop_tx, release_drop_rx) = mpsc::channel();
23754 let submit_controller = Arc::clone(&controller);
23755 let submitter = std::thread::spawn(move || {
23756 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23757 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23758 entered: drop_entered_tx,
23759 release: release_drop_rx,
23760 }));
23761 assert!(
23762 result.is_err(),
23763 "the injected backend creation failure must reject the submission"
23764 );
23765 });
23766 drop_entered_rx
23767 .recv_timeout(Duration::from_secs(2))
23768 .expect("the rejected future destructor must start");
23769
23770 let retry_ran = Arc::new(AtomicBool::new(false));
23771 let retry_ran_task = Arc::clone(&retry_ran);
23772 let Err(retry) = controller.try_spawn_detached(async move {
23773 retry_ran_task.store(true, Ordering::SeqCst);
23774 }) else {
23775 panic!("backend creation must remain closed during rejected destruction");
23776 };
23777 drop(retry);
23778 assert!(!retry_ran.load(Ordering::SeqCst));
23779
23780 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23781 let shutdown_controller = Arc::clone(&controller);
23782 let shutdown = std::thread::spawn(move || {
23783 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23784 });
23785 wait_until("initial shutdown to close runtime admission", || {
23786 matches!(
23787 controller
23788 .state
23789 .lock()
23790 .unwrap_or_else(std::sync::PoisonError::into_inner)
23791 .lifecycle,
23792 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
23793 )
23794 });
23795 assert!(
23796 shutdown_rx
23797 .recv_timeout(Duration::from_millis(200))
23798 .is_err(),
23799 "initial shutdown must wait for rejected user state destruction"
23800 );
23801 assert!(
23802 controller.try_spawn_detached(async {}).is_err(),
23803 "shutdown must keep first-generation admission closed while it waits"
23804 );
23805
23806 let (start_tx, start_rx) = mpsc::channel();
23807 let start_controller = Arc::clone(&controller);
23808 let starter = std::thread::spawn(move || {
23809 start_tx.send(start_controller.start()).unwrap();
23810 });
23811 assert!(
23812 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23813 "start must wait for the zero-backend shutdown transition to finish"
23814 );
23815
23816 release_drop_tx.send(()).unwrap();
23817 submitter.join().unwrap();
23818 shutdown_rx
23819 .recv_timeout(Duration::from_secs(2))
23820 .unwrap()
23821 .unwrap();
23822 shutdown.join().unwrap();
23823 start_rx
23824 .recv_timeout(Duration::from_secs(2))
23825 .unwrap()
23826 .unwrap();
23827 starter.join().unwrap();
23828
23829 let ran = Arc::new(AtomicBool::new(false));
23830 let ran_task = Arc::clone(&ran);
23831 controller
23832 .try_spawn_detached(async move {
23833 ran_task.store(true, Ordering::SeqCst);
23834 })
23835 .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
23836 let dispatch = controller_current_thread_dispatch(&controller);
23837 controller.drive_current_thread_dispatch(dispatch, false);
23838 assert!(ran.load(Ordering::SeqCst));
23839 controller.shutdown().unwrap();
23840 }
23841
23842 #[cfg(not(target_family = "wasm"))]
23843 #[test]
23844 fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
23845 use std::sync::mpsc;
23846
23847 struct BlockingDropFuture {
23848 entered: mpsc::Sender<()>,
23849 release: mpsc::Receiver<()>,
23850 }
23851
23852 impl Future for BlockingDropFuture {
23853 type Output = ();
23854
23855 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23856 Poll::Pending
23857 }
23858 }
23859
23860 impl Drop for BlockingDropFuture {
23861 fn drop(&mut self) {
23862 self.entered.send(()).unwrap();
23863 self.release.recv().unwrap();
23864 }
23865 }
23866
23867 let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
23868 let old_generation = controller.backend().generation();
23869 controller.shutdown().unwrap();
23870
23871 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23872 let (release_drop_tx, release_drop_rx) = mpsc::channel();
23873 let submit_controller = Arc::clone(&controller);
23874 let submitter = std::thread::spawn(move || {
23875 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23876 entered: drop_entered_tx,
23877 release: release_drop_rx,
23878 }));
23879 assert!(
23880 result.is_err(),
23881 "the stopped runtime must reject the submission"
23882 );
23883 });
23884 drop_entered_rx
23885 .recv_timeout(Duration::from_secs(2))
23886 .unwrap();
23887
23888 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23889 let shutdown_controller = Arc::clone(&controller);
23890 let shutdown = std::thread::spawn(move || {
23891 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23892 });
23893 wait_until(
23894 "stopped shutdown to enter its zero-backend transition",
23895 || {
23896 matches!(
23897 controller
23898 .state
23899 .lock()
23900 .unwrap_or_else(std::sync::PoisonError::into_inner)
23901 .lifecycle,
23902 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
23903 )
23904 },
23905 );
23906
23907 let (start_tx, start_rx) = mpsc::channel();
23908 let start_controller = Arc::clone(&controller);
23909 let starter = std::thread::spawn(move || {
23910 start_tx.send(start_controller.start()).unwrap();
23911 });
23912 assert!(
23913 shutdown_rx
23914 .recv_timeout(Duration::from_millis(200))
23915 .is_err(),
23916 "stopped shutdown must wait for rejected destruction"
23917 );
23918 assert!(
23919 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23920 "restart must wait for the stopped zero-backend transition"
23921 );
23922
23923 release_drop_tx.send(()).unwrap();
23924 submitter.join().unwrap();
23925 shutdown_rx
23926 .recv_timeout(Duration::from_secs(2))
23927 .unwrap()
23928 .unwrap();
23929 shutdown.join().unwrap();
23930 start_rx
23931 .recv_timeout(Duration::from_secs(2))
23932 .unwrap()
23933 .unwrap();
23934 starter.join().unwrap();
23935
23936 let new_generation = controller.backend().generation();
23937 assert_ne!(new_generation, old_generation);
23938 controller.shutdown().unwrap();
23939 }
23940
23941 #[test]
23942 fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
23943 use std::sync::mpsc;
23944
23945 struct ShutdownOnDrop {
23946 controller: Arc<RuntimeController>,
23947 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
23948 }
23949
23950 impl Future for ShutdownOnDrop {
23951 type Output = ();
23952
23953 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23954 Poll::Pending
23955 }
23956 }
23957
23958 impl Drop for ShutdownOnDrop {
23959 fn drop(&mut self) {
23960 self.result.send(self.controller.shutdown()).unwrap();
23961 }
23962 }
23963
23964 let controller = Arc::new(current_thread_controller(
23965 "failed-initial-reentrant-shutdown",
23966 ));
23967 let (result_tx, result_rx) = mpsc::channel();
23968 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23969 let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
23970 controller: Arc::clone(&controller),
23971 result: result_tx,
23972 }));
23973 assert!(rejected.is_err());
23974
23975 let error = result_rx
23976 .recv_timeout(Duration::from_secs(1))
23977 .expect("the rejected destructor must not self-deadlock")
23978 .expect_err("rejected destruction cannot wait for its own shutdown");
23979 assert!(error.to_string().contains("rejected submission"));
23980 {
23981 let state = controller
23982 .state
23983 .lock()
23984 .unwrap_or_else(std::sync::PoisonError::into_inner);
23985 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23986 assert_eq!(state.rejected_drops, 0);
23987 }
23988
23989 let handle = controller
23990 .try_spawn(async { 23usize })
23991 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
23992 let dispatch = controller_current_thread_dispatch(&controller);
23993 controller.drive_current_thread_dispatch(dispatch, false);
23994 assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
23995 controller.shutdown().unwrap();
23996 }
23997
23998 #[test]
23999 fn failed_initial_submission_destructor_cannot_reenter_start() {
24000 use std::sync::mpsc;
24001
24002 struct StartOnDrop {
24003 controller: Arc<RuntimeController>,
24004 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
24005 release: mpsc::Receiver<()>,
24006 }
24007
24008 impl Future for StartOnDrop {
24009 type Output = ();
24010
24011 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24012 Poll::Pending
24013 }
24014 }
24015
24016 impl Drop for StartOnDrop {
24017 fn drop(&mut self) {
24018 self.result.send(self.controller.start()).unwrap();
24019 self.release.recv().unwrap();
24020 }
24021 }
24022
24023 let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
24024 let (result_tx, result_rx) = mpsc::channel();
24025 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24026 let (submission_tx, submission_rx) = mpsc::channel();
24027 let submit_controller = Arc::clone(&controller);
24028 let submitter = std::thread::spawn(move || {
24029 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24030 let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
24031 controller: Arc::clone(&submit_controller),
24032 result: result_tx,
24033 release: release_drop_rx,
24034 }));
24035 submission_tx.send(rejected).unwrap();
24036 });
24037
24038 let error = result_rx
24039 .recv_timeout(Duration::from_secs(1))
24040 .expect("the rejected destructor must not self-deadlock")
24041 .expect_err("rejected destruction must not report a successful runtime start");
24042 assert!(error.to_string().contains("rejected submission"));
24043 {
24044 let state = controller
24045 .state
24046 .lock()
24047 .unwrap_or_else(std::sync::PoisonError::into_inner);
24048 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24049 assert_eq!(state.rejected_drops, 1);
24050 }
24051
24052 let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
24053 let (external_start_tx, external_start_rx) = mpsc::channel();
24054 let start_controller = Arc::clone(&controller);
24055 let starter = std::thread::spawn(move || {
24056 BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
24057 *slot.borrow_mut() = Some(Box::new(move || {
24058 wait_entered_tx.send(()).unwrap();
24059 }));
24060 });
24061 external_start_tx.send(start_controller.start()).unwrap();
24062 });
24063 wait_entered_rx
24064 .recv_timeout(Duration::from_secs(1))
24065 .expect("external start must reach the Initial rejected-destruction wait");
24066 assert!(
24067 external_start_rx.try_recv().is_err(),
24068 "external start must not return while rejected destruction remains active"
24069 );
24070
24071 release_drop_tx.send(()).unwrap();
24072 assert!(
24073 submission_rx
24074 .recv_timeout(Duration::from_secs(2))
24075 .unwrap()
24076 .is_err()
24077 );
24078 submitter.join().unwrap();
24079 external_start_rx
24080 .recv_timeout(Duration::from_secs(2))
24081 .unwrap()
24082 .unwrap();
24083 starter.join().unwrap();
24084
24085 {
24086 let state = controller
24087 .state
24088 .lock()
24089 .unwrap_or_else(std::sync::PoisonError::into_inner);
24090 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24091 assert_eq!(state.rejected_drops, 0);
24092 }
24093
24094 let handle = controller
24095 .try_spawn(async { 31usize })
24096 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
24097 let dispatch = controller_current_thread_dispatch(&controller);
24098 controller.drive_current_thread_dispatch(dispatch, false);
24099 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
24100 controller.shutdown().unwrap();
24101 }
24102
24103 #[test]
24104 fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
24105 use std::sync::mpsc;
24106
24107 struct BlockingDropFuture {
24108 controller: Arc<RuntimeController>,
24109 entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24110 release: mpsc::Receiver<()>,
24111 }
24112
24113 impl Future for BlockingDropFuture {
24114 type Output = ();
24115
24116 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24117 Poll::Pending
24118 }
24119 }
24120
24121 impl Drop for BlockingDropFuture {
24122 fn drop(&mut self) {
24123 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
24124 self
24125 .entered
24126 .send((generation, self.controller.shutdown()))
24127 .unwrap();
24128 self.release.recv().unwrap();
24129 }
24130 }
24131
24132 let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
24133 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24134 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24135 let (runner_done_tx, runner_done_rx) = mpsc::channel();
24136 let runner_controller = Arc::clone(&controller);
24137 let runner = std::thread::spawn(move || {
24138 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24139 let result = catch_unwind(AssertUnwindSafe(|| {
24140 block_on_with_controller(
24141 &runner_controller,
24142 BlockingDropFuture {
24143 controller: Arc::clone(&runner_controller),
24144 entered: drop_entered_tx,
24145 release: release_drop_rx,
24146 },
24147 );
24148 }));
24149 runner_done_tx.send(result.is_err()).unwrap();
24150 });
24151
24152 let (generation, reentrant_shutdown) = drop_entered_rx
24153 .recv_timeout(Duration::from_secs(2))
24154 .expect("failed block_on acquisition must destroy its owned future");
24155 assert_eq!(generation, None, "no runtime generation was created");
24156 let error = reentrant_shutdown
24157 .expect_err("rejected block_on destruction cannot wait for its own shutdown");
24158 assert!(error.to_string().contains("rejected submission"));
24159
24160 let Err(retry_error) = controller.try_backend() else {
24161 panic!("backend retry must remain closed during rejected block_on destruction");
24162 };
24163 assert!(retry_error.to_string().contains("rejected submission"));
24164
24165 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24166 let shutdown_controller = Arc::clone(&controller);
24167 let shutdown = std::thread::spawn(move || {
24168 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24169 });
24170 assert!(
24171 shutdown_rx
24172 .recv_timeout(Duration::from_millis(200))
24173 .is_err(),
24174 "initial shutdown must wait for rejected block_on destruction"
24175 );
24176
24177 release_drop_tx.send(()).unwrap();
24178 assert!(
24179 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24180 "block_on must preserve its infallible panic contract after contained cleanup"
24181 );
24182 runner.join().unwrap();
24183 shutdown_rx
24184 .recv_timeout(Duration::from_secs(2))
24185 .unwrap()
24186 .unwrap();
24187 shutdown.join().unwrap();
24188
24189 controller
24190 .start()
24191 .expect("zero-generation shutdown must remain restartable");
24192 let ran = Arc::new(AtomicBool::new(false));
24193 let ran_task = Arc::clone(&ran);
24194 controller
24195 .try_spawn_detached(async move {
24196 ran_task.store(true, Ordering::SeqCst);
24197 })
24198 .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
24199 let dispatch = controller_current_thread_dispatch(&controller);
24200 controller.drive_current_thread_dispatch(dispatch, false);
24201 assert!(ran.load(Ordering::SeqCst));
24202 controller.shutdown().unwrap();
24203 }
24204
24205 #[test]
24206 fn public_block_on_contains_poll_and_drop_double_panic() {
24207 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
24208
24209 if std::env::var_os(CHILD_ENV).is_some() {
24210 struct PanicInPollAndDrop;
24211
24212 impl Future for PanicInPollAndDrop {
24213 type Output = ();
24214
24215 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24216 panic!("intentional block_on poll panic");
24217 }
24218 }
24219
24220 impl Drop for PanicInPollAndDrop {
24221 fn drop(&mut self) {
24222 panic!("intentional block_on future destructor panic");
24223 }
24224 }
24225
24226 let controller = current_thread_controller("block-on-double-panic");
24227 assert!(
24228 catch_unwind(AssertUnwindSafe(|| {
24229 block_on_with_controller(&controller, PanicInPollAndDrop);
24230 }))
24231 .is_err(),
24232 "the original poll panic must still reach the caller"
24233 );
24234 controller
24235 .shutdown()
24236 .expect("contained future destruction must not strand the runtime");
24237 return;
24238 }
24239
24240 let output = std::process::Command::new(std::env::current_exe().unwrap())
24241 .arg("--exact")
24242 .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
24243 .arg("--nocapture")
24244 .env(CHILD_ENV, "1")
24245 .output()
24246 .expect("the subprocess test must start");
24247 assert!(
24248 output.status.success(),
24249 "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
24250 output.status.code(),
24251 String::from_utf8_lossy(&output.stdout),
24252 String::from_utf8_lossy(&output.stderr)
24253 );
24254 }
24255
24256 #[test]
24257 fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
24258 let controller = RuntimeController::new();
24259 controller
24260 .start()
24261 .expect("initial lifecycle start must succeed");
24262 controller
24263 .shutdown()
24264 .expect("zero-work lifecycle shutdown must succeed");
24265 controller
24266 .start()
24267 .expect("zero-work lifecycle restart must remain lazy");
24268 controller
24269 .configure(RuntimeOptions {
24270 flavor: RuntimeFlavor::CurrentThread,
24271 worker_threads: 1,
24272 max_blocking_tasks: 1,
24273 thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
24274 park_deadline: None,
24275 drain_linger: DEFAULT_DRAIN_LINGER,
24276 })
24277 .expect("configuration before first real async use must succeed");
24278
24279 let first_generation = controller.backend().generation();
24280 controller
24281 .shutdown()
24282 .expect("the first real generation must stop");
24283 assert!(matches!(
24284 &controller
24285 .state
24286 .lock()
24287 .unwrap_or_else(std::sync::PoisonError::into_inner)
24288 .lifecycle,
24289 RuntimeLifecycle::Stopped
24290 ));
24291 let error = controller
24292 .configure(RuntimeOptions::default())
24293 .expect_err("a real generation must permanently freeze configuration");
24294 assert_eq!(
24295 error.to_string(),
24296 "the async runtime configuration is frozen; configure it before the first async call"
24297 );
24298
24299 controller
24300 .start()
24301 .expect("the stopped real generation must restart");
24302 let restarted_generation = {
24303 let state = controller
24304 .state
24305 .lock()
24306 .unwrap_or_else(std::sync::PoisonError::into_inner);
24307 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
24308 panic!("a real generation restart must eagerly create its replacement backend");
24309 };
24310 backend.generation()
24311 };
24312 assert_ne!(
24313 restarted_generation, first_generation,
24314 "a real generation restart must not reuse the retired backend"
24315 );
24316 controller
24317 .shutdown()
24318 .expect("the replacement generation must stop");
24319 }
24320
24321 #[test]
24322 fn runtime_controller_rejects_submissions_after_shutdown() {
24323 let controller = current_thread_controller("lifecycle-reject");
24324 controller.start().expect("runtime must start");
24325 drop(controller.backend());
24326 controller.shutdown().expect("runtime must stop");
24327
24328 let async_ran = Arc::new(AtomicBool::new(false));
24329 let async_ran_task = Arc::clone(&async_ran);
24330 let Err(rejected_future) = controller.try_spawn_detached(async move {
24331 async_ran_task.store(true, Ordering::SeqCst);
24332 }) else {
24333 panic!("shutdown must reject async work");
24334 };
24335 assert!(
24336 !async_ran.load(Ordering::SeqCst),
24337 "a rejected future must not be polled"
24338 );
24339 futures::executor::block_on(rejected_future);
24340 assert!(
24341 async_ran.load(Ordering::SeqCst),
24342 "the original future must remain usable"
24343 );
24344
24345 let blocking_ran = Arc::new(AtomicBool::new(false));
24346 let blocking_ran_task = Arc::clone(&blocking_ran);
24347 let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
24348 blocking_ran_task.store(true, Ordering::SeqCst);
24349 7
24350 }) else {
24351 panic!("shutdown must reject blocking work");
24352 };
24353 assert_eq!(
24354 blocking_error.to_string(),
24355 "the async runtime is stopped; call start before submitting work"
24356 );
24357 assert!(
24358 !blocking_ran.load(Ordering::SeqCst),
24359 "rejected blocking work must not run"
24360 );
24361 assert_eq!(
24362 rejected_work(),
24363 7,
24364 "the original closure must remain usable"
24365 );
24366
24367 let config_error = controller
24368 .configure(RuntimeOptions::default())
24369 .expect_err("shutdown must not make runtime configuration mutable again");
24370 assert_eq!(
24371 config_error.to_string(),
24372 "the async runtime configuration is frozen; configure it before the first async call"
24373 );
24374
24375 let state = controller
24376 .state
24377 .lock()
24378 .unwrap_or_else(std::sync::PoisonError::into_inner);
24379 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
24380 }
24381
24382 #[test]
24383 fn rejected_future_can_be_resubmitted_after_restart() {
24384 let controller = current_thread_controller("lifecycle-retry-future");
24385 controller.start().expect("runtime must start");
24386 drop(controller.backend());
24387 controller.shutdown().expect("runtime must stop");
24388
24389 let runs = Arc::new(AtomicUsize::new(0));
24390 let runs_task = Arc::clone(&runs);
24391 let future = async move {
24392 runs_task.fetch_add(1, Ordering::SeqCst);
24393 23
24394 };
24395 let Err((error, future)) = controller.try_spawn(future) else {
24396 panic!("stopped runtime must reject the future");
24397 };
24398 assert!(error.to_string().contains("runtime is stopped"));
24399 assert_eq!(runs.load(Ordering::SeqCst), 0);
24400
24401 controller.start().expect("runtime must restart");
24402 let handle = controller
24403 .try_spawn(future)
24404 .unwrap_or_else(|_| panic!("restart must accept retained future"));
24405 let dispatch = controller_current_thread_dispatch(&controller);
24406 controller.drive_current_thread_dispatch(dispatch, false);
24407 assert_eq!(
24408 futures::executor::block_on(handle).expect("retained future must complete"),
24409 23
24410 );
24411 assert_eq!(runs.load(Ordering::SeqCst), 1);
24412 controller
24413 .shutdown()
24414 .expect("replacement runtime must stop");
24415 }
24416
24417 #[test]
24418 fn runtime_controller_start_after_shutdown_accepts_work() {
24419 let controller = current_thread_controller("lifecycle-restart");
24420 controller.start().expect("runtime must start");
24421 drop(controller.backend());
24422 controller.shutdown().expect("runtime must stop");
24423 controller.start().expect("runtime must restart");
24424
24425 let async_ran = Arc::new(AtomicBool::new(false));
24426 let async_ran_task = Arc::clone(&async_ran);
24427 controller
24428 .try_spawn_detached(async move {
24429 async_ran_task.store(true, Ordering::SeqCst);
24430 })
24431 .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
24432 assert!(
24433 !async_ran.load(Ordering::SeqCst),
24434 "hostless CurrentThread submission must remain enqueue-only"
24435 );
24436 let dispatch = controller_current_thread_dispatch(&controller);
24437 controller.drive_current_thread_dispatch(dispatch, false);
24438 assert!(
24439 async_ran.load(Ordering::SeqCst),
24440 "restarted runtime must poll submitted work"
24441 );
24442
24443 let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
24444 panic!("restarted runtime must accept blocking work");
24445 };
24446 assert_eq!(
24447 futures::executor::block_on(handle).expect("blocking work must complete after restart"),
24448 11
24449 );
24450 }
24451
24452 #[test]
24453 fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
24454 let controller = current_thread_controller("stale-host-generation");
24455 let old_generation = controller.backend().generation();
24456 let old_dispatch = controller_current_thread_dispatch(&controller);
24457 controller.shutdown().unwrap();
24458 controller.start().unwrap();
24459 let new_generation = controller.backend().generation();
24460 assert_ne!(new_generation, old_generation);
24461
24462 let ran = Arc::new(AtomicBool::new(false));
24463 let ran_task = Arc::clone(&ran);
24464 controller
24465 .try_spawn_detached(async move {
24466 ran_task.store(true, Ordering::SeqCst);
24467 })
24468 .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
24469 let new_dispatch = controller_current_thread_dispatch(&controller);
24470 assert_ne!(new_dispatch, old_dispatch);
24471
24472 controller.drive_current_thread_dispatch(old_dispatch, false);
24473 assert!(
24474 !ran.load(Ordering::SeqCst),
24475 "a callback from the retired generation must not touch the restarted executor"
24476 );
24477 controller.drive_current_thread_dispatch(new_dispatch, false);
24478 assert!(ran.load(Ordering::SeqCst));
24479 controller.shutdown().unwrap();
24480 }
24481
24482 #[test]
24483 fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
24484 let controller = current_thread_controller("stale-host-cancellation");
24485 let old_generation = controller.backend().generation();
24486 let old_dispatch = controller_current_thread_dispatch(&controller);
24487 controller.shutdown().unwrap();
24488 controller.start().unwrap();
24489 let backend = controller.backend();
24490 let new_generation = backend.generation();
24491 assert_ne!(new_generation, old_generation);
24492 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24493 panic!("configured CurrentThread backend must create a CurrentThread executor");
24494 };
24495 let new_dispatch = current_thread_dispatch(executor);
24496 assert_ne!(new_dispatch, old_dispatch);
24497
24498 controller.cancel_current_thread_dispatch_for_test(old_dispatch);
24499 assert_eq!(
24500 executor.dispatch_pending.load(Ordering::Acquire),
24501 new_dispatch,
24502 "a delayed failure from the retired generation must not release the replacement latch"
24503 );
24504
24505 controller.cancel_current_thread_dispatch_for_test(new_dispatch);
24506 assert_eq!(
24507 executor.dispatch_pending.load(Ordering::Acquire),
24508 0,
24509 "the live generation must be able to cancel its accepted dispatch"
24510 );
24511 drop(backend);
24512 controller.shutdown().unwrap();
24513 }
24514
24515 #[cfg(not(target_family = "wasm"))]
24516 #[test]
24517 fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
24518 use std::sync::mpsc;
24519
24520 type ProbeResult = (
24521 &'static str,
24522 Option<u64>,
24523 Result<(), String>,
24524 Result<(), String>,
24525 Result<(), String>,
24526 );
24527
24528 struct StaleGenerationProbe {
24529 label: &'static str,
24530 controller: Arc<RuntimeController>,
24531 result: mpsc::Sender<ProbeResult>,
24532 }
24533
24534 impl Drop for StaleGenerationProbe {
24535 fn drop(&mut self) {
24536 let generation = active_runtime_generation();
24537 let submission = match self.controller.try_spawn(async {}) {
24538 Ok(handle) => {
24539 drop(handle);
24540 Ok(())
24541 }
24542 Err((error, future)) => {
24543 drop(future);
24544 Err(error.to_string())
24545 }
24546 };
24547 let start = self.controller.start().map_err(|error| error.to_string());
24548 let shutdown = self
24549 .controller
24550 .shutdown()
24551 .map_err(|error| error.to_string());
24552 self
24553 .result
24554 .send((self.label, generation, submission, start, shutdown))
24555 .unwrap();
24556 }
24557 }
24558
24559 let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
24560 let old_generation = controller.backend().generation();
24561 let (result_tx, result_rx) = mpsc::channel();
24562
24563 let async_controller = Arc::clone(&controller);
24564 let async_result = result_tx.clone();
24565 let async_handle = controller
24566 .try_spawn(async move {
24567 StaleGenerationProbe {
24568 label: "async",
24569 controller: async_controller,
24570 result: async_result,
24571 }
24572 })
24573 .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
24574 let old_dispatch = controller_current_thread_dispatch(&controller);
24575 controller.drive_current_thread_dispatch(old_dispatch, false);
24576
24577 let ready_controller = Arc::clone(&controller);
24578 let ready_handle = controller
24579 .try_spawn_blocking(move || StaleGenerationProbe {
24580 label: "ready",
24581 controller: ready_controller,
24582 result: result_tx,
24583 })
24584 .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
24585
24586 controller.shutdown().unwrap();
24587 controller.start().unwrap();
24588 let new_generation = controller.backend().generation();
24589 assert_ne!(new_generation, old_generation);
24590
24591 drop(async_handle);
24592 let async_result = result_rx
24593 .recv_timeout(Duration::from_secs(2))
24594 .expect("the retained async output must be destroyed without waiting on the replacement");
24595 drop(ready_handle);
24596 let ready_result = result_rx
24597 .recv_timeout(Duration::from_secs(2))
24598 .expect("the retained immediate output must be destroyed without touching the replacement");
24599
24600 for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
24601 let (label, generation, submission, start, shutdown) = result;
24602 assert_eq!(label, expected_label);
24603 assert_eq!(generation, Some(old_generation));
24604 for error in [submission, start, shutdown] {
24605 assert!(
24606 error
24607 .expect_err("retired-generation output destruction must be rejected")
24608 .contains("retired generation")
24609 );
24610 }
24611 }
24612
24613 let survivor = controller
24614 .try_spawn(async { 29usize })
24615 .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
24616 let new_dispatch = controller_current_thread_dispatch(&controller);
24617 controller.drive_current_thread_dispatch(new_dispatch, false);
24618 assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
24619 controller.shutdown().unwrap();
24620 }
24621
24622 #[cfg(not(target_family = "wasm"))]
24623 #[test]
24624 fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
24625 use std::sync::mpsc;
24626
24627 struct ShutdownOnDrop {
24628 controller: Arc<RuntimeController>,
24629 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24630 }
24631
24632 impl Drop for ShutdownOnDrop {
24633 fn drop(&mut self) {
24634 self
24635 .result
24636 .send((active_runtime_generation(), self.controller.shutdown()))
24637 .unwrap();
24638 }
24639 }
24640
24641 let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
24642 let old_generation = controller.backend().generation();
24643 let (result_tx, result_rx) = mpsc::channel();
24644 let old_handle = controller
24645 .try_spawn_blocking({
24646 let controller = Arc::clone(&controller);
24647 move || ShutdownOnDrop {
24648 controller,
24649 result: result_tx,
24650 }
24651 })
24652 .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
24653 wait_until(
24654 "the first-generation blocking result to be buffered",
24655 || {
24656 controller
24657 .metrics
24658 .blocking_tasks_completed
24659 .load(Ordering::Acquire)
24660 == 1
24661 },
24662 );
24663 controller.shutdown().unwrap();
24664 controller.start().unwrap();
24665 let new_generation = controller.backend().generation();
24666 assert_ne!(new_generation, old_generation);
24667
24668 let (holder_started_tx, holder_started_rx) = mpsc::channel();
24669 let (release_holder_tx, release_holder_rx) = mpsc::channel();
24670 let holder = controller
24671 .try_spawn_blocking(move || {
24672 holder_started_tx.send(()).unwrap();
24673 release_holder_rx.recv().unwrap();
24674 })
24675 .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
24676 holder_started_rx
24677 .recv_timeout(Duration::from_secs(2))
24678 .unwrap();
24679
24680 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24681 let shutdown_controller = Arc::clone(&controller);
24682 let shutdown = std::thread::spawn(move || {
24683 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24684 });
24685 wait_until("the replacement generation to enter Stopping", || {
24686 matches!(
24687 controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
24688 RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
24689 )
24690 });
24691
24692 let dropper = std::thread::spawn(move || drop(old_handle));
24693 let (observed_generation, reentrant_shutdown) =
24694 match result_rx.recv_timeout(Duration::from_secs(2)) {
24695 Ok(result) => result,
24696 Err(error) => {
24697 release_holder_tx.send(()).unwrap();
24698 let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
24699 let _ = dropper.join();
24700 let _ = shutdown.join();
24701 panic!("retained first-generation output waited on replacement shutdown: {error}");
24702 }
24703 };
24704 assert_eq!(observed_generation, Some(old_generation));
24705 let error = reentrant_shutdown
24706 .expect_err("a retained first-generation output must not control replacement shutdown");
24707 assert!(error.to_string().contains("retired generation"));
24708
24709 release_holder_tx.send(()).unwrap();
24710 shutdown_rx
24711 .recv_timeout(Duration::from_secs(2))
24712 .unwrap()
24713 .unwrap();
24714 dropper.join().unwrap();
24715 shutdown.join().unwrap();
24716 futures::executor::block_on(holder).unwrap();
24717 }
24718
24719 #[cfg(not(target_family = "wasm"))]
24720 #[test]
24721 fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
24722 use std::sync::mpsc;
24723
24724 let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
24725 let generation = controller.backend().generation();
24726 let dispatch = controller_current_thread_dispatch(&controller);
24727 let (admitted_tx, admitted_rx) = mpsc::channel();
24728 let (release_tx, release_rx) = mpsc::channel();
24729 let driver_controller = Arc::clone(&controller);
24730 let driver = std::thread::spawn(move || {
24731 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
24732 *slot.borrow_mut() = Some(Box::new(move || {
24733 admitted_tx.send(()).unwrap();
24734 release_rx.recv().unwrap();
24735 }));
24736 });
24737 driver_controller.drive_current_thread_dispatch(dispatch, false);
24738 });
24739 admitted_rx
24740 .recv_timeout(Duration::from_secs(2))
24741 .expect("the host turn must claim its scheduler role before execution");
24742
24743 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24744 let shutdown_controller = Arc::clone(&controller);
24745 let shutdown = std::thread::spawn(move || {
24746 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24747 });
24748 wait_until(
24749 "shutdown to close the admitted CurrentThread generation",
24750 || {
24751 let state = controller
24752 .state
24753 .lock()
24754 .unwrap_or_else(std::sync::PoisonError::into_inner);
24755 matches!(
24756 &state.lifecycle,
24757 RuntimeLifecycle::Stopping(identity) if identity.generation == generation
24758 )
24759 },
24760 );
24761 assert!(
24762 shutdown_rx
24763 .recv_timeout(Duration::from_millis(200))
24764 .is_err(),
24765 "shutdown must wait for an admitted host callback even before it starts draining"
24766 );
24767
24768 release_tx.send(()).unwrap();
24769 driver.join().unwrap();
24770 shutdown_rx
24771 .recv_timeout(Duration::from_secs(2))
24772 .unwrap()
24773 .unwrap();
24774 shutdown.join().unwrap();
24775 }
24776
24777 #[test]
24778 fn spawn_racing_shutdown_cannot_recreate_backend() {
24779 use std::sync::mpsc;
24780
24781 let controller = Arc::new(current_thread_controller("lifecycle-race"));
24782 controller.start().expect("runtime must start");
24783 drop(controller.backend());
24784
24785 let (at_hook_tx, at_hook_rx) = mpsc::channel();
24786 let (release_tx, release_rx) = mpsc::channel();
24787 let ran = Arc::new(AtomicBool::new(false));
24788 let ran_task = Arc::clone(&ran);
24789 let worker_controller = Arc::clone(&controller);
24790 let worker = std::thread::spawn(move || {
24791 BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
24792 *slot.borrow_mut() = Some(Box::new(move || {
24793 at_hook_tx
24794 .send(())
24795 .expect("test coordinator must still be listening");
24796 release_rx
24797 .recv()
24798 .expect("test coordinator must release the submission");
24799 }));
24800 });
24801
24802 let future = async move {
24803 ran_task.store(true, Ordering::SeqCst);
24804 };
24805 match worker_controller.try_spawn_detached(future) {
24806 Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
24807 Err(future) => drop(future),
24808 }
24809 });
24810
24811 at_hook_rx
24812 .recv_timeout(Duration::from_secs(5))
24813 .expect("submitting thread must stop before taking the controller lock");
24814 controller.shutdown().expect("runtime must stop");
24815 release_tx
24816 .send(())
24817 .expect("submitting thread must still be waiting");
24818 worker.join().expect("submitting thread must not panic");
24819
24820 assert!(
24821 !ran.load(Ordering::SeqCst),
24822 "the rejected task must never run"
24823 );
24824 let state = controller
24825 .state
24826 .lock()
24827 .unwrap_or_else(std::sync::PoisonError::into_inner);
24828 assert!(
24829 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
24830 "the rejected submission must not lazily recreate a backend"
24831 );
24832 }
24833
24834 #[cfg(not(target_family = "wasm"))]
24835 #[test]
24836 fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
24837 use std::sync::mpsc;
24838
24839 struct BlockingDropFuture {
24840 work: Arc<GenerationWork>,
24841 entered: mpsc::Sender<(Option<u64>, usize)>,
24842 release: mpsc::Receiver<()>,
24843 }
24844
24845 impl Future for BlockingDropFuture {
24846 type Output = ();
24847
24848 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24849 Poll::Pending
24850 }
24851 }
24852
24853 impl Drop for BlockingDropFuture {
24854 fn drop(&mut self) {
24855 let active = self
24856 .work
24857 .state
24858 .lock()
24859 .unwrap_or_else(std::sync::PoisonError::into_inner)
24860 .active;
24861 self
24862 .entered
24863 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24864 .unwrap();
24865 self.release.recv().unwrap();
24866 }
24867 }
24868
24869 let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
24870 let backend = controller.backend();
24871 let old_generation = backend.generation();
24872 let work = Arc::clone(&backend.work);
24873 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24874 panic!("configured CurrentThread backend must create a CurrentThread executor");
24875 };
24876 let executor = Arc::clone(executor);
24877 drop(backend);
24878
24879 let (registered_tx, registered_rx) = mpsc::channel();
24880 let (release_submission_tx, release_submission_rx) = mpsc::channel();
24881 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24882 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24883 let submit_controller = Arc::clone(&controller);
24884 let submit_work = Arc::clone(&work);
24885 let submitter = std::thread::spawn(move || {
24886 AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
24887 *slot.borrow_mut() = Some(Box::new(move || {
24888 registered_tx.send(()).unwrap();
24889 release_submission_rx.recv().unwrap();
24890 }));
24891 });
24892 submit_controller
24893 .try_spawn_detached(BlockingDropFuture {
24894 work: submit_work,
24895 entered: drop_entered_tx,
24896 release: release_drop_rx,
24897 })
24898 .unwrap_or_else(|_| {
24899 panic!("the submission linearized before shutdown and must be accepted")
24900 });
24901 });
24902 registered_rx
24903 .recv_timeout(Duration::from_secs(2))
24904 .expect("the submitting thread must register generation work before shutdown");
24905
24906 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24907 let shutdown_controller = Arc::clone(&controller);
24908 let shutdown = std::thread::spawn(move || {
24909 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24910 });
24911 wait_until("CurrentThread shutdown to close the runnable queue", || {
24912 executor
24913 .queue
24914 .lock()
24915 .unwrap_or_else(std::sync::PoisonError::into_inner)
24916 .closed
24917 });
24918 release_submission_tx.send(()).unwrap();
24919
24920 let (drop_generation, active_during_drop) = drop_entered_rx
24921 .recv_timeout(Duration::from_secs(2))
24922 .expect("the closed queue must destroy the accepted unpolled future");
24923 assert_eq!(drop_generation, Some(old_generation));
24924 assert_eq!(
24925 active_during_drop, 1,
24926 "accepted generation work must remain registered through user future destruction"
24927 );
24928
24929 let (start_tx, start_rx) = mpsc::channel();
24930 let start_controller = Arc::clone(&controller);
24931 let starter = std::thread::spawn(move || {
24932 start_tx.send(start_controller.start()).unwrap();
24933 });
24934 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
24935 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
24936 let shutdown_overlapped = early_shutdown.is_some();
24937 let restart_overlapped = early_start.is_some();
24938
24939 release_drop_tx.send(()).unwrap();
24940 submitter.join().unwrap();
24941 let shutdown_result = early_shutdown.unwrap_or_else(|| {
24942 shutdown_rx
24943 .recv_timeout(Duration::from_secs(2))
24944 .expect("shutdown must finish after drop")
24945 });
24946 shutdown_result.unwrap();
24947 shutdown.join().unwrap();
24948 let start_result = early_start.unwrap_or_else(|| {
24949 start_rx
24950 .recv_timeout(Duration::from_secs(2))
24951 .expect("restart must follow shutdown")
24952 });
24953 start_result.unwrap();
24954 starter.join().unwrap();
24955
24956 assert!(
24957 !shutdown_overlapped,
24958 "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
24959 );
24960 assert!(
24961 !restart_overlapped,
24962 "restart must not create a new generation while an accepted old-generation future is being destroyed"
24963 );
24964 assert_ne!(controller.backend().generation(), old_generation);
24965 controller.shutdown().unwrap();
24966 }
24967
24968 #[cfg(not(target_family = "wasm"))]
24969 #[test]
24970 fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
24971 use std::sync::mpsc;
24972
24973 struct BlockingDrop {
24974 work: Arc<GenerationWork>,
24975 entered: mpsc::Sender<(Option<u64>, usize)>,
24976 release: mpsc::Receiver<()>,
24977 }
24978
24979 impl Drop for BlockingDrop {
24980 fn drop(&mut self) {
24981 let active = self
24982 .work
24983 .state
24984 .lock()
24985 .unwrap_or_else(std::sync::PoisonError::into_inner)
24986 .active;
24987 self
24988 .entered
24989 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24990 .unwrap();
24991 self.release.recv().unwrap();
24992 }
24993 }
24994
24995 let controller = Arc::new(multi_thread_controller(
24996 "accepted-blocking-submission-drop",
24997 2,
24998 1,
24999 ));
25000 let backend = controller.backend();
25001 let old_generation = backend.generation();
25002 let work = Arc::clone(&backend.work);
25003 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25004 panic!("configured MultiThread backend must create a MultiThread executor");
25005 };
25006 let executor = Arc::downgrade(executor);
25007 drop(backend);
25008
25009 let (registered_tx, registered_rx) = mpsc::channel();
25010 let (release_submission_tx, release_submission_rx) = mpsc::channel();
25011 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
25012 let (release_drop_tx, release_drop_rx) = mpsc::channel();
25013 let submit_controller = Arc::clone(&controller);
25014 let submitter = std::thread::spawn(move || {
25015 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
25016 *slot.borrow_mut() = Some(Box::new(move || {
25017 registered_tx.send(()).unwrap();
25018 release_submission_rx.recv().unwrap();
25019 }));
25020 });
25021 let drop_signal = BlockingDrop {
25022 work,
25023 entered: drop_entered_tx,
25024 release: release_drop_rx,
25025 };
25026 let handle = submit_controller
25027 .try_spawn_blocking(move || {
25028 let _drop_signal = drop_signal;
25029 })
25030 .unwrap_or_else(|_| {
25031 panic!("the submission linearized before shutdown and must be accepted")
25032 });
25033 assert!(
25034 futures::executor::block_on(handle).is_err(),
25035 "the executor must cancel work that reaches its queue after shutdown closed it"
25036 );
25037 });
25038 registered_rx
25039 .recv_timeout(Duration::from_secs(2))
25040 .expect("the submitting thread must register generation work before shutdown");
25041
25042 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25043 let shutdown_controller = Arc::clone(&controller);
25044 let shutdown = std::thread::spawn(move || {
25045 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25046 });
25047 wait_until("MultiThread shutdown to close the blocking queue", || {
25048 executor
25049 .upgrade()
25050 .expect("the retiring executor must remain alive until shutdown completes")
25051 .blocking_queue
25052 .lock()
25053 .unwrap_or_else(std::sync::PoisonError::into_inner)
25054 .closed
25055 });
25056 release_submission_tx.send(()).unwrap();
25057
25058 let (drop_generation, active_during_drop) = drop_entered_rx
25059 .recv_timeout(Duration::from_secs(2))
25060 .expect("the closed queue must destroy the accepted blocking closure");
25061 assert_eq!(drop_generation, Some(old_generation));
25062 assert_eq!(
25063 active_during_drop, 1,
25064 "accepted generation work must remain registered through blocking closure destruction"
25065 );
25066
25067 let (start_tx, start_rx) = mpsc::channel();
25068 let start_controller = Arc::clone(&controller);
25069 let starter = std::thread::spawn(move || {
25070 start_tx.send(start_controller.start()).unwrap();
25071 });
25072 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
25073 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
25074 let shutdown_overlapped = early_shutdown.is_some();
25075 let restart_overlapped = early_start.is_some();
25076
25077 release_drop_tx.send(()).unwrap();
25078 submitter.join().unwrap();
25079 let shutdown_result = early_shutdown.unwrap_or_else(|| {
25080 shutdown_rx
25081 .recv_timeout(Duration::from_secs(2))
25082 .expect("shutdown must finish after drop")
25083 });
25084 shutdown_result.unwrap();
25085 shutdown.join().unwrap();
25086 let start_result = early_start.unwrap_or_else(|| {
25087 start_rx
25088 .recv_timeout(Duration::from_secs(2))
25089 .expect("restart must follow shutdown")
25090 });
25091 start_result.unwrap();
25092 starter.join().unwrap();
25093
25094 assert!(
25095 !shutdown_overlapped,
25096 "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
25097 );
25098 assert!(
25099 !restart_overlapped,
25100 "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
25101 );
25102 assert_ne!(controller.backend().generation(), old_generation);
25103 controller.shutdown().unwrap();
25104 }
25105
25106 #[cfg(not(target_family = "wasm"))]
25107 #[test]
25108 fn multi_thread_controller_reports_its_exact_physical_worker_count() {
25109 let controller = multi_thread_controller("lifecycle-physical", 1, 8);
25110 let options = controller.options();
25111 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
25112
25113 let backend = controller.backend();
25114 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25115 panic!("configured MultiThread backend must create a Rayon executor");
25116 };
25117 assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
25118 let lifecycle = Arc::clone(&executor.worker_lifecycle);
25119 drop(backend);
25120
25121 controller.shutdown().expect("runtime must stop");
25122 assert_eq!(
25123 lifecycle.remaining(),
25124 0,
25125 "shutdown must observe every physical worker exit"
25126 );
25127 }
25128
25129 #[cfg(not(target_family = "wasm"))]
25130 #[test]
25131 fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
25132 use std::sync::mpsc;
25149
25150 let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
25151 let backend = controller.backend();
25152 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25153 panic!("configured MultiThread backend must create a Rayon executor");
25154 };
25155 let worker_count = executor.pool.current_num_threads();
25156 let completed = Arc::new(AtomicUsize::new(0));
25157 let probe_completed = Arc::clone(&completed);
25158 executor.pool.broadcast(move |_| {
25159 WORKER_TLS_DROP_PROBE.with(|slot| {
25160 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25161 action: Some(WorkerTlsDropAction::Record {
25162 completed: Arc::clone(&probe_completed),
25163 }),
25164 });
25165 assert!(
25166 previous.is_none(),
25167 "each worker must install exactly one TLS drop probe"
25168 );
25169 });
25170 });
25171 drop(backend);
25172
25173 assert_eq!(
25174 completed.load(Ordering::SeqCst),
25175 0,
25176 "no worker TLS destructor may run before shutdown retires the workers"
25177 );
25178
25179 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25180 let shutdown_controller = Arc::clone(&controller);
25181 let sample_completed = Arc::clone(&completed);
25182 let shutdown = std::thread::spawn(move || {
25183 let result = shutdown_controller.shutdown();
25184 let seen_at_return = sample_completed.load(Ordering::SeqCst);
25188 shutdown_tx.send((result, seen_at_return)).unwrap();
25189 });
25190 let (shutdown_result, seen_at_return) = shutdown_rx
25191 .recv_timeout(Duration::from_secs(10))
25192 .expect("shutdown must finish once worker TLS destructors have run");
25193 shutdown_result.unwrap();
25194 assert_eq!(
25195 seen_at_return, worker_count,
25196 "every worker TLS destructor must have completed before shutdown() returned: \
25197 the worker-join barrier must include TLS-destructor completion"
25198 );
25199 join_within(
25200 "the shutdown thread exits after teardown",
25201 shutdown,
25202 Duration::from_secs(30),
25203 );
25204 }
25205
25206 #[cfg(not(target_family = "wasm"))]
25207 #[test]
25208 fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
25209 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
25210
25211 if std::env::var_os(CHILD_ENV).is_some() {
25212 use std::sync::mpsc;
25213
25214 let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
25215 let backend = controller.backend();
25216 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25217 panic!("configured MultiThread backend must create a Rayon executor");
25218 };
25219 let installed = Arc::new(AtomicBool::new(false));
25220 let (completed_tx, completed_rx) = mpsc::channel();
25221 let probe_controller = Arc::clone(&controller);
25222 let probe_installed = Arc::clone(&installed);
25223 executor.pool.broadcast(move |_| {
25224 if probe_installed.swap(true, Ordering::SeqCst) {
25225 return;
25226 }
25227 WORKER_TLS_DROP_PROBE.with(|slot| {
25228 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25229 action: Some(WorkerTlsDropAction::ReenterLifecycle {
25230 controller: Arc::clone(&probe_controller),
25231 completed: completed_tx.clone(),
25232 }),
25233 });
25234 assert!(
25235 previous.is_none(),
25236 "the selected worker must have no prior TLS probe"
25237 );
25238 });
25239 });
25240 assert!(
25241 installed.load(Ordering::SeqCst),
25242 "one worker must install the lifecycle probe"
25243 );
25244 drop(backend);
25245
25246 controller
25247 .shutdown()
25248 .expect("external shutdown must join the retiring workers");
25249 let (start, shutdown) = completed_rx
25250 .recv_timeout(Duration::from_secs(2))
25251 .expect("the TLS destructor must complete both lifecycle reentry attempts");
25252 assert_eq!(
25253 start.unwrap_err(),
25254 "cannot restart the async runtime from work in the generation being stopped"
25255 );
25256 assert_eq!(
25257 shutdown.unwrap_err(),
25258 "cannot wait for async runtime shutdown from work in the generation being stopped"
25259 );
25260 return;
25261 }
25262
25263 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
25264 .arg("--exact")
25265 .arg(
25266 "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
25267 )
25268 .arg("--nocapture")
25269 .env(CHILD_ENV, "1")
25270 .stdout(std::process::Stdio::piped())
25271 .stderr(std::process::Stdio::piped())
25272 .spawn()
25273 .expect("the TLS lifecycle-reentry subprocess must start");
25274 let deadline = Instant::now() + Duration::from_secs(5);
25275 let status = loop {
25276 if let Some(status) = child
25277 .try_wait()
25278 .expect("the subprocess status must be readable")
25279 {
25280 break status;
25281 }
25282 if Instant::now() >= deadline {
25283 let _ = child.kill();
25284 let output = child
25285 .wait_with_output()
25286 .expect("the timed-out subprocess must be reaped");
25287 panic!(
25288 "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
25289 String::from_utf8_lossy(&output.stdout),
25290 String::from_utf8_lossy(&output.stderr)
25291 );
25292 }
25293 std::thread::sleep(Duration::from_millis(10));
25294 };
25295 let output = child
25296 .wait_with_output()
25297 .expect("the completed subprocess output must be read");
25298 assert!(
25299 status.success(),
25300 "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
25301 status.code(),
25302 String::from_utf8_lossy(&output.stdout),
25303 String::from_utf8_lossy(&output.stderr)
25304 );
25305 }
25306
25307 #[cfg(not(target_family = "wasm"))]
25308 #[test]
25309 fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
25310 use std::sync::mpsc;
25311
25312 struct DropSignal(mpsc::Sender<()>);
25313 impl Drop for DropSignal {
25314 fn drop(&mut self) {
25315 let _ = self.0.send(());
25316 }
25317 }
25318
25319 let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
25320 let (polled_tx, polled_rx) = mpsc::channel();
25321 let (dropped_tx, dropped_rx) = mpsc::channel();
25322 controller
25323 .try_spawn_detached(async move {
25324 let _drop_signal = DropSignal(dropped_tx);
25325 polled_tx.send(()).unwrap();
25326 std::future::pending::<()>().await;
25327 })
25328 .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
25329 polled_rx
25330 .recv_timeout(Duration::from_secs(5))
25331 .expect("the pending task must be polled before shutdown");
25332
25333 controller
25334 .shutdown()
25335 .expect("shutdown must cancel accepted async work");
25336 dropped_rx
25337 .recv_timeout(Duration::from_secs(5))
25338 .expect("shutdown must drop the accepted task future before returning");
25339 }
25340
25341 #[cfg(not(target_family = "wasm"))]
25342 #[test]
25343 fn current_thread_shutdown_outranks_perpetual_self_wake() {
25344 use std::sync::mpsc;
25345
25346 struct SelfWakingPending {
25347 entered: Option<mpsc::Sender<()>>,
25348 }
25349
25350 impl Future for SelfWakingPending {
25351 type Output = ();
25352
25353 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25354 if let Some(entered) = self.entered.take() {
25355 entered.send(()).unwrap();
25356 }
25357 cx.waker().wake_by_ref();
25358 Poll::Pending
25359 }
25360 }
25361
25362 let controller = Arc::new(current_thread_controller(
25363 "current-thread-self-wake-shutdown",
25364 ));
25365 let (entered_tx, entered_rx) = mpsc::channel();
25366 let (outcome_tx, outcome_rx) = mpsc::channel();
25367 let block_on_controller = Arc::clone(&controller);
25368 let runner = std::thread::spawn(move || {
25369 let mut future = std::pin::pin!(SelfWakingPending {
25370 entered: Some(entered_tx)
25371 });
25372 outcome_tx
25373 .send(block_on_controller.block_on(future.as_mut()))
25374 .unwrap();
25375 });
25376 entered_rx
25377 .recv_timeout(Duration::from_secs(2))
25378 .expect("the self-waking future must be polled before shutdown");
25379
25380 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25381 let shutdown_controller = Arc::clone(&controller);
25382 let shutdown = std::thread::spawn(move || {
25383 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25384 });
25385 shutdown_rx
25386 .recv_timeout(Duration::from_secs(2))
25387 .expect("shutdown must outrank a stored self-wake permit")
25388 .unwrap();
25389 assert_eq!(
25390 outcome_rx
25391 .recv_timeout(Duration::from_secs(2))
25392 .expect("the raw block_on driver must report shutdown"),
25393 BlockOnOutcome::Stopped
25394 );
25395 runner.join().unwrap();
25396 shutdown.join().unwrap();
25397 }
25398
25399 #[cfg(not(target_family = "wasm"))]
25400 #[test]
25401 fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
25402 use std::sync::mpsc;
25403
25404 let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
25405 let backend = controller.backend();
25406 let executor = match &backend.executor {
25407 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25408 RuntimeExecutor::CurrentThread(_) => unreachable!(),
25409 };
25410 let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
25411 let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
25412 for _ in 0..2 {
25413 let worker_ready_tx = worker_ready_tx.clone();
25414 let release_workers = Arc::clone(&release_workers);
25415 executor.pool.spawn(move || {
25416 worker_ready_tx.send(()).unwrap();
25417 let (released, changed) = &*release_workers;
25418 let mut released = released
25419 .lock()
25420 .unwrap_or_else(std::sync::PoisonError::into_inner);
25421 while !*released {
25422 released = changed
25423 .wait(released)
25424 .unwrap_or_else(std::sync::PoisonError::into_inner);
25425 }
25426 });
25427 }
25428 drop(worker_ready_tx);
25429 for _ in 0..2 {
25430 worker_ready_rx
25431 .recv_timeout(Duration::from_secs(5))
25432 .expect("both workers must be occupied before the FIFO runnable is submitted");
25433 }
25434 drop(executor);
25435 drop(backend);
25436
25437 let polls = Arc::new(AtomicUsize::new(0));
25438 let handle = controller.spawn(StopPollProbe {
25439 polls: Arc::clone(&polls),
25440 first_poll: None,
25441 });
25442 wait_until("the FIFO runnable to be queued", || {
25443 controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
25444 });
25445
25446 let observed_queue = Arc::new(Mutex::new(None));
25447 let hook_observed_queue = Arc::clone(&observed_queue);
25448 let hook_metrics = Arc::clone(&controller.metrics);
25449 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25450 *slot.borrow_mut() = Some(Box::new(move || {
25451 let (released, changed) = &*release_workers;
25452 *released
25453 .lock()
25454 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
25455 changed.notify_all();
25456 wait_until("the stopped FIFO runnable claim", || {
25457 hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
25458 });
25459 *hook_observed_queue
25460 .lock()
25461 .unwrap_or_else(std::sync::PoisonError::into_inner) =
25462 Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
25463 }));
25464 });
25465
25466 controller
25467 .shutdown()
25468 .expect("shutdown must cancel the stopped FIFO claim");
25469 assert_eq!(polls.load(Ordering::SeqCst), 0);
25470 assert_eq!(
25471 *observed_queue
25472 .lock()
25473 .unwrap_or_else(std::sync::PoisonError::into_inner),
25474 Some(0)
25475 );
25476 assert_eq!(
25477 controller.metrics.queued_runnables.load(Ordering::SeqCst),
25478 0
25479 );
25480 drop(handle);
25481 }
25482
25483 #[cfg(not(target_family = "wasm"))]
25484 #[test]
25485 fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
25486 use std::sync::mpsc;
25487
25488 let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
25489 let backend = controller.backend();
25490 let executor = match &backend.executor {
25491 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25492 RuntimeExecutor::CurrentThread(_) => unreachable!(),
25493 };
25494 let polls = Arc::new(AtomicUsize::new(0));
25495 let scheduler = Arc::downgrade(&executor);
25496 let (runnable, task) = async_task::spawn(
25497 StopPollProbe {
25498 polls: Arc::clone(&polls),
25499 first_poll: None,
25500 },
25501 move |runnable| {
25502 if let Some(executor) = scheduler.upgrade() {
25503 executor.schedule(runnable);
25504 }
25505 },
25506 );
25507 let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
25508 let (release_slot_tx, release_slot_rx) = mpsc::channel();
25509 let (claim_done_tx, claim_done_rx) = mpsc::channel();
25510 let worker_executor = Arc::clone(&executor);
25511 let worker_metrics = Arc::clone(&controller.metrics);
25512 executor.pool.spawn(move || {
25513 let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
25514 worker_executor.schedule(runnable);
25515 slot_ready_tx.send(()).unwrap();
25516 release_slot_rx.recv().unwrap();
25517 let claimed = worker_executor.run_one_runnable();
25518 claim_done_tx
25519 .send((
25520 claimed,
25521 worker_metrics.queued_runnables.load(Ordering::SeqCst),
25522 ))
25523 .unwrap();
25524 });
25525 slot_ready_rx
25526 .recv_timeout(Duration::from_secs(5))
25527 .expect("the worker must publish the LIFO runnable before shutdown");
25528 assert_eq!(
25529 controller.metrics.queued_runnables.load(Ordering::SeqCst),
25530 1
25531 );
25532 drop(executor);
25533 drop(backend);
25534
25535 let observed_claim = Arc::new(Mutex::new(None));
25536 let hook_observed_claim = Arc::clone(&observed_claim);
25537 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25538 *slot.borrow_mut() = Some(Box::new(move || {
25539 release_slot_tx.send(()).unwrap();
25540 let result = claim_done_rx
25541 .recv_timeout(Duration::from_secs(5))
25542 .expect("the stopped LIFO runnable claim must complete before abort");
25543 *hook_observed_claim
25544 .lock()
25545 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
25546 }));
25547 });
25548
25549 controller
25550 .shutdown()
25551 .expect("shutdown must cancel the stopped LIFO claim");
25552 assert_eq!(polls.load(Ordering::SeqCst), 0);
25553 assert_eq!(
25554 *observed_claim
25555 .lock()
25556 .unwrap_or_else(std::sync::PoisonError::into_inner),
25557 Some((true, 0))
25558 );
25559 assert_eq!(
25560 controller.metrics.queued_runnables.load(Ordering::SeqCst),
25561 0
25562 );
25563 drop(task);
25564 }
25565
25566 #[cfg(not(target_family = "wasm"))]
25567 #[test]
25568 fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
25569 use std::sync::mpsc;
25570
25571 struct BlockingDrop {
25572 entered: mpsc::Sender<()>,
25573 release: mpsc::Receiver<()>,
25574 }
25575
25576 impl Drop for BlockingDrop {
25577 fn drop(&mut self) {
25578 self.entered.send(()).unwrap();
25579 self.release.recv().unwrap();
25580 }
25581 }
25582
25583 struct TwoPollProbe {
25584 first_poll: Option<mpsc::Sender<()>>,
25585 release_first_poll: mpsc::Receiver<()>,
25586 second_poll: mpsc::Sender<()>,
25587 }
25588
25589 impl Future for TwoPollProbe {
25590 type Output = ();
25591
25592 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25593 if let Some(first_poll) = self.first_poll.take() {
25594 first_poll.send(()).unwrap();
25595 self.release_first_poll.recv().unwrap();
25596 } else {
25597 self.second_poll.send(()).unwrap();
25598 }
25599 cx.waker().wake_by_ref();
25600 Poll::Pending
25601 }
25602 }
25603
25604 let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
25605 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
25606 let (release_drop_tx, release_drop_rx) = mpsc::channel();
25607 let blocking_drop = BlockingDrop {
25608 entered: drop_entered_tx,
25609 release: release_drop_rx,
25610 };
25611 controller
25612 .try_spawn_detached(async move {
25613 let _drop = blocking_drop;
25614 std::future::pending::<()>().await;
25615 })
25616 .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
25617
25618 let (first_poll_tx, first_poll_rx) = mpsc::channel();
25619 let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
25620 let (second_poll_tx, second_poll_rx) = mpsc::channel();
25621 let (outcome_tx, outcome_rx) = mpsc::channel();
25622 let runner_controller = Arc::clone(&controller);
25623 let runner = std::thread::spawn(move || {
25624 let mut future = std::pin::pin!(TwoPollProbe {
25625 first_poll: Some(first_poll_tx),
25626 release_first_poll: release_first_poll_rx,
25627 second_poll: second_poll_tx,
25628 });
25629 outcome_tx
25630 .send(runner_controller.block_on(future.as_mut()))
25631 .unwrap();
25632 });
25633 first_poll_rx
25634 .recv_timeout(Duration::from_secs(2))
25635 .expect("the active block_on poll must enter before shutdown");
25636
25637 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25638 let shutdown_controller = Arc::clone(&controller);
25639 let shutdown = std::thread::spawn(move || {
25640 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25641 });
25642 drop_entered_rx
25643 .recv_timeout(Duration::from_secs(2))
25644 .expect("shutdown must begin destroying queued work");
25645
25646 release_first_poll_tx.send(()).unwrap();
25647 let polled_again = second_poll_rx
25648 .recv_timeout(Duration::from_millis(200))
25649 .is_ok();
25650 release_drop_tx.send(()).unwrap();
25651
25652 assert!(
25653 !polled_again,
25654 "an active block_on driver must observe generation stop before queued user cleanup blocks"
25655 );
25656 assert_eq!(
25657 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25658 BlockOnOutcome::Stopped
25659 );
25660 shutdown_rx
25661 .recv_timeout(Duration::from_secs(2))
25662 .unwrap()
25663 .unwrap();
25664 runner.join().unwrap();
25665 shutdown.join().unwrap();
25666 }
25667
25668 #[cfg(not(target_family = "wasm"))]
25669 #[test]
25670 fn shutdown_cancels_nested_block_on_within_deadline() {
25671 use std::sync::mpsc;
25672
25673 let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
25674 let task_controller = Arc::clone(&controller);
25675 let (entered_tx, entered_rx) = mpsc::channel();
25676 let task = controller
25677 .try_spawn(async move {
25678 entered_tx.send(()).unwrap();
25679 block_on_with_controller(&task_controller, std::future::pending::<()>());
25680 })
25681 .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
25682 entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25683
25684 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25685 let shutdown_controller = Arc::clone(&controller);
25686 let shutdown = std::thread::spawn(move || {
25687 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25688 });
25689 shutdown_rx
25690 .recv_timeout(Duration::from_secs(2))
25691 .expect("shutdown must wake and cancel an active nested block_on")
25692 .unwrap();
25693 shutdown.join().unwrap();
25694 assert!(
25695 futures::executor::block_on(task).is_err(),
25696 "the cancelled nested block_on task must resolve as stopped"
25697 );
25698 }
25699
25700 #[cfg(not(target_family = "wasm"))]
25701 #[test]
25702 fn stopped_public_block_on_drops_future_before_generation_retirement() {
25703 use std::sync::mpsc;
25704
25705 struct PendingDrop {
25706 entered: Option<mpsc::Sender<()>>,
25707 dropped: mpsc::Sender<Option<u64>>,
25708 release: mpsc::Receiver<()>,
25709 }
25710
25711 impl Future for PendingDrop {
25712 type Output = ();
25713
25714 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25715 if let Some(entered) = self.entered.take() {
25716 entered.send(()).unwrap();
25717 }
25718 Poll::Pending
25719 }
25720 }
25721
25722 impl Drop for PendingDrop {
25723 fn drop(&mut self) {
25724 self
25725 .dropped
25726 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25727 .unwrap();
25728 self.release.recv().unwrap();
25729 }
25730 }
25731
25732 let controller = Arc::new(current_thread_controller(
25733 "public-block-on-generation-destruction",
25734 ));
25735 let generation = controller.backend().generation();
25736 let (entered_tx, entered_rx) = mpsc::channel();
25737 let (dropped_tx, dropped_rx) = mpsc::channel();
25738 let (release_tx, release_rx) = mpsc::channel();
25739 let (runner_done_tx, runner_done_rx) = mpsc::channel();
25740 let block_controller = Arc::clone(&controller);
25741 let runner = std::thread::spawn(move || {
25742 let result = catch_unwind(AssertUnwindSafe(|| {
25743 block_on_with_controller(
25744 &block_controller,
25745 PendingDrop {
25746 entered: Some(entered_tx),
25747 dropped: dropped_tx,
25748 release: release_rx,
25749 },
25750 );
25751 }));
25752 runner_done_tx.send(result.is_err()).unwrap();
25753 });
25754 entered_rx
25755 .recv_timeout(Duration::from_secs(2))
25756 .expect("the public block_on future must be polled before shutdown");
25757
25758 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25759 let shutdown_controller = Arc::clone(&controller);
25760 let shutdown = std::thread::spawn(move || {
25761 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25762 });
25763
25764 assert_eq!(
25765 dropped_rx
25766 .recv_timeout(Duration::from_secs(2))
25767 .expect("shutdown must wake block_on and begin destroying its future"),
25768 Some(generation),
25769 "the stopped future must be destroyed under its retiring generation identity"
25770 );
25771 assert!(
25772 shutdown_rx
25773 .recv_timeout(Duration::from_millis(200))
25774 .is_err(),
25775 "shutdown must not publish Stopped while the public block_on future destructor is running"
25776 );
25777
25778 release_tx.send(()).unwrap();
25779 assert!(
25780 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25781 "the public helper must retain its existing early-stop panic behavior"
25782 );
25783 shutdown_rx
25784 .recv_timeout(Duration::from_secs(2))
25785 .unwrap()
25786 .unwrap();
25787 runner.join().unwrap();
25788 shutdown.join().unwrap();
25789 }
25790
25791 #[cfg(not(target_family = "wasm"))]
25792 #[test]
25793 fn shutdown_contains_panicking_nested_block_on_future_drop() {
25794 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
25795
25796 if std::env::var_os(CHILD_ENV).is_some() {
25797 use std::sync::mpsc;
25798
25799 struct PendingPanicOnDrop {
25800 entered: Option<mpsc::Sender<()>>,
25801 }
25802
25803 impl Future for PendingPanicOnDrop {
25804 type Output = ();
25805
25806 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25807 if let Some(entered) = self.entered.take() {
25808 entered.send(()).unwrap();
25809 }
25810 Poll::Pending
25811 }
25812 }
25813
25814 impl Drop for PendingPanicOnDrop {
25815 fn drop(&mut self) {
25816 panic!("intentional nested block_on future destructor panic");
25817 }
25818 }
25819
25820 let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
25821 let task_controller = Arc::clone(&controller);
25822 let (entered_tx, entered_rx) = mpsc::channel();
25823 let task = controller
25824 .try_spawn(async move {
25825 block_on_with_controller(
25826 &task_controller,
25827 PendingPanicOnDrop {
25828 entered: Some(entered_tx),
25829 },
25830 );
25831 })
25832 .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
25833 entered_rx
25834 .recv_timeout(Duration::from_secs(2))
25835 .expect("the nested block_on future must be polled before shutdown");
25836
25837 controller
25838 .shutdown()
25839 .expect("shutdown must contain nested future destruction");
25840 assert!(
25841 futures::executor::block_on(task).is_err(),
25842 "shutdown cancellation must still surface as a task error"
25843 );
25844 return;
25845 }
25846
25847 let output = std::process::Command::new(std::env::current_exe().unwrap())
25848 .arg("--exact")
25849 .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
25850 .arg("--nocapture")
25851 .env(CHILD_ENV, "1")
25852 .output()
25853 .expect("the subprocess test must start");
25854 assert!(
25855 output.status.success(),
25856 "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
25857 output.status.code(),
25858 String::from_utf8_lossy(&output.stdout),
25859 String::from_utf8_lossy(&output.stderr)
25860 );
25861 }
25862
25863 #[test]
25864 fn panicking_future_drop_is_contained_before_async_task_drop() {
25865 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
25866
25867 if std::env::var_os(CHILD_ENV).is_some() {
25868 struct PanicOnDrop;
25869 impl Drop for PanicOnDrop {
25870 fn drop(&mut self) {
25871 panic!("intentional future destructor panic");
25872 }
25873 }
25874
25875 let controller = current_thread_controller("future-drop-subprocess");
25876 let bomb = PanicOnDrop;
25877 controller
25878 .try_spawn_detached(async move {
25879 let _bomb = bomb;
25880 std::future::pending::<()>().await;
25881 })
25882 .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
25883 controller
25884 .shutdown()
25885 .expect("shutdown must contain the future destructor panic");
25886 return;
25887 }
25888
25889 let output = std::process::Command::new(std::env::current_exe().unwrap())
25890 .arg("--exact")
25891 .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
25892 .arg("--nocapture")
25893 .env(CHILD_ENV, "1")
25894 .output()
25895 .expect("the subprocess test must start");
25896 assert!(
25897 output.status.success(),
25898 "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25899 output.status.code(),
25900 String::from_utf8_lossy(&output.stdout),
25901 String::from_utf8_lossy(&output.stderr)
25902 );
25903 }
25904
25905 #[test]
25906 fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
25907 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
25908
25909 if std::env::var_os(CHILD_ENV).is_some() {
25910 struct PanicOnDrop;
25911 impl Drop for PanicOnDrop {
25912 fn drop(&mut self) {
25913 panic!("intentional detached output destructor panic");
25914 }
25915 }
25916
25917 let controller = current_thread_controller("output-drop-subprocess");
25918 let handle = controller
25919 .try_spawn(async { PanicOnDrop })
25920 .unwrap_or_else(|_| panic!("runtime must accept the task"));
25921 drop(handle);
25922 let dispatch = controller_current_thread_dispatch(&controller);
25923 controller.drive_current_thread_dispatch(dispatch, false);
25924 controller.shutdown().unwrap();
25925 return;
25926 }
25927
25928 let output = std::process::Command::new(std::env::current_exe().unwrap())
25929 .arg("--exact")
25930 .arg(
25931 "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
25932 )
25933 .arg("--nocapture")
25934 .env(CHILD_ENV, "1")
25935 .output()
25936 .expect("the subprocess test must start");
25937 assert!(
25938 output.status.success(),
25939 "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25940 output.status.code(),
25941 String::from_utf8_lossy(&output.stdout),
25942 String::from_utf8_lossy(&output.stderr)
25943 );
25944 }
25945
25946 #[cfg(not(target_family = "wasm"))]
25947 #[test]
25948 fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
25949 use std::sync::mpsc;
25950
25951 struct BlockingDrop {
25952 controller: Arc<RuntimeController>,
25953 entered: mpsc::Sender<Option<u64>>,
25954 release: mpsc::Receiver<()>,
25955 reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
25956 }
25957
25958 impl Drop for BlockingDrop {
25959 fn drop(&mut self) {
25960 self
25961 .entered
25962 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25963 .unwrap();
25964 self.release.recv().unwrap();
25965 self
25966 .reentrant_shutdown
25967 .send(self.controller.shutdown())
25968 .unwrap();
25969 }
25970 }
25971
25972 let controller = Arc::new(current_thread_controller(
25973 "current-thread-host-drain-output-retirement",
25974 ));
25975 let old_generation = controller.backend().generation();
25976 let (entered_tx, entered_rx) = mpsc::channel();
25977 let (release_tx, release_rx) = mpsc::channel();
25978 let (reentrant_tx, reentrant_rx) = mpsc::channel();
25979 let output_controller = Arc::clone(&controller);
25980 let handle = controller
25981 .try_spawn(async move {
25982 BlockingDrop {
25983 controller: output_controller,
25984 entered: entered_tx,
25985 release: release_rx,
25986 reentrant_shutdown: reentrant_tx,
25987 }
25988 })
25989 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
25990 drop(handle);
25991 let dispatch = controller_current_thread_dispatch(&controller);
25992
25993 let driver_controller = Arc::clone(&controller);
25994 let driver =
25995 std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
25996 assert_eq!(
25997 entered_rx
25998 .recv_timeout(Duration::from_secs(2))
25999 .expect("the host turn must begin destroying the detached output"),
26000 Some(old_generation),
26001 "detached output destruction must retain the generation that produced it"
26002 );
26003
26004 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26005 let shutdown_controller = Arc::clone(&controller);
26006 let shutdown = std::thread::spawn(move || {
26007 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26008 });
26009 wait_until(
26010 "CurrentThread shutdown to enter the old generation's stopping state",
26011 || {
26012 let state = controller
26013 .state
26014 .lock()
26015 .unwrap_or_else(std::sync::PoisonError::into_inner);
26016 matches!(
26017 &state.lifecycle,
26018 RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
26019 )
26020 },
26021 );
26022
26023 let (restart_tx, restart_rx) = mpsc::channel();
26024 let restart_controller = Arc::clone(&controller);
26025 let restart = std::thread::spawn(move || {
26026 restart_controller.start().unwrap();
26027 restart_tx
26028 .send(restart_controller.backend().generation())
26029 .unwrap();
26030 });
26031
26032 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
26033 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
26034 let shutdown_overlapped = early_shutdown.is_some();
26035 let restart_overlapped = early_restart.is_some();
26036 release_tx.send(()).unwrap();
26037
26038 let reentrant_error = reentrant_rx
26039 .recv_timeout(Duration::from_secs(2))
26040 .expect("the retiring output destructor must finish its lifecycle check")
26041 .expect_err("old-generation output destruction must not shut down a replacement runtime");
26042 assert!(
26043 reentrant_error
26044 .to_string()
26045 .contains("generation being stopped"),
26046 "the destructor must still observe its old generation as Stopping: {reentrant_error}"
26047 );
26048
26049 let shutdown_result = early_shutdown.unwrap_or_else(|| {
26050 shutdown_rx
26051 .recv_timeout(Duration::from_secs(2))
26052 .expect("shutdown must finish after detached output destruction retires")
26053 });
26054 shutdown_result.unwrap();
26055 let new_generation = early_restart.unwrap_or_else(|| {
26056 restart_rx
26057 .recv_timeout(Duration::from_secs(2))
26058 .expect("restart must finish after the old host turn retires")
26059 });
26060
26061 driver.join().unwrap();
26062 shutdown.join().unwrap();
26063 restart.join().unwrap();
26064 assert!(
26065 !shutdown_overlapped,
26066 "shutdown must not publish Stopped while a host turn is destroying detached output"
26067 );
26068 assert!(
26069 !restart_overlapped,
26070 "restart must not overlap a host turn from the retiring generation"
26071 );
26072 assert_ne!(new_generation, old_generation);
26073 controller.shutdown().unwrap();
26074 }
26075
26076 #[cfg(not(target_family = "wasm"))]
26077 #[test]
26078 fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
26079 use std::sync::mpsc;
26080
26081 struct ShutdownOnDrop {
26082 controller: Arc<RuntimeController>,
26083 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26084 }
26085
26086 impl Drop for ShutdownOnDrop {
26087 fn drop(&mut self) {
26088 let _ = self.result.send(self.controller.shutdown());
26089 }
26090 }
26091
26092 let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
26093 let (running_tx, running_rx) = mpsc::channel();
26094 let (release_tx, release_rx) = mpsc::channel();
26095 let running = controller
26096 .try_spawn_blocking(move || {
26097 running_tx.send(()).unwrap();
26098 release_rx.recv().unwrap();
26099 })
26100 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26101 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26102
26103 let (reentrant_tx, reentrant_rx) = mpsc::channel();
26104 let bomb = ShutdownOnDrop {
26105 controller: Arc::clone(&controller),
26106 result: reentrant_tx,
26107 };
26108 let queued = controller
26109 .try_spawn_blocking(move || {
26110 let _bomb = &bomb;
26111 })
26112 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26113
26114 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26115 let shutdown_controller = Arc::clone(&controller);
26116 let shutdown = std::thread::spawn(move || {
26117 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26118 });
26119 let error = reentrant_rx
26120 .recv_timeout(Duration::from_secs(2))
26121 .expect("queued destruction must not self-deadlock")
26122 .expect_err("the retiring generation must reject reentrant shutdown");
26123 assert!(error.to_string().contains("generation being stopped"));
26124 release_tx.send(()).unwrap();
26125 shutdown_rx
26126 .recv_timeout(Duration::from_secs(2))
26127 .unwrap()
26128 .unwrap();
26129 shutdown.join().unwrap();
26130 futures::executor::block_on(running).unwrap();
26131 assert!(futures::executor::block_on(queued).is_err());
26132 }
26133
26134 #[cfg(not(target_family = "wasm"))]
26135 #[test]
26136 fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
26137 use std::sync::mpsc;
26138
26139 struct ShutdownAfterRelease {
26140 controller: Arc<RuntimeController>,
26141 entered: mpsc::Sender<()>,
26142 release: mpsc::Receiver<()>,
26143 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26144 }
26145
26146 impl Drop for ShutdownAfterRelease {
26147 fn drop(&mut self) {
26148 self.entered.send(()).unwrap();
26149 self.release.recv().unwrap();
26150 self.result.send(self.controller.shutdown()).unwrap();
26151 }
26152 }
26153
26154 let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
26155 let (holder_started_tx, holder_started_rx) = mpsc::channel();
26156 let (release_holder_tx, release_holder_rx) = mpsc::channel();
26157 let holder = controller
26158 .try_spawn_blocking(move || {
26159 holder_started_tx.send(()).unwrap();
26160 release_holder_rx.recv().unwrap();
26161 })
26162 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26163 holder_started_rx
26164 .recv_timeout(Duration::from_secs(2))
26165 .unwrap();
26166
26167 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26168 let shutdown_controller = Arc::clone(&controller);
26169 let shutdown = std::thread::spawn(move || {
26170 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26171 });
26172 wait_until("the runtime entered Stopping", || {
26173 matches!(
26174 controller
26175 .state
26176 .lock()
26177 .unwrap_or_else(std::sync::PoisonError::into_inner)
26178 .lifecycle,
26179 RuntimeLifecycle::Stopping(_)
26180 )
26181 });
26182
26183 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26184 let (release_drop_tx, release_drop_rx) = mpsc::channel();
26185 let (reentrant_tx, reentrant_rx) = mpsc::channel();
26186 let rejected_controller = Arc::clone(&controller);
26187 let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
26188 let rejected = std::thread::spawn(move || {
26189 let bomb = ShutdownAfterRelease {
26190 controller: Arc::clone(&rejected_controller),
26191 entered: drop_entered_tx,
26192 release: release_drop_rx,
26193 result: reentrant_tx,
26194 };
26195 let handle = rejected_controller.spawn(async move {
26196 let _bomb = bomb;
26197 std::future::pending::<()>().await;
26198 });
26199 rejected_done_tx
26200 .send(futures::executor::block_on(handle))
26201 .unwrap();
26202 });
26203 drop_entered_rx
26204 .recv_timeout(Duration::from_secs(2))
26205 .unwrap();
26206
26207 release_holder_tx.send(()).unwrap();
26208 assert!(
26209 shutdown_rx
26210 .recv_timeout(Duration::from_millis(200))
26211 .is_err(),
26212 "shutdown crossed into Stopped before rejected destruction finished"
26213 );
26214
26215 let (start_tx, start_rx) = mpsc::channel();
26216 let start_controller = Arc::clone(&controller);
26217 let starter = std::thread::spawn(move || {
26218 start_tx.send(start_controller.start()).unwrap();
26219 });
26220 assert!(
26221 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26222 "restart crossed into a new generation before rejected destruction finished"
26223 );
26224
26225 release_drop_tx.send(()).unwrap();
26226 let error = reentrant_rx
26227 .recv_timeout(Duration::from_secs(2))
26228 .expect("the rejected destructor must complete")
26229 .expect_err("the retiring generation must reject the destructor's shutdown");
26230 assert!(error.to_string().contains("generation being stopped"));
26231 assert!(
26232 rejected_done_rx
26233 .recv_timeout(Duration::from_secs(2))
26234 .unwrap()
26235 .is_err()
26236 );
26237 rejected.join().unwrap();
26238
26239 shutdown_rx
26240 .recv_timeout(Duration::from_secs(2))
26241 .unwrap()
26242 .unwrap();
26243 shutdown.join().unwrap();
26244 start_rx
26245 .recv_timeout(Duration::from_secs(2))
26246 .unwrap()
26247 .unwrap();
26248 starter.join().unwrap();
26249 futures::executor::block_on(holder).unwrap();
26250 controller.shutdown().unwrap();
26251 }
26252
26253 #[cfg(not(target_family = "wasm"))]
26254 #[test]
26255 fn shutdown_contains_hostile_caught_panic_payload() {
26256 use std::{panic::panic_any, sync::mpsc};
26257
26258 struct PanicAgain;
26259 impl Drop for PanicAgain {
26260 fn drop(&mut self) {
26261 panic!("caught panic payload destructor escaped");
26262 }
26263 }
26264
26265 struct PanicPayloadOnDrop(mpsc::Sender<()>);
26266 impl Drop for PanicPayloadOnDrop {
26267 fn drop(&mut self) {
26268 let _ = self.0.send(());
26269 panic_any(PanicAgain);
26270 }
26271 }
26272
26273 let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
26274 let (running_tx, running_rx) = mpsc::channel();
26275 let (release_tx, release_rx) = mpsc::channel();
26276 let running = controller
26277 .try_spawn_blocking(move || {
26278 running_tx.send(()).unwrap();
26279 release_rx.recv().unwrap();
26280 })
26281 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26282 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26283
26284 let (drop_tx, drop_rx) = mpsc::channel();
26285 let payload = PanicPayloadOnDrop(drop_tx);
26286 let queued = controller
26287 .try_spawn_blocking(move || {
26288 let _payload = &payload;
26289 })
26290 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26291
26292 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26293 let shutdown_controller = Arc::clone(&controller);
26294 let shutdown = std::thread::spawn(move || {
26295 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26296 });
26297 drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
26298 release_tx.send(()).unwrap();
26299 shutdown_rx
26300 .recv_timeout(Duration::from_secs(2))
26301 .expect("hostile panic payload destruction must remain contained")
26302 .unwrap();
26303 shutdown
26304 .join()
26305 .expect("the caught payload must not panic during destruction");
26306 futures::executor::block_on(running).unwrap();
26307 assert!(futures::executor::block_on(queued).is_err());
26308 }
26309
26310 #[cfg(not(target_family = "wasm"))]
26311 #[test]
26312 fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
26313 use std::sync::mpsc;
26314
26315 struct DropSignal(mpsc::Sender<()>);
26316 impl Drop for DropSignal {
26317 fn drop(&mut self) {
26318 let _ = self.0.send(());
26319 }
26320 }
26321
26322 let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
26323 let (running_tx, running_rx) = mpsc::channel();
26324 let (release_tx, release_rx) = mpsc::channel();
26325 let running = controller
26326 .try_spawn_blocking(move || {
26327 running_tx.send(()).unwrap();
26328 release_rx.recv().unwrap();
26329 7usize
26330 })
26331 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26332 running_rx
26333 .recv_timeout(Duration::from_secs(5))
26334 .expect("the first blocking job must hold the only blocking slot");
26335
26336 let queued_ran = Arc::new(AtomicBool::new(false));
26337 let queued_ran_work = Arc::clone(&queued_ran);
26338 let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
26339 let queued_drop_signal = DropSignal(queued_dropped_tx);
26340 let queued = controller
26341 .try_spawn_blocking(move || {
26342 let _drop_signal = queued_drop_signal;
26343 queued_ran_work.store(true, Ordering::SeqCst);
26344 11usize
26345 })
26346 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26347
26348 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26349 let shutdown_controller = Arc::clone(&controller);
26350 let shutdown = std::thread::spawn(move || {
26351 shutdown_controller.shutdown().unwrap();
26352 shutdown_done_tx.send(()).unwrap();
26353 });
26354
26355 queued_dropped_rx
26356 .recv_timeout(Duration::from_secs(5))
26357 .expect("shutdown must drop queued blocking work");
26358 assert!(
26359 !queued_ran.load(Ordering::SeqCst),
26360 "queued blocking work must not execute"
26361 );
26362 assert!(
26363 shutdown_done_rx.try_recv().is_err(),
26364 "shutdown must wait while an accepted blocking closure is still running"
26365 );
26366
26367 release_tx.send(()).unwrap();
26368 shutdown_done_rx
26369 .recv_timeout(Duration::from_secs(5))
26370 .expect("shutdown must finish after running blocking work retires");
26371 shutdown.join().unwrap();
26372 assert_eq!(futures::executor::block_on(running).unwrap(), 7);
26373 assert!(
26374 futures::executor::block_on(queued).is_err(),
26375 "the queued blocking handle must resolve as cancelled"
26376 );
26377 }
26378
26379 #[cfg(not(target_family = "wasm"))]
26380 #[test]
26381 fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
26382 use std::sync::mpsc;
26383
26384 struct PanicOnDrop(mpsc::Sender<()>);
26385 impl Drop for PanicOnDrop {
26386 fn drop(&mut self) {
26387 let _ = self.0.send(());
26388 panic!("intentional queued blocking destructor panic");
26389 }
26390 }
26391
26392 let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
26393 let (running_tx, running_rx) = mpsc::channel();
26394 let (release_tx, release_rx) = mpsc::channel();
26395 let running = controller
26396 .try_spawn_blocking(move || {
26397 running_tx.send(()).unwrap();
26398 release_rx.recv().unwrap();
26399 })
26400 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26401 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26402
26403 let (drop_tx, drop_rx) = mpsc::channel();
26404 let panic_on_drop = PanicOnDrop(drop_tx);
26405 let queued = controller
26406 .try_spawn_blocking(move || {
26407 let _keep_captured_until_run = &panic_on_drop;
26408 })
26409 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26410
26411 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26412 let shutdown_controller = Arc::clone(&controller);
26413 let shutdown = std::thread::spawn(move || {
26414 shutdown_controller.shutdown().unwrap();
26415 shutdown_done_tx.send(()).unwrap();
26416 });
26417
26418 drop_rx
26419 .recv_timeout(Duration::from_secs(5))
26420 .expect("shutdown must attempt to drop the queued closure");
26421 release_tx.send(()).unwrap();
26422 shutdown_done_rx
26423 .recv_timeout(Duration::from_secs(5))
26424 .expect("a panicking queued destructor must not strand shutdown");
26425 shutdown
26426 .join()
26427 .expect("shutdown must contain the destructor panic");
26428 futures::executor::block_on(running).unwrap();
26429 assert!(futures::executor::block_on(queued).is_err());
26430
26431 controller
26432 .start()
26433 .expect("the stopped controller must restart");
26434 let restarted = controller
26435 .try_spawn_blocking(|| 17usize)
26436 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26437 assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
26438 controller.shutdown().unwrap();
26439 }
26440
26441 #[cfg(not(target_family = "wasm"))]
26442 #[test]
26443 fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
26444 use std::{panic::panic_any, sync::mpsc};
26445
26446 struct ReentrantPayload {
26447 controller: Arc<RuntimeController>,
26448 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26449 }
26450
26451 impl Drop for ReentrantPayload {
26452 fn drop(&mut self) {
26453 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26454 let shutdown = self.controller.shutdown();
26455 self.result.send((generation, shutdown)).unwrap();
26456 }
26457 }
26458
26459 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26460
26461 impl Drop for PanicPayloadOnDrop {
26462 fn drop(&mut self) {
26463 panic_any(self.0.take().expect("the result must panic only once"));
26464 }
26465 }
26466
26467 let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
26468 let generation = controller.backend().generation();
26469 let (started_tx, started_rx) = mpsc::channel();
26470 let (release_tx, release_rx) = mpsc::channel();
26471 let (result_tx, result_rx) = mpsc::channel();
26472 let payload_controller = Arc::clone(&controller);
26473 let handle = controller
26474 .try_spawn_blocking(move || {
26475 started_tx.send(()).unwrap();
26476 release_rx.recv().unwrap();
26477 PanicPayloadOnDrop(Some(ReentrantPayload {
26478 controller: payload_controller,
26479 result: result_tx,
26480 }))
26481 })
26482 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26483 started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26484
26485 drop(handle);
26488 release_tx.send(()).unwrap();
26489 let (observed_generation, reentrant_shutdown) = result_rx
26490 .recv_timeout(Duration::from_secs(5))
26491 .expect("the dropped result panic payload must not self-deadlock");
26492 assert_eq!(observed_generation, Some(generation));
26493 let error =
26494 reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
26495 assert!(error.to_string().contains("work running on that runtime"));
26496
26497 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26498 let shutdown_controller = Arc::clone(&controller);
26499 let shutdown = std::thread::spawn(move || {
26500 shutdown_controller.shutdown().unwrap();
26501 shutdown_done_tx.send(()).unwrap();
26502 });
26503 shutdown_done_rx
26504 .recv_timeout(Duration::from_secs(5))
26505 .expect("a dropped-result destructor panic must not strand shutdown");
26506 shutdown
26507 .join()
26508 .expect("shutdown must contain the destructor panic");
26509
26510 controller
26511 .start()
26512 .expect("the stopped controller must restart");
26513 let restarted = controller
26514 .try_spawn_blocking(|| 19usize)
26515 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26516 assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
26517 controller.shutdown().unwrap();
26518 }
26519
26520 #[cfg(not(target_family = "wasm"))]
26521 #[test]
26522 fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
26523 use std::sync::mpsc;
26524
26525 struct PanicOnDrop(mpsc::Sender<()>);
26526
26527 impl Drop for PanicOnDrop {
26528 fn drop(&mut self) {
26529 self.0.send(()).unwrap();
26530 panic!("intentional buffered blocking result destructor panic");
26531 }
26532 }
26533
26534 let options = RuntimeOptions {
26535 flavor: RuntimeFlavor::MultiThread,
26536 worker_threads: 2,
26537 max_blocking_tasks: 1,
26538 thread_name_prefix: "buffered-blocking-result-drop".to_string(),
26539 park_deadline: None,
26540 drain_linger: DEFAULT_DRAIN_LINGER,
26541 };
26542 let executor =
26543 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26544 let (started_tx, started_rx) = mpsc::channel();
26545 let (dropped_tx, dropped_rx) = mpsc::channel();
26546 let handle = executor.schedule_blocking(move || {
26547 started_tx.send(()).unwrap();
26548 PanicOnDrop(dropped_tx)
26549 });
26550 started_rx
26551 .recv_timeout(Duration::from_secs(2))
26552 .expect("the blocking closure must run");
26553 wait_until("the completed blocking result to be buffered", || {
26554 executor.active_blocking.load(Ordering::Acquire) == 0
26555 });
26556 assert!(
26557 dropped_rx.try_recv().is_err(),
26558 "the buffered result must remain owned by the live receiver"
26559 );
26560
26561 let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26562 assert!(
26563 dropped.is_ok(),
26564 "dropping the buffered result must contain its destructor panic"
26565 );
26566 dropped_rx
26567 .recv_timeout(Duration::from_secs(2))
26568 .expect("dropping the receiver must reclaim the buffered result");
26569 }
26570
26571 #[cfg(not(target_family = "wasm"))]
26572 #[test]
26573 fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
26574 use std::sync::mpsc;
26575
26576 struct PanicOnDrop(mpsc::Sender<()>);
26577 impl Drop for PanicOnDrop {
26578 fn drop(&mut self) {
26579 let _ = self.0.send(());
26580 panic!("intentional late blocking destructor panic");
26581 }
26582 }
26583
26584 let executor = multi_thread_executor(2, None, "closed-queue-drop");
26585 executor.begin_shutdown();
26586
26587 let (drop_tx, drop_rx) = mpsc::channel();
26588 let panic_on_drop = PanicOnDrop(drop_tx);
26589 let handle = executor.schedule_blocking(move || {
26590 let _keep_captured_until_run = &panic_on_drop;
26591 });
26592
26593 drop_rx
26594 .recv_timeout(Duration::from_secs(5))
26595 .expect("the closed queue must retire the rejected closure");
26596 assert!(
26597 futures::executor::block_on(handle).is_err(),
26598 "the rejected blocking handle must resolve as cancelled"
26599 );
26600 }
26601
26602 #[cfg(not(target_family = "wasm"))]
26603 #[test]
26604 fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
26605 struct PanicWake;
26606 impl std::task::Wake for PanicWake {
26607 fn wake(self: Arc<Self>) {
26608 panic!("intentional timer waker panic");
26609 }
26610 }
26611
26612 let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
26613 let backend = controller.backend();
26614 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26615 panic!("configured MultiThread backend must create a Rayon executor");
26616 };
26617 let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
26618 let waker = Waker::from(Arc::new(PanicWake));
26619 let mut cx = Context::from_waker(&waker);
26620 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
26621 drop(backend);
26622
26623 controller
26624 .shutdown()
26625 .expect("shutdown must contain the timer waker panic");
26626 controller
26627 .start()
26628 .expect("the stopped controller must restart");
26629 let restarted = controller
26630 .try_spawn_blocking(|| 23usize)
26631 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26632 assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
26633 controller.shutdown().unwrap();
26634 }
26635
26636 #[cfg(not(target_family = "wasm"))]
26637 #[test]
26638 fn multi_thread_restart_waits_for_old_generation_worker_exit() {
26639 use std::sync::mpsc;
26640
26641 let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
26642 let backend = controller.backend();
26643 let old_generation = backend.generation();
26644 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26645 panic!("configured MultiThread backend must create a Rayon executor");
26646 };
26647 let old_workers = Arc::clone(&executor.worker_lifecycle);
26648 drop(backend);
26649
26650 let (running_tx, running_rx) = mpsc::channel();
26651 let (release_tx, release_rx) = mpsc::channel();
26652 let running = controller
26653 .try_spawn_blocking(move || {
26654 running_tx.send(()).unwrap();
26655 release_rx.recv().unwrap();
26656 })
26657 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26658 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26659
26660 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26661 let shutdown_controller = Arc::clone(&controller);
26662 let shutdown = std::thread::spawn(move || {
26663 shutdown_controller.shutdown().unwrap();
26664 shutdown_done_tx.send(()).unwrap();
26665 });
26666 wait_until("controller entered Stopping", || {
26667 let state = controller
26668 .state
26669 .lock()
26670 .unwrap_or_else(std::sync::PoisonError::into_inner);
26671 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
26672 });
26673
26674 let (start_done_tx, start_done_rx) = mpsc::channel();
26675 let start_controller = Arc::clone(&controller);
26676 let starter = std::thread::spawn(move || {
26677 start_controller.start().unwrap();
26678 start_done_tx.send(()).unwrap();
26679 });
26680 assert!(
26681 start_done_rx.try_recv().is_err(),
26682 "start must wait while shutdown is quiescing"
26683 );
26684 assert!(
26685 old_workers.remaining() > 0,
26686 "the held old generation must still own workers"
26687 );
26688
26689 release_tx.send(()).unwrap();
26690 futures::executor::block_on(running).unwrap();
26691 shutdown_done_rx
26692 .recv_timeout(Duration::from_secs(5))
26693 .unwrap();
26694 start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26695 shutdown.join().unwrap();
26696 starter.join().unwrap();
26697 assert_eq!(
26698 old_workers.remaining(),
26699 0,
26700 "restart may begin only after every old worker exits"
26701 );
26702
26703 let backend = controller.backend();
26704 assert_ne!(backend.generation(), old_generation);
26705 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26706 panic!("restarted backend must remain MultiThread");
26707 };
26708 assert_eq!(
26709 executor.pool.current_num_threads(),
26710 controller.options().worker_threads
26711 );
26712 drop(backend);
26713 controller.shutdown().unwrap();
26714 }
26715
26716 #[cfg(not(target_family = "wasm"))]
26717 #[test]
26718 fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
26719 let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
26720 let task_controller = Arc::clone(&controller);
26721 let handle = controller
26722 .try_spawn(async move {
26723 task_controller
26724 .shutdown()
26725 .expect_err("runtime work must not wait for its own generation")
26726 .to_string()
26727 })
26728 .unwrap_or_else(|_| panic!("running runtime must accept the task"));
26729 let message = futures::executor::block_on(handle).unwrap();
26730 assert!(message.contains("cannot shut down"));
26731 controller.shutdown().unwrap();
26732 }
26733
26734 #[cfg(not(target_family = "wasm"))]
26741 #[test]
26742 fn multi_thread_blocking_active_never_exceeds_max_blocking() {
26743 use std::sync::{Barrier, mpsc};
26765 use std::time::Duration;
26766
26767 let (done_tx, done_rx) = mpsc::channel();
26768 let peak_out = Arc::new(Mutex::new(None));
26769 let peak_slot = Arc::clone(&peak_out);
26770 let runner = std::thread::spawn(move || {
26771 let metrics = Arc::new(RuntimeMetrics::default());
26772 let options = RuntimeOptions {
26773 flavor: RuntimeFlavor::MultiThread,
26774 worker_threads: 4,
26775 max_blocking_tasks: 2,
26776 thread_name_prefix: "rd9-blk-cap".to_string(),
26777 park_deadline: None,
26778 drain_linger: DEFAULT_DRAIN_LINGER,
26779 };
26780 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26781
26782 let holder_count = options.max_blocking_tasks; let extra_count = 4usize; let (held_tx, held_rx) = mpsc::channel();
26787 let release = Arc::new(Barrier::new(holder_count + 1));
26788 let mut holder_handles = Vec::new();
26789 for _ in 0..holder_count {
26790 let held_tx = held_tx.clone();
26791 let release = Arc::clone(&release);
26792 holder_handles.push(executor.schedule_blocking(move || {
26793 held_tx.send(()).unwrap();
26794 release.wait();
26795 0usize
26796 }));
26797 }
26798 for _ in 0..holder_count {
26800 held_rx.recv().unwrap();
26801 }
26802
26803 let (extra_started_tx, extra_started_rx) = mpsc::channel();
26805 let mut extra_handles = Vec::new();
26806 for i in 0..extra_count {
26807 let extra_started_tx = extra_started_tx.clone();
26808 extra_handles.push(executor.schedule_blocking(move || {
26809 extra_started_tx.send(i).unwrap();
26810 i * 2
26811 }));
26812 }
26813
26814 assert!(
26817 matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
26818 "a beyond-cap blocking job ran before the cap was released (cap check broken)"
26819 );
26820
26821 release.wait();
26823
26824 for handle in holder_handles {
26825 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
26826 }
26827 for (i, handle) in extra_handles.into_iter().enumerate() {
26830 assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
26831 }
26832
26833 assert_eq!(
26836 metrics.blocking_tasks_started.load(Ordering::Relaxed),
26837 (holder_count + extra_count) as u64
26838 );
26839
26840 *peak_slot
26841 .lock()
26842 .unwrap_or_else(std::sync::PoisonError::into_inner) =
26843 Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
26844 let _ = done_tx.send(());
26845 });
26846
26847 match done_rx.recv_timeout(Duration::from_secs(10)) {
26848 Ok(()) => runner.join().unwrap(),
26849 Err(error) => panic!("blocking-cap test did not complete ({error})"),
26850 }
26851 let peak = peak_out
26852 .lock()
26853 .unwrap_or_else(std::sync::PoisonError::into_inner)
26854 .expect("peak captured");
26855 assert!(
26856 peak >= 1,
26857 "at least one blocking job must have run (peak {peak})"
26858 );
26859 assert!(
26860 peak <= 2,
26861 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
26862 );
26863 }
26864
26865 #[cfg(not(target_family = "wasm"))]
26866 #[test]
26867 fn multi_thread_drain_budget_completes_large_runnable_batch() {
26868 use std::sync::mpsc;
26873 use std::time::{Duration, Instant};
26874
26875 let (done_tx, done_rx) = mpsc::channel();
26876 let runner = std::thread::spawn(move || {
26877 let metrics = Arc::new(RuntimeMetrics::default());
26878 let options = RuntimeOptions {
26879 flavor: RuntimeFlavor::MultiThread,
26880 worker_threads: 2,
26881 max_blocking_tasks: 2,
26882 thread_name_prefix: "rd9-budget".to_string(),
26883 park_deadline: None,
26884 drain_linger: DEFAULT_DRAIN_LINGER,
26885 };
26886 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26887
26888 let task_count = 200usize; let completed = Arc::new(AtomicU64::new(0));
26890 let mut tasks = Vec::new();
26891 for _ in 0..task_count {
26892 let completed = Arc::clone(&completed);
26893 let scheduler = Arc::clone(&executor);
26894 let (runnable, task) = async_task::spawn(
26895 async move {
26896 completed.fetch_add(1, Ordering::Relaxed);
26897 },
26898 move |r| scheduler.schedule(r),
26899 );
26900 executor.schedule(runnable);
26901 tasks.push(task);
26902 }
26903 for task in tasks {
26904 futures::executor::block_on(task);
26905 }
26906
26907 assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
26910 assert_eq!(
26911 metrics.runnable_polls.load(Ordering::Relaxed),
26912 task_count as u64
26913 );
26914
26915 let deadline = Instant::now() + Duration::from_secs(5);
26919 loop {
26920 let active = metrics.active_runnables.load(Ordering::Relaxed);
26921 let queued = metrics.queued_runnables.load(Ordering::Relaxed);
26922 if active == 0 && queued == 0 {
26923 break;
26924 }
26925 assert!(
26926 Instant::now() < deadline,
26927 "runnable counters did not settle to 0: active={active} queued={queued}"
26928 );
26929 std::thread::yield_now();
26930 }
26931 let _ = done_tx.send(());
26932 });
26933
26934 match done_rx.recv_timeout(Duration::from_secs(15)) {
26935 Ok(()) => runner.join().unwrap(),
26936 Err(error) => panic!("drain-budget test did not complete ({error})"),
26937 }
26938 }
26939
26940 #[cfg(not(target_family = "wasm"))]
26941 #[test]
26942 fn multi_thread_spawned_panic_surfaces_as_join_error() {
26943 use std::sync::mpsc;
26950 use std::time::Duration;
26951
26952 let (done_tx, done_rx) = mpsc::channel();
26953 let runner = std::thread::spawn(move || {
26954 let metrics = Arc::new(RuntimeMetrics::default());
26955 let options = RuntimeOptions {
26956 flavor: RuntimeFlavor::MultiThread,
26957 worker_threads: 2,
26958 max_blocking_tasks: 2,
26959 thread_name_prefix: "rd9-panic".to_string(),
26960 park_deadline: None,
26961 drain_linger: DEFAULT_DRAIN_LINGER,
26962 };
26963 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
26964
26965 let scheduler = Arc::clone(&executor);
26966 let wrapped = async {
26967 AssertUnwindSafe(async { panic!("x") })
26968 .catch_unwind()
26969 .await
26970 .map(|()| ContainedTaskOutput::new((), u64::MAX))
26971 .map_err(panic_payload_to_join_error)
26972 };
26973 let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
26974 executor.schedule(runnable);
26975
26976 let result: Result<(), JoinError> =
26977 futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
26978 task: task.fallible(),
26979 awaiter: None,
26980 dependency: Arc::new(TaskDependency::default()),
26981 }));
26982 let _ = done_tx.send(result);
26983 });
26984
26985 let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
26986 Ok(result) => {
26987 runner.join().unwrap();
26988 result
26989 }
26990 Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
26991 };
26992 let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
26993 assert_eq!(error.to_string(), "x");
26994 }
26995
26996 #[test]
26997 fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
26998 use std::sync::mpsc;
26999
27000 let metrics = Arc::new(RuntimeMetrics::default());
27001 metrics.tasks_spawned.store(1, Ordering::Relaxed);
27002 metrics.tasks_completed.store(2, Ordering::Relaxed);
27003 metrics.tasks_panicked.store(3, Ordering::Relaxed);
27004 metrics.runnable_schedules.store(4, Ordering::Relaxed);
27005 metrics.runnable_polls.store(5, Ordering::Relaxed);
27006 metrics
27007 .blocking_tasks_scheduled
27008 .store(14, Ordering::Relaxed);
27009 metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
27010 metrics
27011 .blocking_tasks_completed
27012 .store(11, Ordering::Relaxed);
27013 metrics.runnable_scheduled();
27014 let (guards_live_tx, guards_live_rx) = mpsc::channel();
27015 let (release_tx, release_rx) = mpsc::channel();
27016 let guard_metrics = Arc::clone(&metrics);
27017 let guard_thread = std::thread::spawn(move || {
27018 let runnable_guard = guard_metrics.runnable_started();
27019 let blocking_guard = guard_metrics.blocking_started(true);
27020 guards_live_tx.send(()).unwrap();
27021 release_rx.recv().unwrap();
27022 drop(runnable_guard);
27023 drop(blocking_guard);
27024 });
27025 guards_live_rx.recv().unwrap();
27026 let fingerprint_before_reset = metrics.progress_fingerprint();
27027
27028 metrics.reset();
27029
27030 assert!(
27031 metrics.progress_fingerprint() != fingerprint_before_reset,
27032 "a reset must advance the deadlock detector generation even if counters later repeat"
27033 );
27034 assert_eq!(
27035 metrics.tasks_spawned.load(Ordering::Relaxed),
27036 0,
27037 "tasks_spawned"
27038 );
27039 assert_eq!(
27040 metrics.tasks_completed.load(Ordering::Relaxed),
27041 0,
27042 "tasks_completed"
27043 );
27044 assert_eq!(
27045 metrics.tasks_panicked.load(Ordering::Relaxed),
27046 0,
27047 "tasks_panicked"
27048 );
27049 assert_eq!(
27050 metrics.runnable_schedules.load(Ordering::Relaxed),
27051 0,
27052 "runnable_schedules"
27053 );
27054 assert_eq!(
27055 metrics.runnable_polls.load(Ordering::Relaxed),
27056 0,
27057 "runnable_polls"
27058 );
27059 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
27060 assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
27061 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
27062 assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
27063 assert_eq!(
27064 metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
27065 0,
27066 "blocking_tasks_scheduled"
27067 );
27068 assert_eq!(
27069 metrics.blocking_tasks_started.load(Ordering::Relaxed),
27070 0,
27071 "blocking_tasks_started"
27072 );
27073 assert_eq!(
27074 metrics.blocking_tasks_completed.load(Ordering::Relaxed),
27075 0,
27076 "blocking_tasks_completed"
27077 );
27078 assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
27079 assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
27080
27081 release_tx.send(()).unwrap();
27082 guard_thread.join().unwrap();
27083 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
27084 assert_eq!(
27085 metrics.active_blocking_tasks.load(Ordering::Relaxed),
27086 0,
27087 "a live blocking guard completed after reset without unsigned underflow"
27088 );
27089 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
27090 }
27091
27092 #[test]
27093 fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
27094 use std::sync::mpsc;
27095
27096 fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
27097 RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
27098 *slot.borrow_mut() = Some(Box::new(move || {
27099 entered.send(()).unwrap();
27100 release.recv().unwrap();
27101 }));
27102 });
27103 }
27104
27105 let options = RuntimeOptions {
27106 flavor: RuntimeFlavor::CurrentThread,
27107 worker_threads: 1,
27108 max_blocking_tasks: 1,
27109 thread_name_prefix: "metrics-snapshot".to_string(),
27110 park_deadline: None,
27111 drain_linger: DEFAULT_DRAIN_LINGER,
27112 };
27113
27114 let queued_metrics = Arc::new(RuntimeMetrics::default());
27115 let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
27116 let (queued_release_tx, queued_release_rx) = mpsc::channel();
27117 let queued_writer_metrics = Arc::clone(&queued_metrics);
27118 let queued_writer = std::thread::spawn(move || {
27119 pause_next_high_water_update(queued_entered_tx, queued_release_rx);
27120 queued_writer_metrics.runnable_scheduled();
27121 });
27122 queued_entered_rx.recv().unwrap();
27123 assert_eq!(
27124 queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
27125 0
27126 );
27127 let snapshot = queued_metrics.snapshot(&options);
27128 assert_eq!(snapshot.queued_runnables, 1);
27129 assert_eq!(snapshot.max_queued_runnables, 1);
27130 queued_release_tx.send(()).unwrap();
27131 queued_writer.join().unwrap();
27132 queued_metrics.runnable_cancelled();
27133
27134 let active_metrics = Arc::new(RuntimeMetrics::default());
27135 active_metrics.queued_runnables.store(1, Ordering::Relaxed);
27136 let (active_entered_tx, active_entered_rx) = mpsc::channel();
27137 let (active_release_tx, active_release_rx) = mpsc::channel();
27138 let active_writer_metrics = Arc::clone(&active_metrics);
27139 let active_writer = std::thread::spawn(move || {
27140 pause_next_high_water_update(active_entered_tx, active_release_rx);
27141 let _active = active_writer_metrics.runnable_started();
27142 });
27143 active_entered_rx.recv().unwrap();
27144 assert_eq!(
27145 active_metrics.max_active_runnables.load(Ordering::Relaxed),
27146 0
27147 );
27148 let snapshot = active_metrics.snapshot(&options);
27149 assert_eq!(snapshot.active_runnables, 1);
27150 assert_eq!(snapshot.max_active_runnables, 1);
27151 active_release_tx.send(()).unwrap();
27152 active_writer.join().unwrap();
27153
27154 let blocking_metrics = Arc::new(RuntimeMetrics::default());
27155 let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
27156 let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
27157 let blocking_writer_metrics = Arc::clone(&blocking_metrics);
27158 let blocking_writer = std::thread::spawn(move || {
27159 pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
27160 let _active = blocking_writer_metrics.blocking_started(true);
27161 });
27162 blocking_entered_rx.recv().unwrap();
27163 assert_eq!(
27164 blocking_metrics
27165 .max_active_blocking_tasks
27166 .load(Ordering::Relaxed),
27167 0
27168 );
27169 let snapshot = blocking_metrics.snapshot(&options);
27170 assert_eq!(snapshot.active_blocking_tasks, 1);
27171 assert_eq!(snapshot.max_active_blocking_tasks, 1);
27172 blocking_release_tx.send(()).unwrap();
27173 blocking_writer.join().unwrap();
27174 }
27175
27176 #[test]
27177 fn runtime_metrics_reset_fails_before_generation_wraparound() {
27178 let metrics = RuntimeMetrics::default();
27179 metrics
27180 .reset_generation
27181 .store(u64::MAX - 1, Ordering::SeqCst);
27182 metrics.tasks_spawned.store(7, Ordering::Relaxed);
27183
27184 let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
27185 assert!(exhausted.is_err());
27186 assert!(!metrics.reset_lock.is_poisoned());
27187 assert_eq!(
27188 metrics.reset_generation.load(Ordering::SeqCst),
27189 u64::MAX - 1
27190 );
27191 assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
27192 }
27193
27194 struct NeverReady;
27206 impl Future for NeverReady {
27207 type Output = ();
27208 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27209 Poll::Pending
27210 }
27211 }
27212
27213 #[cfg(not(target_family = "wasm"))]
27214 struct ReadyAfterOwnerLanePublication {
27215 dependency: BlockingDependency,
27216 ready: Arc<AtomicBool>,
27217 }
27218
27219 #[cfg(not(target_family = "wasm"))]
27220 impl Future for ReadyAfterOwnerLanePublication {
27221 type Output = ();
27222
27223 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27224 if self.ready.load(Ordering::SeqCst) {
27225 Poll::Ready(())
27226 } else {
27227 let mut published = PublishedBlockingDependencies::default();
27230 record_blocking_dependency(&mut published, self.dependency);
27231 Poll::Pending
27232 }
27233 }
27234 }
27235
27236 #[test]
27237 fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
27238 use std::sync::mpsc;
27245
27246 let (done_tx, done_rx) = mpsc::channel();
27247 let runner = std::thread::spawn(move || {
27248 let metrics = Arc::new(RuntimeMetrics::default());
27249 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27252
27253 let payload = catch_unwind(AssertUnwindSafe(|| {
27254 let mut future = std::pin::pin!(NeverReady);
27255 executor.block_on(future.as_mut());
27256 }))
27257 .expect_err("a threadless park with an empty queue and no pending wake must panic");
27258 let diagnostic = payload
27259 .downcast_ref::<BlockOnDeadlock>()
27260 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
27261 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
27262 assert_eq!(
27263 diagnostic.park_deadline, None,
27264 "the certain case carries no deadline"
27265 );
27266 let message = diagnostic.to_string();
27267 assert!(
27268 message.contains("block_on") && message.contains("JS"),
27269 "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
27270 );
27271 let _ = done_tx.send(());
27272 });
27273
27274 match done_rx.recv_timeout(Duration::from_secs(10)) {
27275 Ok(()) => runner.join().unwrap(),
27276 Err(error) => panic!(
27277 "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
27278 ),
27279 }
27280 }
27281
27282 #[test]
27283 fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
27284 use std::sync::mpsc;
27290
27291 struct PendThenReady {
27292 polls: usize,
27293 }
27294 impl Future for PendThenReady {
27295 type Output = ();
27296 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
27297 self.polls += 1;
27298 if self.polls >= 2 {
27299 Poll::Ready(())
27300 } else {
27301 cx.waker().wake_by_ref();
27302 Poll::Pending
27303 }
27304 }
27305 }
27306
27307 let (done_tx, done_rx) = mpsc::channel();
27308 let runner = std::thread::spawn(move || {
27309 let metrics = Arc::new(RuntimeMetrics::default());
27310 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27311 let mut future = std::pin::pin!(PendThenReady { polls: 0 });
27312 executor.block_on(future.as_mut());
27313 assert_eq!(
27314 future.polls, 2,
27315 "the self-woken future must have been re-polled to completion"
27316 );
27317 let _ = done_tx.send(());
27318 });
27319
27320 match done_rx.recv_timeout(Duration::from_secs(10)) {
27321 Ok(()) => runner.join().unwrap(),
27322 Err(error) => panic!(
27323 "a self-waking Pending future must complete without panicking on a threadless build ({error})"
27324 ),
27325 }
27326 }
27327
27328 #[cfg(not(target_family = "wasm"))]
27329 #[test]
27330 fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
27331 use std::sync::mpsc;
27336
27337 let (done_tx, done_rx) = mpsc::channel();
27338 let runner = std::thread::spawn(move || {
27339 let metrics = Arc::new(RuntimeMetrics::default());
27340 let executor = Arc::new(CurrentThreadExecutor::with_detection(
27341 metrics,
27342 false,
27343 Some(Duration::from_millis(50)),
27344 ));
27345
27346 let payload = catch_unwind(AssertUnwindSafe(|| {
27347 let mut future = std::pin::pin!(NeverReady);
27348 executor.block_on(future.as_mut());
27349 }))
27350 .expect_err("a dead native CT park must fire the armed deadline");
27351 let diagnostic = payload
27352 .downcast_ref::<BlockOnDeadlock>()
27353 .expect("typed payload");
27354 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
27355 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27356 drop(executor);
27359 let _ = done_tx.send(());
27360 });
27361
27362 match done_rx.recv_timeout(Duration::from_secs(10)) {
27363 Ok(()) => join_within(
27364 "the CT deadline-fire runner exits after teardown",
27365 runner,
27366 Duration::from_secs(30),
27367 ),
27368 Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
27369 }
27370 }
27371
27372 #[cfg(not(target_family = "wasm"))]
27373 #[test]
27374 fn current_thread_native_park_deadline_resets_on_runtime_progress() {
27375 use std::sync::mpsc;
27392
27393 let deadline = Duration::from_millis(1000);
27394 let metrics = Arc::new(RuntimeMetrics::default());
27395 let executor = Arc::new(CurrentThreadExecutor::with_detection(
27396 Arc::clone(&metrics),
27397 false,
27398 Some(deadline),
27399 ));
27400
27401 let (value_tx, value_rx) = oneshot::channel::<usize>();
27402 let (done_tx, done_rx) = mpsc::channel();
27403 let runner = std::thread::spawn(move || {
27404 let mut output = None;
27405 {
27406 let mut future = std::pin::pin!(async {
27407 output = Some(value_rx.await.unwrap());
27408 });
27409 executor.block_on(future.as_mut());
27410 }
27411 assert_eq!(output, Some(9));
27412 drop(executor);
27414 let _ = done_tx.send(());
27415 });
27416
27417 let hold_until = Instant::now() + deadline * 5 / 2;
27423 while Instant::now() < hold_until {
27424 std::thread::sleep(Duration::from_millis(25));
27425 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27426 }
27427 value_tx.send(9).unwrap();
27428
27429 match done_rx.recv_timeout(Duration::from_secs(30)) {
27430 Ok(()) => join_within(
27431 "the CT deadline-reset runner exits after teardown",
27432 runner,
27433 Duration::from_secs(30),
27434 ),
27435 Err(error) => panic!(
27436 "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
27437 ),
27438 }
27439 }
27440
27441 #[cfg(not(target_family = "wasm"))]
27442 #[test]
27443 fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
27444 use std::sync::mpsc;
27449
27450 let (done_tx, done_rx) = mpsc::channel();
27451 let runner = std::thread::spawn(move || {
27452 let options = RuntimeOptions {
27453 flavor: RuntimeFlavor::MultiThread,
27454 worker_threads: 1,
27455 max_blocking_tasks: 1,
27456 thread_name_prefix: "deadline-fire".to_string(),
27457 park_deadline: Some(Duration::from_millis(50)),
27458 drain_linger: DEFAULT_DRAIN_LINGER,
27459 };
27460 let executor =
27461 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27462
27463 let payload = {
27464 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27465 catch_unwind(AssertUnwindSafe(|| {
27466 let mut future = std::pin::pin!(NeverReady);
27467 executor.block_on(future.as_mut());
27468 }))
27469 .expect_err("a dead cooperative park must fire the armed deadline")
27470 };
27471 let diagnostic = payload
27472 .downcast_ref::<BlockOnDeadlock>()
27473 .expect("typed payload");
27474 assert_eq!(
27475 diagnostic.kind,
27476 BlockOnDeadlockKind::MultiThreadCooperativeDeadline
27477 );
27478 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27479 let message = diagnostic.to_string();
27480 assert!(
27481 message.contains("block_on"),
27482 "diagnostic must name the hazard, got: {message}"
27483 );
27484 assert_eq!(
27485 executor.parked_drivers.count.load(Ordering::SeqCst),
27486 0,
27487 "the firing driver must deregister itself before panicking"
27488 );
27489 drop(executor);
27491 let _ = done_tx.send(());
27492 });
27493
27494 match done_rx.recv_timeout(Duration::from_secs(10)) {
27495 Ok(()) => join_within(
27496 "the MT deadline-fire runner exits after executor teardown",
27497 runner,
27498 Duration::from_secs(30),
27499 ),
27500 Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
27501 }
27502 }
27503
27504 #[cfg(not(target_family = "wasm"))]
27505 #[test]
27506 fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
27507 use std::sync::mpsc;
27523
27524 let (done_tx, done_rx) = mpsc::channel();
27525 let runner = std::thread::spawn(move || {
27526 let deadline = Duration::from_millis(1000);
27527 let metrics = Arc::new(RuntimeMetrics::default());
27528 let options = RuntimeOptions {
27529 flavor: RuntimeFlavor::MultiThread,
27530 worker_threads: 1,
27531 max_blocking_tasks: 1,
27532 thread_name_prefix: "deadline-reset".to_string(),
27533 park_deadline: Some(deadline),
27534 drain_linger: DEFAULT_DRAIN_LINGER,
27535 };
27536 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
27537
27538 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27541 let td_exec = Arc::clone(&executor);
27542 let completed = Arc::new(AtomicBool::new(false));
27543 let td_flag = Arc::clone(&completed);
27544 let td_body = async move {
27545 let mut inner = std::pin::pin!(async move {
27546 assert_eq!(gate_rx.await.unwrap(), 9);
27547 });
27548 td_exec.block_on(inner.as_mut());
27549 td_flag.store(true, Ordering::SeqCst);
27550 };
27551 let sched = Arc::clone(&executor);
27552 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
27553 executor.schedule(td_runnable);
27554 wait_until("the sole driver parked", || {
27555 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
27556 });
27557
27558 let hold_until = Instant::now() + deadline * 5 / 2;
27564 while Instant::now() < hold_until {
27565 std::thread::sleep(Duration::from_millis(25));
27566 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27567 }
27568 assert!(
27569 !completed.load(Ordering::SeqCst),
27570 "the driver must still be parked awaiting its late wake"
27571 );
27572 gate_tx.send(9).unwrap();
27573 futures::executor::block_on(td_task);
27574 assert!(
27575 completed.load(Ordering::SeqCst),
27576 "the late wake must complete the parked driver"
27577 );
27578 drop(executor);
27580 let _ = done_tx.send(());
27581 });
27582
27583 match done_rx.recv_timeout(Duration::from_secs(30)) {
27584 Ok(()) => join_within(
27585 "the MT deadline-reset runner exits after executor teardown",
27586 runner,
27587 Duration::from_secs(30),
27588 ),
27589 Err(error) => panic!(
27590 "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
27591 ),
27592 }
27593 }
27594
27595 #[cfg(not(target_family = "wasm"))]
27596 #[test]
27597 fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
27598 use std::sync::mpsc;
27603
27604 let (done_tx, done_rx) = mpsc::channel();
27605 let runner = std::thread::spawn(move || {
27606 let options = RuntimeOptions {
27607 flavor: RuntimeFlavor::MultiThread,
27608 worker_threads: 2,
27609 max_blocking_tasks: 2,
27610 thread_name_prefix: "foreign-exempt".to_string(),
27611 park_deadline: Some(Duration::from_millis(40)),
27612 drain_linger: DEFAULT_DRAIN_LINGER,
27613 };
27614 let executor =
27615 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27616
27617 let (value_tx, value_rx) = oneshot::channel::<usize>();
27618 let waker_thread = std::thread::spawn(move || {
27619 std::thread::sleep(Duration::from_millis(300));
27621 let _ = value_tx.send(7);
27622 });
27623
27624 let started = Instant::now();
27625 let mut output = None;
27626 {
27627 let mut future = std::pin::pin!(async {
27628 output = Some(value_rx.await.unwrap());
27629 });
27630 executor.block_on(future.as_mut());
27632 }
27633 assert_eq!(output, Some(7));
27634 assert!(
27635 started.elapsed() >= Duration::from_millis(250),
27636 "the park must genuinely have outlived the 40ms deadline many times over"
27637 );
27638 waker_thread.join().unwrap();
27639 drop(executor);
27644 let _ = done_tx.send(());
27645 });
27646
27647 match done_rx.recv_timeout(Duration::from_secs(10)) {
27648 Ok(()) => join_within(
27649 "the exempt foreign-park runner exits after executor teardown",
27650 runner,
27651 Duration::from_secs(30),
27652 ),
27653 Err(error) => {
27654 panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
27655 }
27656 }
27657 }
27658
27659 #[cfg(not(target_family = "wasm"))]
27660 #[test]
27661 fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
27662 use std::sync::mpsc;
27677
27678 let (done_tx, done_rx) = mpsc::channel();
27679 let runner = std::thread::spawn(move || {
27680 let options = RuntimeOptions {
27681 flavor: RuntimeFlavor::MultiThread,
27682 worker_threads: 1,
27683 max_blocking_tasks: 1,
27684 thread_name_prefix: "edge-wake".to_string(),
27685 park_deadline: Some(Duration::from_millis(40)),
27686 drain_linger: DEFAULT_DRAIN_LINGER,
27687 };
27688 let executor =
27689 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27690
27691 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27693 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
27695
27696 let hook_exec = Arc::clone(&executor);
27697 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
27698 *slot.borrow_mut() = Some(Box::new(move || {
27699 let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27700 let handle = hook_exec.schedule_blocking(move || {
27704 let _ = gate_tx.send(5usize);
27705 0usize
27706 });
27707 drop(handle); let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27709 hook_saw_tx.send((before, after)).unwrap();
27710 }));
27711 });
27712
27713 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27716 let mut output = None;
27717 {
27718 let mut future = std::pin::pin!(async {
27719 output = Some(gate_rx.await.unwrap());
27720 });
27721 executor.block_on(future.as_mut());
27722 }
27723 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
27728 assert_eq!(
27729 output,
27730 Some(5),
27731 "the edge-delivered wake's job must have run and completed the future"
27732 );
27733
27734 let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
27735 assert_eq!(
27736 before, 1,
27737 "the hook must observe the parker STILL registered (race window)"
27738 );
27739 assert_eq!(
27740 after, 0,
27741 "the racing wake_one must have popped the parker (wake DELIVERED)"
27742 );
27743 drop(executor);
27745 let _ = done_tx.send(());
27746 });
27747
27748 match done_rx.recv_timeout(Duration::from_secs(10)) {
27749 Ok(()) => join_within(
27750 "the expiry-edge-wake runner exits after executor teardown",
27751 runner,
27752 Duration::from_secs(30),
27753 ),
27754 Err(error) => panic!(
27755 "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
27756 ),
27757 }
27758 }
27759
27760 #[cfg(not(target_family = "wasm"))]
27761 #[test]
27762 fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
27763 use std::sync::mpsc;
27775
27776 let (done_tx, done_rx) = mpsc::channel();
27777 let runner = std::thread::spawn(move || {
27778 let options = RuntimeOptions {
27779 flavor: RuntimeFlavor::MultiThread,
27780 worker_threads: 1,
27781 max_blocking_tasks: 1,
27782 thread_name_prefix: "verdict-blocking".to_string(),
27783 park_deadline: Some(Duration::from_millis(40)),
27784 drain_linger: DEFAULT_DRAIN_LINGER,
27785 };
27786 let executor =
27787 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27788 executor
27789 .active_drainers
27790 .store(executor.max_drainers, Ordering::SeqCst);
27791
27792 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27794 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
27797
27798 let hook_exec = Arc::clone(&executor);
27799 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
27800 *slot.borrow_mut() = Some(Box::new(move || {
27801 let handle = hook_exec.schedule_blocking(move || {
27802 let _ = gate_tx.send(11usize);
27803 0usize
27804 });
27805 drop(handle); hook_saw_tx
27807 .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
27808 .unwrap();
27809 }));
27810 });
27811
27812 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27815 let mut output = None;
27816 {
27817 let mut future = std::pin::pin!(async {
27818 output = Some(gate_rx.await.unwrap());
27819 });
27820 executor.block_on(future.as_mut());
27821 }
27822 assert_eq!(
27823 output,
27824 Some(11),
27825 "the enqueued job must have been run by the surviving driver via run_one"
27826 );
27827
27828 let registered_at_hook = hook_saw_rx
27829 .recv()
27830 .expect("the verdict hook must have fired");
27831 assert_eq!(
27832 registered_at_hook, 0,
27833 "the hook fires post-deregister: no permit can be delivered, only the fingerprint's enqueue counter can catch this submission"
27834 );
27835 executor.active_drainers.store(0, Ordering::SeqCst);
27836 let _ = done_tx.send(());
27837 });
27838
27839 match done_rx.recv_timeout(Duration::from_secs(10)) {
27840 Ok(()) => runner.join().unwrap(),
27841 Err(error) => panic!(
27842 "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
27843 ),
27844 }
27845 }
27846
27847 #[cfg(not(target_family = "wasm"))]
27848 #[test]
27849 fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
27850 use std::sync::mpsc;
27856
27857 struct ReadyAfterPublication(Arc<AtomicBool>);
27858
27859 impl Future for ReadyAfterPublication {
27860 type Output = ();
27861
27862 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27863 if self.0.load(Ordering::SeqCst) {
27864 Poll::Ready(())
27865 } else {
27866 Poll::Pending
27867 }
27868 }
27869 }
27870
27871 let (done_tx, done_rx) = mpsc::channel();
27872 let runner = std::thread::spawn(move || {
27873 let options = RuntimeOptions {
27874 flavor: RuntimeFlavor::MultiThread,
27875 worker_threads: 1,
27876 max_blocking_tasks: 1,
27877 thread_name_prefix: "final-verdict-publication".to_string(),
27878 park_deadline: Some(Duration::from_millis(40)),
27879 drain_linger: DEFAULT_DRAIN_LINGER,
27880 };
27881 let executor =
27882 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27883
27884 let ready = Arc::new(AtomicBool::new(false));
27885 let baseline_fingerprint = executor.metrics.progress_fingerprint();
27886 let baseline_publications = executor.deadlock_state.publications();
27887 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
27888 let hook_exec = Arc::clone(&executor);
27889 let hook_ready = Arc::clone(&ready);
27890 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
27891 *slot.borrow_mut() = Some(Box::new(move || {
27892 let publication_executor = Arc::clone(&hook_exec);
27893 std::thread::spawn(move || {
27894 let mut admission = publication_executor
27895 .begin_deadlock_admission()
27896 .expect("the deadline detector must enable admission accounting");
27897 hook_ready.store(true, Ordering::SeqCst);
27898 admission.mark_published();
27899 })
27900 .join()
27901 .unwrap();
27902 hook_saw_tx
27903 .send((
27904 hook_exec.deadlock_state.publications(),
27905 !hook_exec.has_queued_runnable(),
27906 hook_exec
27907 .blocking_queue
27908 .lock()
27909 .unwrap_or_else(std::sync::PoisonError::into_inner)
27910 .is_empty(),
27911 hook_exec.next_timer_deadline().is_none(),
27912 hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
27913 hook_exec.parked_drivers.count.load(Ordering::SeqCst),
27914 ))
27915 .unwrap();
27916 }));
27917 });
27918
27919 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27920 let mut future = std::pin::pin!(ReadyAfterPublication(ready));
27921 assert_eq!(
27922 executor.block_on(future.as_mut()),
27923 BlockOnOutcome::Completed,
27924 "the publication epoch must veto the first verdict and permit the next poll"
27925 );
27926 let (
27927 publications,
27928 runnable_queue_empty,
27929 blocking_queue_empty,
27930 timer_heap_empty,
27931 fingerprint_unchanged,
27932 parked_drivers,
27933 ) = hook_saw_rx
27934 .recv()
27935 .expect("the final-verdict hook must have fired");
27936 assert_eq!(
27937 publications,
27938 baseline_publications
27939 .checked_add(1)
27940 .expect("test publication epoch must not overflow"),
27941 "the cross-thread admission must publish exactly one epoch"
27942 );
27943 assert!(
27944 runnable_queue_empty,
27945 "the publication test must not queue a runnable"
27946 );
27947 assert!(
27948 blocking_queue_empty,
27949 "the publication test must not queue blocking work"
27950 );
27951 assert!(
27952 timer_heap_empty,
27953 "the publication test must not register a timer"
27954 );
27955 assert!(
27956 fingerprint_unchanged,
27957 "ordinary metrics progress must not veto the publication-only verdict"
27958 );
27959 assert_eq!(
27960 parked_drivers, 0,
27961 "the publication must land after the cooperative parker was deregistered"
27962 );
27963 let _ = done_tx.send(());
27964 });
27965
27966 match done_rx.recv_timeout(Duration::from_secs(10)) {
27967 Ok(()) => runner.join().unwrap(),
27968 Err(error) => panic!(
27969 "a publication-only cross-thread admission was not observed by the final verdict ({error})"
27970 ),
27971 }
27972 }
27973
27974 #[cfg(not(target_family = "wasm"))]
27975 #[test]
27976 fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
27977 use std::sync::mpsc;
27985
27986 let executor = multi_thread_executor(
27987 1,
27988 Some(Duration::from_millis(40)),
27989 "deadline-selected-handoff",
27990 );
27991 let owner = executor.fresh_owner_token();
27992 executor.active_blocking_owners.register(owner);
27993 executor
27994 .active_blocking
27995 .store(executor.max_blocking, Ordering::SeqCst);
27996
27997 let ready = Arc::new(AtomicBool::new(false));
27998 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
27999 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
28000 let (selected_tx, selected_rx) = mpsc::channel();
28001 let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
28002 let (released_tx, released_rx) = mpsc::channel();
28003 let (renewed_tx, renewed_rx) = mpsc::channel();
28004 let release_executor = Arc::clone(&executor);
28005 let renewal_executor = Arc::clone(&executor);
28006 let release_ready = Arc::clone(&ready);
28007 let releaser = std::thread::spawn(move || {
28008 let reservation =
28009 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
28010 .expect("the synthetic exact-owner lane must start reserved");
28011 reservation_ready_tx.send(()).unwrap();
28012 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
28013 *slot.borrow_mut() = Some(Box::new(move || {
28014 selected_tx.send(()).unwrap();
28015 allow_unpark_rx.recv().unwrap();
28016 let renewed = renewal_executor
28017 .active_blocking_owners
28018 .reserve(owner)
28019 .expect("the released exact-owner lane must be reservable before unpark");
28020 release_ready.store(true, Ordering::SeqCst);
28021 renewed_tx.send(renewed).unwrap();
28022 }));
28023 });
28024 release_reservation_rx.recv().unwrap();
28025 released_tx
28026 .send(release_executor.release_owner_lane_and_wake(reservation))
28027 .unwrap();
28028 });
28029 reservation_ready_rx
28030 .recv_timeout(Duration::from_secs(2))
28031 .expect("the exact-owner reservation must be established before the park");
28032
28033 let baseline_fingerprint = executor.metrics.progress_fingerprint();
28034 let baseline_publications = executor.deadlock_state.publications();
28035 let expiry_executor = Arc::clone(&executor);
28036 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
28037 *slot.borrow_mut() = Some(Box::new(move || {
28038 release_reservation_tx.send(()).unwrap();
28039 selected_rx
28040 .recv_timeout(Duration::from_secs(2))
28041 .expect("reservation release must select the still-registered exact-owner driver");
28042 assert_eq!(
28043 expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
28044 0,
28045 "handoff selection must remove the driver before pausing ahead of unpark"
28046 );
28047 }));
28048 });
28049
28050 let verdict_executor = Arc::clone(&executor);
28051 let verdict_ready = Arc::clone(&ready);
28052 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28053 *slot.borrow_mut() = Some(Box::new(move || {
28054 assert!(
28055 verdict_executor.active_blocking_owners.is_available(owner),
28056 "reservation release must publish lane availability before handoff selection"
28057 );
28058 assert!(!verdict_executor.has_queued_runnable());
28059 assert!(
28060 verdict_executor
28061 .blocking_queue
28062 .lock()
28063 .unwrap_or_else(std::sync::PoisonError::into_inner)
28064 .is_empty()
28065 );
28066 assert!(verdict_executor.next_timer_deadline().is_none());
28067 assert!(
28068 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28069 "ordinary metrics progress must not save the selected-handoff verdict"
28070 );
28071 assert_eq!(
28072 verdict_executor.deadlock_state.publications(),
28073 baseline_publications
28074 );
28075 assert!(
28076 !verdict_ready.load(Ordering::SeqCst),
28077 "the selected handoff must still be paused before unpark at the gated verdict"
28078 );
28079 allow_unpark_tx.send(()).unwrap();
28080 }));
28081 });
28082 let exact_owner_executor = Arc::clone(&executor);
28083 DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
28084 *slot.borrow_mut() = Some(Box::new(move |parker| {
28085 assert!(
28086 parker.consume_permit(),
28087 "the selected handoff must unpark before exact-owner final predicates run"
28088 );
28089 assert!(
28090 parker.has_owner_handoff(),
28091 "consuming the ordinary permit must leave the exact-owner handoff pending"
28092 );
28093 assert!(
28094 !exact_owner_executor
28095 .active_blocking_owners
28096 .is_available(owner),
28097 "the renewed reservation must prevent lane availability from saving the verdict"
28098 );
28099 }));
28100 });
28101
28102 {
28103 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28104 let _owner = BlockingOwnerGuard::enter(Some(owner));
28105 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28106 dependency: BlockingDependency {
28107 job: BlockingJobId(u64::MAX - 80),
28108 owner: Some(owner)
28109 },
28110 ready: Arc::clone(&ready),
28111 });
28112 assert_eq!(
28113 executor.block_on(future.as_mut()),
28114 BlockOnOutcome::Completed
28115 );
28116 }
28117 assert!(
28118 released_rx
28119 .recv_timeout(Duration::from_secs(2))
28120 .expect("the selected handoff publication must finish"),
28121 "reservation release must have selected the cooperative driver"
28122 );
28123 let renewed = renewed_rx
28124 .recv_timeout(Duration::from_secs(2))
28125 .expect("the selected handoff must renew the owner reservation before unpark");
28126 releaser.join().unwrap();
28127
28128 executor.active_blocking_owners.release(owner, renewed);
28129 executor.active_blocking.store(0, Ordering::SeqCst);
28130 executor.active_blocking_owners.unregister(owner);
28131 executor.begin_shutdown();
28132 executor.wait_until_scheduler_idle();
28133 }
28134
28135 #[cfg(not(target_family = "wasm"))]
28136 #[test]
28137 fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
28138 use std::sync::mpsc;
28143
28144 let executor = multi_thread_executor(
28145 1,
28146 Some(Duration::from_millis(40)),
28147 "deadline-owner-no-parker",
28148 );
28149 let owner = executor.fresh_owner_token();
28150 executor.active_blocking_owners.register(owner);
28151 executor
28152 .active_blocking
28153 .store(executor.max_blocking, Ordering::SeqCst);
28154
28155 let ready = Arc::new(AtomicBool::new(false));
28156 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
28157 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
28158 let (released_tx, released_rx) = mpsc::channel();
28159 let release_executor = Arc::clone(&executor);
28160 let release_ready = Arc::clone(&ready);
28161 let releaser = std::thread::spawn(move || {
28162 let reservation =
28163 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
28164 .expect("the synthetic exact-owner lane must start reserved");
28165 reservation_ready_tx.send(()).unwrap();
28166 release_reservation_rx.recv().unwrap();
28167 let woke = release_executor.release_owner_lane_and_wake(reservation);
28168 release_ready.store(true, Ordering::SeqCst);
28169 released_tx.send(woke).unwrap();
28170 });
28171 reservation_ready_rx
28172 .recv_timeout(Duration::from_secs(2))
28173 .expect("the exact-owner reservation must be established before the park");
28174
28175 let baseline_fingerprint = executor.metrics.progress_fingerprint();
28176 let baseline_publications = executor.deadlock_state.publications();
28177 let verdict_executor = Arc::clone(&executor);
28178 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
28179 *slot.borrow_mut() = Some(Box::new(move || {
28180 assert_eq!(
28181 verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
28182 0,
28183 "the release must begin only after deadline deregistration"
28184 );
28185 release_reservation_tx.send(()).unwrap();
28186 assert!(
28187 !released_rx
28188 .recv_timeout(Duration::from_secs(2))
28189 .expect("the no-parker owner release must finish"),
28190 "no registered parker may receive the released owner lane"
28191 );
28192 assert!(verdict_executor.active_blocking_owners.is_available(owner));
28193 assert!(!verdict_executor.has_queued_runnable());
28194 assert!(
28195 verdict_executor
28196 .blocking_queue
28197 .lock()
28198 .unwrap_or_else(std::sync::PoisonError::into_inner)
28199 .is_empty()
28200 );
28201 assert!(verdict_executor.next_timer_deadline().is_none());
28202 assert!(
28203 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28204 "ordinary metrics progress must not save the no-parker release verdict"
28205 );
28206 assert_eq!(
28207 verdict_executor.deadlock_state.publications(),
28208 baseline_publications
28209 );
28210 }));
28211 });
28212
28213 {
28214 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28215 let _owner = BlockingOwnerGuard::enter(Some(owner));
28216 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28217 dependency: BlockingDependency {
28218 job: BlockingJobId(u64::MAX - 81),
28219 owner: Some(owner)
28220 },
28221 ready: Arc::clone(&ready),
28222 });
28223 assert_eq!(
28224 executor.block_on(future.as_mut()),
28225 BlockOnOutcome::Completed
28226 );
28227 }
28228 releaser.join().unwrap();
28229
28230 executor.active_blocking.store(0, Ordering::SeqCst);
28231 executor.active_blocking_owners.unregister(owner);
28232 executor.begin_shutdown();
28233 executor.wait_until_scheduler_idle();
28234 }
28235
28236 #[cfg(not(target_family = "wasm"))]
28237 #[test]
28238 fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
28239 use std::sync::mpsc;
28253
28254 let controller = Arc::new(RuntimeController::new());
28255 controller
28256 .configure(RuntimeOptions {
28257 flavor: RuntimeFlavor::MultiThread,
28258 worker_threads: 2,
28259 max_blocking_tasks: 1,
28260 thread_name_prefix: "shutdown-gated-verdict".to_string(),
28261 park_deadline: Some(Duration::from_millis(40)),
28262 drain_linger: DEFAULT_DRAIN_LINGER,
28263 })
28264 .expect("the shutdown race test configuration must be accepted");
28265
28266 let backend = controller.backend();
28267 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28268 panic!("the configured backend must be MultiThread");
28269 };
28270 let executor_id = executor.id;
28271 let weak_executor = Arc::downgrade(executor);
28272 drop(backend);
28273
28274 let (task_polled_tx, task_polled_rx) = mpsc::channel();
28275 let task = controller
28276 .try_spawn(async move {
28277 task_polled_tx.send(()).unwrap();
28278 std::future::pending::<()>().await;
28279 })
28280 .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
28281 task_polled_rx
28282 .recv_timeout(Duration::from_secs(2))
28283 .expect("the accepted task must publish an abortable pending waker");
28284
28285 let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
28286 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28287 let (driver_result_tx, driver_result_rx) = mpsc::channel();
28288 let driver_controller = Arc::clone(&controller);
28289 let driver = std::thread::spawn(move || {
28290 DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
28291 *slot.borrow_mut() = Some(Box::new(move || {
28292 gate_closed_tx.send(()).unwrap();
28293 release_verdict_rx.recv().unwrap();
28294 }));
28295 });
28296
28297 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28298 let mut pending = std::pin::pin!(NeverReady);
28299 let result = catch_unwind(AssertUnwindSafe(|| {
28300 driver_controller.block_on(pending.as_mut())
28301 }));
28302 let result = match result {
28303 Ok(outcome) => Ok(outcome),
28304 Err(payload) => {
28305 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28306 discard_panic_payload(payload);
28307 Err(was_deadlock)
28308 }
28309 };
28310 driver_result_tx.send(result).unwrap();
28311 });
28312
28313 gate_closed_rx
28314 .recv_timeout(Duration::from_secs(2))
28315 .expect("the cooperative driver must pause with the verdict gate closed");
28316
28317 let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
28318 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28319 let shutdown_controller = Arc::clone(&controller);
28320 let shutdown = std::thread::spawn(move || {
28321 DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
28322 *slot.borrow_mut() = Some(Box::new(move || {
28323 abort_blocked_tx.send(()).unwrap();
28324 }));
28325 });
28326 shutdown_result_tx
28327 .send(shutdown_controller.shutdown())
28328 .unwrap();
28329 });
28330
28331 abort_blocked_rx
28332 .recv_timeout(Duration::from_secs(2))
28333 .expect("the accepted task's abort wake must reach the closed verdict gate");
28334 let lifecycle_is_stopping = {
28335 let state = controller
28336 .state
28337 .lock()
28338 .unwrap_or_else(std::sync::PoisonError::into_inner);
28339 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
28340 };
28341 let stop_was_published = weak_executor
28342 .upgrade()
28343 .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
28344
28345 release_verdict_tx.send(()).unwrap();
28346
28347 let driver_result = driver_result_rx
28348 .recv_timeout(Duration::from_secs(2))
28349 .expect("the gated driver must resolve after observing shutdown");
28350 let shutdown_result = shutdown_result_rx
28351 .recv_timeout(Duration::from_secs(5))
28352 .expect("shutdown must finish after the verdict gate reopens");
28353 join_within(
28358 "the gated cooperative driver exits after teardown",
28359 driver,
28360 Duration::from_secs(30),
28361 );
28362 join_within(
28363 "the shutdown thread exits after teardown",
28364 shutdown,
28365 Duration::from_secs(30),
28366 );
28367
28368 assert!(
28369 lifecycle_is_stopping,
28370 "the abort wake must occur after Running -> Stopping"
28371 );
28372 assert!(
28373 stop_was_published,
28374 "generation stop must be visible before shutdown invokes accepted-task aborts"
28375 );
28376 assert_eq!(
28377 driver_result,
28378 Ok(BlockOnOutcome::Stopped),
28379 "shutdown must not be reported as a gated BlockOnDeadlock"
28380 );
28381 shutdown_result.unwrap();
28382 assert!(
28383 futures::executor::block_on(task).is_err(),
28384 "the accepted pending task must resolve as cancelled"
28385 );
28386 }
28387
28388 #[cfg(not(target_family = "wasm"))]
28389 #[test]
28390 fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
28391 use std::sync::mpsc;
28396
28397 let controller = Arc::new(RuntimeController::new());
28398 controller
28399 .configure(RuntimeOptions {
28400 flavor: RuntimeFlavor::MultiThread,
28401 worker_threads: 2,
28402 max_blocking_tasks: 1,
28403 thread_name_prefix: "verdict-before-shutdown".to_string(),
28404 park_deadline: Some(Duration::from_millis(40)),
28405 drain_linger: DEFAULT_DRAIN_LINGER,
28406 })
28407 .expect("the verdict-first test configuration must be accepted");
28408
28409 let backend = controller.backend();
28410 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28411 panic!("the configured backend must be MultiThread");
28412 };
28413 let executor_id = executor.id;
28414 drop(backend);
28415
28416 let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
28417 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28418 let (driver_result_tx, driver_result_rx) = mpsc::channel();
28419 let driver_controller = Arc::clone(&controller);
28420 let driver = std::thread::spawn(move || {
28421 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28422 *slot.borrow_mut() = Some(Box::new(move || {
28423 verdict_locked_tx.send(()).unwrap();
28424 release_verdict_rx.recv().unwrap();
28425 }));
28426 });
28427
28428 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28429 let mut pending = std::pin::pin!(NeverReady);
28430 let result = catch_unwind(AssertUnwindSafe(|| {
28431 driver_controller.block_on(pending.as_mut())
28432 }));
28433 let result = match result {
28434 Ok(outcome) => Ok(outcome),
28435 Err(payload) => {
28436 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28437 discard_panic_payload(payload);
28438 Err(was_deadlock)
28439 }
28440 };
28441 driver_result_tx.send(result).unwrap();
28442 });
28443
28444 verdict_locked_rx
28445 .recv_timeout(Duration::from_secs(2))
28446 .expect("the verdict must hold the synchronized stop-publication guard");
28447
28448 let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
28449 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28450 let shutdown_controller = Arc::clone(&controller);
28451 let shutdown = std::thread::spawn(move || {
28452 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
28453 *slot.borrow_mut() = Some(Box::new(move || {
28454 shutdown_waiting_tx.send(()).unwrap();
28455 }));
28456 });
28457 shutdown_result_tx
28458 .send(shutdown_controller.shutdown())
28459 .unwrap();
28460 });
28461
28462 shutdown_waiting_rx
28463 .recv_timeout(Duration::from_secs(2))
28464 .expect("shutdown must reach the lifecycle-to-publication lock edge");
28465 let early_shutdown = shutdown_result_rx
28466 .recv_timeout(Duration::from_millis(100))
28467 .ok();
28468 let shutdown_was_blocked = early_shutdown.is_none();
28469 release_verdict_tx.send(()).unwrap();
28470
28471 let driver_result = driver_result_rx
28472 .recv_timeout(Duration::from_secs(2))
28473 .expect("the verdict-first driver must finish after release");
28474 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28475 shutdown_result_rx
28476 .recv_timeout(Duration::from_secs(5))
28477 .expect("shutdown must finish after the verdict releases publication")
28478 });
28479 driver.join().unwrap();
28480 shutdown.join().unwrap();
28481
28482 assert!(
28483 shutdown_was_blocked,
28484 "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
28485 );
28486 assert_eq!(
28487 driver_result,
28488 Err(true),
28489 "a verdict that linearizes before shutdown must retain the typed deadlock"
28490 );
28491 shutdown_result.unwrap();
28492 }
28493
28494 #[cfg(not(target_family = "wasm"))]
28495 #[test]
28496 fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
28497 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
28498
28499 if std::env::var_os(CHILD_ENV).is_some() {
28500 let executor =
28501 multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
28502 executor
28505 .active_drainers
28506 .store(executor.max_drainers, Ordering::SeqCst);
28507
28508 let hook_returned = Arc::new(AtomicBool::new(false));
28509 let hook_flag = Arc::clone(&hook_returned);
28510 let hook_executor = Arc::clone(&executor);
28511 let previous_hook = std::panic::take_hook();
28512 std::panic::set_hook(Box::new(move |_| {
28513 let handle = hook_executor.schedule_blocking(|| ());
28514 drop(handle);
28515 hook_flag.store(true, Ordering::SeqCst);
28516 }));
28517
28518 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28519 let result = catch_unwind(AssertUnwindSafe(|| {
28520 let mut pending = std::pin::pin!(std::future::pending::<()>());
28521 executor.block_on(pending.as_mut());
28522 }));
28523 std::panic::set_hook(previous_hook);
28524
28525 let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
28526 assert!(
28527 payload.downcast_ref::<BlockOnDeadlock>().is_some(),
28528 "the deadline must retain its structured diagnostic"
28529 );
28530 assert!(
28531 hook_returned.load(Ordering::SeqCst),
28532 "the panic hook's scheduler submission must return before unwind"
28533 );
28534
28535 executor.active_drainers.store(0, Ordering::SeqCst);
28536 executor.begin_shutdown();
28537 executor.wait_until_scheduler_idle();
28538 return;
28539 }
28540
28541 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28542 .arg("--exact")
28543 .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
28544 .arg("--nocapture")
28545 .env(CHILD_ENV, "1")
28546 .stdout(std::process::Stdio::piped())
28547 .stderr(std::process::Stdio::piped())
28548 .spawn()
28549 .expect("the panic-hook subprocess must start");
28550 let deadline = Instant::now() + Duration::from_secs(5);
28551 let status = loop {
28552 if let Some(status) = child
28553 .try_wait()
28554 .expect("the subprocess status must be readable")
28555 {
28556 break status;
28557 }
28558 if Instant::now() >= deadline {
28559 let _ = child.kill();
28560 let output = child
28561 .wait_with_output()
28562 .expect("the timed-out subprocess must be reaped");
28563 panic!(
28564 "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
28565 String::from_utf8_lossy(&output.stdout),
28566 String::from_utf8_lossy(&output.stderr)
28567 );
28568 }
28569 std::thread::sleep(Duration::from_millis(10));
28570 };
28571 let output = child
28572 .wait_with_output()
28573 .expect("the completed subprocess output must be read");
28574 assert!(
28575 status.success(),
28576 "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
28577 status.code(),
28578 String::from_utf8_lossy(&output.stdout),
28579 String::from_utf8_lossy(&output.stderr)
28580 );
28581 }
28582
28583 #[test]
28594 fn contained_waker_separates_wake_and_drop_panics() {
28595 struct PanicWakeAndDrop;
28596
28597 impl std::task::Wake for PanicWakeAndDrop {
28598 fn wake(self: Arc<Self>) {
28599 panic!("intentional consuming wake panic");
28600 }
28601
28602 fn wake_by_ref(self: &Arc<Self>) {
28603 panic!("intentional borrowed wake panic");
28604 }
28605 }
28606
28607 impl Drop for PanicWakeAndDrop {
28608 fn drop(&mut self) {
28609 panic!("intentional RawWaker destructor panic");
28610 }
28611 }
28612
28613 wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
28614 }
28615
28616 struct TestHostileRawWakerState {
28617 clones: AtomicUsize,
28618 drop_panics: AtomicUsize,
28619 panic_on_clone: Option<usize>,
28620 }
28621
28622 struct TestHostileRawWakerToken {
28623 state: Arc<TestHostileRawWakerState>,
28624 panic_on_drop: bool,
28625 }
28626
28627 unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
28628 let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
28629 let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
28630 assert_ne!(
28631 token.state.panic_on_clone,
28632 Some(clone),
28633 "intentional RawWaker clone panic"
28634 );
28635 let clone = Box::new(TestHostileRawWakerToken {
28636 state: Arc::clone(&token.state),
28637 panic_on_drop: true,
28638 });
28639 std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
28640 }
28641
28642 unsafe fn wake_test_hostile_waker(data: *const ()) {
28643 unsafe { drop_test_hostile_waker(data) };
28644 }
28645
28646 unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
28647
28648 unsafe fn drop_test_hostile_waker(data: *const ()) {
28649 let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
28650 if token.panic_on_drop {
28651 token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
28652 panic!("intentional cloned RawWaker destructor panic");
28653 }
28654 }
28655
28656 static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
28657 clone_test_hostile_waker,
28658 wake_test_hostile_waker,
28659 wake_test_hostile_waker_by_ref,
28660 drop_test_hostile_waker,
28661 );
28662
28663 fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
28664 let token = Box::new(TestHostileRawWakerToken {
28665 state,
28666 panic_on_drop: false,
28667 });
28668 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
28669 unsafe { Waker::from_raw(raw) }
28670 }
28671
28672 struct CountingWake(AtomicUsize);
28674 impl std::task::Wake for CountingWake {
28675 fn wake(self: Arc<Self>) {
28676 self.0.fetch_add(1, Ordering::SeqCst);
28677 }
28678 }
28679
28680 type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
28682
28683 struct StubHostTimerDriver {
28687 timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
28688 }
28689
28690 impl StubHostTimerDriver {
28691 fn new() -> Self {
28692 Self {
28693 timers: Mutex::new(FxHashMap::default()),
28694 }
28695 }
28696 }
28697
28698 impl TimerDriver for StubHostTimerDriver {
28699 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28700 let mut timers = self.timers.lock().unwrap();
28701 if let Some(cell) = timers.get(&id) {
28702 *cell.0.lock().unwrap() = waker;
28703 return;
28704 }
28705 let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
28706 timers.insert(id, Arc::clone(&cell));
28707 std::thread::spawn(move || {
28708 let now = Instant::now();
28709 if deadline > now {
28710 std::thread::sleep(deadline - now);
28711 }
28712 if !cell.1.load(Ordering::SeqCst) {
28713 cell.0.lock().unwrap().wake_by_ref();
28714 }
28715 });
28716 }
28717
28718 fn cancel(&self, id: TimerId) {
28719 let removed = self.timers.lock().unwrap().remove(&id);
28720 if let Some(cell) = removed {
28721 cell.1.store(true, Ordering::SeqCst);
28722 }
28723 }
28724 }
28725
28726 struct ManualStubDriver {
28732 live: AtomicBool,
28733 registers: Mutex<Vec<(TimerId, Instant)>>,
28734 cancels: Mutex<Vec<TimerId>>,
28735 pending: Mutex<FxHashMap<TimerId, Waker>>,
28736 swept: AtomicBool,
28737 }
28738
28739 impl ManualStubDriver {
28740 fn new() -> Arc<Self> {
28741 Arc::new(Self {
28742 live: AtomicBool::new(true),
28743 registers: Mutex::new(Vec::new()),
28744 cancels: Mutex::new(Vec::new()),
28745 pending: Mutex::new(FxHashMap::default()),
28746 swept: AtomicBool::new(false),
28747 })
28748 }
28749
28750 fn kill(&self) {
28751 self.live.store(false, Ordering::SeqCst);
28752 }
28753 }
28754
28755 impl TimerDriver for ManualStubDriver {
28756 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28757 self.registers.lock().unwrap().push((id, deadline));
28758 self.pending.lock().unwrap().insert(id, waker);
28759 }
28760
28761 fn cancel(&self, id: TimerId) {
28762 self.cancels.lock().unwrap().push(id);
28763 self.pending.lock().unwrap().remove(&id);
28764 }
28765
28766 fn is_live(&self) -> bool {
28767 self.live.load(Ordering::SeqCst)
28768 }
28769
28770 fn on_swept(&self) {
28771 self.swept.store(true, Ordering::SeqCst);
28774 let wakers: Vec<Waker> = self
28775 .pending
28776 .lock()
28777 .unwrap()
28778 .drain()
28779 .map(|(_, w)| w)
28780 .collect();
28781 for waker in wakers {
28782 waker.wake();
28783 }
28784 }
28785 }
28786
28787 struct PanickingTimerDriver {
28788 swept: AtomicBool,
28789 }
28790
28791 impl TimerDriver for PanickingTimerDriver {
28792 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28793
28794 fn cancel(&self, _id: TimerId) {}
28795
28796 fn is_live(&self) -> bool {
28797 panic!("intentional timer-driver liveness panic");
28798 }
28799
28800 fn on_swept(&self) {
28801 self.swept.store(true, Ordering::SeqCst);
28802 panic!("intentional timer-driver sweep panic");
28803 }
28804 }
28805
28806 struct PanickingRegisterTimerDriver {
28807 registers: AtomicUsize,
28808 cancels: Mutex<Vec<TimerId>>,
28809 pending: Mutex<FxHashMap<TimerId, Waker>>,
28810 }
28811
28812 impl PanickingRegisterTimerDriver {
28813 fn new() -> Arc<Self> {
28814 Arc::new(Self {
28815 registers: AtomicUsize::new(0),
28816 cancels: Mutex::new(Vec::new()),
28817 pending: Mutex::new(FxHashMap::default()),
28818 })
28819 }
28820 }
28821
28822 impl TimerDriver for PanickingRegisterTimerDriver {
28823 fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
28824 self.registers.fetch_add(1, Ordering::SeqCst);
28825 self.pending.lock().unwrap().insert(id, waker);
28826 panic!("intentional timer-driver register panic after arming");
28827 }
28828
28829 fn cancel(&self, id: TimerId) {
28830 self.cancels.lock().unwrap().push(id);
28831 self.pending.lock().unwrap().remove(&id);
28832 }
28833 }
28834
28835 struct PanicBeforeRetainingTimerDriver {
28836 registers: AtomicUsize,
28837 }
28838
28839 impl TimerDriver for PanicBeforeRetainingTimerDriver {
28840 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28841 self.registers.fetch_add(1, Ordering::SeqCst);
28842 panic!("intentional timer-driver register panic before retaining its waker");
28843 }
28844
28845 fn cancel(&self, _id: TimerId) {}
28846 }
28847
28848 struct PanickingCancelDropTimerDriver {
28849 cancels: Arc<AtomicUsize>,
28850 drops: Arc<AtomicUsize>,
28851 }
28852
28853 impl TimerDriver for PanickingCancelDropTimerDriver {
28854 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28855
28856 fn cancel(&self, _id: TimerId) {
28857 self.cancels.fetch_add(1, Ordering::SeqCst);
28858 panic!("intentional timer-driver cancel panic");
28859 }
28860 }
28861
28862 impl Drop for PanickingCancelDropTimerDriver {
28863 fn drop(&mut self) {
28864 self.drops.fetch_add(1, Ordering::SeqCst);
28865 panic!("intentional timer-driver destructor panic");
28866 }
28867 }
28868
28869 struct UnregisterLaterThenPanicTimerDriver {
28870 registry: Weak<TimerDriverRegistry>,
28871 later: Mutex<Option<TimerDriverId>>,
28872 registers: AtomicUsize,
28873 }
28874
28875 impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
28876 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28877 self.registers.fetch_add(1, Ordering::SeqCst);
28878 let later = *self.later.lock().unwrap();
28879 if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
28880 registry.unregister(later);
28881 }
28882 panic!("intentional earlier timer-driver panic after unregistering a later driver");
28883 }
28884
28885 fn cancel(&self, _id: TimerId) {}
28886 }
28887
28888 struct ReentrantLivenessDriver {
28889 registry: std::sync::Weak<TimerDriverRegistry>,
28890 registration: Mutex<Option<TimerDriverId>>,
28891 }
28892
28893 impl TimerDriver for ReentrantLivenessDriver {
28894 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28895
28896 fn cancel(&self, _id: TimerId) {}
28897
28898 fn is_live(&self) -> bool {
28899 let registration = *self.registration.lock().unwrap();
28900 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
28901 registry.unregister(id);
28902 }
28903 false
28904 }
28905 }
28906
28907 struct ReentrantMultiUnregisterTimerDriver {
28908 registry: std::sync::Weak<TimerDriverRegistry>,
28909 registrations: Mutex<Vec<TimerDriverId>>,
28910 }
28911
28912 impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
28913 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28914
28915 fn cancel(&self, _id: TimerId) {}
28916
28917 fn is_live(&self) -> bool {
28918 if let Some(registry) = self.registry.upgrade() {
28919 for registration in self.registrations.lock().unwrap().iter().copied() {
28920 registry.unregister(registration);
28921 }
28922 }
28923 false
28924 }
28925 }
28926
28927 struct ReentrantSweepTimerDriver {
28928 registry: std::sync::Weak<TimerDriverRegistry>,
28929 registration: Mutex<Option<TimerDriverId>>,
28930 sweeps: AtomicUsize,
28931 removed_before_sweep: AtomicBool,
28932 }
28933
28934 impl TimerDriver for ReentrantSweepTimerDriver {
28935 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28936
28937 fn cancel(&self, _id: TimerId) {}
28938
28939 fn is_live(&self) -> bool {
28940 false
28941 }
28942
28943 fn on_swept(&self) {
28944 self.sweeps.fetch_add(1, Ordering::SeqCst);
28945 let registration = self.registration.lock().unwrap().take();
28946 if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
28947 let state = registry
28948 .state
28949 .try_lock()
28950 .expect("the sweep callback must run outside the timer-driver registry lock");
28951 assert!(
28952 !state.entries.contains_key(®istration),
28953 "the dead driver must be unlinked before its sweep callback runs"
28954 );
28955 self.removed_before_sweep.store(true, Ordering::SeqCst);
28956 drop(state);
28957 registry.unregister(registration);
28960 }
28961 }
28962 }
28963
28964 struct ReentrantDropDriver {
28965 registry: std::sync::Weak<TimerDriverRegistry>,
28966 fallback: TimerDriverId,
28967 }
28968
28969 impl TimerDriver for ReentrantDropDriver {
28970 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28971
28972 fn cancel(&self, _id: TimerId) {}
28973 }
28974
28975 impl Drop for ReentrantDropDriver {
28976 fn drop(&mut self) {
28977 if let Some(registry) = self.registry.upgrade() {
28978 registry.unregister(self.fallback);
28979 }
28980 }
28981 }
28982
28983 #[derive(Default)]
28986 struct WakeFlag(AtomicBool);
28987
28988 impl std::task::Wake for WakeFlag {
28989 fn wake(self: Arc<Self>) {
28990 self.0.store(true, Ordering::SeqCst);
28991 }
28992 }
28993
28994 #[cfg(not(target_family = "wasm"))]
28995 fn multi_thread_executor(
28996 workers: usize,
28997 park_deadline: Option<Duration>,
28998 prefix: &str,
28999 ) -> Arc<MultiThreadExecutor> {
29000 let options = RuntimeOptions {
29001 flavor: RuntimeFlavor::MultiThread,
29002 worker_threads: workers,
29003 max_blocking_tasks: workers,
29004 thread_name_prefix: prefix.to_string(),
29005 park_deadline,
29006 drain_linger: DEFAULT_DRAIN_LINGER,
29007 };
29008 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
29009 }
29010
29011 fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
29014 let registry = Arc::new(TimerDriverRegistry::default());
29015 registry.register(driver);
29016 registry
29017 }
29018
29019 #[cfg(not(target_family = "wasm"))]
29020 fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
29021 make_sleep(
29023 &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
29024 &Arc::new(TimerDriverRegistry::default()),
29025 deadline,
29026 )
29027 }
29028
29029 #[cfg(not(target_family = "wasm"))]
29030 #[test]
29031 fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
29032 use std::sync::mpsc;
29033
29034 let executor = multi_thread_executor(
29035 1,
29036 Some(Duration::from_secs(1)),
29037 "timer-registration-admission",
29038 );
29039 let (entered_tx, entered_rx) = mpsc::channel();
29040 let (release_tx, release_rx) = mpsc::channel();
29041 let registration_executor = Arc::clone(&executor);
29042 let registration = std::thread::spawn(move || {
29043 TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
29044 *slot.borrow_mut() = Some(Box::new(move || {
29045 entered_tx.send(()).unwrap();
29046 release_rx.recv().unwrap();
29047 }));
29048 });
29049 registration_executor.register_timer(
29050 1,
29051 Instant::now() + Duration::from_secs(60),
29052 futures::task::noop_waker(),
29053 &Arc::new(AtomicBool::new(false)),
29054 );
29055 });
29056
29057 entered_rx
29058 .recv_timeout(Duration::from_secs(2))
29059 .expect("timer registration must pause after entering verdict admission");
29060 let verdict = executor.try_begin_deadlock_verdict();
29061 let admission_blocked_verdict = verdict.is_none();
29062 drop(verdict);
29063 release_tx.send(()).unwrap();
29064 registration.join().unwrap();
29065
29066 assert!(
29067 admission_blocked_verdict,
29068 "a timer registration must remain announced until its heap entry is visible"
29069 );
29070 assert!(
29071 executor.next_timer_deadline().is_some(),
29072 "the admitted registration must publish its timer after release"
29073 );
29074 executor.begin_shutdown();
29075 executor.wait_until_scheduler_idle();
29076 }
29077
29078 #[cfg(not(target_family = "wasm"))]
29079 #[derive(Clone, Copy, Debug)]
29080 enum TimerFireAdmissionPath {
29081 Timekeeper,
29082 Cooperative,
29083 Shutdown,
29084 }
29085
29086 #[cfg(not(target_family = "wasm"))]
29087 struct ScheduleAfterTimerWakeRelease {
29088 executor: Arc<MultiThreadExecutor>,
29089 runnable: Mutex<Option<Runnable>>,
29090 entered: std::sync::mpsc::Sender<()>,
29091 release: Mutex<std::sync::mpsc::Receiver<()>>,
29092 scheduled: std::sync::mpsc::Sender<()>,
29093 }
29094
29095 #[cfg(not(target_family = "wasm"))]
29096 impl ScheduleAfterTimerWakeRelease {
29097 fn publish(&self) {
29098 self.entered.send(()).unwrap();
29099 self
29100 .release
29101 .lock()
29102 .unwrap_or_else(std::sync::PoisonError::into_inner)
29103 .recv()
29104 .unwrap();
29105 let runnable = self
29106 .runnable
29107 .lock()
29108 .unwrap_or_else(std::sync::PoisonError::into_inner)
29109 .take();
29110 if let Some(runnable) = runnable {
29111 self.executor.schedule(runnable);
29112 }
29113 self.scheduled.send(()).unwrap();
29114 }
29115 }
29116
29117 #[cfg(not(target_family = "wasm"))]
29118 impl std::task::Wake for ScheduleAfterTimerWakeRelease {
29119 fn wake(self: Arc<Self>) {
29120 self.publish();
29121 }
29122
29123 fn wake_by_ref(self: &Arc<Self>) {
29124 self.publish();
29125 }
29126 }
29127
29128 #[cfg(not(target_family = "wasm"))]
29129 fn insert_timer_for_fire_admission_test(
29130 executor: &MultiThreadExecutor,
29131 id: TimerId,
29132 deadline: Instant,
29133 waker: Waker,
29134 fired: Arc<AtomicBool>,
29135 ) {
29136 let mut inner = executor
29137 .timers
29138 .inner
29139 .lock()
29140 .unwrap_or_else(std::sync::PoisonError::into_inner);
29141 inner.entries.insert(id, HeapTimerEntry { waker, fired });
29142 inner.queue.push(deadline, id);
29143 }
29144
29145 #[cfg(not(target_family = "wasm"))]
29146 fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
29147 use std::sync::mpsc;
29148
29149 let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
29150 let (runnable, task) = async_task::spawn(async {}, |_| {});
29151 task.detach();
29152 let (entered_tx, entered_rx) = mpsc::channel();
29153 let (release_tx, release_rx) = mpsc::channel();
29154 let (scheduled_tx, scheduled_rx) = mpsc::channel();
29155 let fired = Arc::new(AtomicBool::new(false));
29156 let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
29157 executor: Arc::clone(&executor),
29158 runnable: Mutex::new(Some(runnable)),
29159 entered: entered_tx,
29160 release: Mutex::new(release_rx),
29161 scheduled: scheduled_tx,
29162 }));
29163
29164 let fire_thread = match path {
29165 TimerFireAdmissionPath::Timekeeper => {
29166 executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
29167 None
29168 }
29169 TimerFireAdmissionPath::Cooperative => {
29170 insert_timer_for_fire_admission_test(
29171 &executor,
29172 1,
29173 Instant::now(),
29174 waker,
29175 Arc::clone(&fired),
29176 );
29177 let fire_executor = Arc::clone(&executor);
29178 Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
29179 }
29180 TimerFireAdmissionPath::Shutdown => {
29181 insert_timer_for_fire_admission_test(
29182 &executor,
29183 1,
29184 Instant::now() + Duration::from_secs(60),
29185 waker,
29186 Arc::clone(&fired),
29187 );
29188 let fire_executor = Arc::clone(&executor);
29189 Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
29190 }
29191 };
29192
29193 entered_rx
29197 .recv_timeout(Duration::from_secs(10))
29198 .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
29199 assert!(
29200 executor.next_timer_deadline().is_none(),
29201 "{path:?} must exercise the remove-before-wake window"
29202 );
29203 assert!(
29204 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
29205 "{path:?} must retain admission while its waker is publishing"
29206 );
29207 let verdict = executor.try_begin_deadlock_verdict();
29208 let admission_blocked_verdict = verdict.is_none();
29209 drop(verdict);
29210
29211 release_tx.send(()).unwrap();
29212 scheduled_rx
29213 .recv_timeout(Duration::from_secs(10))
29214 .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
29215 if let Some(fire_thread) = fire_thread {
29216 fire_thread.join().unwrap();
29217 }
29218 assert!(
29219 admission_blocked_verdict,
29220 "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
29221 );
29222
29223 executor.begin_shutdown();
29224 executor.wait_until_scheduler_idle();
29225 }
29226
29227 #[cfg(not(target_family = "wasm"))]
29228 #[test]
29229 fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
29230 for path in [
29231 TimerFireAdmissionPath::Timekeeper,
29232 TimerFireAdmissionPath::Cooperative,
29233 TimerFireAdmissionPath::Shutdown,
29234 ] {
29235 assert_timer_fire_path_holds_deadlock_admission(path);
29236 }
29237 }
29238
29239 #[cfg(not(target_family = "wasm"))]
29240 #[test]
29241 fn multi_thread_sleep_fires_from_foreign_block_on() {
29242 use std::sync::mpsc;
29247
29248 let (done_tx, done_rx) = mpsc::channel();
29249 let runner = std::thread::spawn(move || {
29250 let executor = multi_thread_executor(2, None, "timer-foreign");
29251 let started = Instant::now();
29252 let mut future = std::pin::pin!(async {
29253 heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
29254 });
29255 executor.block_on(future.as_mut());
29256 assert!(
29257 started.elapsed() >= Duration::from_millis(80),
29258 "the sleep must not resolve before its deadline"
29259 );
29260 let _ = done_tx.send(());
29261 });
29262
29263 match done_rx.recv_timeout(Duration::from_secs(10)) {
29264 Ok(()) => runner.join().unwrap(),
29265 Err(error) => {
29266 panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
29267 }
29268 }
29269 }
29270
29271 #[cfg(not(target_family = "wasm"))]
29272 #[test]
29273 fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
29274 use std::sync::mpsc;
29277
29278 let (done_tx, done_rx) = mpsc::channel();
29279 let runner = std::thread::spawn(move || {
29280 let executor = multi_thread_executor(1, None, "timer-drainer");
29281
29282 let sleep_exec = Arc::clone(&executor);
29285 let sleep_done = Arc::new(AtomicBool::new(false));
29286 let sleep_flag = Arc::clone(&sleep_done);
29287 let sched = Arc::clone(&executor);
29288 let (sleep_runnable, sleep_task) = async_task::spawn(
29289 async move {
29290 heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
29291 sleep_flag.store(true, Ordering::SeqCst);
29292 },
29293 move |r| sched.schedule(r),
29294 );
29295 executor.schedule(sleep_runnable);
29296 wait_until("the timekeeper parked on the 60s deadline", || {
29297 executor
29298 .timers
29299 .inner
29300 .lock()
29301 .unwrap()
29302 .timekeeper_parker
29303 .is_some()
29304 });
29305
29306 let counter = Arc::new(AtomicUsize::new(0));
29308 let mut tasks = Vec::new();
29309 for _ in 0..32 {
29310 let counter = Arc::clone(&counter);
29311 let sched = Arc::clone(&executor);
29312 let (runnable, task) = async_task::spawn(
29313 async move {
29314 counter.fetch_add(1, Ordering::SeqCst);
29315 },
29316 move |r| sched.schedule(r),
29317 );
29318 executor.schedule(runnable);
29319 tasks.push(task);
29320 }
29321 for task in tasks {
29322 futures::executor::block_on(task);
29323 }
29324 assert_eq!(counter.load(Ordering::SeqCst), 32);
29325 assert!(
29326 !sleep_done.load(Ordering::SeqCst),
29327 "the 60s timer must still be pending: the work ran DURING the timer wait"
29328 );
29329 executor.shutdown_timers();
29331 futures::executor::block_on(sleep_task);
29332 let _ = done_tx.send(());
29333 });
29334
29335 match done_rx.recv_timeout(Duration::from_secs(15)) {
29336 Ok(()) => runner.join().unwrap(),
29337 Err(error) => {
29338 panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
29339 }
29340 }
29341 }
29342
29343 #[cfg(not(target_family = "wasm"))]
29344 #[test]
29345 fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
29346 let executor = multi_thread_executor(2, None, "timer-lazy-start");
29347 assert!(
29348 executor.timers.thread.lock().unwrap().is_none(),
29349 "constructing a runtime without timers must not create an extra OS thread"
29350 );
29351
29352 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29353 assert!(
29354 executor.timers.thread.lock().unwrap().is_none(),
29355 "an unpolled sleep has not registered a deadline"
29356 );
29357 let waker = futures::task::noop_waker();
29358 let mut cx = Context::from_waker(&waker);
29359 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29360 assert!(
29361 executor.timers.thread.lock().unwrap().is_some(),
29362 "the first timer registration must start the dedicated service"
29363 );
29364 }
29365
29366 #[cfg(not(target_family = "wasm"))]
29367 #[test]
29368 fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
29369 use std::sync::mpsc;
29370
29371 let executor = multi_thread_executor(1, None, "timer-closed-start");
29372 executor.shutdown_timers();
29373 let handle_guard = executor.timers.thread.lock().unwrap();
29374 let ensure_executor = Arc::clone(&executor);
29375 let (completed_tx, completed_rx) = mpsc::channel();
29376 let ensure = std::thread::spawn(move || {
29377 ensure_executor.timers.ensure_started().unwrap();
29378 completed_tx.send(()).unwrap();
29379 });
29380
29381 completed_rx.recv_timeout(Duration::from_secs(10)).expect(
29384 "ensure_started consulted or joined the timer handle before observing the closed heap",
29385 );
29386 drop(handle_guard);
29387 ensure.join().unwrap();
29388 }
29389
29390 #[cfg(not(target_family = "wasm"))]
29391 #[test]
29392 fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
29393 const TIMERS: u64 = 4096;
29394
29395 let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
29396 let base = Instant::now() + Duration::from_secs(3600);
29397 for id in 0..TIMERS {
29398 executor.register_timer(
29399 id,
29400 base + Duration::from_secs(id),
29401 futures::task::noop_waker(),
29402 &Arc::new(AtomicBool::new(false)),
29403 );
29404 }
29405 {
29406 let inner = executor.timers.inner.lock().unwrap();
29407 assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
29408 assert_eq!(inner.queue.len(), inner.entries.len());
29409 }
29410
29411 for id in 1..TIMERS {
29414 executor.cancel_timer(id);
29415 }
29416 {
29417 let inner = executor.timers.inner.lock().unwrap();
29418 assert_eq!(inner.entries.len(), 1);
29419 assert_eq!(inner.queue.len(), 1);
29420 assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
29421 }
29422
29423 executor.cancel_timer(0);
29424 {
29425 let inner = executor.timers.inner.lock().unwrap();
29426 assert!(inner.entries.is_empty());
29427 assert!(inner.queue.is_empty());
29428 }
29429 executor.shutdown_timers();
29430 executor.timers.join();
29431 }
29432
29433 #[cfg(not(target_family = "wasm"))]
29434 #[test]
29435 fn timer_registration_is_transactional_when_timekeeper_start_fails() {
29436 let executor = multi_thread_executor(2, None, "timer-start-failure");
29437 executor
29438 .timers
29439 .fail_next_start
29440 .store(true, Ordering::SeqCst);
29441
29442 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29443 let waker = Waker::from(Arc::clone(&wake_count));
29444 let baseline_waker_refs = Arc::strong_count(&wake_count);
29445 let mut cx = Context::from_waker(&waker);
29446 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29447
29448 let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
29449 assert!(
29450 failure.is_err(),
29451 "the injected timer-thread start failure must panic the poll"
29452 );
29453 {
29454 let inner = executor.timers.inner.lock().unwrap();
29455 assert!(
29456 inner.entries.is_empty(),
29457 "a failed start must not retain a timer entry"
29458 );
29459 assert!(
29460 inner.queue.is_empty(),
29461 "a failed start must not retain a stale heap node"
29462 );
29463 assert!(
29464 inner.timekeeper_parker.is_none(),
29465 "a failed start must not publish a timekeeper parker"
29466 );
29467 }
29468 assert!(
29469 executor.timers.thread.lock().unwrap().is_none(),
29470 "a failed start must not retain a thread handle"
29471 );
29472 assert_eq!(
29473 Arc::strong_count(&wake_count),
29474 baseline_waker_refs,
29475 "a failed start must release the cloned timer waker"
29476 );
29477
29478 assert!(
29479 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29480 "the same Sleep must be safely retryable after a transient start failure"
29481 );
29482 {
29483 let inner = executor.timers.inner.lock().unwrap();
29484 assert_eq!(
29485 inner.entries.len(),
29486 1,
29487 "the retry must publish exactly one timer entry"
29488 );
29489 assert_eq!(
29490 inner.queue.len(),
29491 1,
29492 "the retry must publish exactly one heap node"
29493 );
29494 }
29495 assert!(
29496 executor.timers.thread.lock().unwrap().is_some(),
29497 "the retry must start the dedicated timekeeper"
29498 );
29499
29500 drop(sleep);
29501 assert!(
29502 executor.timers.inner.lock().unwrap().entries.is_empty(),
29503 "dropping the retried Sleep must cancel its registration"
29504 );
29505 executor.shutdown_timers();
29506 executor.wait_until_scheduler_idle();
29507 assert!(
29508 executor.timers.thread.lock().unwrap().is_none(),
29509 "shutdown after a recovered start failure must join the timekeeper"
29510 );
29511 }
29512
29513 #[cfg(not(target_family = "wasm"))]
29514 #[test]
29515 fn pending_timer_preserves_every_configured_rayon_worker() {
29516 use std::sync::mpsc;
29517
29518 let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
29519 let waker = futures::task::noop_waker();
29520 let mut cx = Context::from_waker(&waker);
29521 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29522 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29523 wait_until("the dedicated timer thread to park", || {
29524 executor
29525 .timers
29526 .inner
29527 .lock()
29528 .unwrap()
29529 .timekeeper_parker
29530 .is_some()
29531 });
29532
29533 let released = Arc::new((Mutex::new(false), Condvar::new()));
29534 let (started_tx, started_rx) = mpsc::channel();
29535 let (completed_tx, completed_rx) = mpsc::channel();
29536 for _ in 0..2 {
29537 let released = Arc::clone(&released);
29538 let started_tx = started_tx.clone();
29539 let completed_tx = completed_tx.clone();
29540 executor.pool.spawn(move || {
29541 started_tx.send(()).unwrap();
29542 let (lock, changed) = &*released;
29543 let mut released = lock
29544 .lock()
29545 .unwrap_or_else(std::sync::PoisonError::into_inner);
29546 while !*released {
29547 released = changed
29548 .wait(released)
29549 .unwrap_or_else(std::sync::PoisonError::into_inner);
29550 }
29551 completed_tx.send(()).unwrap();
29552 });
29553 }
29554 drop(started_tx);
29555 drop(completed_tx);
29556
29557 let first = started_rx.recv_timeout(Duration::from_secs(2));
29558 let second = started_rx.recv_timeout(Duration::from_secs(2));
29559 {
29560 let (lock, changed) = &*released;
29561 *lock
29562 .lock()
29563 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29564 changed.notify_all();
29565 }
29566 drop(sleep);
29567 for _ in 0..2 {
29568 completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29569 }
29570
29571 assert!(
29572 first.is_ok(),
29573 "the first configured Rayon worker did not start"
29574 );
29575 assert!(
29576 second.is_ok(),
29577 "a pending timer consumed one of the two configured Rayon workers"
29578 );
29579 }
29580
29581 #[cfg(not(target_family = "wasm"))]
29582 #[test]
29583 fn timekeeper_never_enters_a_blocking_closure() {
29584 use std::{
29585 sync::{Barrier, mpsc},
29586 time::Duration,
29587 };
29588
29589 let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
29590 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29591 let waker = Waker::from(Arc::clone(&wake_count));
29592 let mut cx = Context::from_waker(&waker);
29593 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
29594 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29595 wait_until(
29596 "the timekeeper parked before blocking work was submitted",
29597 || {
29598 executor
29599 .timers
29600 .inner
29601 .lock()
29602 .unwrap()
29603 .timekeeper_parker
29604 .is_some()
29605 },
29606 );
29607
29608 let release = Arc::new(Barrier::new(2));
29609 let blocker_release = Arc::clone(&release);
29610 let (started_tx, started_rx) = mpsc::sync_channel(1);
29611 let blocking = executor.schedule_blocking(move || {
29612 started_tx.send(()).unwrap();
29613 blocker_release.wait();
29614 });
29615 started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
29618
29619 wait_until(
29620 "the timer waker to fire while the Rayon worker is blocked",
29621 || wake_count.0.load(Ordering::SeqCst) != 0,
29622 );
29623 release.wait();
29624 futures::executor::block_on(blocking).unwrap();
29625 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
29626 }
29627
29628 #[cfg(not(target_family = "wasm"))]
29629 #[test]
29630 fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
29631 use std::sync::mpsc;
29632
29633 struct PendingEntered(Option<mpsc::Sender<()>>);
29634
29635 impl Future for PendingEntered {
29636 type Output = ();
29637
29638 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29639 if let Some(entered) = self.0.take() {
29640 entered.send(()).unwrap();
29641 }
29642 Poll::Pending
29643 }
29644 }
29645
29646 let options = RuntimeOptions {
29647 flavor: RuntimeFlavor::MultiThread,
29648 worker_threads: 2,
29649 max_blocking_tasks: 1,
29650 thread_name_prefix: "nested-timer-wake".to_string(),
29651 park_deadline: None,
29652 drain_linger: DEFAULT_DRAIN_LINGER,
29653 };
29654 let executor =
29655 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
29656
29657 let timer_exec = Arc::clone(&executor);
29658 let (timer_tx, timer_rx) = mpsc::channel();
29659 let timer_scheduler = Arc::clone(&executor);
29660 let (timer_runnable, timer_task) = async_task::spawn(
29661 async move {
29662 heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
29663 timer_tx.send(()).unwrap();
29664 },
29665 move |runnable| timer_scheduler.schedule(runnable),
29666 );
29667 executor.schedule(timer_runnable);
29668 wait_until(
29669 "the dedicated timekeeper parked before nested block_on",
29670 || {
29671 executor
29672 .timers
29673 .inner
29674 .lock()
29675 .unwrap()
29676 .timekeeper_parker
29677 .is_some()
29678 },
29679 );
29680
29681 let (entered_tx, entered_rx) = mpsc::channel();
29682 let nested_exec = Arc::clone(&executor);
29683 let nested_scheduler = Arc::clone(&executor);
29684 let (nested_runnable, nested_task) = async_task::spawn(
29685 async move {
29686 let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
29687 let outcome = nested_exec.block_on(pending.as_mut());
29688 drop(pending);
29689 assert_eq!(
29690 outcome,
29691 BlockOnOutcome::Stopped,
29692 "shutdown must stop the raw nested block_on driver"
29693 );
29694 },
29695 move |runnable| nested_scheduler.schedule(runnable),
29696 );
29697 executor.schedule(nested_runnable);
29698 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29699
29700 let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
29701 let (release_tx, release_rx) = mpsc::channel();
29702 let blocking = executor.schedule_blocking(move || {
29703 blocking_started_tx.send(()).unwrap();
29704 release_rx.recv().unwrap();
29705 });
29706 blocking_started_rx
29707 .recv_timeout(Duration::from_secs(2))
29708 .expect("a general worker must service blocking work");
29709 timer_rx
29710 .recv_timeout(Duration::from_secs(2))
29711 .expect("the timer wake must reach the nested cooperative driver");
29712
29713 release_tx.send(()).unwrap();
29714 futures::executor::block_on(blocking).unwrap();
29715 executor.begin_shutdown();
29716 assert_eq!(
29717 futures::executor::block_on(nested_task.fallible()),
29718 Some(())
29719 );
29720 futures::executor::block_on(timer_task);
29721 }
29722
29723 #[cfg(not(target_family = "wasm"))]
29724 #[test]
29725 fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
29726 use std::sync::mpsc;
29730
29731 let (done_tx, done_rx) = mpsc::channel();
29732 let runner = std::thread::spawn(move || {
29733 let executor = multi_thread_executor(2, None, "timer-rearm");
29734
29735 let long_exec = Arc::clone(&executor);
29736 let long_done = Arc::new(AtomicBool::new(false));
29737 let long_flag = Arc::clone(&long_done);
29738 let sched = Arc::clone(&executor);
29739 let (long_runnable, long_task) = async_task::spawn(
29740 async move {
29741 heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
29742 long_flag.store(true, Ordering::SeqCst);
29743 },
29744 move |r| sched.schedule(r),
29745 );
29746 executor.schedule(long_runnable);
29747 wait_until("the timekeeper parked on the 60s sentinel", || {
29748 executor
29749 .timers
29750 .inner
29751 .lock()
29752 .unwrap()
29753 .timekeeper_parker
29754 .is_some()
29755 });
29756
29757 let started = Instant::now();
29758 let mut future = std::pin::pin!(async {
29759 heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
29760 });
29761 executor.block_on(future.as_mut());
29762 let elapsed = started.elapsed();
29763 assert!(
29764 elapsed >= Duration::from_millis(60),
29765 "fired before its deadline: {elapsed:?}"
29766 );
29767 assert!(
29768 !long_done.load(Ordering::SeqCst),
29769 "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
29770 timekeeper, not by the sentinel expiring"
29771 );
29772 executor.shutdown_timers();
29773 futures::executor::block_on(long_task);
29774 let _ = done_tx.send(());
29775 });
29776
29777 match done_rx.recv_timeout(Duration::from_secs(15)) {
29778 Ok(()) => runner.join().unwrap(),
29779 Err(error) => panic!(
29780 "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
29781 ),
29782 }
29783 }
29784
29785 #[cfg(not(target_family = "wasm"))]
29786 #[test]
29787 fn multi_thread_sleep_cancels_registration_on_drop() {
29788 let executor = multi_thread_executor(1, None, "timer-cancel");
29791 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29792 let waker = Waker::from(Arc::clone(&wake_count));
29793 let mut cx = Context::from_waker(&waker);
29794
29795 let deadline = Instant::now() + Duration::from_millis(250);
29799 let mut sleep = heap_sleep(&executor, deadline);
29800 assert!(
29801 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29802 "must register, not resolve"
29803 );
29804 assert!(
29805 executor.next_timer_deadline().is_some(),
29806 "the registration must be in the heap"
29807 );
29808 drop(sleep);
29809 assert!(
29810 executor.next_timer_deadline().is_none(),
29811 "cancel-on-drop must retire the heap registration"
29812 );
29813 while Instant::now() < deadline + Duration::from_millis(25) {
29818 std::thread::sleep(Duration::from_millis(10));
29819 }
29820 executor.fire_due_timers();
29821 assert_eq!(
29822 wake_count.0.load(Ordering::SeqCst),
29823 0,
29824 "a dropped Sleep's waker must never fire"
29825 );
29826 }
29827
29828 #[cfg(not(target_family = "wasm"))]
29829 #[test]
29830 fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
29831 let executor = multi_thread_executor(2, None, "timer-cancel-retire");
29832 let waker = futures::task::noop_waker();
29833 let mut cx = Context::from_waker(&waker);
29834 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29835
29836 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29837 wait_until(
29838 "the timekeeper parked on the timer that will be cancelled",
29839 || {
29840 executor
29841 .timers
29842 .inner
29843 .lock()
29844 .unwrap()
29845 .timekeeper_parker
29846 .is_some()
29847 },
29848 );
29849
29850 drop(sleep);
29851 wait_until(
29852 "the cancelled timer's timekeeper returned to its idle wait",
29853 || {
29854 executor.next_timer_deadline().is_none()
29855 && executor
29856 .timers
29857 .inner
29858 .lock()
29859 .unwrap()
29860 .timekeeper_parker
29861 .is_none()
29862 },
29863 );
29864 }
29865
29866 #[cfg(not(target_family = "wasm"))]
29867 #[test]
29868 fn timer_waker_replacement_drops_outside_heap_lock() {
29869 use std::sync::mpsc;
29870
29871 struct DropOtherTimer(Mutex<Option<Sleep>>);
29872
29873 #[expect(
29874 clippy::manual_noop_waker,
29875 reason = "the no-op wake type has a reentrant Drop used by this regression"
29876 )]
29877 impl std::task::Wake for DropOtherTimer {
29878 fn wake(self: Arc<Self>) {}
29879 }
29880
29881 impl Drop for DropOtherTimer {
29882 fn drop(&mut self) {
29883 drop(self.0.lock().unwrap().take());
29884 }
29885 }
29886
29887 let (done_tx, done_rx) = mpsc::channel();
29888 let runner = std::thread::spawn(move || {
29889 let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
29890 let deadline = Instant::now() + Duration::from_secs(120);
29891 let mut other = heap_sleep(&executor, deadline);
29892 let noop = futures::task::noop_waker();
29893 let mut noop_cx = Context::from_waker(&noop);
29894 assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
29895
29896 let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
29897 let mut primary = heap_sleep(&executor, deadline);
29898 let mut old_cx = Context::from_waker(&old);
29899 assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
29900 drop(old);
29901
29902 let replacement = futures::task::noop_waker();
29903 let mut replacement_cx = Context::from_waker(&replacement);
29904 assert!(
29905 Pin::new(&mut primary)
29906 .poll(&mut replacement_cx)
29907 .is_pending()
29908 );
29909 drop(primary);
29910 executor.shutdown_timers();
29911 done_tx.send(()).unwrap();
29912 });
29913 done_rx
29916 .recv_timeout(Duration::from_secs(10))
29917 .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
29918 runner.join().unwrap();
29919 }
29920
29921 #[cfg(not(target_family = "wasm"))]
29922 #[test]
29923 fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
29924 use std::sync::mpsc;
29925
29926 struct DropFinalExecutorOnWake {
29927 executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
29928 completed: mpsc::Sender<()>,
29929 }
29930
29931 impl std::task::Wake for DropFinalExecutorOnWake {
29932 fn wake(self: Arc<Self>) {
29933 let executor = self
29934 .executor
29935 .lock()
29936 .unwrap_or_else(std::sync::PoisonError::into_inner)
29937 .take()
29938 .expect("the timer waker must own the final executor reference");
29939 assert_eq!(
29940 ON_TIMER_THREAD.with(std::cell::Cell::get),
29941 Some(executor.id),
29942 "the dedicated timer thread must deliver the timer wake"
29943 );
29944 drop(executor);
29945 self
29946 .completed
29947 .send(())
29948 .expect("the test receiver must outlive the timer-thread callback");
29949 }
29950 }
29951
29952 let executor = multi_thread_executor(1, None, "timer-final-arc");
29953 let (completed_tx, completed_rx) = mpsc::channel();
29954 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
29955 {
29956 let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
29957 executor: Mutex::new(Some(Arc::clone(&executor))),
29958 completed: completed_tx,
29959 }));
29960 let mut cx = Context::from_waker(&waker);
29961 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29962 }
29963 wait_until(
29964 "the timer thread parked before owning the final executor Arc",
29965 || {
29966 executor
29967 .timers
29968 .inner
29969 .lock()
29970 .unwrap()
29971 .timekeeper_parker
29972 .is_some()
29973 },
29974 );
29975
29976 drop(executor);
29980 completed_rx
29983 .recv_timeout(Duration::from_secs(10))
29984 .expect("dropping the final executor Arc on the timer thread attempted to join itself");
29985 }
29986
29987 #[cfg(not(target_family = "wasm"))]
29988 #[test]
29989 fn multi_thread_shutdown_with_pending_timers_drain_fires() {
29990 use std::sync::mpsc;
29995
29996 let (done_tx, done_rx) = mpsc::channel();
29997 let runner = std::thread::spawn(move || {
29998 let controller = Arc::new(RuntimeController::new());
29999 let backend = controller.backend();
30001 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30002 panic!("default native backend must be MultiThread");
30003 };
30004 let executor = Arc::downgrade(executor);
30005 let mut late = heap_sleep(
30006 &executor
30007 .upgrade()
30008 .expect("the initial executor must be live"),
30009 Instant::now() + Duration::from_secs(120),
30010 );
30011 drop(backend);
30012
30013 let (parked_tx, parked_rx) = mpsc::channel();
30014 let block_controller = Arc::clone(&controller);
30015 let block_exec = Weak::clone(&executor);
30016 let blocker = std::thread::spawn(move || {
30017 let mut future = std::pin::pin!(async {
30018 let executor = block_exec
30019 .upgrade()
30020 .expect("the runtime must remain live while blocking");
30021 let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
30022 drop(executor);
30023 parked_tx.send(()).unwrap();
30024 sleep.await;
30025 });
30026 block_controller.block_on(future.as_mut());
30027 });
30028 parked_rx.recv().unwrap();
30029 wait_until("the 120s sleep registered in the heap", || {
30030 executor
30031 .upgrade()
30032 .is_some_and(|executor| executor.next_timer_deadline().is_some())
30033 });
30034
30035 controller
30037 .shutdown()
30038 .expect("runtime shutdown must succeed");
30039 blocker
30040 .join()
30041 .expect("the blocked sleep must be drain-fired by shutdown, not hang");
30042
30043 let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
30046 let mut cx = Context::from_waker(&waker);
30047 assert!(
30048 Pin::new(&mut late).poll(&mut cx).is_ready(),
30049 "a Sleep polled after shutdown must fire early, never park a closing runtime"
30050 );
30051 let _ = done_tx.send(());
30052 });
30053
30054 match done_rx.recv_timeout(Duration::from_secs(10)) {
30055 Ok(()) => runner.join().unwrap(),
30056 Err(error) => {
30057 panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
30058 }
30059 }
30060 }
30061
30062 #[cfg(not(target_family = "wasm"))]
30063 #[test]
30064 fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
30065 use std::sync::mpsc;
30074
30075 let (done_tx, done_rx) = mpsc::channel();
30076 let runner = std::thread::spawn(move || {
30077 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
30078
30079 let task_exec = Arc::clone(&executor);
30080 let body = async move {
30081 let started = Instant::now();
30082 let mut inner = std::pin::pin!(async {
30083 heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
30084 });
30085 task_exec.block_on(inner.as_mut());
30087 assert!(started.elapsed() >= Duration::from_millis(250));
30088 };
30089 let sched = Arc::clone(&executor);
30090 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
30091 executor.schedule(runnable);
30092
30093 futures::executor::block_on(task);
30097 drop(executor);
30103 let _ = done_tx.send(());
30104 });
30105
30106 match done_rx.recv_timeout(Duration::from_secs(10)) {
30107 Ok(()) => join_within(
30108 "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
30109 runner,
30110 Duration::from_secs(30),
30111 ),
30112 Err(error) => {
30113 panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
30114 }
30115 }
30116 }
30117
30118 #[cfg(not(target_family = "wasm"))]
30119 #[test]
30120 fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
30121 use std::sync::mpsc;
30128
30129 struct GateWake {
30132 gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
30133 }
30134 impl std::task::Wake for GateWake {
30135 fn wake(self: Arc<Self>) {
30136 let gate = self.gate.lock().unwrap().take();
30137 if let Some(gate) = gate {
30138 let _ = gate.send(23);
30139 }
30140 }
30141 }
30142
30143 let (done_tx, done_rx) = mpsc::channel();
30144 let runner = std::thread::spawn(move || {
30145 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
30146 executor
30150 .active_drainers
30151 .store(executor.max_drainers, Ordering::SeqCst);
30152
30153 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30156 let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
30157
30158 let hook_exec = Arc::clone(&executor);
30159 let hook_gate = Arc::clone(&gate_tx);
30160 let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
30163 let hook_sleep_slot = Arc::clone(&parked_sleep);
30164 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
30165 *slot.borrow_mut() = Some(Box::new(move || {
30166 let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
30167 let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
30168 let mut cx = Context::from_waker(&waker);
30169 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30170 *hook_sleep_slot.lock().unwrap() = Some(sleep);
30171 }));
30172 });
30173
30174 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30177 let mut output = None;
30178 {
30179 let mut future = std::pin::pin!(async {
30180 output = Some(gate_rx.await.unwrap());
30181 });
30182 executor.block_on(future.as_mut());
30183 }
30184 DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
30190 assert_eq!(
30191 output,
30192 Some(23),
30193 "the edge-registered timer must have fired and released the gate"
30194 );
30195 executor.active_drainers.store(0, Ordering::SeqCst);
30196 drop(executor);
30199 let _ = done_tx.send(());
30200 });
30201
30202 match done_rx.recv_timeout(Duration::from_secs(10)) {
30203 Ok(()) => join_within(
30204 "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
30205 runner,
30206 Duration::from_secs(30),
30207 ),
30208 Err(error) => panic!(
30209 "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
30210 ),
30211 }
30212 }
30213
30214 #[test]
30215 fn current_thread_sleep_without_host_driver_fails_loud() {
30216 let metrics = Arc::new(RuntimeMetrics::default());
30220 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30221 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
30222
30223 let payload = catch_unwind(AssertUnwindSafe(|| {
30224 let _sleep = make_sleep(
30225 &backend,
30226 &Arc::new(TimerDriverRegistry::default()),
30227 Instant::now() + Duration::from_millis(10),
30228 );
30229 }))
30230 .expect_err("sleep_until on CurrentThread without a driver must panic");
30231 let message = payload
30232 .downcast_ref::<String>()
30233 .cloned()
30234 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
30235 .expect("panic payload must be a message");
30236 assert!(
30237 message.contains("CurrentThread runtime has no live timer driver registered"),
30238 "the diagnostic must name the missing driver, got: {message}"
30239 );
30240 }
30241
30242 #[test]
30243 fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
30244 let registry = TimerDriverRegistry::default();
30248 let a = ManualStubDriver::new();
30249 let b = ManualStubDriver::new();
30250 let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
30251 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
30252 assert_ne!(a_id, b_id, "registration handles must be distinct");
30253 assert_eq!(
30254 registry.current().map(|(id, _)| id),
30255 Some(b_id),
30256 "newest live registrant serves"
30257 );
30258 assert!(registry.has_live_driver());
30259
30260 b.kill();
30263 assert_eq!(
30264 registry.current().map(|(id, _)| id),
30265 Some(a_id),
30266 "dead newest must be skipped"
30267 );
30268 b.live.store(true, Ordering::SeqCst);
30271 assert_eq!(
30272 registry.current().map(|(id, _)| id),
30273 Some(a_id),
30274 "swept entries stay gone"
30275 );
30276
30277 registry.unregister(a_id);
30280 assert!(registry.current().is_none());
30281 assert!(
30282 !registry.has_live_driver(),
30283 "an emptied registry must not report a live driver"
30284 );
30285 }
30286
30287 #[test]
30288 fn timer_driver_registry_contains_callback_panics_and_falls_back() {
30289 let registry = TimerDriverRegistry::default();
30290 let fallback = ManualStubDriver::new();
30291 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30292 let panicking = Arc::new(PanickingTimerDriver {
30293 swept: AtomicBool::new(false),
30294 });
30295 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30296
30297 assert_eq!(
30298 registry.current().map(|(id, _)| id),
30299 Some(fallback_id),
30300 "a panicking liveness callback must be treated as a dead driver"
30301 );
30302 assert!(
30303 panicking.swept.load(Ordering::SeqCst),
30304 "a panicking liveness callback must still run its contained sweep hook"
30305 );
30306 assert!(
30307 registry.has_live_driver(),
30308 "the healthy fallback must remain selectable"
30309 );
30310 }
30311
30312 #[test]
30313 fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
30314 use std::{sync::mpsc, time::Duration};
30315
30316 let registry = Arc::new(TimerDriverRegistry::default());
30317 let fallback = ManualStubDriver::new();
30318 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30319 let reentrant = Arc::new(ReentrantLivenessDriver {
30320 registry: Arc::downgrade(®istry),
30321 registration: Mutex::new(None),
30322 });
30323 let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30324 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
30325
30326 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
30327 let selection_registry = Arc::clone(®istry);
30328 let selection_thread = std::thread::spawn(move || {
30329 selection_tx
30330 .send(selection_registry.current().map(|(id, _)| id))
30331 .unwrap();
30332 });
30333 assert_eq!(
30334 selection_rx
30335 .recv_timeout(Duration::from_secs(10))
30336 .expect("is_live re-entry deadlocked"),
30337 Some(fallback_id)
30338 );
30339 selection_thread.join().unwrap();
30340
30341 let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
30342 registry: Arc::downgrade(®istry),
30343 fallback: fallback_id,
30344 });
30345 let drop_id = registry.register(Arc::clone(&drop_driver));
30346 drop(drop_driver);
30347
30348 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
30349 let drop_registry = Arc::clone(®istry);
30350 let drop_thread = std::thread::spawn(move || {
30351 drop_registry.unregister(drop_id);
30352 drop_tx.send(()).unwrap();
30353 });
30354 drop_rx
30355 .recv_timeout(Duration::from_secs(10))
30356 .expect("driver destructor re-entry deadlocked");
30357 drop_thread.join().unwrap();
30358 assert!(
30359 registry.current().is_none(),
30360 "the destructor must be able to unregister the fallback"
30361 );
30362 }
30363
30364 #[test]
30365 fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
30366 const DRIVER_PAIRS: usize = 2_048;
30367
30368 let registry = Arc::new(TimerDriverRegistry::default());
30369 let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
30370 let mut live = Vec::with_capacity(DRIVER_PAIRS);
30371 let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
30372 for _ in 0..DRIVER_PAIRS {
30373 let driver = Arc::new(ReentrantSweepTimerDriver {
30374 registry: Arc::downgrade(®istry),
30375 registration: Mutex::new(None),
30376 sweeps: AtomicUsize::new(0),
30377 removed_before_sweep: AtomicBool::new(false),
30378 });
30379 let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30380 *driver.registration.lock().unwrap() = Some(id);
30381 dead.push(driver);
30382
30383 let driver = ManualStubDriver::new();
30384 live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
30385 live.push(driver);
30386 }
30387 let dead_tail = Arc::new(ReentrantSweepTimerDriver {
30388 registry: Arc::downgrade(®istry),
30389 registration: Mutex::new(None),
30390 sweeps: AtomicUsize::new(0),
30391 removed_before_sweep: AtomicBool::new(false),
30392 });
30393 let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
30394 *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
30395 dead.push(dead_tail);
30396
30397 let selection = registry.live();
30398 assert_eq!(
30399 selection
30400 .as_slice()
30401 .iter()
30402 .map(|(id, _)| *id)
30403 .collect::<Vec<_>>(),
30404 live_ids,
30405 "sweeping every interleaved dead host must retain live registration order"
30406 );
30407 drop(selection);
30408 assert!(
30409 dead.iter().all(|driver| {
30410 driver.sweeps.load(Ordering::SeqCst) == 1
30411 && driver.removed_before_sweep.load(Ordering::SeqCst)
30412 && driver.registration.lock().unwrap().is_none()
30413 }),
30414 "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
30415 );
30416
30417 {
30418 let state = registry
30419 .state
30420 .lock()
30421 .unwrap_or_else(std::sync::PoisonError::into_inner);
30422 assert_eq!(state.entries.len(), DRIVER_PAIRS);
30423 assert_eq!(state.oldest, live_ids.first().copied());
30424 assert_eq!(state.newest, live_ids.last().copied());
30425 assert_eq!(
30426 state
30427 .ordered_snapshot()
30428 .iter()
30429 .map(|(id, _)| *id)
30430 .collect::<Vec<_>>(),
30431 live_ids,
30432 "keyed removals must preserve the linked order of every live host"
30433 );
30434 }
30435 let second_selection = registry.live();
30436 assert_eq!(
30437 second_selection
30438 .as_slice()
30439 .iter()
30440 .map(|(id, _)| *id)
30441 .collect::<Vec<_>>(),
30442 live_ids,
30443 "a second selection must retain the same live order"
30444 );
30445 drop(second_selection);
30446 assert!(
30447 dead
30448 .iter()
30449 .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
30450 "permanently swept hosts must not receive repeated eviction callbacks"
30451 );
30452 assert_eq!(
30453 registry.current().map(|(id, _)| id),
30454 live_ids.last().copied(),
30455 "newest-live compatibility selection must survive the mass sweep"
30456 );
30457
30458 drop(live);
30459 drop(dead);
30460 }
30461
30462 #[test]
30463 fn timer_driver_registry_snapshot_contains_each_driver_drop() {
30464 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
30465
30466 if std::env::var_os(CHILD_ENV).is_some() {
30467 let registry = Arc::new(TimerDriverRegistry::default());
30468 let fallback = ManualStubDriver::new();
30469 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30470 let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
30471 registry: Arc::downgrade(®istry),
30472 registrations: Mutex::new(Vec::new()),
30473 });
30474 registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30475
30476 let first_cancels = Arc::new(AtomicUsize::new(0));
30477 let first_drops = Arc::new(AtomicUsize::new(0));
30478 let first = Arc::new(PanickingCancelDropTimerDriver {
30479 cancels: Arc::clone(&first_cancels),
30480 drops: Arc::clone(&first_drops),
30481 });
30482 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30483 drop(first);
30484
30485 let second_cancels = Arc::new(AtomicUsize::new(0));
30486 let second_drops = Arc::new(AtomicUsize::new(0));
30487 let second = Arc::new(PanickingCancelDropTimerDriver {
30488 cancels: Arc::clone(&second_cancels),
30489 drops: Arc::clone(&second_drops),
30490 });
30491 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30492 drop(second);
30493 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
30494
30495 assert_eq!(
30496 registry.current().map(|(id, _)| id),
30497 Some(fallback_id),
30498 "selection must retry after reentrant removals and preserve the healthy fallback"
30499 );
30500 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30501 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30502 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30503 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30504 assert_eq!(
30505 registry
30506 .state
30507 .lock()
30508 .unwrap_or_else(std::sync::PoisonError::into_inner)
30509 .entries
30510 .len(),
30511 1
30512 );
30513 return;
30514 }
30515
30516 let output = std::process::Command::new(std::env::current_exe().unwrap())
30517 .arg("--exact")
30518 .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
30519 .arg("--nocapture")
30520 .env(CHILD_ENV, "1")
30521 .output()
30522 .expect("the hostile timer-driver snapshot subprocess test must start");
30523 assert!(
30524 output.status.success(),
30525 "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30526 output.status.code(),
30527 String::from_utf8_lossy(&output.stdout),
30528 String::from_utf8_lossy(&output.stderr)
30529 );
30530 }
30531
30532 #[test]
30533 fn timer_driver_registry_live_result_contains_each_driver_drop() {
30534 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
30535
30536 if std::env::var_os(CHILD_ENV).is_some() {
30537 let registry = TimerDriverRegistry::default();
30538
30539 let first_cancels = Arc::new(AtomicUsize::new(0));
30540 let first_drops = Arc::new(AtomicUsize::new(0));
30541 let first = Arc::new(PanickingCancelDropTimerDriver {
30542 cancels: Arc::clone(&first_cancels),
30543 drops: Arc::clone(&first_drops),
30544 });
30545 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30546 drop(first);
30547
30548 let second_cancels = Arc::new(AtomicUsize::new(0));
30549 let second_drops = Arc::new(AtomicUsize::new(0));
30550 let second = Arc::new(PanickingCancelDropTimerDriver {
30551 cancels: Arc::clone(&second_cancels),
30552 drops: Arc::clone(&second_drops),
30553 });
30554 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30555 drop(second);
30556
30557 let live = registry.live();
30558 assert_eq!(live.len(), 2);
30559 registry.unregister(first_id);
30560 registry.unregister(second_id);
30561 drop(live);
30562
30563 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30564 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30565 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30566 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30567 assert!(
30568 registry
30569 .state
30570 .lock()
30571 .unwrap_or_else(std::sync::PoisonError::into_inner)
30572 .entries
30573 .is_empty()
30574 );
30575 return;
30576 }
30577
30578 let output = std::process::Command::new(std::env::current_exe().unwrap())
30579 .arg("--exact")
30580 .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
30581 .arg("--nocapture")
30582 .env(CHILD_ENV, "1")
30583 .output()
30584 .expect("the hostile live timer-driver snapshot subprocess test must start");
30585 assert!(
30586 output.status.success(),
30587 "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30588 output.status.code(),
30589 String::from_utf8_lossy(&output.stdout),
30590 String::from_utf8_lossy(&output.stderr)
30591 );
30592 }
30593
30594 #[test]
30595 fn timer_driver_registry_drop_contains_each_driver_destructor() {
30596 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
30597
30598 if std::env::var_os(CHILD_ENV).is_some() {
30599 let first_drops = Arc::new(AtomicUsize::new(0));
30600 let second_drops = Arc::new(AtomicUsize::new(0));
30601 let registry = TimerDriverRegistry::default();
30602 let first = Arc::new(PanickingCancelDropTimerDriver {
30603 cancels: Arc::new(AtomicUsize::new(0)),
30604 drops: Arc::clone(&first_drops),
30605 });
30606 let second = Arc::new(PanickingCancelDropTimerDriver {
30607 cancels: Arc::new(AtomicUsize::new(0)),
30608 drops: Arc::clone(&second_drops),
30609 });
30610 registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30611 registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30612 drop(first);
30613 drop(second);
30614
30615 drop(registry);
30616
30617 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30618 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30619 return;
30620 }
30621
30622 let output = std::process::Command::new(std::env::current_exe().unwrap())
30623 .arg("--exact")
30624 .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
30625 .arg("--nocapture")
30626 .env(CHILD_ENV, "1")
30627 .output()
30628 .expect("the hostile timer-driver registry drop subprocess test must start");
30629 assert!(
30630 output.status.success(),
30631 "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
30632 output.status.code(),
30633 String::from_utf8_lossy(&output.stdout),
30634 String::from_utf8_lossy(&output.stderr)
30635 );
30636 }
30637
30638 #[test]
30639 fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
30640 const DRIVERS: usize = 512;
30641
30642 let metrics = Arc::new(RuntimeMetrics::default());
30643 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30644 let backend =
30645 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30646 let registry = Arc::new(TimerDriverRegistry::default());
30647 let drivers = (0..DRIVERS)
30648 .map(|_| {
30649 let driver = ManualStubDriver::new();
30650 registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30651 driver
30652 })
30653 .collect::<Vec<_>>();
30654 let mut sleep = make_sleep(
30655 &backend,
30656 ®istry,
30657 Instant::now() + Duration::from_secs(60),
30658 );
30659 let mut cx = Context::from_waker(Waker::noop());
30660
30661 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30662 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30663 let timer_id = drivers[0].registers.lock().unwrap()[0].0;
30664 match &sleep.inner {
30665 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
30666 #[cfg(not(target_family = "wasm"))]
30667 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30668 }
30669 for driver in &drivers {
30670 assert_eq!(
30671 driver.registers.lock().unwrap().len(),
30672 2,
30673 "a re-poll must refresh each live driver exactly once"
30674 );
30675 }
30676
30677 for driver in drivers.iter().step_by(2) {
30678 driver.kill();
30679 }
30680 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30681 match &sleep.inner {
30682 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
30683 #[cfg(not(target_family = "wasm"))]
30684 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30685 }
30686 for (index, driver) in drivers.iter().enumerate() {
30687 let registers = driver.registers.lock().unwrap().len();
30688 let cancels = driver.cancels.lock().unwrap().clone();
30689 if index % 2 == 0 {
30690 assert_eq!(registers, 2);
30691 assert_eq!(cancels, vec![timer_id]);
30692 } else {
30693 assert_eq!(registers, 3);
30694 assert!(cancels.is_empty());
30695 }
30696 }
30697
30698 drop(sleep);
30699 for driver in drivers {
30700 assert_eq!(
30701 driver.cancels.lock().unwrap().as_slice(),
30702 &[timer_id],
30703 "every physical arm must be cancelled exactly once"
30704 );
30705 }
30706 assert_eq!(registry.pending_len(), 0);
30707 assert!(!executor.host_timers.has_pending());
30708 }
30709
30710 #[test]
30711 fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
30712 let metrics = Arc::new(RuntimeMetrics::default());
30713 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30714 let backend =
30715 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30716 let healthy = ManualStubDriver::new();
30717 let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30718 let deadline = Instant::now() + Duration::from_secs(60);
30719 let mut sleep = make_sleep(&backend, ®istry, deadline);
30720 let mut cx = Context::from_waker(Waker::noop());
30721
30722 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30723 let timer_id = healthy.registers.lock().unwrap()[0].0;
30724
30725 let panicking = PanickingRegisterTimerDriver::new();
30726 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30727 let result = catch_unwind(AssertUnwindSafe(|| {
30728 let _ = Pin::new(&mut sleep).poll(&mut cx);
30729 }));
30730 assert!(
30731 result.is_err(),
30732 "the hostile register panic must retain its normal propagation"
30733 );
30734 assert_eq!(
30735 healthy.registers.lock().unwrap().len(),
30736 2,
30737 "the previously armed driver must have completed its refresh before the later panic"
30738 );
30739 assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
30740 assert!(
30741 panicking.pending.lock().unwrap().contains_key(&timer_id),
30742 "the hostile driver must model an arm committed before it panics"
30743 );
30744
30745 drop(sleep);
30746 assert_eq!(
30747 healthy.cancels.lock().unwrap().as_slice(),
30748 &[timer_id],
30749 "unwind must preserve cancellation ownership for the previously armed driver"
30750 );
30751 assert_eq!(
30752 panicking.cancels.lock().unwrap().as_slice(),
30753 &[timer_id],
30754 "unwind must preserve cancellation ownership for the driver that armed and then panicked"
30755 );
30756 assert!(panicking.pending.lock().unwrap().is_empty());
30757 assert_eq!(registry.pending_len(), 0);
30758 assert!(!executor.host_timers.has_pending());
30759 }
30760
30761 #[test]
30762 fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
30763 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
30764
30765 if std::env::var_os(CHILD_ENV).is_some() {
30766 let metrics = Arc::new(RuntimeMetrics::default());
30767 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30768 let backend =
30769 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30770 let registry = Arc::new(TimerDriverRegistry::default());
30771 let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
30772 registry: Arc::downgrade(®istry),
30773 later: Mutex::new(None),
30774 registers: AtomicUsize::new(0),
30775 });
30776 registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
30777
30778 let later_cancels = Arc::new(AtomicUsize::new(0));
30779 let later_drops = Arc::new(AtomicUsize::new(0));
30780 let later = Arc::new(PanickingCancelDropTimerDriver {
30781 cancels: Arc::clone(&later_cancels),
30782 drops: Arc::clone(&later_drops),
30783 });
30784 let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
30785 *earlier.later.lock().unwrap() = Some(later_id);
30786 drop(later);
30787
30788 let mut sleep = make_sleep(
30789 &backend,
30790 ®istry,
30791 Instant::now() + Duration::from_secs(60),
30792 );
30793 let mut cx = Context::from_waker(Waker::noop());
30794 let result = catch_unwind(AssertUnwindSafe(|| {
30795 let _ = Pin::new(&mut sleep).poll(&mut cx);
30796 }));
30797
30798 assert!(
30799 result.is_err(),
30800 "the earlier driver's register panic must still propagate"
30801 );
30802 assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
30803 assert_eq!(
30804 later_drops.load(Ordering::SeqCst),
30805 0,
30806 "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
30807 );
30808
30809 drop(sleep);
30810 assert_eq!(
30811 later_cancels.load(Ordering::SeqCst),
30812 1,
30813 "the later driver must remain owned for cancellation despite its registry removal"
30814 );
30815 assert_eq!(
30816 later_drops.load(Ordering::SeqCst),
30817 1,
30818 "the later driver's final destructor must run once behind containment"
30819 );
30820 assert_eq!(registry.pending_len(), 0);
30821 assert!(!executor.host_timers.has_pending());
30822 return;
30823 }
30824
30825 let output = std::process::Command::new(std::env::current_exe().unwrap())
30826 .arg("--exact")
30827 .arg(
30828 "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
30829 )
30830 .arg("--nocapture")
30831 .env(CHILD_ENV, "1")
30832 .output()
30833 .expect("the hostile host timer snapshot subprocess test must start");
30834 assert!(
30835 output.status.success(),
30836 "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
30837 output.status.code(),
30838 String::from_utf8_lossy(&output.stdout),
30839 String::from_utf8_lossy(&output.stderr)
30840 );
30841 }
30842
30843 #[test]
30844 fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
30845 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
30846
30847 if std::env::var_os(CHILD_ENV).is_some() {
30848 let metrics = Arc::new(RuntimeMetrics::default());
30849 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30850 let backend =
30851 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30852 let cancels = Arc::new(AtomicUsize::new(0));
30853 let drops = Arc::new(AtomicUsize::new(0));
30854 let hostile = Arc::new(PanickingCancelDropTimerDriver {
30855 cancels: Arc::clone(&cancels),
30856 drops: Arc::clone(&drops),
30857 });
30858 let healthy = ManualStubDriver::new();
30859 let registry = Arc::new(TimerDriverRegistry::default());
30860 let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
30861 registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30862 let mut sleep = make_sleep(
30863 &backend,
30864 ®istry,
30865 Instant::now() + Duration::from_secs(60),
30866 );
30867 let mut cx = Context::from_waker(Waker::noop());
30868
30869 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30870 let timer_id = healthy.registers.lock().unwrap()[0].0;
30871
30872 registry.unregister(hostile_id);
30875 drop(hostile);
30876 drop(sleep);
30877 assert_eq!(cancels.load(Ordering::SeqCst), 1);
30878 assert_eq!(drops.load(Ordering::SeqCst), 1);
30879 assert_eq!(
30880 healthy.cancels.lock().unwrap().as_slice(),
30881 &[timer_id],
30882 "one hostile cancellation must not skip later armed drivers"
30883 );
30884 assert_eq!(registry.pending_len(), 0);
30885 assert!(!executor.host_timers.has_pending());
30886 return;
30887 }
30888
30889 let output = std::process::Command::new(std::env::current_exe().unwrap())
30890 .arg("--exact")
30891 .arg(
30892 "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
30893 )
30894 .arg("--nocapture")
30895 .env(CHILD_ENV, "1")
30896 .output()
30897 .expect("the hostile timer-driver cancellation subprocess test must start");
30898 assert!(
30899 output.status.success(),
30900 "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
30901 output.status.code(),
30902 String::from_utf8_lossy(&output.stdout),
30903 String::from_utf8_lossy(&output.stderr)
30904 );
30905 }
30906
30907 #[test]
30908 fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
30909 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
30910
30911 if std::env::var_os(CHILD_ENV).is_some() {
30912 let metrics = Arc::new(RuntimeMetrics::default());
30913 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30914 let backend =
30915 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30916 let driver = ManualStubDriver::new();
30917 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30918 let state = Arc::new(TestHostileRawWakerState {
30919 clones: AtomicUsize::new(0),
30920 drop_panics: AtomicUsize::new(0),
30921 panic_on_clone: Some(2),
30922 });
30923
30924 let result = catch_unwind(AssertUnwindSafe(|| {
30925 let mut sleep = make_sleep(
30926 &backend,
30927 ®istry,
30928 Instant::now() + Duration::from_secs(60),
30929 );
30930 let waker = test_hostile_waker(Arc::clone(&state));
30931 let mut cx = Context::from_waker(&waker);
30932 let _ = Pin::new(&mut sleep).poll(&mut cx);
30933 }));
30934 assert!(
30935 result.is_err(),
30936 "the original RawWaker clone panic must still propagate"
30937 );
30938 assert_eq!(state.clones.load(Ordering::SeqCst), 2);
30939 assert_eq!(
30940 state.drop_panics.load(Ordering::SeqCst),
30941 1,
30942 "the first clone must be destroyed once behind a contained boundary"
30943 );
30944 assert!(driver.registers.lock().unwrap().is_empty());
30945 assert_eq!(registry.pending_len(), 0);
30946 assert!(!executor.host_timers.has_pending());
30947 return;
30948 }
30949
30950 let output = std::process::Command::new(std::env::current_exe().unwrap())
30951 .arg("--exact")
30952 .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
30953 .arg("--nocapture")
30954 .env(CHILD_ENV, "1")
30955 .output()
30956 .expect("the hostile RawWaker subprocess test must start");
30957 assert!(
30958 output.status.success(),
30959 "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
30960 output.status.code(),
30961 String::from_utf8_lossy(&output.stdout),
30962 String::from_utf8_lossy(&output.stderr)
30963 );
30964 }
30965
30966 #[test]
30967 fn host_sleep_register_unwind_contains_unretained_waker_drop() {
30968 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
30969
30970 if std::env::var_os(CHILD_ENV).is_some() {
30971 let metrics = Arc::new(RuntimeMetrics::default());
30972 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30973 let backend =
30974 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30975 let driver = Arc::new(PanicBeforeRetainingTimerDriver {
30976 registers: AtomicUsize::new(0),
30977 });
30978 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30979 let state = Arc::new(TestHostileRawWakerState {
30980 clones: AtomicUsize::new(0),
30981 drop_panics: AtomicUsize::new(0),
30982 panic_on_clone: None,
30983 });
30984 let mut sleep = make_sleep(
30985 &backend,
30986 ®istry,
30987 Instant::now() + Duration::from_secs(60),
30988 );
30989 let waker = test_hostile_waker(Arc::clone(&state));
30990 let mut cx = Context::from_waker(&waker);
30991
30992 let result = catch_unwind(AssertUnwindSafe(|| {
30993 let _ = Pin::new(&mut sleep).poll(&mut cx);
30994 }));
30995 assert!(
30996 result.is_err(),
30997 "the timer-driver register panic must still propagate"
30998 );
30999 assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
31000 assert_eq!(state.clones.load(Ordering::SeqCst), 3);
31001 assert_eq!(
31002 state.drop_panics.load(Ordering::SeqCst),
31003 1,
31004 "the unretained register argument must be destroyed once behind the proxy boundary"
31005 );
31006 assert_eq!(registry.pending_len(), 1);
31007 assert!(executor.host_timers.has_pending());
31008
31009 drop(sleep);
31010 assert_eq!(
31011 state.drop_panics.load(Ordering::SeqCst),
31012 3,
31013 "repoll and registry wakers must retain their existing contained cleanup"
31014 );
31015 assert_eq!(registry.pending_len(), 0);
31016 assert!(!executor.host_timers.has_pending());
31017 return;
31018 }
31019
31020 let output = std::process::Command::new(std::env::current_exe().unwrap())
31021 .arg("--exact")
31022 .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
31023 .arg("--nocapture")
31024 .env(CHILD_ENV, "1")
31025 .output()
31026 .expect("the hostile register-waker subprocess test must start");
31027 assert!(
31028 output.status.success(),
31029 "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
31030 output.status.code(),
31031 String::from_utf8_lossy(&output.stdout),
31032 String::from_utf8_lossy(&output.stderr)
31033 );
31034 }
31035
31036 #[test]
31037 fn host_sleep_rearms_on_next_live_driver_after_eviction() {
31038 let metrics = Arc::new(RuntimeMetrics::default());
31044 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31045 let backend =
31046 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31047
31048 let a = ManualStubDriver::new();
31049 let b = ManualStubDriver::new();
31050 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31051 let deadline = Instant::now() + Duration::from_secs(60);
31052
31053 let (timer_id, b_id) = {
31054 let mut sleep = std::pin::pin!(make_sleep(&backend, ®istry, deadline));
31055 let mut cx = Context::from_waker(Waker::noop());
31056
31057 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31059 let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
31060 assert_eq!(armed_deadline, deadline);
31061 assert!(
31062 executor.host_timers.has_pending(),
31063 "first poll must arm the wake-token"
31064 );
31065
31066 a.kill();
31070 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31071 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31072 assert_eq!(
31073 b.registers.lock().unwrap().as_slice(),
31074 &[(timer_id, deadline)],
31075 "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
31076 );
31077 assert_eq!(
31078 a.cancels.lock().unwrap().as_slice(),
31079 &[timer_id],
31080 "the dead driver gets a best-effort cancel for the old arm"
31081 );
31082 assert_eq!(
31083 registry.current().map(|(id, _)| id),
31084 Some(b_id),
31085 "the dead driver was evicted"
31086 );
31087 assert!(
31088 executor.host_timers.has_pending(),
31089 "the wake-token survives the re-arm (still exactly this sleep's entry)"
31090 );
31091 (timer_id, b_id)
31092 };
31093
31094 let _ = b_id;
31097 assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
31098 assert!(
31099 !executor.host_timers.has_pending(),
31100 "drop must retire the wake-token"
31101 );
31102 }
31103
31104 #[test]
31105 fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
31106 let metrics = Arc::new(RuntimeMetrics::default());
31107 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31108 let backend =
31109 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31110 let responsive = Arc::new(StubHostTimerDriver::new());
31111 let starved = ManualStubDriver::new();
31112 let registry = Arc::new(TimerDriverRegistry::default());
31113 registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
31114 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31117
31118 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
31119 let waker = Waker::from(Arc::clone(&wake_count));
31120 let mut cx = Context::from_waker(&waker);
31121 let deadline = Instant::now() + Duration::from_millis(500);
31126 let mut sleep = make_sleep(&backend, ®istry, deadline);
31127 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31128
31129 let responsive_timer = *responsive
31130 .timers
31131 .lock()
31132 .unwrap()
31133 .keys()
31134 .next()
31135 .expect("responsive timer must be armed");
31136 assert_eq!(
31137 starved.registers.lock().unwrap().as_slice(),
31138 &[(responsive_timer, deadline)],
31139 "every live host must receive the same timer identity and absolute deadline"
31140 );
31141
31142 let fire_bound = Instant::now() + Duration::from_secs(30);
31149 while wake_count.0.load(Ordering::SeqCst) == 0 {
31150 assert!(
31151 Instant::now() < fire_bound,
31152 "the responsive host must wake a sleep whose newest host is live but starved"
31153 );
31154 std::thread::sleep(Duration::from_millis(10));
31155 }
31156 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31159 assert_eq!(
31160 starved.cancels.lock().unwrap().as_slice(),
31161 &[responsive_timer],
31162 "completion through the winning host must cancel the starved losing arm"
31163 );
31164 }
31165
31166 #[test]
31167 fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
31168 use std::sync::mpsc;
31169
31170 struct ReentrantTimerRegistrationWake {
31171 registry: Weak<TimerDriverRegistry>,
31172 wakes: AtomicUsize,
31173 reentered: AtomicBool,
31174 }
31175
31176 impl std::task::Wake for ReentrantTimerRegistrationWake {
31177 fn wake(self: Arc<Self>) {
31178 self.wakes.fetch_add(1, Ordering::SeqCst);
31179 if let Some(registry) = self.registry.upgrade() {
31180 let _ = registry.has_live_driver();
31181 self.reentered.store(true, Ordering::SeqCst);
31182 }
31183 }
31184 }
31185
31186 let metrics = Arc::new(RuntimeMetrics::default());
31187 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31188 let backend =
31189 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31190 let registry = Arc::new(TimerDriverRegistry::default());
31191 let starved = ManualStubDriver::new();
31192 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31193
31194 let deadline = Instant::now() + Duration::from_secs(2);
31201 let mut first = make_sleep(&backend, ®istry, deadline);
31202 let mut second = make_sleep(&backend, ®istry, deadline);
31203 let first_wake = Arc::new(ReentrantTimerRegistrationWake {
31204 registry: Arc::downgrade(®istry),
31205 wakes: AtomicUsize::new(0),
31206 reentered: AtomicBool::new(false),
31207 });
31208 let second_wake = Arc::new(ReentrantTimerRegistrationWake {
31209 registry: Arc::downgrade(®istry),
31210 wakes: AtomicUsize::new(0),
31211 reentered: AtomicBool::new(false),
31212 });
31213 let first_waker = Waker::from(Arc::clone(&first_wake));
31214 let second_waker = Waker::from(Arc::clone(&second_wake));
31215 let mut first_cx = Context::from_waker(&first_waker);
31216 let mut second_cx = Context::from_waker(&second_waker);
31217
31218 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31219 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31220 let initial = starved.registers.lock().unwrap().clone();
31221 let first_id = initial[0].0;
31222 let second_id = initial[1].0;
31223 assert_ne!(first_id, second_id);
31224 assert_eq!(
31225 registry.pending_len(),
31226 2,
31227 "one stable repoll entry is retained per sleep"
31228 );
31229
31230 let responsive = Arc::new(StubHostTimerDriver::new());
31231 let (registered_tx, registered_rx) = mpsc::sync_channel(1);
31232 let registration_registry = Arc::clone(®istry);
31233 let registration_driver = Arc::clone(&responsive);
31234 let registration = std::thread::spawn(move || {
31235 let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
31236 registered_tx.send(id).unwrap();
31237 });
31238 registered_rx
31242 .recv_timeout(Duration::from_secs(10))
31243 .expect("late driver registration or repoll callback re-entry deadlocked");
31244 registration.join().unwrap();
31245
31246 assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
31247 assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
31248 assert!(first_wake.reentered.load(Ordering::SeqCst));
31249 assert!(second_wake.reentered.load(Ordering::SeqCst));
31250 assert!(
31251 responsive.timers.lock().unwrap().is_empty(),
31252 "registration requests bounded re-polls instead of invoking driver callbacks inline"
31253 );
31254
31255 assert!(
31260 Instant::now() < deadline,
31261 "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
31262 this runner is starved beyond the margin this test budgets for"
31263 );
31264 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31265 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31266 let responsive_ids = responsive
31267 .timers
31268 .lock()
31269 .unwrap()
31270 .keys()
31271 .copied()
31272 .collect::<FxHashSet<_>>();
31273 assert_eq!(
31274 responsive_ids,
31275 FxHashSet::from_iter([first_id, second_id]),
31276 "every pending sleep must re-arm its existing identity on the new driver"
31277 );
31278 assert_eq!(
31279 starved
31280 .registers
31281 .lock()
31282 .unwrap()
31283 .iter()
31284 .filter(|(id, _)| *id == first_id)
31285 .count(),
31286 2,
31287 "the old host receives only the normal waker refresh on the one re-poll"
31288 );
31289 assert_eq!(
31290 starved
31291 .registers
31292 .lock()
31293 .unwrap()
31294 .iter()
31295 .filter(|(id, _)| *id == second_id)
31296 .count(),
31297 2,
31298 "the old host receives only the normal waker refresh on the one re-poll"
31299 );
31300 assert_eq!(
31301 registry.pending_len(),
31302 2,
31303 "late registration must not duplicate pending state"
31304 );
31305
31306 let fire_bound = Instant::now() + Duration::from_secs(30);
31313 while first_wake.wakes.load(Ordering::SeqCst) < 2
31314 || second_wake.wakes.load(Ordering::SeqCst) < 2
31315 {
31316 assert!(
31317 Instant::now() < fire_bound,
31318 "the late responsive host must fire both timers while the original host remains starved"
31319 );
31320 std::thread::sleep(Duration::from_millis(10));
31321 }
31322 assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
31323 assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
31324
31325 let mut cancelled = starved.cancels.lock().unwrap().clone();
31326 cancelled.sort_unstable();
31327 let mut expected = vec![first_id, second_id];
31328 expected.sort_unstable();
31329 assert_eq!(
31330 cancelled, expected,
31331 "completion must cancel both losing starved-host arms"
31332 );
31333 assert_eq!(
31334 registry.pending_len(),
31335 0,
31336 "completion retires every repoll entry"
31337 );
31338 assert!(!executor.host_timers.has_pending());
31339 }
31340
31341 #[test]
31342 fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
31343 let metrics = Arc::new(RuntimeMetrics::default());
31347 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31348 let backend =
31349 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31350
31351 let a = ManualStubDriver::new();
31352 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31353 let mut sleep = std::pin::pin!(make_sleep(
31354 &backend,
31355 ®istry,
31356 Instant::now() + Duration::from_secs(60)
31357 ));
31358 let mut cx = Context::from_waker(Waker::noop());
31359 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31360
31361 a.kill();
31362 let payload = catch_unwind(AssertUnwindSafe(|| {
31363 let _ = sleep.as_mut().poll(&mut cx);
31364 }))
31365 .expect_err("a sleep with every driver dead must panic, not pend silently");
31366 let message = payload
31367 .downcast_ref::<String>()
31368 .cloned()
31369 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
31370 .expect("panic payload must be a message");
31371 assert!(
31372 message.contains("lost every live timer driver mid-sleep"),
31373 "the diagnostic must name the mid-flight driver loss, got: {message}"
31374 );
31375 }
31376
31377 #[test]
31378 fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
31379 let metrics = Arc::new(RuntimeMetrics::default());
31386 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31387 let backend =
31388 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31389
31390 let a = ManualStubDriver::new();
31391 let b = ManualStubDriver::new();
31392 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31393 let mut sleep = std::pin::pin!(make_sleep(
31394 &backend,
31395 ®istry,
31396 Instant::now() + Duration::from_secs(60)
31397 ));
31398
31399 let flag = Arc::new(WakeFlag::default());
31401 let waker = Waker::from(Arc::clone(&flag));
31402 let mut cx = Context::from_waker(&waker);
31403 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31404 assert!(!flag.0.load(Ordering::SeqCst));
31405
31406 a.kill();
31409 registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31410 let _ = registry.has_live_driver();
31411
31412 assert!(
31413 a.swept.load(Ordering::SeqCst),
31414 "the sweep must notify the swept driver"
31415 );
31416 assert!(
31417 flag.0.load(Ordering::SeqCst),
31418 "a sleep armed on the swept driver must be woken into re-selection, not stranded"
31419 );
31420
31421 assert!(sleep.as_mut().poll(&mut cx).is_pending());
31423 assert_eq!(
31424 b.registers.lock().unwrap().len(),
31425 1,
31426 "the re-poll must re-arm on the live driver"
31427 );
31428 }
31429
31430 #[test]
31431 fn current_thread_sleep_fires_through_stub_host_driver() {
31432 use std::sync::mpsc;
31435
31436 let (done_tx, done_rx) = mpsc::channel();
31437 let runner = std::thread::spawn(move || {
31438 let metrics = Arc::new(RuntimeMetrics::default());
31439 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31440 let backend =
31441 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31442 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31443
31444 let started = Instant::now();
31445 let mut future = std::pin::pin!(async {
31446 make_sleep(
31447 &backend,
31448 ®istry_with(driver),
31449 Instant::now() + Duration::from_millis(60),
31450 )
31451 .await;
31452 });
31453 executor.block_on(future.as_mut());
31454 assert!(started.elapsed() >= Duration::from_millis(60));
31455 assert!(
31456 !executor.host_timers.has_pending(),
31457 "a completed sleep must retire its pending-registry entry"
31458 );
31459 let _ = done_tx.send(());
31460 });
31461
31462 match done_rx.recv_timeout(Duration::from_secs(10)) {
31463 Ok(()) => runner.join().unwrap(),
31464 Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
31465 }
31466 }
31467
31468 #[test]
31469 fn current_thread_timer_shutdown_retires_pending_repoll_state() {
31470 let metrics = Arc::new(RuntimeMetrics::default());
31471 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31472 let backend =
31473 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31474 let drivers = Arc::new(TimerDriverRegistry::default());
31475 let driver = ManualStubDriver::new();
31476 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31477 let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
31478 let mut cx = Context::from_waker(Waker::noop());
31479
31480 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31481 let timer_id = driver.registers.lock().unwrap()[0].0;
31482 assert_eq!(drivers.pending_len(), 1);
31483
31484 executor.host_timers.shutdown();
31485 assert_eq!(
31486 drivers.pending_len(),
31487 0,
31488 "shutdown must retire the registration-repoll index before the Sleep is dropped"
31489 );
31490 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31491 assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
31492 }
31493
31494 #[test]
31495 fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
31496 use std::sync::mpsc;
31497
31498 let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
31499 let backend = controller.backend();
31500 let drivers = Arc::new(TimerDriverRegistry::default());
31501 let driver = ManualStubDriver::new();
31502 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31503 let sleep = make_sleep(
31504 &backend,
31505 &drivers,
31506 Instant::now() + Duration::from_secs(120),
31507 );
31508 drop(backend);
31509
31510 let (polled_tx, polled_rx) = mpsc::channel();
31511 let block_controller = Arc::clone(&controller);
31512 let blocker = std::thread::spawn(move || {
31513 let mut announced = false;
31514 let mut sleep = std::pin::pin!(sleep);
31515 let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
31516 let poll = sleep.as_mut().poll(cx);
31517 if !announced {
31518 announced = true;
31519 polled_tx.send(()).unwrap();
31520 }
31521 poll
31522 }));
31523 block_controller.block_on(future.as_mut());
31524 });
31525 polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
31526
31527 let (shutdown_tx, shutdown_rx) = mpsc::channel();
31528 let shutdown_controller = Arc::clone(&controller);
31529 let shutdown = std::thread::spawn(move || {
31530 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
31531 });
31532 shutdown_rx
31533 .recv_timeout(Duration::from_secs(2))
31534 .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
31535 .unwrap();
31536 shutdown.join().unwrap();
31537 blocker.join().unwrap();
31538 assert_eq!(driver.cancels.lock().unwrap().len(), 1);
31539 }
31540
31541 #[test]
31542 fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
31543 use std::sync::mpsc;
31554
31555 let (done_tx, done_rx) = mpsc::channel();
31556 let runner = std::thread::spawn(move || {
31557 let metrics = Arc::new(RuntimeMetrics::default());
31558 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31562 let backend =
31563 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31564 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31565
31566 let payload = catch_unwind(AssertUnwindSafe(|| {
31567 let mut future = std::pin::pin!(async {
31568 make_sleep(
31569 &backend,
31570 ®istry_with(driver),
31571 Instant::now() + Duration::from_millis(60),
31572 )
31573 .await;
31574 });
31575 executor.block_on(future.as_mut());
31576 }))
31577 .expect_err("a threadless park must self-detect even while a host timer is pending");
31578 let diagnostic = payload
31579 .downcast_ref::<BlockOnDeadlock>()
31580 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31581 assert_eq!(
31582 diagnostic.kind,
31583 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
31584 );
31585 assert_eq!(
31586 diagnostic.park_deadline, None,
31587 "the certain case carries no deadline"
31588 );
31589 let message = diagnostic.to_string();
31590 assert!(
31591 message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
31592 "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
31593 );
31594 let _ = done_tx.send(());
31595 });
31596
31597 match done_rx.recv_timeout(Duration::from_secs(10)) {
31598 Ok(()) => runner.join().unwrap(),
31599 Err(error) => panic!(
31600 "a pending host timer suppressed the threadless certain-deadlock diagnostic -- block_on parked (or completed via a helper-thread fire that cannot exist on the real target) instead of panicking ({error})"
31601 ),
31602 }
31603 }
31604
31605 #[test]
31606 fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
31607 use std::sync::mpsc;
31616
31617 let (done_tx, done_rx) = mpsc::channel();
31618 let runner = std::thread::spawn(move || {
31619 let metrics = Arc::new(RuntimeMetrics::default());
31620 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31621 let backend =
31622 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31623 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31624
31625 let _unpolled = make_sleep(
31628 &backend,
31629 ®istry_with(driver),
31630 Instant::now() + Duration::from_secs(60),
31631 );
31632
31633 let payload = catch_unwind(AssertUnwindSafe(|| {
31634 let mut future = std::pin::pin!(NeverReady);
31635 executor.block_on(future.as_mut());
31636 }))
31637 .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
31638 let diagnostic = payload
31639 .downcast_ref::<BlockOnDeadlock>()
31640 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31641 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
31642 let _ = done_tx.send(());
31643 });
31644
31645 match done_rx.recv_timeout(Duration::from_secs(10)) {
31646 Ok(()) => runner.join().unwrap(),
31647 Err(error) => panic!(
31648 "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
31649 ),
31650 }
31651 }
31652
31653 #[cfg(not(target_family = "wasm"))]
31654 #[test]
31655 fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
31656 use std::sync::mpsc;
31661
31662 let (done_tx, done_rx) = mpsc::channel();
31663 let runner = std::thread::spawn(move || {
31664 let metrics = Arc::new(RuntimeMetrics::default());
31665 let executor = Arc::new(CurrentThreadExecutor::with_detection(
31677 metrics,
31678 false,
31679 Some(Duration::from_secs(2)),
31680 ));
31681 let backend =
31682 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31683 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31684
31685 let started = Instant::now();
31686 let mut future = std::pin::pin!(async {
31687 make_sleep(
31688 &backend,
31689 ®istry_with(driver),
31690 Instant::now() + Duration::from_millis(200),
31691 )
31692 .await;
31693 });
31694 executor.block_on(future.as_mut());
31695 assert!(started.elapsed() >= Duration::from_millis(200));
31696 let _ = done_tx.send(());
31697 });
31698
31699 match done_rx.recv_timeout(Duration::from_secs(30)) {
31700 Ok(()) => runner.join().unwrap(),
31701 Err(error) => {
31702 panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
31703 }
31704 }
31705 }
31706
31707 #[test]
31708 fn host_timer_unrepresentable_grace_remains_live() {
31709 let registry = HostTimerRegistry::default();
31710 let fired = Arc::new(AtomicBool::new(false));
31711 let drivers = Arc::new(TimerDriverRegistry::default());
31712 let repoll = Arc::new(HostTimerRepoll::default());
31713 assert!(
31714 Instant::now().checked_add(Duration::MAX).is_none(),
31715 "Duration::MAX must exercise the unrepresentable-grace path"
31716 );
31717 assert!(
31718 registry.register(
31719 1,
31720 Instant::now(),
31721 futures::task::noop_waker(),
31722 &fired,
31723 &drivers,
31724 &repoll,
31725 ),
31726 "the open registry must accept the timer"
31727 );
31728
31729 assert!(
31730 registry.has_live_wait(Duration::MAX),
31731 "an unrepresentable grace deadline is effectively unbounded"
31732 );
31733 registry.remove(1);
31734 }
31735
31736 #[test]
31737 fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
31738 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
31743 let expected = diagnostic.to_string();
31744 let payload: Box<dyn Any + Send> = Box::new(diagnostic);
31745 assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
31746 }
31747}