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, LazyLock, Weak,
14 atomic::{AtomicBool, AtomicU32, 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;
24use crate::sync::{BoundedWaits, Condvar, Mutex, MutexGuard, UnboundedCondvar};
25#[cfg(all(test, napi_runtime_os_threads))]
27use async_task::Task;
28use async_task::{FallibleTask, Runnable};
29use futures::{
30 FutureExt,
31 future::{AbortHandle, AbortRegistration, Abortable},
32};
33
34#[cfg(napi_runtime_os_threads)]
35use crossbeam_deque::{Injector, Steal};
36#[cfg(napi_runtime_os_threads)]
37use futures::channel::oneshot;
38#[cfg(all(test, napi_runtime_os_threads))]
39use rayon::max_num_threads;
40#[cfg(napi_runtime_os_threads)]
41use rayon::{ThreadPool, ThreadPoolBuilder};
42
43fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
46 counter
47 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
48 .expect(exhausted)
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum RuntimeFlavor {
53 CurrentThread,
54 MultiThread,
55}
56
57#[derive(Debug, Clone)]
58pub struct RuntimeOptions {
59 pub flavor: RuntimeFlavor,
60 pub worker_threads: usize,
78 pub max_blocking_tasks: usize,
79 pub thread_name_prefix: String,
80 pub park_deadline: Option<Duration>,
93 pub drain_linger: Duration,
108}
109
110#[derive(Debug, Clone, Copy, Default)]
117pub struct RuntimeOptionsPatch {
118 pub flavor: Option<RuntimeFlavor>,
119 pub worker_threads: Option<usize>,
120 pub max_blocking_tasks: Option<usize>,
121 pub drain_linger: Option<Duration>,
124}
125
126impl RuntimeOptionsPatch {
127 fn apply_to(self, options: &mut RuntimeOptions) {
128 if let Some(flavor) = self.flavor {
129 options.flavor = flavor;
130 }
131 if let Some(worker_threads) = self.worker_threads {
132 options.worker_threads = worker_threads;
133 }
134 if let Some(max_blocking_tasks) = self.max_blocking_tasks {
135 options.max_blocking_tasks = max_blocking_tasks;
136 }
137 if let Some(drain_linger) = self.drain_linger {
138 options.drain_linger = drain_linger;
139 }
140 }
141}
142
143impl Default for RuntimeOptions {
144 fn default() -> Self {
145 let worker_threads = std::thread::available_parallelism()
146 .map_or(1, usize::from)
147 .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
148 Self {
149 flavor: if cfg!(target_family = "wasm") {
150 RuntimeFlavor::CurrentThread
151 } else {
152 RuntimeFlavor::MultiThread
153 },
154 worker_threads,
155 max_blocking_tasks: worker_threads,
156 thread_name_prefix: "napi-async-runtime".to_string(),
157 park_deadline: None,
158 drain_linger: DEFAULT_DRAIN_LINGER,
159 }
160 }
161}
162
163impl RuntimeOptions {
164 fn validate(self) -> Result<Self, RuntimeConfigError> {
165 #[cfg(not(napi_runtime_os_threads))]
166 let rayon_max_threads = None;
167 #[cfg(napi_runtime_os_threads)]
168 let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
169 self.validate_with_rayon_max_threads(rayon_max_threads)
170 }
171
172 fn validate_with_rayon_max_threads(
173 mut self,
174 rayon_max_threads: Option<usize>,
175 ) -> Result<Self, RuntimeConfigError> {
176 if self.worker_threads == 0 {
177 return Err(RuntimeConfigError(
178 "worker_threads must be greater than zero".to_string(),
179 ));
180 }
181 if self.max_blocking_tasks == 0 {
182 return Err(RuntimeConfigError(
183 "max_blocking_tasks must be greater than zero".to_string(),
184 ));
185 }
186 self.drain_linger = self.drain_linger.min(MAX_DRAIN_LINGER);
193 if self.flavor == RuntimeFlavor::CurrentThread {
194 self.worker_threads = 1;
195 self.max_blocking_tasks = 1;
196 } else {
197 let Some(rayon_max_threads) = rayon_max_threads else {
198 return Err(RuntimeConfigError(
199 "the multi-thread runtime is unavailable in this threadless WebAssembly build \
200 (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
201 .to_string(),
202 ));
203 };
204 if rayon_max_threads < 2 {
205 return Err(RuntimeConfigError(
206 "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
207 ));
208 }
209 self.worker_threads = self
217 .worker_threads
218 .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
219 .min(rayon_max_threads);
220 let blocking_capacity = self.worker_threads - 1;
221 self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
222 }
223 Ok(self)
224 }
225}
226
227pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
236
237pub const DRAIN_LINGER_ENV: &str = "NAPI_RUNTIME_DRAIN_LINGER_US";
261
262pub const DEFAULT_DRAIN_LINGER_MICROS: u64 = 500;
266
267pub const MAX_DRAIN_LINGER_MICROS: u64 = 100_000;
274
275const DEFAULT_DRAIN_LINGER: Duration = Duration::from_micros(DEFAULT_DRAIN_LINGER_MICROS);
277
278const MAX_DRAIN_LINGER: Duration = Duration::from_micros(MAX_DRAIN_LINGER_MICROS);
280
281#[cfg(napi_runtime_os_threads)]
295const DRAIN_LINGER_FRAME_FACTOR: u32 = 16;
296
297#[cfg(napi_runtime_os_threads)]
300const MAX_DRAIN_LINGER_FRAME_RESIDENCE: Duration = Duration::from_secs(60);
301
302const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
318
319#[cfg(napi_runtime_wasi_threads)]
353#[inline(always)]
354fn on_thread_handoff() {
355 napi_sys::wasi_heap_sync::refresh_if_behind();
356}
357
358#[cfg(not(napi_runtime_wasi_threads))]
360#[inline(always)]
361fn on_thread_handoff() {}
362
363#[derive(Debug, Clone)]
364pub struct RuntimeConfigError(String);
365
366impl fmt::Display for RuntimeConfigError {
367 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368 f.write_str(&self.0)
369 }
370}
371
372impl std::error::Error for RuntimeConfigError {}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub enum BlockOnDeadlockKind {
377 CurrentThreadCertain,
382 CurrentThreadCertainTimerBacked,
389 CurrentThreadDeadline,
392 MultiThreadCooperativeDeadline,
396}
397
398#[derive(Debug, Clone)]
406pub struct BlockOnDeadlock {
407 pub kind: BlockOnDeadlockKind,
408 pub park_deadline: Option<Duration>,
411}
412
413impl BlockOnDeadlock {
414 fn current_thread_certain() -> Self {
415 Self {
416 kind: BlockOnDeadlockKind::CurrentThreadCertain,
417 park_deadline: None,
418 }
419 }
420
421 fn current_thread_certain_timer_backed() -> Self {
422 Self {
423 kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
424 park_deadline: None,
425 }
426 }
427
428 fn current_thread_deadline(deadline: Duration) -> Self {
429 Self {
430 kind: BlockOnDeadlockKind::CurrentThreadDeadline,
431 park_deadline: Some(deadline),
432 }
433 }
434
435 #[cfg(napi_runtime_os_threads)]
438 fn multi_thread_cooperative(deadline: Duration) -> Self {
439 Self {
440 kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
441 park_deadline: Some(deadline),
442 }
443 }
444}
445
446impl fmt::Display for BlockOnDeadlock {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 let deadline_ms = self
449 .park_deadline
450 .map_or(0, |deadline| deadline.as_millis());
451 match self.kind {
452 BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
453 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
454 park with an empty task queue and no pending wake on a threadless build -- no other \
455 thread exists that could ever deliver the wake, so this park would never end. This is \
456 the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
457 (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
458 dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
459 continuation is the one now parking.",
460 ),
461 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
462 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
463 park with an empty task queue on a threadless build while a host timer is pending. \
464 The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
465 only run on the very thread that is now parking, so it can never fire, and no other \
466 thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
467 hazard: native code called `block_on` on a future that (transitively) awaits a JS \
468 continuation, but the only thread that could run that continuation is the one now \
469 parking.",
470 ),
471 BlockOnDeadlockKind::CurrentThreadDeadline => write!(
472 f,
473 "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
474 {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
475 task queue, no pending wake and ZERO runtime progress across the whole window. This is \
476 the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
477 awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
478 once this thread returns to the event loop. If this park is legitimate, raise or unset \
479 the deadline.",
480 ),
481 BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
482 f,
483 "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
484 parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
485 no runnable or blocking work available and ZERO executor progress across the whole \
486 window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
487 The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
488 If this park is legitimate, raise or unset the deadline.",
489 ),
490 }
491 }
492}
493
494impl std::error::Error for BlockOnDeadlock {}
495
496#[derive(Debug)]
497pub struct JoinError {
498 message: String,
499}
500
501impl JoinError {
502 fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
503 Self {
504 message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
508 deadlock.to_string()
509 } else if let Some(message) = panic.downcast_ref::<&str>() {
510 (*message).to_string()
511 } else if let Some(message) = panic.downcast_ref::<String>() {
512 message.clone()
513 } else {
514 "async runtime task panicked".to_string()
515 },
516 }
517 }
518}
519
520impl fmt::Display for JoinError {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 f.write_str(&self.message)
523 }
524}
525
526impl std::error::Error for JoinError {}
527
528#[derive(Debug, Clone, Copy, PartialEq, Eq)]
529enum BlockOnOutcome {
530 Completed,
531 Stopped,
532}
533
534fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
535 if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
540 std::mem::forget(nested_payload);
541 }
542}
543
544#[derive(Default)]
545struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
546
547impl RetainedPanicPayload {
548 fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
549 if self.0.is_some() {
550 discard_panic_payload(payload);
551 } else {
552 self.0 = Some(payload);
553 }
554 }
555
556 fn is_some(&self) -> bool {
557 self.0.is_some()
558 }
559
560 fn resume(mut self) -> ! {
561 resume_unwind(
562 self
563 .0
564 .take()
565 .expect("a retained panic payload must remain owned"),
566 )
567 }
568}
569
570impl Drop for RetainedPanicPayload {
571 fn drop(&mut self) {
572 if let Some(payload) = self.0.take() {
573 discard_panic_payload(payload);
574 }
575 }
576}
577
578fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
579 match catch_unwind(AssertUnwindSafe(function)) {
580 Ok(value) => Some(value),
581 Err(payload) => {
582 discard_panic_payload(payload);
583 None
584 }
585 }
586}
587
588struct ContainedArc<T: ?Sized> {
589 value: Option<Arc<T>>,
590}
591
592impl<T: ?Sized> ContainedArc<T> {
593 fn new(value: Arc<T>) -> Self {
594 Self { value: Some(value) }
595 }
596
597 fn get(&self) -> &T {
598 self
599 .value
600 .as_deref()
601 .expect("the contained Arc must remain owned")
602 }
603
604 #[cfg(test)]
605 fn into_inner(mut self) -> Arc<T> {
606 self
607 .value
608 .take()
609 .expect("the contained Arc must remain owned")
610 }
611}
612
613impl<T: ?Sized> Drop for ContainedArc<T> {
614 fn drop(&mut self) {
615 if let Some(value) = self.value.take() {
616 let _ = catch_unwind_contained(|| drop(value));
617 }
618 }
619}
620
621struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
622 values: Vec<(I, Arc<T>)>,
623}
624
625impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
626 fn new(values: Vec<(I, Arc<T>)>) -> Self {
627 Self { values }
628 }
629
630 fn as_slice(&self) -> &[(I, Arc<T>)] {
631 &self.values
632 }
633
634 fn len(&self) -> usize {
635 self.values.len()
636 }
637
638 fn is_empty(&self) -> bool {
639 self.values.is_empty()
640 }
641
642 fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
643 self
644 .values
645 .pop()
646 .map(|(id, value)| (id, ContainedArc::new(value)))
647 }
648}
649
650impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
651 fn drop(&mut self) {
652 while let Some((_, value)) = self.values.pop() {
653 let _ = catch_unwind_contained(|| drop(value));
654 }
655 }
656}
657
658struct PanicContainedWaker {
659 waker: ManuallyDrop<Waker>,
660 generation: Option<u64>,
661}
662
663impl std::task::Wake for PanicContainedWaker {
664 fn wake(self: Arc<Self>) {
665 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
666 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
667 }
668
669 fn wake_by_ref(self: &Arc<Self>) {
670 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
671 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
672 }
673}
674
675impl Drop for PanicContainedWaker {
676 fn drop(&mut self) {
677 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
678 let _ = catch_unwind_contained(|| {
679 unsafe { ManuallyDrop::drop(&mut self.waker) };
681 });
682 }
683}
684
685fn panic_contained_waker(waker: Waker) -> Waker {
686 Waker::from(Arc::new(PanicContainedWaker {
687 waker: ManuallyDrop::new(waker),
688 generation: active_runtime_generation(),
689 }))
690}
691
692struct PanicContainedWakerSource {
693 waker: ManuallyDrop<Waker>,
694 generation: Option<u64>,
695}
696
697impl PanicContainedWakerSource {
698 fn new(waker: Waker) -> Self {
699 Self {
700 waker: ManuallyDrop::new(waker),
701 generation: active_runtime_generation(),
702 }
703 }
704
705 fn as_waker(&self) -> &Waker {
706 &self.waker
707 }
708}
709
710impl Drop for PanicContainedWakerSource {
711 fn drop(&mut self) {
712 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
713 let _ = catch_unwind_contained(|| {
714 unsafe { ManuallyDrop::drop(&mut self.waker) };
716 });
717 }
718}
719
720struct PanicContainedWakerCache {
721 source: PanicContainedWakerSource,
722 contained: Waker,
723}
724
725impl PanicContainedWakerCache {
726 fn new(waker: &Waker) -> Self {
727 let generation = active_runtime_generation();
728 let _generation = RuntimeGenerationGuard::enter_optional(generation);
729 let source = PanicContainedWakerSource::new(waker.clone());
730 let contained = panic_contained_waker(source.as_waker().clone());
731 Self { source, contained }
732 }
733
734 fn update(&mut self, waker: &Waker) {
735 let will_wake = {
736 let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
737 self.source.as_waker().will_wake(waker)
738 };
739 if !will_wake {
740 *self = Self::new(waker);
741 }
742 }
743
744 fn contained(&self) -> &Waker {
745 &self.contained
746 }
747}
748
749fn wake_waker_contained(waker: Waker) {
750 let _ = catch_unwind_contained(|| waker.wake_by_ref());
755 let _ = catch_unwind_contained(|| drop(waker));
756}
757
758fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
759 match catch_unwind(AssertUnwindSafe(function)) {
760 Ok(value) => Ok(value),
761 Err(payload) => {
762 let error = JoinError::from_panic(&*payload);
763 discard_panic_payload(payload);
764 Err(error)
765 }
766 }
767}
768
769fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
770 let error = JoinError::from_panic(&*payload);
771 discard_panic_payload(payload);
772 error
773}
774
775struct ContainedTaskOutput<T> {
776 output: ManuallyDrop<T>,
777 generation: u64,
778}
779
780impl<T> ContainedTaskOutput<T> {
781 fn new(output: T, generation: u64) -> Self {
782 Self {
783 output: ManuallyDrop::new(output),
784 generation,
785 }
786 }
787
788 fn into_inner(mut self) -> T {
789 let output = unsafe { ManuallyDrop::take(&mut self.output) };
792 std::mem::forget(self);
793 output
794 }
795}
796
797impl<T> Drop for ContainedTaskOutput<T> {
798 fn drop(&mut self) {
799 let _generation = RuntimeGenerationGuard::enter(self.generation);
800 let _ = catch_unwind_contained(|| {
801 unsafe { ManuallyDrop::drop(&mut self.output) };
804 });
805 }
806}
807
808#[cfg(napi_runtime_os_threads)]
809#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
810struct BlockingJobId(u64);
811
812#[cfg(napi_runtime_os_threads)]
813static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
814
815#[cfg(all(test, napi_runtime_os_threads))]
816thread_local! {
817 static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
818 const { std::cell::RefCell::new(None) };
819 static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
820 const { std::cell::RefCell::new(None) };
821 static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
822 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
823 const { std::cell::RefCell::new(None) };
824 static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
825 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
826 const { std::cell::RefCell::new(None) };
827 static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
828 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
829 const { std::cell::RefCell::new(None) };
830 static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
831 const { std::cell::RefCell::new(None) };
832 static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
833 const { std::cell::RefCell::new(None) };
834 static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
835 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
836 const { std::cell::RefCell::new(None) };
837 static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
838 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
839 const { std::cell::RefCell::new(None) };
840 static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
841 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
842 const { std::cell::RefCell::new(None) };
843 static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
844 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
845 const { std::cell::RefCell::new(None) };
846 static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
847 const { std::cell::RefCell::new(None) };
848 static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
849 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
850 const { std::cell::RefCell::new(None) };
851 static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
852 const { std::cell::RefCell::new(None) };
853}
854
855#[cfg(all(test, napi_runtime_os_threads))]
856struct WorkerTlsDropProbe {
857 action: Option<WorkerTlsDropAction>,
858}
859
860#[cfg(all(test, napi_runtime_os_threads))]
874#[cfg_attr(target_family = "wasm", allow(dead_code))]
877enum WorkerTlsDropAction {
878 Record { completed: Arc<AtomicUsize> },
882 ReenterLifecycle {
883 controller: Arc<RuntimeController>,
884 completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
885 },
886}
887
888#[cfg(all(test, napi_runtime_os_threads))]
889impl Drop for WorkerTlsDropProbe {
890 fn drop(&mut self) {
891 match self
892 .action
893 .take()
894 .expect("worker TLS drop probe action already consumed")
895 {
896 WorkerTlsDropAction::Record { completed } => {
897 completed.fetch_add(1, Ordering::SeqCst);
898 }
899 WorkerTlsDropAction::ReenterLifecycle {
904 controller,
905 completed,
906 } => {
907 let start = controller.start().map_err(|error| error.to_string());
908 let shutdown = controller.shutdown().map_err(|error| error.to_string());
909 let _ = completed.send((start, shutdown));
910 }
911 }
912 }
913}
914
915#[cfg(all(test, napi_runtime_os_threads))]
923fn platform_runs_thread_local_destructors() -> bool {
924 struct Bump(Arc<AtomicUsize>);
925 impl Drop for Bump {
926 fn drop(&mut self) {
927 self.0.fetch_add(1, Ordering::SeqCst);
928 }
929 }
930 thread_local! {
931 static PROBE: std::cell::Cell<Option<Bump>> = const { std::cell::Cell::new(None) };
932 }
933 let ran = Arc::new(AtomicUsize::new(0));
934 let probe = Arc::clone(&ran);
935 std::thread::spawn(move || PROBE.with(|slot| slot.set(Some(Bump(probe)))))
936 .join()
937 .expect("TLS destructor probe thread must not panic");
938 ran.load(Ordering::SeqCst) == 1
939}
940
941#[cfg(all(test, napi_runtime_os_threads))]
942fn run_dependency_claim_test_hook() {
943 if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
944 hook();
945 }
946}
947
948#[cfg(all(test, napi_runtime_os_threads))]
949fn run_current_thread_dispatch_claim_test_hook() {
950 if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
951 {
952 hook();
953 }
954}
955
956#[cfg(all(test, napi_runtime_os_threads))]
957fn run_current_thread_dispatch_publication_blocked_test_hook() {
958 if let Some(hook) =
959 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
960 {
961 hook();
962 }
963}
964
965#[cfg(all(test, napi_runtime_os_threads))]
966fn run_current_thread_dispatch_recovery_test_hook() {
967 if let Some(hook) =
968 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
969 {
970 hook();
971 }
972}
973
974#[cfg(all(test, napi_runtime_os_threads))]
975fn run_current_thread_failed_publication_retired_test_hook() {
976 if let Some(hook) =
977 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
978 {
979 hook();
980 }
981}
982
983#[cfg(all(test, napi_runtime_os_threads))]
984fn run_current_thread_dispatch_call_test_hook() {
985 if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
986 hook();
987 }
988}
989
990#[cfg(all(test, napi_runtime_os_threads))]
991fn run_current_thread_dispatch_returned_test_hook() {
992 if let Some(hook) =
993 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
994 {
995 hook();
996 }
997}
998
999#[cfg(all(test, napi_runtime_os_threads))]
1000fn run_current_thread_blocking_claim_test_hook() {
1001 if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1002 {
1003 hook();
1004 }
1005}
1006
1007#[cfg(all(test, napi_runtime_os_threads))]
1008fn run_current_thread_runnable_claim_test_hook() {
1009 if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1010 {
1011 hook();
1012 }
1013}
1014
1015#[cfg(all(test, napi_runtime_os_threads))]
1016fn run_multi_thread_work_claimed_test_hook() {
1017 if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1018 hook();
1019 }
1020}
1021
1022#[cfg(all(test, napi_runtime_os_threads))]
1023fn run_multi_thread_work_started_test_hook() {
1024 if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1025 hook();
1026 }
1027}
1028
1029#[cfg(all(test, napi_runtime_os_threads))]
1030fn run_dependency_predicate_test_hook() {
1031 if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1032 hook();
1033 }
1034}
1035
1036#[cfg(all(test, napi_runtime_os_threads))]
1037fn run_before_owned_blocking_queue_claim_test_hook() {
1038 if let Some(hook) =
1039 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1040 {
1041 hook();
1042 }
1043}
1044
1045#[cfg(napi_runtime_os_threads)]
1046#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1047struct BlockingDependency {
1048 job: BlockingJobId,
1049 owner: Option<BlockingOwnerToken>,
1050}
1051
1052#[cfg(napi_runtime_os_threads)]
1053#[derive(Debug)]
1054struct DependencyClaim {
1055 claimed: AtomicBool,
1056 transition: Mutex<()>,
1057}
1058
1059#[cfg(napi_runtime_os_threads)]
1060impl DependencyClaim {
1061 fn new() -> Self {
1062 Self {
1063 claimed: AtomicBool::new(false),
1064 transition: Mutex::new(()),
1065 }
1066 }
1067
1068 fn is_live(&self) -> bool {
1069 !self.claimed.load(Ordering::Acquire)
1070 }
1071
1072 fn cancel_link(&self, link: &DependencyLink) {
1073 let _transition = self
1074 .transition
1075 .lock()
1076 .unwrap_or_else(std::sync::PoisonError::into_inner);
1077 link.cancel();
1078 }
1079
1080 fn try_claim(&self, link: &DependencyLink) -> bool {
1081 let _transition = self
1082 .transition
1083 .lock()
1084 .unwrap_or_else(std::sync::PoisonError::into_inner);
1085 #[cfg(test)]
1086 run_dependency_claim_test_hook();
1087 if self.claimed.load(Ordering::Acquire) || !link.is_live() {
1088 return false;
1089 }
1090 self.claimed.store(true, Ordering::Release);
1091 true
1092 }
1093}
1094
1095#[cfg(napi_runtime_os_threads)]
1096#[derive(Debug)]
1097struct DependencyLink {
1098 live: AtomicBool,
1099 parent: Option<Weak<DependencyLink>>,
1100}
1101
1102#[cfg(napi_runtime_os_threads)]
1103impl DependencyLink {
1104 fn root() -> Arc<Self> {
1105 Arc::new(Self {
1106 live: AtomicBool::new(true),
1107 parent: None,
1108 })
1109 }
1110
1111 fn derived(parent: &Arc<Self>) -> Arc<Self> {
1112 Arc::new(Self {
1113 live: AtomicBool::new(true),
1114 parent: Some(Arc::downgrade(parent)),
1115 })
1116 }
1117
1118 fn is_live(&self) -> bool {
1119 if !self.live.load(Ordering::Acquire) {
1120 return false;
1121 }
1122 let Some(parent) = &self.parent else {
1123 return true;
1124 };
1125 let Some(mut current) = parent.upgrade() else {
1126 return false;
1127 };
1128 loop {
1129 if !current.live.load(Ordering::Acquire) {
1130 return false;
1131 }
1132 let Some(parent) = ¤t.parent else {
1133 return true;
1134 };
1135 let Some(next) = parent.upgrade() else {
1136 return false;
1137 };
1138 current = next;
1139 }
1140 }
1141
1142 fn cancel(&self) {
1143 self.live.store(false, Ordering::Release);
1144 }
1145}
1146
1147#[cfg(napi_runtime_os_threads)]
1148#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1149struct DependencyPublicationIdentity {
1150 job: BlockingJobId,
1151 claim: *const DependencyClaim,
1152 link: *const DependencyLink,
1153}
1154
1155#[cfg(napi_runtime_os_threads)]
1156#[derive(Clone, Debug)]
1157struct DependencyPublication {
1158 dependency: BlockingDependency,
1159 claim: Arc<DependencyClaim>,
1160 link: Arc<DependencyLink>,
1161}
1162
1163#[cfg(napi_runtime_os_threads)]
1164impl DependencyPublication {
1165 fn owned(dependency: BlockingDependency) -> Self {
1166 Self {
1167 dependency,
1168 claim: Arc::new(DependencyClaim::new()),
1169 link: DependencyLink::root(),
1170 }
1171 }
1172
1173 fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1174 Self {
1175 dependency: BlockingDependency {
1176 owner: self.dependency.owner.or(owner),
1177 ..self.dependency
1178 },
1179 claim: Arc::clone(&self.claim),
1180 link: DependencyLink::derived(&self.link),
1181 }
1182 }
1183
1184 fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1185 self.dependency.owner = self.dependency.owner.or(owner);
1186 self
1187 }
1188
1189 fn same_as(&self, other: &Self) -> bool {
1190 self.dependency == other.dependency
1191 && Arc::ptr_eq(&self.claim, &other.claim)
1192 && Arc::ptr_eq(&self.link, &other.link)
1193 }
1194
1195 fn identity(&self) -> DependencyPublicationIdentity {
1196 DependencyPublicationIdentity {
1197 job: self.dependency.job,
1198 claim: Arc::as_ptr(&self.claim),
1199 link: Arc::as_ptr(&self.link),
1200 }
1201 }
1202
1203 fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1204 Some(DependencyPublicationIdentity {
1205 job: self.dependency.job,
1206 claim: Arc::as_ptr(&self.claim),
1207 link: self.link.parent.as_ref()?.as_ptr(),
1208 })
1209 }
1210
1211 fn is_derived_from(&self, source: &Self) -> bool {
1212 self.immediate_source_identity() == Some(source.identity())
1213 }
1214
1215 fn is_live(&self) -> bool {
1216 self.claim.is_live() && self.link.is_live()
1217 }
1218
1219 fn cancel(&self) {
1220 self.claim.cancel_link(&self.link);
1221 }
1222
1223 fn try_claim(&self) -> bool {
1224 self.claim.try_claim(&self.link)
1225 }
1226}
1227
1228#[cfg(napi_runtime_os_threads)]
1229struct TaskDependencyEntry {
1230 sequence: u64,
1231 publication: DependencyPublication,
1232}
1233
1234#[cfg(napi_runtime_os_threads)]
1235struct SelectedDependencyPublication {
1236 sequence: u64,
1237 publication: DependencyPublication,
1238}
1239
1240#[cfg(napi_runtime_os_threads)]
1241#[derive(Clone, Copy)]
1242enum OwnerDependencyMode {
1243 Available,
1244 Exact,
1245}
1246
1247#[cfg(napi_runtime_os_threads)]
1248#[derive(Default)]
1249struct TaskDependencyState {
1250 current: VecDeque<TaskDependencyEntry>,
1251 next_sequence: u64,
1252 unowned_sequences: VecDeque<u64>,
1253 owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1254 waiter: Option<Waker>,
1264}
1265
1266#[cfg(napi_runtime_os_threads)]
1267impl TaskDependencyState {
1268 fn append(&mut self, publication: DependencyPublication) -> u64 {
1271 let sequence = self.next_sequence;
1272 self.next_sequence = self
1273 .next_sequence
1274 .checked_add(1)
1275 .expect("task dependency sequence space exhausted");
1276 match publication.dependency.owner {
1277 Some(owner) => self
1278 .owner_sequences
1279 .entry(owner)
1280 .or_default()
1281 .push_back(sequence),
1282 None => self.unowned_sequences.push_back(sequence),
1283 }
1284 self.current.push_back(TaskDependencyEntry {
1285 sequence,
1286 publication,
1287 });
1288 sequence
1289 }
1290
1291 fn entry_in(
1292 current: &VecDeque<TaskDependencyEntry>,
1293 sequence: u64,
1294 ) -> Option<&TaskDependencyEntry> {
1295 let front = current.front()?.sequence;
1296 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1297 let entry = current.get(index)?;
1298 (entry.sequence == sequence).then_some(entry)
1299 }
1300
1301 fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1302 Self::entry_in(&self.current, sequence)
1303 }
1304
1305 fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1306 let front = self.current.front()?.sequence;
1307 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1308 let entry = self.current.get_mut(index)?;
1309 (entry.sequence == sequence).then_some(entry)
1310 }
1311
1312 fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1313 match dependency.owner {
1314 Some(owner) => {
1315 let remove_owner = self
1316 .owner_sequences
1317 .get_mut(&owner)
1318 .is_some_and(|sequences| {
1319 if sequences.front() == Some(&sequence) {
1320 sequences.pop_front();
1321 }
1322 sequences.is_empty()
1323 });
1324 if remove_owner {
1325 self.owner_sequences.remove(&owner);
1326 }
1327 }
1328 None => {
1329 if self.unowned_sequences.front() == Some(&sequence) {
1330 self.unowned_sequences.pop_front();
1331 }
1332 }
1333 }
1334 }
1335
1336 fn clear_indexes(&mut self) {
1337 self.unowned_sequences.clear();
1338 self.owner_sequences.clear();
1339 }
1340}
1341
1342#[cfg(napi_runtime_os_threads)]
1343struct TaskDependency {
1344 state: Mutex<TaskDependencyState>,
1345 owner_hint: Option<BlockingOwnerToken>,
1346 has_current: AtomicBool,
1347 generation: u64,
1348 #[cfg(test)]
1349 entry_visits: AtomicUsize,
1350}
1351
1352#[cfg(napi_runtime_os_threads)]
1353impl Default for TaskDependency {
1354 fn default() -> Self {
1355 Self::new(u64::MAX, None)
1356 }
1357}
1358
1359#[cfg(napi_runtime_os_threads)]
1360impl TaskDependency {
1361 fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1362 Self {
1363 state: Mutex::new(TaskDependencyState::default()),
1364 owner_hint,
1365 has_current: AtomicBool::new(false),
1366 generation,
1367 #[cfg(test)]
1368 entry_visits: AtomicUsize::new(0),
1369 }
1370 }
1371
1372 fn new_task(generation: u64) -> Self {
1373 Self::new(generation, None)
1374 }
1375
1376 fn notify_waiter(&self, waiter: Option<Waker>) {
1377 if let Some(waiter) = waiter {
1378 let _generation = RuntimeGenerationGuard::enter(self.generation);
1379 wake_waker_contained(waiter);
1380 }
1381 }
1382
1383 fn clear_waiter(&self) {
1384 let waiter = self
1385 .state
1386 .lock()
1387 .unwrap_or_else(std::sync::PoisonError::into_inner)
1388 .waiter
1389 .take();
1390 if let Some(waiter) = waiter {
1391 let _generation = RuntimeGenerationGuard::enter(self.generation);
1392 let _ = catch_unwind_contained(|| drop(waiter));
1393 }
1394 }
1395
1396 fn clear(&self) {
1397 let waiter = {
1398 let mut state = self
1399 .state
1400 .lock()
1401 .unwrap_or_else(std::sync::PoisonError::into_inner);
1402 if state.current.is_empty() {
1403 None
1404 } else {
1405 for current in state.current.drain(..) {
1406 current.publication.cancel();
1407 }
1408 state.clear_indexes();
1409 self.has_current.store(false, Ordering::Release);
1410 state.waiter.take()
1411 }
1412 };
1413 self.notify_waiter(waiter);
1414 }
1415
1416 fn retire(&self, sequence: u64) {
1426 let waiter = {
1427 let mut state = self
1428 .state
1429 .lock()
1430 .unwrap_or_else(std::sync::PoisonError::into_inner);
1431 let cancelled = match state.entry_mut(sequence) {
1432 Some(current) => {
1433 self.note_entry_visit();
1434 current.publication.cancel();
1435 true
1436 }
1437 None => false,
1438 };
1439 if cancelled {
1440 self.prune_stale_prefix(&mut state);
1441 state.waiter.take()
1442 } else {
1443 None
1444 }
1445 };
1446 self.notify_waiter(waiter);
1447 }
1448
1449 fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1450 let Some(first_publication) = publications.first() else {
1451 return;
1452 };
1453 let publication_identities = (publications.len() > 1).then(|| {
1454 publications
1455 .iter()
1456 .map(DependencyPublication::identity)
1457 .collect::<FxHashSet<_>>()
1458 });
1459 let waiter = {
1460 let mut state = self
1461 .state
1462 .lock()
1463 .unwrap_or_else(std::sync::PoisonError::into_inner);
1464 let removed = state
1465 .current
1466 .iter_mut()
1467 .filter_map(|current| {
1468 self.note_entry_visit();
1469 let is_derived = publication_identities.as_ref().map_or_else(
1470 || current.publication.is_derived_from(first_publication),
1471 |identities| {
1472 current
1473 .publication
1474 .immediate_source_identity()
1475 .is_some_and(|identity| identities.contains(&identity))
1476 },
1477 );
1478 if !is_derived {
1479 return None;
1480 }
1481 current.publication.cancel();
1482 Some(current.sequence)
1483 })
1484 .collect::<Vec<_>>();
1485 if removed.is_empty() {
1488 None
1489 } else {
1490 self.prune_stale_prefix(&mut state);
1491 state.waiter.take()
1492 }
1493 };
1494 self.notify_waiter(waiter);
1495 }
1496
1497 fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1500 let publication = DependencyPublication::owned(BlockingDependency {
1501 owner: dependency.owner.or(self.owner_hint),
1502 ..dependency
1503 });
1504 self.set_entry(publication)
1505 }
1506
1507 fn holds_live(&self, sequence: u64) -> bool {
1513 let state = self
1514 .state
1515 .lock()
1516 .unwrap_or_else(std::sync::PoisonError::into_inner);
1517 TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1518 self.note_entry_visit();
1519 entry.publication.is_live()
1520 })
1521 }
1522
1523 fn set_borrowed(&self, publication: &DependencyPublication) {
1524 let _sequence = self.set_entry(publication.derived(self.owner_hint));
1525 }
1526
1527 fn set_entry(&self, publication: DependencyPublication) -> u64 {
1528 let (sequence, waiter) = {
1529 let mut state = self
1530 .state
1531 .lock()
1532 .unwrap_or_else(std::sync::PoisonError::into_inner);
1533 let sequence = state.append(publication);
1536 self.has_current.store(true, Ordering::Release);
1537 (sequence, state.waiter.take())
1538 };
1539 self.notify_waiter(waiter);
1540 sequence
1541 }
1542
1543 fn note_entry_visit(&self) {
1544 #[cfg(test)]
1545 self.entry_visits.fetch_add(1, Ordering::Relaxed);
1546 }
1547
1548 fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1549 while state.current.front().is_some_and(|current| {
1550 self.note_entry_visit();
1551 !current.publication.is_live()
1552 }) {
1553 let current = state
1554 .current
1555 .pop_front()
1556 .expect("the stale prefix entry must exist");
1557 state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1558 }
1559 self
1560 .has_current
1561 .store(!state.current.is_empty(), Ordering::Release);
1562 }
1563
1564 fn first_live_sequence(
1565 &self,
1566 current: &VecDeque<TaskDependencyEntry>,
1567 sequences: &mut VecDeque<u64>,
1568 ) -> Option<u64> {
1569 loop {
1570 let sequence = *sequences.front()?;
1571 let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1572 sequences.pop_front();
1573 continue;
1574 };
1575 self.note_entry_visit();
1576 if entry.publication.is_live() {
1577 return Some(sequence);
1578 }
1579 sequences.pop_front();
1580 }
1581 }
1582
1583 fn first_live_owner_sequence(
1584 &self,
1585 state: &mut TaskDependencyState,
1586 owner: BlockingOwnerToken,
1587 ) -> Option<u64> {
1588 let sequence = {
1589 let current = &state.current;
1590 state
1591 .owner_sequences
1592 .get_mut(&owner)
1593 .and_then(|sequences| self.first_live_sequence(current, sequences))
1594 };
1595 if sequence.is_none() {
1596 state.owner_sequences.remove(&owner);
1597 }
1598 sequence
1599 }
1600
1601 fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1602 self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1603 }
1604
1605 #[cfg(test)]
1606 fn get(&self) -> Option<DependencyPublication> {
1607 self
1608 .state
1609 .lock()
1610 .unwrap_or_else(std::sync::PoisonError::into_inner)
1611 .current
1612 .iter()
1613 .find(|current| current.publication.is_live())
1614 .map(|current| current.publication.clone())
1615 }
1616
1617 fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1618 self
1619 .state
1620 .lock()
1621 .unwrap_or_else(std::sync::PoisonError::into_inner)
1622 .current
1623 .iter()
1624 .filter(|current| {
1625 self.note_entry_visit();
1626 current.publication.is_live()
1627 })
1628 .map(|current| current.publication.clone())
1629 .collect()
1630 }
1631
1632 fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1633 let mut state = self
1634 .state
1635 .lock()
1636 .unwrap_or_else(std::sync::PoisonError::into_inner);
1637 self.prune_stale_prefix(&mut state);
1638 let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1639 let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1640 let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1641 (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1642 (Some(_) | None, Some(unowned)) => (unowned, true),
1643 (Some(exact), None) => (exact, false),
1644 (None, None) => return None,
1645 };
1646 if bind_owner {
1647 let removed = state.unowned_sequences.pop_front();
1648 debug_assert_eq!(removed, Some(sequence));
1649 let current = state
1650 .entry_mut(sequence)
1651 .expect("the selected dependency must remain indexed");
1652 debug_assert_eq!(current.publication.dependency.owner, None);
1653 current.publication.dependency.owner = Some(owner);
1654 state
1658 .owner_sequences
1659 .entry(owner)
1660 .or_default()
1661 .push_front(sequence);
1662 }
1663 let publication = state
1664 .entry(sequence)
1665 .expect("the selected dependency must remain indexed")
1666 .publication
1667 .clone();
1668 Some(SelectedDependencyPublication {
1669 sequence,
1670 publication,
1671 })
1672 }
1673
1674 fn has_live_owner_dependency(
1675 &self,
1676 owner: BlockingOwnerToken,
1677 mode: OwnerDependencyMode,
1678 ) -> bool {
1679 #[cfg(all(test, napi_runtime_os_threads))]
1680 run_dependency_predicate_test_hook();
1681 let mut state = self
1682 .state
1683 .lock()
1684 .unwrap_or_else(std::sync::PoisonError::into_inner);
1685 self.prune_stale_prefix(&mut state);
1686 if self.first_live_owner_sequence(&mut state, owner).is_some() {
1687 return true;
1688 }
1689 matches!(mode, OwnerDependencyMode::Available)
1690 && self.first_live_unowned_sequence(&mut state).is_some()
1691 }
1692
1693 #[cfg(test)]
1694 fn get_dependency(&self) -> Option<BlockingDependency> {
1695 self.get().map(|publication| publication.dependency)
1696 }
1697
1698 #[cfg(test)]
1699 fn get_dependencies(&self) -> Vec<BlockingDependency> {
1700 self
1701 .snapshot_all_live()
1702 .into_iter()
1703 .map(|publication| publication.dependency)
1704 .collect()
1705 }
1706
1707 #[cfg(test)]
1708 fn reset_entry_visits(&self) {
1709 self.entry_visits.store(0, Ordering::Relaxed);
1710 }
1711
1712 #[cfg(test)]
1713 fn entry_visits(&self) -> usize {
1714 self.entry_visits.load(Ordering::Relaxed)
1715 }
1716
1717 fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1721 let mut state = self
1722 .state
1723 .lock()
1724 .unwrap_or_else(std::sync::PoisonError::into_inner);
1725 let Some(current) = state.entry(selected.sequence) else {
1726 return false;
1727 };
1728 self.note_entry_visit();
1729 if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1730 return false;
1731 }
1732 let dependency = current.publication.dependency;
1733 state
1734 .entry_mut(selected.sequence)
1735 .expect("the claimed dependency must remain indexed")
1736 .publication
1737 .cancel();
1738 state.remove_sequence_indexes(selected.sequence, dependency);
1739 self.prune_stale_prefix(&mut state);
1740 true
1741 }
1742
1743 fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1744 let _generation = RuntimeGenerationGuard::enter(self.generation);
1745 let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1746 return Vec::new();
1747 };
1748 let (previous, unused, current) = {
1749 let mut state = self
1750 .state
1751 .lock()
1752 .unwrap_or_else(std::sync::PoisonError::into_inner);
1753 let current = state
1754 .current
1755 .iter()
1756 .filter(|current| current.publication.is_live())
1757 .map(|current| current.publication.clone())
1758 .collect();
1759 if state
1760 .waiter
1761 .as_ref()
1762 .is_some_and(|previous| previous.will_wake(&waker))
1763 {
1764 (None, Some(waker), current)
1765 } else {
1766 (state.waiter.replace(waker), None, current)
1767 }
1768 };
1769 if let Some(previous) = previous {
1770 let _ = catch_unwind_contained(|| drop(previous));
1771 }
1772 if let Some(unused) = unused {
1773 let _ = catch_unwind_contained(|| drop(unused));
1774 }
1775 current
1776 }
1777
1778 fn begin_poll(&self) {
1779 if !self.has_current.load(Ordering::Acquire) {
1780 return;
1781 }
1782 let waiter = {
1783 let mut state = self
1784 .state
1785 .lock()
1786 .unwrap_or_else(std::sync::PoisonError::into_inner);
1787 if state.current.is_empty() {
1788 self.has_current.store(false, Ordering::Release);
1789 None
1790 } else {
1791 for previous in state.current.drain(..) {
1792 previous.publication.cancel();
1793 }
1794 state.clear_indexes();
1795 self.has_current.store(false, Ordering::Release);
1796 state.waiter.take()
1797 }
1798 };
1799 self.notify_waiter(waiter);
1800 }
1801
1802 #[cfg(test)]
1803 fn register_waiter(&self, waker: &Waker) {
1804 let _ = self.register_waiter_and_get(waker);
1805 }
1806}
1807
1808#[cfg(napi_runtime_os_threads)]
1809impl Drop for TaskDependency {
1810 fn drop(&mut self) {
1811 let state = self
1812 .state
1813 .get_mut()
1814 .unwrap_or_else(std::sync::PoisonError::into_inner);
1815 for current in state.current.drain(..) {
1816 current.publication.cancel();
1817 }
1818 if let Some(waiter) = state.waiter.take() {
1819 let _generation = RuntimeGenerationGuard::enter(self.generation);
1820 let _ = catch_unwind_contained(|| drop(waiter));
1821 }
1822 }
1823}
1824
1825#[cfg(napi_runtime_os_threads)]
1826thread_local! {
1827 static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1828 const { std::cell::RefCell::new(Vec::new()) };
1829}
1830
1831#[cfg(napi_runtime_os_threads)]
1832struct TaskDependencyGuard(Arc<TaskDependency>);
1833
1834#[cfg(napi_runtime_os_threads)]
1835impl TaskDependencyGuard {
1836 fn enter(dependency: &Arc<TaskDependency>) -> Self {
1837 let dependency = Arc::clone(dependency);
1838 CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1839 Self(dependency)
1840 }
1841}
1842
1843#[cfg(napi_runtime_os_threads)]
1844impl Drop for TaskDependencyGuard {
1845 fn drop(&mut self) {
1846 CURRENT_DEPENDENCY_STACK.with(|stack| {
1847 let current = stack
1848 .borrow_mut()
1849 .pop()
1850 .expect("blocking dependency context underflow");
1851 debug_assert!(Arc::ptr_eq(¤t, &self.0));
1852 });
1853 }
1854}
1855
1856#[cfg(napi_runtime_os_threads)]
1857struct BlockOnDependencyGuard {
1858 dependency: Arc<TaskDependency>,
1859 _context: TaskDependencyGuard,
1860}
1861
1862#[cfg(napi_runtime_os_threads)]
1863impl BlockOnDependencyGuard {
1864 fn enter(generation: u64) -> Self {
1865 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1866 let dependency = Arc::new(TaskDependency::new(generation, owner));
1867 let context = TaskDependencyGuard::enter(&dependency);
1868 Self {
1869 dependency,
1870 _context: context,
1871 }
1872 }
1873
1874 fn clear(&self) {
1875 self.dependency.clear();
1876 }
1877}
1878
1879#[cfg(napi_runtime_os_threads)]
1894struct PublishedBlockingDependency {
1895 state: Weak<TaskDependency>,
1896 sequence: u64,
1897}
1898
1899#[cfg(napi_runtime_os_threads)]
1918#[derive(Default)]
1919struct PublishedBlockingDependencies {
1920 first: Option<PublishedBlockingDependency>,
1921 rest: Vec<PublishedBlockingDependency>,
1922}
1923
1924#[cfg(napi_runtime_os_threads)]
1925impl PublishedBlockingDependency {
1926 fn still_stands(&self) -> bool {
1941 self
1942 .state
1943 .upgrade()
1944 .is_some_and(|state| state.holds_live(self.sequence))
1945 }
1946}
1947
1948#[cfg(napi_runtime_os_threads)]
1949impl PublishedBlockingDependencies {
1950 fn prune(&mut self) {
1975 if self.rest.is_empty() {
1976 if self
1977 .first
1978 .as_ref()
1979 .is_some_and(|first| first.state.strong_count() == 0)
1980 {
1981 self.first = None;
1982 }
1983 return;
1984 }
1985 self.rest.retain(PublishedBlockingDependency::still_stands);
1986 if self
1987 .first
1988 .as_ref()
1989 .is_some_and(|first| !first.still_stands())
1990 {
1991 self.first = self.rest.pop();
1992 }
1993 }
1994
1995 fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1996 self.prune();
1997 let target = Arc::as_ptr(&state);
1998 if let Some(existing) = self
1999 .first
2000 .iter_mut()
2001 .chain(self.rest.iter_mut())
2002 .find(|published| std::ptr::eq(published.state.as_ptr(), target))
2003 {
2004 if state.holds_live(existing.sequence) {
2015 return;
2016 }
2017 existing.sequence = state.set_owned(dependency);
2021 return;
2022 }
2023 let sequence = state.set_owned(dependency);
2024 let published = PublishedBlockingDependency {
2025 state: Arc::downgrade(&state),
2026 sequence,
2027 };
2028 match &mut self.first {
2029 slot @ None => *slot = Some(published),
2030 Some(_) => self.rest.push(published),
2031 }
2032 }
2033
2034 fn retire_all(self) {
2044 for published in self.first.into_iter().chain(self.rest) {
2045 if let Some(state) = published.state.upgrade() {
2046 state.retire(published.sequence);
2047 }
2048 }
2049 }
2050}
2051
2052#[cfg(napi_runtime_os_threads)]
2053fn record_blocking_dependency(
2054 published: &mut PublishedBlockingDependencies,
2055 dependency: BlockingDependency,
2056) {
2057 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2058 if let Some(current) = current {
2059 let dependency = BlockingDependency {
2060 owner: dependency
2061 .owner
2062 .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
2063 ..dependency
2064 };
2065 published.publish(current, dependency);
2066 }
2067}
2068
2069#[cfg(napi_runtime_os_threads)]
2070fn propagate_blocking_dependency(publication: DependencyPublication) {
2071 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2072 if let Some(current) = current {
2073 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
2074 let publication = publication.with_owner_if_none(owner);
2075 current.set_borrowed(&publication);
2076 }
2077}
2078
2079#[cfg(napi_runtime_os_threads)]
2080fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
2081 published.retire_all();
2085}
2086
2087#[cfg(napi_runtime_os_threads)]
2088fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
2089 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2090 if let Some(current) = current {
2091 current.clear_if_publications(publications);
2092 }
2093}
2094
2095enum JoinHandleInner<T> {
2096 Task {
2097 task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2098 awaiter: Option<PanicContainedWakerCache>,
2099 #[cfg(napi_runtime_os_threads)]
2100 dependency: Arc<TaskDependency>,
2101 },
2102 #[cfg(napi_runtime_os_threads)]
2103 Blocking {
2104 receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2105 dependency: BlockingDependency,
2106 published: PublishedBlockingDependencies,
2109 },
2110 Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2111}
2112
2113pub struct JoinHandle<T>(JoinHandleInner<T>);
2114
2115impl<T> Unpin for JoinHandle<T> {}
2116
2117impl<T> JoinHandle<T> {
2118 fn detach_task(&mut self) {
2119 let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2120 match inner {
2121 JoinHandleInner::Task {
2122 task,
2123 awaiter: _,
2124 #[cfg(napi_runtime_os_threads)]
2125 dependency,
2126 } => {
2127 #[cfg(napi_runtime_os_threads)]
2128 let current_dependencies = dependency.snapshot_all_live();
2129 #[cfg(napi_runtime_os_threads)]
2130 dependency.clear_waiter();
2131 task.detach();
2132 #[cfg(napi_runtime_os_threads)]
2133 let _ = catch_unwind_contained(|| {
2137 clear_propagated_blocking_dependencies(¤t_dependencies);
2138 });
2139 }
2140 #[cfg(napi_runtime_os_threads)]
2141 JoinHandleInner::Blocking {
2142 receiver,
2143 dependency: _,
2144 published,
2145 } => {
2146 let _ = catch_unwind_contained(|| drop(receiver));
2150 let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2153 }
2154 JoinHandleInner::Ready(result) => {
2155 let _ = catch_unwind_contained(|| drop(result));
2156 }
2157 }
2158 }
2159
2160 pub fn detach(mut self) {
2161 self.detach_task();
2162 }
2163}
2164
2165impl<T> Drop for JoinHandle<T> {
2166 fn drop(&mut self) {
2167 self.detach_task();
2171 }
2172}
2173
2174impl<T> Future for JoinHandle<T> {
2175 type Output = Result<T, JoinError>;
2176
2177 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2178 match &mut self.get_mut().0 {
2179 JoinHandleInner::Task {
2180 task,
2181 awaiter: awaiter_slot,
2182 #[cfg(napi_runtime_os_threads)]
2183 dependency,
2184 } => {
2185 let awaiter = match awaiter_slot {
2186 Some(awaiter) => {
2187 awaiter.update(cx.waker());
2188 awaiter
2189 }
2190 None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2191 };
2192 let contained_waker = awaiter.contained();
2193 let mut contained_cx = Context::from_waker(contained_waker);
2194 let poll = Pin::new(task).poll(&mut contained_cx);
2195 #[cfg(napi_runtime_os_threads)]
2196 if poll.is_pending() {
2197 for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2198 propagate_blocking_dependency(current_dependency);
2199 }
2200 } else {
2201 dependency.clear_waiter();
2202 }
2203 if poll.is_ready() {
2204 *awaiter_slot = None;
2205 }
2206 match poll {
2207 Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2208 Poll::Ready(None) => Poll::Ready(Err(JoinError {
2209 message: "async runtime stopped before the task completed".to_string(),
2210 })),
2211 Poll::Pending => Poll::Pending,
2212 }
2213 }
2214 #[cfg(napi_runtime_os_threads)]
2215 JoinHandleInner::Blocking {
2216 receiver,
2217 dependency,
2218 published,
2219 } => {
2220 let poll = Pin::new(receiver).poll(cx);
2221 if poll.is_pending() {
2222 record_blocking_dependency(published, *dependency);
2223 }
2224 match poll {
2225 Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2226 Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2227 message: "async runtime stopped before the blocking task completed".to_string(),
2228 })),
2229 Poll::Pending => Poll::Pending,
2230 }
2231 }
2232 JoinHandleInner::Ready(result) => Poll::Ready(
2233 result
2234 .take()
2235 .expect("JoinHandle polled after completion")
2236 .map(ContainedTaskOutput::into_inner),
2237 ),
2238 }
2239 }
2240}
2241
2242#[derive(Debug, Clone, Copy)]
2243pub struct RuntimeMetricsSnapshot {
2244 pub flavor: RuntimeFlavor,
2245 pub worker_threads: usize,
2246 pub max_blocking_tasks: usize,
2247 pub tasks_spawned: u64,
2248 pub tasks_completed: u64,
2249 pub tasks_panicked: u64,
2250 pub runnable_schedules: u64,
2251 pub runnable_polls: u64,
2252 pub queued_runnables: u64,
2253 pub max_queued_runnables: u64,
2254 pub active_runnables: u64,
2255 pub max_active_runnables: u64,
2256 pub blocking_tasks_started: u64,
2257 pub blocking_tasks_completed: u64,
2258 pub active_blocking_tasks: u64,
2259 pub max_active_blocking_tasks: u64,
2260}
2261
2262#[derive(Default)]
2263struct RuntimeMetrics {
2264 tasks_spawned: AtomicU64,
2265 tasks_completed: AtomicU64,
2266 tasks_panicked: AtomicU64,
2267 runnable_schedules: AtomicU64,
2268 runnable_polls: AtomicU64,
2269 queued_runnables: AtomicU64,
2270 max_queued_runnables: AtomicU64,
2271 active_runnables: AtomicU64,
2272 max_active_runnables: AtomicU64,
2273 blocking_tasks_scheduled: AtomicU64,
2279 blocking_tasks_started: AtomicU64,
2280 blocking_tasks_completed: AtomicU64,
2281 active_blocking_tasks: AtomicU64,
2282 max_active_blocking_tasks: AtomicU64,
2283 reset_generation: AtomicU64,
2287 reset_lock: Mutex<()>,
2288}
2289
2290#[cfg(test)]
2291thread_local! {
2292 static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2293 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2294 const { std::cell::RefCell::new(None) };
2295}
2296
2297#[cfg(test)]
2298fn run_runtime_metrics_high_water_test_hook() {
2299 if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2300 hook();
2301 }
2302}
2303
2304impl RuntimeMetrics {
2305 fn runnable_scheduled(&self) {
2306 self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2307 let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2308 #[cfg(test)]
2309 run_runtime_metrics_high_water_test_hook();
2310 self
2311 .max_queued_runnables
2312 .fetch_max(queued, Ordering::Relaxed);
2313 }
2314
2315 fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2316 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2317 self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2318 let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2319 #[cfg(test)]
2320 run_runtime_metrics_high_water_test_hook();
2321 self
2322 .max_active_runnables
2323 .fetch_max(active, Ordering::Relaxed);
2324 ActiveRunnableGuard { metrics: self }
2325 }
2326
2327 #[cfg(any(napi_runtime_os_threads, test))]
2328 fn runnable_cancelled(&self) {
2329 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2330 }
2331
2332 fn blocking_scheduled(&self) {
2336 self
2337 .blocking_tasks_scheduled
2338 .fetch_add(1, Ordering::Relaxed);
2339 }
2340
2341 fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2342 self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2343 if count_active_lane {
2344 let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2345 #[cfg(test)]
2346 run_runtime_metrics_high_water_test_hook();
2347 self
2348 .max_active_blocking_tasks
2349 .fetch_max(active, Ordering::Relaxed);
2350 }
2351 BlockingTaskGuard {
2352 metrics: Arc::clone(self),
2353 count_active_lane,
2354 }
2355 }
2356
2357 fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2358 let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2359 let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2360 let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2361 RuntimeMetricsSnapshot {
2362 flavor: options.flavor,
2363 worker_threads: options.worker_threads,
2364 max_blocking_tasks: options.max_blocking_tasks,
2365 tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2366 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2367 tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2368 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2369 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2370 queued_runnables,
2371 max_queued_runnables: self
2372 .max_queued_runnables
2373 .load(Ordering::Relaxed)
2374 .max(queued_runnables),
2375 active_runnables,
2376 max_active_runnables: self
2377 .max_active_runnables
2378 .load(Ordering::Relaxed)
2379 .max(active_runnables),
2380 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2381 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2382 active_blocking_tasks,
2383 max_active_blocking_tasks: self
2384 .max_active_blocking_tasks
2385 .load(Ordering::Relaxed)
2386 .max(active_blocking_tasks),
2387 }
2388 }
2389
2390 fn progress_fingerprint(&self) -> ProgressFingerprint {
2399 loop {
2400 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2401 if !reset_generation.is_multiple_of(2) {
2402 std::hint::spin_loop();
2403 continue;
2404 }
2405 let fingerprint = ProgressFingerprint {
2406 reset_generation,
2407 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2408 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2409 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2410 blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2411 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2412 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2413 };
2414 if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2415 return fingerprint;
2416 }
2417 }
2418 }
2419
2420 fn reset(&self) {
2421 let reset_guard = self
2422 .reset_lock
2423 .lock()
2424 .unwrap_or_else(std::sync::PoisonError::into_inner);
2425 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2433 let Some(resetting_generation) = reset_generation.checked_add(1) else {
2434 drop(reset_guard);
2435 panic!("runtime metrics reset generation space exhausted");
2436 };
2437 let Some(settled_generation) = resetting_generation.checked_add(1) else {
2438 drop(reset_guard);
2439 panic!("runtime metrics reset generation space exhausted");
2440 };
2441 self
2442 .reset_generation
2443 .store(resetting_generation, Ordering::SeqCst);
2444 self.tasks_spawned.store(0, Ordering::Relaxed);
2445 self.tasks_completed.store(0, Ordering::Relaxed);
2446 self.tasks_panicked.store(0, Ordering::Relaxed);
2447 self.runnable_schedules.store(0, Ordering::Relaxed);
2448 self.runnable_polls.store(0, Ordering::Relaxed);
2449 self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2450 self.blocking_tasks_started.store(0, Ordering::Relaxed);
2451 self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2452 self
2453 .reset_generation
2454 .store(settled_generation, Ordering::SeqCst);
2455 }
2456}
2457
2458#[derive(Clone, Copy, PartialEq, Eq)]
2460struct ProgressFingerprint {
2461 reset_generation: u64,
2462 runnable_schedules: u64,
2463 runnable_polls: u64,
2464 tasks_completed: u64,
2465 blocking_tasks_scheduled: u64,
2466 blocking_tasks_started: u64,
2467 blocking_tasks_completed: u64,
2468}
2469
2470struct ActiveRunnableGuard<'a> {
2471 metrics: &'a RuntimeMetrics,
2472}
2473
2474impl Drop for ActiveRunnableGuard<'_> {
2475 fn drop(&mut self) {
2476 self
2477 .metrics
2478 .active_runnables
2479 .fetch_sub(1, Ordering::Relaxed);
2480 }
2481}
2482
2483struct BlockingTaskGuard {
2484 metrics: Arc<RuntimeMetrics>,
2485 count_active_lane: bool,
2486}
2487
2488impl Drop for BlockingTaskGuard {
2489 fn drop(&mut self) {
2490 if self.count_active_lane {
2491 self
2492 .metrics
2493 .active_blocking_tasks
2494 .fetch_sub(1, Ordering::Relaxed);
2495 }
2496 self
2497 .metrics
2498 .blocking_tasks_completed
2499 .fetch_add(1, Ordering::Relaxed);
2500 }
2501}
2502
2503static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2504
2505fn next_runtime_generation() -> u64 {
2506 next_unique_id(
2507 &NEXT_RUNTIME_GENERATION,
2508 "async runtime generation id space exhausted",
2509 )
2510}
2511
2512thread_local! {
2513 static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2514 const { std::cell::Cell::new(None) };
2515}
2516
2517fn active_runtime_generation() -> Option<u64> {
2518 ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2519}
2520
2521struct RuntimeGenerationGuard {
2522 previous: Option<u64>,
2523}
2524
2525impl RuntimeGenerationGuard {
2526 fn enter(generation: u64) -> Self {
2527 Self::enter_optional(Some(generation))
2528 }
2529
2530 fn enter_optional(generation: Option<u64>) -> Self {
2531 Self {
2532 previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2533 }
2534 }
2535}
2536
2537impl Drop for RuntimeGenerationGuard {
2538 fn drop(&mut self) {
2539 ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2540 }
2541}
2542
2543struct ContainedFuture<F> {
2544 future: ManuallyDrop<F>,
2545 generation: u64,
2546}
2547
2548impl<F> ContainedFuture<F> {
2549 fn new(future: F, generation: u64) -> Self {
2550 Self {
2551 future: ManuallyDrop::new(future),
2552 generation,
2553 }
2554 }
2555}
2556
2557impl<F: Future> Future for ContainedFuture<F> {
2558 type Output = F::Output;
2559
2560 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2561 let this = unsafe { self.get_unchecked_mut() };
2563 let _generation = RuntimeGenerationGuard::enter(this.generation);
2564 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2567 }
2568}
2569
2570impl<F> Drop for ContainedFuture<F> {
2571 fn drop(&mut self) {
2572 let _generation = RuntimeGenerationGuard::enter(self.generation);
2573 let _ = catch_unwind_contained(|| {
2574 unsafe { ManuallyDrop::drop(&mut self.future) };
2576 });
2577 }
2578}
2579
2580#[derive(Default)]
2581struct GenerationStop {
2582 stopping: AtomicBool,
2583 publication: Mutex<()>,
2584 parkers: Mutex<Vec<Weak<DriverParker>>>,
2585}
2586
2587impl GenerationStop {
2588 fn is_stopping(&self) -> bool {
2589 self.stopping.load(Ordering::Acquire)
2590 }
2591
2592 fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2593 #[cfg(all(test, napi_runtime_os_threads))]
2594 run_before_generation_stop_publication_lock_test_hook();
2595 self.runnable_claim_guard()
2596 }
2597
2598 fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2599 GenerationStopPublicationGuard {
2600 stop: self,
2601 _publication: self
2602 .publication
2603 .lock()
2604 .unwrap_or_else(std::sync::PoisonError::into_inner),
2605 }
2606 }
2607
2608 fn publish_stopping(&self) {
2609 self.publication_guard().publish_stopping();
2610 }
2611
2612 fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2613 {
2614 let mut parkers = self
2615 .parkers
2616 .lock()
2617 .unwrap_or_else(std::sync::PoisonError::into_inner);
2618 parkers.retain(|candidate| candidate.strong_count() != 0);
2619 parkers.push(Arc::downgrade(parker));
2620 }
2621 if self.is_stopping() {
2622 parker.unpark();
2623 }
2624 GenerationStopGuard {
2625 stop: Arc::clone(self),
2626 parker: Arc::downgrade(parker),
2627 }
2628 }
2629
2630 fn wake_all(&self) -> usize {
2635 let mut count = 0;
2636 let mut registered = self
2637 .parkers
2638 .lock()
2639 .unwrap_or_else(std::sync::PoisonError::into_inner);
2640 registered.retain(|candidate| {
2641 let Some(parker) = candidate.upgrade() else {
2642 return false;
2643 };
2644 count += 1;
2645 parker.unpark();
2648 true
2649 });
2650 count
2651 }
2652
2653 fn begin_shutdown(&self) {
2654 self.publish_stopping();
2655 let parkers = {
2656 let mut registered = self
2657 .parkers
2658 .lock()
2659 .unwrap_or_else(std::sync::PoisonError::into_inner);
2660 let parkers = registered
2661 .iter()
2662 .filter_map(Weak::upgrade)
2663 .collect::<Vec<_>>();
2664 registered.clear();
2665 parkers
2666 };
2667 for parker in parkers {
2668 parker.unpark();
2669 }
2670 }
2671}
2672
2673struct GenerationStopPublicationGuard<'a> {
2674 stop: &'a GenerationStop,
2675 _publication: MutexGuard<'a, ()>,
2676}
2677
2678impl GenerationStopPublicationGuard<'_> {
2679 fn is_stopping(&self) -> bool {
2680 self.stop.is_stopping()
2681 }
2682
2683 fn publish_stopping(&self) {
2684 self.stop.stopping.store(true, Ordering::Release);
2685 }
2686}
2687
2688struct GenerationStopGuard {
2689 stop: Arc<GenerationStop>,
2690 parker: Weak<DriverParker>,
2691}
2692
2693impl Drop for GenerationStopGuard {
2694 fn drop(&mut self) {
2695 let Some(parker) = self.parker.upgrade() else {
2696 return;
2697 };
2698 let mut registered = self
2699 .stop
2700 .parkers
2701 .lock()
2702 .unwrap_or_else(std::sync::PoisonError::into_inner);
2703 if let Some(index) = registered.iter().position(|candidate| {
2704 candidate
2705 .upgrade()
2706 .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2707 }) {
2708 registered.swap_remove(index);
2709 }
2710 }
2711}
2712
2713#[derive(Clone, Copy)]
2715enum PendingRead {
2716 Locked,
2719 NonBlocking,
2724}
2725
2726impl PendingRead {
2727 fn read<T>(self, mutex: &Mutex<T>, pending: impl FnOnce(&T) -> bool) -> bool {
2731 let state = match self {
2732 Self::Locked => mutex.lock(),
2733 Self::NonBlocking => match mutex.try_lock() {
2734 Ok(state) => Ok(state),
2735 Err(std::sync::TryLockError::Poisoned(poisoned)) => Err(poisoned),
2736 Err(std::sync::TryLockError::WouldBlock) => return true,
2737 },
2738 };
2739 pending(&state.unwrap_or_else(std::sync::PoisonError::into_inner))
2740 }
2741}
2742
2743struct GenerationWorkState {
2744 closed: bool,
2745 next_task_id: u64,
2746 active: usize,
2747 abort_handles: FxHashMap<u64, AbortHandle>,
2748}
2749
2750struct GenerationWork {
2751 id: u64,
2752 state: Mutex<GenerationWorkState>,
2753 idle: Condvar,
2754}
2755
2756impl GenerationWork {
2757 fn new() -> Arc<Self> {
2758 Arc::new(Self {
2759 id: next_runtime_generation(),
2760 state: Mutex::new(GenerationWorkState {
2761 closed: false,
2762 next_task_id: 0,
2763 active: 0,
2764 abort_handles: FxHashMap::default(),
2765 }),
2766 idle: Condvar::new(),
2767 })
2768 }
2769
2770 fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2771 let (abort_handle, abort_registration) = AbortHandle::new_pair();
2772 let mut state = self
2773 .state
2774 .lock()
2775 .unwrap_or_else(std::sync::PoisonError::into_inner);
2776 if state.closed {
2777 return None;
2778 }
2779 let task_id = state.next_task_id;
2780 let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2781 drop(state);
2782 panic!("async runtime generation task id space exhausted");
2783 };
2784 state.next_task_id = next_task_id;
2785 state.active += 1;
2786 state.abort_handles.insert(task_id, abort_handle);
2787 Some((
2788 abort_registration,
2789 GenerationWorkGuard {
2790 work: Arc::clone(self),
2791 task_id: Some(task_id),
2792 },
2793 ))
2794 }
2795
2796 fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2797 let mut state = self
2798 .state
2799 .lock()
2800 .unwrap_or_else(std::sync::PoisonError::into_inner);
2801 if state.closed {
2802 return None;
2803 }
2804 state.active += 1;
2805 Some(GenerationWorkGuard {
2806 work: Arc::clone(self),
2807 task_id: None,
2808 })
2809 }
2810
2811 fn close_and_abort(&self) {
2812 let abort_handles = {
2817 let mut state = self
2818 .state
2819 .lock()
2820 .unwrap_or_else(std::sync::PoisonError::into_inner);
2821 state.closed = true;
2822 std::mem::take(&mut state.abort_handles)
2823 };
2824 for abort_handle in abort_handles.values() {
2825 abort_handle.abort();
2826 }
2827 }
2828
2829 fn wait_until_idle(&self) {
2830 self.wait_until_idle_retiring(|| false, || {});
2831 }
2832
2833 fn has_active_work(&self, read: PendingRead) -> bool {
2836 read.read(&self.state, |state| state.active != 0)
2837 }
2838
2839 fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2847 let mut state = self
2848 .state
2849 .lock()
2850 .unwrap_or_else(std::sync::PoisonError::into_inner);
2851 while state.active != 0 {
2852 if has_deferred() {
2853 drop(state);
2854 retire();
2855 state = self
2856 .state
2857 .lock()
2858 .unwrap_or_else(std::sync::PoisonError::into_inner);
2859 continue;
2860 }
2861 state = self
2862 .idle
2863 .wait(state)
2864 .unwrap_or_else(std::sync::PoisonError::into_inner);
2865 }
2866 }
2867
2868 fn notify_idle_waiters(&self) {
2869 let _state = self
2870 .state
2871 .lock()
2872 .unwrap_or_else(std::sync::PoisonError::into_inner);
2873 self.idle.notify_all();
2874 }
2875}
2876
2877struct GenerationWorkGuard {
2878 work: Arc<GenerationWork>,
2879 task_id: Option<u64>,
2880}
2881
2882impl Drop for GenerationWorkGuard {
2883 fn drop(&mut self) {
2884 let abort_handle = {
2890 let mut state = self
2891 .work
2892 .state
2893 .lock()
2894 .unwrap_or_else(std::sync::PoisonError::into_inner);
2895 let abort_handle = self
2896 .task_id
2897 .and_then(|task_id| state.abort_handles.remove(&task_id));
2898 state.active = state
2899 .active
2900 .checked_sub(1)
2901 .expect("generation work count underflow");
2902 if state.active == 0 {
2903 self.work.idle.notify_all();
2904 }
2905 abort_handle
2906 };
2907 drop(abort_handle);
2908 }
2909}
2910
2911struct RegisteredTaskFuture<F> {
2917 future: ManuallyDrop<F>,
2918 registration: Option<GenerationWorkGuard>,
2919 generation: u64,
2920}
2921
2922impl<F> RegisteredTaskFuture<F> {
2923 fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2924 Self {
2925 future: ManuallyDrop::new(future),
2926 registration: Some(registration),
2927 generation,
2928 }
2929 }
2930}
2931
2932impl<F: Future> Future for RegisteredTaskFuture<F> {
2933 type Output = F::Output;
2934
2935 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2936 let this = unsafe { self.get_unchecked_mut() };
2938 let _generation = RuntimeGenerationGuard::enter(this.generation);
2939 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2942 }
2943}
2944
2945impl<F> Drop for RegisteredTaskFuture<F> {
2946 fn drop(&mut self) {
2947 let _generation = RuntimeGenerationGuard::enter(self.generation);
2948 let _ = catch_unwind_contained(|| {
2949 unsafe { ManuallyDrop::drop(&mut self.future) };
2951 });
2952 drop(self.registration.take());
2955 }
2956}
2957
2958#[cfg(napi_runtime_os_threads)]
2959struct RegisteredBlockingFunction<F> {
2960 function: Option<F>,
2961 registration: Option<GenerationWorkGuard>,
2962 generation: u64,
2963}
2964
2965#[cfg(napi_runtime_os_threads)]
2966impl<F> RegisteredBlockingFunction<F> {
2967 fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2968 Self {
2969 function: Some(function),
2970 registration: Some(registration),
2971 generation,
2972 }
2973 }
2974
2975 fn run<T>(mut self) -> Result<T, JoinError>
2976 where
2977 F: FnOnce() -> T,
2978 {
2979 on_thread_handoff();
2980 let _generation = RuntimeGenerationGuard::enter(self.generation);
2981 let result = catch_unwind_join_error(
2982 self
2983 .function
2984 .take()
2985 .expect("blocking function must be available"),
2986 );
2987 drop(self.registration.take());
2988 result
2989 }
2990}
2991
2992#[cfg(napi_runtime_os_threads)]
2993impl<F> Drop for RegisteredBlockingFunction<F> {
2994 fn drop(&mut self) {
2995 let _generation = RuntimeGenerationGuard::enter(self.generation);
2996 if let Some(function) = self.function.take() {
2997 let _ = catch_unwind_contained(|| drop(function));
2998 }
2999 drop(self.registration.take());
3003 }
3004}
3005
3006fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
3007 on_thread_handoff();
3008 let _active = metrics.runnable_started();
3009 let _ = catch_unwind_contained(|| runnable.run());
3010}
3011
3012#[cfg(napi_runtime_os_threads)]
3013enum RunnableClaim {
3014 Empty,
3015 Run(Runnable),
3016 Cancel(Runnable),
3017}
3018
3019static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
3020static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
3021
3022fn next_current_thread_dispatch_id() -> u64 {
3023 next_unique_id(
3024 &NEXT_CURRENT_THREAD_DISPATCH_ID,
3025 "CurrentThread dispatch id space exhausted",
3026 )
3027 .checked_add(1)
3028 .expect("CurrentThread dispatch id space exhausted")
3029}
3030
3031struct CurrentThreadBlockingAdmission {
3032 state: Mutex<CurrentThreadBlockingAdmissionState>,
3033}
3034
3035struct CurrentThreadBlockingAdmissionState {
3036 owner: Option<BlockingOwnerToken>,
3037 closed: bool,
3038 #[cfg(napi_runtime_os_threads)]
3039 queue: BlockingQueue,
3040}
3041
3042struct CurrentThreadExecutor {
3043 id: u64,
3044 generation: u64,
3045 stop: Arc<GenerationStop>,
3046 queue: Mutex<CurrentThreadQueue>,
3047 blocking_admission: CurrentThreadBlockingAdmission,
3048 draining: AtomicBool,
3049 scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
3050 scheduler_idle: Condvar,
3051 dispatch_pending: AtomicU64,
3055 recovery_dispatch: AtomicU64,
3059 cancelling_failed_dispatch: AtomicBool,
3065 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3066 task_drivers: &'static CurrentThreadTaskDriverRegistry,
3067 metrics: Arc<RuntimeMetrics>,
3068 threadless: bool,
3076 park_deadline: Option<Duration>,
3077 host_timers: Arc<HostTimerRegistry>,
3087 generation_work: ArcSwapOption<GenerationWork>,
3092}
3093
3094impl Drop for CurrentThreadExecutor {
3095 fn drop(&mut self) {
3096 self.retire_rejected_work();
3099 }
3100}
3101
3102struct CurrentThreadQueue {
3103 closed: bool,
3104 runnables: VecDeque<Runnable>,
3105 rejected: Vec<Runnable>,
3120 #[cfg(napi_runtime_os_threads)]
3124 rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
3125}
3126
3127#[derive(Default)]
3128struct CurrentThreadSchedulerState {
3129 active_host_turns: usize,
3130 active_dispatch_calls: usize,
3131 dispatch_publications: Vec<CurrentThreadDispatchPublication>,
3132}
3133
3134struct CurrentThreadDispatchPublication {
3135 dispatch: u64,
3136 republish_requested: bool,
3137}
3138
3139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3140enum CurrentThreadDispatchRequest {
3141 Accepted,
3142 Coalesced,
3143 Unavailable,
3144}
3145
3146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3147struct CurrentThreadTaskDispatchFailure {
3148 dispatch: u64,
3149 epoch: u64,
3150}
3151
3152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3153enum CurrentThreadHostDispatchResult {
3154 Accepted,
3155 Unavailable,
3156 Failed(CurrentThreadTaskDispatchFailure),
3157}
3158
3159enum CurrentThreadDispatchPublicationStep {
3160 Republish,
3161 Complete(Option<CurrentThreadHostDispatchResult>),
3162}
3163
3164struct CurrentThreadHostTurn {
3165 executor: Arc<CurrentThreadExecutor>,
3166 draining: bool,
3167 _generation: RuntimeGenerationGuard,
3168}
3169
3170impl CurrentThreadHostTurn {
3171 fn drive(&mut self) {
3172 let executor = Arc::clone(&self.executor);
3173 executor.drive_admitted_host_turn(self);
3174 }
3175
3176 fn release_draining(&mut self) {
3177 if !self.draining {
3178 return;
3179 }
3180 let _scheduler = self
3181 .executor
3182 .scheduler_idle_lock
3183 .lock()
3184 .unwrap_or_else(std::sync::PoisonError::into_inner);
3185 self.executor.draining.store(false, Ordering::Release);
3186 self.draining = false;
3187 }
3188}
3189
3190impl Drop for CurrentThreadHostTurn {
3191 fn drop(&mut self) {
3192 let mut scheduler = self
3193 .executor
3194 .scheduler_idle_lock
3195 .lock()
3196 .unwrap_or_else(std::sync::PoisonError::into_inner);
3197 if self.draining {
3198 self.executor.draining.store(false, Ordering::Release);
3199 self.draining = false;
3200 }
3201 scheduler.active_host_turns = scheduler
3202 .active_host_turns
3203 .checked_sub(1)
3204 .expect("CurrentThread active host-turn count underflow");
3205 self.executor.scheduler_idle.notify_all();
3206 }
3207}
3208
3209#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3216pub struct CurrentThreadTaskCallbackLease {
3217 _host_turn: CurrentThreadHostTurn,
3218 _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3219}
3220
3221impl CurrentThreadTaskCallbackLease {
3222 fn new(host_turn: CurrentThreadHostTurn) -> Self {
3223 Self {
3224 _host_turn: host_turn,
3225 _not_send: std::marker::PhantomData,
3226 }
3227 }
3228}
3229
3230struct CurrentThreadDispatchCall<'a> {
3231 executor: &'a CurrentThreadExecutor,
3232 publication: Option<u64>,
3233 _generation: RuntimeGenerationGuard,
3234}
3235
3236impl CurrentThreadDispatchCall<'_> {
3237 fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3238 let dispatch = self
3239 .publication
3240 .expect("the CurrentThread dispatch call must own a publication");
3241 let index = scheduler
3242 .dispatch_publications
3243 .iter()
3244 .position(|publication| publication.dispatch == dispatch)
3245 .expect("the CurrentThread dispatch publication must remain registered");
3246 scheduler.dispatch_publications.swap_remove(index);
3247 self.publication = None;
3248 }
3249}
3250
3251impl Drop for CurrentThreadDispatchCall<'_> {
3252 fn drop(&mut self) {
3253 let mut scheduler = self
3254 .executor
3255 .scheduler_idle_lock
3256 .lock()
3257 .unwrap_or_else(std::sync::PoisonError::into_inner);
3258 if let Some(dispatch) = self.publication.take()
3259 && let Some(index) = scheduler
3260 .dispatch_publications
3261 .iter()
3262 .position(|publication| publication.dispatch == dispatch)
3263 {
3264 scheduler.dispatch_publications.swap_remove(index);
3265 }
3266 scheduler.active_dispatch_calls = scheduler
3267 .active_dispatch_calls
3268 .checked_sub(1)
3269 .expect("CurrentThread active dispatch-call count underflow");
3270 self.executor.scheduler_idle.notify_all();
3271 }
3272}
3273
3274thread_local! {
3275 static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3286}
3287
3288struct CurrentThreadScheduleScope;
3291
3292impl CurrentThreadScheduleScope {
3293 fn enter() -> Self {
3294 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3295 depth.set(
3296 depth
3297 .get()
3298 .checked_add(1)
3299 .expect("CurrentThread schedule depth exhausted"),
3300 );
3301 });
3302 Self
3303 }
3304
3305 fn is_active() -> bool {
3307 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3308 }
3309}
3310
3311impl Drop for CurrentThreadScheduleScope {
3312 fn drop(&mut self) {
3313 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3314 depth.set(
3315 depth
3316 .get()
3317 .checked_sub(1)
3318 .expect("CurrentThread schedule depth underflow"),
3319 );
3320 });
3321 }
3322}
3323
3324thread_local! {
3325 static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3346 const { std::cell::Cell::new(false) };
3347}
3348
3349struct CurrentThreadRejectedWorkRetirementScope;
3352
3353impl CurrentThreadRejectedWorkRetirementScope {
3354 fn enter() -> Option<Self> {
3358 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3359 if active.replace(true) {
3360 return None;
3364 }
3365 Some(Self)
3366 })
3367 }
3368}
3369
3370impl Drop for CurrentThreadRejectedWorkRetirementScope {
3371 fn drop(&mut self) {
3372 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3373 }
3374}
3375
3376struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3377
3378impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3379 fn drop(&mut self) {
3380 self
3381 .0
3382 .cancelling_failed_dispatch
3383 .store(false, Ordering::Release);
3384 }
3385}
3386
3387enum CurrentThreadDispatchFailureAction<'a> {
3388 None,
3389 Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3390 Publish {
3391 dispatch: u64,
3392 call: CurrentThreadDispatchCall<'a>,
3393 },
3394}
3395
3396struct CurrentThreadDispatchFailureRecovery<'a> {
3397 action: CurrentThreadDispatchFailureAction<'a>,
3398 _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3399}
3400
3401struct CurrentThreadRegistryCancellationGuard<'a> {
3402 registry: &'a CurrentThreadTaskDriverRegistry,
3403 failure: Option<CurrentThreadTaskDispatchFailure>,
3404}
3405
3406impl CurrentThreadRegistryCancellationGuard<'_> {
3407 fn new(
3408 registry: &CurrentThreadTaskDriverRegistry,
3409 failure: CurrentThreadTaskDispatchFailure,
3410 ) -> CurrentThreadRegistryCancellationGuard<'_> {
3411 CurrentThreadRegistryCancellationGuard {
3412 registry,
3413 failure: Some(failure),
3414 }
3415 }
3416}
3417
3418impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3419 fn drop(&mut self) {
3420 if let Some(failure) = self.failure.take() {
3421 let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3422 }
3423 }
3424}
3425
3426struct CurrentThreadBlockingGuard<'a> {
3427 executor: &'a CurrentThreadExecutor,
3428 owner: BlockingOwnerToken,
3429 previous_owner: Option<BlockingOwnerToken>,
3430 owns_lane: bool,
3431}
3432
3433impl CurrentThreadBlockingGuard<'_> {
3434 fn counts_active_lane(&self) -> bool {
3435 self.owns_lane
3436 }
3437}
3438
3439impl Drop for CurrentThreadBlockingGuard<'_> {
3440 fn drop(&mut self) {
3441 BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3442 if !self.owns_lane {
3443 return;
3444 }
3445 let mut state = self
3446 .executor
3447 .blocking_admission
3448 .state
3449 .lock()
3450 .unwrap_or_else(std::sync::PoisonError::into_inner);
3451 debug_assert_eq!(state.owner, Some(self.owner));
3452 state.owner = None;
3453 drop(state);
3454 self.executor.stop.wake_all();
3455 }
3456}
3457
3458impl CurrentThreadExecutor {
3459 const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3462 #[cfg(napi_runtime_os_threads)]
3464 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3465 const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3469
3470 #[cfg(test)]
3474 fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3475 Self::with_detection(metrics, THREADLESS_BUILD, None)
3476 }
3477
3478 #[cfg(test)]
3484 fn with_detection(
3485 metrics: Arc<RuntimeMetrics>,
3486 threadless: bool,
3487 park_deadline: Option<Duration>,
3488 ) -> Self {
3489 Self::with_generation(
3490 metrics,
3491 threadless,
3492 park_deadline,
3493 u64::MAX,
3494 Arc::new(GenerationStop::default()),
3495 )
3496 }
3497
3498 fn with_generation(
3499 metrics: Arc<RuntimeMetrics>,
3500 threadless: bool,
3501 park_deadline: Option<Duration>,
3502 generation: u64,
3503 stop: Arc<GenerationStop>,
3504 ) -> Self {
3505 Self {
3506 id: next_unique_id(
3507 &NEXT_RUNTIME_EXECUTOR_ID,
3508 "async runtime executor id space exhausted",
3509 ),
3510 generation,
3511 stop,
3512 queue: Mutex::new(CurrentThreadQueue {
3513 closed: false,
3514 runnables: VecDeque::new(),
3515 rejected: Vec::new(),
3516 #[cfg(napi_runtime_os_threads)]
3517 rejected_blocking: Vec::new(),
3518 }),
3519 blocking_admission: CurrentThreadBlockingAdmission {
3520 state: Mutex::new(CurrentThreadBlockingAdmissionState {
3521 owner: None,
3522 closed: false,
3523 #[cfg(napi_runtime_os_threads)]
3524 queue: BlockingQueue {
3525 closed: false,
3526 head: None,
3527 tail: None,
3528 jobs: FxHashMap::default(),
3529 },
3530 }),
3531 },
3532 draining: AtomicBool::new(false),
3533 scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3534 scheduler_idle: Condvar::new(),
3535 dispatch_pending: AtomicU64::new(0),
3536 recovery_dispatch: AtomicU64::new(0),
3537 cancelling_failed_dispatch: AtomicBool::new(false),
3538 task_dispatch: dispatch_current_thread_tasks,
3539 task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3540 metrics,
3541 threadless,
3542 park_deadline,
3543 host_timers: Arc::new(HostTimerRegistry::default()),
3544 generation_work: ArcSwapOption::empty(),
3545 }
3546 }
3547
3548 fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3549 self.generation_work.store(Some(Arc::clone(work)));
3550 }
3551
3552 fn has_rejected_work(&self) -> bool {
3555 self.rejected_work_pending(PendingRead::Locked)
3556 }
3557
3558 fn rejected_work_pending(&self, read: PendingRead) -> bool {
3561 read.read(&self.queue, |queue| {
3562 #[cfg(napi_runtime_os_threads)]
3563 let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3564 #[cfg(not(napi_runtime_os_threads))]
3565 let has_rejected_blocking = false;
3566 !queue.rejected.is_empty() || has_rejected_blocking
3567 })
3568 }
3569
3570 fn scheduler_work_pending(&self, read: PendingRead) -> bool {
3573 read.read(&self.scheduler_idle_lock, |idle| {
3574 idle.active_host_turns != 0 || idle.active_dispatch_calls != 0
3575 })
3576 }
3577
3578 fn retire_rejected_work(&self) {
3587 if !self.has_rejected_work() {
3588 return;
3589 }
3590 let _generation = RuntimeGenerationGuard::enter(self.generation);
3591 loop {
3592 #[cfg(napi_runtime_os_threads)]
3593 let (rejected, rejected_blocking) = {
3594 let mut queue = self
3595 .queue
3596 .lock()
3597 .unwrap_or_else(std::sync::PoisonError::into_inner);
3598 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3599 return;
3600 }
3601 (
3602 std::mem::take(&mut queue.rejected),
3603 std::mem::take(&mut queue.rejected_blocking),
3604 )
3605 };
3606 #[cfg(not(napi_runtime_os_threads))]
3607 let rejected = {
3608 let mut queue = self
3609 .queue
3610 .lock()
3611 .unwrap_or_else(std::sync::PoisonError::into_inner);
3612 if queue.rejected.is_empty() {
3613 return;
3614 }
3615 std::mem::take(&mut queue.rejected)
3616 };
3617 for runnable in rejected {
3618 let _ = catch_unwind_contained(|| drop(runnable));
3619 }
3620 #[cfg(napi_runtime_os_threads)]
3621 for job in rejected_blocking {
3622 let _ = catch_unwind_contained(|| drop(job));
3623 }
3624 }
3625 }
3626
3627 fn retire_rejected_work_off_waker_stack(&self) {
3633 if CurrentThreadScheduleScope::is_active() {
3634 return;
3635 }
3636 self.retire_rejected_work();
3637 }
3638
3639 fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3649 self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3650 }
3651
3652 fn request_rejected_work_retirement(self: &Arc<Self>) {
3753 if !self.has_rejected_work() {
3754 return;
3755 }
3756 let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3757 return;
3762 };
3763 for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3764 if !self.needs_rejected_work_retirement_dispatch() {
3765 return;
3766 }
3767 if matches!(
3768 self.request_drain_with(next_current_thread_dispatch_id),
3769 CurrentThreadDispatchRequest::Unavailable
3770 ) {
3771 return;
3773 }
3774 }
3775 if self.publish_final_rejected_work_retirement()
3776 && self.needs_rejected_work_retirement_dispatch()
3777 {
3778 let _ = self.request_drain_with(next_current_thread_dispatch_id);
3797 }
3798 }
3799
3800 fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3827 if !self.needs_rejected_work_retirement_dispatch() {
3828 return false;
3829 }
3830 let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3831 return false;
3834 };
3835 let dispatch = next_current_thread_dispatch_id();
3836 let dispatch_call = {
3837 let mut scheduler = self.lock_scheduler_for_publication();
3838 self.dispatch_pending.store(dispatch, Ordering::Release);
3839 self
3840 .begin_host_dispatch_publication(&mut scheduler, dispatch)
3841 .expect("a fresh CurrentThread retirement dispatch must own its publication")
3842 };
3843 let result = self.publish_host_dispatch(dispatch, dispatch_call);
3844 {
3845 let _queue = self
3849 .queue
3850 .lock()
3851 .unwrap_or_else(std::sync::PoisonError::into_inner);
3852 drop(cancelling);
3853 }
3854 if let Some(work) = self.generation_work.load_full() {
3857 work.notify_idle_waiters();
3858 }
3859 result == CurrentThreadHostDispatchResult::Accepted
3860 }
3861
3862 fn fresh_owner_token(&self) -> BlockingOwnerToken {
3863 BlockingOwnerToken {
3864 executor_id: self.id,
3865 frame: next_unique_id(
3866 &NEXT_BLOCKING_FRAME,
3867 "blocking owner frame id space exhausted",
3868 ),
3869 }
3870 }
3871
3872 fn enter_blocking_owner(
3873 &self,
3874 owner: BlockingOwnerToken,
3875 owns_lane: bool,
3876 ) -> CurrentThreadBlockingGuard<'_> {
3877 let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3878 CurrentThreadBlockingGuard {
3879 executor: self,
3880 owner,
3881 previous_owner,
3882 owns_lane,
3883 }
3884 }
3885
3886 fn schedule_blocking<F, T>(
3887 self: &Arc<Self>,
3888 function: F,
3889 registration: GenerationWorkGuard,
3890 ) -> JoinHandle<T>
3891 where
3892 F: FnOnce() -> T + Send + 'static,
3893 T: Send + 'static,
3894 {
3895 self.metrics.blocking_scheduled();
3897 let mut function = Some(function);
3898 let mut registration = Some(registration);
3899 let ambient_owner = BLOCKING_OWNER
3900 .with(std::cell::Cell::get)
3901 .filter(|owner| owner.executor_id == self.id);
3902 let mut run_owner = None;
3903 let mut owns_lane = false;
3904 let mut rejected = false;
3905
3906 #[cfg(napi_runtime_os_threads)]
3907 let (sender, receiver) = oneshot::channel();
3908 #[cfg(napi_runtime_os_threads)]
3909 let dependency = BlockingDependency {
3910 job: BlockingJobId(next_unique_id(
3911 &NEXT_BLOCKING_JOB_ID,
3912 "blocking job id space exhausted",
3913 )),
3914 owner: ambient_owner,
3915 };
3916 #[cfg(napi_runtime_os_threads)]
3917 let mut queued = false;
3918
3919 {
3920 let mut state = self
3921 .blocking_admission
3922 .state
3923 .lock()
3924 .unwrap_or_else(std::sync::PoisonError::into_inner);
3925 #[cfg(all(test, napi_runtime_os_threads))]
3926 run_current_thread_blocking_claim_test_hook();
3927 if self.stop.is_stopping()
3931 || state.closed
3932 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3933 {
3934 rejected = true;
3935 } else if ambient_owner.is_some() && state.owner == ambient_owner {
3936 run_owner = ambient_owner;
3937 } else {
3938 #[cfg(napi_runtime_os_threads)]
3939 let queue_empty = state.queue.is_empty();
3940 #[cfg(not(napi_runtime_os_threads))]
3941 let queue_empty = true;
3942
3943 if state.owner.is_none() && queue_empty {
3944 let owner = self.fresh_owner_token();
3945 state.owner = Some(owner);
3946 run_owner = Some(owner);
3947 owns_lane = true;
3948 } else {
3949 #[cfg(napi_runtime_os_threads)]
3950 {
3951 let function = function
3952 .take()
3953 .expect("blocking function must be available");
3954 let registration = registration
3955 .take()
3956 .expect("blocking registration must be available");
3957 let generation = self.generation;
3958 let function = RegisteredBlockingFunction::new(function, registration, generation);
3959 state.queue.push(QueuedBlocking {
3960 id: dependency.job,
3961 run: Box::new(move || {
3962 let _generation = RuntimeGenerationGuard::enter(generation);
3963 let result = function
3964 .run()
3965 .map(|output| ContainedTaskOutput::new(output, generation));
3966 let _ = sender.send(result);
3967 }),
3968 });
3969 queued = true;
3970 }
3971 #[cfg(not(napi_runtime_os_threads))]
3972 {
3973 rejected = true;
3980 }
3981 }
3982 }
3983 }
3984
3985 #[cfg(napi_runtime_os_threads)]
3986 if queued {
3987 self.stop.wake_all();
3988 if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3989 self.request_drain();
3990 }
3991 return JoinHandle(JoinHandleInner::Blocking {
3992 receiver,
3993 dependency,
3994 published: PublishedBlockingDependencies::default(),
3995 });
3996 }
3997
3998 if rejected {
3999 let _generation = RuntimeGenerationGuard::enter(self.generation);
4000 let _ = catch_unwind_contained(|| drop(function.take()));
4001 drop(registration.take());
4002 return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
4003 message: "the async runtime stopped before the blocking task could start".to_string(),
4004 }))));
4005 }
4006
4007 let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
4008 let blocking = self.enter_blocking_owner(owner, owns_lane);
4009 let _registration = registration
4010 .take()
4011 .expect("blocking registration must be available");
4012 let _generation = RuntimeGenerationGuard::enter(self.generation);
4013 let result = {
4014 let _task = self.metrics.blocking_started(blocking.counts_active_lane());
4015 on_thread_handoff();
4016 catch_unwind_join_error(
4017 function
4018 .take()
4019 .expect("blocking function must be available"),
4020 )
4021 }
4022 .map(|output| ContainedTaskOutput::new(output, self.generation));
4023 drop(blocking);
4024 #[cfg(napi_runtime_os_threads)]
4025 if owns_lane && !self.draining.load(Ordering::Acquire) {
4026 self.service_queued_blocking_work();
4030 }
4031 JoinHandle(JoinHandleInner::Ready(Some(result)))
4032 }
4033
4034 #[cfg(napi_runtime_os_threads)]
4035 fn take_queued_blocking(
4036 &self,
4037 ) -> Option<(
4038 Box<dyn FnOnce() + Send + 'static>,
4039 CurrentThreadBlockingGuard<'_>,
4040 )> {
4041 let (job, owner) = {
4042 let mut state = self
4043 .blocking_admission
4044 .state
4045 .lock()
4046 .unwrap_or_else(std::sync::PoisonError::into_inner);
4047 #[cfg(all(test, napi_runtime_os_threads))]
4048 run_current_thread_blocking_claim_test_hook();
4049 if self.stop.is_stopping()
4050 || state.closed
4051 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4052 || state.owner.is_some()
4053 {
4054 return None;
4055 }
4056 let job = state.queue.pop_front()?;
4057 let owner = self.fresh_owner_token();
4058 state.owner = Some(owner);
4059 (job, owner)
4060 };
4061 Some((job, self.enter_blocking_owner(owner, true)))
4062 }
4063
4064 #[cfg(napi_runtime_os_threads)]
4065 fn run_one_blocking(&self) -> bool {
4066 let Some((job, blocking)) = self.take_queued_blocking() else {
4067 return false;
4068 };
4069 let _generation = RuntimeGenerationGuard::enter(self.generation);
4070 {
4071 let _task = self.metrics.blocking_started(true);
4072 let _ = catch_unwind_contained(job);
4073 }
4074 drop(blocking);
4075 true
4076 }
4077
4078 #[cfg(napi_runtime_os_threads)]
4079 fn service_queued_blocking_work(&self) {
4080 while self.run_one_blocking() {}
4081 }
4082
4083 #[cfg(napi_runtime_os_threads)]
4084 fn has_serviceable_blocking_work(&self) -> bool {
4085 let state = self
4086 .blocking_admission
4087 .state
4088 .lock()
4089 .unwrap_or_else(std::sync::PoisonError::into_inner);
4090 !state.closed && state.owner.is_none() && !state.queue.is_empty()
4091 }
4092
4093 #[cfg(napi_runtime_os_threads)]
4094 fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
4095 let Some(ambient_owner) = BLOCKING_OWNER
4096 .with(std::cell::Cell::get)
4097 .filter(|owner| owner.executor_id == self.id)
4098 else {
4099 return false;
4100 };
4101 let Some(current) = dependency.select_for_owner(ambient_owner) else {
4102 return false;
4103 };
4104 let job = {
4105 let mut state = self
4106 .blocking_admission
4107 .state
4108 .lock()
4109 .unwrap_or_else(std::sync::PoisonError::into_inner);
4110 #[cfg(all(test, napi_runtime_os_threads))]
4111 run_current_thread_blocking_claim_test_hook();
4112 if self.stop.is_stopping()
4113 || state.closed
4114 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4115 || state.owner != Some(ambient_owner)
4116 {
4117 return false;
4118 }
4119 if dependency.try_claim(¤t) {
4120 state.queue.remove(current.publication.dependency.job)
4121 } else {
4122 None
4123 }
4124 };
4125 let Some(job) = job else {
4126 return false;
4127 };
4128 let _generation = RuntimeGenerationGuard::enter(self.generation);
4129 let _task = self.metrics.blocking_started(false);
4130 let _ = catch_unwind_contained(job);
4131 true
4132 }
4133
4134 fn close_blocking_admission(&self) {
4135 #[cfg(napi_runtime_os_threads)]
4136 let queued = {
4137 let mut state = self
4138 .blocking_admission
4139 .state
4140 .lock()
4141 .unwrap_or_else(std::sync::PoisonError::into_inner);
4142 state.closed = true;
4143 state.queue.closed = true;
4144 state.queue.take_all()
4145 };
4146 #[cfg(not(napi_runtime_os_threads))]
4147 {
4148 let mut state = self
4149 .blocking_admission
4150 .state
4151 .lock()
4152 .unwrap_or_else(std::sync::PoisonError::into_inner);
4153 state.closed = true;
4154 }
4155 #[cfg(napi_runtime_os_threads)]
4156 for job in queued {
4157 let _ = catch_unwind_contained(|| drop(job));
4158 }
4159 }
4160
4161 #[cfg(test)]
4162 fn with_task_dispatch(
4163 metrics: Arc<RuntimeMetrics>,
4164 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4165 ) -> Self {
4166 Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4167 }
4168
4169 #[cfg(test)]
4170 fn with_task_dispatch_registry(
4171 metrics: Arc<RuntimeMetrics>,
4172 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4173 task_drivers: &'static CurrentThreadTaskDriverRegistry,
4174 ) -> Self {
4175 let mut executor = Self::new(metrics);
4176 executor.task_dispatch = task_dispatch;
4177 executor.task_drivers = task_drivers;
4178 executor
4179 }
4180
4181 fn schedule(self: &Arc<Self>, runnable: Runnable) {
4186 let _scope = CurrentThreadScheduleScope::enter();
4187 self.schedule_runnable(runnable);
4188 }
4189
4190 fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4197 self.schedule_runnable(runnable);
4198 }
4199
4200 fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4201 self.metrics.runnable_scheduled();
4202 {
4203 let mut queue = self
4204 .queue
4205 .lock()
4206 .unwrap_or_else(std::sync::PoisonError::into_inner);
4207 if queue.closed {
4212 self
4219 .metrics
4220 .queued_runnables
4221 .fetch_sub(1, Ordering::Relaxed);
4222 queue.rejected.push(runnable);
4223 drop(queue);
4224 if let Some(work) = self.generation_work.load_full() {
4225 work.notify_idle_waiters();
4226 }
4227 return;
4228 }
4229 if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4230 self
4234 .metrics
4235 .queued_runnables
4236 .fetch_sub(1, Ordering::Relaxed);
4237 queue.rejected.push(runnable);
4238 return;
4239 }
4240 queue.runnables.push_back(runnable);
4241 }
4242 self.stop.wake_all();
4246 if !self.draining.load(Ordering::Acquire) {
4247 self.request_drain();
4248 }
4249 }
4250
4251 fn request_drain(self: &Arc<Self>) {
4260 let _ = self.request_drain_with(next_current_thread_dispatch_id);
4261 }
4262
4263 fn lock_scheduler_for_publication(&self) -> MutexGuard<'_, CurrentThreadSchedulerState> {
4264 #[cfg(all(test, napi_runtime_os_threads))]
4265 match self.scheduler_idle_lock.try_lock() {
4266 Ok(scheduler) => scheduler,
4267 Err(std::sync::TryLockError::WouldBlock) => {
4268 run_current_thread_dispatch_publication_blocked_test_hook();
4269 self
4270 .scheduler_idle_lock
4271 .lock()
4272 .unwrap_or_else(std::sync::PoisonError::into_inner)
4273 }
4274 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4275 }
4276 #[cfg(any(not(test), not(napi_runtime_os_threads)))]
4277 self
4278 .scheduler_idle_lock
4279 .lock()
4280 .unwrap_or_else(std::sync::PoisonError::into_inner)
4281 }
4282
4283 fn request_drain_with(
4284 self: &Arc<Self>,
4285 next_dispatch: impl FnOnce() -> u64,
4286 ) -> CurrentThreadDispatchRequest {
4287 let mut scheduler = self.lock_scheduler_for_publication();
4288 if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4289 return CurrentThreadDispatchRequest::Unavailable;
4290 }
4291 if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4292 return CurrentThreadDispatchRequest::Coalesced;
4293 }
4294 let dispatch = next_dispatch();
4295 if self
4296 .dispatch_pending
4297 .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4298 .is_err()
4299 {
4300 return CurrentThreadDispatchRequest::Coalesced;
4301 }
4302 let dispatch_call = self
4303 .begin_host_dispatch_publication(&mut scheduler, dispatch)
4304 .expect("a fresh CurrentThread dispatch must own its publication");
4305 drop(scheduler);
4306 match self.publish_host_dispatch(dispatch, dispatch_call) {
4307 CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4308 CurrentThreadDispatchRequest::Accepted
4309 }
4310 CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4311 }
4312 }
4313
4314 fn claim_dispatch_call<'a>(
4315 &'a self,
4316 scheduler: &mut CurrentThreadSchedulerState,
4317 ) -> CurrentThreadDispatchCall<'a> {
4318 let generation = RuntimeGenerationGuard::enter(self.generation);
4319 let active_dispatch_calls = scheduler
4320 .active_dispatch_calls
4321 .checked_add(1)
4322 .expect("CurrentThread active dispatch-call count exhausted");
4323 scheduler.active_dispatch_calls = active_dispatch_calls;
4324 CurrentThreadDispatchCall {
4325 executor: self,
4326 publication: None,
4327 _generation: generation,
4328 }
4329 }
4330
4331 fn begin_host_dispatch_publication<'a>(
4332 &'a self,
4333 scheduler: &mut CurrentThreadSchedulerState,
4334 dispatch: u64,
4335 ) -> Option<CurrentThreadDispatchCall<'a>> {
4336 if let Some(publication) = scheduler
4338 .dispatch_publications
4339 .iter_mut()
4340 .find(|publication| publication.dispatch == dispatch)
4341 {
4342 publication.republish_requested = true;
4343 return None;
4344 }
4345 let mut dispatch_call = self.claim_dispatch_call(scheduler);
4346 scheduler
4347 .dispatch_publications
4348 .push(CurrentThreadDispatchPublication {
4349 dispatch,
4350 republish_requested: false,
4351 });
4352 dispatch_call.publication = Some(dispatch);
4353 Some(dispatch_call)
4354 }
4355
4356 fn publish_host_dispatch(
4357 self: &Arc<Self>,
4358 dispatch: u64,
4359 mut dispatch_call: CurrentThreadDispatchCall<'_>,
4360 ) -> CurrentThreadHostDispatchResult {
4361 let mut panic_payload = RetainedPanicPayload::default();
4362 loop {
4363 let result = match catch_unwind(AssertUnwindSafe(|| {
4364 #[cfg(all(test, napi_runtime_os_threads))]
4365 run_current_thread_dispatch_call_test_hook();
4366 let result = (self.task_dispatch)(dispatch);
4367 #[cfg(all(test, napi_runtime_os_threads))]
4368 run_current_thread_dispatch_returned_test_hook();
4369 result
4370 })) {
4371 Ok(result) => Some(result),
4372 Err(payload) => {
4373 panic_payload.capture(payload);
4374 None
4375 }
4376 };
4377
4378 let step = catch_unwind(AssertUnwindSafe(|| {
4379 let (republish, cancelling, failure, result) = {
4380 let mut scheduler = self
4381 .scheduler_idle_lock
4382 .lock()
4383 .unwrap_or_else(std::sync::PoisonError::into_inner);
4384 let index = scheduler
4385 .dispatch_publications
4386 .iter()
4387 .position(|publication| publication.dispatch == dispatch)
4388 .expect("the CurrentThread dispatch publication must remain registered");
4389 let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4390 let republish = dispatch_remains_pending
4391 && !self.stop.is_stopping()
4392 && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4393 && scheduler.dispatch_publications[index].republish_requested;
4394 if republish {
4395 if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4396 let _ = self.task_drivers.rearm_failed_publication(failure);
4397 }
4398 scheduler.dispatch_publications[index].republish_requested = false;
4399 (true, None, None, result)
4400 } else {
4401 dispatch_call.finish_publication(&mut scheduler);
4402 let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4403 #[cfg(all(test, napi_runtime_os_threads))]
4404 run_current_thread_failed_publication_retired_test_hook();
4405 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4406 } else {
4407 None
4408 };
4409 let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4410 && dispatch_remains_pending)
4411 .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4412 .flatten();
4413 let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4414 && !dispatch_remains_pending
4415 {
4416 Some(CurrentThreadHostDispatchResult::Accepted)
4419 } else {
4420 result
4421 };
4422 (false, cancelling, failure, result)
4423 }
4424 };
4425
4426 if republish {
4427 return CurrentThreadDispatchPublicationStep::Republish;
4428 }
4429 if let Some(cancelling) = cancelling {
4430 self.cancel_work_after_host_dispatch_failure(cancelling);
4431 }
4432 if let Some(failure) = failure {
4433 self.run_host_dispatch_failure(failure.action);
4434 }
4435 CurrentThreadDispatchPublicationStep::Complete(result)
4436 }));
4437
4438 match step {
4439 Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4440 Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4441 if panic_payload.is_some() {
4442 panic_payload.resume();
4443 }
4444 return result
4445 .expect("a CurrentThread host dispatch without a result must resume its panic");
4446 }
4447 Err(payload) => {
4448 panic_payload.capture(payload);
4449 panic_payload.resume();
4450 }
4451 }
4452 }
4453 }
4454
4455 fn claim_host_turn(
4456 self: &Arc<Self>,
4457 scheduler: &mut CurrentThreadSchedulerState,
4458 ) -> CurrentThreadHostTurn {
4459 scheduler.active_host_turns = scheduler
4460 .active_host_turns
4461 .checked_add(1)
4462 .expect("CurrentThread active host-turn count exhausted");
4463 self.draining.store(true, Ordering::Release);
4464 CurrentThreadHostTurn {
4465 executor: Arc::clone(self),
4466 draining: true,
4467 _generation: RuntimeGenerationGuard::enter(self.generation),
4468 }
4469 }
4470
4471 fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4472 let mut scheduler = self
4473 .scheduler_idle_lock
4474 .lock()
4475 .unwrap_or_else(std::sync::PoisonError::into_inner);
4476 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4477 return None;
4478 }
4479 if dispatch == 0
4480 || self
4481 .dispatch_pending
4482 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4483 .is_err()
4484 {
4485 return None;
4486 }
4487 #[cfg(all(test, napi_runtime_os_threads))]
4488 run_current_thread_dispatch_claim_test_hook();
4489 let _ =
4490 self
4491 .recovery_dispatch
4492 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4493 Some(self.claim_host_turn(&mut scheduler))
4494 }
4495
4496 #[cfg(test)]
4497 fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
4498 let mut scheduler = self
4499 .scheduler_idle_lock
4500 .lock()
4501 .unwrap_or_else(std::sync::PoisonError::into_inner);
4502 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4503 return None;
4504 }
4505 Some(self.claim_host_turn(&mut scheduler))
4506 }
4507
4508 #[cfg(test)]
4509 fn drive_host_turn(self: &Arc<Self>) {
4510 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4511 let host_turn = if dispatch == 0 {
4512 self.try_admit_host_turn_without_dispatch()
4513 } else {
4514 self.try_admit_host_turn(dispatch)
4515 };
4516 if let Some(mut host_turn) = host_turn {
4517 host_turn.drive();
4518 }
4519 }
4520
4521 #[cfg(test)]
4522 fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4523 if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4524 host_turn.drive();
4525 }
4526 }
4527
4528 fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4532 self.retire_rejected_work_off_waker_stack();
4535 #[cfg(napi_runtime_os_threads)]
4536 let mut runnable_streak = 0usize;
4537 for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4538 #[cfg(napi_runtime_os_threads)]
4539 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4540 runnable_streak = 0;
4541 if self.run_one_blocking() {
4542 continue;
4543 }
4544 }
4545 if self.drain_one() {
4546 #[cfg(napi_runtime_os_threads)]
4547 {
4548 runnable_streak += 1;
4549 }
4550 continue;
4551 }
4552 #[cfg(napi_runtime_os_threads)]
4553 if self.run_one_blocking() {
4554 runnable_streak = 0;
4555 continue;
4556 }
4557 break;
4558 }
4559 host_turn.release_draining();
4564 self.request_drain_if_work_remains();
4565 }
4566
4567 fn request_drain_if_queued(self: &Arc<Self>) {
4568 self.retire_rejected_work_off_waker_stack();
4569 if !self.has_serviceable_work() {
4570 return;
4571 }
4572 let mut scheduler = self.lock_scheduler_for_publication();
4573 if self.stop.is_stopping()
4574 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4575 || self.draining.load(Ordering::Acquire)
4576 {
4577 return;
4578 }
4579 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4580 if dispatch == 0 {
4581 drop(scheduler);
4582 self.request_drain_if_work_remains();
4583 return;
4584 }
4585
4586 let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4590 return;
4591 };
4592 drop(scheduler);
4593 let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4594 }
4595
4596 fn retire_unavailable_host_dispatch_locked(
4597 &self,
4598 dispatch: u64,
4599 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4600 if self
4601 .dispatch_pending
4602 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4603 .is_err()
4604 {
4605 return None;
4606 }
4607 if self
4608 .recovery_dispatch
4609 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4610 .is_ok()
4611 {
4612 self.begin_failed_dispatch_cancellation()
4613 } else {
4614 None
4615 }
4616 }
4617
4618 fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4619 let failure = {
4621 let mut scheduler = self
4622 .scheduler_idle_lock
4623 .lock()
4624 .unwrap_or_else(std::sync::PoisonError::into_inner);
4625 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4626 };
4627 if let Some(failure) = failure {
4628 self.run_host_dispatch_failure(failure.action);
4629 }
4630 self.retire_rejected_work_off_waker_stack();
4631 }
4632
4633 fn begin_host_dispatch_failure_locked<'a>(
4634 &'a self,
4635 scheduler: &mut CurrentThreadSchedulerState,
4636 failure: CurrentThreadTaskDispatchFailure,
4637 ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4638 if !self.task_drivers.is_current_failure(failure) {
4639 return None;
4640 }
4641 let registry_cancellation =
4642 CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4643 let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4644 Some(CurrentThreadDispatchFailureRecovery {
4645 action,
4646 _registry_cancellation: registry_cancellation,
4647 })
4648 }
4649
4650 fn begin_host_dispatch_failure_transition_locked<'a>(
4651 &'a self,
4652 scheduler: &mut CurrentThreadSchedulerState,
4653 dispatch: u64,
4654 ) -> CurrentThreadDispatchFailureAction<'a> {
4655 if dispatch == 0
4656 || self
4657 .dispatch_pending
4658 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4659 .is_err()
4660 {
4661 return CurrentThreadDispatchFailureAction::None;
4662 }
4663 #[cfg(all(test, napi_runtime_os_threads))]
4664 run_current_thread_dispatch_recovery_test_hook();
4665
4666 if self
4667 .recovery_dispatch
4668 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4669 .is_ok()
4670 {
4671 return self.begin_failed_dispatch_cancellation().map_or(
4675 CurrentThreadDispatchFailureAction::None,
4676 CurrentThreadDispatchFailureAction::Cancel,
4677 );
4678 }
4679
4680 if self.stop.is_stopping()
4681 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4682 || self.draining.load(Ordering::Acquire)
4683 || !self.has_serviceable_work()
4684 {
4685 return CurrentThreadDispatchFailureAction::None;
4686 }
4687
4688 let replacement = next_current_thread_dispatch_id();
4692 self.recovery_dispatch.store(replacement, Ordering::Release);
4693 self.dispatch_pending.store(replacement, Ordering::Release);
4694 let dispatch_call = self
4695 .begin_host_dispatch_publication(scheduler, replacement)
4696 .expect("a reserved CurrentThread replacement must own its publication");
4697 CurrentThreadDispatchFailureAction::Publish {
4698 dispatch: replacement,
4699 call: dispatch_call,
4700 }
4701 }
4702
4703 #[cfg(test)]
4704 fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4705 let failure = {
4706 let mut scheduler = self
4707 .scheduler_idle_lock
4708 .lock()
4709 .unwrap_or_else(std::sync::PoisonError::into_inner);
4710 self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4711 };
4712 self.run_host_dispatch_failure(failure);
4713 }
4714
4715 fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4716 match failure {
4717 CurrentThreadDispatchFailureAction::None => {}
4718 CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4719 self.cancel_work_after_host_dispatch_failure(cancelling);
4720 }
4721 CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4722 let _ = self.publish_host_dispatch(dispatch, call);
4723 }
4724 }
4725 }
4726
4727 fn has_serviceable_work(&self) -> bool {
4728 let has_queued_runnable = {
4729 let queue = self
4730 .queue
4731 .lock()
4732 .unwrap_or_else(std::sync::PoisonError::into_inner);
4733 !queue.closed && !queue.runnables.is_empty()
4734 };
4735 #[cfg(napi_runtime_os_threads)]
4736 let has_queued_blocking = self.has_serviceable_blocking_work();
4737 #[cfg(not(napi_runtime_os_threads))]
4738 let has_queued_blocking = false;
4739 has_queued_runnable || has_queued_blocking
4740 }
4741
4742 fn request_drain_if_work_remains(self: &Arc<Self>) {
4743 if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4744 self.request_drain();
4745 }
4746 }
4747
4748 fn begin_failed_dispatch_cancellation(
4749 &self,
4750 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4751 if self
4752 .cancelling_failed_dispatch
4753 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4754 .is_err()
4755 {
4756 return None;
4757 }
4758 self.dispatch_pending.store(0, Ordering::Release);
4759 self.recovery_dispatch.store(0, Ordering::Release);
4760 Some(CurrentThreadDispatchCancellationGuard(self))
4761 }
4762
4763 fn cancel_work_after_host_dispatch_failure(
4802 self: &Arc<Self>,
4803 cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4804 ) {
4805 let mut cancelling = Some(cancelling);
4806 #[cfg(napi_runtime_os_threads)]
4809 let queued_blocking = {
4810 let mut state = self
4811 .blocking_admission
4812 .state
4813 .lock()
4814 .unwrap_or_else(std::sync::PoisonError::into_inner);
4815 state.queue.take_all()
4816 };
4817 {
4818 let mut queue = self
4819 .queue
4820 .lock()
4821 .unwrap_or_else(std::sync::PoisonError::into_inner);
4822 let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4823 self
4825 .metrics
4826 .queued_runnables
4827 .fetch_sub(moved, Ordering::Relaxed);
4828 let runnables = std::mem::take(&mut queue.runnables);
4829 queue.rejected.extend(runnables);
4830 #[cfg(napi_runtime_os_threads)]
4831 queue.rejected_blocking.extend(queued_blocking);
4832 if CurrentThreadScheduleScope::is_active() {
4833 drop(cancelling.take());
4838 drop(queue);
4839 if let Some(work) = self.generation_work.load_full() {
4842 work.notify_idle_waiters();
4843 }
4844 self.request_rejected_work_retirement();
4848 return;
4849 }
4850 }
4851
4852 let _generation = RuntimeGenerationGuard::enter(self.generation);
4858 loop {
4859 #[cfg(napi_runtime_os_threads)]
4860 let (rejected, rejected_blocking) = {
4861 let mut queue = self
4862 .queue
4863 .lock()
4864 .unwrap_or_else(std::sync::PoisonError::into_inner);
4865 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4866 drop(cancelling.take());
4867 break;
4868 }
4869 (
4870 std::mem::take(&mut queue.rejected),
4871 std::mem::take(&mut queue.rejected_blocking),
4872 )
4873 };
4874 #[cfg(not(napi_runtime_os_threads))]
4875 let rejected = {
4876 let mut queue = self
4877 .queue
4878 .lock()
4879 .unwrap_or_else(std::sync::PoisonError::into_inner);
4880 if queue.rejected.is_empty() {
4881 drop(cancelling.take());
4882 break;
4883 }
4884 std::mem::take(&mut queue.rejected)
4885 };
4886 for runnable in rejected {
4887 let _ = catch_unwind_contained(|| drop(runnable));
4888 }
4889 #[cfg(napi_runtime_os_threads)]
4890 for job in rejected_blocking {
4891 let _ = catch_unwind_contained(|| drop(job));
4892 }
4893 }
4894 }
4895
4896 fn take_runnable(&self) -> Option<Runnable> {
4897 let scheduler = self
4902 .scheduler_idle_lock
4903 .lock()
4904 .unwrap_or_else(std::sync::PoisonError::into_inner);
4905 #[cfg(all(test, napi_runtime_os_threads))]
4906 run_current_thread_runnable_claim_test_hook();
4907 let stop_publication = self.stop.publication_guard();
4908 if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4909 return None;
4910 }
4911 let runnable = {
4912 let mut queue = self
4913 .queue
4914 .lock()
4915 .unwrap_or_else(std::sync::PoisonError::into_inner);
4916 if queue.closed {
4917 None
4918 } else {
4919 queue.runnables.pop_front()
4920 }
4921 };
4922 drop(stop_publication);
4923 drop(scheduler);
4924 runnable
4925 }
4926
4927 fn drain_one(&self) -> bool {
4928 let runnable = self.take_runnable();
4929 if let Some(runnable) = runnable {
4930 #[cfg(napi_runtime_os_threads)]
4931 let _non_owner = BlockingOwnerGuard::enter(None);
4932 run_runnable(&self.metrics, runnable);
4933 true
4934 } else {
4935 false
4936 }
4937 }
4938
4939 fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4961 let parker = Arc::new(DriverParker::default());
4962 let _stop_registration = self.stop.register(&parker);
4963 #[cfg(napi_runtime_os_threads)]
4964 let dependency = BlockOnDependencyGuard::enter(self.generation);
4965 let waker = std::task::Waker::from(Arc::clone(&parker));
4966 let mut cx = Context::from_waker(&waker);
4967 #[cfg(napi_runtime_os_threads)]
4968 let mut runnable_streak = 0usize;
4969 loop {
4970 if self.stop.is_stopping() {
4971 return BlockOnOutcome::Stopped;
4972 }
4973 #[cfg(napi_runtime_os_threads)]
4974 dependency.clear();
4975 on_thread_handoff();
4976 if future.as_mut().poll(&mut cx).is_ready() {
4977 return BlockOnOutcome::Completed;
4978 }
4979 if self.stop.is_stopping() {
4983 return BlockOnOutcome::Stopped;
4984 }
4985 #[cfg(napi_runtime_os_threads)]
4986 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4987 runnable_streak = 0;
4988 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4989 continue;
4990 }
4991 }
4992 if self.drain_one() {
4993 #[cfg(napi_runtime_os_threads)]
4994 {
4995 runnable_streak += 1;
4996 }
4997 continue;
4998 }
4999 #[cfg(napi_runtime_os_threads)]
5000 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
5001 runnable_streak = 0;
5002 continue;
5003 }
5004 #[cfg(napi_runtime_os_threads)]
5005 {
5006 runnable_streak = 0;
5007 }
5008 if parker.consume_permit() {
5012 continue;
5013 }
5014 if self.threadless {
5026 if self.host_timers.has_pending() {
5027 std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
5028 }
5029 std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
5030 }
5031 match self.park_deadline {
5032 None => parker.park(),
5036 Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
5037 DeadlineParkOutcome::Woken => {}
5038 DeadlineParkOutcome::Expired(armed) => {
5039 if parker.consume_permit()
5054 || self.metrics.progress_fingerprint() != armed
5055 || self.host_timers.has_live_wait(deadline)
5056 {
5057 continue;
5058 }
5059 std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
5060 }
5061 },
5062 }
5063 }
5064 }
5065
5066 fn begin_shutdown(&self) {
5067 let _generation = RuntimeGenerationGuard::enter(self.generation);
5068 self.stop.begin_shutdown();
5069 self.close_blocking_admission();
5070 let (queued, rejected) = {
5071 let mut queue = self
5072 .queue
5073 .lock()
5074 .unwrap_or_else(std::sync::PoisonError::into_inner);
5075 queue.closed = true;
5076 #[cfg(napi_runtime_os_threads)]
5077 let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
5078 let rejected = std::mem::take(&mut queue.rejected);
5079 #[cfg(napi_runtime_os_threads)]
5080 let rejected = (rejected, rejected_blocking);
5081 (std::mem::take(&mut queue.runnables), rejected)
5082 };
5083 self.dispatch_pending.store(0, Ordering::Release);
5084 self.recovery_dispatch.store(0, Ordering::Release);
5085 self.task_drivers.retire_for_shutdown();
5086 for runnable in queued {
5087 self
5088 .metrics
5089 .queued_runnables
5090 .fetch_sub(1, Ordering::Relaxed);
5091 let _ = catch_unwind_contained(|| drop(runnable));
5094 }
5095 #[cfg(napi_runtime_os_threads)]
5098 let (rejected, rejected_blocking) = rejected;
5099 for runnable in rejected {
5100 let _ = catch_unwind_contained(|| drop(runnable));
5101 }
5102 #[cfg(napi_runtime_os_threads)]
5103 for job in rejected_blocking {
5104 let _ = catch_unwind_contained(|| drop(job));
5105 }
5106 self.host_timers.shutdown();
5107 }
5108
5109 fn wait_until_scheduler_idle(&self) {
5110 self.retire_rejected_work();
5113 let mut idle = self
5114 .scheduler_idle_lock
5115 .lock()
5116 .unwrap_or_else(std::sync::PoisonError::into_inner);
5117 while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
5118 idle = self
5119 .scheduler_idle
5120 .wait(idle)
5121 .unwrap_or_else(std::sync::PoisonError::into_inner);
5122 }
5123 debug_assert!(
5124 idle.dispatch_publications.is_empty(),
5125 "an idle CurrentThread scheduler must not retain dispatch publications"
5126 );
5127 }
5128}
5129
5130#[cfg(napi_runtime_os_threads)]
5143thread_local! {
5144 static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5145 static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5149}
5150
5151#[cfg(napi_runtime_os_threads)]
5157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5158struct SchedulerDriverRole {
5159 executor_id: u64,
5160 can_run_blocking: bool,
5161}
5162
5163#[cfg(napi_runtime_os_threads)]
5164thread_local! {
5165 static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5166 const { std::cell::Cell::new(None) };
5167}
5168
5169#[cfg(napi_runtime_os_threads)]
5186thread_local! {
5187 static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5188 const { std::cell::Cell::new(None) };
5189}
5190
5191#[cfg(napi_runtime_os_threads)]
5192struct OnPoolWorkerGuard {
5193 previous_worker: Option<u64>,
5194 previous_driver: Option<SchedulerDriverRole>,
5195}
5196
5197#[cfg(napi_runtime_os_threads)]
5198impl OnPoolWorkerGuard {
5199 fn enter(id: u64) -> Self {
5203 Self::enter_with_role(id, true)
5204 }
5205
5206 fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5207 Self {
5208 previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5209 previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5210 flag.replace(Some(SchedulerDriverRole {
5211 executor_id: id,
5212 can_run_blocking,
5213 }))
5214 }),
5215 }
5216 }
5217}
5218
5219#[cfg(napi_runtime_os_threads)]
5220impl Drop for OnPoolWorkerGuard {
5221 fn drop(&mut self) {
5222 IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5223 ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5224 }
5225}
5226
5227#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5240struct BlockingOwnerToken {
5241 executor_id: u64,
5242 frame: u64,
5243}
5244
5245#[cfg(napi_runtime_os_threads)]
5246enum OwnedBlockingOutcome {
5247 Ineligible,
5248 OwnerBusy(BlockingOwnerToken),
5249 ReservationReleased {
5250 owner: BlockingOwnerToken,
5251 ran: bool,
5252 },
5253}
5254
5255static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5258#[cfg(napi_runtime_os_threads)]
5259static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5260
5261thread_local! {
5264 static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5265 const { std::cell::Cell::new(None) };
5266}
5267
5268#[cfg(napi_runtime_os_threads)]
5275struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5276
5277#[cfg(napi_runtime_os_threads)]
5278impl BlockingOwnerGuard {
5279 fn enter(token: Option<BlockingOwnerToken>) -> Self {
5280 Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5281 }
5282}
5283
5284#[cfg(napi_runtime_os_threads)]
5285impl Drop for BlockingOwnerGuard {
5286 fn drop(&mut self) {
5287 BLOCKING_OWNER.with(|cell| cell.set(self.0));
5288 }
5289}
5290
5291#[cfg(napi_runtime_os_threads)]
5292#[derive(Default)]
5293struct ActiveBlockingOwners {
5294 owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5298}
5299
5300#[cfg(napi_runtime_os_threads)]
5301impl ActiveBlockingOwners {
5302 fn register(&self, owner: BlockingOwnerToken) {
5303 let previous = self
5304 .owners
5305 .lock()
5306 .unwrap_or_else(std::sync::PoisonError::into_inner)
5307 .insert(owner, None);
5308 debug_assert!(previous.is_none());
5309 }
5310
5311 fn unregister(&self, owner: BlockingOwnerToken) {
5312 let removed = self
5313 .owners
5314 .lock()
5315 .unwrap_or_else(std::sync::PoisonError::into_inner)
5316 .remove(&owner);
5317 debug_assert!(removed.is_some());
5318 }
5319
5320 fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5321 let mut owners = self
5322 .owners
5323 .lock()
5324 .unwrap_or_else(std::sync::PoisonError::into_inner);
5325 let reservation = owners.get_mut(&owner)?;
5326 if reservation.is_some() {
5327 return None;
5328 }
5329 let id = next_unique_id(
5330 &NEXT_OWNER_RESERVATION,
5331 "blocking owner reservation id space exhausted",
5332 );
5333 *reservation = Some(id);
5334 Some(id)
5335 }
5336
5337 fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5338 let mut owners = self
5339 .owners
5340 .lock()
5341 .unwrap_or_else(std::sync::PoisonError::into_inner);
5342 if let Some(current) = owners.get_mut(&owner)
5343 && *current == Some(reservation)
5344 {
5345 *current = None;
5346 }
5347 }
5348
5349 fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5350 self
5351 .owners
5352 .lock()
5353 .unwrap_or_else(std::sync::PoisonError::into_inner)
5354 .get(&owner)
5355 .is_some_and(Option::is_none)
5356 }
5357}
5358
5359#[cfg(napi_runtime_os_threads)]
5360struct ActiveBlockingOwnerGuard<'a> {
5361 owners: &'a ActiveBlockingOwners,
5362 owner: BlockingOwnerToken,
5363}
5364
5365#[cfg(napi_runtime_os_threads)]
5366impl<'a> ActiveBlockingOwnerGuard<'a> {
5367 fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5368 owners.register(owner);
5369 Self { owners, owner }
5370 }
5371}
5372
5373#[cfg(napi_runtime_os_threads)]
5374impl Drop for ActiveBlockingOwnerGuard<'_> {
5375 fn drop(&mut self) {
5376 self.owners.unregister(self.owner);
5377 }
5378}
5379
5380#[cfg(napi_runtime_os_threads)]
5381struct OwnerLaneReservation<'a> {
5382 owners: &'a ActiveBlockingOwners,
5383 owner: BlockingOwnerToken,
5384 reservation: u64,
5385}
5386
5387#[cfg(napi_runtime_os_threads)]
5388impl<'a> OwnerLaneReservation<'a> {
5389 fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5390 let reservation = owners.reserve(owner)?;
5391 Some(Self {
5392 owners,
5393 owner,
5394 reservation,
5395 })
5396 }
5397}
5398
5399#[cfg(napi_runtime_os_threads)]
5400impl Drop for OwnerLaneReservation<'_> {
5401 fn drop(&mut self) {
5402 self.owners.release(self.owner, self.reservation);
5403 }
5404}
5405
5406#[cfg(napi_runtime_os_threads)]
5410type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5411
5412#[cfg(napi_runtime_os_threads)]
5413struct QueuedBlocking {
5414 id: BlockingJobId,
5415 run: BlockingClosure,
5416}
5417
5418#[cfg(napi_runtime_os_threads)]
5419struct BlockingQueueNode {
5420 previous: Option<BlockingJobId>,
5421 next: Option<BlockingJobId>,
5422 run: Box<dyn FnOnce() + Send + 'static>,
5423}
5424
5425#[cfg(napi_runtime_os_threads)]
5426struct BlockingQueue {
5427 closed: bool,
5428 head: Option<BlockingJobId>,
5429 tail: Option<BlockingJobId>,
5430 jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5431}
5432
5433#[cfg(napi_runtime_os_threads)]
5434impl BlockingQueue {
5435 fn push(&mut self, job: QueuedBlocking) {
5436 let previous = self.tail;
5437 let replaced = self.jobs.insert(
5438 job.id,
5439 BlockingQueueNode {
5440 previous,
5441 next: None,
5442 run: job.run,
5443 },
5444 );
5445 debug_assert!(replaced.is_none());
5446 if let Some(previous) = previous {
5447 self
5448 .jobs
5449 .get_mut(&previous)
5450 .expect("blocking queue tail must be indexed")
5451 .next = Some(job.id);
5452 } else {
5453 self.head = Some(job.id);
5454 }
5455 self.tail = Some(job.id);
5456 }
5457
5458 fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5459 self.head.and_then(|id| self.remove(id))
5460 }
5461
5462 fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5463 let node = self.jobs.remove(&id)?;
5464 if let Some(previous) = node.previous {
5465 self
5466 .jobs
5467 .get_mut(&previous)
5468 .expect("blocking queue predecessor must be indexed")
5469 .next = node.next;
5470 } else {
5471 self.head = node.next;
5472 }
5473 if let Some(next) = node.next {
5474 self
5475 .jobs
5476 .get_mut(&next)
5477 .expect("blocking queue successor must be indexed")
5478 .previous = node.previous;
5479 } else {
5480 self.tail = node.previous;
5481 }
5482 Some(node.run)
5483 }
5484
5485 fn is_empty(&self) -> bool {
5486 debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5487 debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5488 self.jobs.is_empty()
5489 }
5490
5491 fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5492 self.head = None;
5493 self.tail = None;
5494 std::mem::take(&mut self.jobs)
5495 .into_values()
5496 .map(|node| node.run)
5497 .collect()
5498 }
5499}
5500
5501#[cfg(napi_runtime_os_threads)]
5502struct WorkerLifecycle {
5503 remaining: Mutex<usize>,
5504 handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5505}
5506
5507#[cfg(napi_runtime_os_threads)]
5508impl WorkerLifecycle {
5509 fn new(worker_threads: usize) -> Arc<Self> {
5510 Arc::new(Self {
5511 remaining: Mutex::new(worker_threads),
5512 handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5513 })
5514 }
5515
5516 fn retain(&self, handle: std::thread::JoinHandle<()>) {
5517 self
5518 .handles
5519 .lock()
5520 .unwrap_or_else(std::sync::PoisonError::into_inner)
5521 .as_mut()
5522 .expect("runtime worker handles already joined")
5523 .push(handle);
5524 }
5525
5526 fn worker_exited(&self) {
5527 let mut remaining = self
5528 .remaining
5529 .lock()
5530 .unwrap_or_else(std::sync::PoisonError::into_inner);
5531 *remaining = remaining
5532 .checked_sub(1)
5533 .expect("runtime worker exit count underflow");
5534 }
5535
5536 fn wait_for_all_workers(&self) {
5537 let handles = self
5538 .handles
5539 .lock()
5540 .unwrap_or_else(std::sync::PoisonError::into_inner)
5541 .take()
5542 .expect("runtime worker handles already joined");
5543 for handle in handles {
5544 if let Err(payload) = crate::sync::join(handle) {
5545 discard_panic_payload(payload);
5546 }
5547 }
5548 }
5549
5550 #[cfg(test)]
5551 fn remaining(&self) -> usize {
5552 *self
5553 .remaining
5554 .lock()
5555 .unwrap_or_else(std::sync::PoisonError::into_inner)
5556 }
5557}
5558
5559#[cfg(napi_runtime_os_threads)]
5560struct MultiThreadExecutor {
5561 generation: u64,
5562 stop: Arc<GenerationStop>,
5563 id: u64,
5566 pool: ThreadPool,
5567 injector: Injector<Runnable>,
5574 blocking_queue: Mutex<BlockingQueue>,
5575 active_drainers: AtomicUsize,
5576 active_blocking: AtomicUsize,
5577 scheduler_idle_lock: Mutex<usize>,
5585 scheduler_idle: Condvar,
5586 worker_lifecycle: Arc<WorkerLifecycle>,
5587 parked_drivers: ParkedDrivers,
5595 active_blocking_owners: ActiveBlockingOwners,
5596 #[cfg(test)]
5597 owner_lending_attempts: AtomicUsize,
5598 #[cfg(test)]
5608 drain_linger_registrations: AtomicUsize,
5609 #[cfg(test)]
5610 drain_linger_expiries: AtomicUsize,
5611 max_drainers: usize,
5612 max_blocking: usize,
5613 park_deadline: Option<Duration>,
5620 drain_linger: Option<Duration>,
5625 deadlock_state: Arc<MultiThreadDeadlockState>,
5629 timers: TimerHeap,
5632 metrics: Arc<RuntimeMetrics>,
5633}
5634
5635#[cfg(napi_runtime_os_threads)]
5636impl MultiThreadExecutor {
5637 const RUNNABLE_BUDGET: usize = 64;
5640 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5643
5644 #[cfg(test)]
5645 fn new(
5646 options: &RuntimeOptions,
5647 metrics: Arc<RuntimeMetrics>,
5648 ) -> Result<Self, RuntimeConfigError> {
5649 Self::new_for_generation(
5650 options,
5651 metrics,
5652 u64::MAX,
5653 Arc::new(GenerationStop::default()),
5654 )
5655 }
5656
5657 fn new_for_generation(
5658 options: &RuntimeOptions,
5659 metrics: Arc<RuntimeMetrics>,
5660 generation: u64,
5661 stop: Arc<GenerationStop>,
5662 ) -> Result<Self, RuntimeConfigError> {
5663 let id = next_unique_id(
5664 &NEXT_RUNTIME_EXECUTOR_ID,
5665 "async runtime executor id space exhausted",
5666 );
5667 let thread_name_prefix = options.thread_name_prefix.clone();
5668 let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5669 options.park_deadline.is_some(),
5670 ));
5671 let timers = TimerHeap::new(
5672 generation,
5673 id,
5674 format!("{thread_name_prefix}-timer"),
5675 Arc::clone(&deadlock_state),
5676 );
5677 let pool_threads = options.worker_threads;
5681 let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5682 let exit_lifecycle = Arc::clone(&worker_lifecycle);
5683 let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5684 let pool = ThreadPoolBuilder::new()
5685 .num_threads(pool_threads)
5686 .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5687 .start_handler(move |_| {
5688 ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5689 })
5690 .exit_handler(move |_| {
5691 IN_SCHEDULER_DRIVER.with(|driver| {
5692 if driver.get().is_some_and(|role| role.executor_id == id) {
5693 driver.set(None);
5694 }
5695 });
5696 exit_lifecycle.worker_exited();
5702 })
5703 .spawn_handler(move |thread| {
5704 let mut builder = std::thread::Builder::new();
5705 if let Some(name) = thread.name() {
5706 builder = builder.name(name.to_string());
5707 }
5708 if let Some(stack_size) = thread.stack_size() {
5709 builder = builder.stack_size(stack_size);
5710 }
5711 let handle = builder.spawn(move || thread.run())?;
5712 spawn_lifecycle.retain(handle);
5713 Ok(())
5714 })
5715 .build()
5716 .map_err(|error| {
5717 worker_lifecycle.wait_for_all_workers();
5718 RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5719 })?;
5720 Ok(Self {
5721 generation,
5722 stop,
5723 id,
5724 pool,
5725 injector: Injector::new(),
5726 blocking_queue: Mutex::new(BlockingQueue {
5727 closed: false,
5728 head: None,
5729 tail: None,
5730 jobs: FxHashMap::default(),
5731 }),
5732 active_drainers: AtomicUsize::new(0),
5733 active_blocking: AtomicUsize::new(0),
5734 scheduler_idle_lock: Mutex::new(0),
5735 scheduler_idle: Condvar::new(),
5736 worker_lifecycle,
5737 parked_drivers: ParkedDrivers::default(),
5738 active_blocking_owners: ActiveBlockingOwners::default(),
5739 #[cfg(test)]
5740 owner_lending_attempts: AtomicUsize::new(0),
5741 #[cfg(test)]
5742 drain_linger_registrations: AtomicUsize::new(0),
5743 #[cfg(test)]
5744 drain_linger_expiries: AtomicUsize::new(0),
5745 max_drainers: pool_threads,
5746 max_blocking: options.max_blocking_tasks,
5747 park_deadline: options.park_deadline,
5748 drain_linger: {
5749 let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5753 (!budget.is_zero()).then_some(budget)
5754 },
5755 deadlock_state,
5756 timers,
5757 metrics,
5758 })
5759 }
5760
5761 fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5762 self.deadlock_state.begin_admission()
5763 }
5764
5765 fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5766 self.deadlock_state.try_begin_verdict()
5767 }
5768
5769 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5770 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5772 self.parked_drivers.wake_one_owner_dependency(owner)
5773 }
5774
5775 fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5776 let owner = reservation.owner;
5777 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5781 drop(reservation);
5782 self.parked_drivers.wake_one_owner_dependency(owner)
5783 }
5784
5785 fn schedule(self: &Arc<Self>, runnable: Runnable) {
5786 let mut admission = self.begin_deadlock_admission();
5787 self.metrics.runnable_scheduled();
5790 if IN_SCHEDULER_DRIVER
5810 .with(std::cell::Cell::get)
5811 .is_some_and(|role| role.executor_id == self.id)
5812 && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5813 {
5814 match LIFO_SLOT.with(std::cell::Cell::take) {
5815 None => {
5816 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5817 if let Some(admission) = admission.as_mut() {
5818 admission.mark_published();
5819 }
5820 return;
5821 }
5822 Some((id, displaced)) if id == self.id => {
5826 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5827 self.push_to_queue_and_wake(displaced);
5828 if let Some(admission) = admission.as_mut() {
5829 admission.mark_published();
5830 }
5831 return;
5832 }
5833 Some(foreign) => {
5840 LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5841 }
5842 }
5843 }
5844 self.push_to_queue_and_wake(runnable);
5845 if let Some(admission) = admission.as_mut() {
5846 admission.mark_published();
5847 }
5848 }
5849
5850 fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5854 self.injector.push(runnable);
5855 std::sync::atomic::fence(Ordering::SeqCst);
5864 self.wake_for_new_work();
5865 }
5866
5867 fn wake_for_new_work(self: &Arc<Self>) {
5876 if !self.parked_drivers.wake_one() {
5877 self.ensure_drainer();
5878 }
5879 }
5880
5881 fn wake_for_blocking_work(self: &Arc<Self>) {
5882 if !self.parked_drivers.wake_one_blocking() {
5885 self.ensure_drainer();
5886 }
5887 }
5888
5889 #[cfg(test)]
5890 fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5891 where
5892 F: FnOnce() -> T + Send + 'static,
5893 T: Send + 'static,
5894 {
5895 self.schedule_blocking_result(move || catch_unwind_join_error(function))
5896 }
5897
5898 fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5899 where
5900 F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5901 T: Send + 'static,
5902 {
5903 let mut admission = self.begin_deadlock_admission();
5904 let (sender, receiver) = oneshot::channel();
5905 let generation = self.generation;
5906 let dependency = BlockingDependency {
5907 job: BlockingJobId(next_unique_id(
5908 &NEXT_BLOCKING_JOB_ID,
5909 "blocking job id space exhausted",
5910 )),
5911 owner: BLOCKING_OWNER
5912 .with(std::cell::Cell::get)
5913 .filter(|owner| owner.executor_id == self.id),
5914 };
5915 self.metrics.blocking_scheduled();
5920 let queued = QueuedBlocking {
5921 id: dependency.job,
5922 run: Box::new(move || {
5923 let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5924 let _ = sender.send(result);
5925 }),
5926 };
5927 let rejected = {
5928 let mut queue = self
5929 .blocking_queue
5930 .lock()
5931 .unwrap_or_else(std::sync::PoisonError::into_inner);
5932 if queue.closed {
5933 Some(queued)
5934 } else {
5935 queue.push(queued);
5936 None
5937 }
5938 };
5939 if let Some(rejected) = rejected {
5940 let _generation = RuntimeGenerationGuard::enter(self.generation);
5945 let _ = catch_unwind_contained(|| drop(rejected));
5946 } else {
5947 self.wake_for_blocking_work();
5949 if let Some(admission) = admission.as_mut() {
5950 admission.mark_published();
5951 }
5952 }
5953 JoinHandle(JoinHandleInner::Blocking {
5954 receiver,
5955 dependency,
5956 published: PublishedBlockingDependencies::default(),
5957 })
5958 }
5959
5960 fn ensure_drainer(self: &Arc<Self>) {
5961 loop {
5962 let active = self.active_drainers.load(Ordering::Acquire);
5963 if active >= self.max_drainers {
5964 return;
5965 }
5966 if self
5967 .active_drainers
5968 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5969 .is_ok()
5970 {
5971 let executor = Arc::clone(self);
5972 self.pool.spawn_fifo(move || executor.drain());
5973 return;
5974 }
5975 }
5976 }
5977
5978 fn drain(self: Arc<Self>) {
5979 let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5985 let _on_pool = OnPoolWorkerGuard::enter(self.id);
5988 let _slot_backstop = LifoSlotFlushGuard(&self);
5994
5995 let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5996 let mut linger: Option<DrainLinger> = None;
5999 let mut idle_deadline: Option<Instant> = None;
6005 let mut frame_deadline: Option<Instant> = None;
6011
6012 'frame: loop {
6013 let mut runnable_streak = 0usize;
6014 for _ in 0..Self::RUNNABLE_BUDGET {
6015 if self.run_one_fair(&mut runnable_streak) {
6016 idle_deadline = None;
6017 continue;
6018 }
6019 self.flush_lifo_slot();
6027 let Some(budget) = linger_budget else {
6049 break 'frame;
6050 };
6051 if self.stop.is_stopping() {
6052 break 'frame;
6053 }
6054 let now = Instant::now();
6055 let frame = *frame_deadline.get_or_insert_with(|| {
6061 now
6062 + budget
6063 .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
6064 .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
6065 });
6066 let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
6067 if now >= deadline {
6068 #[cfg(test)]
6075 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6076 break 'frame;
6077 }
6078 let state = match &linger {
6079 Some(state) => state,
6080 None => linger.insert(DrainLinger::new(&self)),
6081 };
6082 self
6083 .parked_drivers
6084 .register_with_role(&state.parker, true, None);
6085 #[cfg(test)]
6086 self
6087 .drain_linger_registrations
6088 .fetch_add(1, Ordering::SeqCst);
6089 if self.has_queued_work_for_role(true) {
6090 self.parked_drivers.deregister(&state.parker);
6091 continue 'frame;
6092 }
6093 if self.stop.is_stopping() {
6098 self.parked_drivers.deregister(&state.parker);
6099 break 'frame;
6100 }
6101 let woken = state.parker.park_timeout(deadline - now);
6102 self.parked_drivers.deregister(&state.parker);
6103 if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
6104 continue 'frame;
6105 }
6106 #[cfg(test)]
6111 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6112 break 'frame;
6113 }
6114 break 'frame;
6119 }
6120
6121 self.flush_lifo_slot();
6124 self.finish_draining();
6125 }
6126
6127 fn pop_lifo_slot(&self) -> Option<Runnable> {
6131 LIFO_SLOT.with(|slot| match slot.take() {
6132 Some((id, runnable)) if id == self.id => Some(runnable),
6133 other => {
6134 slot.set(other);
6135 None
6136 }
6137 })
6138 }
6139
6140 fn flush_lifo_slot(self: &Arc<Self>) {
6147 let mut admission = self.begin_deadlock_admission();
6148 if let Some(runnable) = self.pop_lifo_slot() {
6149 self.push_to_queue_and_wake(runnable);
6150 if let Some(admission) = admission.as_mut() {
6151 admission.mark_published();
6152 }
6153 }
6154 }
6155
6156 fn fresh_owner_token(&self) -> BlockingOwnerToken {
6160 BlockingOwnerToken {
6161 executor_id: self.id,
6162 frame: next_unique_id(
6163 &NEXT_BLOCKING_FRAME,
6164 "blocking owner frame id space exhausted",
6165 ),
6166 }
6167 }
6168
6169 fn finish_draining(self: &Arc<Self>) {
6170 self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6180 let has_runnable = self.has_queued_runnable();
6181 let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6182 && !self
6183 .blocking_queue
6184 .lock()
6185 .unwrap_or_else(std::sync::PoisonError::into_inner)
6186 .is_empty();
6187 if has_runnable || has_blocking {
6188 self.ensure_drainer();
6189 }
6190 let idle = self
6196 .scheduler_idle_lock
6197 .lock()
6198 .unwrap_or_else(std::sync::PoisonError::into_inner);
6199 if *idle > 0 {
6200 self.scheduler_idle.notify_all();
6201 }
6202 }
6203
6204 fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6205 if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6206 self.cooperative_block_on(future)
6214 } else {
6215 self.parking_block_on(future)
6219 }
6220 }
6221
6222 fn parking_block_on(
6223 self: &Arc<Self>,
6224 mut future: Pin<&mut dyn Future<Output = ()>>,
6225 ) -> BlockOnOutcome {
6226 let parker = Arc::new(DriverParker::default());
6227 let _stop_registration = self.stop.register(&parker);
6228 let waker = Waker::from(Arc::clone(&parker));
6229 let mut cx = Context::from_waker(&waker);
6230 loop {
6231 if self.stop.is_stopping() {
6232 return BlockOnOutcome::Stopped;
6233 }
6234 on_thread_handoff();
6235 if future.as_mut().poll(&mut cx).is_ready() {
6236 return BlockOnOutcome::Completed;
6237 }
6238 if self.stop.is_stopping() {
6239 return BlockOnOutcome::Stopped;
6240 }
6241 parker.park();
6242 }
6243 }
6244
6245 fn run_runnable(
6246 self: &Arc<Self>,
6247 runnable: Runnable,
6248 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6249 ) {
6250 on_thread_handoff();
6251 let _non_owner = BlockingOwnerGuard::enter(None);
6254 let _active = self.metrics.runnable_started();
6255 #[cfg(test)]
6256 run_multi_thread_work_started_test_hook();
6257 drop(admission);
6261 let _ = catch_unwind_contained(|| runnable.run());
6262 }
6263
6264 fn cancel_runnable(
6265 &self,
6266 runnable: Runnable,
6267 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6268 ) {
6269 self.metrics.runnable_cancelled();
6270 drop(admission);
6273 let _generation = RuntimeGenerationGuard::enter(self.generation);
6274 let _ = catch_unwind_contained(|| drop(runnable));
6275 }
6276
6277 fn finish_runnable_claim(
6278 self: &Arc<Self>,
6279 claim: RunnableClaim,
6280 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6281 ) -> bool {
6282 match claim {
6283 RunnableClaim::Empty => {
6284 drop(admission);
6285 false
6286 }
6287 RunnableClaim::Run(runnable) => {
6288 #[cfg(test)]
6289 run_multi_thread_work_claimed_test_hook();
6290 self.run_runnable(runnable, admission);
6291 true
6292 }
6293 RunnableClaim::Cancel(runnable) => {
6294 #[cfg(test)]
6295 run_multi_thread_work_claimed_test_hook();
6296 self.cancel_runnable(runnable, admission);
6297 true
6298 }
6299 }
6300 }
6301
6302 fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6303 match runnable {
6304 Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6305 Some(runnable) => RunnableClaim::Run(runnable),
6306 None => RunnableClaim::Empty,
6307 }
6308 }
6309
6310 fn steal_one(&self) -> Option<Runnable> {
6314 loop {
6315 match self.injector.steal() {
6316 Steal::Success(runnable) => return Some(runnable),
6317 Steal::Empty => return None,
6318 Steal::Retry => continue,
6319 }
6320 }
6321 }
6322
6323 #[cfg(test)]
6330 fn push_fifo_raw(&self, runnable: Runnable) {
6331 self.injector.push(runnable);
6332 std::sync::atomic::fence(Ordering::SeqCst);
6333 }
6334
6335 fn claim_fifo_runnable(&self) -> RunnableClaim {
6336 let stop_publication = self.stop.runnable_claim_guard();
6337 let runnable = self.steal_one();
6338 let stopping = stop_publication.is_stopping();
6339 drop(stop_publication);
6340 Self::runnable_claim(stopping, runnable)
6341 }
6342
6343 fn claim_runnable(&self) -> RunnableClaim {
6344 let stop_publication = self.stop.runnable_claim_guard();
6345 let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6346 let stopping = stop_publication.is_stopping();
6347 drop(stop_publication);
6348 Self::runnable_claim(stopping, runnable)
6349 }
6350
6351 fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6352 let admission = self.begin_deadlock_admission();
6353 let claim = self.claim_fifo_runnable();
6354 self.finish_runnable_claim(claim, admission)
6355 }
6356
6357 fn run_one_runnable(self: &Arc<Self>) -> bool {
6359 let admission = self.begin_deadlock_admission();
6360 let claim = self.claim_runnable();
6363 self.finish_runnable_claim(claim, admission)
6364 }
6365
6366 fn run_one_blocking(self: &Arc<Self>) -> bool {
6367 if let Some((blocking, admission)) = self.take_blocking() {
6368 let _generation = RuntimeGenerationGuard::enter(self.generation);
6369 {
6370 let _task = self.metrics.blocking_started(true);
6371 #[cfg(test)]
6372 run_multi_thread_work_started_test_hook();
6373 drop(admission);
6376 let owner = self.fresh_owner_token();
6377 let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6378 let _owner = BlockingOwnerGuard::enter(Some(owner));
6379 let _ = catch_unwind_contained(blocking);
6384 }
6385 self.active_blocking.fetch_sub(1, Ordering::Release);
6386 return true;
6387 }
6388 false
6389 }
6390
6391 fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6395 self.run_one_fair_with_exact(runnable_streak, None)
6396 }
6397
6398 fn run_one_fair_with_exact(
6402 self: &Arc<Self>,
6403 runnable_streak: &mut usize,
6404 exact_dependency: Option<&TaskDependency>,
6405 ) -> bool {
6406 let mut exact_attempted = false;
6407 if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6408 *runnable_streak = 0;
6409 if let Some(dependency) = exact_dependency {
6410 exact_attempted = true;
6411 if self.try_owned_blocking_over_cap(dependency) {
6412 return true;
6413 }
6414 }
6415 if self.run_one_blocking() {
6416 return true;
6417 }
6418 }
6419 if self.run_one_runnable() {
6420 *runnable_streak += 1;
6421 return true;
6422 }
6423 if !exact_attempted
6424 && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6425 {
6426 *runnable_streak = 0;
6427 return true;
6428 }
6429 if self.run_one_blocking() {
6430 *runnable_streak = 0;
6431 return true;
6432 }
6433 false
6434 }
6435
6436 #[cfg(test)]
6437 fn run_one(self: &Arc<Self>) -> bool {
6438 let mut runnable_streak = 0;
6439 self.run_one_fair(&mut runnable_streak)
6440 }
6441
6442 fn run_owned_blocking_over_cap(
6452 self: &Arc<Self>,
6453 dependency: &TaskDependency,
6454 ) -> OwnedBlockingOutcome {
6455 #[cfg(test)]
6456 self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6457
6458 let Some(ambient_owner) = BLOCKING_OWNER
6459 .with(std::cell::Cell::get)
6460 .filter(|owner| owner.executor_id == self.id)
6461 else {
6462 return OwnedBlockingOutcome::Ineligible;
6463 };
6464 let Some(current) = dependency.select_for_owner(ambient_owner) else {
6465 return OwnedBlockingOutcome::Ineligible;
6466 };
6467 let admission = self.begin_deadlock_admission();
6468 let owner = current
6469 .publication
6470 .dependency
6471 .owner
6472 .expect("selected dependency must carry its owner");
6473 let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6474 else {
6475 return OwnedBlockingOutcome::OwnerBusy(owner);
6476 };
6477 #[cfg(test)]
6478 run_before_owned_blocking_queue_claim_test_hook();
6479 let job = {
6480 let mut queue = self
6481 .blocking_queue
6482 .lock()
6483 .unwrap_or_else(std::sync::PoisonError::into_inner);
6484 if queue.closed || self.stop.is_stopping() {
6488 None
6489 } else if dependency.try_claim(¤t) {
6493 queue.remove(current.publication.dependency.job)
6494 } else {
6495 None
6496 }
6497 };
6498 let ran = if let Some(job) = job {
6499 #[cfg(test)]
6500 run_multi_thread_work_claimed_test_hook();
6501 let _generation = RuntimeGenerationGuard::enter(self.generation);
6502 {
6503 let _task = self.metrics.blocking_started(false);
6504 #[cfg(test)]
6505 run_multi_thread_work_started_test_hook();
6506 drop(admission);
6509 let nested_owner = self.fresh_owner_token();
6510 let _active_owner =
6511 ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6512 let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6513 let _ = catch_unwind_contained(job);
6514 }
6515 true
6516 } else {
6517 drop(admission);
6518 false
6519 };
6520 self.release_owner_lane_and_wake(reservation);
6523 OwnedBlockingOutcome::ReservationReleased { owner, ran }
6524 }
6525
6526 fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6527 matches!(
6528 self.run_owned_blocking_over_cap(dependency),
6529 OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6530 )
6531 }
6532
6533 fn service_owner_handoff(
6534 self: &Arc<Self>,
6535 parker: &DriverParker,
6536 dependency: &TaskDependency,
6537 can_run_blocking: bool,
6538 ) -> Option<bool> {
6539 let handoff_owner = parker.take_owner_handoff()?;
6540 if !can_run_blocking {
6541 self.wake_one_owner_dependency(handoff_owner);
6542 return Some(false);
6543 }
6544 match self.run_owned_blocking_over_cap(dependency) {
6545 OwnedBlockingOutcome::Ineligible => {
6546 self.wake_one_owner_dependency(handoff_owner);
6547 Some(false)
6548 }
6549 OwnedBlockingOutcome::OwnerBusy(owner) => {
6550 if owner != handoff_owner {
6551 self.wake_one_owner_dependency(handoff_owner);
6552 }
6553 Some(false)
6554 }
6555 OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6556 if owner != handoff_owner {
6557 self.wake_one_owner_dependency(handoff_owner);
6558 }
6559 Some(ran)
6560 }
6561 }
6562 }
6563
6564 fn forward_owner_handoff(&self, parker: &DriverParker) {
6565 if let Some(owner) = parker.take_owner_handoff() {
6566 self.wake_one_owner_dependency(owner);
6567 }
6568 }
6569
6570 fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6571 let Some(ambient_owner) = BLOCKING_OWNER
6572 .with(std::cell::Cell::get)
6573 .filter(|owner| owner.executor_id == self.id)
6574 else {
6575 return false;
6576 };
6577 self.active_blocking_owners.is_available(ambient_owner)
6578 && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6579 }
6580
6581 fn has_exact_owner_work_for_role(
6582 &self,
6583 parker: &DriverParker,
6584 dependency: &TaskDependency,
6585 can_run_blocking: bool,
6586 ) -> bool {
6587 parker.has_owner_handoff()
6588 || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6589 }
6590
6591 fn cooperative_block_on(
6600 self: &Arc<Self>,
6601 mut future: Pin<&mut dyn Future<Output = ()>>,
6602 ) -> BlockOnOutcome {
6603 let parker = Arc::new(DriverParker::default());
6608 let _stop_registration = self.stop.register(&parker);
6609 let dependency = BlockOnDependencyGuard::enter(self.generation);
6610 let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6611 let can_run_blocking =
6612 driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6613 let _exit = CooperativeDriverExitGuard {
6614 executor: self,
6615 parker: &parker,
6616 can_run_blocking,
6617 };
6618 let waker = std::task::Waker::from(Arc::clone(&parker));
6623 let mut cx = Context::from_waker(&waker);
6624 let mut runnable_streak = 0usize;
6632 let mut work_streak = 0usize;
6633 let mut force_fifo = false;
6634 loop {
6635 if self.stop.is_stopping() {
6636 return BlockOnOutcome::Stopped;
6637 }
6638 dependency.clear();
6639 on_thread_handoff();
6640 if future.as_mut().poll(&mut cx).is_ready() {
6641 return BlockOnOutcome::Completed;
6642 }
6643 if self.stop.is_stopping() {
6644 return BlockOnOutcome::Stopped;
6645 }
6646 if self
6647 .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6648 .is_some_and(|ran| ran)
6649 {
6650 continue;
6651 }
6652 if force_fifo {
6653 force_fifo = false;
6654 if self.run_one_fifo_runnable() {
6655 runnable_streak += 1;
6656 work_streak += 1;
6657 continue;
6658 }
6659 }
6660 let ran_work = if can_run_blocking {
6661 self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6662 } else if self.run_one_runnable() {
6663 runnable_streak += 1;
6664 true
6665 } else {
6666 false
6667 };
6668 if ran_work {
6669 work_streak += 1;
6670 if work_streak >= Self::RUNNABLE_BUDGET {
6671 self.flush_lifo_slot();
6676 work_streak = 0;
6677 force_fifo = true;
6678 }
6679 continue;
6680 }
6681 runnable_streak = 0;
6682 work_streak = 0;
6683 self.flush_lifo_slot();
6688 self.parked_drivers.register_with_role(
6692 &parker,
6693 can_run_blocking,
6694 Some(Arc::clone(&dependency.dependency)),
6695 );
6696 if self.has_queued_work_for_role(can_run_blocking)
6697 || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6698 {
6699 self.parked_drivers.deregister(&parker);
6700 continue;
6701 }
6702 if self.stop.is_stopping() {
6703 return BlockOnOutcome::Stopped;
6704 }
6705 if let Some(next) = self.next_timer_deadline() {
6720 let now = Instant::now();
6721 if next > now {
6722 parker.park_timeout(next - now);
6723 }
6724 self.parked_drivers.deregister(&parker);
6725 self.fire_due_timers();
6726 continue;
6727 }
6728 match self.park_deadline {
6729 None => parker.park(),
6730 Some(deadline) => {
6731 let deadlock_publications = self.deadlock_state.publications();
6737 match park_with_deadline(&parker, deadline, &self.metrics) {
6738 DeadlineParkOutcome::Woken => {}
6739 DeadlineParkOutcome::Expired(armed) => {
6740 #[cfg(test)]
6741 run_deadline_expiry_test_hook();
6742 self.parked_drivers.deregister(&parker);
6779 if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6780 continue;
6781 }
6782 #[cfg(test)]
6785 run_deadline_verdict_test_hook();
6786 if self.metrics.progress_fingerprint() != armed {
6787 continue;
6788 }
6789 if self.next_timer_deadline().is_some() {
6798 continue;
6799 }
6800 #[cfg(test)]
6803 run_deadline_final_verdict_test_hook();
6804 let Some(verdict) = self.try_begin_deadlock_verdict() else {
6805 continue;
6806 };
6807 #[cfg(test)]
6808 run_deadline_gated_verdict_test_hook();
6809 let stop_publication = self.stop.publication_guard();
6814 #[cfg(test)]
6815 run_deadline_synchronized_stop_test_hook();
6816 if stop_publication.is_stopping() {
6817 return BlockOnOutcome::Stopped;
6818 }
6819 let owner_handoff_publication = self
6820 .deadlock_state
6821 .owner_handoff_publication_guard()
6822 .expect("a deadline verdict requires exact-owner publication");
6823 #[cfg(test)]
6824 run_deadline_exact_owner_verdict_test_hook(&parker);
6825 if self.has_exact_owner_work_for_role(
6833 &parker,
6834 &dependency.dependency,
6835 can_run_blocking,
6836 ) || parker.consume_permit()
6837 || self.has_queued_work_for_role(can_run_blocking)
6838 || self.deadlock_state.publications() != deadlock_publications
6839 || self.metrics.progress_fingerprint() != armed
6840 || self.next_timer_deadline().is_some()
6841 {
6842 continue;
6843 }
6844 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6845 drop(owner_handoff_publication);
6850 drop(stop_publication);
6851 drop(verdict);
6852 std::panic::panic_any(diagnostic);
6853 }
6854 }
6855 }
6856 }
6857 self.parked_drivers.deregister(&parker);
6860 }
6861 }
6862
6863 fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6869 if self.has_queued_runnable() {
6870 return true;
6871 }
6872 can_run_blocking
6873 && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6874 && !self
6875 .blocking_queue
6876 .lock()
6877 .unwrap_or_else(std::sync::PoisonError::into_inner)
6878 .is_empty()
6879 }
6880
6881 fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6882 if self.has_queued_runnable() {
6883 self.wake_for_new_work();
6884 }
6885 if self.stop.is_stopping() {
6889 return;
6890 }
6891 if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6892 && !self
6893 .blocking_queue
6894 .lock()
6895 .unwrap_or_else(std::sync::PoisonError::into_inner)
6896 .is_empty()
6897 {
6898 let consumed = can_run_blocking && self.run_one_blocking();
6906 let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6907 && !self
6908 .blocking_queue
6909 .lock()
6910 .unwrap_or_else(std::sync::PoisonError::into_inner)
6911 .is_empty();
6912 if !self.stop.is_stopping() && (!consumed || has_residue) {
6913 self.wake_for_blocking_work();
6914 }
6915 }
6916 }
6917
6918 fn has_queued_runnable(&self) -> bool {
6919 std::sync::atomic::fence(Ordering::SeqCst);
6926 !self.injector.is_empty()
6927 }
6928
6929 fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6933 let admission = self.begin_deadlock_admission();
6934 loop {
6935 let active = self.active_blocking.load(Ordering::Acquire);
6936 if active >= self.max_blocking {
6937 return None;
6938 }
6939 let mut queue = self
6940 .blocking_queue
6941 .lock()
6942 .unwrap_or_else(std::sync::PoisonError::into_inner);
6943 if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6944 return None;
6945 }
6946 if self
6947 .active_blocking
6948 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6949 .is_ok()
6950 {
6951 let blocking = queue
6952 .pop_front()
6953 .expect("a non-empty blocking queue must have a FIFO head");
6954 drop(queue);
6955 #[cfg(test)]
6956 run_multi_thread_work_claimed_test_hook();
6957 return Some((blocking, admission));
6958 }
6959 }
6960 }
6961}
6962
6963#[cfg(napi_runtime_os_threads)]
6964struct MultiThreadDeadlockState {
6965 enabled: bool,
6966 verdict_closed: AtomicBool,
6967 admissions: AtomicUsize,
6968 publications: AtomicU64,
6969 owner_handoff_publication: Mutex<()>,
6970}
6971
6972#[cfg(napi_runtime_os_threads)]
6973impl MultiThreadDeadlockState {
6974 fn new(enabled: bool) -> Self {
6975 Self {
6976 enabled,
6977 verdict_closed: AtomicBool::new(false),
6978 admissions: AtomicUsize::new(0),
6979 publications: AtomicU64::new(0),
6980 owner_handoff_publication: Mutex::new(()),
6981 }
6982 }
6983
6984 fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6985 if !self.enabled {
6986 return None;
6987 }
6988 loop {
6989 while self.verdict_closed.load(Ordering::SeqCst) {
6990 #[cfg(test)]
6991 run_deadlock_closed_gate_admission_test_hook();
6992 std::hint::spin_loop();
6993 }
6994 self.admissions.fetch_add(1, Ordering::SeqCst);
6995 if !self.verdict_closed.load(Ordering::SeqCst) {
6996 return Some(MultiThreadDeadlockAdmissionGuard {
6997 state: Arc::clone(self),
6998 published: false,
6999 });
7000 }
7001 self.admissions.fetch_sub(1, Ordering::SeqCst);
7002 }
7003 }
7004
7005 fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
7006 debug_assert!(
7007 self.enabled,
7008 "a deadline verdict requires an enabled admission state"
7009 );
7010 if self
7011 .verdict_closed
7012 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
7013 .is_err()
7014 {
7015 return None;
7016 }
7017 if self.admissions.load(Ordering::SeqCst) != 0 {
7018 self.verdict_closed.store(false, Ordering::SeqCst);
7019 return None;
7020 }
7021 Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
7022 }
7023
7024 fn publications(&self) -> u64 {
7025 self.publications.load(Ordering::SeqCst)
7026 }
7027
7028 fn owner_handoff_publication_guard(&self) -> Option<MutexGuard<'_, ()>> {
7029 self.enabled.then(|| {
7030 self
7031 .owner_handoff_publication
7032 .lock()
7033 .unwrap_or_else(std::sync::PoisonError::into_inner)
7034 })
7035 }
7036}
7037
7038#[cfg(napi_runtime_os_threads)]
7039struct MultiThreadDeadlockAdmissionGuard {
7040 state: Arc<MultiThreadDeadlockState>,
7041 published: bool,
7042}
7043
7044#[cfg(napi_runtime_os_threads)]
7045impl MultiThreadDeadlockAdmissionGuard {
7046 fn mark_published(&mut self) {
7047 self.published = true;
7048 }
7049}
7050
7051#[cfg(napi_runtime_os_threads)]
7052impl Drop for MultiThreadDeadlockAdmissionGuard {
7053 fn drop(&mut self) {
7054 if self.published {
7055 self.state.publications.fetch_add(1, Ordering::SeqCst);
7056 }
7057 self.state.admissions.fetch_sub(1, Ordering::SeqCst);
7060 }
7061}
7062
7063#[cfg(napi_runtime_os_threads)]
7064struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
7065
7066#[cfg(napi_runtime_os_threads)]
7067impl Drop for MultiThreadDeadlockVerdictGuard {
7068 fn drop(&mut self) {
7069 self.0.verdict_closed.store(false, Ordering::SeqCst);
7070 }
7071}
7072
7073#[cfg(napi_runtime_os_threads)]
7079struct CooperativeDriverExitGuard<'a> {
7080 executor: &'a Arc<MultiThreadExecutor>,
7081 parker: &'a Arc<DriverParker>,
7082 can_run_blocking: bool,
7083}
7084
7085#[cfg(napi_runtime_os_threads)]
7086impl Drop for CooperativeDriverExitGuard<'_> {
7087 fn drop(&mut self) {
7088 let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
7089 let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
7090 let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
7091 let _ = catch_unwind_contained(|| {
7092 self
7093 .executor
7094 .compensate_queued_work_wake(self.can_run_blocking);
7095 });
7096 }
7097}
7098
7099#[cfg(napi_runtime_os_threads)]
7105struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
7106
7107#[cfg(napi_runtime_os_threads)]
7108impl Drop for LifoSlotFlushGuard<'_> {
7109 fn drop(&mut self) {
7110 self.0.flush_lifo_slot();
7111 }
7112}
7113
7114#[cfg(napi_runtime_os_threads)]
7121struct DrainLinger {
7122 parker: Arc<DriverParker>,
7123 _stop_registration: GenerationStopGuard,
7124}
7125
7126#[cfg(napi_runtime_os_threads)]
7127impl DrainLinger {
7128 fn new(executor: &MultiThreadExecutor) -> Self {
7129 let parker = Arc::new(DriverParker::default());
7130 let stop_registration = executor.stop.register(&parker);
7131 Self {
7132 parker,
7133 _stop_registration: stop_registration,
7134 }
7135 }
7136}
7137
7138const PARKER_EMPTY: usize = 0;
7160const PARKER_NOTIFIED: usize = 1;
7162const PARKER_SLEEPING: usize = 2;
7164
7165#[derive(Default)]
7169struct DriverParker {
7170 state: AtomicUsize,
7171 lock: Mutex<()>,
7172 condvar: UnboundedCondvar,
7173 #[cfg(napi_runtime_os_threads)]
7174 owner_handoff_pending: AtomicBool,
7175 #[cfg(napi_runtime_os_threads)]
7176 owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7177}
7178
7179impl DriverParker {
7180 #[cfg(napi_runtime_os_threads)]
7181 fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7182 let mut pending = self
7183 .owner_handoff
7184 .lock()
7185 .unwrap_or_else(std::sync::PoisonError::into_inner);
7186 debug_assert!(pending.is_none());
7187 *pending = Some(owner);
7188 self.owner_handoff_pending.store(true, Ordering::Release);
7189 }
7190
7191 #[cfg(napi_runtime_os_threads)]
7192 fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7193 if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7194 return None;
7195 }
7196 self
7197 .owner_handoff
7198 .lock()
7199 .unwrap_or_else(std::sync::PoisonError::into_inner)
7200 .take()
7201 }
7202
7203 #[cfg(napi_runtime_os_threads)]
7204 fn has_owner_handoff(&self) -> bool {
7205 self.owner_handoff_pending.load(Ordering::Acquire)
7206 }
7207
7208 fn unpark(&self) {
7215 if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7216 drop(
7221 self
7222 .lock
7223 .lock()
7224 .unwrap_or_else(std::sync::PoisonError::into_inner),
7225 );
7226 self.condvar.notify_one();
7227 }
7228 }
7229
7230 fn consume_permit(&self) -> bool {
7235 self
7236 .state
7237 .compare_exchange(
7238 PARKER_NOTIFIED,
7239 PARKER_EMPTY,
7240 Ordering::SeqCst,
7241 Ordering::SeqCst,
7242 )
7243 .is_ok()
7244 }
7245
7246 fn park(&self) {
7250 if self.consume_permit() {
7252 return;
7253 }
7254 let mut guard = self
7255 .lock
7256 .lock()
7257 .unwrap_or_else(std::sync::PoisonError::into_inner);
7258 match self.state.compare_exchange(
7259 PARKER_EMPTY,
7260 PARKER_SLEEPING,
7261 Ordering::SeqCst,
7262 Ordering::SeqCst,
7263 ) {
7264 Ok(_) => {}
7265 Err(_) => {
7269 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7270 return;
7271 }
7272 }
7273 loop {
7274 guard = self
7275 .condvar
7276 .wait(guard)
7277 .unwrap_or_else(std::sync::PoisonError::into_inner);
7278 if self
7279 .state
7280 .compare_exchange(
7281 PARKER_NOTIFIED,
7282 PARKER_EMPTY,
7283 Ordering::SeqCst,
7284 Ordering::SeqCst,
7285 )
7286 .is_ok()
7287 {
7288 return;
7289 }
7290 }
7292 }
7293
7294 fn park_timeout(&self, timeout: Duration) -> bool {
7300 if self.consume_permit() {
7301 return true;
7302 }
7303 let mut guard = self
7304 .lock
7305 .lock()
7306 .unwrap_or_else(std::sync::PoisonError::into_inner);
7307 match self.state.compare_exchange(
7308 PARKER_EMPTY,
7309 PARKER_SLEEPING,
7310 Ordering::SeqCst,
7311 Ordering::SeqCst,
7312 ) {
7313 Ok(_) => {}
7314 Err(_) => {
7318 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7319 return true;
7320 }
7321 }
7322 let Some(deadline) = Instant::now().checked_add(timeout) else {
7323 loop {
7327 guard = self
7328 .condvar
7329 .wait(guard)
7330 .unwrap_or_else(std::sync::PoisonError::into_inner);
7331 if self
7332 .state
7333 .compare_exchange(
7334 PARKER_NOTIFIED,
7335 PARKER_EMPTY,
7336 Ordering::SeqCst,
7337 Ordering::SeqCst,
7338 )
7339 .is_ok()
7340 {
7341 return true;
7342 }
7343 }
7344 };
7345 loop {
7346 if self
7347 .state
7348 .compare_exchange(
7349 PARKER_NOTIFIED,
7350 PARKER_EMPTY,
7351 Ordering::SeqCst,
7352 Ordering::SeqCst,
7353 )
7354 .is_ok()
7355 {
7356 return true;
7357 }
7358 let now = Instant::now();
7359 if now >= deadline {
7360 return match self.state.compare_exchange(
7365 PARKER_SLEEPING,
7366 PARKER_EMPTY,
7367 Ordering::SeqCst,
7368 Ordering::SeqCst,
7369 ) {
7370 Ok(_) => false,
7371 Err(_) => {
7372 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7373 true
7374 }
7375 };
7376 }
7377 let (returned_guard, _timed_out) = self
7378 .condvar
7379 .wait_timeout(guard, deadline - now)
7380 .unwrap_or_else(std::sync::PoisonError::into_inner);
7381 guard = returned_guard;
7382 }
7385 }
7386}
7387
7388impl std::task::Wake for DriverParker {
7389 fn wake(self: Arc<Self>) {
7390 self.unpark();
7391 }
7392
7393 fn wake_by_ref(self: &Arc<Self>) {
7394 self.unpark();
7395 }
7396}
7397
7398enum DeadlineParkOutcome {
7400 Woken,
7402 Expired(ProgressFingerprint),
7411}
7412
7413fn park_with_deadline(
7422 parker: &DriverParker,
7423 deadline: Duration,
7424 metrics: &RuntimeMetrics,
7425) -> DeadlineParkOutcome {
7426 let mut armed = metrics.progress_fingerprint();
7427 loop {
7428 if parker.park_timeout(deadline) {
7429 return DeadlineParkOutcome::Woken;
7430 }
7431 let current = metrics.progress_fingerprint();
7432 if current != armed {
7433 armed = current;
7434 continue;
7435 }
7436 return DeadlineParkOutcome::Expired(armed);
7437 }
7438}
7439
7440#[cfg(all(test, napi_runtime_os_threads))]
7448thread_local! {
7449 static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7450 const { std::cell::RefCell::new(None) };
7451}
7452
7453#[cfg(all(test, napi_runtime_os_threads))]
7454fn run_deadline_expiry_test_hook() {
7455 if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7456 hook();
7457 }
7458}
7459
7460#[cfg(all(test, napi_runtime_os_threads))]
7467thread_local! {
7468 static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7469 const { std::cell::RefCell::new(None) };
7470}
7471
7472#[cfg(all(test, napi_runtime_os_threads))]
7473fn run_deadline_verdict_test_hook() {
7474 if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7475 hook();
7476 }
7477}
7478
7479#[cfg(all(test, napi_runtime_os_threads))]
7484thread_local! {
7485 static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7486 const { std::cell::RefCell::new(None) };
7487}
7488
7489#[cfg(all(test, napi_runtime_os_threads))]
7490fn run_deadline_final_verdict_test_hook() {
7491 if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7492 hook();
7493 }
7494}
7495
7496#[cfg(all(test, napi_runtime_os_threads))]
7501thread_local! {
7502 static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7503 const { std::cell::RefCell::new(None) };
7504}
7505
7506#[cfg(all(test, napi_runtime_os_threads))]
7507fn run_deadline_gated_verdict_test_hook() {
7508 if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7509 hook();
7510 }
7511}
7512
7513#[cfg(all(test, napi_runtime_os_threads))]
7517thread_local! {
7518 static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7519 const { std::cell::RefCell::new(None) };
7520}
7521
7522#[cfg(all(test, napi_runtime_os_threads))]
7523fn run_deadline_synchronized_stop_test_hook() {
7524 if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7525 hook();
7526 }
7527}
7528
7529#[cfg(all(test, napi_runtime_os_threads))]
7533type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7534
7535#[cfg(all(test, napi_runtime_os_threads))]
7536thread_local! {
7537 static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7538 std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7539 const { std::cell::RefCell::new(None) };
7540}
7541
7542#[cfg(all(test, napi_runtime_os_threads))]
7543fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7544 if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7545 hook(parker);
7546 }
7547}
7548
7549#[cfg(all(test, napi_runtime_os_threads))]
7553thread_local! {
7554 static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7555 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7556 const { std::cell::RefCell::new(None) };
7557}
7558
7559#[cfg(all(test, napi_runtime_os_threads))]
7560fn run_before_generation_stop_publication_lock_test_hook() {
7561 if let Some(hook) =
7562 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7563 {
7564 hook();
7565 }
7566}
7567
7568#[cfg(all(test, napi_runtime_os_threads))]
7572thread_local! {
7573 static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7574 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7575 const { std::cell::RefCell::new(None) };
7576}
7577
7578#[cfg(all(test, napi_runtime_os_threads))]
7579fn run_deadlock_closed_gate_admission_test_hook() {
7580 if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7581 {
7582 hook();
7583 }
7584}
7585
7586#[cfg(all(test, napi_runtime_os_threads))]
7590thread_local! {
7591 static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7592 const { std::cell::RefCell::new(None) };
7593}
7594
7595#[cfg(all(test, napi_runtime_os_threads))]
7596fn run_timer_registration_admission_test_hook() {
7597 if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7598 hook();
7599 }
7600}
7601
7602#[cfg(all(test, napi_runtime_os_threads))]
7603thread_local! {
7604 static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7605 const { std::cell::RefCell::new(None) };
7606}
7607
7608#[cfg(all(test, napi_runtime_os_threads))]
7609fn run_owner_handoff_selected_test_hook() {
7610 if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7611 hook();
7612 }
7613}
7614
7615#[cfg(napi_runtime_os_threads)]
7618#[derive(Default)]
7619struct ParkedDrivers {
7620 parked: Mutex<Vec<ParkedDriver>>,
7621 count: AtomicUsize,
7624}
7625
7626#[cfg(napi_runtime_os_threads)]
7627struct ParkedDriver {
7628 parker: Arc<DriverParker>,
7629 can_run_blocking: bool,
7630 dependency: Option<Arc<TaskDependency>>,
7631}
7632
7633#[cfg(napi_runtime_os_threads)]
7634impl ParkedDrivers {
7635 #[cfg(test)]
7638 fn register(&self, parker: &Arc<DriverParker>) {
7639 self.register_with_role(parker, true, None);
7640 }
7641
7642 fn register_with_role(
7643 &self,
7644 parker: &Arc<DriverParker>,
7645 can_run_blocking: bool,
7646 dependency: Option<Arc<TaskDependency>>,
7647 ) {
7648 let mut parked = self
7649 .parked
7650 .lock()
7651 .unwrap_or_else(std::sync::PoisonError::into_inner);
7652 parked.push(ParkedDriver {
7653 parker: Arc::clone(parker),
7654 can_run_blocking,
7655 dependency,
7656 });
7657 self.count.store(parked.len(), Ordering::SeqCst);
7658 }
7659
7660 fn deregister(&self, parker: &Arc<DriverParker>) {
7664 let removed = {
7665 let mut parked = self
7666 .parked
7667 .lock()
7668 .unwrap_or_else(std::sync::PoisonError::into_inner);
7669 let removed = parked
7670 .iter()
7671 .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7672 .map(|index| parked.swap_remove(index));
7673 self.count.store(parked.len(), Ordering::SeqCst);
7674 removed
7675 };
7676 drop(removed);
7677 }
7678
7679 fn wake_one(&self) -> bool {
7702 if self.count.load(Ordering::SeqCst) == 0 {
7703 return false;
7707 }
7708 let driver = {
7709 let mut parked = self
7710 .parked
7711 .lock()
7712 .unwrap_or_else(std::sync::PoisonError::into_inner);
7713 let driver = parked.pop();
7714 self.count.store(parked.len(), Ordering::SeqCst);
7715 driver
7716 };
7717 match driver {
7718 Some(driver) => {
7719 driver.parker.unpark();
7720 true
7721 }
7722 None => false,
7723 }
7724 }
7725
7726 fn wake_one_blocking(&self) -> bool {
7729 if self.count.load(Ordering::SeqCst) == 0 {
7730 return false;
7731 }
7732 let driver = {
7733 let mut parked = self
7734 .parked
7735 .lock()
7736 .unwrap_or_else(std::sync::PoisonError::into_inner);
7737 let driver = parked
7738 .iter()
7739 .rposition(|driver| driver.can_run_blocking)
7740 .map(|index| parked.swap_remove(index));
7741 self.count.store(parked.len(), Ordering::SeqCst);
7742 driver
7743 };
7744 match driver {
7745 Some(driver) => {
7746 driver.parker.unpark();
7747 true
7748 }
7749 None => false,
7750 }
7751 }
7752
7753 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7758 loop {
7759 if self.count.load(Ordering::SeqCst) == 0 {
7760 return false;
7761 }
7762 let candidates = {
7763 let parked = self
7764 .parked
7765 .lock()
7766 .unwrap_or_else(std::sync::PoisonError::into_inner);
7767 parked
7768 .iter()
7769 .rev()
7770 .filter_map(|driver| {
7771 if !driver.can_run_blocking {
7772 return None;
7773 }
7774 driver
7775 .dependency
7776 .as_ref()
7777 .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7778 })
7779 .collect::<Vec<_>>()
7780 };
7781 let selected = candidates.into_iter().find(|(_, dependency)| {
7782 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7783 });
7784 let Some((selected_parker, selected_dependency)) = selected else {
7785 return false;
7786 };
7787 let driver = {
7788 let mut parked = self
7789 .parked
7790 .lock()
7791 .unwrap_or_else(std::sync::PoisonError::into_inner);
7792 let driver = parked
7793 .iter()
7794 .rposition(|driver| {
7795 Arc::ptr_eq(&driver.parker, &selected_parker)
7796 && driver
7797 .dependency
7798 .as_ref()
7799 .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7800 })
7801 .map(|index| parked.swap_remove(index));
7802 if let Some(driver) = &driver {
7807 driver.parker.grant_owner_handoff(owner);
7808 }
7809 self.count.store(parked.len(), Ordering::SeqCst);
7810 driver
7811 };
7812 if let Some(driver) = driver {
7813 #[cfg(test)]
7814 run_owner_handoff_selected_test_hook();
7815 driver.parker.unpark();
7816 return true;
7817 }
7818 }
7819 }
7820
7821 fn wake_all(&self) {
7824 if self.count.load(Ordering::SeqCst) == 0 {
7825 return;
7826 }
7827 let drivers = {
7828 let mut parked = self
7829 .parked
7830 .lock()
7831 .unwrap_or_else(std::sync::PoisonError::into_inner);
7832 let drivers = parked.drain(..).collect::<Vec<_>>();
7833 self.count.store(0, Ordering::SeqCst);
7834 drivers
7835 };
7836 for driver in drivers {
7837 driver.parker.unpark();
7838 }
7839 }
7840}
7841
7842pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7887 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7921
7922 fn is_live(&self) -> bool {
7923 true
7924 }
7925
7926 fn on_swept(&self) {}
7929}
7930
7931#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7932pub struct CurrentThreadTaskDriverId(u64);
7933
7934type CurrentThreadTaskDriverSnapshot =
7935 ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7936
7937#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7938pub struct CurrentThreadTaskDelivery {
7939 driver: CurrentThreadTaskDriverId,
7940 capability: u64,
7941 dispatch: u64,
7942}
7943
7944impl CurrentThreadTaskDelivery {
7945 pub fn capability(self) -> u64 {
7947 self.capability
7948 }
7949}
7950
7951struct CurrentThreadTaskDeliveryState {
7952 delivery: CurrentThreadTaskDelivery,
7953 claimed: bool,
7954 accepted: bool,
7955}
7956
7957struct CurrentThreadTaskDriverEntry {
7958 driver: Arc<dyn CurrentThreadTaskDriver>,
7959 delivery: Option<CurrentThreadTaskDeliveryState>,
7960 pending: Option<u64>,
7961 previous: Option<CurrentThreadTaskDriverId>,
7962 next: Option<CurrentThreadTaskDriverId>,
7963}
7964
7965#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7966enum CurrentThreadDispatchPhase {
7967 #[default]
7968 Pending,
7969 Serviced,
7970 Cancelling,
7971}
7972
7973#[derive(Default)]
7974struct CurrentThreadDispatchState {
7975 phase: CurrentThreadDispatchPhase,
7976 epoch: u64,
7977 publications: usize,
7978 references: usize,
7979 accepted: bool,
7980 accepted_failure: bool,
7981}
7982
7983impl CurrentThreadDispatchState {
7984 fn is_serviced(&self) -> bool {
7985 self.phase == CurrentThreadDispatchPhase::Serviced
7986 }
7987
7988 fn is_cancelling(&self) -> bool {
7989 self.phase == CurrentThreadDispatchPhase::Cancelling
7990 }
7991
7992 fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7993 CurrentThreadTaskDispatchFailure {
7994 dispatch,
7995 epoch: self.epoch,
7996 }
7997 }
7998}
7999
8000#[derive(Default)]
8001struct CurrentThreadTaskDriverRegistryState {
8002 entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
8003 oldest: Option<CurrentThreadTaskDriverId>,
8004 newest: Option<CurrentThreadTaskDriverId>,
8005 delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
8006 dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
8007}
8008
8009impl CurrentThreadTaskDriverRegistryState {
8010 fn insert_driver(
8011 &mut self,
8012 id: CurrentThreadTaskDriverId,
8013 driver: Arc<dyn CurrentThreadTaskDriver>,
8014 ) {
8015 let previous = self.newest;
8016 let replaced = self.entries.insert(
8017 id,
8018 CurrentThreadTaskDriverEntry {
8019 driver,
8020 delivery: None,
8021 pending: None,
8022 previous,
8023 next: None,
8024 },
8025 );
8026 assert!(
8027 replaced.is_none(),
8028 "CurrentThread task driver id must be unique"
8029 );
8030 if let Some(previous) = previous {
8031 self
8032 .entries
8033 .get_mut(&previous)
8034 .expect("the newest CurrentThread task driver must remain registered")
8035 .next = Some(id);
8036 } else {
8037 self.oldest = Some(id);
8038 }
8039 self.newest = Some(id);
8040 }
8041
8042 fn remove_driver(
8043 &mut self,
8044 id: CurrentThreadTaskDriverId,
8045 ) -> Option<CurrentThreadTaskDriverEntry> {
8046 let entry = self.entries.remove(&id)?;
8047 if let Some(previous) = entry.previous {
8048 self
8049 .entries
8050 .get_mut(&previous)
8051 .expect("the previous CurrentThread task driver must remain registered")
8052 .next = entry.next;
8053 } else {
8054 self.oldest = entry.next;
8055 }
8056 if let Some(next) = entry.next {
8057 self
8058 .entries
8059 .get_mut(&next)
8060 .expect("the next CurrentThread task driver must remain registered")
8061 .previous = entry.previous;
8062 } else {
8063 self.newest = entry.previous;
8064 }
8065 if let Some(delivery) = &entry.delivery {
8066 let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
8067 debug_assert_eq!(
8068 indexed,
8069 Some(id),
8070 "delivery capability index must match its driver"
8071 );
8072 }
8073 Some(entry)
8074 }
8075
8076 fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8077 let mut snapshot = Vec::with_capacity(self.entries.len());
8078 let mut current = self.oldest;
8079 while let Some(id) = current {
8080 let entry = self
8081 .entries
8082 .get(&id)
8083 .expect("CurrentThread task driver order must reference a live entry");
8084 snapshot.push((id, Arc::clone(&entry.driver)));
8085 current = entry.next;
8086 }
8087 debug_assert_eq!(
8088 snapshot.len(),
8089 self.entries.len(),
8090 "CurrentThread task driver order must contain every entry exactly once"
8091 );
8092 snapshot
8093 }
8094
8095 fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
8096 if self.entries.len() != expected.len() {
8097 return false;
8098 }
8099 let mut current = self.oldest;
8100 for expected in expected {
8101 if current != Some(*expected) {
8102 return false;
8103 }
8104 current = self
8105 .entries
8106 .get(expected)
8107 .expect("CurrentThread task driver order must reference a live entry")
8108 .next;
8109 }
8110 current.is_none()
8111 }
8112}
8113
8114struct CurrentThreadTaskDriverSelection {
8115 live: CurrentThreadTaskDriverSnapshot,
8116 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8117}
8118
8119#[derive(Default)]
8120struct CurrentThreadTaskDeliveryCompletion {
8121 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8122 redispatch: bool,
8123}
8124
8125struct CurrentThreadTaskDispatchCompletion {
8126 result: CurrentThreadHostDispatchResult,
8127 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8128}
8129
8130struct CurrentThreadTaskDispatchPublicationGuard<'a> {
8131 registry: &'a CurrentThreadTaskDriverRegistry,
8132 dispatch: Option<u64>,
8133}
8134
8135impl CurrentThreadTaskDispatchPublicationGuard<'_> {
8136 fn finish(mut self) -> CurrentThreadHostDispatchResult {
8137 let dispatch = self
8138 .dispatch
8139 .expect("the CurrentThread task dispatch publication must remain armed");
8140 let result = self.registry.finish_publication(dispatch);
8141 self.dispatch = None;
8142 result
8143 }
8144}
8145
8146impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
8147 fn drop(&mut self) {
8148 if let Some(dispatch) = self.dispatch.take() {
8149 self.registry.abort_publication(dispatch);
8150 }
8151 }
8152}
8153
8154#[derive(Default)]
8155struct CurrentThreadTaskDriverRegistry {
8156 state: Mutex<CurrentThreadTaskDriverRegistryState>,
8157 next_id: AtomicU64,
8158 next_delivery: AtomicU64,
8159}
8160
8161impl CurrentThreadTaskDriverRegistry {
8162 fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8163 let id = next_unique_id(
8164 &self.next_id,
8165 "CurrentThread task driver id space exhausted",
8166 );
8167 let id = CurrentThreadTaskDriverId(id);
8168 self
8169 .state
8170 .lock()
8171 .unwrap_or_else(std::sync::PoisonError::into_inner)
8172 .insert_driver(id, driver);
8173 id
8174 }
8175
8176 fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8177 let (removed, failed_dispatches) = {
8178 let mut state = self
8179 .state
8180 .lock()
8181 .unwrap_or_else(std::sync::PoisonError::into_inner);
8182 let removed = state.remove_driver(id);
8183 let mut failed_dispatches = Vec::new();
8184 if let Some(entry) = &removed {
8185 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8186 }
8187 (removed, failed_dispatches)
8188 };
8189 let _ = catch_unwind_contained(|| drop(removed));
8190 CurrentThreadTaskDeliveryCompletion {
8191 failed_dispatches,
8192 redispatch: false,
8193 }
8194 }
8195
8196 fn live(&self) -> CurrentThreadTaskDriverSelection {
8197 let mut failed_dispatches = Vec::new();
8198 loop {
8199 let snapshot = CurrentThreadTaskDriverSnapshot::new({
8200 let state = self
8201 .state
8202 .lock()
8203 .unwrap_or_else(std::sync::PoisonError::into_inner);
8204 state.ordered_snapshot()
8205 });
8206 let mut dead_ids = FxHashSet::default();
8207 let mut live_ids = Vec::new();
8208 for (id, driver) in snapshot.as_slice() {
8209 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8210 live_ids.push(*id);
8211 } else {
8212 dead_ids.insert(*id);
8213 }
8214 }
8215
8216 let (live, swept, retry) = {
8217 let mut state = self
8218 .state
8219 .lock()
8220 .unwrap_or_else(std::sync::PoisonError::into_inner);
8221 let mut swept = Vec::new();
8222 let mut current = state.oldest;
8223 while let Some(id) = current {
8224 let next = state
8225 .entries
8226 .get(&id)
8227 .expect("CurrentThread task driver order must reference a live entry")
8228 .next;
8229 if dead_ids.contains(&id)
8230 && let Some(entry) = state.remove_driver(id)
8231 {
8232 swept.push(entry);
8233 }
8234 current = next;
8235 }
8236 for entry in &swept {
8237 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8238 }
8239 let retry = !state.ordered_ids_match(&live_ids);
8240 let live = if retry {
8241 Vec::new()
8242 } else {
8243 state.ordered_snapshot()
8244 };
8245 (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8246 };
8247
8248 drop(snapshot);
8251 for entry in swept {
8252 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8253 let _ = catch_unwind_contained(|| drop(entry));
8254 }
8255 if !retry {
8256 return CurrentThreadTaskDriverSelection {
8257 live,
8258 failed_dispatches,
8259 };
8260 }
8261 }
8262 }
8263
8264 #[cfg(test)]
8265 fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8266 let mut live = self.live().live;
8267 live.pop().map(|(id, driver)| (id, driver.into_inner()))
8268 }
8269
8270 fn next_delivery(
8271 &self,
8272 driver: CurrentThreadTaskDriverId,
8273 dispatch: u64,
8274 ) -> CurrentThreadTaskDelivery {
8275 let capability = next_unique_id(
8276 &self.next_delivery,
8277 "CurrentThread task delivery id space exhausted",
8278 )
8279 .checked_add(1)
8280 .expect("CurrentThread task delivery id space exhausted");
8281 CurrentThreadTaskDelivery {
8282 driver,
8283 capability,
8284 dispatch,
8285 }
8286 }
8287
8288 fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8289 let dispatch_state = state
8290 .dispatches
8291 .get_mut(&dispatch)
8292 .expect("a referenced CurrentThread dispatch must remain registered");
8293 dispatch_state.references = dispatch_state
8294 .references
8295 .checked_add(1)
8296 .expect("CurrentThread dispatch reference count exhausted");
8297 }
8298
8299 fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8300 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8301 return;
8302 };
8303 dispatch_state.references = dispatch_state
8304 .references
8305 .checked_sub(1)
8306 .expect("CurrentThread dispatch reference count underflow");
8307 }
8308
8309 fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8310 let publication = {
8311 let mut state = self
8312 .state
8313 .lock()
8314 .unwrap_or_else(std::sync::PoisonError::into_inner);
8315 let dispatch_state = state.dispatches.entry(dispatch).or_default();
8316 if dispatch_state.is_serviced() {
8317 return CurrentThreadTaskDispatchCompletion {
8318 result: CurrentThreadHostDispatchResult::Accepted,
8319 failed_dispatches: Vec::new(),
8320 };
8321 }
8322 if dispatch_state.is_cancelling() {
8323 return CurrentThreadTaskDispatchCompletion {
8324 result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8325 failed_dispatches: Vec::new(),
8326 };
8327 }
8328 dispatch_state.publications = dispatch_state
8329 .publications
8330 .checked_add(1)
8331 .expect("CurrentThread dispatch publication count exhausted");
8332 CurrentThreadTaskDispatchPublicationGuard {
8333 registry: self,
8334 dispatch: Some(dispatch),
8335 }
8336 };
8337
8338 let CurrentThreadTaskDriverSelection {
8339 mut live,
8340 mut failed_dispatches,
8341 } = self.live();
8342 let planned_deliveries = {
8343 let mut state = self
8344 .state
8345 .lock()
8346 .unwrap_or_else(std::sync::PoisonError::into_inner);
8347 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8348 return CurrentThreadTaskDispatchCompletion {
8349 result: CurrentThreadHostDispatchResult::Unavailable,
8350 failed_dispatches,
8351 };
8352 };
8353 if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8354 Vec::new()
8355 } else {
8356 let mut reserved_deliveries = Vec::with_capacity(live.len());
8360 for (id, _) in live.as_slice().iter().rev() {
8361 let Some(entry) = state.entries.get(id) else {
8362 continue;
8363 };
8364 if entry.delivery.is_none() {
8365 reserved_deliveries.push(self.next_delivery(*id, dispatch));
8366 }
8367 }
8368 state.delivery_drivers.reserve(reserved_deliveries.len());
8369 let mut reserved_deliveries = reserved_deliveries.into_iter();
8370 let mut planned_deliveries = Vec::with_capacity(live.len());
8371 let mut retired = Vec::with_capacity(live.len());
8372 let mut accepted_reservation = false;
8373 for (id, _) in live.as_slice().iter().rev() {
8374 let Some(entry) = state.entries.get(id) else {
8375 continue;
8376 };
8377 if let Some(delivery) = &entry.delivery
8378 && delivery.delivery.dispatch == dispatch
8379 {
8380 accepted_reservation |= delivery.accepted;
8381 continue;
8382 }
8383 if entry.delivery.is_some() {
8384 let previous = state
8385 .entries
8386 .get_mut(id)
8387 .expect("the CurrentThread task driver must remain registered")
8388 .pending
8389 .replace(dispatch);
8390 if previous != Some(dispatch) {
8391 if let Some(previous) = previous {
8392 Self::remove_dispatch_reference(&mut state, previous);
8393 }
8394 Self::add_dispatch_reference(&mut state, dispatch);
8395 }
8396 retired.push(previous);
8397 accepted_reservation = true;
8398 continue;
8399 }
8400 let delivery = reserved_deliveries
8401 .next()
8402 .expect("every available CurrentThread host must have a reserved delivery");
8403 let pending = state
8404 .entries
8405 .get_mut(id)
8406 .expect("the CurrentThread task driver must remain registered")
8407 .pending
8408 .take();
8409 let accepted = pending == Some(dispatch);
8410 if !accepted && pending.is_some() {
8411 retired.push(pending);
8412 }
8413 match pending {
8414 Some(pending) if pending == dispatch => {}
8415 Some(pending) => {
8416 Self::remove_dispatch_reference(&mut state, pending);
8417 Self::add_dispatch_reference(&mut state, dispatch);
8418 }
8419 None => Self::add_dispatch_reference(&mut state, dispatch),
8420 }
8421 let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8422 assert!(
8423 indexed.is_none(),
8424 "CurrentThread delivery capability must be unique"
8425 );
8426 state
8427 .entries
8428 .get_mut(id)
8429 .expect("the CurrentThread task driver must remain registered")
8430 .delivery = Some(CurrentThreadTaskDeliveryState {
8431 delivery,
8432 claimed: false,
8433 accepted,
8434 });
8435 accepted_reservation |= accepted;
8436 planned_deliveries.push((*id, delivery));
8437 }
8438 let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8439 debug_assert!(
8440 all_reserved_deliveries_installed,
8441 "every reserved CurrentThread delivery must be installed"
8442 );
8443 if accepted_reservation {
8444 state
8445 .dispatches
8446 .get_mut(&dispatch)
8447 .expect("the publishing dispatch state must remain registered")
8448 .accepted = true;
8449 }
8450 for dispatch in retired.into_iter().flatten() {
8451 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8452 }
8453 planned_deliveries
8454 }
8455 };
8456
8457 let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8462 let mut planned_deliveries = planned_deliveries.into_iter();
8463 let mut next_delivery = planned_deliveries.next();
8464 while let Some((id, driver)) = live.pop() {
8465 let Some((planned_id, delivery)) = next_delivery else {
8466 break;
8467 };
8468 if planned_id == id {
8469 deliveries.push((id, driver, delivery));
8470 next_delivery = planned_deliveries.next();
8471 } else {
8472 next_delivery = Some((planned_id, delivery));
8473 }
8474 }
8475 debug_assert!(
8476 next_delivery.is_none() && planned_deliveries.len() == 0,
8477 "every planned CurrentThread delivery must retain its selected driver"
8478 );
8479
8480 for (id, driver, delivery) in deliveries {
8481 if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8482 self.accept_delivery(id, delivery);
8483 } else {
8484 let (entry, failures) = self.reject_delivery(id, delivery);
8485 failed_dispatches.extend(failures);
8486 if let Some(entry) = entry {
8487 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8488 let _ = catch_unwind_contained(|| drop(entry));
8489 }
8490 }
8491 }
8492
8493 let result = publication.finish();
8494 debug_assert!(
8495 !failed_dispatches
8496 .iter()
8497 .any(|failure| failure.dispatch == dispatch),
8498 "the active publication must report its own terminal outcome directly"
8499 );
8500 CurrentThreadTaskDispatchCompletion {
8501 result,
8502 failed_dispatches,
8503 }
8504 }
8505
8506 #[cfg(test)]
8507 fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8508 self.dispatch_with_completion(dispatch).result
8509 }
8510
8511 fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8512 let mut state = self
8513 .state
8514 .lock()
8515 .unwrap_or_else(std::sync::PoisonError::into_inner);
8516 let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8517 entry
8518 .delivery
8519 .as_ref()
8520 .is_some_and(|current| current.delivery == delivery)
8521 }) {
8522 state
8523 .entries
8524 .get_mut(&id)
8525 .expect("the matching CurrentThread task driver must remain registered")
8526 .delivery
8527 .as_mut()
8528 .expect("the matching delivery must remain installed")
8529 .accepted = true;
8530 true
8531 } else {
8532 false
8537 };
8538 if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8539 dispatch_state.accepted = true;
8540 dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8541 }
8542 }
8543
8544 fn reject_delivery(
8545 &self,
8546 id: CurrentThreadTaskDriverId,
8547 delivery: CurrentThreadTaskDelivery,
8548 ) -> (
8549 Option<CurrentThreadTaskDriverEntry>,
8550 Vec<CurrentThreadTaskDispatchFailure>,
8551 ) {
8552 let mut state = self
8553 .state
8554 .lock()
8555 .unwrap_or_else(std::sync::PoisonError::into_inner);
8556 if !state.entries.get(&id).is_some_and(|entry| {
8557 entry
8558 .delivery
8559 .as_ref()
8560 .is_some_and(|current| current.delivery == delivery)
8561 }) {
8562 return (None, Vec::new());
8563 }
8564 let entry = state
8565 .remove_driver(id)
8566 .expect("the matching CurrentThread task driver must remain registered");
8567 let mut failures = Vec::new();
8568 Self::finish_entry_references(&mut state, &entry, &mut failures);
8569 (Some(entry), failures)
8570 }
8571
8572 fn finish_delivery(
8573 &self,
8574 delivery: CurrentThreadTaskDelivery,
8575 failed: bool,
8576 ) -> CurrentThreadTaskDeliveryCompletion {
8577 let mut state = self
8578 .state
8579 .lock()
8580 .unwrap_or_else(std::sync::PoisonError::into_inner);
8581 if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8582 entry
8583 .delivery
8584 .as_ref()
8585 .is_some_and(|current| current.delivery == delivery)
8586 }) {
8587 return CurrentThreadTaskDeliveryCompletion::default();
8588 }
8589 let current = state
8590 .entries
8591 .get_mut(&delivery.driver)
8592 .expect("the matching CurrentThread task driver must remain registered")
8593 .delivery
8594 .take()
8595 .expect("the matching delivery must remain installed");
8596 let indexed = state.delivery_drivers.remove(&delivery.capability);
8597 debug_assert_eq!(
8598 indexed,
8599 Some(delivery.driver),
8600 "delivery capability index must match its driver"
8601 );
8602 let redispatch = state
8603 .entries
8604 .get(&delivery.driver)
8605 .expect("the matching CurrentThread task driver must remain registered")
8606 .pending
8607 .is_some_and(|dispatch| {
8608 state
8609 .dispatches
8610 .get(&dispatch)
8611 .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8612 });
8613 let mut failed_dispatches = Vec::new();
8614 Self::finish_dispatch_reference(
8619 &mut state,
8620 delivery.dispatch,
8621 true,
8622 failed || !current.claimed,
8623 &mut failed_dispatches,
8624 );
8625 CurrentThreadTaskDeliveryCompletion {
8626 failed_dispatches,
8627 redispatch,
8628 }
8629 }
8630
8631 fn claim_delivery(&self, capability: u64) -> Option<u64> {
8632 let mut state = self
8633 .state
8634 .lock()
8635 .unwrap_or_else(std::sync::PoisonError::into_inner);
8636 let driver = *state.delivery_drivers.get(&capability)?;
8637 let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8638 if delivery.capability != capability {
8639 return None;
8640 }
8641 let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8642 if dispatch_state.is_serviced()
8643 || dispatch_state.is_cancelling()
8644 || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8645 {
8646 return None;
8647 }
8648 let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8649 current.claimed = true;
8650 current.accepted = true;
8651 state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8652 Some(delivery.dispatch)
8653 }
8654
8655 fn mark_serviced(&self, dispatch: u64) {
8656 let mut state = self
8657 .state
8658 .lock()
8659 .unwrap_or_else(std::sync::PoisonError::into_inner);
8660 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8661 return;
8662 };
8663 dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8664 let mut removed_references = 0usize;
8665 for entry in state.entries.values_mut() {
8666 if entry.pending == Some(dispatch) {
8667 entry.pending = None;
8668 removed_references = removed_references
8669 .checked_add(1)
8670 .expect("CurrentThread dispatch reference count exhausted");
8671 }
8672 }
8673 let dispatch_state = state
8674 .dispatches
8675 .get_mut(&dispatch)
8676 .expect("the serviced CurrentThread dispatch must remain registered");
8677 dispatch_state.references = dispatch_state
8678 .references
8679 .checked_sub(removed_references)
8680 .expect("CurrentThread dispatch reference count underflow");
8681 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8682 }
8683
8684 fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8685 let delivery = {
8686 let state = self
8687 .state
8688 .lock()
8689 .unwrap_or_else(std::sync::PoisonError::into_inner);
8690 state
8691 .delivery_drivers
8692 .get(&capability)
8693 .and_then(|driver| state.entries.get(driver))
8694 .and_then(|entry| entry.delivery.as_ref())
8695 .map(|delivery| delivery.delivery)
8696 .filter(|delivery| delivery.capability == capability)
8697 };
8698 delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8699 self.finish_delivery(delivery, true)
8700 })
8701 }
8702
8703 fn finish_dispatch_reference(
8704 state: &mut CurrentThreadTaskDriverRegistryState,
8705 dispatch: u64,
8706 accepted: bool,
8707 failed: bool,
8708 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8709 ) {
8710 let (publications, references) = {
8711 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8712 return;
8713 };
8714 dispatch_state.references = dispatch_state
8715 .references
8716 .checked_sub(1)
8717 .expect("CurrentThread dispatch reference count underflow");
8718 dispatch_state.accepted |= accepted;
8719 dispatch_state.accepted_failure |= accepted && failed;
8720 (dispatch_state.publications, dispatch_state.references)
8721 };
8722 if references != 0 || publications != 0 {
8723 return;
8724 }
8725 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8726 return;
8727 };
8728 if dispatch_state.accepted_failure
8729 && !dispatch_state.is_serviced()
8730 && !dispatch_state.is_cancelling()
8731 {
8732 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8733 failures.push(dispatch_state.failure(dispatch));
8734 } else if !dispatch_state.is_cancelling() {
8735 state.dispatches.remove(&dispatch);
8736 }
8737 }
8738
8739 fn finish_entry_references(
8740 state: &mut CurrentThreadTaskDriverRegistryState,
8741 entry: &CurrentThreadTaskDriverEntry,
8742 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8743 ) {
8744 if let Some(delivery) = &entry.delivery {
8745 Self::finish_dispatch_reference(
8746 state,
8747 delivery.delivery.dispatch,
8748 delivery.accepted,
8749 delivery.accepted,
8750 failures,
8751 );
8752 }
8753 if let Some(pending) = entry.pending {
8754 Self::finish_dispatch_reference(state, pending, true, true, failures);
8755 }
8756 }
8757
8758 fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8759 let mut state = self
8760 .state
8761 .lock()
8762 .unwrap_or_else(std::sync::PoisonError::into_inner);
8763 let publications = {
8764 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8765 return CurrentThreadHostDispatchResult::Unavailable;
8766 };
8767 dispatch_state.publications = dispatch_state
8768 .publications
8769 .checked_sub(1)
8770 .expect("CurrentThread dispatch publication count underflow");
8771 dispatch_state.publications
8772 };
8773 if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8774 return CurrentThreadHostDispatchResult::Accepted;
8775 }
8776 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8777 return CurrentThreadHostDispatchResult::Unavailable;
8778 };
8779 if dispatch_state.is_cancelling() {
8780 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8781 }
8782 if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8783 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8784 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8785 }
8786 let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8787 state.dispatches.remove(&dispatch);
8788 if accepted {
8789 CurrentThreadHostDispatchResult::Accepted
8790 } else {
8791 CurrentThreadHostDispatchResult::Unavailable
8792 }
8793 }
8794
8795 fn abort_publication(&self, dispatch: u64) {
8796 let mut state = self
8797 .state
8798 .lock()
8799 .unwrap_or_else(std::sync::PoisonError::into_inner);
8800 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8801 return;
8802 };
8803 let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8804 return;
8805 };
8806 dispatch_state.publications = publications;
8807 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8808 }
8809
8810 fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8811 let mut state = self
8812 .state
8813 .lock()
8814 .unwrap_or_else(std::sync::PoisonError::into_inner);
8815 let can_rearm = state
8816 .dispatches
8817 .get(&failure.dispatch)
8818 .is_some_and(|dispatch_state| {
8819 dispatch_state.is_cancelling()
8820 && dispatch_state.epoch == failure.epoch
8821 && dispatch_state.publications == 0
8822 })
8823 && !Self::has_dispatch_reference(&state, failure.dispatch);
8824 if !can_rearm {
8825 return false;
8826 }
8827 let dispatch_state = state
8828 .dispatches
8829 .get_mut(&failure.dispatch)
8830 .expect("the exact failed CurrentThread dispatch must remain registered");
8831 let epoch = dispatch_state
8832 .epoch
8833 .checked_add(1)
8834 .expect("CurrentThread dispatch failure epoch space exhausted");
8835 *dispatch_state = CurrentThreadDispatchState {
8836 epoch,
8837 ..CurrentThreadDispatchState::default()
8838 };
8839 true
8840 }
8841
8842 fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8843 self
8844 .state
8845 .lock()
8846 .unwrap_or_else(std::sync::PoisonError::into_inner)
8847 .dispatches
8848 .get(&failure.dispatch)
8849 .is_some_and(|dispatch_state| {
8850 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8851 })
8852 }
8853
8854 fn retire_unreferenced_dispatch(
8855 state: &mut CurrentThreadTaskDriverRegistryState,
8856 dispatch: Option<u64>,
8857 ) {
8858 let Some(dispatch) = dispatch else {
8859 return;
8860 };
8861 if Self::has_dispatch_reference(state, dispatch) {
8862 return;
8863 }
8864 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8865 return;
8866 };
8867 if dispatch_state.publications == 0
8868 && !dispatch_state.is_cancelling()
8869 && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8870 {
8871 state.dispatches.remove(&dispatch);
8872 }
8873 }
8874
8875 fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8876 state
8877 .dispatches
8878 .get(&dispatch)
8879 .is_some_and(|dispatch_state| dispatch_state.references != 0)
8880 }
8881
8882 fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8883 let mut state = self
8884 .state
8885 .lock()
8886 .unwrap_or_else(std::sync::PoisonError::into_inner);
8887 if state
8888 .dispatches
8889 .get(&failure.dispatch)
8890 .is_some_and(|dispatch_state| {
8891 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8892 })
8893 {
8894 state.dispatches.remove(&failure.dispatch);
8895 }
8896 }
8897
8898 fn retire_for_shutdown(&self) {
8899 let mut state = self
8900 .state
8901 .lock()
8902 .unwrap_or_else(std::sync::PoisonError::into_inner);
8903 state.dispatches.clear();
8904 for entry in state.entries.values_mut() {
8905 entry.pending = None;
8906 }
8907 }
8908}
8909
8910static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8911 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8912
8913pub fn register_current_thread_task_driver(
8914 driver: Arc<dyn CurrentThreadTaskDriver>,
8915) -> CurrentThreadTaskDriverId {
8916 CURRENT_THREAD_TASK_DRIVERS.register(driver)
8917}
8918
8919pub fn request_current_thread_task_drain() {
8924 RUNTIME.request_current_thread_drain();
8925}
8926
8927pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8928 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8929}
8930
8931fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8932 let CurrentThreadTaskDispatchCompletion {
8933 result,
8934 failed_dispatches,
8935 } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8936 complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8937 failed_dispatches,
8938 redispatch: false,
8939 });
8940 result
8941}
8942
8943pub type TimerId = u64;
8944
8945static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8947
8948pub trait TimerDriver: Send + Sync + 'static {
8954 fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8958 fn cancel(&self, id: TimerId);
8961 fn is_live(&self) -> bool {
8966 true
8967 }
8968 fn on_swept(&self) {}
8978}
8979
8980#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8984pub struct TimerDriverId(u64);
8985
8986type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8987
8988struct TimerDriverEntry {
8989 driver: Arc<dyn TimerDriver>,
8990 previous: Option<TimerDriverId>,
8991 next: Option<TimerDriverId>,
8992}
8993
8994#[derive(Default)]
8995struct TimerDriverRegistryState {
8996 entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8997 oldest: Option<TimerDriverId>,
8998 newest: Option<TimerDriverId>,
8999}
9000
9001impl TimerDriverRegistryState {
9002 fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
9003 let previous = self.newest;
9004 let replaced = self.entries.insert(
9005 id,
9006 TimerDriverEntry {
9007 driver,
9008 previous,
9009 next: None,
9010 },
9011 );
9012 assert!(replaced.is_none(), "timer driver id must be unique");
9013 if let Some(previous) = previous {
9014 self
9015 .entries
9016 .get_mut(&previous)
9017 .expect("the newest timer driver must remain registered")
9018 .next = Some(id);
9019 } else {
9020 self.oldest = Some(id);
9021 }
9022 self.newest = Some(id);
9023 }
9024
9025 fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
9026 let entry = self.entries.remove(&id)?;
9027 if let Some(previous) = entry.previous {
9028 self
9029 .entries
9030 .get_mut(&previous)
9031 .expect("the previous timer driver must remain registered")
9032 .next = entry.next;
9033 } else {
9034 self.oldest = entry.next;
9035 }
9036 if let Some(next) = entry.next {
9037 self
9038 .entries
9039 .get_mut(&next)
9040 .expect("the next timer driver must remain registered")
9041 .previous = entry.previous;
9042 } else {
9043 self.newest = entry.previous;
9044 }
9045 Some(entry)
9046 }
9047
9048 fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
9049 let mut snapshot = Vec::with_capacity(self.entries.len());
9050 let mut current = self.oldest;
9051 while let Some(id) = current {
9052 let entry = self
9053 .entries
9054 .get(&id)
9055 .expect("timer driver order must reference a live entry");
9056 snapshot.push((id, Arc::clone(&entry.driver)));
9057 current = entry.next;
9058 }
9059 debug_assert_eq!(
9060 snapshot.len(),
9061 self.entries.len(),
9062 "timer driver order must contain every entry exactly once"
9063 );
9064 snapshot
9065 }
9066
9067 fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
9068 if self.entries.len() != expected.len() {
9069 return false;
9070 }
9071 let mut current = self.oldest;
9072 for expected in expected {
9073 if current != Some(*expected) {
9074 return false;
9075 }
9076 current = self
9077 .entries
9078 .get(expected)
9079 .expect("timer driver order must reference a live entry")
9080 .next;
9081 }
9082 current.is_none()
9083 }
9084}
9085
9086#[derive(Default)]
9104pub struct TimerDriverRegistry {
9105 state: Mutex<TimerDriverRegistryState>,
9109 pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
9114 next_id: AtomicU64,
9115}
9116
9117impl Drop for TimerDriverRegistry {
9118 fn drop(&mut self) {
9119 let state = self
9120 .state
9121 .get_mut()
9122 .unwrap_or_else(std::sync::PoisonError::into_inner);
9123 state.oldest = None;
9124 state.newest = None;
9125 let entries = std::mem::take(&mut state.entries);
9126 for (_, entry) in entries {
9127 let _ = catch_unwind_contained(|| drop(entry));
9128 }
9129 }
9130}
9131
9132impl TimerDriverRegistry {
9133 pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9135 let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
9136 let id = TimerDriverId(id);
9137 self
9138 .state
9139 .lock()
9140 .unwrap_or_else(std::sync::PoisonError::into_inner)
9141 .insert_driver(id, driver);
9142 let pending = {
9147 let mut entries = self
9148 .pending
9149 .lock()
9150 .unwrap_or_else(std::sync::PoisonError::into_inner);
9151 let mut pending = Vec::with_capacity(entries.len());
9152 entries.retain(|_, repoll| {
9153 if let Some(repoll) = repoll.upgrade() {
9154 pending.push(repoll);
9155 true
9156 } else {
9157 false
9158 }
9159 });
9160 pending
9161 };
9162 for repoll in pending {
9163 repoll.wake();
9164 }
9165 id
9166 }
9167
9168 pub fn unregister(&self, id: TimerDriverId) {
9170 let removed = self
9171 .state
9172 .lock()
9173 .unwrap_or_else(std::sync::PoisonError::into_inner)
9174 .remove_driver(id);
9175 let _ = catch_unwind_contained(|| drop(removed));
9178 }
9179
9180 fn live(&self) -> HostTimerDriverSnapshot {
9191 loop {
9192 let snapshot = HostTimerDriverSnapshot::new({
9193 let state = self
9194 .state
9195 .lock()
9196 .unwrap_or_else(std::sync::PoisonError::into_inner);
9197 state.ordered_snapshot()
9198 });
9199 let mut dead_ids = FxHashSet::default();
9200 let mut live_ids = Vec::new();
9201 for (id, driver) in snapshot.as_slice() {
9202 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9203 live_ids.push(*id);
9204 } else {
9205 dead_ids.insert(*id);
9206 }
9207 }
9208
9209 let (live, swept, retry) = {
9210 let mut state = self
9211 .state
9212 .lock()
9213 .unwrap_or_else(std::sync::PoisonError::into_inner);
9214 let mut swept = Vec::new();
9215 let mut current = state.oldest;
9216 while let Some(id) = current {
9217 let next = state
9218 .entries
9219 .get(&id)
9220 .expect("timer driver order must reference a live entry")
9221 .next;
9222 if dead_ids.contains(&id)
9223 && let Some(entry) = state.remove_driver(id)
9224 {
9225 swept.push(entry);
9226 }
9227 current = next;
9228 }
9229 let retry = !state.ordered_ids_match(&live_ids);
9230 let live = if retry {
9231 Vec::new()
9232 } else {
9233 state.ordered_snapshot()
9234 };
9235 (HostTimerDriverSnapshot::new(live), swept, retry)
9236 };
9237
9238 drop(snapshot);
9241 for entry in swept {
9242 let _ = catch_unwind_contained(|| entry.driver.on_swept());
9243 let _ = catch_unwind_contained(|| drop(entry));
9244 }
9245 if !retry {
9246 return live;
9247 }
9248 }
9249 }
9250
9251 #[cfg(test)]
9252 fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9253 let mut live = self.live();
9254 live.pop().map(|(id, driver)| (id, driver.into_inner()))
9255 }
9256
9257 pub fn has_live_driver(&self) -> bool {
9259 !self.live().is_empty()
9260 }
9261
9262 fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9263 let replaced = self
9264 .pending
9265 .lock()
9266 .unwrap_or_else(std::sync::PoisonError::into_inner)
9267 .insert(id, Arc::downgrade(repoll));
9268 assert!(
9269 replaced.is_none(),
9270 "host timer {id} registered its repoll signal twice"
9271 );
9272 if repoll.is_closed() {
9275 self.remove_pending(id);
9276 }
9277 }
9278
9279 fn remove_pending(&self, id: TimerId) {
9280 let removed = self
9281 .pending
9282 .lock()
9283 .unwrap_or_else(std::sync::PoisonError::into_inner)
9284 .remove(&id);
9285 let _ = catch_unwind_contained(|| drop(removed));
9286 }
9287
9288 #[cfg(test)]
9289 fn pending_len(&self) -> usize {
9290 self
9291 .pending
9292 .lock()
9293 .unwrap_or_else(std::sync::PoisonError::into_inner)
9294 .len()
9295 }
9296}
9297
9298static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9301 LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9302
9303pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9309 TIMER_DRIVERS.register(driver)
9310}
9311
9312pub fn unregister_timer_driver(id: TimerDriverId) {
9316 TIMER_DRIVERS.unregister(id);
9317}
9318
9319pub fn has_live_timer_driver() -> bool {
9326 TIMER_DRIVERS.has_live_driver()
9327}
9328
9329#[derive(Default)]
9333struct HostTimerRepoll {
9334 state: Mutex<HostTimerRepollState>,
9335}
9336
9337#[derive(Default)]
9338struct HostTimerRepollState {
9339 closed: bool,
9340 notified: bool,
9341 waker: Option<Waker>,
9342}
9343
9344impl HostTimerRepoll {
9345 fn register(&self, waker: Waker) {
9346 let (replaced, wake) = {
9347 let mut state = self
9348 .state
9349 .lock()
9350 .unwrap_or_else(std::sync::PoisonError::into_inner);
9351 if state.closed {
9352 (Some(waker), None)
9353 } else if state.notified {
9354 state.notified = false;
9355 (state.waker.take(), Some(waker))
9356 } else {
9357 (state.waker.replace(waker), None)
9358 }
9359 };
9360 let _ = catch_unwind_contained(|| drop(replaced));
9361 if let Some(waker) = wake {
9362 wake_waker_contained(waker);
9363 }
9364 }
9365
9366 fn wake(&self) {
9367 let waker = {
9368 let mut state = self
9369 .state
9370 .lock()
9371 .unwrap_or_else(std::sync::PoisonError::into_inner);
9372 if state.closed {
9373 return;
9374 }
9375 if let Some(waker) = state.waker.take() {
9376 Some(waker)
9377 } else {
9378 state.notified = true;
9379 None
9380 }
9381 };
9382 if let Some(waker) = waker {
9383 wake_waker_contained(waker);
9384 }
9385 }
9386
9387 fn close(&self) {
9388 let waker = {
9389 let mut state = self
9390 .state
9391 .lock()
9392 .unwrap_or_else(std::sync::PoisonError::into_inner);
9393 state.closed = true;
9394 state.notified = false;
9395 state.waker.take()
9396 };
9397 let _ = catch_unwind_contained(|| drop(waker));
9398 }
9399
9400 fn is_closed(&self) -> bool {
9401 self
9402 .state
9403 .lock()
9404 .unwrap_or_else(std::sync::PoisonError::into_inner)
9405 .closed
9406 }
9407}
9408
9409#[derive(Default)]
9416struct HostTimerRegistry {
9417 state: Mutex<HostTimerRegistryState>,
9418}
9419
9420#[derive(Default)]
9421struct HostTimerRegistryState {
9422 closed: bool,
9423 timers: FxHashMap<TimerId, HostTimerEntry>,
9424}
9425
9426struct HostTimerEntry {
9427 deadline: Instant,
9428 waker: Waker,
9429 fired: Arc<AtomicBool>,
9430 drivers: Weak<TimerDriverRegistry>,
9431 repoll: Arc<HostTimerRepoll>,
9432}
9433
9434impl HostTimerRegistry {
9435 fn register(
9436 &self,
9437 id: TimerId,
9438 deadline: Instant,
9439 waker: Waker,
9440 fired: &Arc<AtomicBool>,
9441 drivers: &Arc<TimerDriverRegistry>,
9442 repoll: &Arc<HostTimerRepoll>,
9443 ) -> bool {
9444 let replaced = {
9445 let mut state = self
9446 .state
9447 .lock()
9448 .unwrap_or_else(std::sync::PoisonError::into_inner);
9449 if state.closed || fired.load(Ordering::SeqCst) {
9450 fired.store(true, Ordering::SeqCst);
9451 Err(waker)
9452 } else {
9453 Ok(state.timers.insert(
9454 id,
9455 HostTimerEntry {
9456 deadline,
9457 waker,
9458 fired: Arc::clone(fired),
9459 drivers: Arc::downgrade(drivers),
9460 repoll: Arc::clone(repoll),
9461 },
9462 ))
9463 }
9464 };
9465 match replaced {
9466 Ok(Some(entry)) => {
9467 let _ = catch_unwind_contained(|| drop(entry));
9468 true
9469 }
9470 Ok(None) => true,
9471 Err(waker) => {
9472 let _ = catch_unwind_contained(|| drop(waker));
9473 false
9474 }
9475 }
9476 }
9477
9478 fn remove(&self, id: TimerId) {
9479 let removed = self
9480 .state
9481 .lock()
9482 .unwrap_or_else(std::sync::PoisonError::into_inner)
9483 .timers
9484 .remove(&id);
9485 if let Some(entry) = removed {
9486 let _ = catch_unwind_contained(|| drop(entry));
9487 }
9488 }
9489
9490 fn shutdown(&self) {
9491 let timers = {
9492 let mut state = self
9493 .state
9494 .lock()
9495 .unwrap_or_else(std::sync::PoisonError::into_inner);
9496 state.closed = true;
9497 state
9498 .timers
9499 .drain()
9500 .map(|(id, entry)| {
9501 entry.fired.store(true, Ordering::SeqCst);
9502 (id, entry)
9503 })
9504 .collect::<Vec<_>>()
9505 };
9506 for (id, entry) in timers {
9507 entry.repoll.close();
9508 if let Some(drivers) = entry.drivers.upgrade() {
9509 drivers.remove_pending(id);
9510 }
9511 wake_waker_contained(entry.waker);
9512 }
9513 }
9514
9515 fn has_pending(&self) -> bool {
9520 !self
9521 .state
9522 .lock()
9523 .unwrap_or_else(std::sync::PoisonError::into_inner)
9524 .timers
9525 .is_empty()
9526 }
9527
9528 fn has_live_wait(&self, grace: Duration) -> bool {
9537 let now = Instant::now();
9538 self
9539 .state
9540 .lock()
9541 .unwrap_or_else(std::sync::PoisonError::into_inner)
9542 .timers
9543 .values()
9544 .any(|entry| {
9545 entry
9546 .deadline
9547 .checked_add(grace)
9548 .is_none_or(|deadline| deadline > now)
9549 })
9550 }
9551}
9552
9553struct HostTimerPendingGuard {
9557 registry: Arc<HostTimerRegistry>,
9558 drivers: Arc<TimerDriverRegistry>,
9559 repoll: Arc<HostTimerRepoll>,
9560 id: TimerId,
9561}
9562
9563impl Drop for HostTimerPendingGuard {
9564 fn drop(&mut self) {
9565 self.repoll.close();
9566 self.registry.remove(self.id);
9567 self.drivers.remove_pending(self.id);
9568 }
9569}
9570
9571#[cfg(napi_runtime_os_threads)]
9577struct TimerHeap {
9578 inner: Arc<Mutex<TimerHeapInner>>,
9579 parker: Arc<DriverParker>,
9580 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9581 deadlock_state: Arc<MultiThreadDeadlockState>,
9582 #[cfg(test)]
9583 fail_next_start: AtomicBool,
9584 generation: u64,
9585 executor_id: u64,
9586 thread_name: String,
9587}
9588
9589#[cfg(napi_runtime_os_threads)]
9590#[derive(Default)]
9591struct TimerHeapInner {
9592 queue: TimerDeadlineQueue,
9596 entries: FxHashMap<TimerId, HeapTimerEntry>,
9597 timekeeper_parker: Option<Arc<DriverParker>>,
9601 closed: bool,
9605}
9606
9607#[cfg(napi_runtime_os_threads)]
9608struct HeapTimerEntry {
9609 waker: Waker,
9610 fired: Arc<AtomicBool>,
9615}
9616
9617#[cfg(napi_runtime_os_threads)]
9618#[derive(Default)]
9619struct TimerDeadlineQueue {
9620 nodes: Vec<(Instant, TimerId)>,
9621 positions: FxHashMap<TimerId, usize>,
9622}
9623
9624#[cfg(napi_runtime_os_threads)]
9625impl TimerDeadlineQueue {
9626 #[cfg(test)]
9627 fn len(&self) -> usize {
9628 self.nodes.len()
9629 }
9630
9631 #[cfg(test)]
9632 fn is_empty(&self) -> bool {
9633 self.nodes.is_empty()
9634 }
9635
9636 fn clear(&mut self) {
9637 self.nodes.clear();
9638 self.positions.clear();
9639 }
9640
9641 fn peek(&self) -> Option<(Instant, TimerId)> {
9642 self.nodes.first().copied()
9643 }
9644
9645 fn push(&mut self, deadline: Instant, id: TimerId) {
9646 let index = self.nodes.len();
9647 assert!(
9648 self.positions.insert(id, index).is_none(),
9649 "timer deadline queue received duplicate id {id}"
9650 );
9651 self.nodes.push((deadline, id));
9652 self.sift_up(index);
9653 }
9654
9655 fn pop(&mut self) -> Option<(Instant, TimerId)> {
9656 let first = self.nodes.first().copied()?;
9657 self.positions.remove(&first.1);
9658 let last = self
9659 .nodes
9660 .pop()
9661 .expect("the non-empty timer heap must have a final node");
9662 if !self.nodes.is_empty() {
9663 self.nodes[0] = last;
9664 self.positions.insert(last.1, 0);
9665 self.sift_down(0);
9666 }
9667 Some(first)
9668 }
9669
9670 fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9671 let index = self.positions.remove(&id)?;
9672 let removed = self.nodes[index];
9673 let last = self.nodes.pop().expect("an indexed timer node must exist");
9674 if index < self.nodes.len() {
9675 self.nodes[index] = last;
9676 self.positions.insert(last.1, index);
9677 if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9678 self.sift_up(index);
9679 } else {
9680 self.sift_down(index);
9681 }
9682 }
9683 Some(removed)
9684 }
9685
9686 fn sift_up(&mut self, mut index: usize) {
9687 while index > 0 {
9688 let parent = (index - 1) / 2;
9689 if self.nodes[parent] <= self.nodes[index] {
9690 break;
9691 }
9692 self.swap(parent, index);
9693 index = parent;
9694 }
9695 }
9696
9697 fn sift_down(&mut self, mut index: usize) {
9698 loop {
9699 let left = index * 2 + 1;
9700 if left >= self.nodes.len() {
9701 return;
9702 }
9703 let right = left + 1;
9704 let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9705 right
9706 } else {
9707 left
9708 };
9709 if self.nodes[index] <= self.nodes[child] {
9710 return;
9711 }
9712 self.swap(index, child);
9713 index = child;
9714 }
9715 }
9716
9717 fn swap(&mut self, left: usize, right: usize) {
9718 self.nodes.swap(left, right);
9719 self.positions.insert(self.nodes[left].1, left);
9720 self.positions.insert(self.nodes[right].1, right);
9721 }
9722}
9723
9724#[cfg(napi_runtime_os_threads)]
9725impl TimerHeapInner {
9726 fn next_deadline(&self) -> Option<Instant> {
9727 self.queue.peek().map(|(deadline, _)| deadline)
9728 }
9729}
9730
9731#[cfg(napi_runtime_os_threads)]
9732impl TimerHeap {
9733 fn new(
9734 generation: u64,
9735 executor_id: u64,
9736 thread_name: String,
9737 deadlock_state: Arc<MultiThreadDeadlockState>,
9738 ) -> Self {
9739 Self {
9740 inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9741 parker: Arc::new(DriverParker::default()),
9742 thread: Mutex::new(None),
9743 deadlock_state,
9744 #[cfg(test)]
9745 fail_next_start: AtomicBool::new(false),
9746 generation,
9747 executor_id,
9748 thread_name,
9749 }
9750 }
9751
9752 fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9753 if self
9754 .inner
9755 .lock()
9756 .unwrap_or_else(std::sync::PoisonError::into_inner)
9757 .closed
9758 {
9759 return Ok(());
9760 }
9761 let current_thread = std::thread::current().id();
9762 loop {
9763 let finished = {
9764 let mut thread = self
9765 .thread
9766 .lock()
9767 .unwrap_or_else(std::sync::PoisonError::into_inner);
9768 match thread.as_ref() {
9769 Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9770 Some(handle) if !handle.is_finished() => return Ok(()),
9771 Some(_) => thread.take(),
9772 None => {
9773 if self
9776 .inner
9777 .lock()
9778 .unwrap_or_else(std::sync::PoisonError::into_inner)
9779 .closed
9780 {
9781 return Ok(());
9782 }
9783 #[cfg(test)]
9784 if self.fail_next_start.swap(false, Ordering::SeqCst) {
9785 return Err(RuntimeConfigError(
9786 "injected runtime timer thread creation failure".to_string(),
9787 ));
9788 }
9789
9790 let inner = Arc::clone(&self.inner);
9791 let parker = Arc::clone(&self.parker);
9792 let deadlock_state = Arc::clone(&self.deadlock_state);
9793 let generation = self.generation;
9794 let executor_id = self.executor_id;
9795 let handle = std::thread::Builder::new()
9796 .name(self.thread_name.clone())
9797 .spawn(move || {
9798 ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9801 let _generation = RuntimeGenerationGuard::enter(generation);
9802 timer_timekeeper_main(&inner, &parker, &deadlock_state);
9803 })
9804 .map_err(|error| {
9805 RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9806 })?;
9807 *thread = Some(handle);
9808 return Ok(());
9809 }
9810 }
9811 };
9812
9813 if let Some(finished) = finished
9817 && finished.thread().id() != current_thread
9818 && let Err(payload) = finished.join()
9819 {
9820 discard_panic_payload(payload);
9821 }
9822 if self
9823 .inner
9824 .lock()
9825 .unwrap_or_else(std::sync::PoisonError::into_inner)
9826 .closed
9827 {
9828 return Ok(());
9829 }
9830 }
9831 }
9832
9833 fn close(&self) -> Vec<Waker> {
9834 let mut inner = self
9835 .inner
9836 .lock()
9837 .unwrap_or_else(std::sync::PoisonError::into_inner);
9838 inner.closed = true;
9839 inner.queue.clear();
9840 inner.timekeeper_parker = None;
9841 inner
9842 .entries
9843 .drain()
9844 .map(|(_, entry)| {
9845 entry.fired.store(true, Ordering::SeqCst);
9846 entry.waker
9847 })
9848 .collect()
9849 }
9850
9851 fn join(&self) {
9852 let handle = self
9853 .thread
9854 .lock()
9855 .unwrap_or_else(std::sync::PoisonError::into_inner)
9856 .take();
9857 if let Some(handle) = handle {
9858 if handle.thread().id() == std::thread::current().id() {
9859 drop(handle);
9863 return;
9864 }
9865 if let Err(payload) = crate::sync::join(handle) {
9866 discard_panic_payload(payload);
9867 }
9868 }
9869 }
9870}
9871
9872#[cfg(napi_runtime_os_threads)]
9873impl Drop for TimerHeap {
9874 fn drop(&mut self) {
9875 let mut admission = self.deadlock_state.begin_admission();
9876 let wakers = self.close();
9877 if !wakers.is_empty()
9878 && let Some(admission) = admission.as_mut()
9879 {
9880 admission.mark_published();
9881 }
9882 self.parker.unpark();
9883 for waker in wakers {
9884 wake_waker_contained(waker);
9885 }
9886 drop(admission);
9887 self.join();
9888 }
9889}
9890
9891#[cfg(napi_runtime_os_threads)]
9892fn timer_timekeeper_main(
9893 inner: &Mutex<TimerHeapInner>,
9894 parker: &Arc<DriverParker>,
9895 deadlock_state: &Arc<MultiThreadDeadlockState>,
9896) {
9897 loop {
9898 let mut admission = deadlock_state.begin_admission();
9902 let (due, deadline) = {
9903 let mut inner = inner
9904 .lock()
9905 .unwrap_or_else(std::sync::PoisonError::into_inner);
9906 if inner.closed {
9907 inner.timekeeper_parker = None;
9908 return;
9909 }
9910 let now = Instant::now();
9911 let mut due = Vec::new();
9912 while let Some((deadline, id)) = inner.queue.peek() {
9913 if deadline > now {
9914 break;
9915 }
9916 inner.queue.pop();
9917 if let Some(entry) = inner.entries.remove(&id) {
9918 entry.fired.store(true, Ordering::SeqCst);
9919 due.push(entry.waker);
9920 }
9921 }
9922 let deadline = inner.next_deadline();
9923 inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9924 (due, deadline)
9925 };
9926
9927 if !due.is_empty() {
9928 if let Some(admission) = admission.as_mut() {
9929 admission.mark_published();
9930 }
9931 for waker in due {
9932 wake_waker_contained(waker);
9933 }
9934 continue;
9935 }
9936 drop(admission);
9939
9940 match deadline {
9941 Some(deadline) => {
9942 let now = Instant::now();
9943 if deadline > now {
9944 parker.park_timeout(deadline - now);
9945 }
9946 }
9947 None => parker.park(),
9948 }
9949 }
9950}
9951
9952#[cfg(napi_runtime_os_threads)]
9953impl MultiThreadExecutor {
9954 fn register_timer(
9960 self: &Arc<Self>,
9961 id: TimerId,
9962 deadline: Instant,
9963 waker: Waker,
9964 fired: &Arc<AtomicBool>,
9965 ) {
9966 let mut admission = self.begin_deadlock_admission();
9967 #[cfg(test)]
9968 run_timer_registration_admission_test_hook();
9969 if let Err(error) = self.timers.ensure_started() {
9973 let _ = catch_unwind_contained(|| drop(waker));
9974 panic!("{error}");
9975 }
9976 let (became_earliest, published, discarded_waker) = {
9977 let mut inner = self
9978 .timers
9979 .inner
9980 .lock()
9981 .unwrap_or_else(std::sync::PoisonError::into_inner);
9982 if inner.closed || fired.load(Ordering::SeqCst) {
9983 fired.store(true, Ordering::SeqCst);
9984 (false, false, Some(waker))
9985 } else if let Some(entry) = inner.entries.get_mut(&id) {
9986 (
9988 false,
9989 false,
9990 Some(std::mem::replace(&mut entry.waker, waker)),
9991 )
9992 } else {
9993 let previous = inner.next_deadline();
9994 inner.entries.insert(
9995 id,
9996 HeapTimerEntry {
9997 waker,
9998 fired: Arc::clone(fired),
9999 },
10000 );
10001 inner.queue.push(deadline, id);
10002 (
10003 previous.is_none_or(|previous| deadline < previous),
10004 true,
10005 None,
10006 )
10007 }
10008 };
10009 if published && let Some(admission) = admission.as_mut() {
10010 admission.mark_published();
10011 }
10012 if let Some(waker) = discarded_waker {
10013 let _ = catch_unwind_contained(|| drop(waker));
10016 }
10017 if became_earliest {
10018 self.note_earlier_timer();
10020 }
10021 }
10022
10023 fn cancel_timer(&self, id: TimerId) {
10027 let (removed, earliest_changed) = {
10028 let mut inner = self
10029 .timers
10030 .inner
10031 .lock()
10032 .unwrap_or_else(std::sync::PoisonError::into_inner);
10033 let previous = inner.next_deadline();
10034 let removed = inner.entries.remove(&id);
10035 if removed.is_some() {
10036 let queued = inner.queue.remove(id);
10037 debug_assert!(
10038 queued.is_some(),
10039 "a live timer entry must have one indexed deadline node"
10040 );
10041 }
10042 let current = inner.next_deadline();
10043 (removed, previous != current)
10044 };
10045 if let Some(entry) = removed {
10046 let _ = catch_unwind_contained(|| drop(entry));
10049 }
10050 if earliest_changed {
10051 self.timers.parker.unpark();
10052 self.parked_drivers.wake_all();
10053 }
10054 }
10055
10056 fn next_timer_deadline(&self) -> Option<Instant> {
10058 self
10059 .timers
10060 .inner
10061 .lock()
10062 .unwrap_or_else(std::sync::PoisonError::into_inner)
10063 .next_deadline()
10064 }
10065
10066 fn fire_due_timers(&self) {
10071 let mut admission = self.begin_deadlock_admission();
10072 let due: Vec<Waker> = {
10073 let mut inner = self
10074 .timers
10075 .inner
10076 .lock()
10077 .unwrap_or_else(std::sync::PoisonError::into_inner);
10078 let now = Instant::now();
10079 let mut due = Vec::new();
10080 while let Some((deadline, id)) = inner.queue.peek() {
10081 if deadline > now {
10082 break;
10083 }
10084 inner.queue.pop();
10085 if let Some(entry) = inner.entries.remove(&id) {
10086 entry.fired.store(true, Ordering::SeqCst);
10087 due.push(entry.waker);
10088 }
10089 }
10090 due
10091 };
10092 if !due.is_empty()
10093 && let Some(admission) = admission.as_mut()
10094 {
10095 admission.mark_published();
10096 }
10097 for waker in due {
10098 wake_waker_contained(waker);
10101 }
10102 }
10103
10104 fn note_earlier_timer(self: &Arc<Self>) {
10108 self.timers.parker.unpark();
10109 self.parked_drivers.wake_one();
10110 }
10111
10112 fn shutdown_timers(&self) {
10118 let mut admission = self.begin_deadlock_admission();
10119 let wakers = self.timers.close();
10120 if !wakers.is_empty()
10121 && let Some(admission) = admission.as_mut()
10122 {
10123 admission.mark_published();
10124 }
10125 for waker in wakers {
10126 wake_waker_contained(waker);
10130 }
10131 self.timers.parker.unpark();
10132 }
10133
10134 fn begin_shutdown(&self) {
10135 let _generation = RuntimeGenerationGuard::enter(self.generation);
10136 self.stop.begin_shutdown();
10141 let queued = {
10142 let mut queue = self
10143 .blocking_queue
10144 .lock()
10145 .unwrap_or_else(std::sync::PoisonError::into_inner);
10146 queue.closed = true;
10149 queue.take_all()
10150 };
10151 for queued in queued {
10156 let _ = catch_unwind_contained(|| drop(queued));
10157 }
10158 self.shutdown_timers();
10159 }
10160
10161 fn scheduler_work_pending(&self) -> bool {
10165 self.active_drainers.load(Ordering::Acquire) != 0
10166 }
10167
10168 fn wait_until_scheduler_idle(&self) {
10169 self.timers.join();
10170 let mut idle = self
10171 .scheduler_idle_lock
10172 .lock()
10173 .unwrap_or_else(std::sync::PoisonError::into_inner);
10174 *idle += 1;
10180 while self.active_drainers.load(Ordering::Acquire) != 0 {
10181 idle = self
10182 .scheduler_idle
10183 .wait(idle)
10184 .unwrap_or_else(std::sync::PoisonError::into_inner);
10185 }
10186 *idle -= 1;
10187 }
10188}
10189
10190pub struct Sleep {
10195 deadline: Instant,
10196 inner: SleepInner,
10197}
10198
10199enum SleepInner {
10200 #[cfg(napi_runtime_os_threads)]
10201 Heap(HeapSleep),
10202 Host(HostSleep),
10203}
10204
10205#[cfg(napi_runtime_os_threads)]
10206struct HeapSleep {
10207 id: TimerId,
10208 executor: Weak<MultiThreadExecutor>,
10209 fired: Arc<AtomicBool>,
10210 registered: bool,
10211}
10212
10213struct HostSleep {
10214 id: TimerId,
10215 drivers: Arc<TimerDriverRegistry>,
10219 armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10223 registry: Arc<HostTimerRegistry>,
10226 fired: Arc<AtomicBool>,
10227 repoll: Arc<HostTimerRepoll>,
10231 pending: Option<HostTimerPendingGuard>,
10236}
10237
10238fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10239 let _ = catch_unwind_contained(|| driver.cancel(id));
10240 let _ = catch_unwind_contained(|| drop(driver));
10241}
10242
10243fn cancel_host_timer_drivers(
10244 id: TimerId,
10245 armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10246) {
10247 for (_, driver) in armed.drain() {
10248 cancel_host_timer_driver(id, driver);
10249 }
10250}
10251
10252impl Future for Sleep {
10253 type Output = ();
10254
10255 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10256 let deadline = self.deadline;
10257 let this = self.get_mut();
10258 match &mut this.inner {
10259 #[cfg(napi_runtime_os_threads)]
10260 SleepInner::Heap(heap) => {
10261 if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10262 if heap.registered {
10263 if let Some(executor) = heap.executor.upgrade() {
10266 executor.cancel_timer(heap.id);
10267 }
10268 heap.registered = false;
10269 }
10270 return Poll::Ready(());
10271 }
10272 let Some(executor) = heap.executor.upgrade() else {
10273 heap.fired.store(true, Ordering::SeqCst);
10274 heap.registered = false;
10275 return Poll::Ready(());
10276 };
10277 executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10278 heap.registered = true;
10279 if heap.fired.load(Ordering::SeqCst) {
10283 heap.registered = false;
10284 return Poll::Ready(());
10285 }
10286 Poll::Pending
10287 }
10288 SleepInner::Host(host) => {
10289 if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10290 cancel_host_timer_drivers(host.id, &mut host.armed);
10291 host.pending.take();
10292 return Poll::Ready(());
10293 }
10294 let repoll_waker = cx.waker().clone();
10299 let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10300 Ok(waker) => waker,
10301 Err(payload) => {
10302 let _ = catch_unwind_contained(|| drop(repoll_waker));
10303 resume_unwind(payload);
10304 }
10305 };
10306 host.repoll.register(repoll_waker);
10307 if !host.registry.register(
10308 host.id,
10309 deadline,
10310 registry_waker,
10311 &host.fired,
10312 &host.drivers,
10313 &host.repoll,
10314 ) {
10315 cancel_host_timer_drivers(host.id, &mut host.armed);
10316 if host.pending.take().is_none() {
10317 host.repoll.close();
10318 }
10319 return Poll::Ready(());
10320 }
10321 if host.pending.is_none() {
10322 host.drivers.register_pending(host.id, &host.repoll);
10330 host.pending = Some(HostTimerPendingGuard {
10331 registry: Arc::clone(&host.registry),
10332 drivers: Arc::clone(&host.drivers),
10333 repoll: Arc::clone(&host.repoll),
10334 id: host.id,
10335 });
10336 }
10337 let current = host.drivers.live();
10341 assert!(
10342 !current.as_slice().is_empty(),
10343 "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10344 that registered timer drivers (via \
10345 `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10346 one per importing env through `registerTimerHost`) have all been torn down, so this \
10347 `sleep_until` can never be woken."
10348 );
10349 let missing = current
10355 .as_slice()
10356 .iter()
10357 .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10358 .count();
10359 host.armed.reserve(missing);
10360 for (current_id, current_driver) in current.as_slice() {
10361 host
10362 .armed
10363 .entry(*current_id)
10364 .or_insert_with(|| Arc::clone(current_driver));
10365 }
10366 for (current_id, _) in current.as_slice() {
10367 let armed_driver = host
10368 .armed
10369 .get(current_id)
10370 .expect("every live timer driver must have cancellation ownership before callbacks");
10371 armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10372 }
10373 let current_ids = current
10374 .as_slice()
10375 .iter()
10376 .map(|(current_id, _)| *current_id)
10377 .collect::<FxHashSet<_>>();
10378 let retired = host
10379 .armed
10380 .keys()
10381 .filter(|armed_id| !current_ids.contains(armed_id))
10382 .copied()
10383 .collect::<Vec<_>>();
10384 for retired in retired {
10385 let old_driver = host
10386 .armed
10387 .remove(&retired)
10388 .expect("the retired timer driver must remain armed");
10389 cancel_host_timer_driver(host.id, old_driver);
10390 }
10391 Poll::Pending
10392 }
10393 }
10394 }
10395}
10396
10397impl Drop for Sleep {
10398 fn drop(&mut self) {
10399 match &mut self.inner {
10400 #[cfg(napi_runtime_os_threads)]
10401 SleepInner::Heap(heap) => {
10402 if heap.registered
10403 && let Some(executor) = heap.executor.upgrade()
10404 {
10405 executor.cancel_timer(heap.id);
10406 }
10407 }
10408 SleepInner::Host(host) => {
10409 host.repoll.close();
10412 cancel_host_timer_drivers(host.id, &mut host.armed);
10413 }
10415 }
10416 }
10417}
10418
10419pub fn sleep_until(deadline: Instant) -> Sleep {
10424 make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10425}
10426
10427fn make_sleep(
10431 backend: &RuntimeBackend,
10432 host_drivers: &Arc<TimerDriverRegistry>,
10433 deadline: Instant,
10434) -> Sleep {
10435 let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10436 match &backend.executor {
10437 #[cfg(napi_runtime_os_threads)]
10438 RuntimeExecutor::MultiThread(executor) => Sleep {
10439 deadline,
10440 inner: SleepInner::Heap(HeapSleep {
10441 id,
10442 executor: Arc::downgrade(executor),
10443 fired: Arc::new(AtomicBool::new(false)),
10444 registered: false,
10445 }),
10446 },
10447 RuntimeExecutor::CurrentThread(executor) => {
10448 assert!(
10456 host_drivers.has_live_driver(),
10457 "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10458 single-thread flavor delegates timers to the host event loop. Install one via \
10459 `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10460 a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10461 );
10462 Sleep {
10469 deadline,
10470 inner: SleepInner::Host(HostSleep {
10471 id,
10472 drivers: Arc::clone(host_drivers),
10473 armed: FxHashMap::default(),
10474 registry: Arc::clone(&executor.host_timers),
10475 fired: Arc::new(AtomicBool::new(false)),
10476 repoll: Arc::new(HostTimerRepoll::default()),
10477 pending: None,
10478 }),
10479 }
10480 }
10481 }
10482}
10483
10484#[derive(Clone)]
10485enum RuntimeExecutor {
10486 CurrentThread(Arc<CurrentThreadExecutor>),
10487 #[cfg(napi_runtime_os_threads)]
10488 MultiThread(Arc<MultiThreadExecutor>),
10489}
10490
10491enum WeakRuntimeExecutor {
10492 CurrentThread(Weak<CurrentThreadExecutor>),
10493 #[cfg(napi_runtime_os_threads)]
10494 MultiThread(Weak<MultiThreadExecutor>),
10495}
10496
10497impl WeakRuntimeExecutor {
10498 fn schedule(&self, runnable: Runnable) {
10499 match self {
10500 Self::CurrentThread(executor) => {
10501 if let Some(executor) = executor.upgrade() {
10502 executor.schedule(runnable);
10503 }
10504 }
10505 #[cfg(napi_runtime_os_threads)]
10506 Self::MultiThread(executor) => {
10507 if let Some(executor) = executor.upgrade() {
10508 executor.schedule(runnable);
10509 }
10510 }
10511 }
10512 }
10513}
10514
10515#[derive(Clone)]
10516struct RuntimeBackend {
10517 work: Arc<GenerationWork>,
10518 executor: RuntimeExecutor,
10519}
10520
10521impl RuntimeBackend {
10522 fn new(
10523 options: &RuntimeOptions,
10524 metrics: Arc<RuntimeMetrics>,
10525 ) -> Result<Self, RuntimeConfigError> {
10526 #[cfg(test)]
10527 if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10528 return Err(RuntimeConfigError(
10529 "injected async runtime backend creation failure".to_string(),
10530 ));
10531 }
10532
10533 let work = GenerationWork::new();
10534 let stop = Arc::new(GenerationStop::default());
10535 let executor = match options.flavor {
10536 RuntimeFlavor::CurrentThread => {
10537 let executor = CurrentThreadExecutor::with_generation(
10538 metrics,
10539 THREADLESS_BUILD,
10540 options.park_deadline,
10541 work.id,
10542 Arc::clone(&stop),
10543 );
10544 executor.attach_generation_work(&work);
10545 RuntimeExecutor::CurrentThread(Arc::new(executor))
10546 }
10547 RuntimeFlavor::MultiThread => {
10548 #[cfg(napi_runtime_os_threads)]
10549 {
10550 RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10551 options, metrics, work.id, stop,
10552 )?))
10553 }
10554 #[cfg(not(napi_runtime_os_threads))]
10555 {
10556 let _ = metrics;
10557 return Err(RuntimeConfigError(
10558 "the multi-thread runtime is unavailable in this threadless WebAssembly build \
10559 (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
10560 .to_string(),
10561 ));
10562 }
10563 }
10564 };
10565 Ok(Self { work, executor })
10566 }
10567
10568 #[cfg(test)]
10569 fn from_executor(executor: RuntimeExecutor) -> Self {
10570 let work = GenerationWork::new();
10571 if let RuntimeExecutor::CurrentThread(executor) = &executor {
10572 executor.attach_generation_work(&work);
10573 }
10574 Self { work, executor }
10575 }
10576
10577 fn generation(&self) -> u64 {
10578 self.work.id
10579 }
10580
10581 fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10582 match &self.executor {
10583 RuntimeExecutor::CurrentThread(executor) => {
10584 WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10585 }
10586 #[cfg(napi_runtime_os_threads)]
10587 RuntimeExecutor::MultiThread(executor) => {
10588 WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10589 }
10590 }
10591 }
10592
10593 fn schedule_submission(&self, runnable: Runnable) {
10597 match &self.executor {
10598 RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10599 #[cfg(napi_runtime_os_threads)]
10600 RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10601 }
10602 }
10603
10604 fn spawn_registered_blocking<F, T>(
10605 &self,
10606 function: F,
10607 registration: GenerationWorkGuard,
10608 metrics: &Arc<RuntimeMetrics>,
10609 ) -> JoinHandle<T>
10610 where
10611 F: FnOnce() -> T + Send + 'static,
10612 T: Send + 'static,
10613 {
10614 match &self.executor {
10615 RuntimeExecutor::CurrentThread(executor) => {
10616 let _ = metrics;
10617 executor.schedule_blocking(function, registration)
10618 }
10619 #[cfg(napi_runtime_os_threads)]
10620 RuntimeExecutor::MultiThread(executor) => {
10621 let generation = self.generation();
10622 let _ = metrics;
10623 let function = RegisteredBlockingFunction::new(function, registration, generation);
10624 executor.schedule_blocking_result(move || function.run())
10625 }
10626 }
10627 }
10628
10629 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10630 match &self.executor {
10631 RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10632 #[cfg(napi_runtime_os_threads)]
10633 RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10634 }
10635 }
10636
10637 fn generation_stop(&self) -> Arc<GenerationStop> {
10638 match &self.executor {
10639 RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10640 #[cfg(napi_runtime_os_threads)]
10641 RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10642 }
10643 }
10644
10645 fn begin_shutdown(&self) {
10646 let _generation = RuntimeGenerationGuard::enter(self.generation());
10647 self.work.close_and_abort();
10648 match &self.executor {
10649 RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10650 #[cfg(napi_runtime_os_threads)]
10651 RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10652 }
10653 }
10654
10655 fn wait_until_idle(&self) {
10656 match &self.executor {
10657 RuntimeExecutor::CurrentThread(executor) => {
10658 self.work.wait_until_idle_retiring(
10664 || executor.has_rejected_work(),
10665 || executor.retire_rejected_work(),
10666 );
10667 executor.wait_until_scheduler_idle();
10668 }
10669 #[cfg(napi_runtime_os_threads)]
10670 RuntimeExecutor::MultiThread(executor) => {
10671 self.work.wait_until_idle();
10672 executor.wait_until_scheduler_idle();
10673 }
10674 }
10675 }
10676
10677 fn work_pending(&self, read: PendingRead) -> bool {
10692 if self.work.has_active_work(read) {
10693 return true;
10694 }
10695 match &self.executor {
10696 RuntimeExecutor::CurrentThread(executor) => {
10697 executor.rejected_work_pending(read) || executor.scheduler_work_pending(read)
10698 }
10699 #[cfg(napi_runtime_os_threads)]
10700 RuntimeExecutor::MultiThread(executor) => executor.scheduler_work_pending(),
10701 }
10702 }
10703
10704 #[cfg(napi_runtime_os_threads)]
10705 fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10706 match &self.executor {
10707 RuntimeExecutor::CurrentThread(_) => None,
10708 RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10709 }
10710 }
10711
10712 fn stop_identity(&self) -> RuntimeStopIdentity {
10713 RuntimeStopIdentity {
10714 generation: self.generation(),
10715 #[cfg(napi_runtime_os_threads)]
10716 executor_id: match &self.executor {
10717 RuntimeExecutor::CurrentThread(_) => None,
10718 RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10719 },
10720 }
10721 }
10722
10723 fn is_current(&self) -> bool {
10724 self.stop_identity().is_current()
10725 }
10726}
10727
10728#[derive(Clone, Copy)]
10729struct RuntimeStopIdentity {
10730 generation: u64,
10731 #[cfg(napi_runtime_os_threads)]
10732 executor_id: Option<u64>,
10733}
10734
10735impl RuntimeStopIdentity {
10736 fn is_current(self) -> bool {
10737 if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10738 return true;
10739 }
10740 #[cfg(napi_runtime_os_threads)]
10741 if let Some(executor_id) = self.executor_id
10742 && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10743 || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10744 {
10745 return true;
10746 }
10747 false
10748 }
10749}
10750
10751enum RuntimeLifecycle {
10752 Initial,
10753 StoppedBeforeFirstUse,
10754 Running(RuntimeBackend),
10755 Stopping(RuntimeStopIdentity),
10756 StoppingWithoutBackend(ZeroBackendShutdownTarget),
10757 Stopped,
10758}
10759
10760#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10761enum ZeroBackendShutdownTarget {
10762 StoppedBeforeFirstUse,
10763 Stopped,
10764}
10765
10766impl ZeroBackendShutdownTarget {
10767 fn into_lifecycle(self) -> RuntimeLifecycle {
10768 match self {
10769 Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10770 Self::Stopped => RuntimeLifecycle::Stopped,
10771 }
10772 }
10773}
10774
10775struct RuntimeState {
10776 options: RuntimeOptions,
10777 lifecycle: RuntimeLifecycle,
10778 rejected_drops: usize,
10779 draining: Option<ShutdownDrain>,
10804 phase_ever_opened: bool,
10824}
10825
10826impl RuntimeState {
10827 fn open_phase_one(&mut self, drain: ShutdownDrain) {
10831 self.phase_ever_opened = true;
10832 self.draining = Some(drain);
10833 }
10834
10835 fn forget_shutdown_handoff(&mut self) {
10852 debug_assert!(
10860 !matches!(
10861 self.draining,
10862 Some(ShutdownDrain::Backend(_) | ShutdownDrain::Joining(_) | ShutdownDrain::ZeroBackend(_))
10863 ),
10864 "a start must never publish over an outstanding backend drain"
10865 );
10866 self.draining = None;
10867 }
10868}
10869
10870enum ShutdownDrain {
10873 Backend(RuntimeBackend),
10876 Joining(Option<RuntimeBackend>),
10896 Settled,
10899 ZeroBackend(ZeroBackendShutdownTarget),
10916}
10917
10918#[cfg(test)]
10919thread_local! {
10920 static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10921 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10922 static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10923 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10924 static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10925 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10926 static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10927 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10928 static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10929 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10930 static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10931 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10932 static AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK:
10933 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10934 static BETWEEN_SHUTDOWN_PHASES_TEST_HOOK:
10935 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10936 static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10937 std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10938}
10939
10940#[cfg(test)]
10941fn run_before_runtime_submission_lock_test_hook() {
10942 if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10943 {
10944 hook();
10945 }
10946}
10947
10948#[cfg(test)]
10949fn run_after_async_work_registration_test_hook() {
10950 if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10951 {
10952 hook();
10953 }
10954}
10955
10956#[cfg(test)]
10957fn run_after_blocking_work_registration_test_hook() {
10958 if let Some(hook) =
10959 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10960 {
10961 hook();
10962 }
10963}
10964
10965#[cfg(test)]
10966fn run_after_current_thread_host_admission_test_hook() {
10967 if let Some(hook) =
10968 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10969 {
10970 hook();
10971 }
10972}
10973
10974#[cfg(test)]
10975fn run_before_initial_start_wait_test_hook() {
10976 if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10977 hook();
10978 }
10979}
10980
10981#[cfg(test)]
10982fn run_after_generation_stop_publication_test_hook() {
10983 if let Some(hook) =
10984 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10985 {
10986 hook();
10987 }
10988}
10989
10990#[cfg(test)]
10994fn run_after_shutdown_drain_taken_test_hook() {
10995 if let Some(hook) = AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10996 hook();
10997 }
10998}
10999
11000#[cfg(test)]
11004fn run_between_shutdown_phases_test_hook() {
11005 if let Some(hook) = BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
11006 hook();
11007 }
11008}
11009
11010thread_local! {
11011 static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
11012 const { std::cell::Cell::new(0) };
11013}
11014
11015struct RejectedSubmissionDropContext;
11016
11017impl RejectedSubmissionDropContext {
11018 fn enter() -> Self {
11019 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
11020 Self
11021 }
11022
11023 fn is_current() -> bool {
11024 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
11025 }
11026}
11027
11028impl Drop for RejectedSubmissionDropContext {
11029 fn drop(&mut self) {
11030 IN_REJECTED_SUBMISSION_DROP.with(|depth| {
11031 depth.set(
11032 depth
11033 .get()
11034 .checked_sub(1)
11035 .expect("rejected submission drop depth underflow"),
11036 );
11037 });
11038 }
11039}
11040
11041struct RuntimeController {
11042 state: Mutex<RuntimeState>,
11043 lifecycle_changed: Condvar,
11044 metrics: Arc<RuntimeMetrics>,
11045 current_backend: ArcSwapOption<RuntimeBackend>,
11051 #[cfg_attr(not(any(napi_runtime_wasi_threads, test)), allow(dead_code))]
11062 pool_workers: AtomicU32,
11063}
11064
11065fn pool_workers_for(options: &RuntimeOptions) -> u32 {
11070 match options.flavor {
11071 RuntimeFlavor::MultiThread => u32::try_from(options.worker_threads).unwrap_or(u32::MAX),
11072 RuntimeFlavor::CurrentThread => 0,
11073 }
11074}
11075
11076struct RejectedSubmissionGuard<'a> {
11077 controller: &'a RuntimeController,
11078}
11079
11080impl Drop for RejectedSubmissionGuard<'_> {
11081 fn drop(&mut self) {
11082 let mut state = self
11083 .controller
11084 .state
11085 .lock()
11086 .unwrap_or_else(std::sync::PoisonError::into_inner);
11087 state.rejected_drops = state
11088 .rejected_drops
11089 .checked_sub(1)
11090 .expect("rejected submission drop count underflow");
11091 self.controller.lifecycle_changed.notify_all();
11092 }
11093}
11094
11095impl RuntimeController {
11096 fn new() -> Self {
11097 let options = RuntimeOptions::default()
11098 .validate()
11099 .expect("default async runtime options must be valid");
11100 let pool_workers = AtomicU32::new(pool_workers_for(&options));
11101 Self {
11102 state: Mutex::new(RuntimeState {
11103 options,
11104 lifecycle: RuntimeLifecycle::Initial,
11105 rejected_drops: 0,
11106 draining: None,
11107 phase_ever_opened: false,
11108 }),
11109 lifecycle_changed: Condvar::new(),
11110 metrics: Arc::new(RuntimeMetrics::default()),
11111 current_backend: ArcSwapOption::empty(),
11112 pool_workers,
11113 }
11114 }
11115
11116 fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
11117 let options = options.validate()?;
11118 let mut state = self
11119 .state
11120 .lock()
11121 .unwrap_or_else(std::sync::PoisonError::into_inner);
11122 Self::ensure_configuration_mutable(&state)?;
11123 self
11124 .pool_workers
11125 .store(pool_workers_for(&options), Ordering::Release);
11126 state.options = options;
11127 Ok(())
11128 }
11129
11130 fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
11131 self.configure_partial_inner(patch, |_| {})
11132 }
11133
11134 fn configure_partial_inner(
11135 &self,
11136 patch: RuntimeOptionsPatch,
11137 after_merge: impl FnOnce(&RuntimeOptions),
11138 ) -> Result<(), RuntimeConfigError> {
11139 let mut state = self
11140 .state
11141 .lock()
11142 .unwrap_or_else(std::sync::PoisonError::into_inner);
11143 Self::ensure_configuration_mutable(&state)?;
11144
11145 let mut options = state.options.clone();
11146 patch.apply_to(&mut options);
11147 after_merge(&options);
11148 let options = options.validate()?;
11149 self
11150 .pool_workers
11151 .store(pool_workers_for(&options), Ordering::Release);
11152 state.options = options;
11153 Ok(())
11154 }
11155
11156 #[cfg(any(napi_runtime_wasi_threads, test))]
11157 fn pool_workers(&self) -> u32 {
11158 self.pool_workers.load(Ordering::Acquire)
11159 }
11160
11161 fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11162 if matches!(
11163 &state.lifecycle,
11164 RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
11165 ) {
11166 return Ok(());
11167 }
11168 Err(RuntimeConfigError(
11169 "the async runtime configuration is frozen; configure it before the first async call"
11170 .to_string(),
11171 ))
11172 }
11173
11174 fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11175 let Some(active_generation) = active_runtime_generation() else {
11176 return Ok(());
11177 };
11178 let lifecycle_generation = match &state.lifecycle {
11179 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11180 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11181 RuntimeLifecycle::Initial
11182 | RuntimeLifecycle::StoppedBeforeFirstUse
11183 | RuntimeLifecycle::StoppingWithoutBackend(_)
11184 | RuntimeLifecycle::Stopped => None,
11185 };
11186 if lifecycle_generation == Some(active_generation) {
11187 return Ok(());
11188 }
11189 Err(RuntimeConfigError(
11190 "cannot access or control the async runtime from work owned by a retired generation"
11191 .to_string(),
11192 ))
11193 }
11194
11195 fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
11196 Self::ensure_active_generation_current(state)?;
11197 match &state.lifecycle {
11198 RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
11199 RuntimeLifecycle::StoppedBeforeFirstUse
11200 | RuntimeLifecycle::Stopping(_)
11201 | RuntimeLifecycle::StoppingWithoutBackend(_)
11202 | RuntimeLifecycle::Stopped => {
11203 return Err(RuntimeConfigError(
11204 "the async runtime is stopped; call start before submitting work".to_string(),
11205 ));
11206 }
11207 RuntimeLifecycle::Initial => {}
11208 }
11209 if state.rejected_drops != 0 {
11210 return Err(RuntimeConfigError(
11211 "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
11212 ));
11213 }
11214 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11215 self.pool_workers.store(0, Ordering::Release);
11217 state.lifecycle = RuntimeLifecycle::Running(backend.clone());
11218 state.forget_shutdown_handoff();
11219 self.current_backend.store(Some(Arc::new(backend.clone())));
11220 Ok(backend)
11221 }
11222
11223 fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
11224 let mut state = self
11225 .state
11226 .lock()
11227 .unwrap_or_else(std::sync::PoisonError::into_inner);
11228 self.backend_locked(&mut state)
11229 }
11230
11231 fn backend(&self) -> RuntimeBackend {
11232 self.try_backend().unwrap_or_else(|error| panic!("{error}"))
11233 }
11234
11235 fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
11236 let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
11237 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11238 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11239 RuntimeLifecycle::Initial
11240 | RuntimeLifecycle::StoppedBeforeFirstUse
11241 | RuntimeLifecycle::StoppingWithoutBackend(_)
11242 | RuntimeLifecycle::Stopped => None,
11243 });
11244 state.rejected_drops += 1;
11245 generation
11246 }
11247
11248 #[inline]
11256 fn fast_register<R>(
11257 &self,
11258 register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
11259 ) -> Option<(Arc<RuntimeBackend>, R)> {
11260 let guard = self.current_backend.load();
11261 let backend = guard.as_ref()?; if let Some(active) = active_runtime_generation()
11263 && active != backend.generation()
11264 {
11265 return None; }
11267 let registration = register(&backend.work)?; Some((Arc::clone(backend), registration))
11271 }
11272
11273 fn try_spawn_tracked<F, T>(
11274 &self,
11275 future: F,
11276 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11277 where
11278 F: Future<Output = T> + Send + 'static,
11279 T: Send + 'static,
11280 {
11281 #[cfg(test)]
11282 run_before_runtime_submission_lock_test_hook();
11283
11284 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
11285 #[cfg(test)]
11286 run_after_async_work_registration_test_hook();
11287 return Ok(spawn_registered(
11288 &backend,
11289 Arc::clone(&self.metrics),
11290 future,
11291 registration,
11292 ));
11293 }
11294
11295 let (backend, registration) = {
11296 let mut state = self
11297 .state
11298 .lock()
11299 .unwrap_or_else(std::sync::PoisonError::into_inner);
11300 let backend = match self.backend_locked(&mut state) {
11301 Ok(backend) => backend,
11302 Err(error) => {
11303 let generation = Self::begin_rejected_drop(&mut state);
11304 return Err((error, future, generation));
11305 }
11306 };
11307 let Some(registration) = backend.work.try_register_async() else {
11308 let generation = Self::begin_rejected_drop(&mut state);
11309 return Err((
11310 RuntimeConfigError(
11311 "the async runtime is stopped; call start before submitting work".to_string(),
11312 ),
11313 future,
11314 generation,
11315 ));
11316 };
11317 (backend, registration)
11318 };
11319 #[cfg(test)]
11320 run_after_async_work_registration_test_hook();
11321 Ok(spawn_registered(
11322 &backend,
11323 Arc::clone(&self.metrics),
11324 future,
11325 registration,
11326 ))
11327 }
11328
11329 fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11330 where
11331 F: Future<Output = T> + Send + 'static,
11332 T: Send + 'static,
11333 {
11334 match self.try_spawn_tracked(future) {
11335 Ok(handle) => Ok(handle),
11336 Err((error, future, _generation)) => {
11337 drop(RejectedSubmissionGuard { controller: self });
11338 Err((error, future))
11339 }
11340 }
11341 }
11342
11343 fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
11344 where
11345 F: Future<Output = T> + Send + 'static,
11346 T: Send + 'static,
11347 {
11348 match self.try_spawn_tracked(future) {
11349 Ok(handle) => handle,
11350 Err((error, future, generation)) => {
11351 let _rejection = RejectedSubmissionGuard { controller: self };
11352 let _drop_context = RejectedSubmissionDropContext::enter();
11353 let _generation = generation.map(RuntimeGenerationGuard::enter);
11354 let _ = catch_unwind_contained(|| drop(future));
11355 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11356 message: error.to_string(),
11357 }))))
11358 }
11359 }
11360 }
11361
11362 fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
11363 where
11364 F: Future<Output = ()> + Send + 'static,
11365 {
11366 match self.try_spawn(future) {
11367 Ok(handle) => {
11368 handle.detach();
11369 Ok(())
11370 }
11371 Err((_, future)) => Err(future),
11372 }
11373 }
11374
11375 fn try_spawn_blocking_tracked<F, T>(
11376 &self,
11377 function: F,
11378 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11379 where
11380 F: FnOnce() -> T + Send + 'static,
11381 T: Send + 'static,
11382 {
11383 #[cfg(test)]
11384 run_before_runtime_submission_lock_test_hook();
11385
11386 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
11387 #[cfg(test)]
11388 run_after_blocking_work_registration_test_hook();
11389 return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
11390 }
11391
11392 let (backend, registration) = {
11393 let mut state = self
11394 .state
11395 .lock()
11396 .unwrap_or_else(std::sync::PoisonError::into_inner);
11397 let backend = match self.backend_locked(&mut state) {
11398 Ok(backend) => backend,
11399 Err(error) => {
11400 let generation = Self::begin_rejected_drop(&mut state);
11401 return Err((error, function, generation));
11402 }
11403 };
11404 let Some(registration) = backend.work.try_register_work() else {
11405 let generation = Self::begin_rejected_drop(&mut state);
11406 return Err((
11407 RuntimeConfigError(
11408 "the async runtime is stopped; call start before submitting work".to_string(),
11409 ),
11410 function,
11411 generation,
11412 ));
11413 };
11414 (backend, registration)
11415 };
11416 #[cfg(test)]
11417 run_after_blocking_work_registration_test_hook();
11418 Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11419 }
11420
11421 fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11422 where
11423 F: FnOnce() -> T + Send + 'static,
11424 T: Send + 'static,
11425 {
11426 match self.try_spawn_blocking_tracked(function) {
11427 Ok(handle) => Ok(handle),
11428 Err((error, function, _generation)) => {
11429 drop(RejectedSubmissionGuard { controller: self });
11430 Err((error, function))
11431 }
11432 }
11433 }
11434
11435 fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11436 where
11437 F: FnOnce() -> T + Send + 'static,
11438 T: Send + 'static,
11439 {
11440 match self.try_spawn_blocking_tracked(function) {
11441 Ok(handle) => handle,
11442 Err((error, function, generation)) => {
11443 let _rejection = RejectedSubmissionGuard { controller: self };
11444 let _drop_context = RejectedSubmissionDropContext::enter();
11445 let _generation = generation.map(RuntimeGenerationGuard::enter);
11446 let _ = catch_unwind_contained(|| drop(function));
11447 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11448 message: error.to_string(),
11449 }))))
11450 }
11451 }
11452 }
11453
11454 #[cfg(test)]
11455 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11456 self
11457 .try_block_on(future)
11458 .unwrap_or_else(|error| panic!("{error}"))
11459 }
11460
11461 fn try_block_on_scope(
11462 &self,
11463 ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11464 let mut state = self
11465 .state
11466 .lock()
11467 .unwrap_or_else(std::sync::PoisonError::into_inner);
11468 let backend = match self.backend_locked(&mut state) {
11469 Ok(backend) => backend,
11470 Err(error) => {
11471 let generation = Self::begin_rejected_drop(&mut state);
11472 return Err((error, generation));
11473 }
11474 };
11475 let Some(registration) = backend.work.try_register_work() else {
11476 let generation = Self::begin_rejected_drop(&mut state);
11477 return Err((
11478 RuntimeConfigError(
11479 "the async runtime is stopped; call start before submitting work".to_string(),
11480 ),
11481 generation,
11482 ));
11483 };
11484 Ok((backend, registration))
11485 }
11486
11487 fn try_block_on(
11488 &self,
11489 future: Pin<&mut dyn Future<Output = ()>>,
11490 ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11491 let (backend, registration) = match self.try_block_on_scope() {
11492 Ok(scope) => scope,
11493 Err((error, _generation)) => {
11494 drop(RejectedSubmissionGuard { controller: self });
11495 return Err(error);
11496 }
11497 };
11498 let _registration = registration;
11499 let _generation = RuntimeGenerationGuard::enter(backend.generation());
11500 Ok(backend.block_on(future))
11501 }
11502
11503 fn start(&self) -> Result<(), RuntimeConfigError> {
11504 let mut state = self
11505 .state
11506 .lock()
11507 .unwrap_or_else(std::sync::PoisonError::into_inner);
11508 loop {
11509 Self::ensure_active_generation_current(&state)?;
11510 match &state.lifecycle {
11511 RuntimeLifecycle::Initial => {
11515 if state.rejected_drops == 0 {
11516 state.forget_shutdown_handoff();
11521 return Ok(());
11522 }
11523 if RejectedSubmissionDropContext::is_current() {
11524 return Err(RuntimeConfigError(
11525 "cannot start the async runtime while a rejected submission is being destroyed"
11526 .to_string(),
11527 ));
11528 }
11529 #[cfg(test)]
11530 run_before_initial_start_wait_test_hook();
11531 state = self
11532 .lifecycle_changed
11533 .wait(state)
11534 .unwrap_or_else(std::sync::PoisonError::into_inner);
11535 }
11536 RuntimeLifecycle::Running(_) => {
11537 state.forget_shutdown_handoff();
11538 return Ok(());
11539 }
11540 RuntimeLifecycle::Stopping(identity) => {
11541 if identity.is_current() {
11542 return Err(RuntimeConfigError(
11543 "cannot restart the async runtime from work in the generation being stopped"
11544 .to_string(),
11545 ));
11546 }
11547 state = self
11548 .lifecycle_changed
11549 .wait(state)
11550 .unwrap_or_else(std::sync::PoisonError::into_inner);
11551 }
11552 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11553 if RejectedSubmissionDropContext::is_current() {
11554 return Err(RuntimeConfigError(
11555 "cannot restart the async runtime while a rejected submission is being destroyed"
11556 .to_string(),
11557 ));
11558 }
11559 state = self
11560 .lifecycle_changed
11561 .wait(state)
11562 .unwrap_or_else(std::sync::PoisonError::into_inner);
11563 }
11564 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11565 if state.rejected_drops == 0 {
11566 if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11567 state.lifecycle = RuntimeLifecycle::Initial;
11568 state.forget_shutdown_handoff();
11569 return Ok(());
11570 }
11571 break;
11572 }
11573 if RejectedSubmissionDropContext::is_current() {
11574 return Err(RuntimeConfigError(
11575 "cannot restart the async runtime while a rejected submission is being destroyed"
11576 .to_string(),
11577 ));
11578 }
11579 state = self
11580 .lifecycle_changed
11581 .wait(state)
11582 .unwrap_or_else(std::sync::PoisonError::into_inner);
11583 }
11584 }
11585 }
11586 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11587 self.pool_workers.store(0, Ordering::Release);
11589 self.current_backend.store(Some(Arc::new(backend.clone())));
11590 state.lifecycle = RuntimeLifecycle::Running(backend);
11591 state.forget_shutdown_handoff();
11592 Ok(())
11593 }
11594
11595 fn options(&self) -> RuntimeOptions {
11596 self
11597 .state
11598 .lock()
11599 .unwrap_or_else(std::sync::PoisonError::into_inner)
11600 .options
11601 .clone()
11602 }
11603
11604 fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11605 let state = self
11606 .state
11607 .lock()
11608 .unwrap_or_else(std::sync::PoisonError::into_inner);
11609 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11610 return None;
11611 };
11612 match &backend.executor {
11613 RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11614 #[cfg(napi_runtime_os_threads)]
11615 RuntimeExecutor::MultiThread(_) => None,
11616 }
11617 }
11618
11619 fn request_current_thread_drain(&self) {
11620 if let Some(executor) = self.running_current_thread_executor() {
11621 executor.request_drain_if_queued();
11622 }
11623 }
11624
11625 fn retire_rejected_current_thread_work(&self) {
11626 if let Some(executor) = self.running_current_thread_executor() {
11627 executor.retire_rejected_work_off_waker_stack();
11628 }
11629 }
11630
11631 fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11632 let executor = {
11633 let state = self
11634 .state
11635 .lock()
11636 .unwrap_or_else(std::sync::PoisonError::into_inner);
11637 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11638 return;
11639 };
11640 match &backend.executor {
11641 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11642 #[cfg(napi_runtime_os_threads)]
11643 RuntimeExecutor::MultiThread(_) => return,
11644 }
11645 };
11646 executor.cancel_host_dispatch(failure);
11647 }
11648
11649 #[cfg(test)]
11650 fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11651 let executor = {
11652 let state = self
11653 .state
11654 .lock()
11655 .unwrap_or_else(std::sync::PoisonError::into_inner);
11656 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11657 return;
11658 };
11659 match &backend.executor {
11660 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11661 #[cfg(napi_runtime_os_threads)]
11662 RuntimeExecutor::MultiThread(_) => return,
11663 }
11664 };
11665 executor.cancel_host_dispatch_for_test(dispatch);
11666 }
11667
11668 fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11669 let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11670 self.drive_current_thread_dispatch_with_lease(dispatch, true)
11671 }
11672
11673 fn drive_current_thread_dispatch_with_lease(
11674 &self,
11675 dispatch: u64,
11676 mark_serviced: bool,
11677 ) -> Option<CurrentThreadTaskCallbackLease> {
11678 let mut host_turn = {
11679 let state = self
11680 .state
11681 .lock()
11682 .unwrap_or_else(std::sync::PoisonError::into_inner);
11683 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11684 return None;
11685 };
11686 let executor = match &backend.executor {
11687 RuntimeExecutor::CurrentThread(executor) => executor,
11688 #[cfg(napi_runtime_os_threads)]
11689 RuntimeExecutor::MultiThread(_) => return None,
11690 };
11691 let host_turn = executor.try_admit_host_turn(dispatch)?;
11692 if mark_serviced {
11693 CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11694 }
11695 host_turn
11696 };
11697 #[cfg(test)]
11698 run_after_current_thread_host_admission_test_hook();
11699 host_turn.drive();
11700 Some(CurrentThreadTaskCallbackLease::new(host_turn))
11701 }
11702
11703 #[cfg(test)]
11704 fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11705 self
11706 .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11707 .is_some()
11708 }
11709
11710 fn begin_shutdown(&self) -> Result<bool, RuntimeConfigError> {
11749 let _bounded_waits = BoundedWaits::enter();
11751 let backend = {
11752 let mut state = self
11753 .state
11754 .lock()
11755 .unwrap_or_else(std::sync::PoisonError::into_inner);
11756 Self::ensure_active_generation_current(&state)?;
11762 if let Some(drain) = &state.draining {
11763 let outstanding = match drain {
11764 ShutdownDrain::Backend(backend) => Some(backend.clone()),
11768 ShutdownDrain::Joining(backend) => backend.clone(),
11769 ShutdownDrain::ZeroBackend(_) => {
11777 if RejectedSubmissionDropContext::is_current() {
11778 return Err(RuntimeConfigError(
11779 "cannot shut down the async runtime while a rejected submission is being destroyed"
11780 .to_string(),
11781 ));
11782 }
11783 return Ok(state.rejected_drops != 0);
11784 }
11785 ShutdownDrain::Settled => None,
11786 };
11787 drop(state);
11797 return Ok(outstanding.is_some_and(|backend| backend.work_pending(PendingRead::Locked)));
11798 }
11799 loop {
11800 Self::ensure_active_generation_current(&state)?;
11801 match &state.lifecycle {
11802 RuntimeLifecycle::Initial => {
11803 if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11804 return Err(RuntimeConfigError(
11805 "cannot shut down the async runtime while a rejected submission is being destroyed"
11806 .to_string(),
11807 ));
11808 }
11809 let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11810 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11811 if state.rejected_drops != 0 {
11812 state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11819 self.lifecycle_changed.notify_all();
11820 return Ok(true);
11821 }
11822 state.lifecycle = target.into_lifecycle();
11823 state.open_phase_one(ShutdownDrain::Settled);
11824 self.lifecycle_changed.notify_all();
11825 return Ok(false);
11826 }
11827 RuntimeLifecycle::Running(backend) => {
11828 if backend.is_current() {
11829 return Err(RuntimeConfigError(
11830 "cannot shut down the async runtime from work running on that runtime".to_string(),
11831 ));
11832 }
11833 let identity = backend.stop_identity();
11834 let stop = backend.generation_stop();
11835 let stop_publication = stop.publication_guard();
11840 stop_publication.publish_stopping();
11841 let RuntimeLifecycle::Running(backend) =
11842 std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11843 else {
11844 unreachable!();
11845 };
11846 self.current_backend.store(None);
11847 state.open_phase_one(ShutdownDrain::Backend(backend.clone()));
11851 drop(stop_publication);
11855 break backend;
11856 }
11857 RuntimeLifecycle::Stopping(identity) => {
11858 if identity.is_current() {
11859 return Err(RuntimeConfigError(
11860 "cannot wait for async runtime shutdown from work in the generation being stopped"
11861 .to_string(),
11862 ));
11863 }
11864 state = self
11865 .lifecycle_changed
11866 .wait(state)
11867 .unwrap_or_else(std::sync::PoisonError::into_inner);
11868 }
11869 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11870 if RejectedSubmissionDropContext::is_current() {
11871 return Err(RuntimeConfigError(
11872 "cannot shut down the async runtime while a rejected submission is being destroyed"
11873 .to_string(),
11874 ));
11875 }
11876 state = self
11877 .lifecycle_changed
11878 .wait(state)
11879 .unwrap_or_else(std::sync::PoisonError::into_inner);
11880 }
11881 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11882 if state.rejected_drops == 0 {
11883 state.open_phase_one(ShutdownDrain::Settled);
11884 return Ok(false);
11885 }
11886 if RejectedSubmissionDropContext::is_current() {
11887 return Err(RuntimeConfigError(
11888 "cannot shut down the async runtime while a rejected submission is being destroyed"
11889 .to_string(),
11890 ));
11891 }
11892 let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11893 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11894 } else {
11895 ZeroBackendShutdownTarget::Stopped
11896 };
11897 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11901 state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11902 self.lifecycle_changed.notify_all();
11903 return Ok(true);
11904 }
11905 }
11906 }
11907 };
11908
11909 #[cfg(test)]
11910 run_after_generation_stop_publication_test_hook();
11911 backend.begin_shutdown();
11912 Ok(backend.work_pending(PendingRead::Locked))
11913 }
11914
11915 fn runtime_work_pending(&self) -> bool {
11932 let backend = {
11933 let state = match self.state.try_lock() {
11934 Ok(state) => state,
11935 Err(std::sync::TryLockError::Poisoned(poisoned)) => poisoned.into_inner(),
11936 Err(std::sync::TryLockError::WouldBlock) => return true,
11937 };
11938 match (&state.draining, &state.lifecycle) {
11939 (Some(ShutdownDrain::Backend(backend) | ShutdownDrain::Joining(Some(backend))), _) => {
11942 backend.clone()
11943 }
11944 (Some(ShutdownDrain::ZeroBackend(_)), _) => return state.rejected_drops != 0,
11950 (None, RuntimeLifecycle::Running(backend)) => backend.clone(),
11951 (Some(ShutdownDrain::Joining(None) | ShutdownDrain::Settled), _) | (None, _) => {
11956 return false;
11957 }
11958 }
11959 };
11960 backend.work_pending(PendingRead::NonBlocking)
11961 }
11962
11963 fn finish_shutdown(&self) -> Result<(), RuntimeConfigError> {
12003 let _bounded_waits = BoundedWaits::enter();
12004 const STOPPING_WAIT_ERROR: &str =
12005 "cannot wait for async runtime shutdown from work in the generation being stopped";
12006 const REJECTED_DROP_ERROR: &str =
12007 "cannot shut down the async runtime while a rejected submission is being destroyed";
12008 const NO_PHASE_ERROR: &str =
12009 "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown";
12010
12011 let backend = {
12012 let mut state = self
12013 .state
12014 .lock()
12015 .unwrap_or_else(std::sync::PoisonError::into_inner);
12016 match &state.draining {
12019 Some(ShutdownDrain::Settled) => {
12020 state.draining = None;
12022 return Ok(());
12023 }
12024 Some(ShutdownDrain::Backend(backend)) => {
12025 if backend.is_current() {
12026 return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
12027 }
12028 }
12029 Some(ShutdownDrain::ZeroBackend(_)) => loop {
12037 let target = match (&state.draining, &state.lifecycle) {
12038 (
12039 Some(ShutdownDrain::ZeroBackend(parked)),
12040 RuntimeLifecycle::StoppingWithoutBackend(published),
12041 ) if published == parked => *parked,
12042 _ => return Ok(()),
12046 };
12047 if state.rejected_drops == 0 {
12048 state.draining = None;
12053 state.lifecycle = target.into_lifecycle();
12054 self.lifecycle_changed.notify_all();
12055 return Ok(());
12056 }
12057 if RejectedSubmissionDropContext::is_current() {
12058 return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
12059 }
12060 state = self
12065 .lifecycle_changed
12066 .wait(state)
12067 .unwrap_or_else(std::sync::PoisonError::into_inner);
12068 },
12069 Some(ShutdownDrain::Joining(_)) | None => {
12074 if !state.phase_ever_opened {
12075 return Err(RuntimeConfigError(NO_PHASE_ERROR.to_string()));
12079 }
12080 loop {
12088 match &state.lifecycle {
12089 RuntimeLifecycle::Stopping(identity) => {
12090 if identity.is_current() {
12091 return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
12092 }
12093 }
12094 RuntimeLifecycle::StoppingWithoutBackend(_) => {
12095 if RejectedSubmissionDropContext::is_current() {
12096 return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
12097 }
12098 }
12099 RuntimeLifecycle::StoppedBeforeFirstUse
12104 | RuntimeLifecycle::Stopped
12105 | RuntimeLifecycle::Initial
12106 | RuntimeLifecycle::Running(_) => return Ok(()),
12107 }
12108 state = self
12109 .lifecycle_changed
12110 .wait(state)
12111 .unwrap_or_else(std::sync::PoisonError::into_inner);
12112 }
12113 }
12114 }
12115 let Some(ShutdownDrain::Backend(backend)) = state.draining.take() else {
12123 unreachable!();
12124 };
12125 state.draining = Some(ShutdownDrain::Joining(Some(backend.clone())));
12126 backend
12127 };
12128
12129 #[cfg(test)]
12130 run_after_shutdown_drain_taken_test_hook();
12131 backend.wait_until_idle();
12132
12133 #[cfg(napi_runtime_os_threads)]
12134 let worker_lifecycle = backend.worker_lifecycle();
12135 {
12143 let mut state = self
12144 .state
12145 .lock()
12146 .unwrap_or_else(std::sync::PoisonError::into_inner);
12147 debug_assert!(
12148 matches!(state.draining, Some(ShutdownDrain::Joining(Some(_)))),
12149 "nothing may disturb an in-flight drain marker before its publication"
12150 );
12151 state.draining = Some(ShutdownDrain::Joining(None));
12152 }
12153 drop(backend);
12154
12155 #[cfg(napi_runtime_os_threads)]
12156 if let Some(worker_lifecycle) = worker_lifecycle {
12157 worker_lifecycle.wait_for_all_workers();
12158 }
12159
12160 let mut state = self
12161 .state
12162 .lock()
12163 .unwrap_or_else(std::sync::PoisonError::into_inner);
12164 while state.rejected_drops != 0 {
12165 state = self
12166 .lifecycle_changed
12167 .wait(state)
12168 .unwrap_or_else(std::sync::PoisonError::into_inner);
12169 }
12170 debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
12171 debug_assert!(
12172 matches!(state.draining, Some(ShutdownDrain::Joining(None))),
12173 "nothing may retire an in-flight drain marker but its own publication"
12174 );
12175 state.draining = None;
12179 state.lifecycle = RuntimeLifecycle::Stopped;
12180 self.lifecycle_changed.notify_all();
12181 Ok(())
12182 }
12183
12184 fn shutdown(&self) -> Result<(), RuntimeConfigError> {
12197 self.begin_shutdown()?;
12198 #[cfg(test)]
12199 run_between_shutdown_phases_test_hook();
12200 self.finish_shutdown()
12201 }
12202}
12203
12204static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
12205
12206pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
12207 RUNTIME.configure(options)
12208}
12209
12210pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
12211 RUNTIME.configure_partial(patch)
12212}
12213
12214pub fn configured_options() -> RuntimeOptions {
12215 RUNTIME.options()
12216}
12217
12218#[cfg(napi_runtime_wasi_threads)]
12234#[unsafe(no_mangle)]
12235pub extern "C" fn napi_wasm_runtime_pool_workers() -> u32 {
12236 RUNTIME.pool_workers()
12237}
12238
12239pub fn is_multi_threaded() -> bool {
12240 RUNTIME.options().flavor == RuntimeFlavor::MultiThread
12241}
12242
12243fn spawn_registered<F, T>(
12244 backend: &RuntimeBackend,
12245 metrics: Arc<RuntimeMetrics>,
12246 future: F,
12247 registration: (AbortRegistration, GenerationWorkGuard),
12248) -> JoinHandle<T>
12249where
12250 F: Future<Output = T> + Send + 'static,
12251 T: Send + 'static,
12252{
12253 metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
12254 let generation = backend.generation();
12255 let (abort_registration, work_registration) = registration;
12256 #[cfg(napi_runtime_os_threads)]
12257 let dependency = Arc::new(TaskDependency::new_task(generation));
12258 #[cfg(napi_runtime_os_threads)]
12259 let task_dependency = Arc::clone(&dependency);
12260 let wrapped = async move {
12261 let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
12262 let mut abortable = std::pin::pin!(abortable);
12263 let polled = futures::future::poll_fn(|cx| {
12264 #[cfg(napi_runtime_os_threads)]
12265 task_dependency.begin_poll();
12266 #[cfg(napi_runtime_os_threads)]
12267 let _dependency = TaskDependencyGuard::enter(&task_dependency);
12268 abortable.as_mut().poll(cx)
12269 });
12270 match AssertUnwindSafe(polled).catch_unwind().await {
12271 Ok(Ok(output)) => {
12272 metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
12273 Ok(ContainedTaskOutput::new(output, generation))
12274 }
12275 Ok(Err(_)) => Err(JoinError {
12276 message: "async runtime stopped before the task completed".to_string(),
12277 }),
12278 Err(panic) => {
12279 metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
12280 Err(panic_payload_to_join_error(panic))
12281 }
12282 }
12283 };
12284 let scheduler = backend.downgrade_executor();
12285 let (runnable, task) = async_task::spawn(
12289 RegisteredTaskFuture::new(wrapped, work_registration, generation),
12290 move |runnable| {
12291 scheduler.schedule(runnable);
12292 },
12293 );
12294 backend.schedule_submission(runnable);
12295 JoinHandle(JoinHandleInner::Task {
12296 task: task.fallible(),
12297 awaiter: None,
12298 #[cfg(napi_runtime_os_threads)]
12299 dependency,
12300 })
12301}
12302
12303pub fn spawn<F, T>(future: F) -> JoinHandle<T>
12304where
12305 F: Future<Output = T> + Send + 'static,
12306 T: Send + 'static,
12307{
12308 RUNTIME.spawn(future)
12309}
12310
12311pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12323where
12324 F: Future<Output = T> + Send + 'static,
12325 T: Send + 'static,
12326{
12327 RUNTIME.try_spawn(future)
12328}
12329
12330pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
12331where
12332 F: Future<Output = ()> + Send + 'static,
12333{
12334 RUNTIME.try_spawn_detached(future)
12335}
12336
12337pub fn spawn_detached<F>(future: F)
12338where
12339 F: Future<Output = ()> + Send + 'static,
12340{
12341 spawn(future).detach();
12342}
12343
12344pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
12345where
12346 F: FnOnce() -> T + Send + 'static,
12347 T: Send + 'static,
12348{
12349 RUNTIME.spawn_blocking(function)
12350}
12351
12352pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12360where
12361 F: FnOnce() -> T + Send + 'static,
12362 T: Send + 'static,
12363{
12364 RUNTIME.try_spawn_blocking(function)
12365}
12366
12367fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
12368 let (backend, registration) = match controller.try_block_on_scope() {
12369 Ok(scope) => scope,
12370 Err((error, generation)) => {
12371 let _rejection = RejectedSubmissionGuard { controller };
12372 let _drop_context = RejectedSubmissionDropContext::enter();
12373 let _generation = generation.map(RuntimeGenerationGuard::enter);
12374 let _ = catch_unwind_contained(|| drop(future));
12375 panic!("{error}");
12376 }
12377 };
12378 let generation = backend.generation();
12379 let _registration = registration;
12380 let _generation = RuntimeGenerationGuard::enter(generation);
12381 let mut output = None;
12382 {
12383 let future = ContainedFuture::new(future, generation);
12384 let mut erased = std::pin::pin!(async {
12385 output = Some(future.await);
12386 });
12387 let _ = backend.block_on(erased.as_mut());
12388 }
12393 output.expect("async runtime returned before the future completed")
12394}
12395
12396pub fn block_on<F: Future>(future: F) -> F::Output {
12397 block_on_with_controller(&RUNTIME, future)
12398}
12399
12400pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
12401 try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
12402}
12403
12404pub fn try_block_on_dyn(
12414 future: Pin<&mut dyn Future<Output = ()>>,
12415) -> Result<(), RuntimeConfigError> {
12416 match RUNTIME.try_block_on(future)? {
12417 BlockOnOutcome::Completed => Ok(()),
12418 BlockOnOutcome::Stopped => Err(RuntimeConfigError(
12419 "the async runtime stopped before block_on completed its future".to_string(),
12420 )),
12421 }
12422}
12423
12424pub fn start() -> Result<(), RuntimeConfigError> {
12425 RUNTIME.start()
12426}
12427
12428pub fn shutdown() -> Result<(), RuntimeConfigError> {
12436 RUNTIME.shutdown()
12437}
12438
12439pub fn begin_shutdown() -> Result<bool, RuntimeConfigError> {
12462 RUNTIME.begin_shutdown()
12463}
12464
12465pub fn runtime_work_pending() -> bool {
12476 RUNTIME.runtime_work_pending()
12477}
12478
12479pub fn finish_shutdown() -> Result<(), RuntimeConfigError> {
12492 RUNTIME.finish_shutdown()
12493}
12494
12495fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
12496 for failure in completion.failed_dispatches {
12497 RUNTIME.cancel_current_thread_dispatch(failure);
12498 }
12499 if completion.redispatch {
12500 RUNTIME.request_current_thread_drain();
12501 }
12502 RUNTIME.retire_rejected_current_thread_work();
12508}
12509
12510pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12519 complete_current_thread_task_delivery(
12520 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
12521 );
12522}
12523
12524pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12530 complete_current_thread_task_delivery(
12531 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
12532 );
12533}
12534
12535pub fn cancel_current_thread_task_dispatch(delivery: u64) {
12542 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
12543}
12544
12545pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
12554 RUNTIME.drive_current_thread_tasks(delivery)
12555}
12556
12557pub fn reset_metrics() {
12558 RUNTIME.metrics.reset();
12559}
12560
12561pub fn metrics() -> RuntimeMetricsSnapshot {
12562 let options = RUNTIME.options();
12563 RUNTIME.metrics.snapshot(&options)
12564}
12565
12566#[cfg(test)]
12567#[cfg_attr(target_family = "wasm", allow(dead_code))]
12572mod tests {
12573 use super::*;
12574
12575 static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12599 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12600 LazyLock::new(Mutex::default);
12601 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12602 static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12603 static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12604 static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12605 LazyLock::new(Mutex::default);
12606 static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12607 static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12608 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12609 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12610 LazyLock::new(Mutex::default);
12611 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
12612 static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
12613 CurrentThreadTaskDriverRegistry,
12614 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12615 static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12616 static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12617 static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12618 LazyLock::new(Mutex::default);
12619 static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12620 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12621 static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12622 LazyLock::new(Mutex::default);
12623 static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12624 static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12625 LazyLock::new(Mutex::default);
12626 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12627 LazyLock::new(Mutex::default);
12628 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
12629 CurrentThreadTaskDriverRegistry,
12630 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12631 static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12632 LazyLock::new(Mutex::default);
12633 static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12634 static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12635 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12636 static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12637 CurrentThreadTaskDriverRegistry,
12638 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12639 static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12640 CurrentThreadTaskDriverRegistry,
12641 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12642 static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12643 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12644 static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12645 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12646 static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12647 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12648 static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12649 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12650 static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12651 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12652 static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
12653 CurrentThreadTaskDriverRegistry,
12654 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12655 static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12656 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12657 static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12658
12659 thread_local! {
12660 static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
12661 const { std::cell::Cell::new(0) };
12662 static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
12663 const { std::cell::Cell::new(0) };
12664 }
12665
12666 #[cfg(napi_runtime_os_threads)]
12667 struct StopPollProbe {
12668 polls: Arc<AtomicUsize>,
12669 first_poll: Option<std::sync::mpsc::Sender<()>>,
12670 }
12671
12672 #[cfg(napi_runtime_os_threads)]
12673 impl Future for StopPollProbe {
12674 type Output = ();
12675
12676 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12677 self.polls.fetch_add(1, Ordering::SeqCst);
12678 if let Some(first_poll) = self.first_poll.take() {
12679 first_poll.send(()).unwrap();
12680 }
12681 Poll::Pending
12682 }
12683 }
12684
12685 #[cfg(napi_runtime_os_threads)]
12686 struct PanickingJoinAwaiter {
12687 wakes: Arc<AtomicUsize>,
12688 drops: Arc<AtomicUsize>,
12689 }
12690
12691 #[cfg(napi_runtime_os_threads)]
12692 impl std::task::Wake for PanickingJoinAwaiter {
12693 fn wake(self: Arc<Self>) {
12694 self.wakes.fetch_add(1, Ordering::SeqCst);
12695 panic!("intentional JoinHandle awaiter wake panic");
12696 }
12697
12698 fn wake_by_ref(self: &Arc<Self>) {
12699 self.wakes.fetch_add(1, Ordering::SeqCst);
12700 panic!("intentional JoinHandle awaiter wake-by-ref panic");
12701 }
12702 }
12703
12704 #[cfg(napi_runtime_os_threads)]
12705 impl Drop for PanickingJoinAwaiter {
12706 fn drop(&mut self) {
12707 self.drops.fetch_add(1, Ordering::SeqCst);
12708 panic!("intentional JoinHandle awaiter destructor panic");
12709 }
12710 }
12711
12712 #[cfg(napi_runtime_os_threads)]
12713 fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
12714 Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
12715 }
12716
12717 #[cfg(napi_runtime_os_threads)]
12718 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12719 enum JoinAwaiterWakerEventKind {
12720 Clone,
12721 Wake,
12722 WakeByRef,
12723 Drop,
12724 }
12725
12726 #[cfg(napi_runtime_os_threads)]
12727 struct JoinAwaiterWakerState {
12728 events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
12729 }
12730
12731 #[cfg(napi_runtime_os_threads)]
12732 impl JoinAwaiterWakerState {
12733 fn record(&self, kind: JoinAwaiterWakerEventKind) {
12734 self
12735 .events
12736 .lock()
12737 .unwrap_or_else(std::sync::PoisonError::into_inner)
12738 .push((kind, active_runtime_generation()));
12739 }
12740
12741 fn clear(&self) {
12742 self
12743 .events
12744 .lock()
12745 .unwrap_or_else(std::sync::PoisonError::into_inner)
12746 .clear();
12747 }
12748
12749 fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
12750 self
12751 .events
12752 .lock()
12753 .unwrap_or_else(std::sync::PoisonError::into_inner)
12754 .clone()
12755 }
12756 }
12757
12758 #[cfg(napi_runtime_os_threads)]
12759 struct JoinAwaiterWakerToken {
12760 state: Arc<JoinAwaiterWakerState>,
12761 }
12762
12763 #[cfg(napi_runtime_os_threads)]
12764 unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
12765 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12766 token.state.record(JoinAwaiterWakerEventKind::Clone);
12767 let clone = Box::new(JoinAwaiterWakerToken {
12768 state: Arc::clone(&token.state),
12769 });
12770 std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
12771 }
12772
12773 #[cfg(napi_runtime_os_threads)]
12774 unsafe fn wake_join_awaiter_waker(data: *const ()) {
12775 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12776 token.state.record(JoinAwaiterWakerEventKind::Wake);
12777 }
12778
12779 #[cfg(napi_runtime_os_threads)]
12780 unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
12781 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12782 token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
12783 }
12784
12785 #[cfg(napi_runtime_os_threads)]
12786 unsafe fn drop_join_awaiter_waker(data: *const ()) {
12787 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12788 token.state.record(JoinAwaiterWakerEventKind::Drop);
12789 }
12790
12791 #[cfg(napi_runtime_os_threads)]
12792 static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
12793 clone_join_awaiter_waker,
12794 wake_join_awaiter_waker,
12795 wake_join_awaiter_waker_by_ref,
12796 drop_join_awaiter_waker,
12797 );
12798
12799 #[cfg(napi_runtime_os_threads)]
12800 fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
12801 let token = Box::new(JoinAwaiterWakerToken { state });
12802 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
12803 unsafe { Waker::from_raw(raw) }
12804 }
12805
12806 #[cfg(napi_runtime_os_threads)]
12807 fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
12808 wait_until("the block_on driver to sleep", || {
12809 stop
12810 .parkers
12811 .lock()
12812 .unwrap_or_else(std::sync::PoisonError::into_inner)
12813 .iter()
12814 .filter_map(Weak::upgrade)
12815 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12816 });
12817 }
12818
12819 #[cfg(panic = "unwind")]
12820 #[test]
12821 fn unique_ids_fail_before_wraparound() {
12822 let counter = AtomicU64::new(u64::MAX - 1);
12823 assert_eq!(
12824 next_unique_id(&counter, "test id space exhausted"),
12825 u64::MAX - 1
12826 );
12827
12828 let exhausted = catch_unwind(AssertUnwindSafe(|| {
12829 next_unique_id(&counter, "test id space exhausted");
12830 }));
12831 assert!(exhausted.is_err());
12832 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
12833 }
12834
12835 #[cfg(panic = "unwind")]
12836 #[test]
12837 fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
12838 let work = GenerationWork::new();
12839 {
12840 let mut state = work.state.lock().unwrap();
12841 state.next_task_id = u64::MAX;
12842 }
12843
12844 let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
12845 assert!(exhausted.is_err());
12846
12847 let state = work.state.lock().unwrap();
12848 assert_eq!(state.next_task_id, u64::MAX);
12849 assert_eq!(state.active, 0);
12850 assert!(state.abort_handles.is_empty());
12851 }
12852
12853 struct GenerationStateLockProbe {
12858 work: Weak<GenerationWork>,
12859 dropped_with_lock_free: Arc<Mutex<Option<bool>>>,
12860 }
12861
12862 #[expect(
12863 clippy::manual_noop_waker,
12864 reason = "the test observes the waker payload's Drop"
12865 )]
12866 impl std::task::Wake for GenerationStateLockProbe {
12867 fn wake(self: Arc<Self>) {}
12868 }
12869
12870 impl Drop for GenerationStateLockProbe {
12871 fn drop(&mut self) {
12872 let lock_free = self
12873 .work
12874 .upgrade()
12875 .is_some_and(|work| work.state.try_lock().is_ok());
12876 *self.dropped_with_lock_free.lock().unwrap() = Some(lock_free);
12877 }
12878 }
12879
12880 fn register_task_with_state_lock_probe(
12884 work: &Arc<GenerationWork>,
12885 ) -> (GenerationWorkGuard, Arc<Mutex<Option<bool>>>) {
12886 let (registration, guard) = work
12887 .try_register_async()
12888 .expect("an open generation must accept the task");
12889 let dropped_with_lock_free = Arc::new(Mutex::new(None));
12890 let waker = Waker::from(Arc::new(GenerationStateLockProbe {
12891 work: Arc::downgrade(work),
12892 dropped_with_lock_free: Arc::clone(&dropped_with_lock_free),
12893 }));
12894 let mut task = std::pin::pin!(Abortable::new(std::future::pending::<()>(), registration));
12895 assert!(
12896 task
12897 .as_mut()
12898 .poll(&mut Context::from_waker(&waker))
12899 .is_pending()
12900 );
12901 (guard, dropped_with_lock_free)
12902 }
12903
12904 #[test]
12905 fn generation_work_guard_drops_its_abort_handle_outside_the_state_lock() {
12906 let work = GenerationWork::new();
12911 let (guard, dropped_with_lock_free) = register_task_with_state_lock_probe(&work);
12912 assert_eq!(
12913 *dropped_with_lock_free.lock().unwrap(),
12914 None,
12915 "the handle the generation keeps must still own the abort state"
12916 );
12917
12918 drop(guard);
12919 assert_eq!(
12920 *dropped_with_lock_free.lock().unwrap(),
12921 Some(true),
12922 "the guard must drop the handle it removed after releasing the state lock"
12923 );
12924 }
12925
12926 #[test]
12927 fn close_and_abort_takes_the_abort_handles_out_of_the_state() {
12928 let work = GenerationWork::new();
12932 let (registration, guard) = work
12933 .try_register_async()
12934 .expect("an open generation must accept the task");
12935 let task = Abortable::new(std::future::pending::<()>(), registration);
12936
12937 work.close_and_abort();
12938 assert!(task.is_aborted(), "close must abort the registered task");
12939 assert!(
12940 work.state.lock().unwrap().abort_handles.is_empty(),
12941 "close must take the handles out of the state, not leave them for the guards"
12942 );
12943 drop(task);
12944 drop(guard);
12945 assert_eq!(work.state.lock().unwrap().active, 0);
12946 }
12947
12948 #[cfg(panic = "unwind")]
12949 #[test]
12950 fn caught_panic_payload_destructor_runs() {
12951 struct DropSignal(Arc<AtomicBool>);
12952
12953 impl Drop for DropSignal {
12954 fn drop(&mut self) {
12955 self.0.store(true, Ordering::SeqCst);
12956 }
12957 }
12958
12959 let dropped = Arc::new(AtomicBool::new(false));
12960 let payload = DropSignal(Arc::clone(&dropped));
12961
12962 assert!(
12963 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
12964 "the original panic must remain contained"
12965 );
12966 assert!(
12967 dropped.load(Ordering::SeqCst),
12968 "an ordinary caught panic payload must be destroyed"
12969 );
12970 }
12971
12972 #[cfg(panic = "unwind")]
12973 #[test]
12974 fn hostile_caught_panic_payload_destructor_remains_contained() {
12975 struct PanicOnDrop(Arc<AtomicBool>);
12976
12977 impl Drop for PanicOnDrop {
12978 fn drop(&mut self) {
12979 self.0.store(true, Ordering::SeqCst);
12980 panic!("caught panic payload destructor escaped");
12981 }
12982 }
12983
12984 let dropped = Arc::new(AtomicBool::new(false));
12985 let payload = PanicOnDrop(Arc::clone(&dropped));
12986 let result = catch_unwind(AssertUnwindSafe(|| {
12987 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12988 }));
12989
12990 assert!(
12991 result.is_ok(),
12992 "the payload destructor's nested panic must remain contained"
12993 );
12994 assert!(
12995 result.unwrap().is_none(),
12996 "the original panic must remain contained"
12997 );
12998 assert!(
12999 dropped.load(Ordering::SeqCst),
13000 "the hostile payload destructor must be attempted"
13001 );
13002 }
13003
13004 fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13005 CurrentThreadHostDispatchResult::Accepted
13006 }
13007
13008 fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13009 CurrentThreadHostDispatchResult::Unavailable
13010 }
13011
13012 fn count_current_thread_recovery_host_dispatch(
13013 _dispatch: u64,
13014 ) -> CurrentThreadHostDispatchResult {
13015 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
13016 CurrentThreadHostDispatchResult::Accepted
13017 }
13018
13019 fn reset_current_thread_recovery_host_dispatches() {
13020 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
13021 }
13022
13023 fn current_thread_recovery_host_dispatches() -> usize {
13024 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
13025 }
13026
13027 fn accept_only_first_current_thread_host_dispatch(
13028 _dispatch: u64,
13029 ) -> CurrentThreadHostDispatchResult {
13030 if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
13031 let previous = dispatches.get();
13032 dispatches.set(previous + 1);
13033 previous == 0
13034 }) {
13035 CurrentThreadHostDispatchResult::Accepted
13036 } else {
13037 CurrentThreadHostDispatchResult::Unavailable
13038 }
13039 }
13040
13041 fn reset_current_thread_one_shot_host_dispatches() {
13042 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
13043 }
13044
13045 fn current_thread_one_shot_host_dispatches() -> usize {
13046 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
13047 }
13048
13049 fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13050 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13051 CurrentThreadHostDispatchResult::Accepted
13052 }
13053
13054 fn count_current_thread_blocking_budget_host_dispatch(
13055 _dispatch: u64,
13056 ) -> CurrentThreadHostDispatchResult {
13057 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13058 CurrentThreadHostDispatchResult::Accepted
13059 }
13060
13061 fn count_current_thread_admission_host_dispatch(
13062 _dispatch: u64,
13063 ) -> CurrentThreadHostDispatchResult {
13064 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13065 CurrentThreadHostDispatchResult::Accepted
13066 }
13067
13068 fn count_current_thread_interleaved_recovery_host_dispatch(
13069 _dispatch: u64,
13070 ) -> CurrentThreadHostDispatchResult {
13071 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13072 CurrentThreadHostDispatchResult::Accepted
13073 }
13074
13075 fn dispatch_late_registration_current_thread_tasks(
13076 dispatch: u64,
13077 ) -> CurrentThreadHostDispatchResult {
13078 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
13079 }
13080
13081 fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
13082 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13083 .lock()
13084 .unwrap()
13085 .push(dispatch);
13086 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
13087 }
13088
13089 fn dispatch_authoritative_panic_current_thread_tasks(
13090 dispatch: u64,
13091 ) -> CurrentThreadHostDispatchResult {
13092 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13093 .lock()
13094 .unwrap()
13095 .push(dispatch);
13096 match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
13097 0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
13098 2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
13099 _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
13100 }
13101 }
13102
13103 fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
13104 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13105 }
13106
13107 fn dispatch_waker_stack_failure_current_thread_tasks(
13108 dispatch: u64,
13109 ) -> CurrentThreadHostDispatchResult {
13110 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13111 }
13112
13113 fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
13114 dispatch: u64,
13115 ) -> CurrentThreadHostDispatchResult {
13116 CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13117 }
13118
13119 fn dispatch_submission_failure_current_thread_tasks(
13120 dispatch: u64,
13121 ) -> CurrentThreadHostDispatchResult {
13122 CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13123 }
13124
13125 fn dispatch_retirement_turn_current_thread_tasks(
13126 dispatch: u64,
13127 ) -> CurrentThreadHostDispatchResult {
13128 CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
13129 }
13130
13131 fn dispatch_nested_retirement_current_thread_tasks(
13132 dispatch: u64,
13133 ) -> CurrentThreadHostDispatchResult {
13134 CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
13135 }
13136
13137 fn dispatch_sustained_producer_current_thread_tasks(
13138 dispatch: u64,
13139 ) -> CurrentThreadHostDispatchResult {
13140 CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
13141 }
13142
13143 fn dispatch_retirement_retry_current_thread_tasks(
13144 dispatch: u64,
13145 ) -> CurrentThreadHostDispatchResult {
13146 CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
13147 }
13148
13149 fn dispatch_final_retirement_offer_current_thread_tasks(
13150 dispatch: u64,
13151 ) -> CurrentThreadHostDispatchResult {
13152 CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
13153 }
13154
13155 fn dispatch_inline_final_offer_current_thread_tasks(
13156 dispatch: u64,
13157 ) -> CurrentThreadHostDispatchResult {
13158 CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
13159 }
13160
13161 fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
13162 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13163 .lock()
13164 .unwrap()
13165 .push(dispatch);
13166 assert!(
13167 !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13168 "injected CurrentThread host dispatch panic"
13169 );
13170 CurrentThreadHostDispatchResult::Accepted
13171 }
13172
13173 fn panic_once_current_thread_armed_republish_host_dispatch(
13174 dispatch: u64,
13175 ) -> CurrentThreadHostDispatchResult {
13176 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13177 .lock()
13178 .unwrap()
13179 .push(dispatch);
13180 assert!(
13181 !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13182 "injected armed CurrentThread host dispatch panic"
13183 );
13184 CurrentThreadHostDispatchResult::Accepted
13185 }
13186
13187 fn record_current_thread_republish_rearm_dispatch(
13188 dispatch: u64,
13189 ) -> CurrentThreadHostDispatchResult {
13190 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13191 .lock()
13192 .unwrap()
13193 .push(dispatch);
13194 CurrentThreadHostDispatchResult::Accepted
13195 }
13196
13197 fn reject_initial_current_thread_publication_once(
13198 dispatch: u64,
13199 ) -> CurrentThreadHostDispatchResult {
13200 let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13201 .lock()
13202 .unwrap();
13203 dispatches.push(dispatch);
13204 if dispatches.len() == 1 {
13205 CurrentThreadHostDispatchResult::Unavailable
13206 } else {
13207 CurrentThreadHostDispatchResult::Accepted
13208 }
13209 }
13210
13211 fn reject_current_thread_replacement_publication_once(
13212 dispatch: u64,
13213 ) -> CurrentThreadHostDispatchResult {
13214 let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13215 .lock()
13216 .unwrap();
13217 dispatches.push(dispatch);
13218 if dispatches.len() == 2 {
13219 CurrentThreadHostDispatchResult::Unavailable
13220 } else {
13221 CurrentThreadHostDispatchResult::Accepted
13222 }
13223 }
13224
13225 fn fail_current_thread_replacement_publication_once(
13226 dispatch: u64,
13227 ) -> CurrentThreadHostDispatchResult {
13228 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13229 .lock()
13230 .unwrap()
13231 .push(dispatch);
13232 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
13233 }
13234
13235 fn count_current_thread_shutdown_republish_host_dispatch(
13236 _dispatch: u64,
13237 ) -> CurrentThreadHostDispatchResult {
13238 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13239 CurrentThreadHostDispatchResult::Accepted
13240 }
13241
13242 fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13243 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13244 CurrentThreadHostDispatchResult::Accepted
13245 }
13246
13247 fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
13248 let pending = executor.dispatch_pending.load(Ordering::Acquire);
13249 if pending != 0 {
13250 return pending;
13251 }
13252 let dispatch = next_current_thread_dispatch_id();
13253 match executor.dispatch_pending.compare_exchange(
13254 0,
13255 dispatch,
13256 Ordering::AcqRel,
13257 Ordering::Acquire,
13258 ) {
13259 Ok(_) => dispatch,
13260 Err(pending) => pending,
13261 }
13262 }
13263
13264 fn current_thread_dispatch_failure(
13265 dispatch: u64,
13266 epoch: u64,
13267 ) -> CurrentThreadTaskDispatchFailure {
13268 CurrentThreadTaskDispatchFailure { dispatch, epoch }
13269 }
13270
13271 fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
13272 let executor = controller
13273 .running_current_thread_executor()
13274 .expect("the test controller must have a running CurrentThread executor");
13275 current_thread_dispatch(&executor)
13276 }
13277
13278 #[test]
13279 fn current_thread_executor_drives_spawned_tasks() {
13280 let metrics = Arc::new(RuntimeMetrics::default());
13281 let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
13282 let scheduler = Arc::clone(&executor);
13283 let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
13284 scheduler.schedule(runnable);
13285 });
13286
13287 executor.schedule(runnable);
13288
13289 let mut output = None;
13290 {
13291 let mut future = std::pin::pin!(async {
13292 output = Some(task.await);
13293 });
13294 executor.block_on(future.as_mut());
13295 }
13296 assert_eq!(output, Some(42));
13297 assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
13298 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
13299 }
13300
13301 #[cfg(napi_runtime_os_threads)]
13302 #[test]
13303 fn current_thread_shutdown_wake_does_not_repoll_user_future() {
13304 use std::sync::mpsc;
13305
13306 let executor = Arc::new(CurrentThreadExecutor::with_detection(
13307 Arc::new(RuntimeMetrics::default()),
13308 false,
13309 None,
13310 ));
13311 let polls = Arc::new(AtomicUsize::new(0));
13312 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13313 let (outcome_tx, outcome_rx) = mpsc::channel();
13314 let driver_executor = Arc::clone(&executor);
13315 let driver_polls = Arc::clone(&polls);
13316 let driver = std::thread::spawn(move || {
13317 let mut future = std::pin::pin!(StopPollProbe {
13318 polls: driver_polls,
13319 first_poll: Some(first_poll_tx)
13320 });
13321 outcome_tx
13322 .send(driver_executor.block_on(future.as_mut()))
13323 .unwrap();
13324 });
13325
13326 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13327 wait_for_generation_parker_to_sleep(&executor.stop);
13328 executor.stop.begin_shutdown();
13329
13330 assert_eq!(
13331 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13332 BlockOnOutcome::Stopped
13333 );
13334 driver.join().unwrap();
13335 assert_eq!(
13336 polls.load(Ordering::SeqCst),
13337 1,
13338 "a shutdown wake must be checked before the next CurrentThread user poll"
13339 );
13340 }
13341
13342 #[cfg(napi_runtime_os_threads)]
13343 #[test]
13344 fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
13345 use std::sync::mpsc;
13346
13347 let metrics = Arc::new(RuntimeMetrics::default());
13348 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13349 Arc::clone(&metrics),
13350 accept_current_thread_host_dispatch,
13351 ));
13352 let polls = Arc::new(AtomicUsize::new(0));
13353 let task_polls = Arc::clone(&polls);
13354 let (runnable, task) = async_task::spawn(
13355 async move {
13356 task_polls.fetch_add(1, Ordering::SeqCst);
13357 },
13358 |_| {},
13359 );
13360 executor.schedule(runnable);
13361 let dispatch = current_thread_dispatch(&executor);
13362
13363 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
13364 let (release_claim_tx, release_claim_rx) = mpsc::channel();
13365 let driver_executor = Arc::clone(&executor);
13366 let driver = std::thread::spawn(move || {
13367 CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
13368 *slot.borrow_mut() = Some(Box::new(move || {
13369 claim_locked_tx.send(()).unwrap();
13370 release_claim_rx.recv().unwrap();
13371 }));
13372 });
13373 driver_executor.drive_host_turn_for_dispatch(dispatch);
13374 });
13375 claim_locked_rx
13376 .recv_timeout(Duration::from_secs(2))
13377 .expect("the admitted host turn must pause before its runnable claim");
13378
13379 executor.stop.publish_stopping();
13380 release_claim_tx.send(()).unwrap();
13381 driver.join().unwrap();
13382
13383 assert_eq!(
13384 polls.load(Ordering::SeqCst),
13385 0,
13386 "stop publication must outrank a later runnable claim by an already-admitted host turn"
13387 );
13388 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13389
13390 executor.begin_shutdown();
13391 assert!(
13392 futures::executor::block_on(task.fallible()).is_none(),
13393 "shutdown must cancel the runnable rejected by the stopped claim"
13394 );
13395 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13396 }
13397
13398 #[cfg(napi_runtime_os_threads)]
13399 #[test]
13400 fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
13401 use std::sync::mpsc;
13402
13403 let metrics = Arc::new(RuntimeMetrics::default());
13404 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13405 Arc::clone(&metrics),
13406 accept_current_thread_host_dispatch,
13407 ));
13408 let polls = Arc::new(AtomicUsize::new(0));
13409 let task_polls = Arc::clone(&polls);
13410 let (runnable, task) = async_task::spawn(
13411 async move {
13412 task_polls.fetch_add(1, Ordering::SeqCst);
13413 },
13414 |_| {},
13415 );
13416 executor.schedule(runnable);
13417
13418 let queue = executor
13419 .queue
13420 .lock()
13421 .unwrap_or_else(std::sync::PoisonError::into_inner);
13422 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
13423 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
13424 let cancelling_executor = Arc::clone(&executor);
13425 let cancellation = std::thread::spawn(move || {
13426 let scheduler = cancelling_executor
13427 .scheduler_idle_lock
13428 .lock()
13429 .unwrap_or_else(std::sync::PoisonError::into_inner);
13430 let cancelling = cancelling_executor
13431 .begin_failed_dispatch_cancellation()
13432 .expect("the test must publish terminal dispatch cancellation");
13433 drop(scheduler);
13434 cancellation_published_tx.send(()).unwrap();
13435 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
13436 cancellation_done_tx.send(()).unwrap();
13437 });
13438 cancellation_published_rx
13439 .recv_timeout(Duration::from_secs(2))
13440 .expect("terminal dispatch cancellation must publish before queue draining");
13441
13442 let claimant_executor = Arc::clone(&executor);
13443 let claimant = std::thread::spawn(move || claimant_executor.drain_one());
13444 assert!(
13445 !claimant.join().unwrap(),
13446 "a runnable claim after terminal cancellation publication must be rejected"
13447 );
13448 assert_eq!(polls.load(Ordering::SeqCst), 0);
13449 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13450 assert!(
13451 matches!(
13452 cancellation_done_rx.try_recv(),
13453 Err(mpsc::TryRecvError::Empty)
13454 ),
13455 "terminal cancellation must still be waiting to drain the queued runnable"
13456 );
13457
13458 drop(queue);
13459 cancellation_done_rx
13460 .recv_timeout(Duration::from_secs(2))
13461 .expect("terminal cancellation must drain the rejected runnable");
13462 cancellation.join().unwrap();
13463 assert!(
13464 futures::executor::block_on(task.fallible()).is_none(),
13465 "terminal cancellation must cancel the runnable it prevented from starting"
13466 );
13467 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13468 }
13469
13470 #[cfg(napi_runtime_os_threads)]
13471 #[test]
13472 fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
13473 use std::sync::mpsc;
13474
13475 let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
13476 let polls = Arc::new(AtomicUsize::new(0));
13477 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13478 let (outcome_tx, outcome_rx) = mpsc::channel();
13479 let driver_executor = Arc::clone(&executor);
13480 let driver_polls = Arc::clone(&polls);
13481 let driver = std::thread::spawn(move || {
13482 let mut future = std::pin::pin!(StopPollProbe {
13483 polls: driver_polls,
13484 first_poll: Some(first_poll_tx)
13485 });
13486 outcome_tx
13487 .send(driver_executor.block_on(future.as_mut()))
13488 .unwrap();
13489 });
13490
13491 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13492 wait_for_generation_parker_to_sleep(&executor.stop);
13493 executor.stop.begin_shutdown();
13494
13495 assert_eq!(
13496 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13497 BlockOnOutcome::Stopped
13498 );
13499 driver.join().unwrap();
13500 assert_eq!(
13501 polls.load(Ordering::SeqCst),
13502 1,
13503 "a shutdown wake must be checked before the next parking block_on user poll"
13504 );
13505 }
13506
13507 #[cfg(napi_runtime_os_threads)]
13508 #[test]
13509 fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
13510 use std::sync::mpsc;
13511
13512 let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
13513 let polls = Arc::new(AtomicUsize::new(0));
13514 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13515 let (outcome_tx, outcome_rx) = mpsc::channel();
13516 let driver_executor = Arc::clone(&executor);
13517 let driver_polls = Arc::clone(&polls);
13518 let driver = std::thread::spawn(move || {
13519 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
13520 let mut future = std::pin::pin!(StopPollProbe {
13521 polls: driver_polls,
13522 first_poll: Some(first_poll_tx)
13523 });
13524 outcome_tx
13525 .send(driver_executor.block_on(future.as_mut()))
13526 .unwrap();
13527 });
13528
13529 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13530 wait_for_generation_parker_to_sleep(&executor.stop);
13531 executor.stop.begin_shutdown();
13532
13533 assert_eq!(
13534 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13535 BlockOnOutcome::Stopped
13536 );
13537 driver.join().unwrap();
13538 assert_eq!(
13539 polls.load(Ordering::SeqCst),
13540 1,
13541 "a shutdown wake must be checked before the next cooperative user poll"
13542 );
13543 }
13544
13545 #[test]
13546 fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
13547 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13548 Arc::new(RuntimeMetrics::default()),
13549 accept_current_thread_host_dispatch,
13550 ));
13551 let pending = u64::MAX - 1;
13552 executor.dispatch_pending.store(pending, Ordering::Release);
13553 let mut allocations = 0;
13554
13555 executor.request_drain_with(|| {
13556 allocations += 1;
13557 u64::MAX
13558 });
13559
13560 assert_eq!(allocations, 0);
13561 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
13562 }
13563
13564 #[cfg(napi_runtime_os_threads)]
13565 #[test]
13566 fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
13567 use std::sync::mpsc;
13568
13569 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13570 Arc::new(RuntimeMetrics::default()),
13571 accept_current_thread_host_dispatch,
13572 ));
13573 let dispatch = current_thread_dispatch(&executor);
13574 let scheduler = executor
13575 .scheduler_idle_lock
13576 .lock()
13577 .unwrap_or_else(std::sync::PoisonError::into_inner);
13578 let (claiming_tx, claiming_rx) = mpsc::channel();
13579 let (claimed_tx, claimed_rx) = mpsc::channel();
13580 let claiming_executor = Arc::clone(&executor);
13581 let claimant = std::thread::spawn(move || {
13582 claiming_tx.send(()).unwrap();
13583 claimed_tx
13584 .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
13585 .unwrap();
13586 });
13587 claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13588
13589 executor.stop.begin_shutdown();
13590 drop(scheduler);
13591
13592 assert!(
13593 !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13594 "an old-generation dispatch must not claim a host turn after stop becomes visible"
13595 );
13596 claimant.join().unwrap();
13597 assert_eq!(
13598 executor.dispatch_pending.load(Ordering::Acquire),
13599 dispatch,
13600 "rejecting the stopped claim must not consume its stale capability"
13601 );
13602 }
13603
13604 #[cfg(napi_runtime_os_threads)]
13605 #[test]
13606 fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
13607 use std::sync::mpsc;
13608
13609 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13610 let metrics = Arc::new(RuntimeMetrics::default());
13611 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13612 Arc::clone(&metrics),
13613 count_current_thread_stopped_host_dispatch,
13614 ));
13615 let scheduler = executor
13616 .scheduler_idle_lock
13617 .lock()
13618 .unwrap_or_else(std::sync::PoisonError::into_inner);
13619 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13620 let scheduling_executor = Arc::clone(&executor);
13621 let scheduling = std::thread::spawn(move || {
13622 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13623 *slot.borrow_mut() = Some(Box::new(move || {
13624 publication_blocked_tx.send(()).unwrap();
13625 }));
13626 });
13627 let scheduler = Arc::clone(&scheduling_executor);
13628 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13629 scheduler.schedule(runnable);
13630 });
13631 task.detach();
13632 scheduling_executor.schedule(runnable);
13633 });
13634
13635 publication_blocked_rx
13636 .recv_timeout(Duration::from_secs(2))
13637 .expect("dispatch publication must block behind the scheduler-idle mutex");
13638 executor.begin_shutdown();
13639 drop(scheduler);
13640 scheduling.join().unwrap();
13641 executor.wait_until_scheduler_idle();
13642
13643 assert_eq!(
13644 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
13645 0,
13646 "a publication that reaches the scheduler-idle barrier after stop must not call the host"
13647 );
13648 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13649 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13650 }
13651
13652 #[cfg(napi_runtime_os_threads)]
13653 #[test]
13654 fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
13655 use std::{sync::mpsc, time::Duration};
13656
13657 let metrics = Arc::new(RuntimeMetrics::default());
13658 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13659 Arc::clone(&metrics),
13660 accept_current_thread_host_dispatch,
13661 ));
13662 let scheduler = Arc::clone(&executor);
13663 let completed = Arc::new(AtomicBool::new(false));
13664 let completed_task = Arc::clone(&completed);
13665 let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
13666 let shared = async move {
13667 wake_rx.await.expect("wake sender dropped");
13668 }
13669 .boxed()
13670 .shared();
13671 let (runnable, task) = async_task::spawn(
13672 async move {
13673 shared.await;
13674 completed_task.store(true, Ordering::SeqCst);
13675 },
13676 move |runnable| scheduler.schedule(runnable),
13677 );
13678
13679 executor.schedule(runnable);
13680 assert!(
13681 !completed.load(Ordering::SeqCst),
13682 "accepted host dispatch must defer the first poll"
13683 );
13684 executor.drive_host_turn();
13685
13686 let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
13687 std::thread::spawn(move || {
13688 wake_tx.send(()).expect("shared wake receiver dropped");
13689 send_returned_tx.send(()).unwrap();
13690 });
13691 send_returned_rx
13692 .recv_timeout(Duration::from_secs(1))
13693 .expect("Shared wake re-entered its own waker mutex through inline polling");
13694 assert!(
13695 !completed.load(Ordering::SeqCst),
13696 "the wake must remain queued until a fresh host turn"
13697 );
13698
13699 executor.drive_host_turn();
13700 futures::executor::block_on(task);
13701 assert!(completed.load(Ordering::SeqCst));
13702 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13703 }
13704
13705 #[cfg(napi_runtime_os_threads)]
13706 #[test]
13707 fn current_thread_hostless_wake_is_enqueue_only() {
13708 use std::{sync::mpsc, time::Duration};
13709
13710 struct LockOnSecondPoll {
13711 gate: Arc<Mutex<()>>,
13712 first: bool,
13713 waker: Option<mpsc::Sender<Waker>>,
13714 completed: Arc<AtomicBool>,
13715 }
13716
13717 impl Future for LockOnSecondPoll {
13718 type Output = ();
13719
13720 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
13721 if self.first {
13722 self.first = false;
13723 self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
13724 return Poll::Pending;
13725 }
13726 drop(self.gate.lock().unwrap());
13727 self.completed.store(true, Ordering::SeqCst);
13728 Poll::Ready(())
13729 }
13730 }
13731
13732 let metrics = Arc::new(RuntimeMetrics::default());
13733 let executor = Arc::new(CurrentThreadExecutor::new(metrics));
13734 let gate = Arc::new(Mutex::new(()));
13735 let completed = Arc::new(AtomicBool::new(false));
13736 let (waker_tx, waker_rx) = mpsc::channel();
13737 let scheduler = Arc::clone(&executor);
13738 let (runnable, task) = async_task::spawn(
13739 LockOnSecondPoll {
13740 gate: Arc::clone(&gate),
13741 first: true,
13742 waker: Some(waker_tx),
13743 completed: Arc::clone(&completed),
13744 },
13745 move |runnable| scheduler.schedule(runnable),
13746 );
13747 executor.schedule(runnable);
13748 executor.drive_host_turn();
13749 let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
13750
13751 let (returned_tx, returned_rx) = mpsc::channel();
13752 let wake_gate = Arc::clone(&gate);
13753 let waking = std::thread::spawn(move || {
13754 let _held = wake_gate.lock().unwrap();
13755 waker.wake();
13756 returned_tx.send(()).unwrap();
13757 });
13758 returned_rx
13759 .recv_timeout(Duration::from_secs(1))
13760 .expect("hostless wake must enqueue without polling under the caller's lock");
13761 waking.join().unwrap();
13762 assert!(!completed.load(Ordering::SeqCst));
13763
13764 executor.drive_host_turn();
13765 futures::executor::block_on(task);
13766 assert!(completed.load(Ordering::SeqCst));
13767 }
13768
13769 #[cfg(napi_runtime_os_threads)]
13770 #[test]
13771 fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
13772 use std::sync::mpsc;
13773
13774 let controller = Arc::new(current_thread_controller(
13775 "current-thread-hostless-late-work",
13776 ));
13777 let backend = controller.backend();
13778 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
13779 panic!("configured CurrentThread backend must create a CurrentThread executor");
13780 };
13781 let executor = Arc::clone(executor);
13782 drop(backend);
13783
13784 let (value_tx, value_rx) = oneshot::channel();
13785 let (done_tx, done_rx) = mpsc::channel();
13786 let block_on_controller = Arc::clone(&controller);
13787 let runner = std::thread::spawn(move || {
13788 let mut output = None;
13789 let outcome = {
13790 let mut future = std::pin::pin!(async {
13791 output = Some(value_rx.await.expect("scheduled sender must deliver"));
13792 });
13793 block_on_controller.block_on(future.as_mut())
13794 };
13795 done_tx.send((outcome, output)).unwrap();
13796 });
13797
13798 wait_until(
13799 "the hostless CurrentThread block_on driver to sleep",
13800 || {
13801 executor
13802 .stop
13803 .parkers
13804 .lock()
13805 .unwrap_or_else(std::sync::PoisonError::into_inner)
13806 .iter()
13807 .filter_map(Weak::upgrade)
13808 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13809 },
13810 );
13811
13812 controller
13813 .try_spawn_detached(async move {
13814 value_tx
13815 .send(37usize)
13816 .expect("block_on receiver must remain alive");
13817 })
13818 .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
13819
13820 let (outcome, output) = done_rx
13821 .recv_timeout(Duration::from_secs(2))
13822 .expect("queue publication must wake the sleeping explicit driver");
13823 assert_eq!(outcome, BlockOnOutcome::Completed);
13824 assert_eq!(output, Some(37));
13825 runner.join().unwrap();
13826 controller.shutdown().unwrap();
13827 }
13828
13829 #[cfg(napi_runtime_os_threads)]
13830 #[test]
13831 fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
13832 use std::sync::mpsc;
13833
13834 struct BlockingPoll {
13835 entered: Option<mpsc::Sender<()>>,
13836 release: mpsc::Receiver<()>,
13837 }
13838
13839 impl Future for BlockingPoll {
13840 type Output = ();
13841
13842 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
13843 self.entered.take().unwrap().send(()).unwrap();
13844 self.release.recv().unwrap();
13845 Poll::Ready(())
13846 }
13847 }
13848
13849 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13850 Arc::new(RuntimeMetrics::default()),
13851 reject_current_thread_host_dispatch,
13852 ));
13853 let (value_tx, value_rx) = oneshot::channel();
13854 let (older_done_tx, older_done_rx) = mpsc::channel();
13855 let older_executor = Arc::clone(&executor);
13856 let older = std::thread::spawn(move || {
13857 let mut value = None;
13858 {
13859 let mut future = std::pin::pin!(async {
13860 value = Some(value_rx.await.unwrap());
13861 });
13862 assert_eq!(
13863 older_executor.block_on(future.as_mut()),
13864 BlockOnOutcome::Completed
13865 );
13866 }
13867 older_done_tx.send(value.unwrap()).unwrap();
13868 });
13869 wait_until("the older CurrentThread driver to sleep", || {
13870 executor
13871 .stop
13872 .parkers
13873 .lock()
13874 .unwrap_or_else(std::sync::PoisonError::into_inner)
13875 .iter()
13876 .filter_map(Weak::upgrade)
13877 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13878 });
13879
13880 let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
13881 let (release_newer_tx, release_newer_rx) = mpsc::channel();
13882 let newer_executor = Arc::clone(&executor);
13883 let newer = std::thread::spawn(move || {
13884 let mut future = std::pin::pin!(BlockingPoll {
13885 entered: Some(newer_entered_tx),
13886 release: release_newer_rx
13887 });
13888 assert_eq!(
13889 newer_executor.block_on(future.as_mut()),
13890 BlockOnOutcome::Completed
13891 );
13892 });
13893 newer_entered_rx
13894 .recv_timeout(Duration::from_secs(1))
13895 .expect("the newer driver must be blocked inside its poll");
13896
13897 let scheduler = Arc::clone(&executor);
13898 let (runnable, task) = async_task::spawn(
13899 async move {
13900 value_tx.send(41usize).unwrap();
13901 },
13902 move |runnable| scheduler.schedule(runnable),
13903 );
13904 task.detach();
13905 executor.schedule(runnable);
13906
13907 assert_eq!(
13908 older_done_rx
13909 .recv_timeout(Duration::from_secs(2))
13910 .expect("queue publication must also wake the older sleeping driver"),
13911 41
13912 );
13913 release_newer_tx.send(()).unwrap();
13914 older.join().unwrap();
13915 newer.join().unwrap();
13916 }
13917
13918 #[test]
13919 fn current_thread_shutdown_cancels_host_queued_task() {
13920 use std::sync::mpsc;
13921
13922 struct DropSignal(mpsc::Sender<()>);
13923
13924 impl Drop for DropSignal {
13925 fn drop(&mut self) {
13926 self.0.send(()).unwrap();
13927 }
13928 }
13929
13930 let metrics = Arc::new(RuntimeMetrics::default());
13931 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13932 Arc::clone(&metrics),
13933 accept_current_thread_host_dispatch,
13934 ));
13935 let scheduler = Arc::clone(&executor);
13936 let (dropped_tx, dropped_rx) = mpsc::channel();
13937 let signal = DropSignal(dropped_tx);
13938 let (runnable, task) = async_task::spawn(
13939 async move {
13940 let _signal = signal;
13941 futures::future::pending::<()>().await;
13942 },
13943 move |runnable| scheduler.schedule(runnable),
13944 );
13945
13946 executor.schedule(runnable);
13947 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13948 executor.begin_shutdown();
13949
13950 dropped_rx
13951 .recv_timeout(Duration::from_secs(1))
13952 .expect("shutdown must drop a task queued behind a host turn");
13953 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13954 assert!(
13955 futures::executor::block_on(task.fallible()).is_none(),
13956 "the queued task handle must resolve as cancelled"
13957 );
13958 }
13959
13960 #[test]
13961 fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
13962 reset_current_thread_recovery_host_dispatches();
13963 let metrics = Arc::new(RuntimeMetrics::default());
13964 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13965 Arc::clone(&metrics),
13966 count_current_thread_recovery_host_dispatch,
13967 ));
13968 let scheduler = Arc::clone(&executor);
13969 let completed = Arc::new(AtomicBool::new(false));
13970 let completed_task = Arc::clone(&completed);
13971 let (runnable, task) = async_task::spawn(
13972 async move {
13973 completed_task.store(true, Ordering::SeqCst);
13974 },
13975 move |runnable| scheduler.schedule(runnable),
13976 );
13977
13978 executor.schedule(runnable);
13979 assert_eq!(current_thread_recovery_host_dispatches(), 1);
13980 let lost_dispatch = current_thread_dispatch(&executor);
13981
13982 executor.request_drain_if_queued();
13986 assert_eq!(
13987 current_thread_recovery_host_dispatches(),
13988 2,
13989 "the newly registered host must receive the pending executor dispatch"
13990 );
13991 assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13992 executor.drive_host_turn_for_dispatch(lost_dispatch);
13993 futures::executor::block_on(task);
13994 assert!(completed.load(Ordering::SeqCst));
13995 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13996
13997 let second_completed = Arc::new(AtomicBool::new(false));
13998 let second_completed_task = Arc::clone(&second_completed);
13999 let second_scheduler = Arc::clone(&executor);
14000 let (second_runnable, second_task) = async_task::spawn(
14001 async move {
14002 second_completed_task.store(true, Ordering::SeqCst);
14003 },
14004 move |runnable| second_scheduler.schedule(runnable),
14005 );
14006 executor.schedule(second_runnable);
14007 assert_eq!(
14008 current_thread_recovery_host_dispatches(),
14009 3,
14010 "work queued after the stale callback must receive a new dispatch"
14011 );
14012 let third_dispatch = current_thread_dispatch(&executor);
14013
14014 executor.drive_host_turn_for_dispatch(lost_dispatch);
14017 assert!(!second_completed.load(Ordering::SeqCst));
14018 executor.drive_host_turn_for_dispatch(third_dispatch);
14019 futures::executor::block_on(second_task);
14020 assert!(second_completed.load(Ordering::SeqCst));
14021
14022 let third_scheduler = Arc::clone(&executor);
14023 let (third_runnable, third_task) =
14024 async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
14025 executor.schedule(third_runnable);
14026 assert_eq!(
14027 current_thread_recovery_host_dispatches(),
14028 4,
14029 "a stale callback must not leave the dispatch latch stuck"
14030 );
14031 executor.drive_host_turn();
14032 futures::executor::block_on(third_task);
14033 }
14034
14035 #[test]
14036 fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
14037 let metrics = Arc::new(RuntimeMetrics::default());
14038 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14039 Arc::clone(&metrics),
14040 accept_current_thread_host_dispatch,
14041 ));
14042 let scheduler = Arc::clone(&executor);
14043 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14044 scheduler.schedule(runnable)
14045 });
14046
14047 executor.schedule(runnable);
14048 let failed_dispatch = current_thread_dispatch(&executor);
14049 executor.cancel_host_dispatch_for_test(failed_dispatch);
14050 let recovery_dispatch = current_thread_dispatch(&executor);
14051 assert_eq!(
14052 executor.recovery_dispatch.load(Ordering::Acquire),
14053 recovery_dispatch
14054 );
14055
14056 executor.request_drain_if_queued();
14057 let new_host_dispatch = current_thread_dispatch(&executor);
14058 assert_eq!(new_host_dispatch, recovery_dispatch);
14059 assert_eq!(
14060 executor.recovery_dispatch.load(Ordering::Acquire),
14061 recovery_dispatch
14062 );
14063 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14064
14065 executor.drive_host_turn_for_dispatch(new_host_dispatch);
14066 assert_eq!(futures::executor::block_on(task), 17);
14067 }
14068
14069 #[cfg(napi_runtime_os_threads)]
14070 #[test]
14071 fn current_thread_host_admission_serializes_dispatch_publication() {
14072 use std::sync::mpsc;
14073
14074 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14075 let metrics = Arc::new(RuntimeMetrics::default());
14076 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14077 Arc::clone(&metrics),
14078 count_current_thread_admission_host_dispatch,
14079 ));
14080 let first_scheduler = Arc::clone(&executor);
14081 let (first_runnable, first_task) =
14082 async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
14083 first_task.detach();
14084 executor.schedule(first_runnable);
14085 let dispatch = current_thread_dispatch(&executor);
14086 assert_eq!(
14087 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14088 1
14089 );
14090
14091 let (claimed_tx, claimed_rx) = mpsc::channel();
14092 let (release_tx, release_rx) = mpsc::channel();
14093 let driver_executor = Arc::clone(&executor);
14094 let driver = std::thread::spawn(move || {
14095 CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
14096 *slot.borrow_mut() = Some(Box::new(move || {
14097 claimed_tx.send(()).unwrap();
14098 release_rx.recv().unwrap();
14099 }));
14100 });
14101 driver_executor.drive_host_turn_for_dispatch(dispatch);
14102 });
14103 claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14104
14105 let second_ran = Arc::new(AtomicBool::new(false));
14106 let second_ran_task = Arc::clone(&second_ran);
14107 let scheduling_executor = Arc::clone(&executor);
14108 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
14109 let (scheduled_tx, scheduled_rx) = mpsc::channel();
14110 let scheduling = std::thread::spawn(move || {
14111 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14112 *slot.borrow_mut() = Some(Box::new(move || {
14113 publication_blocked_tx.send(()).unwrap();
14114 }));
14115 });
14116 let scheduler = Arc::clone(&scheduling_executor);
14117 let (runnable, task) = async_task::spawn(
14118 async move {
14119 second_ran_task.store(true, Ordering::SeqCst);
14120 },
14121 move |runnable| scheduler.schedule(runnable),
14122 );
14123 task.detach();
14124 scheduling_executor.schedule(runnable);
14125 scheduled_tx.send(()).unwrap();
14126 });
14127
14128 publication_blocked_rx
14129 .recv_timeout(Duration::from_secs(2))
14130 .unwrap();
14131 assert!(
14132 matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
14133 "dispatch publication must wait until host admission publishes its draining role"
14134 );
14135 assert_eq!(
14136 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14137 1,
14138 "the admission gap must not publish a duplicate host dispatch"
14139 );
14140
14141 release_tx.send(()).unwrap();
14142 scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14143 scheduling.join().unwrap();
14144 driver.join().unwrap();
14145 if !second_ran.load(Ordering::SeqCst) {
14146 executor.drive_host_turn();
14147 }
14148 assert!(second_ran.load(Ordering::SeqCst));
14149 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14150 }
14151
14152 #[test]
14153 fn current_thread_host_turn_yields_after_runnable_budget() {
14154 struct SelfWakingFuture {
14155 remaining: usize,
14156 }
14157
14158 impl Future for SelfWakingFuture {
14159 type Output = ();
14160
14161 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14162 if self.remaining == 0 {
14163 return Poll::Ready(());
14164 }
14165 self.remaining -= 1;
14166 cx.waker().wake_by_ref();
14167 Poll::Pending
14168 }
14169 }
14170
14171 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14172 let metrics = Arc::new(RuntimeMetrics::default());
14173 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14174 Arc::clone(&metrics),
14175 count_current_thread_budget_host_dispatch,
14176 ));
14177 let scheduler = Arc::clone(&executor);
14178 let (runnable, task) = async_task::spawn(
14179 SelfWakingFuture {
14180 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14181 },
14182 move |runnable| scheduler.schedule(runnable),
14183 );
14184
14185 executor.schedule(runnable);
14186 executor.drive_host_turn();
14187
14188 assert_eq!(
14189 metrics.runnable_polls.load(Ordering::Relaxed),
14190 CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
14191 );
14192 assert_eq!(
14193 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14194 2,
14195 "remaining hot work must continue through a fresh host turn"
14196 );
14197
14198 executor.drive_host_turn();
14199 futures::executor::block_on(task);
14200 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14201 }
14202
14203 #[cfg(napi_runtime_os_threads)]
14204 #[test]
14205 fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
14206 const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
14207
14208 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14209 let metrics = Arc::new(RuntimeMetrics::default());
14210 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14211 Arc::clone(&metrics),
14212 count_current_thread_blocking_budget_host_dispatch,
14213 ));
14214 let work = GenerationWork::new();
14215 let observed = Arc::new(Mutex::new(Vec::new()));
14216
14217 let task_executor = Arc::clone(&executor);
14218 let wake_executor = Arc::clone(&executor);
14219 let producer_executor = Arc::clone(&executor);
14220 let producer_work = Arc::clone(&work);
14221 let producer_observed = Arc::clone(&observed);
14222 let outer_registration = work
14223 .try_register_work()
14224 .expect("the outer blocking job must be accepted");
14225 let (runnable, task) = async_task::spawn(
14226 async move {
14227 let queued = task_executor
14228 .schedule_blocking(
14229 move || {
14230 std::thread::spawn(move || {
14231 (0..QUEUED_BLOCKING_JOBS)
14232 .map(|index| {
14233 let registration = producer_work
14234 .try_register_work()
14235 .expect("queued blocking work must be accepted");
14236 let observed = Arc::clone(&producer_observed);
14237 producer_executor.schedule_blocking(
14238 move || {
14239 observed
14240 .lock()
14241 .unwrap_or_else(std::sync::PoisonError::into_inner)
14242 .push(index);
14243 },
14244 registration,
14245 )
14246 })
14247 .collect::<Vec<_>>()
14248 })
14249 .join()
14250 .expect("the blocking producer must finish")
14251 },
14252 outer_registration,
14253 )
14254 .await
14255 .expect("the inline blocking job must finish");
14256 for handle in queued {
14257 handle.await.expect("queued blocking work must finish");
14258 }
14259 },
14260 move |runnable| wake_executor.schedule(runnable),
14261 );
14262
14263 executor.schedule(runnable);
14264 executor.drive_host_turn();
14265
14266 let first_turn = observed
14267 .lock()
14268 .unwrap_or_else(std::sync::PoisonError::into_inner)
14269 .clone();
14270 assert!(
14271 !first_turn.is_empty(),
14272 "the first host turn must service queued blocking work"
14273 );
14274 assert!(
14275 first_turn.len() < QUEUED_BLOCKING_JOBS,
14276 "inline lane release must not drain the entire blocking FIFO in one host turn"
14277 );
14278 assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
14279 assert_eq!(
14280 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14281 2,
14282 "remaining blocking work must continue through a fresh host turn"
14283 );
14284
14285 let mut continuation_turns = 0;
14286 while observed
14287 .lock()
14288 .unwrap_or_else(std::sync::PoisonError::into_inner)
14289 .len()
14290 < QUEUED_BLOCKING_JOBS
14291 {
14292 continuation_turns += 1;
14293 assert!(
14294 continuation_turns <= QUEUED_BLOCKING_JOBS,
14295 "queued blocking work failed to make bounded host-turn progress"
14296 );
14297 executor.drive_host_turn();
14298 }
14299 executor.drive_host_turn();
14300 futures::executor::block_on(task);
14301
14302 assert_eq!(
14303 *observed
14304 .lock()
14305 .unwrap_or_else(std::sync::PoisonError::into_inner),
14306 (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
14307 "blocking jobs must preserve FIFO order across host turns"
14308 );
14309 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14310 }
14311
14312 #[cfg(napi_runtime_os_threads)]
14313 #[test]
14314 fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
14315 use std::sync::mpsc;
14316
14317 struct SelfWakingFuture {
14318 remaining: usize,
14319 }
14320
14321 impl Future for SelfWakingFuture {
14322 type Output = ();
14323
14324 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14325 if self.remaining == 0 {
14326 return Poll::Ready(());
14327 }
14328 self.remaining -= 1;
14329 cx.waker().wake_by_ref();
14330 Poll::Pending
14331 }
14332 }
14333
14334 let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
14335 let old_generation = controller.backend().generation();
14336 controller
14337 .try_spawn_detached(SelfWakingFuture {
14338 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14339 })
14340 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
14341 let dispatch = controller_current_thread_dispatch(&controller);
14342
14343 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14344 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14345 let driver_controller = Arc::clone(&controller);
14346 let driver = std::thread::spawn(move || {
14347 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14348 *slot.borrow_mut() = Some(Box::new(move || {
14349 dispatch_entered_tx.send(()).unwrap();
14350 release_dispatch_rx.recv().unwrap();
14351 }));
14352 });
14353 driver_controller.drive_current_thread_dispatch(dispatch, false);
14354 });
14355 dispatch_entered_rx
14356 .recv_timeout(Duration::from_secs(2))
14357 .expect("the bounded host turn must begin publishing its continuation");
14358
14359 let (shutdown_tx, shutdown_rx) = mpsc::channel();
14360 let shutdown_controller = Arc::clone(&controller);
14361 let shutdown = std::thread::spawn(move || {
14362 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14363 });
14364 wait_until("CurrentThread shutdown to leave Running", || {
14365 let state = controller
14366 .state
14367 .lock()
14368 .unwrap_or_else(std::sync::PoisonError::into_inner);
14369 !matches!(
14370 &state.lifecycle,
14371 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14372 )
14373 });
14374
14375 let (restart_tx, restart_rx) = mpsc::channel();
14376 let restart_controller = Arc::clone(&controller);
14377 let restart = std::thread::spawn(move || {
14378 let result = restart_controller
14379 .start()
14380 .map(|()| restart_controller.backend().generation());
14381 restart_tx.send(result).unwrap();
14382 });
14383
14384 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14385 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14386 let shutdown_overlapped = early_shutdown.is_some();
14387 let restart_overlapped = early_restart.is_some();
14388 release_dispatch_tx.send(()).unwrap();
14389 driver.join().unwrap();
14390
14391 let shutdown_result = early_shutdown.unwrap_or_else(|| {
14392 shutdown_rx
14393 .recv_timeout(Duration::from_secs(2))
14394 .expect("shutdown must finish after continuation publication retires")
14395 });
14396 shutdown_result.unwrap();
14397 let new_generation = early_restart.unwrap_or_else(|| {
14398 restart_rx
14399 .recv_timeout(Duration::from_secs(2))
14400 .expect("restart must follow continuation publication and shutdown")
14401 });
14402 let new_generation = new_generation.unwrap();
14403 shutdown.join().unwrap();
14404 restart.join().unwrap();
14405
14406 assert!(
14407 !shutdown_overlapped,
14408 "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
14409 );
14410 assert!(
14411 !restart_overlapped,
14412 "restart must not overlap an old-generation continuation dispatch"
14413 );
14414 assert_ne!(new_generation, old_generation);
14415 controller.shutdown().unwrap();
14416 }
14417
14418 #[cfg(napi_runtime_os_threads)]
14419 #[test]
14420 fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
14421 use std::sync::mpsc;
14422
14423 let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
14424 let old_generation = controller.backend().generation();
14425 let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
14426 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14427 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14428 let submit_controller = Arc::clone(&controller);
14429 let reentrant_controller = Arc::clone(&controller);
14430 let submitter = std::thread::spawn(move || {
14431 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14432 *slot.borrow_mut() = Some(Box::new(move || {
14433 reentrant_shutdown_tx
14434 .send(reentrant_controller.shutdown())
14435 .unwrap();
14436 dispatch_entered_tx.send(()).unwrap();
14437 release_dispatch_rx.recv().unwrap();
14438 }));
14439 });
14440 submit_controller
14441 .try_spawn_detached(std::future::pending::<()>())
14442 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
14443 });
14444
14445 let reentrant_shutdown = reentrant_shutdown_rx
14446 .recv_timeout(Duration::from_secs(2))
14447 .expect("dispatch publication must not self-deadlock on reentrant shutdown");
14448 dispatch_entered_rx
14449 .recv_timeout(Duration::from_secs(2))
14450 .expect("the initial host dispatch publication must reach the injected pause");
14451
14452 let (shutdown_tx, shutdown_rx) = mpsc::channel();
14453 let shutdown_controller = Arc::clone(&controller);
14454 let shutdown = std::thread::spawn(move || {
14455 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14456 });
14457 wait_until("CurrentThread shutdown to leave Running", || {
14458 let state = controller
14459 .state
14460 .lock()
14461 .unwrap_or_else(std::sync::PoisonError::into_inner);
14462 !matches!(
14463 &state.lifecycle,
14464 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14465 )
14466 });
14467
14468 let (restart_tx, restart_rx) = mpsc::channel();
14469 let restart_controller = Arc::clone(&controller);
14470 let restart = std::thread::spawn(move || {
14471 let result = restart_controller
14472 .start()
14473 .map(|()| restart_controller.backend().generation());
14474 restart_tx.send(result).unwrap();
14475 });
14476
14477 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14478 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14479 let shutdown_overlapped = early_shutdown.is_some();
14480 let restart_overlapped = early_restart.is_some();
14481 release_dispatch_tx.send(()).unwrap();
14482 submitter.join().unwrap();
14483
14484 let shutdown_result = early_shutdown.unwrap_or_else(|| {
14485 shutdown_rx
14486 .recv_timeout(Duration::from_secs(2))
14487 .expect("shutdown must finish after initial dispatch publication retires")
14488 });
14489 shutdown_result.unwrap();
14490 let new_generation = early_restart.unwrap_or_else(|| {
14491 restart_rx
14492 .recv_timeout(Duration::from_secs(2))
14493 .expect("restart must follow initial dispatch publication and shutdown")
14494 });
14495 let new_generation = new_generation.unwrap();
14496 shutdown.join().unwrap();
14497 restart.join().unwrap();
14498
14499 let reentrant_error = reentrant_shutdown
14500 .expect_err("dispatch publication must be generation-scoped scheduler work");
14501 assert!(
14502 reentrant_error
14503 .to_string()
14504 .contains("work running on that runtime")
14505 );
14506 assert!(
14507 !shutdown_overlapped,
14508 "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
14509 );
14510 assert!(
14511 !restart_overlapped,
14512 "restart must not overlap an old-generation host dispatch call"
14513 );
14514 assert_ne!(new_generation, old_generation);
14515 controller.shutdown().unwrap();
14516 }
14517
14518 #[cfg(panic = "unwind")]
14519 #[test]
14520 fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
14521 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14522 .lock()
14523 .unwrap()
14524 .clear();
14525 CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14526 let metrics = Arc::new(RuntimeMetrics::default());
14527 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14528 Arc::clone(&metrics),
14529 panic_once_current_thread_host_dispatch,
14530 ));
14531 let scheduler = Arc::clone(&executor);
14532 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14533 scheduler.schedule(runnable)
14534 });
14535
14536 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14537 assert!(
14538 publication.is_err(),
14539 "the injected host dispatch panic must unwind"
14540 );
14541 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14542 {
14543 let scheduler = executor
14544 .scheduler_idle_lock
14545 .lock()
14546 .unwrap_or_else(std::sync::PoisonError::into_inner);
14547 assert_eq!(scheduler.active_dispatch_calls, 0);
14548 assert!(scheduler.dispatch_publications.is_empty());
14549 }
14550
14551 executor.request_drain_if_queued();
14552
14553 assert_ne!(dispatch, 0);
14554 assert_eq!(
14555 *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14556 .lock()
14557 .unwrap(),
14558 vec![dispatch, dispatch],
14559 "a later host registration must retry the same pending capability"
14560 );
14561 {
14562 let scheduler = executor
14563 .scheduler_idle_lock
14564 .lock()
14565 .unwrap_or_else(std::sync::PoisonError::into_inner);
14566 assert_eq!(scheduler.active_dispatch_calls, 0);
14567 assert!(scheduler.dispatch_publications.is_empty());
14568 }
14569 executor.drive_host_turn_for_dispatch(dispatch);
14570 assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
14571 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14572 }
14573
14574 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14575 #[test]
14576 fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
14577 use std::sync::mpsc;
14578
14579 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14580 .lock()
14581 .unwrap()
14582 .clear();
14583 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14584 let metrics = Arc::new(RuntimeMetrics::default());
14585 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14586 Arc::clone(&metrics),
14587 panic_once_current_thread_armed_republish_host_dispatch,
14588 ));
14589 let scheduler = Arc::clone(&executor);
14590 let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
14591 scheduler.schedule(runnable)
14592 });
14593
14594 let (called_tx, called_rx) = mpsc::channel();
14595 let (release_tx, release_rx) = mpsc::channel();
14596 let publishing_executor = Arc::clone(&executor);
14597 let publication = std::thread::spawn(move || {
14598 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14599 *slot.borrow_mut() = Some(Box::new(move || {
14600 called_tx.send(()).unwrap();
14601 release_rx.recv().unwrap();
14602 }));
14603 });
14604 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14605 });
14606 called_rx
14607 .recv_timeout(Duration::from_secs(2))
14608 .expect("the first publication must pause before calling the host dispatcher");
14609
14610 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14611 executor.request_drain_if_queued();
14612 let publication_state = {
14613 let scheduler = executor
14614 .scheduler_idle_lock
14615 .lock()
14616 .unwrap_or_else(std::sync::PoisonError::into_inner);
14617 (
14618 scheduler.active_dispatch_calls,
14619 scheduler.dispatch_publications.len(),
14620 scheduler.dispatch_publications[0].dispatch,
14621 scheduler.dispatch_publications[0].republish_requested,
14622 )
14623 };
14624 release_tx.send(()).unwrap();
14625
14626 let publication = publication.join().unwrap();
14627 assert!(
14628 publication.is_err(),
14629 "the original host-dispatch panic must resume"
14630 );
14631 assert_ne!(dispatch, 0);
14632 assert_eq!(publication_state, (1, 1, dispatch, true));
14633 assert_eq!(
14634 *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14635 .lock()
14636 .unwrap(),
14637 vec![dispatch, dispatch],
14638 "the armed request must retry automatically before the original panic resumes"
14639 );
14640 {
14641 let scheduler = executor
14642 .scheduler_idle_lock
14643 .lock()
14644 .unwrap_or_else(std::sync::PoisonError::into_inner);
14645 assert_eq!(scheduler.active_dispatch_calls, 0);
14646 assert!(scheduler.dispatch_publications.is_empty());
14647 }
14648 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14649 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14650
14651 executor.drive_host_turn_for_dispatch(dispatch);
14652 assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
14653 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14654 }
14655
14656 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14657 #[test]
14658 fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
14659 use std::sync::mpsc;
14660
14661 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14662 .lock()
14663 .unwrap()
14664 .clear();
14665 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
14666 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14667 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14668 {
14669 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14670 .state
14671 .lock()
14672 .unwrap_or_else(std::sync::PoisonError::into_inner);
14673 assert!(state.entries.is_empty());
14674 assert!(state.dispatches.is_empty());
14675 }
14676 let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14677 .register(Arc::new(FailDuringAuthoritativePanicPublication));
14678 let metrics = Arc::new(RuntimeMetrics::default());
14679 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14680 Arc::clone(&metrics),
14681 dispatch_authoritative_panic_current_thread_tasks,
14682 &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
14683 ));
14684 let scheduler = Arc::clone(&executor);
14685 let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
14686 scheduler.schedule(runnable)
14687 });
14688
14689 let (called_tx, called_rx) = mpsc::channel();
14690 let (release_tx, release_rx) = mpsc::channel();
14691 let publishing_executor = Arc::clone(&executor);
14692 let publication = std::thread::spawn(move || {
14693 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14694 *slot.borrow_mut() = Some(Box::new(move || {
14695 called_tx.send(()).unwrap();
14696 release_rx.recv().unwrap();
14697 }));
14698 });
14699 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14700 });
14701 called_rx
14702 .recv_timeout(Duration::from_secs(2))
14703 .expect("the first publication must pause before raising its panic");
14704
14705 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14706 executor.request_drain_if_queued();
14707 release_tx.send(()).unwrap();
14708
14709 let payload = publication
14710 .join()
14711 .unwrap()
14712 .expect_err("the first host-dispatch panic must resume");
14713 assert!(
14714 payload.is::<FirstHostileCurrentThreadPanic>(),
14715 "the first panic must remain authoritative over nested recovery"
14716 );
14717 assert_eq!(
14718 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14719 1,
14720 "the later hostile payload must be discarded under containment"
14721 );
14722 assert_eq!(
14723 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14724 0,
14725 "the authoritative payload must remain owned by the resumed unwind"
14726 );
14727 discard_panic_payload(payload);
14728 assert_eq!(
14729 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14730 1
14731 );
14732
14733 let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14734 .lock()
14735 .unwrap()
14736 .clone();
14737 assert_eq!(dispatches.len(), 3);
14738 let replacement_dispatch = dispatches[2];
14739 assert_eq!(dispatches[0], failed_dispatch);
14740 assert_eq!(dispatches[1], failed_dispatch);
14741 assert_ne!(replacement_dispatch, failed_dispatch);
14742 assert_eq!(
14743 executor.dispatch_pending.load(Ordering::Acquire),
14744 replacement_dispatch
14745 );
14746 assert_eq!(
14747 executor.recovery_dispatch.load(Ordering::Acquire),
14748 replacement_dispatch
14749 );
14750 {
14751 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14752 .state
14753 .lock()
14754 .unwrap_or_else(std::sync::PoisonError::into_inner);
14755 assert!(
14756 !state.dispatches.contains_key(&failed_dispatch),
14757 "nested recovery unwind must retire the exact failed epoch"
14758 );
14759 assert!(!state.dispatches.contains_key(&replacement_dispatch));
14760 }
14761
14762 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
14763 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14764 let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14765 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14766 executor.request_drain_if_queued();
14767
14768 assert_eq!(
14769 *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14770 .lock()
14771 .unwrap(),
14772 vec![
14773 failed_dispatch,
14774 failed_dispatch,
14775 replacement_dispatch,
14776 replacement_dispatch
14777 ],
14778 "retry must retain the replacement reserved before the nested panic"
14779 );
14780 let delivery = healthy.deliveries()[0];
14781 assert_eq!(
14782 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
14783 Some(replacement_dispatch)
14784 );
14785 let mut host_turn = executor
14786 .try_admit_host_turn(replacement_dispatch)
14787 .expect("the retried replacement delivery must be admitted");
14788 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14789 host_turn.drive();
14790 assert!(
14791 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14792 .finish_delivery(delivery, false)
14793 .failed_dispatches
14794 .is_empty()
14795 );
14796 assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
14797 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14798
14799 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
14800 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14801 .state
14802 .lock()
14803 .unwrap_or_else(std::sync::PoisonError::into_inner);
14804 assert!(state.entries.is_empty());
14805 assert!(state.dispatches.is_empty());
14806 }
14807
14808 #[cfg(napi_runtime_os_threads)]
14809 #[test]
14810 fn current_thread_shutdown_suppresses_queued_republish() {
14811 use std::sync::mpsc;
14812
14813 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14814 let metrics = Arc::new(RuntimeMetrics::default());
14815 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14816 Arc::clone(&metrics),
14817 count_current_thread_shutdown_republish_host_dispatch,
14818 ));
14819 let scheduler = Arc::clone(&executor);
14820 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14821 scheduler.schedule(runnable)
14822 });
14823
14824 let (returned_tx, returned_rx) = mpsc::channel();
14825 let (release_tx, release_rx) = mpsc::channel();
14826 let publishing_executor = Arc::clone(&executor);
14827 let publication = std::thread::spawn(move || {
14828 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14829 *slot.borrow_mut() = Some(Box::new(move || {
14830 returned_tx.send(()).unwrap();
14831 release_rx.recv().unwrap();
14832 }));
14833 });
14834 publishing_executor.schedule(runnable);
14835 });
14836 returned_rx
14837 .recv_timeout(Duration::from_secs(2))
14838 .expect("the initial publication must reach its completion barrier");
14839
14840 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14841 executor.request_drain_if_queued();
14842 let publication_state = {
14843 let scheduler = executor
14844 .scheduler_idle_lock
14845 .lock()
14846 .unwrap_or_else(std::sync::PoisonError::into_inner);
14847 (
14848 scheduler.active_dispatch_calls,
14849 scheduler.dispatch_publications.len(),
14850 scheduler.dispatch_publications[0].dispatch,
14851 scheduler.dispatch_publications[0].republish_requested,
14852 )
14853 };
14854 executor.begin_shutdown();
14855 release_tx.send(()).unwrap();
14856 publication.join().unwrap();
14857 executor.wait_until_scheduler_idle();
14858
14859 assert_ne!(dispatch, 0);
14860 assert_eq!(publication_state, (1, 1, dispatch, true));
14861 assert_eq!(
14862 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
14863 1
14864 );
14865 {
14866 let scheduler = executor
14867 .scheduler_idle_lock
14868 .lock()
14869 .unwrap_or_else(std::sync::PoisonError::into_inner);
14870 assert_eq!(scheduler.active_dispatch_calls, 0);
14871 assert!(scheduler.dispatch_publications.is_empty());
14872 }
14873 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14874 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14875 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14876 assert_eq!(futures::executor::block_on(task.fallible()), None);
14877 }
14878
14879 #[cfg(napi_runtime_os_threads)]
14880 #[test]
14881 fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
14882 use std::sync::mpsc;
14883
14884 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
14885 .lock()
14886 .unwrap()
14887 .clear();
14888 let metrics = Arc::new(RuntimeMetrics::default());
14889 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14890 Arc::clone(&metrics),
14891 record_current_thread_republish_rearm_dispatch,
14892 ));
14893 let scheduler = Arc::clone(&executor);
14894 let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
14895 scheduler.schedule(runnable)
14896 });
14897
14898 let (first_returned_tx, first_returned_rx) = mpsc::channel();
14899 let (release_first_tx, release_first_rx) = mpsc::channel();
14900 let (second_returned_tx, second_returned_rx) = mpsc::channel();
14901 let (release_second_tx, release_second_rx) = mpsc::channel();
14902 let publishing_executor = Arc::clone(&executor);
14903 let publication = std::thread::spawn(move || {
14904 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14905 *slot.borrow_mut() = Some(Box::new(move || {
14906 first_returned_tx.send(()).unwrap();
14907 release_first_rx.recv().unwrap();
14908 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14909 *slot.borrow_mut() = Some(Box::new(move || {
14910 second_returned_tx.send(()).unwrap();
14911 release_second_rx.recv().unwrap();
14912 }));
14913 });
14914 }));
14915 });
14916 publishing_executor.schedule(runnable);
14917 });
14918 first_returned_rx
14919 .recv_timeout(Duration::from_secs(2))
14920 .expect("the first publication must reach its completion barrier");
14921
14922 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14923 executor.request_drain_if_queued();
14924 executor.request_drain_if_queued();
14925 let first_publication_state = {
14926 let scheduler = executor
14927 .scheduler_idle_lock
14928 .lock()
14929 .unwrap_or_else(std::sync::PoisonError::into_inner);
14930 (
14931 scheduler.active_dispatch_calls,
14932 scheduler.dispatch_publications.len(),
14933 scheduler.dispatch_publications[0].dispatch,
14934 scheduler.dispatch_publications[0].republish_requested,
14935 )
14936 };
14937 release_first_tx.send(()).unwrap();
14938 second_returned_rx
14939 .recv_timeout(Duration::from_secs(2))
14940 .expect("the second publication must reach its completion barrier");
14941
14942 executor.request_drain_if_queued();
14943 executor.request_drain_if_queued();
14944 let second_publication_state = {
14945 let scheduler = executor
14946 .scheduler_idle_lock
14947 .lock()
14948 .unwrap_or_else(std::sync::PoisonError::into_inner);
14949 (
14950 scheduler.active_dispatch_calls,
14951 scheduler.dispatch_publications.len(),
14952 scheduler.dispatch_publications[0].dispatch,
14953 scheduler.dispatch_publications[0].republish_requested,
14954 )
14955 };
14956 release_second_tx.send(()).unwrap();
14957 publication.join().unwrap();
14958
14959 assert_ne!(dispatch, 0);
14960 assert_eq!(first_publication_state, (1, 1, dispatch, true));
14961 assert_eq!(second_publication_state, (1, 1, dispatch, true));
14962 assert_eq!(
14963 *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
14964 vec![dispatch, dispatch, dispatch],
14965 "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
14966 );
14967 {
14968 let scheduler = executor
14969 .scheduler_idle_lock
14970 .lock()
14971 .unwrap_or_else(std::sync::PoisonError::into_inner);
14972 assert_eq!(scheduler.active_dispatch_calls, 0);
14973 assert!(scheduler.dispatch_publications.is_empty());
14974 }
14975 executor.drive_host_turn_for_dispatch(dispatch);
14976 assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
14977 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14978 }
14979
14980 #[cfg(napi_runtime_os_threads)]
14981 #[test]
14982 fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
14983 use std::sync::mpsc;
14984
14985 CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14986 .lock()
14987 .unwrap()
14988 .clear();
14989 let metrics = Arc::new(RuntimeMetrics::default());
14990 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14991 Arc::clone(&metrics),
14992 reject_initial_current_thread_publication_once,
14993 ));
14994 let scheduler = Arc::clone(&executor);
14995 let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14996 scheduler.schedule(runnable)
14997 });
14998
14999 let (returned_tx, returned_rx) = mpsc::channel();
15000 let (release_tx, release_rx) = mpsc::channel();
15001 let publishing_executor = Arc::clone(&executor);
15002 let publication = std::thread::spawn(move || {
15003 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15004 *slot.borrow_mut() = Some(Box::new(move || {
15005 returned_tx.send(()).unwrap();
15006 release_rx.recv().unwrap();
15007 }));
15008 });
15009 publishing_executor.schedule(runnable);
15010 });
15011 returned_rx
15012 .recv_timeout(Duration::from_secs(2))
15013 .expect("the unavailable initial publication must reach its completion barrier");
15014
15015 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15016 executor.request_drain_if_queued();
15017 let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
15018 .lock()
15019 .unwrap()
15020 .len();
15021
15022 release_tx.send(()).unwrap();
15023 publication.join().unwrap();
15024
15025 let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
15026 .lock()
15027 .unwrap()
15028 .clone();
15029 assert_ne!(dispatch, 0);
15030 assert_eq!(
15031 attempts_while_unavailable_is_paused, 1,
15032 "a racing host request must join the in-flight publication instead of retiring it independently"
15033 );
15034 assert_eq!(
15035 dispatches,
15036 vec![dispatch, dispatch],
15037 "the publication owner must retry the same internal capability after the new host joins"
15038 );
15039 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
15040 executor.drive_host_turn_for_dispatch(dispatch);
15041 assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
15042 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15043 }
15044
15045 #[cfg(napi_runtime_os_threads)]
15046 #[test]
15047 fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
15048 use std::sync::mpsc;
15049
15050 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15051 .lock()
15052 .unwrap()
15053 .clear();
15054 let metrics = Arc::new(RuntimeMetrics::default());
15055 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15056 Arc::clone(&metrics),
15057 reject_current_thread_replacement_publication_once,
15058 ));
15059 let scheduler = Arc::clone(&executor);
15060 let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
15061 scheduler.schedule(runnable)
15062 });
15063 executor.schedule(runnable);
15064 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15065 assert_ne!(failed_dispatch, 0);
15066
15067 let (returned_tx, returned_rx) = mpsc::channel();
15068 let (release_tx, release_rx) = mpsc::channel();
15069 let cancelling_executor = Arc::clone(&executor);
15070 let cancellation = std::thread::spawn(move || {
15071 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15072 *slot.borrow_mut() = Some(Box::new(move || {
15073 returned_tx.send(()).unwrap();
15074 release_rx.recv().unwrap();
15075 }));
15076 });
15077 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
15078 });
15079 returned_rx
15080 .recv_timeout(Duration::from_secs(2))
15081 .expect("the unavailable replacement publication must reach its completion barrier");
15082
15083 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15084 executor.request_drain_if_queued();
15085 let attempts_while_unavailable_is_paused =
15086 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15087 .lock()
15088 .unwrap()
15089 .len();
15090
15091 release_tx.send(()).unwrap();
15092 cancellation.join().unwrap();
15093
15094 let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15095 .lock()
15096 .unwrap()
15097 .clone();
15098 assert_ne!(replacement_dispatch, 0);
15099 assert_ne!(replacement_dispatch, failed_dispatch);
15100 assert_eq!(
15101 attempts_while_unavailable_is_paused, 2,
15102 "a racing host request must join the in-flight replacement publication"
15103 );
15104 assert_eq!(
15105 dispatches,
15106 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15107 "republication must reuse the sole replacement capability instead of allocating another"
15108 );
15109 assert_eq!(
15110 executor.dispatch_pending.load(Ordering::Acquire),
15111 replacement_dispatch
15112 );
15113 assert_eq!(
15114 executor.recovery_dispatch.load(Ordering::Acquire),
15115 replacement_dispatch
15116 );
15117 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15118
15119 executor.drive_host_turn_for_dispatch(replacement_dispatch);
15120 assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
15121 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15122 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15123 }
15124
15125 #[cfg(napi_runtime_os_threads)]
15126 #[test]
15127 fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
15128 use std::sync::mpsc;
15129
15130 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15131 .lock()
15132 .unwrap()
15133 .clear();
15134 {
15135 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15136 .state
15137 .lock()
15138 .unwrap_or_else(std::sync::PoisonError::into_inner);
15139 assert!(state.entries.is_empty());
15140 assert!(state.dispatches.is_empty());
15141 }
15142 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15143 let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15144 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15145 let metrics = Arc::new(RuntimeMetrics::default());
15146 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15147 Arc::clone(&metrics),
15148 fail_current_thread_replacement_publication_once,
15149 &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
15150 ));
15151 let scheduler = Arc::clone(&executor);
15152 let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
15153 scheduler.schedule(runnable)
15154 });
15155 executor.schedule(runnable);
15156 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15157 assert_ne!(failed_dispatch, 0);
15158 let initial_delivery = initial.deliveries()[0];
15159 let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15160 .finish_delivery(initial_delivery, true);
15161 let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
15162 panic!("the initial accepted delivery must report one exact failure");
15163 };
15164 assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
15165 let failed_dispatch_failure = *failed_dispatch_failure;
15166 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
15167 let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15168 .register(Arc::new(FailDuringReplacementRepublishPublication));
15169
15170 let (returned_tx, returned_rx) = mpsc::channel();
15171 let (release_tx, release_rx) = mpsc::channel();
15172 let cancelling_executor = Arc::clone(&executor);
15173 let cancellation = std::thread::spawn(move || {
15174 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15175 *slot.borrow_mut() = Some(Box::new(move || {
15176 returned_tx.send(()).unwrap();
15177 release_rx.recv().unwrap();
15178 }));
15179 });
15180 cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
15181 });
15182 returned_rx
15183 .recv_timeout(Duration::from_secs(2))
15184 .expect("the failed replacement publication must reach its completion barrier");
15185
15186 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15187 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15188 let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15189 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15190 executor.request_drain_if_queued();
15191 let publication_state = {
15192 let scheduler = executor
15193 .scheduler_idle_lock
15194 .lock()
15195 .unwrap_or_else(std::sync::PoisonError::into_inner);
15196 (
15197 scheduler.active_dispatch_calls,
15198 scheduler.dispatch_publications.len(),
15199 scheduler.dispatch_publications[0].dispatch,
15200 scheduler.dispatch_publications[0].republish_requested,
15201 )
15202 };
15203 release_tx.send(()).unwrap();
15204 cancellation.join().unwrap();
15205
15206 assert_ne!(replacement_dispatch, 0);
15207 assert_ne!(replacement_dispatch, failed_dispatch);
15208 assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
15209 assert_eq!(
15210 *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15211 .lock()
15212 .unwrap(),
15213 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15214 "the newly armed host request must receive the exact replacement before cancellation"
15215 );
15216 assert_eq!(
15217 executor.dispatch_pending.load(Ordering::Acquire),
15218 replacement_dispatch
15219 );
15220 assert_eq!(
15221 executor.recovery_dispatch.load(Ordering::Acquire),
15222 replacement_dispatch
15223 );
15224 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15225
15226 let delivery = healthy.deliveries()[0];
15227 assert_eq!(delivery.dispatch, replacement_dispatch);
15228 assert_eq!(
15229 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15230 .claim_delivery(delivery.capability()),
15231 Some(replacement_dispatch)
15232 );
15233 let mut host_turn = executor
15234 .try_admit_host_turn(replacement_dispatch)
15235 .expect("the rearmed replacement delivery must be admitted");
15236 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15237 host_turn.drive();
15238 assert!(
15239 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15240 .finish_delivery(delivery, false)
15241 .failed_dispatches
15242 .is_empty()
15243 );
15244 assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
15245 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15246 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15247
15248 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
15249 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
15250 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15251 .state
15252 .lock()
15253 .unwrap_or_else(std::sync::PoisonError::into_inner);
15254 assert!(state.entries.is_empty());
15255 assert!(state.dispatches.is_empty());
15256 }
15257
15258 #[cfg(napi_runtime_os_threads)]
15259 #[test]
15260 fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
15261 use std::sync::mpsc;
15262
15263 {
15264 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15265 .state
15266 .lock()
15267 .unwrap_or_else(std::sync::PoisonError::into_inner);
15268 assert!(state.entries.is_empty());
15269 assert!(state.dispatches.is_empty());
15270 }
15271 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15272 let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15273 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15274 let metrics = Arc::new(RuntimeMetrics::default());
15275 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15276 Arc::clone(&metrics),
15277 dispatch_stale_failure_current_thread_tasks,
15278 &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
15279 ));
15280 let scheduler = Arc::clone(&executor);
15281 let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
15282 scheduler.schedule(runnable)
15283 });
15284 executor.schedule(runnable);
15285
15286 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15287 let initial_delivery = initial.deliveries()[0];
15288 let delayed_completion =
15289 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
15290 let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
15291 panic!("the initial accepted delivery must report one exact failure");
15292 };
15293 assert_eq!(
15294 *delayed_failure,
15295 current_thread_dispatch_failure(dispatch, 0)
15296 );
15297 let delayed_failure = *delayed_failure;
15298
15299 let (returned_tx, returned_rx) = mpsc::channel();
15300 let (release_tx, release_rx) = mpsc::channel();
15301 let publishing_executor = Arc::clone(&executor);
15302 let publication = std::thread::spawn(move || {
15303 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15304 *slot.borrow_mut() = Some(Box::new(move || {
15305 returned_tx.send(()).unwrap();
15306 release_rx.recv().unwrap();
15307 }));
15308 });
15309 publishing_executor.request_drain_if_queued();
15310 });
15311 returned_rx
15312 .recv_timeout(Duration::from_secs(2))
15313 .expect("the old failed epoch must pause before executor recovery");
15314
15315 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15316 let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15317 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15318 executor.request_drain_if_queued();
15319 release_tx.send(()).unwrap();
15320 publication.join().unwrap();
15321
15322 let healthy_delivery = healthy.deliveries()[0];
15323 let delayed_initial_delivery = initial.deliveries()[1];
15324 {
15325 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15326 .state
15327 .lock()
15328 .unwrap_or_else(std::sync::PoisonError::into_inner);
15329 let dispatch_state = state
15330 .dispatches
15331 .get(&dispatch)
15332 .expect("the rearmed dispatch must remain registered");
15333 assert_eq!(dispatch_state.epoch, 1);
15334 assert!(!dispatch_state.is_cancelling());
15335 }
15336
15337 executor.cancel_host_dispatch(delayed_failure);
15338
15339 assert_eq!(
15340 executor.dispatch_pending.load(Ordering::Acquire),
15341 dispatch,
15342 "the delayed old-epoch failure must not consume the rearmed executor capability"
15343 );
15344 assert_eq!(
15345 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15346 .state
15347 .lock()
15348 .unwrap_or_else(std::sync::PoisonError::into_inner)
15349 .dispatches
15350 .get(&dispatch)
15351 .map(|state| (state.epoch, state.phase)),
15352 Some((1, CurrentThreadDispatchPhase::Pending)),
15353 "the delayed old-epoch cancellation must not remove the accepted rearm"
15354 );
15355
15356 assert_eq!(
15357 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
15358 Some(dispatch)
15359 );
15360 let mut host_turn = executor
15361 .try_admit_host_turn(dispatch)
15362 .expect("the current-epoch delivery must be admitted");
15363 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15364 host_turn.drive();
15365 assert!(
15366 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15367 .finish_delivery(healthy_delivery, false)
15368 .failed_dispatches
15369 .is_empty()
15370 );
15371 assert!(
15372 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15373 .finish_delivery(delayed_initial_delivery, false)
15374 .failed_dispatches
15375 .is_empty()
15376 );
15377 assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
15378 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15379
15380 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
15381 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
15382 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15383 .state
15384 .lock()
15385 .unwrap_or_else(std::sync::PoisonError::into_inner);
15386 assert!(state.entries.is_empty());
15387 assert!(state.dispatches.is_empty());
15388 }
15389
15390 #[cfg(napi_runtime_os_threads)]
15391 #[test]
15392 fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
15393 use std::sync::mpsc;
15394
15395 {
15396 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15397 .state
15398 .lock()
15399 .unwrap_or_else(std::sync::PoisonError::into_inner);
15400 assert!(state.entries.is_empty());
15401 assert!(state.dispatches.is_empty());
15402 }
15403 let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15404 .register(Arc::new(FailDuringLateRegistrationPublication));
15405
15406 let metrics = Arc::new(RuntimeMetrics::default());
15407 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15408 Arc::clone(&metrics),
15409 dispatch_late_registration_current_thread_tasks,
15410 &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
15411 ));
15412 let scheduler = Arc::clone(&executor);
15413 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15414 scheduler.schedule(runnable)
15415 });
15416 metrics.runnable_scheduled();
15417 executor
15418 .queue
15419 .lock()
15420 .unwrap_or_else(std::sync::PoisonError::into_inner)
15421 .runnables
15422 .push_back(runnable);
15423 let replacement_dispatch = next_current_thread_dispatch_id();
15424 executor
15425 .recovery_dispatch
15426 .store(replacement_dispatch, Ordering::Release);
15427 executor
15428 .dispatch_pending
15429 .store(replacement_dispatch, Ordering::Release);
15430
15431 let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
15432 let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
15433 let publishing_executor = Arc::clone(&executor);
15434 let publication = std::thread::spawn(move || {
15435 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15436 *slot.borrow_mut() = Some(Box::new(move || {
15437 owner_retired_tx.send(()).unwrap();
15438 request_blocked_rx
15439 .recv_timeout(Duration::from_secs(2))
15440 .expect("the binding-like drain request must wait for the failure transition");
15441 }));
15442 });
15443 publishing_executor.request_drain_if_queued();
15444 });
15445 owner_retired_rx
15446 .recv_timeout(Duration::from_secs(2))
15447 .expect("the failed replacement publication must retire its owner");
15448
15449 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15452 let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15453 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15454 let requesting_executor = Arc::clone(&executor);
15455 let request = std::thread::spawn(move || {
15456 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
15457 *slot.borrow_mut() = Some(Box::new(move || {
15458 request_blocked_tx.send(()).unwrap();
15459 }));
15460 });
15461 requesting_executor.request_drain_if_queued();
15462 });
15463
15464 publication.join().unwrap();
15465 request.join().unwrap();
15466
15467 assert!(
15468 healthy.deliveries().is_empty(),
15469 "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
15470 );
15471 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15472 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15473 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15474 assert_eq!(futures::executor::block_on(task.fallible()), None);
15475 {
15476 let scheduler = executor
15477 .scheduler_idle_lock
15478 .lock()
15479 .unwrap_or_else(std::sync::PoisonError::into_inner);
15480 assert_eq!(scheduler.active_dispatch_calls, 0);
15481 assert!(scheduler.dispatch_publications.is_empty());
15482 }
15483
15484 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
15485
15486 let scheduler = Arc::clone(&executor);
15487 let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
15488 scheduler.schedule(runnable)
15489 });
15490 executor.schedule(later_runnable);
15491 let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15492 let later_delivery = healthy.deliveries()[0];
15493 assert_eq!(
15494 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
15495 Some(later_dispatch)
15496 );
15497 let mut host_turn = executor
15498 .try_admit_host_turn(later_dispatch)
15499 .expect("the later exact delivery must be admitted");
15500 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
15501 host_turn.drive();
15502 assert!(
15503 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15504 .finish_delivery(later_delivery, false)
15505 .failed_dispatches
15506 .is_empty()
15507 );
15508 assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
15509 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15510
15511 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
15512 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15513 .state
15514 .lock()
15515 .unwrap_or_else(std::sync::PoisonError::into_inner);
15516 assert!(state.entries.is_empty());
15517 assert!(state.dispatches.is_empty());
15518 }
15519
15520 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
15521 #[test]
15522 fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
15523 {
15524 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15525 .lock()
15526 .unwrap()
15527 .clear();
15528 {
15529 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15530 .state
15531 .lock()
15532 .unwrap_or_else(std::sync::PoisonError::into_inner);
15533 assert!(state.entries.is_empty());
15534 assert!(state.dispatches.is_empty());
15535 }
15536 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15537 .next_delivery
15538 .store(0, Ordering::Relaxed);
15539 let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15540 .register(Arc::new(FailDuringNestedUnwindPublication));
15541
15542 let metrics = Arc::new(RuntimeMetrics::default());
15543 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15544 Arc::clone(&metrics),
15545 dispatch_nested_unwind_current_thread_tasks,
15546 &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
15547 ));
15548 let scheduler = Arc::clone(&executor);
15549 let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
15550 scheduler.schedule(runnable)
15551 });
15552
15553 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15554 *slot.borrow_mut() = Some(Box::new(|| {
15555 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15556 .next_delivery
15557 .store(u64::MAX, Ordering::Relaxed);
15558 }));
15559 });
15560 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
15561 assert!(
15562 publication.is_err(),
15563 "replacement delivery-id exhaustion must unwind through the outer failed publication"
15564 );
15565
15566 let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15567 .lock()
15568 .unwrap()
15569 .clone();
15570 assert_eq!(dispatches.len(), 2);
15571 let failed_dispatch = dispatches[0];
15572 let replacement_dispatch = dispatches[1];
15573 assert_ne!(failed_dispatch, replacement_dispatch);
15574 assert_eq!(
15575 executor.dispatch_pending.load(Ordering::Acquire),
15576 replacement_dispatch
15577 );
15578 assert_eq!(
15579 executor.recovery_dispatch.load(Ordering::Acquire),
15580 replacement_dispatch
15581 );
15582 {
15583 let scheduler = executor
15584 .scheduler_idle_lock
15585 .lock()
15586 .unwrap_or_else(std::sync::PoisonError::into_inner);
15587 assert_eq!(scheduler.active_dispatch_calls, 0);
15588 assert!(scheduler.dispatch_publications.is_empty());
15589 }
15590 {
15591 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15592 .state
15593 .lock()
15594 .unwrap_or_else(std::sync::PoisonError::into_inner);
15595 assert!(
15596 !state.dispatches.contains_key(&failed_dispatch),
15597 "outer unwind cleanup must retire the exact old cancelling dispatch"
15598 );
15599 assert!(
15600 !state.dispatches.contains_key(&replacement_dispatch),
15601 "the replacement publication guard must release its aborted registry state"
15602 );
15603 }
15604
15605 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15606 .next_delivery
15607 .store(1, Ordering::Relaxed);
15608 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
15609 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15610 let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15611 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15612
15613 executor.request_drain_if_queued();
15614
15615 assert_eq!(
15616 *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
15617 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15618 "retry must reuse the replacement reserved before the nested unwind"
15619 );
15620 let delivery = healthy.deliveries()[0];
15621 assert_eq!(delivery.dispatch, replacement_dispatch);
15622 assert_eq!(
15623 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
15624 Some(replacement_dispatch)
15625 );
15626 let mut host_turn = executor
15627 .try_admit_host_turn(replacement_dispatch)
15628 .expect("the retried replacement delivery must be admitted");
15629 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15630 host_turn.drive();
15631 assert!(
15632 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15633 .finish_delivery(delivery, false)
15634 .failed_dispatches
15635 .is_empty()
15636 );
15637 assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
15638 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15639
15640 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
15641 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15642 .state
15643 .lock()
15644 .unwrap_or_else(std::sync::PoisonError::into_inner);
15645 assert!(state.entries.is_empty());
15646 assert!(state.dispatches.is_empty());
15647 }
15648
15649 #[test]
15650 fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
15651 reset_current_thread_recovery_host_dispatches();
15652 let metrics = Arc::new(RuntimeMetrics::default());
15653 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15654 Arc::clone(&metrics),
15655 count_current_thread_recovery_host_dispatch,
15656 ));
15657
15658 let scheduler = Arc::clone(&executor);
15659 let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
15660 scheduler.schedule(runnable)
15661 });
15662 executor.schedule(runnable);
15663 assert_eq!(current_thread_recovery_host_dispatches(), 1);
15664 let failed_dispatch = current_thread_dispatch(&executor);
15665
15666 executor.cancel_host_dispatch_for_test(failed_dispatch);
15669 assert_eq!(
15670 current_thread_recovery_host_dispatches(),
15671 2,
15672 "cancellation must request one replacement without another submission"
15673 );
15674 let replacement_dispatch = current_thread_dispatch(&executor);
15675 assert_ne!(replacement_dispatch, failed_dispatch);
15676 assert_eq!(
15677 executor.recovery_dispatch.load(Ordering::Acquire),
15678 replacement_dispatch
15679 );
15680
15681 executor.drive_host_turn_for_dispatch(replacement_dispatch);
15682 assert_eq!(futures::executor::block_on(task), 1);
15683 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15684 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15685 }
15686
15687 #[test]
15688 fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
15689 reset_current_thread_recovery_host_dispatches();
15690 let metrics = Arc::new(RuntimeMetrics::default());
15691 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15692 Arc::clone(&metrics),
15693 count_current_thread_recovery_host_dispatch,
15694 ));
15695 let backend =
15696 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15697
15698 let registration = backend
15699 .work
15700 .try_register_async()
15701 .expect("the backend must accept work");
15702 let task = spawn_registered(
15703 &backend,
15704 Arc::clone(&metrics),
15705 std::future::pending::<()>(),
15706 registration,
15707 );
15708 let failed_dispatch = current_thread_dispatch(&executor);
15709 executor.cancel_host_dispatch_for_test(failed_dispatch);
15710 let replacement_dispatch = current_thread_dispatch(&executor);
15711
15712 executor.cancel_host_dispatch_for_test(replacement_dispatch);
15713
15714 assert_eq!(
15715 current_thread_recovery_host_dispatches(),
15716 2,
15717 "a second scheduler failure must not create an unbounded retry loop"
15718 );
15719 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15720 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15721 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15722 assert_eq!(
15723 futures::executor::block_on(task).unwrap_err().to_string(),
15724 "async runtime stopped before the task completed",
15725 "terminal host failure must settle the original operation as cancelled"
15726 );
15727 assert_eq!(backend.work.state.lock().unwrap().active, 0);
15728
15729 let later_registration = backend
15730 .work
15731 .try_register_async()
15732 .expect("terminal cancellation must reopen acceptance");
15733 let later_task = spawn_registered(
15734 &backend,
15735 Arc::clone(&metrics),
15736 async { 2usize },
15737 later_registration,
15738 );
15739 assert_eq!(
15740 current_thread_recovery_host_dispatches(),
15741 3,
15742 "terminal cancellation must not permanently close the executor"
15743 );
15744 executor.drive_host_turn();
15745 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
15746 }
15747
15748 #[cfg(napi_runtime_os_threads)]
15757 #[test]
15758 fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
15759 use std::{sync::mpsc, time::Duration};
15760
15761 let (done_tx, done_rx) = mpsc::channel();
15762 std::thread::spawn(move || {
15763 let metrics = Arc::new(RuntimeMetrics::default());
15764 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15765 Arc::clone(&metrics),
15766 accept_current_thread_host_dispatch,
15767 ));
15768
15769 let scheduler_a = Arc::clone(&executor);
15771 let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15772 scheduler_a.schedule(runnable)
15773 });
15774 let shared = async move { task_a.fallible().await }.boxed().shared();
15775 let shared_for_b = shared.clone();
15776
15777 let scheduler_b = Arc::clone(&executor);
15779 let (runnable_b, task_b) = async_task::spawn(
15780 async move {
15781 shared_for_b.await;
15782 },
15783 move |runnable| scheduler_b.schedule(runnable),
15784 );
15785 executor.schedule(runnable_b);
15786 executor.drive_host_turn();
15787 assert_eq!(
15788 metrics.queued_runnables.load(Ordering::Relaxed),
15789 0,
15790 "B must be parked and unscheduled after its first poll"
15791 );
15792
15793 executor.schedule(runnable_a);
15796 let failed_dispatch = current_thread_dispatch(&executor);
15797 executor.cancel_host_dispatch_for_test(failed_dispatch);
15798 let replacement_dispatch = current_thread_dispatch(&executor);
15799 executor.cancel_host_dispatch_for_test(replacement_dispatch);
15800
15801 assert!(
15802 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15803 "the cancellation sweep must reopen the executor"
15804 );
15805 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15806 assert!(
15807 futures::executor::block_on(task_b.fallible()).is_none(),
15808 "B's rejected wake must still cancel B"
15809 );
15810 drop(shared);
15811 done_tx.send(()).unwrap();
15812 });
15813 done_rx
15814 .recv_timeout(Duration::from_secs(5))
15815 .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
15816 }
15817
15818 #[cfg(napi_runtime_os_threads)]
15828 #[test]
15829 fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
15830 use std::{
15831 sync::{Barrier, mpsc},
15832 time::Duration,
15833 };
15834
15835 let (done_tx, done_rx) = mpsc::channel();
15836 std::thread::spawn(move || {
15837 let metrics = Arc::new(RuntimeMetrics::default());
15838 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15839 Arc::clone(&metrics),
15840 accept_current_thread_host_dispatch,
15841 ));
15842 let backend =
15843 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15844 let generation = backend.generation();
15845
15846 let (abort_a, guard_a) = backend
15848 .work
15849 .try_register_async()
15850 .expect("the backend must accept work");
15851 let scheduler_a = Arc::clone(&executor);
15852 let (runnable_a, task_a) = async_task::spawn(
15853 RegisteredTaskFuture::new(
15854 Abortable::new(std::future::pending::<()>(), abort_a),
15855 guard_a,
15856 generation,
15857 ),
15858 move |runnable| scheduler_a.schedule(runnable),
15859 );
15860 let shared = async move { task_a.fallible().await.is_some() }
15861 .boxed()
15862 .shared();
15863 let shared_for_b = shared.clone();
15864
15865 let (abort_b, guard_b) = backend
15869 .work
15870 .try_register_async()
15871 .expect("the backend must accept work");
15872 let wake_in_flight = Arc::new(Barrier::new(2));
15873 let wake_release = Arc::new(Barrier::new(2));
15874 let scheduler_b = Arc::clone(&executor);
15875 let (runnable_b, task_b) = async_task::spawn(
15876 RegisteredTaskFuture::new(
15877 Abortable::new(
15878 async move {
15879 shared_for_b.await;
15880 },
15881 abort_b,
15882 ),
15883 guard_b,
15884 generation,
15885 ),
15886 {
15887 let wake_in_flight = Arc::clone(&wake_in_flight);
15888 let wake_release = Arc::clone(&wake_release);
15889 move |runnable| {
15890 wake_in_flight.wait();
15891 wake_release.wait();
15892 scheduler_b.schedule(runnable);
15893 }
15894 },
15895 );
15896 executor.schedule(runnable_b);
15897 executor.drive_host_turn();
15898 assert_eq!(
15899 metrics.queued_runnables.load(Ordering::Relaxed),
15900 0,
15901 "B must be parked and unscheduled after its first poll"
15902 );
15903
15904 let waker_thread = std::thread::spawn(move || drop(runnable_a));
15908 wake_in_flight.wait();
15909
15910 backend.begin_shutdown();
15913 wake_release.wait();
15914 backend.wait_until_idle();
15915
15916 waker_thread
15917 .join()
15918 .expect("the waker thread must not panic");
15919 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15920 assert!(
15921 futures::executor::block_on(task_b.fallible()).is_none(),
15922 "B's rejected wake must still cancel B"
15923 );
15924 assert_eq!(backend.work.state.lock().unwrap().active, 0);
15925 drop(shared);
15926 done_tx.send(()).unwrap();
15927 });
15928 done_rx
15929 .recv_timeout(Duration::from_secs(5))
15930 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
15931 }
15932
15933 #[cfg(napi_runtime_os_threads)]
15943 #[test]
15944 fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
15945 use std::{sync::mpsc, time::Duration};
15946
15947 let (done_tx, done_rx) = mpsc::channel();
15948 std::thread::spawn(move || {
15949 {
15950 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15951 .state
15952 .lock()
15953 .unwrap_or_else(std::sync::PoisonError::into_inner);
15954 assert!(state.entries.is_empty());
15955 assert!(state.dispatches.is_empty());
15956 }
15957 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
15958 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15959 ));
15960 let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15961 .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15962 let metrics = Arc::new(RuntimeMetrics::default());
15963 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15964 Arc::clone(&metrics),
15965 dispatch_waker_stack_failure_current_thread_tasks,
15966 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15967 ));
15968
15969 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15971 let shared = rx.map(|_| ()).boxed().shared();
15972 let shared_for_b = shared.clone();
15973 let scheduler_b = Arc::clone(&executor);
15974 let (runnable_b, task_b) = async_task::spawn(
15975 async move {
15976 shared_for_b.await;
15977 },
15978 move |runnable| scheduler_b.schedule(runnable),
15979 );
15980 executor.schedule(runnable_b);
15981
15982 let delivery = driver.deliveries()[0];
15985 let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15986 .claim_delivery(delivery.capability())
15987 .expect("the accepted delivery must be claimable");
15988 let mut host_turn = executor
15989 .try_admit_host_turn(dispatch)
15990 .expect("the claimed delivery must be admitted");
15991 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15992 host_turn.drive();
15993 drop(host_turn);
15994 let completion =
15995 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15996 assert!(completion.failed_dispatches.is_empty());
15997 assert!(!completion.redispatch);
15998 assert_eq!(
15999 metrics.queued_runnables.load(Ordering::Relaxed),
16000 0,
16001 "B must be parked and unscheduled after its first poll"
16002 );
16003
16004 driver.fail.store(true, Ordering::SeqCst);
16006 tx.send(()).expect("B must still hold its Shared clone");
16010 assert_eq!(
16011 driver.deliveries().len(),
16012 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
16016 "the wake must publish the dispatch, its one replacement, and the \
16017 retirement turns the terminal transition owes itself"
16018 );
16019 assert!(
16020 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16021 "the terminal failure must reopen the executor before the waker returns"
16022 );
16023 assert!(
16024 executor.has_rejected_work(),
16025 "B must be moved aside, not dropped on the waker's stack"
16026 );
16027 assert_eq!(
16028 metrics.queued_runnables.load(Ordering::Relaxed),
16029 0,
16030 "the moved runnable carries its queued debit"
16031 );
16032 let mut task_b = task_b.fallible();
16033 assert!(
16034 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16035 "B must still be pending: nothing may be dropped on the waker's stack"
16036 );
16037
16038 executor.drive_host_turn();
16041 assert!(!executor.has_rejected_work());
16042 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16043 assert!(
16044 poll_ready_within(&mut task_b, Duration::from_secs(5))
16045 .expect("the host turn must cancel B")
16046 .is_none(),
16047 "the terminal failure must still cancel B"
16048 );
16049 drop(shared);
16050
16051 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
16052 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
16053 .state
16054 .lock()
16055 .unwrap_or_else(std::sync::PoisonError::into_inner);
16056 assert!(state.entries.is_empty());
16057 assert!(state.dispatches.is_empty());
16058 done_tx.send(()).unwrap();
16059 });
16060 done_rx
16061 .recv_timeout(Duration::from_secs(5))
16062 .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
16063 }
16064
16065 #[cfg(napi_runtime_os_threads)]
16069 fn poll_ready_within<F: Future + Unpin>(
16070 future: &mut F,
16071 timeout: std::time::Duration,
16072 ) -> Option<F::Output> {
16073 let deadline = std::time::Instant::now() + timeout;
16074 let mut cx = Context::from_waker(futures::task::noop_waker_ref());
16075 loop {
16076 if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
16077 return Some(output);
16078 }
16079 if std::time::Instant::now() >= deadline {
16080 return None;
16081 }
16082 std::thread::sleep(std::time::Duration::from_millis(1));
16083 }
16084 }
16085
16086 #[cfg(napi_runtime_os_threads)]
16094 #[test]
16095 fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
16096 use std::{sync::mpsc, time::Duration};
16097
16098 const WAIT: Duration = Duration::from_secs(5);
16099 let (done_tx, done_rx) = mpsc::channel();
16100 std::thread::spawn(move || {
16101 let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
16102 {
16103 let state = registry
16104 .state
16105 .lock()
16106 .unwrap_or_else(std::sync::PoisonError::into_inner);
16107 assert!(state.entries.is_empty());
16108 assert!(state.dispatches.is_empty());
16109 }
16110 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
16111 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16112 let metrics = Arc::new(RuntimeMetrics::default());
16113 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16114 Arc::clone(&metrics),
16115 dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
16116 registry,
16117 ));
16118
16119 let service = |index: usize| {
16121 let delivery = driver.deliveries()[index];
16122 let dispatch = registry
16123 .claim_delivery(delivery.capability())
16124 .expect("the accepted delivery must be claimable");
16125 let mut host_turn = executor
16126 .try_admit_host_turn(dispatch)
16127 .expect("the claimed delivery must be admitted");
16128 registry.mark_serviced(dispatch);
16129 host_turn.drive();
16130 drop(host_turn);
16131 let completion = registry.finish_delivery(delivery, false);
16132 assert!(completion.failed_dispatches.is_empty());
16133 assert!(!completion.redispatch);
16134 };
16135
16136 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16138 let shared = rx.map(|_| ()).boxed().shared();
16139 let shared_for_b = shared.clone();
16140 let scheduler_b = Arc::clone(&executor);
16141 let (runnable_b, task_b) = async_task::spawn(
16142 async move {
16143 shared_for_b.await;
16144 },
16145 move |runnable| scheduler_b.schedule(runnable),
16146 );
16147 executor.schedule(runnable_b);
16148 service(0);
16149 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16150
16151 driver.fail.store(true, Ordering::SeqCst);
16154 tx.send(()).expect("B must still hold its Shared clone");
16155 const AFTER_WAKE: usize =
16159 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16160 assert_eq!(driver.deliveries().len(), AFTER_WAKE);
16161 let mut task_b = task_b.fallible();
16162 assert!(
16163 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16164 "B must still be pending: nothing may be dropped on the waker's stack"
16165 );
16166
16167 driver.fail.store(false, Ordering::SeqCst);
16170 let scheduler_later = Arc::clone(&executor);
16171 let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
16172 scheduler_later.schedule(runnable)
16173 });
16174 executor.schedule(runnable_later);
16175 assert_eq!(
16176 driver.deliveries().len(),
16177 AFTER_WAKE + 1,
16178 "a submission after the terminal failure must publish a fresh delivery"
16179 );
16180 assert!(
16181 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16182 "the executor must be open for the healthy host"
16183 );
16184
16185 service(AFTER_WAKE);
16188 let mut task_later = task_later.fallible();
16189 assert_eq!(
16190 poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
16191 Some(42),
16192 "the later task must complete on the delivered host turn"
16193 );
16194 assert!(
16195 poll_ready_within(&mut task_b, WAIT)
16196 .expect("B must have been cancelled by the host turn")
16197 .is_none(),
16198 "the terminal failure must still cancel the work queued when it happened"
16199 );
16200 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16201 drop(shared);
16202
16203 registry.unregister(registration);
16204 let state = registry
16205 .state
16206 .lock()
16207 .unwrap_or_else(std::sync::PoisonError::into_inner);
16208 assert!(state.entries.is_empty());
16209 assert!(state.dispatches.is_empty());
16210 done_tx.send(()).unwrap();
16211 });
16212 done_rx
16213 .recv_timeout(WAIT)
16214 .expect("the healthy host never got a delivery for the later submission");
16215 }
16216
16217 #[cfg(napi_runtime_os_threads)]
16237 #[test]
16238 fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
16239 use std::{sync::mpsc, time::Duration};
16240
16241 const WAIT: Duration = Duration::from_secs(5);
16242 let (done_tx, done_rx) = mpsc::channel();
16243 std::thread::spawn(move || {
16244 let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
16245 {
16246 let state = registry
16247 .state
16248 .lock()
16249 .unwrap_or_else(std::sync::PoisonError::into_inner);
16250 assert!(state.entries.is_empty());
16251 assert!(state.dispatches.is_empty());
16252 }
16253 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16254 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16255 let metrics = Arc::new(RuntimeMetrics::default());
16256 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16257 Arc::clone(&metrics),
16258 dispatch_retirement_turn_current_thread_tasks,
16259 registry,
16260 ));
16261
16262 let service = |index: usize| {
16264 let delivery = driver.deliveries()[index];
16265 let dispatch = registry
16266 .claim_delivery(delivery.capability())
16267 .expect("the accepted delivery must be claimable");
16268 let mut host_turn = executor
16269 .try_admit_host_turn(dispatch)
16270 .expect("the claimed delivery must be admitted");
16271 registry.mark_serviced(dispatch);
16272 host_turn.drive();
16273 drop(host_turn);
16274 let completion = registry.finish_delivery(delivery, false);
16275 assert!(completion.failed_dispatches.is_empty());
16276 assert!(!completion.redispatch);
16277 };
16278
16279 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16281 let shared = rx.map(|_| ()).boxed().shared();
16282 let shared_for_b = shared.clone();
16283 let scheduler_b = Arc::clone(&executor);
16284 let (runnable_b, task_b) = async_task::spawn(
16285 async move {
16286 shared_for_b.await;
16287 },
16288 move |runnable| scheduler_b.schedule(runnable),
16289 );
16290 executor.schedule(runnable_b);
16291 service(0);
16292 assert_eq!(
16293 metrics.queued_runnables.load(Ordering::Relaxed),
16294 0,
16295 "B must be parked and unscheduled after its first poll"
16296 );
16297
16298 driver.fail_budget.store(2, Ordering::SeqCst);
16301 tx.send(()).expect("B must still hold its Shared clone");
16302
16303 assert_eq!(
16304 driver.deliveries().len(),
16305 4,
16306 "the wake must publish the dispatch, its one replacement, and the \
16307 retirement turn the terminal transition owes itself; without that \
16308 third publication no host turn ever follows and B stays pending \
16309 forever"
16310 );
16311 assert!(
16312 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16313 "the terminal failure must reopen the executor before the waker returns"
16314 );
16315 assert!(
16316 executor.has_rejected_work(),
16317 "B must be moved aside, not dropped on the waker's stack"
16318 );
16319 assert_eq!(
16320 metrics.queued_runnables.load(Ordering::Relaxed),
16321 0,
16322 "the moved runnable carries its queued debit"
16323 );
16324 let mut task_b = task_b.fallible();
16325 assert!(
16326 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16327 "B must still be pending: nothing may be dropped on the waker's stack"
16328 );
16329
16330 service(3);
16333 assert!(
16334 !executor.has_rejected_work(),
16335 "the retirement turn must cancel the moved work on its own stack"
16336 );
16337 assert!(
16338 poll_ready_within(&mut task_b, WAIT)
16339 .expect("the retirement turn must cancel B")
16340 .is_none(),
16341 "the terminal failure must still cancel the work queued when it happened"
16342 );
16343 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16344 drop(shared);
16345
16346 registry.unregister(registration);
16347 let state = registry
16348 .state
16349 .lock()
16350 .unwrap_or_else(std::sync::PoisonError::into_inner);
16351 assert!(state.entries.is_empty());
16352 assert!(state.dispatches.is_empty());
16353 done_tx.send(()).unwrap();
16354 });
16355 done_rx.recv_timeout(WAIT).expect(
16356 "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
16357 `queue.rejected` and its JoinHandle never settled",
16358 );
16359 }
16360
16361 #[cfg(napi_runtime_os_threads)]
16391 #[test]
16392 fn current_thread_nested_retirement_failure_keeps_its_continuation() {
16393 use std::{sync::mpsc, time::Duration};
16394
16395 const WAIT: Duration = Duration::from_secs(5);
16396 let (done_tx, done_rx) = mpsc::channel();
16397 std::thread::spawn(move || {
16398 let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
16399 {
16400 let state = registry
16401 .state
16402 .lock()
16403 .unwrap_or_else(std::sync::PoisonError::into_inner);
16404 assert!(state.entries.is_empty());
16405 assert!(state.dispatches.is_empty());
16406 }
16407 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16408 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16409 let metrics = Arc::new(RuntimeMetrics::default());
16410 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16411 Arc::clone(&metrics),
16412 dispatch_nested_retirement_current_thread_tasks,
16413 registry,
16414 ));
16415
16416 let service = |index: usize| {
16418 let delivery = driver.deliveries()[index];
16419 let dispatch = registry
16420 .claim_delivery(delivery.capability())
16421 .expect("the accepted delivery must be claimable");
16422 let mut host_turn = executor
16423 .try_admit_host_turn(dispatch)
16424 .expect("the claimed delivery must be admitted");
16425 registry.mark_serviced(dispatch);
16426 host_turn.drive();
16427 drop(host_turn);
16428 let completion = registry.finish_delivery(delivery, false);
16429 assert!(completion.failed_dispatches.is_empty());
16430 assert!(!completion.redispatch);
16431 };
16432
16433 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16436 let shared = rx.map(|_| ()).boxed().shared();
16437 let shared_for_b = shared.clone();
16438 let scheduler_b = Arc::clone(&executor);
16439 let (runnable_b, task_b) = async_task::spawn(
16440 async move {
16441 shared_for_b.await;
16442 },
16443 move |runnable| scheduler_b.schedule(runnable),
16444 );
16445 executor.schedule(runnable_b);
16446 service(0);
16447 assert_eq!(
16448 metrics.queued_runnables.load(Ordering::Relaxed),
16449 0,
16450 "B must be parked and unscheduled after its first poll"
16451 );
16452
16453 let scheduler_c = Arc::clone(&executor);
16458 let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
16459 scheduler_c.schedule(runnable);
16460 });
16461 let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
16462 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16463 let producer_executor = Arc::clone(&executor);
16464 let producer = std::thread::spawn(move || {
16465 let runnable = gate_rx
16466 .recv()
16467 .expect("the retirement turn must open the window");
16468 producer_executor.schedule(runnable);
16469 queued_tx.send(()).expect("W must still be waiting");
16470 });
16471 driver.during_delivery(3, move || {
16472 gate_tx.send(runnable_c).expect("the producer must be live");
16473 queued_rx
16474 .recv_timeout(WAIT)
16475 .expect("the producer must queue C before the retirement turn fails");
16476 });
16477
16478 driver.fail_budget.store(4, Ordering::SeqCst);
16483 tx.send(()).expect("B must still hold its Shared clone");
16484 producer.join().expect("the producer must not panic");
16485
16486 assert_eq!(
16487 driver.deliveries().len(),
16488 6,
16489 "the nested terminal transition may not publish on this stack, but the \
16490 retirement request it hands back must be serviced after the outer \
16491 publication unwinds; without that sixth delivery no host turn ever \
16492 follows and both B and C stay pending forever"
16493 );
16494 assert!(
16495 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16496 "both terminal failures must reopen the executor before the waker returns"
16497 );
16498 assert!(
16499 executor.has_rejected_work(),
16500 "B and C must be moved aside, not dropped on the waker's stack"
16501 );
16502 assert_eq!(
16503 metrics.queued_runnables.load(Ordering::Relaxed),
16504 0,
16505 "the moved runnables carry their queued debit"
16506 );
16507 let mut task_b = task_b.fallible();
16508 let mut task_c = task_c.fallible();
16509 assert!(
16510 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16511 "B must still be pending: nothing may be dropped on the waker's stack"
16512 );
16513 assert!(
16514 poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
16515 "C must still be pending: nothing may be dropped on the waker's stack"
16516 );
16517
16518 service(5);
16522 assert!(
16523 !executor.has_rejected_work(),
16524 "the retirement turn must cancel both moved runnables on its own stack"
16525 );
16526 assert!(
16527 poll_ready_within(&mut task_b, WAIT)
16528 .expect("the retirement turn must cancel B")
16529 .is_none(),
16530 "the terminal failure must still cancel the work queued when it happened"
16531 );
16532 assert!(
16533 poll_ready_within(&mut task_c, WAIT)
16534 .expect("the retirement turn must cancel C")
16535 .is_none(),
16536 "the nested terminal failure must cancel the work it moved aside too"
16537 );
16538 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16539 drop(shared);
16540
16541 registry.unregister(registration);
16542 let state = registry
16543 .state
16544 .lock()
16545 .unwrap_or_else(std::sync::PoisonError::into_inner);
16546 assert!(state.entries.is_empty());
16547 assert!(state.dispatches.is_empty());
16548 done_tx.send(()).unwrap();
16549 });
16550 done_rx.recv_timeout(WAIT).expect(
16551 "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
16552 runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
16553 );
16554 }
16555
16556 #[cfg(napi_runtime_os_threads)]
16591 #[test]
16592 fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
16593 use std::{sync::mpsc, time::Duration};
16594
16595 const WAIT: Duration = Duration::from_secs(5);
16596 const PRODUCER_BUDGET: usize = 4096;
16599 const BOUNDED_DELIVERIES: usize =
16602 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16603
16604 let (done_tx, done_rx) = mpsc::channel();
16605 std::thread::spawn(move || {
16606 let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
16607 {
16608 let state = registry
16609 .state
16610 .lock()
16611 .unwrap_or_else(std::sync::PoisonError::into_inner);
16612 assert!(state.entries.is_empty());
16613 assert!(state.dispatches.is_empty());
16614 }
16615 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16616 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16617 let metrics = Arc::new(RuntimeMetrics::default());
16618 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16619 Arc::clone(&metrics),
16620 dispatch_sustained_producer_current_thread_tasks,
16621 registry,
16622 ));
16623 let backend =
16624 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16625
16626 let service = |index: usize| {
16628 let delivery = driver.deliveries()[index];
16629 let dispatch = registry
16630 .claim_delivery(delivery.capability())
16631 .expect("the accepted delivery must be claimable");
16632 let mut host_turn = executor
16633 .try_admit_host_turn(dispatch)
16634 .expect("the claimed delivery must be admitted");
16635 registry.mark_serviced(dispatch);
16636 host_turn.drive();
16637 drop(host_turn);
16638 let completion = registry.finish_delivery(delivery, false);
16639 assert!(completion.failed_dispatches.is_empty());
16640 assert!(!completion.redispatch);
16641 };
16642
16643 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16647 let shared = rx.map(|_| ()).boxed().shared();
16648 let shared_for_b = shared.clone();
16649 let work_registration = backend
16650 .work
16651 .try_register_async()
16652 .expect("the backend must accept work");
16653 let task_b = spawn_registered(
16654 &backend,
16655 Arc::clone(&metrics),
16656 async move {
16657 shared_for_b.await;
16658 },
16659 work_registration,
16660 );
16661 service(0);
16662 assert_eq!(
16663 metrics.queued_runnables.load(Ordering::Relaxed),
16664 0,
16665 "B must be parked and unscheduled after its first poll"
16666 );
16667 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16668
16669 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16674 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16675 let producer_executor = Arc::clone(&executor);
16676 let producer = std::thread::spawn(move || {
16677 let mut submissions = 0usize;
16678 while queue_one_rx.recv().is_ok() {
16679 let scheduler = Arc::clone(&producer_executor);
16680 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16681 scheduler.schedule(runnable);
16682 });
16683 task.detach();
16684 producer_executor.schedule(runnable);
16685 submissions += 1;
16686 if queued_tx.send(()).is_err() {
16687 break;
16688 }
16689 }
16690 submissions
16691 });
16692 driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
16693
16694 driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
16697 let before = driver.deliveries().len();
16698 tx.send(()).expect("B must still hold its Shared clone");
16699 let during_wake = driver.deliveries().len() - before;
16700 let unspent = driver.disarm_producer();
16701 let submissions = producer.join().expect("the producer must not panic");
16702
16703 assert_eq!(
16704 during_wake, BOUNDED_DELIVERIES,
16705 "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
16706 on the waker's stack plus its one final offer; without a budget it \
16707 services the producer's stream instead and this wake never returns"
16708 );
16709 assert_eq!(
16710 submissions, during_wake,
16711 "the producer must have queued a runnable during every publication"
16712 );
16713 assert!(
16714 unspent > 0,
16715 "the producer must still have been willing to submit when the turns stopped"
16716 );
16717 assert!(
16718 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16719 "every terminal failure, and the final offer, must reopen the executor \
16720 before the waker returns"
16721 );
16722 assert!(
16723 executor.has_rejected_work(),
16724 "the moved work must be parked, not dropped on the waker's stack"
16725 );
16726 assert_eq!(
16727 executor.dispatch_pending.load(Ordering::Acquire),
16728 0,
16729 "a host that refused every offer including the final one leaves none \
16730 outstanding: this is the documented end state for a host that never accepts"
16731 );
16732 assert_eq!(
16733 metrics.queued_runnables.load(Ordering::Relaxed),
16734 0,
16735 "the moved runnables carry their queued debit"
16736 );
16737 let mut task_b = Box::pin(task_b);
16738 assert!(
16739 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16740 "B must still be pending: nothing may be dropped on the waker's stack"
16741 );
16742 assert_eq!(
16743 backend.work.state.lock().unwrap().active,
16744 1,
16745 "B's generation guard rides with its parked runnable"
16746 );
16747
16748 backend.wait_until_idle();
16752 assert!(!executor.has_rejected_work());
16753 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16754 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16755 assert!(
16756 poll_ready_within(&mut task_b, WAIT)
16757 .expect("the idle wait must cancel B")
16758 .is_err(),
16759 "the terminal failure must still cancel the work queued when it happened"
16760 );
16761 drop(shared);
16762
16763 registry.unregister(registration);
16764 let state = registry
16765 .state
16766 .lock()
16767 .unwrap_or_else(std::sync::PoisonError::into_inner);
16768 assert!(state.entries.is_empty());
16769 assert!(state.dispatches.is_empty());
16770 done_tx.send(()).unwrap();
16771 });
16772 done_rx.recv_timeout(WAIT).expect(
16773 "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
16774 the wake never returned",
16775 );
16776 }
16777
16778 #[cfg(napi_runtime_os_threads)]
16812 #[test]
16813 fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
16814 use std::{sync::mpsc, time::Duration};
16815
16816 const WAIT: Duration = Duration::from_secs(5);
16817 const BUDGETED_DELIVERIES: usize =
16821 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16822
16823 let (done_tx, done_rx) = mpsc::channel();
16824 std::thread::spawn(move || {
16825 struct DropFlag(Arc<AtomicBool>);
16826 impl Drop for DropFlag {
16827 fn drop(&mut self) {
16828 self.0.store(true, Ordering::SeqCst);
16829 }
16830 }
16831
16832 let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
16833 {
16834 let state = registry
16835 .state
16836 .lock()
16837 .unwrap_or_else(std::sync::PoisonError::into_inner);
16838 assert!(state.entries.is_empty());
16839 assert!(state.dispatches.is_empty());
16840 }
16841 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16842 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16843 let metrics = Arc::new(RuntimeMetrics::default());
16844 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16845 Arc::clone(&metrics),
16846 dispatch_final_retirement_offer_current_thread_tasks,
16847 registry,
16848 ));
16849 let backend =
16850 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16851
16852 let service = |index: usize| {
16853 let delivery = driver.deliveries()[index];
16854 let dispatch = registry
16855 .claim_delivery(delivery.capability())
16856 .expect("the accepted delivery must be claimable");
16857 let mut host_turn = executor
16858 .try_admit_host_turn(dispatch)
16859 .expect("the claimed delivery must be admitted");
16860 registry.mark_serviced(dispatch);
16861 host_turn.drive();
16862 drop(host_turn);
16863 let completion = registry.finish_delivery(delivery, false);
16864 assert!(completion.failed_dispatches.is_empty());
16865 assert!(!completion.redispatch);
16866 };
16867
16868 let dropped = Arc::new(AtomicBool::new(false));
16869 let flag = DropFlag(Arc::clone(&dropped));
16870 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16871 let shared = rx.map(|_| ()).boxed().shared();
16872 let shared_for_b = shared.clone();
16873 let work_registration = backend
16874 .work
16875 .try_register_async()
16876 .expect("the backend must accept work");
16877 let task_b = spawn_registered(
16878 &backend,
16879 Arc::clone(&metrics),
16880 async move {
16881 let _flag = flag;
16882 shared_for_b.await;
16883 },
16884 work_registration,
16885 );
16886 service(0);
16887 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16888 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16889
16890 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16891 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16892 let producer_executor = Arc::clone(&executor);
16893 let producer = std::thread::spawn(move || {
16894 let mut submissions = 0usize;
16895 while queue_one_rx.recv().is_ok() {
16896 let scheduler = Arc::clone(&producer_executor);
16897 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16898 scheduler.schedule(runnable);
16899 });
16900 task.detach();
16901 producer_executor.schedule(runnable);
16902 submissions += 1;
16903 if queued_tx.send(()).is_err() {
16904 break;
16905 }
16906 }
16907 submissions
16908 });
16909 driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
16912 driver
16913 .fail_budget
16914 .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
16915
16916 let before = driver.deliveries().len();
16917 tx.send(()).expect("B must still hold its Shared clone");
16918 let during_wake = driver.deliveries().len() - before;
16919 let unspent = driver.disarm_producer();
16920 let submissions = producer.join().expect("the producer must not panic");
16921
16922 assert_eq!(
16923 during_wake,
16924 BUDGETED_DELIVERIES + 1,
16925 "the budget must be followed by the one offer a producer cannot \
16926 inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
16927 deliveries and never asks the host that would have accepted"
16928 );
16929 assert_eq!(
16930 submissions, BUDGETED_DELIVERIES,
16931 "the producer must have queued a runnable during every budgeted \
16932 publication and gone quiescent before the final offer"
16933 );
16934 assert_eq!(
16935 unspent, 0,
16936 "the producer must have run dry exactly as the budget ran out"
16937 );
16938 assert_eq!(
16939 driver.fail_budget.load(Ordering::SeqCst),
16940 0,
16941 "the host must have refused every budgeted delivery and no more"
16942 );
16943 assert!(
16944 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16945 "the final offer must reopen the executor before the waker returns"
16946 );
16947 assert!(
16948 executor.has_rejected_work(),
16949 "nothing may be dropped on the waker's stack"
16950 );
16951 assert_ne!(
16952 executor.dispatch_pending.load(Ordering::Acquire),
16953 0,
16954 "the wake must leave a dispatch outstanding: that accepted delivery is \
16955 the continuation the parked work has"
16956 );
16957 let mut task_b = Box::pin(task_b);
16958 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16959 assert!(!dropped.load(Ordering::SeqCst));
16960 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16961
16962 service(driver.deliveries().len() - 1);
16965 assert!(
16966 !executor.has_rejected_work(),
16967 "that host turn is a depth-0 stack and must retire everything the wake \
16968 moved aside"
16969 );
16970 assert!(
16971 poll_ready_within(&mut task_b, WAIT)
16972 .expect("the final offer's host turn must cancel B")
16973 .is_err()
16974 );
16975 assert!(
16976 dropped.load(Ordering::SeqCst),
16977 "B's future must be destroyed, on the host's stack and not the waker's"
16978 );
16979 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16980 assert_eq!(
16981 backend.work.state.lock().unwrap().active,
16982 0,
16983 "no GenerationWorkGuard may outlive the retirement"
16984 );
16985 drop(shared);
16986
16987 registry.unregister(registration);
16988 let state = registry
16989 .state
16990 .lock()
16991 .unwrap_or_else(std::sync::PoisonError::into_inner);
16992 assert!(state.entries.is_empty());
16993 assert!(state.dispatches.is_empty());
16994 done_tx.send(()).unwrap();
16995 });
16996 done_rx.recv_timeout(WAIT).expect(
16997 "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16998 );
16999 }
17000
17001 #[cfg(napi_runtime_os_threads)]
17019 #[test]
17020 fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
17021 use std::{sync::mpsc, time::Duration};
17022
17023 const WAIT: Duration = Duration::from_secs(5);
17024 let (done_tx, done_rx) = mpsc::channel();
17025 std::thread::spawn(move || {
17026 struct DropFlag(Arc<AtomicBool>);
17027 impl Drop for DropFlag {
17028 fn drop(&mut self) {
17029 self.0.store(true, Ordering::SeqCst);
17030 }
17031 }
17032
17033 let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
17034 {
17035 let state = registry
17036 .state
17037 .lock()
17038 .unwrap_or_else(std::sync::PoisonError::into_inner);
17039 assert!(state.entries.is_empty());
17040 assert!(state.dispatches.is_empty());
17041 }
17042 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
17043 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17044 let metrics = Arc::new(RuntimeMetrics::default());
17045 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17046 Arc::clone(&metrics),
17047 dispatch_retirement_retry_current_thread_tasks,
17048 registry,
17049 ));
17050 let backend =
17051 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17052
17053 let service = |index: usize| {
17054 let delivery = driver.deliveries()[index];
17055 let dispatch = registry
17056 .claim_delivery(delivery.capability())
17057 .expect("the accepted delivery must be claimable");
17058 let mut host_turn = executor
17059 .try_admit_host_turn(dispatch)
17060 .expect("the claimed delivery must be admitted");
17061 registry.mark_serviced(dispatch);
17062 host_turn.drive();
17063 drop(host_turn);
17064 let completion = registry.finish_delivery(delivery, false);
17065 assert!(completion.failed_dispatches.is_empty());
17066 assert!(!completion.redispatch);
17067 };
17068
17069 let dropped = Arc::new(AtomicBool::new(false));
17070 let flag = DropFlag(Arc::clone(&dropped));
17071 let (tx, rx) = futures::channel::oneshot::channel::<()>();
17072 let shared = rx.map(|_| ()).boxed().shared();
17073 let shared_for_b = shared.clone();
17074 let work_registration = backend
17075 .work
17076 .try_register_async()
17077 .expect("the backend must accept work");
17078 let task_b = spawn_registered(
17079 &backend,
17080 Arc::clone(&metrics),
17081 async move {
17082 let _flag = flag;
17083 shared_for_b.await;
17084 },
17085 work_registration,
17086 );
17087 service(0);
17088 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17089 assert_eq!(backend.work.state.lock().unwrap().active, 1);
17090
17091 let before = driver.deliveries().len();
17096 driver.fail_budget.store(3, Ordering::SeqCst);
17097 tx.send(()).expect("B must still hold its Shared clone");
17098 let during_wake = driver.deliveries().len() - before;
17099
17100 assert_eq!(
17101 during_wake, 4,
17102 "a refused offer owes another turn just as a nested transition does: \
17103 work is parked and no dispatch is outstanding"
17104 );
17105 assert_ne!(
17106 executor.dispatch_pending.load(Ordering::Acquire),
17107 0,
17108 "the accepted second offer is the continuation the parked work has"
17109 );
17110 assert!(executor.has_rejected_work());
17111 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17112 let mut task_b = Box::pin(task_b);
17113 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17114 assert!(!dropped.load(Ordering::SeqCst));
17115
17116 service(driver.deliveries().len() - 1);
17118 assert!(!executor.has_rejected_work());
17119 assert!(
17120 poll_ready_within(&mut task_b, WAIT)
17121 .expect("the accepted offer's host turn must cancel B")
17122 .is_err()
17123 );
17124 assert!(dropped.load(Ordering::SeqCst));
17125 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17126 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17127 drop(shared);
17128
17129 registry.unregister(registration);
17130 let state = registry
17131 .state
17132 .lock()
17133 .unwrap_or_else(std::sync::PoisonError::into_inner);
17134 assert!(state.entries.is_empty());
17135 assert!(state.dispatches.is_empty());
17136 done_tx.send(()).unwrap();
17137 });
17138 done_rx
17139 .recv_timeout(WAIT)
17140 .expect("a refused retirement offer left B stranded with a healthy host");
17141 }
17142
17143 #[cfg(napi_runtime_os_threads)]
17175 #[test]
17176 fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
17177 use std::{sync::mpsc, time::Duration};
17178
17179 const WAIT: Duration = Duration::from_secs(5);
17180 const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
17182
17183 let (done_tx, done_rx) = mpsc::channel();
17184 std::thread::spawn(move || {
17185 struct DropFlag(Arc<AtomicBool>);
17186 impl Drop for DropFlag {
17187 fn drop(&mut self) {
17188 self.0.store(true, Ordering::SeqCst);
17189 }
17190 }
17191
17192 let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
17193 {
17194 let state = registry
17195 .state
17196 .lock()
17197 .unwrap_or_else(std::sync::PoisonError::into_inner);
17198 assert!(state.entries.is_empty());
17199 assert!(state.dispatches.is_empty());
17200 }
17201 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
17202 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17203 let metrics = Arc::new(RuntimeMetrics::default());
17204 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17205 Arc::clone(&metrics),
17206 dispatch_inline_final_offer_current_thread_tasks,
17207 registry,
17208 ));
17209 let backend =
17210 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17211
17212 let service = |index: usize| {
17213 let delivery = driver.deliveries()[index];
17214 let dispatch = registry
17215 .claim_delivery(delivery.capability())
17216 .expect("the accepted delivery must be claimable");
17217 let mut host_turn = executor
17218 .try_admit_host_turn(dispatch)
17219 .expect("the claimed delivery must be admitted");
17220 registry.mark_serviced(dispatch);
17221 host_turn.drive();
17222 drop(host_turn);
17223 let completion = registry.finish_delivery(delivery, false);
17224 assert!(completion.failed_dispatches.is_empty());
17225 assert!(!completion.redispatch);
17226 };
17227
17228 let dropped = Arc::new(AtomicBool::new(false));
17229 let flag = DropFlag(Arc::clone(&dropped));
17230 let (tx, rx) = futures::channel::oneshot::channel::<()>();
17231 let shared = rx.map(|_| ()).boxed().shared();
17232 let shared_for_b = shared.clone();
17233 let work_registration = backend
17234 .work
17235 .try_register_async()
17236 .expect("the backend must accept work");
17237 let task_b = spawn_registered(
17238 &backend,
17239 Arc::clone(&metrics),
17240 async move {
17241 let _flag = flag;
17242 shared_for_b.await;
17243 },
17244 work_registration,
17245 );
17246 service(0);
17247 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17248 assert_eq!(backend.work.state.lock().unwrap().active, 1);
17249
17250 let before = driver.deliveries().len();
17255 let serviced_inline = Arc::new(AtomicBool::new(false));
17256 {
17257 let executor = Arc::clone(&executor);
17258 let hook_driver = Arc::clone(&driver);
17259 let serviced_inline = Arc::clone(&serviced_inline);
17260 driver.during_delivery(before + REFUSED_DELIVERIES, move || {
17261 let delivery = *hook_driver
17262 .deliveries()
17263 .last()
17264 .expect("the final offer must be recorded before the hook runs");
17265 let dispatch = registry
17266 .claim_delivery(delivery.capability())
17267 .expect("the final offer must be claimable");
17268 let mut host_turn = executor
17269 .try_admit_host_turn(dispatch)
17270 .expect("the claimed final offer must be admitted");
17271 registry.mark_serviced(dispatch);
17272 host_turn.drive();
17273 drop(host_turn);
17274 let completion = registry.finish_delivery(delivery, false);
17275 assert!(completion.failed_dispatches.is_empty());
17276 serviced_inline.store(true, Ordering::SeqCst);
17277 });
17278 }
17279 driver
17280 .fail_budget
17281 .store(REFUSED_DELIVERIES, Ordering::SeqCst);
17282 tx.send(()).expect("B must still hold its Shared clone");
17283 let during_wake = driver.deliveries().len() - before;
17284
17285 assert!(
17286 serviced_inline.load(Ordering::SeqCst),
17287 "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
17288 );
17289 assert_eq!(
17290 during_wake,
17291 REFUSED_DELIVERIES + 2,
17292 "an accepted offer whose host turn could not act owes the same continuation a refused \
17293 one owes: work parked and no dispatch outstanding"
17294 );
17295 assert_ne!(
17296 executor.dispatch_pending.load(Ordering::Acquire),
17297 0,
17298 "the publication that follows the consumed offer is the continuation the parked work has"
17299 );
17300 assert!(executor.has_rejected_work());
17301 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17302 let mut task_b = Box::pin(task_b);
17303 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17304 assert!(!dropped.load(Ordering::SeqCst));
17305
17306 service(driver.deliveries().len() - 1);
17308 assert!(!executor.has_rejected_work());
17309 assert!(
17310 poll_ready_within(&mut task_b, WAIT)
17311 .expect("the host turn that follows the consumed offer must cancel B")
17312 .is_err()
17313 );
17314 assert!(dropped.load(Ordering::SeqCst));
17315 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17316 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17317 drop(shared);
17318
17319 registry.unregister(registration);
17320 let state = registry
17321 .state
17322 .lock()
17323 .unwrap_or_else(std::sync::PoisonError::into_inner);
17324 assert!(state.entries.is_empty());
17325 assert!(state.dispatches.is_empty());
17326 done_tx.send(()).unwrap();
17327 });
17328 done_rx.recv_timeout(WAIT).expect(
17329 "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
17330 );
17331 }
17332
17333 #[cfg(napi_runtime_os_threads)]
17348 #[test]
17349 fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
17350 use std::time::Duration;
17351
17352 let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
17353 {
17354 let state = registry
17355 .state
17356 .lock()
17357 .unwrap_or_else(std::sync::PoisonError::into_inner);
17358 assert!(state.entries.is_empty());
17359 assert!(state.dispatches.is_empty());
17360 }
17361 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
17362 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17363 let metrics = Arc::new(RuntimeMetrics::default());
17364 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17365 Arc::clone(&metrics),
17366 dispatch_submission_failure_current_thread_tasks,
17367 registry,
17368 ));
17369 let backend =
17370 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17371
17372 driver.fail.store(true, Ordering::SeqCst);
17375
17376 let work_registration = backend
17377 .work
17378 .try_register_async()
17379 .expect("the backend must accept work");
17380 let task = spawn_registered(
17381 &backend,
17382 Arc::clone(&metrics),
17383 std::future::pending::<()>(),
17384 work_registration,
17385 );
17386
17387 assert_eq!(
17388 driver.deliveries().len(),
17389 2,
17390 "the submission must publish the dispatch and exactly one replacement"
17391 );
17392 assert!(
17393 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
17394 "the terminal failure must reopen the executor"
17395 );
17396 assert!(
17397 !executor.has_rejected_work(),
17398 "a submission's own stack is not a waker's: the rejected work must be cancelled here, because nothing else will"
17399 );
17400 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17401 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17402
17403 let mut task = Box::pin(task);
17404 assert_eq!(
17405 poll_ready_within(&mut task, Duration::ZERO)
17406 .expect("the submission must settle without any later entry")
17407 .unwrap_err()
17408 .to_string(),
17409 "async runtime stopped before the task completed",
17410 "terminal host failure must settle the submitted operation as cancelled"
17411 );
17412
17413 registry.unregister(registration);
17414 let state = registry
17415 .state
17416 .lock()
17417 .unwrap_or_else(std::sync::PoisonError::into_inner);
17418 assert!(state.entries.is_empty());
17419 assert!(state.dispatches.is_empty());
17420 }
17421
17422 #[cfg(napi_runtime_os_threads)]
17436 #[test]
17437 fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
17438 use std::{
17439 sync::{Barrier, mpsc},
17440 time::Duration,
17441 };
17442
17443 let (done_tx, done_rx) = mpsc::channel();
17444 std::thread::spawn(move || {
17445 let metrics = Arc::new(RuntimeMetrics::default());
17446 let options = RuntimeOptions {
17447 flavor: RuntimeFlavor::MultiThread,
17448 worker_threads: 2,
17449 max_blocking_tasks: 1,
17450 thread_name_prefix: "mt-inline-drop".to_string(),
17451 park_deadline: None,
17452 drain_linger: DEFAULT_DRAIN_LINGER,
17453 };
17454 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17455 let backend =
17456 RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
17457 let generation = backend.generation();
17458
17459 let (abort_a, guard_a) = backend
17461 .work
17462 .try_register_async()
17463 .expect("the backend must accept work");
17464 let scheduler_a = Arc::clone(&executor);
17465 let (runnable_a, task_a) = async_task::spawn(
17466 RegisteredTaskFuture::new(
17467 Abortable::new(std::future::pending::<()>(), abort_a),
17468 guard_a,
17469 generation,
17470 ),
17471 move |runnable| scheduler_a.schedule(runnable),
17472 );
17473 let shared = async move { task_a.fallible().await.is_some() }
17474 .boxed()
17475 .shared();
17476 let shared_for_b = shared.clone();
17477
17478 let (abort_b, guard_b) = backend
17482 .work
17483 .try_register_async()
17484 .expect("the backend must accept work");
17485 let wake_in_flight = Arc::new(Barrier::new(2));
17486 let wake_release = Arc::new(Barrier::new(2));
17487 let scheduler_b = Arc::clone(&executor);
17488 let (runnable_b, task_b) = async_task::spawn(
17489 RegisteredTaskFuture::new(
17490 Abortable::new(
17491 async move {
17492 shared_for_b.await;
17493 },
17494 abort_b,
17495 ),
17496 guard_b,
17497 generation,
17498 ),
17499 {
17500 let wake_in_flight = Arc::clone(&wake_in_flight);
17501 let wake_release = Arc::clone(&wake_release);
17502 move |runnable| {
17503 wake_in_flight.wait();
17504 wake_release.wait();
17505 scheduler_b.schedule(runnable);
17506 }
17507 },
17508 );
17509 executor.schedule(runnable_b);
17514 wait_until("B parked after its first poll", || {
17515 metrics.runnable_polls.load(Ordering::SeqCst) == 1
17516 && metrics.active_runnables.load(Ordering::SeqCst) == 0
17517 && metrics.queued_runnables.load(Ordering::SeqCst) == 0
17518 });
17519
17520 let waker_thread = std::thread::spawn(move || drop(runnable_a));
17524 wake_in_flight.wait();
17525
17526 backend.begin_shutdown();
17529 wake_release.wait();
17530 backend.wait_until_idle();
17531
17532 waker_thread
17533 .join()
17534 .expect("the waker thread must not panic");
17535 assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
17536 assert!(
17537 futures::executor::block_on(task_b.fallible()).is_none(),
17538 "B's late wake must still cancel B"
17539 );
17540 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17541 drop(shared);
17542 done_tx.send(()).unwrap();
17543 });
17544 done_rx
17545 .recv_timeout(Duration::from_secs(5))
17546 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
17547 }
17548
17549 #[cfg(napi_runtime_os_threads)]
17550 #[test]
17551 fn current_thread_recovery_reservation_blocks_new_host_publication() {
17552 use std::sync::mpsc;
17553
17554 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
17555 let metrics = Arc::new(RuntimeMetrics::default());
17556 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17557 Arc::clone(&metrics),
17558 count_current_thread_interleaved_recovery_host_dispatch,
17559 ));
17560 let backend =
17561 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17562
17563 let registration = backend
17564 .work
17565 .try_register_async()
17566 .expect("the backend must accept work");
17567 let task = spawn_registered(
17568 &backend,
17569 Arc::clone(&metrics),
17570 std::future::pending::<()>(),
17571 registration,
17572 );
17573 let failed_dispatch = current_thread_dispatch(&executor);
17574 assert_eq!(
17575 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17576 1
17577 );
17578
17579 let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
17580 let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
17581 let cancelling_executor = Arc::clone(&executor);
17582 let cancellation = std::thread::spawn(move || {
17583 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
17584 *slot.borrow_mut() = Some(Box::new(move || {
17585 recovery_cleared_tx.send(()).unwrap();
17586 release_recovery_rx.recv().unwrap();
17587 }));
17588 });
17589 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
17590 });
17591 recovery_cleared_rx
17592 .recv_timeout(Duration::from_secs(2))
17593 .expect("failed dispatch cancellation must pause after consuming the old capability");
17594
17595 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
17596 let (publication_done_tx, publication_done_rx) = mpsc::channel();
17597 let publishing_executor = Arc::clone(&executor);
17598 let publication = std::thread::spawn(move || {
17599 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
17600 *slot.borrow_mut() = Some(Box::new(move || {
17601 publication_blocked_tx.send(()).unwrap();
17602 }));
17603 });
17604 publishing_executor.request_drain_if_queued();
17605 publication_done_tx.send(()).unwrap();
17606 });
17607
17608 if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
17609 release_recovery_tx.send(()).unwrap();
17610 cancellation.join().unwrap();
17611 publication.join().unwrap();
17612 panic!("a newly registered host must wait for recovery reservation: {error}");
17613 }
17614 assert!(
17615 matches!(
17616 publication_done_rx.try_recv(),
17617 Err(mpsc::TryRecvError::Empty)
17618 ),
17619 "the new host must not publish in the consumed-to-recovery gap"
17620 );
17621 release_recovery_tx.send(()).unwrap();
17622
17623 cancellation.join().unwrap();
17624 publication_done_rx
17625 .recv_timeout(Duration::from_secs(2))
17626 .unwrap();
17627 publication.join().unwrap();
17628
17629 let replacement_dispatch = current_thread_dispatch(&executor);
17630 assert_ne!(replacement_dispatch, failed_dispatch);
17631 assert_eq!(
17632 executor.recovery_dispatch.load(Ordering::Acquire),
17633 replacement_dispatch
17634 );
17635 assert_eq!(
17636 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17637 3,
17638 "the original publication and two host attempts for its one replacement must run"
17639 );
17640
17641 executor.cancel_host_dispatch_for_test(replacement_dispatch);
17642
17643 assert_eq!(
17644 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17645 3,
17646 "failure of the tagged replacement must cancel work without a second replacement"
17647 );
17648 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17649 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17650 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17651 assert_eq!(
17652 futures::executor::block_on(task).unwrap_err().to_string(),
17653 "async runtime stopped before the task completed"
17654 );
17655
17656 let later_registration = backend
17657 .work
17658 .try_register_async()
17659 .expect("terminal cancellation must reopen acceptance");
17660 let later_task = spawn_registered(
17661 &backend,
17662 Arc::clone(&metrics),
17663 async { 2usize },
17664 later_registration,
17665 );
17666 assert_eq!(
17667 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17668 4
17669 );
17670 executor.drive_host_turn();
17671 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
17672 }
17673
17674 #[cfg(napi_runtime_os_threads)]
17675 #[test]
17676 fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
17677 use std::sync::mpsc;
17678
17679 struct BlockingDrop {
17680 entered: mpsc::Sender<()>,
17681 release: mpsc::Receiver<()>,
17682 }
17683
17684 impl Drop for BlockingDrop {
17685 fn drop(&mut self) {
17686 self.entered.send(()).unwrap();
17687 self.release.recv().unwrap();
17688 }
17689 }
17690
17691 reset_current_thread_recovery_host_dispatches();
17692 let metrics = Arc::new(RuntimeMetrics::default());
17693 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17694 Arc::clone(&metrics),
17695 count_current_thread_recovery_host_dispatch,
17696 ));
17697 let backend =
17698 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17699
17700 let holder_registration = backend
17701 .work
17702 .try_register_work()
17703 .expect("the backend must accept the blocking holder");
17704 let (holder_started_tx, holder_started_rx) = mpsc::channel();
17705 let (release_holder_tx, release_holder_rx) = mpsc::channel();
17706 let holder_executor = Arc::clone(&executor);
17707 let holder = std::thread::spawn(move || {
17708 let handle = holder_executor.schedule_blocking(
17709 move || {
17710 holder_started_tx.send(()).unwrap();
17711 release_holder_rx.recv().unwrap();
17712 },
17713 holder_registration,
17714 );
17715 futures::executor::block_on(handle).unwrap();
17716 });
17717 holder_started_rx
17718 .recv_timeout(Duration::from_secs(2))
17719 .expect("the first blocking call must hold the CurrentThread lane");
17720
17721 let cancelled_registration = backend
17722 .work
17723 .try_register_work()
17724 .expect("the backend must accept queued blocking work");
17725 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
17726 let (release_drop_tx, release_drop_rx) = mpsc::channel();
17727 let blocking_drop = BlockingDrop {
17728 entered: drop_entered_tx,
17729 release: release_drop_rx,
17730 };
17731 let cancelled = executor.schedule_blocking(
17732 move || {
17733 let _blocking_drop = blocking_drop;
17734 },
17735 cancelled_registration,
17736 );
17737
17738 let async_registration = backend
17739 .work
17740 .try_register_async()
17741 .expect("the backend must accept queued async work");
17742 let pending = spawn_registered(
17743 &backend,
17744 Arc::clone(&metrics),
17745 std::future::pending::<()>(),
17746 async_registration,
17747 );
17748 let failed_dispatch = current_thread_dispatch(&executor);
17749 executor.cancel_host_dispatch_for_test(failed_dispatch);
17750 let replacement_dispatch = current_thread_dispatch(&executor);
17751
17752 let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
17753 let cancelling_executor = Arc::clone(&executor);
17754 let cancellation = std::thread::spawn(move || {
17755 cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
17756 cancel_done_tx.send(()).unwrap();
17757 });
17758 drop_entered_rx
17759 .recv_timeout(Duration::from_secs(2))
17760 .expect("terminal dispatch cancellation must destroy the queued blocking capture");
17761 let active_during_drop = backend.work.state.lock().unwrap().active;
17762 let cancellation_blocked = cancel_done_rx.try_recv().is_err();
17763
17764 release_drop_tx.send(()).unwrap();
17765 cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17766 cancellation.join().unwrap();
17767
17768 assert_eq!(
17769 active_during_drop, 2,
17770 "the blocking capture's work registration must remain active until its destructor returns"
17771 );
17772 assert!(
17773 cancellation_blocked,
17774 "terminal cancellation must wait for capture destruction"
17775 );
17776 assert!(futures::executor::block_on(cancelled).is_err());
17777 assert!(futures::executor::block_on(pending).is_err());
17778 assert_eq!(backend.work.state.lock().unwrap().active, 1);
17779
17780 release_holder_tx.send(()).unwrap();
17781 holder.join().unwrap();
17782 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17783 }
17784
17785 #[test]
17786 fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
17787 reset_current_thread_one_shot_host_dispatches();
17788 let metrics = Arc::new(RuntimeMetrics::default());
17789 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17790 Arc::clone(&metrics),
17791 accept_only_first_current_thread_host_dispatch,
17792 ));
17793
17794 let scheduler = Arc::clone(&executor);
17795 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
17796 scheduler.schedule(runnable)
17797 });
17798 executor.schedule(runnable);
17799 let failed_dispatch = current_thread_dispatch(&executor);
17800
17801 executor.cancel_host_dispatch_for_test(failed_dispatch);
17802
17803 assert_eq!(
17804 current_thread_one_shot_host_dispatches(),
17805 2,
17806 "the original and its one bounded replacement must be attempted"
17807 );
17808 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17809 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17810 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17811 assert!(
17812 futures::executor::block_on(task.fallible()).is_none(),
17813 "an unavailable replacement host must settle the original task as cancelled"
17814 );
17815 }
17816
17817 enum TestCurrentThreadTaskDriverBehavior {
17818 Healthy,
17819 PanicInLivenessAndSweep,
17820 PanicInDispatchAndSweep,
17821 }
17822
17823 struct TestCurrentThreadTaskDriver {
17824 behavior: TestCurrentThreadTaskDriverBehavior,
17825 dispatched: AtomicBool,
17826 }
17827
17828 #[derive(Default)]
17829 struct ManualCurrentThreadTaskDriver {
17830 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17831 }
17832
17833 struct FailDuringAuthoritativePanicPublication;
17834 struct FailDuringLateRegistrationPublication;
17835 struct FailDuringNestedUnwindPublication;
17836 struct FailDuringReplacementRepublishPublication;
17837 struct FirstHostileCurrentThreadPanic;
17838 struct LaterHostileCurrentThreadPanic;
17839
17840 impl Drop for FirstHostileCurrentThreadPanic {
17841 fn drop(&mut self) {
17842 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17843 panic!("the first retained CurrentThread panic payload was dropped");
17844 }
17845 }
17846
17847 impl Drop for LaterHostileCurrentThreadPanic {
17848 fn drop(&mut self) {
17849 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17850 panic!("a later CurrentThread panic payload escaped containment");
17851 }
17852 }
17853
17854 impl ManualCurrentThreadTaskDriver {
17855 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17856 self.deliveries.lock().unwrap().clone()
17857 }
17858 }
17859
17860 impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
17861 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17862 self.deliveries.lock().unwrap().push(delivery);
17863 true
17864 }
17865 }
17866
17867 struct SynchronouslyFailingCurrentThreadTaskDriver {
17872 registry: &'static CurrentThreadTaskDriverRegistry,
17873 fail: AtomicBool,
17874 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17875 }
17876
17877 impl SynchronouslyFailingCurrentThreadTaskDriver {
17878 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17879 Self {
17880 registry,
17881 fail: AtomicBool::new(false),
17882 deliveries: Mutex::default(),
17883 }
17884 }
17885
17886 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17887 self.deliveries.lock().unwrap().clone()
17888 }
17889 }
17890
17891 impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
17892 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17893 self.deliveries.lock().unwrap().push(delivery);
17894 if self.fail.load(Ordering::SeqCst) {
17895 let registry = self.registry;
17896 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17897 .join()
17898 .expect("the failing helper must not panic");
17899 assert!(
17900 completion.failed_dispatches.is_empty(),
17901 "the failed delivery must remain provisional until its publication retires"
17902 );
17903 }
17904 true
17905 }
17906 }
17907
17908 type DeliveryHook = Box<dyn FnOnce() + Send>;
17916
17917 struct BoundedFailureCurrentThreadTaskDriver {
17918 registry: &'static CurrentThreadTaskDriverRegistry,
17919 fail_budget: AtomicUsize,
17920 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17921 during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
17922 }
17923
17924 impl BoundedFailureCurrentThreadTaskDriver {
17925 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17926 Self {
17927 registry,
17928 fail_budget: AtomicUsize::new(0),
17929 deliveries: Mutex::default(),
17930 during_delivery: Mutex::default(),
17931 }
17932 }
17933
17934 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17935 self.deliveries.lock().unwrap().clone()
17936 }
17937
17938 fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
17943 *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
17944 }
17945 }
17946
17947 impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
17948 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17949 let index = {
17950 let mut deliveries = self.deliveries.lock().unwrap();
17951 deliveries.push(delivery);
17952 deliveries.len() - 1
17953 };
17954 let hook = {
17955 let mut slot = self.during_delivery.lock().unwrap();
17956 if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
17957 slot.take().map(|(_, hook)| hook)
17958 } else {
17959 None
17960 }
17961 };
17962 if let Some(hook) = hook {
17963 hook();
17964 }
17965 let budget = self.fail_budget.load(Ordering::SeqCst);
17966 if budget != 0 {
17967 self.fail_budget.store(budget - 1, Ordering::SeqCst);
17968 let registry = self.registry;
17969 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17970 .join()
17971 .expect("the failing helper must not panic");
17972 assert!(
17973 completion.failed_dispatches.is_empty(),
17974 "the failed delivery must remain provisional until its publication retires"
17975 );
17976 }
17977 true
17978 }
17979 }
17980
17981 struct SustainedProducerGate {
17988 remaining: usize,
17989 queue_one: std::sync::mpsc::Sender<()>,
17990 queued: std::sync::mpsc::Receiver<()>,
17991 }
17992
17993 struct SustainedProducerCurrentThreadTaskDriver {
17994 registry: &'static CurrentThreadTaskDriverRegistry,
17995 fail_budget: AtomicUsize,
17998 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17999 producer: Mutex<Option<SustainedProducerGate>>,
18000 }
18001
18002 impl SustainedProducerCurrentThreadTaskDriver {
18003 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
18004 Self {
18005 registry,
18006 fail_budget: AtomicUsize::new(0),
18007 deliveries: Mutex::default(),
18008 producer: Mutex::default(),
18009 }
18010 }
18011
18012 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
18013 self.deliveries.lock().unwrap().clone()
18014 }
18015
18016 fn arm_producer(
18017 &self,
18018 remaining: usize,
18019 queue_one: std::sync::mpsc::Sender<()>,
18020 queued: std::sync::mpsc::Receiver<()>,
18021 ) {
18022 *self.producer.lock().unwrap() = Some(SustainedProducerGate {
18023 remaining,
18024 queue_one,
18025 queued,
18026 });
18027 }
18028
18029 fn disarm_producer(&self) -> usize {
18032 self
18033 .producer
18034 .lock()
18035 .unwrap()
18036 .take()
18037 .map_or(0, |gate| gate.remaining)
18038 }
18039 }
18040
18041 impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
18042 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18043 self.deliveries.lock().unwrap().push(delivery);
18044 {
18045 let mut slot = self.producer.lock().unwrap();
18046 if let Some(gate) = slot.as_mut()
18047 && gate.remaining != 0
18048 {
18049 gate.remaining -= 1;
18050 gate.queue_one.send(()).expect("the producer must be live");
18051 gate
18052 .queued
18053 .recv_timeout(std::time::Duration::from_secs(5))
18054 .expect("the producer must queue one runnable per publication");
18055 }
18056 }
18057 let budget = self.fail_budget.load(Ordering::SeqCst);
18058 if budget != 0 {
18059 if budget != usize::MAX {
18060 self.fail_budget.store(budget - 1, Ordering::SeqCst);
18061 }
18062 let completion = self.registry.finish_delivery(delivery, true);
18063 assert!(
18064 completion.failed_dispatches.is_empty(),
18065 "the failed delivery must remain provisional until its publication retires"
18066 );
18067 }
18068 true
18069 }
18070 }
18071
18072 impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
18073 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18074 let completion =
18075 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
18076 assert!(
18077 completion.failed_dispatches.is_empty(),
18078 "the failed delivery must remain provisional until its publication retires"
18079 );
18080 true
18081 }
18082 }
18083
18084 impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
18085 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18086 let completion =
18087 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
18088 assert!(
18089 completion.failed_dispatches.is_empty(),
18090 "the failed delivery must remain provisional until its publication retires"
18091 );
18092 true
18093 }
18094 }
18095
18096 impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
18097 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18098 let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
18099 assert!(
18100 completion.failed_dispatches.is_empty(),
18101 "the failed delivery must remain provisional until its publication retires"
18102 );
18103 true
18104 }
18105 }
18106
18107 impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
18108 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18109 let completion =
18110 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
18111 assert!(
18112 completion.failed_dispatches.is_empty(),
18113 "the failed delivery must remain provisional until its publication retires"
18114 );
18115 true
18116 }
18117 }
18118
18119 struct ReentrantCurrentThreadTaskDriver {
18120 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18121 registration: Mutex<Option<CurrentThreadTaskDriverId>>,
18122 }
18123
18124 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
18125 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18126 true
18127 }
18128
18129 fn is_live(&self) -> bool {
18130 let registration = *self.registration.lock().unwrap();
18131 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
18132 registry.unregister(id);
18133 }
18134 false
18135 }
18136 }
18137
18138 struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
18139 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18140 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18141 }
18142
18143 impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
18144 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18145 true
18146 }
18147
18148 fn is_live(&self) -> bool {
18149 if let Some(registry) = self.registry.upgrade() {
18150 for registration in self.registrations.lock().unwrap().iter().copied() {
18151 let completion = registry.unregister(registration);
18152 assert!(completion.failed_dispatches.is_empty());
18153 assert!(!completion.redispatch);
18154 }
18155 }
18156 false
18157 }
18158 }
18159
18160 struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18161 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18162 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18163 drops: Arc<AtomicUsize>,
18164 }
18165
18166 impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18167 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18168 if let Some(registry) = self.registry.upgrade() {
18169 for registration in self.registrations.lock().unwrap().iter().copied() {
18170 let completion = registry.unregister(registration);
18171 assert!(completion.failed_dispatches.is_empty());
18172 assert!(!completion.redispatch);
18173 }
18174 }
18175 true
18176 }
18177 }
18178
18179 impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18180 fn drop(&mut self) {
18181 self.drops.fetch_add(1, Ordering::SeqCst);
18182 panic!("intentional reentrant CurrentThread task-driver destructor panic");
18183 }
18184 }
18185
18186 struct PanickingDropCurrentThreadTaskDriver {
18187 drops: Arc<AtomicUsize>,
18188 }
18189
18190 impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
18191 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18192 true
18193 }
18194 }
18195
18196 impl Drop for PanickingDropCurrentThreadTaskDriver {
18197 fn drop(&mut self) {
18198 self.drops.fetch_add(1, Ordering::SeqCst);
18199 panic!("intentional CurrentThread task-driver destructor panic");
18200 }
18201 }
18202
18203 struct ReentrantCurrentThreadTaskDriverDrop {
18204 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18205 fallback: CurrentThreadTaskDriverId,
18206 }
18207
18208 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
18209 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18210 true
18211 }
18212 }
18213
18214 impl Drop for ReentrantCurrentThreadTaskDriverDrop {
18215 fn drop(&mut self) {
18216 if let Some(registry) = self.registry.upgrade() {
18217 registry.unregister(self.fallback);
18218 }
18219 }
18220 }
18221
18222 impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
18223 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18224 assert!(
18225 !matches!(
18226 self.behavior,
18227 TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
18228 ),
18229 "intentional task-driver dispatch panic"
18230 );
18231 self.dispatched.store(true, Ordering::SeqCst);
18232 true
18233 }
18234
18235 fn is_live(&self) -> bool {
18236 assert!(
18237 !matches!(
18238 self.behavior,
18239 TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
18240 ),
18241 "intentional task-driver liveness panic"
18242 );
18243 true
18244 }
18245
18246 fn on_swept(&self) {
18247 assert!(
18248 matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
18249 "intentional task-driver sweep panic"
18250 );
18251 }
18252 }
18253
18254 #[cfg(panic = "unwind")]
18255 #[test]
18256 fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
18257 let registry = CurrentThreadTaskDriverRegistry::default();
18258 let fallback = Arc::new(TestCurrentThreadTaskDriver {
18259 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18260 dispatched: AtomicBool::new(false),
18261 });
18262 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18263 registry.register(Arc::new(TestCurrentThreadTaskDriver {
18264 behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
18265 dispatched: AtomicBool::new(false),
18266 }));
18267 registry.register(Arc::new(TestCurrentThreadTaskDriver {
18268 behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
18269 dispatched: AtomicBool::new(false),
18270 }));
18271
18272 assert_eq!(
18273 registry.dispatch(37),
18274 CurrentThreadHostDispatchResult::Accepted,
18275 "a panicking newest host must fall back to a live host"
18276 );
18277 assert!(fallback.dispatched.load(Ordering::SeqCst));
18278 }
18279
18280 #[cfg(panic = "unwind")]
18281 #[test]
18282 fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
18283 let registry = CurrentThreadTaskDriverRegistry::default();
18284 let first = Arc::new(ManualCurrentThreadTaskDriver::default());
18285 let second = Arc::new(ManualCurrentThreadTaskDriver::default());
18286 registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18287 registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18288 registry
18289 .next_delivery
18290 .store(u64::MAX - 1, Ordering::Relaxed);
18291
18292 let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
18293 assert!(
18294 publication.is_err(),
18295 "delivery-id exhaustion on the second host must unwind the publication"
18296 );
18297 {
18298 let state = registry
18299 .state
18300 .lock()
18301 .unwrap_or_else(std::sync::PoisonError::into_inner);
18302 assert!(
18303 !state.dispatches.contains_key(&38),
18304 "unwind cleanup must release the exact registry publication barrier"
18305 );
18306 assert!(
18307 state
18308 .entries
18309 .values()
18310 .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
18311 "failed transactional reservation must leave every physical host slot clean"
18312 );
18313 }
18314 assert!(first.deliveries().is_empty());
18315 assert!(second.deliveries().is_empty());
18316
18317 registry.next_delivery.store(0, Ordering::Relaxed);
18318 assert_eq!(
18319 registry.dispatch(38),
18320 CurrentThreadHostDispatchResult::Accepted
18321 );
18322 let first_delivery = first.deliveries()[0];
18323 let second_delivery = second.deliveries()[0];
18324 assert_eq!(
18325 registry.claim_delivery(first_delivery.capability()),
18326 Some(38)
18327 );
18328 assert_eq!(
18329 registry.claim_delivery(second_delivery.capability()),
18330 Some(38)
18331 );
18332 registry.mark_serviced(38);
18333 assert!(
18334 registry
18335 .finish_delivery(first_delivery, false)
18336 .failed_dispatches
18337 .is_empty()
18338 );
18339 assert!(
18340 registry
18341 .finish_delivery(second_delivery, false)
18342 .failed_dispatches
18343 .is_empty()
18344 );
18345
18346 assert_eq!(
18347 registry.dispatch(39),
18348 CurrentThreadHostDispatchResult::Accepted
18349 );
18350 let first_later = first.deliveries()[1];
18351 let second_later = second.deliveries()[1];
18352 assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
18353 assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
18354 registry.mark_serviced(39);
18355 assert!(
18356 registry
18357 .finish_delivery(first_later, false)
18358 .failed_dispatches
18359 .is_empty()
18360 );
18361 assert!(
18362 registry
18363 .finish_delivery(second_later, false)
18364 .failed_dispatches
18365 .is_empty()
18366 );
18367 assert!(
18368 registry
18369 .state
18370 .lock()
18371 .unwrap_or_else(std::sync::PoisonError::into_inner)
18372 .dispatches
18373 .is_empty()
18374 );
18375 let state = registry
18376 .state
18377 .lock()
18378 .unwrap_or_else(std::sync::PoisonError::into_inner);
18379 assert!(
18380 state
18381 .entries
18382 .values()
18383 .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
18384 );
18385 }
18386
18387 #[test]
18388 fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
18389 let registry = CurrentThreadTaskDriverRegistry::default();
18390 let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
18391 let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
18392 let dispatch = 40;
18393
18394 assert_eq!(
18395 registry.dispatch(dispatch),
18396 CurrentThreadHostDispatchResult::Accepted
18397 );
18398 let failed_delivery = failed.deliveries()[0];
18399 let failure = current_thread_dispatch_failure(dispatch, 0);
18400 assert_eq!(
18401 registry
18402 .finish_delivery(failed_delivery, true)
18403 .failed_dispatches,
18404 vec![failure]
18405 );
18406 registry.unregister(failed_id);
18407
18408 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18409 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18410 assert!(
18411 registry.rearm_failed_publication(failure),
18412 "a closed unreferenced dispatch must reopen for its armed publication owner"
18413 );
18414 assert_eq!(
18415 registry.dispatch(dispatch),
18416 CurrentThreadHostDispatchResult::Accepted
18417 );
18418 let replacement_delivery = replacement.deliveries()[0];
18419 assert_eq!(replacement_delivery.dispatch, dispatch);
18420 assert_eq!(
18421 registry.claim_delivery(replacement_delivery.capability()),
18422 Some(dispatch)
18423 );
18424 registry.mark_serviced(dispatch);
18425 assert!(
18426 registry
18427 .finish_delivery(replacement_delivery, false)
18428 .failed_dispatches
18429 .is_empty()
18430 );
18431 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18432 }
18433
18434 #[test]
18435 fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
18436 struct RacingTaskDriver {
18437 executor: Arc<CurrentThreadExecutor>,
18438 drives: bool,
18439 dispatches: Mutex<Vec<u64>>,
18440 }
18441
18442 impl CurrentThreadTaskDriver for RacingTaskDriver {
18443 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18444 self.dispatches.lock().unwrap().push(delivery.dispatch);
18445 if self.drives {
18446 self
18447 .executor
18448 .drive_host_turn_for_dispatch(delivery.dispatch);
18449 }
18450 true
18451 }
18452 }
18453
18454 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
18455 Arc::new(RuntimeMetrics::default()),
18456 accept_current_thread_host_dispatch,
18457 ));
18458 let polls = Arc::new(AtomicUsize::new(0));
18459 let task_polls = Arc::clone(&polls);
18460 let scheduler = Arc::clone(&executor);
18461 let (runnable, task) = async_task::spawn(
18462 async move {
18463 task_polls.fetch_add(1, Ordering::SeqCst);
18464 },
18465 move |runnable| scheduler.schedule(runnable),
18466 );
18467 executor.schedule(runnable);
18468 let dispatch = current_thread_dispatch(&executor);
18469
18470 let registry = CurrentThreadTaskDriverRegistry::default();
18471 let responsive = Arc::new(RacingTaskDriver {
18472 executor: Arc::clone(&executor),
18473 drives: true,
18474 dispatches: Mutex::new(Vec::new()),
18475 });
18476 let starved = Arc::new(RacingTaskDriver {
18477 executor: Arc::clone(&executor),
18478 drives: false,
18479 dispatches: Mutex::new(Vec::new()),
18480 });
18481 registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
18482 registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
18484
18485 assert_eq!(
18486 registry.dispatch(dispatch),
18487 CurrentThreadHostDispatchResult::Accepted
18488 );
18489 assert_eq!(
18490 responsive.dispatches.lock().unwrap().as_slice(),
18491 &[dispatch]
18492 );
18493 assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
18494 assert_eq!(
18495 polls.load(Ordering::SeqCst),
18496 1,
18497 "the responsive older host must win even when the newest host is starved"
18498 );
18499
18500 executor.drive_host_turn_for_dispatch(dispatch);
18503 assert_eq!(polls.load(Ordering::SeqCst), 1);
18504 futures::executor::block_on(task);
18505 }
18506
18507 #[test]
18508 fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
18509 const HOSTS: usize = 1_024;
18510
18511 let registry = CurrentThreadTaskDriverRegistry::default();
18512 let mut ids = Vec::with_capacity(HOSTS);
18513 let mut drivers = Vec::with_capacity(HOSTS);
18514 for _ in 0..HOSTS {
18515 let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
18516 ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
18517 drivers.push(driver);
18518 }
18519
18520 let dispatch = u64::MAX - 300;
18521 assert_eq!(
18522 registry.dispatch(dispatch),
18523 CurrentThreadHostDispatchResult::Accepted
18524 );
18525 let deliveries = drivers
18526 .iter()
18527 .map(|driver| driver.deliveries()[0])
18528 .collect::<Vec<_>>();
18529 {
18530 let state = registry
18531 .state
18532 .lock()
18533 .unwrap_or_else(std::sync::PoisonError::into_inner);
18534 assert_eq!(state.delivery_drivers.len(), HOSTS);
18535 assert_eq!(
18536 state
18537 .dispatches
18538 .get(&dispatch)
18539 .map(|state| state.references),
18540 Some(HOSTS)
18541 );
18542 assert_eq!(
18543 state
18544 .ordered_snapshot()
18545 .iter()
18546 .map(|(id, _)| *id)
18547 .collect::<Vec<_>>(),
18548 ids,
18549 "keyed driver lookup must retain registration order"
18550 );
18551 }
18552
18553 for delivery in &deliveries {
18554 assert_eq!(
18555 registry.claim_delivery(delivery.capability()),
18556 Some(dispatch)
18557 );
18558 }
18559 registry.mark_serviced(dispatch);
18560 for delivery in deliveries {
18561 assert!(
18562 registry
18563 .finish_delivery(delivery, false)
18564 .failed_dispatches
18565 .is_empty()
18566 );
18567 }
18568 {
18569 let state = registry
18570 .state
18571 .lock()
18572 .unwrap_or_else(std::sync::PoisonError::into_inner);
18573 assert!(state.delivery_drivers.is_empty());
18574 assert!(state.dispatches.is_empty());
18575 }
18576
18577 let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
18578 for id in &removed {
18579 let completion = registry.unregister(*id);
18580 assert!(completion.failed_dispatches.is_empty());
18581 assert!(!completion.redispatch);
18582 }
18583 let expected = ids
18584 .iter()
18585 .copied()
18586 .filter(|id| !removed.contains(id))
18587 .collect::<Vec<_>>();
18588 let live = registry.live().live;
18589 assert_eq!(
18590 live
18591 .as_slice()
18592 .iter()
18593 .map(|(id, _)| *id)
18594 .collect::<Vec<_>>(),
18595 expected,
18596 "O(1) removals must preserve the relative order of surviving host registrations"
18597 );
18598 drop(live);
18599
18600 for id in expected {
18601 registry.unregister(id);
18602 }
18603 let state = registry
18604 .state
18605 .lock()
18606 .unwrap_or_else(std::sync::PoisonError::into_inner);
18607 assert!(state.entries.is_empty());
18608 assert!(state.oldest.is_none());
18609 assert!(state.newest.is_none());
18610 }
18611
18612 #[test]
18613 fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
18614 let registry = CurrentThreadTaskDriverRegistry::default();
18615 let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
18616 let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
18617 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
18618 registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
18619 registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
18620 registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
18621
18622 let dispatch = 41;
18623 assert_eq!(
18624 registry.dispatch(dispatch),
18625 CurrentThreadHostDispatchResult::Accepted
18626 );
18627 let failing_delivery = failing.deliveries()[0];
18628 let delayed_delivery = delayed.deliveries()[0];
18629 let healthy_delivery = healthy.deliveries()[0];
18630 assert_eq!(failing_delivery.dispatch, dispatch);
18631 assert_eq!(delayed_delivery.dispatch, dispatch);
18632 assert_eq!(healthy_delivery.dispatch, dispatch);
18633
18634 assert!(
18635 registry
18636 .finish_delivery(failing_delivery, true)
18637 .failed_dispatches
18638 .is_empty(),
18639 "one host failure must not recover while other accepted attempts remain"
18640 );
18641 assert_eq!(
18642 registry.claim_delivery(healthy_delivery.capability()),
18643 Some(dispatch)
18644 );
18645 registry.mark_serviced(dispatch);
18646 assert!(
18647 registry
18648 .finish_delivery(healthy_delivery, false)
18649 .failed_dispatches
18650 .is_empty()
18651 );
18652 assert_eq!(
18653 registry.claim_delivery(delayed_delivery.capability()),
18654 None,
18655 "a delayed losing delivery must become stale after another host services the dispatch"
18656 );
18657 assert!(
18658 registry
18659 .finish_delivery(delayed_delivery, true)
18660 .failed_dispatches
18661 .is_empty(),
18662 "a stale losing failure must not recover or cancel a serviced dispatch"
18663 );
18664 assert!(
18665 registry.state.lock().unwrap().dispatches.is_empty(),
18666 "all per-dispatch acknowledgement state must retire"
18667 );
18668 }
18669
18670 #[test]
18671 fn current_thread_unclaimed_callback_success_is_delivery_failure() {
18672 let registry = CurrentThreadTaskDriverRegistry::default();
18673 let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
18674 registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
18675
18676 let dispatch = 56;
18677 assert_eq!(
18678 registry.dispatch(dispatch),
18679 CurrentThreadHostDispatchResult::Accepted
18680 );
18681 let delivery = no_op.deliveries()[0];
18682 let completion = registry.finish_delivery(delivery, false);
18683 let failure = current_thread_dispatch_failure(dispatch, 0);
18684 assert_eq!(
18685 completion.failed_dispatches,
18686 vec![failure],
18687 "a callback return without an exact capability claim must enter recovery"
18688 );
18689 assert!(
18690 registry
18691 .state
18692 .lock()
18693 .unwrap()
18694 .dispatches
18695 .get(&dispatch)
18696 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18697 );
18698 registry.finish_cancellation(failure);
18699 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18700 }
18701
18702 #[test]
18703 fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
18704 struct FailDuringPublication {
18705 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18706 }
18707
18708 impl CurrentThreadTaskDriver for FailDuringPublication {
18709 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18710 let completion = self
18711 .registry
18712 .upgrade()
18713 .expect("the registry must outlive publication")
18714 .finish_delivery(delivery, true);
18715 assert!(
18716 completion.failed_dispatches.is_empty(),
18717 "failure must remain provisional until the broadcast closes"
18718 );
18719 true
18720 }
18721 }
18722
18723 struct RejectProvisional;
18724
18725 impl CurrentThreadTaskDriver for RejectProvisional {
18726 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18727 false
18728 }
18729 }
18730
18731 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18732 registry.register(Arc::new(RejectProvisional));
18733 registry.register(Arc::new(FailDuringPublication {
18736 registry: Arc::downgrade(®istry),
18737 }));
18738
18739 let failure = current_thread_dispatch_failure(57, 0);
18740 assert_eq!(
18741 registry.dispatch(57),
18742 CurrentThreadHostDispatchResult::Failed(failure),
18743 "the final provisional rejection must expose the earlier accepted failure"
18744 );
18745 assert!(
18746 registry
18747 .state
18748 .lock()
18749 .unwrap()
18750 .dispatches
18751 .get(&57)
18752 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18753 );
18754 assert_eq!(
18755 registry.dispatch(57),
18756 CurrentThreadHostDispatchResult::Failed(failure),
18757 "a racing replacement publication must preserve terminal failure until cancellation runs"
18758 );
18759 registry.finish_cancellation(failure);
18760 }
18761
18762 #[test]
18763 fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
18764 struct RejectProvisional;
18765
18766 impl CurrentThreadTaskDriver for RejectProvisional {
18767 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18768 false
18769 }
18770 }
18771
18772 let registry = CurrentThreadTaskDriverRegistry::default();
18773 registry.register(Arc::new(RejectProvisional));
18774 registry.register(Arc::new(RejectProvisional));
18775
18776 assert_eq!(
18777 registry.dispatch(58),
18778 CurrentThreadHostDispatchResult::Unavailable
18779 );
18780 assert!(
18781 registry.state.lock().unwrap().dispatches.is_empty(),
18782 "a broadcast with no accepted attempt must not enter failure recovery"
18783 );
18784 }
18785
18786 #[test]
18787 fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
18788 let registry = CurrentThreadTaskDriverRegistry::default();
18789 let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
18790 let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
18791
18792 assert_eq!(
18793 registry.dispatch(1),
18794 CurrentThreadHostDispatchResult::Accepted
18795 );
18796 let first = blocked.deliveries()[0];
18797 for dispatch in 2..=256 {
18798 assert_eq!(
18799 registry.dispatch(dispatch),
18800 CurrentThreadHostDispatchResult::Accepted
18801 );
18802 assert_eq!(
18803 blocked.deliveries().len(),
18804 1,
18805 "a blocked host must never receive a second queued or in-flight callback"
18806 );
18807 }
18808 {
18809 let state = registry.state.lock().unwrap();
18810 let entry = state
18811 .entries
18812 .get(&blocked_id)
18813 .expect("the blocked host must remain registered");
18814 assert_eq!(entry.pending, Some(256));
18815 assert_eq!(
18816 state.dispatches.len(),
18817 2,
18818 "only the in-flight and latest pending dispatches may retain registry state"
18819 );
18820 }
18821
18822 assert_eq!(registry.claim_delivery(first.capability()), Some(1));
18823 registry.mark_serviced(1);
18824 let completion = registry.finish_delivery(first, false);
18825 assert!(completion.failed_dispatches.is_empty());
18826 assert!(completion.redispatch);
18827 assert_eq!(
18828 registry.dispatch(256),
18829 CurrentThreadHostDispatchResult::Accepted
18830 );
18831 let deliveries = blocked.deliveries();
18832 assert_eq!(deliveries.len(), 2);
18833 assert_eq!(
18834 deliveries[1].dispatch, 256,
18835 "completion must schedule exactly the latest coalesced capability"
18836 );
18837 assert_eq!(registry.claim_delivery(first.capability()), None);
18838 assert_eq!(
18839 registry.claim_delivery(deliveries[1].capability()),
18840 Some(256)
18841 );
18842 registry.mark_serviced(256);
18843 assert!(
18844 registry
18845 .finish_delivery(deliveries[1], false)
18846 .failed_dispatches
18847 .is_empty()
18848 );
18849 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18850 }
18851
18852 #[test]
18853 fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
18854 struct RejectAfterFirst {
18855 calls: AtomicUsize,
18856 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
18857 }
18858
18859 impl CurrentThreadTaskDriver for RejectAfterFirst {
18860 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18861 self.deliveries.lock().unwrap().push(delivery);
18862 self.calls.fetch_add(1, Ordering::SeqCst) == 0
18863 }
18864 }
18865
18866 let registry = CurrentThreadTaskDriverRegistry::default();
18867 let host = Arc::new(RejectAfterFirst {
18868 calls: AtomicUsize::new(0),
18869 deliveries: Mutex::new(Vec::new()),
18870 });
18871 registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18872 assert_eq!(
18873 registry.dispatch(11),
18874 CurrentThreadHostDispatchResult::Accepted
18875 );
18876 let first = host.deliveries.lock().unwrap()[0];
18877 assert_eq!(
18878 registry.dispatch(12),
18879 CurrentThreadHostDispatchResult::Accepted
18880 );
18881
18882 assert_eq!(registry.claim_delivery(first.capability()), Some(11));
18883 registry.mark_serviced(11);
18884 let completion = registry.finish_delivery(first, false);
18885 assert!(completion.redispatch);
18886 let failure = current_thread_dispatch_failure(12, 0);
18887 assert_eq!(
18888 registry.dispatch(12),
18889 CurrentThreadHostDispatchResult::Failed(failure),
18890 "an accepted coalesced capability must report failure if its eventual host queue rejects it"
18891 );
18892 assert!(
18893 registry
18894 .state
18895 .lock()
18896 .unwrap()
18897 .dispatches
18898 .get(&12)
18899 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18900 );
18901 registry.finish_cancellation(failure);
18902 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18903 }
18904
18905 #[test]
18906 fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
18907 let registry = CurrentThreadTaskDriverRegistry::default();
18908 let old = Arc::new(ManualCurrentThreadTaskDriver::default());
18909 let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
18910 assert_eq!(
18911 registry.dispatch(73),
18912 CurrentThreadHostDispatchResult::Accepted
18913 );
18914 let old_delivery = old.deliveries()[0];
18915
18916 let completion = registry.unregister(old_id);
18917 let failure = current_thread_dispatch_failure(73, 0);
18918 assert_eq!(
18919 completion.failed_dispatches,
18920 vec![failure],
18921 "unregistering the final accepting host must retire its exact dispatch"
18922 );
18923 registry.finish_cancellation(failure);
18924 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18925 let replacement_id =
18926 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18927 assert_ne!(replacement_id, old_id);
18928 assert_eq!(
18929 registry.dispatch(74),
18930 CurrentThreadHostDispatchResult::Accepted
18931 );
18932 let replacement_delivery = replacement.deliveries()[0];
18933 assert_ne!(replacement_delivery.capability(), old_delivery.capability());
18934
18935 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18936 assert!(
18937 registry
18938 .finish_delivery(old_delivery, true)
18939 .failed_dispatches
18940 .is_empty()
18941 );
18942 assert!(
18943 registry
18944 .fail_delivery(old_delivery.capability())
18945 .failed_dispatches
18946 .is_empty()
18947 );
18948 assert_eq!(
18949 registry.claim_delivery(replacement_delivery.capability()),
18950 Some(74)
18951 );
18952 registry.mark_serviced(74);
18953 assert!(
18954 registry
18955 .finish_delivery(replacement_delivery, false)
18956 .failed_dispatches
18957 .is_empty()
18958 );
18959 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18960 }
18961
18962 #[test]
18963 fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
18964 struct QueuePendingThenReject {
18965 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18966 }
18967
18968 impl CurrentThreadTaskDriver for QueuePendingThenReject {
18969 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18970 assert_eq!(delivery.dispatch, 81);
18971 let completion = self
18972 .registry
18973 .upgrade()
18974 .expect("the registry must outlive publication")
18975 .dispatch_with_completion(82);
18976 assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
18977 assert!(completion.failed_dispatches.is_empty());
18978 false
18979 }
18980 }
18981
18982 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18983 registry.register(Arc::new(QueuePendingThenReject {
18984 registry: Arc::downgrade(®istry),
18985 }));
18986
18987 let completion = registry.dispatch_with_completion(81);
18988 assert_eq!(
18989 completion.result,
18990 CurrentThreadHostDispatchResult::Unavailable
18991 );
18992 let failure = current_thread_dispatch_failure(82, 0);
18993 assert_eq!(
18994 completion.failed_dispatches,
18995 vec![failure],
18996 "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18997 );
18998 assert!(
18999 registry
19000 .state
19001 .lock()
19002 .unwrap()
19003 .dispatches
19004 .get(&82)
19005 .is_some_and(CurrentThreadDispatchState::is_cancelling)
19006 );
19007 registry.finish_cancellation(failure);
19008 assert!(registry.state.lock().unwrap().dispatches.is_empty());
19009 }
19010
19011 #[test]
19012 fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
19013 let registry = CurrentThreadTaskDriverRegistry::default();
19014 let host = Arc::new(ManualCurrentThreadTaskDriver::default());
19015 let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
19016 assert_eq!(
19017 registry.dispatch(91),
19018 CurrentThreadHostDispatchResult::Accepted
19019 );
19020 let old_delivery = host.deliveries()[0];
19021 assert_eq!(
19022 registry.dispatch(92),
19023 CurrentThreadHostDispatchResult::Accepted
19024 );
19025
19026 registry.retire_for_shutdown();
19027 {
19028 let state = registry.state.lock().unwrap();
19029 assert!(state.dispatches.is_empty());
19030 let entry = state
19031 .entries
19032 .get(&host_id)
19033 .expect("the host must remain registered");
19034 assert_eq!(entry.pending, None);
19035 assert!(
19036 entry.delivery.is_some(),
19037 "the physical callback remains single-flight until it actually retires"
19038 );
19039 }
19040 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
19041
19042 assert_eq!(
19043 registry.dispatch(93),
19044 CurrentThreadHostDispatchResult::Accepted
19045 );
19046 assert_eq!(host.deliveries().len(), 1);
19047 let completion = registry.finish_delivery(old_delivery, true);
19048 assert!(
19049 completion.failed_dispatches.is_empty(),
19050 "a retired-generation failure must not cancel restarted work"
19051 );
19052 assert!(completion.redispatch);
19053 assert_eq!(
19054 registry.dispatch(93),
19055 CurrentThreadHostDispatchResult::Accepted
19056 );
19057 let restarted_delivery = host.deliveries()[1];
19058 assert_eq!(restarted_delivery.dispatch, 93);
19059 assert_eq!(
19060 registry.claim_delivery(restarted_delivery.capability()),
19061 Some(93)
19062 );
19063 registry.mark_serviced(93);
19064 assert!(
19065 registry
19066 .finish_delivery(restarted_delivery, false)
19067 .failed_dispatches
19068 .is_empty()
19069 );
19070
19071 registry.unregister(host_id);
19072 let state = registry.state.lock().unwrap();
19073 assert!(state.entries.is_empty());
19074 assert!(state.dispatches.is_empty());
19075 }
19076
19077 #[cfg(napi_runtime_os_threads)]
19078 #[test]
19079 fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
19080 use std::{sync::mpsc, time::Duration};
19081
19082 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19083 let fallback = Arc::new(TestCurrentThreadTaskDriver {
19084 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
19085 dispatched: AtomicBool::new(false),
19086 });
19087 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19088 let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
19089 registry: Arc::downgrade(®istry),
19090 registration: Mutex::new(None),
19091 });
19092 let reentrant_id =
19093 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19094 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
19095
19096 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
19097 let selection_registry = Arc::clone(®istry);
19098 let selection_thread = std::thread::spawn(move || {
19099 selection_tx
19100 .send(selection_registry.current().map(|(id, _)| id))
19101 .unwrap();
19102 });
19103 assert_eq!(
19104 selection_rx
19105 .recv_timeout(Duration::from_secs(10))
19106 .expect("is_live re-entry deadlocked"),
19107 Some(fallback_id)
19108 );
19109 selection_thread.join().unwrap();
19110
19111 let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
19112 Arc::new(ReentrantCurrentThreadTaskDriverDrop {
19113 registry: Arc::downgrade(®istry),
19114 fallback: fallback_id,
19115 });
19116 let drop_id = registry.register(Arc::clone(&drop_driver));
19117 drop(drop_driver);
19118
19119 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
19120 let drop_registry = Arc::clone(®istry);
19121 let drop_thread = std::thread::spawn(move || {
19122 drop_registry.unregister(drop_id);
19123 drop_tx.send(()).unwrap();
19124 });
19125 drop_rx
19126 .recv_timeout(Duration::from_secs(10))
19127 .expect("driver destructor re-entry deadlocked");
19128 drop_thread.join().unwrap();
19129 assert!(
19130 registry.current().is_none(),
19131 "the destructor must unregister the fallback"
19132 );
19133 }
19134
19135 #[cfg(not(target_family = "wasm"))]
19136 #[test]
19137 fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
19138 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
19139
19140 if std::env::var_os(CHILD_ENV).is_some() {
19141 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19142 let fallback = Arc::new(TestCurrentThreadTaskDriver {
19143 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
19144 dispatched: AtomicBool::new(false),
19145 });
19146 let fallback_id =
19147 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19148 let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
19149 registry: Arc::downgrade(®istry),
19150 registrations: Mutex::new(Vec::new()),
19151 });
19152 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19153
19154 let first_drops = Arc::new(AtomicUsize::new(0));
19155 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19156 drops: Arc::clone(&first_drops),
19157 });
19158 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19159 drop(first);
19160
19161 let second_drops = Arc::new(AtomicUsize::new(0));
19162 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19163 drops: Arc::clone(&second_drops),
19164 });
19165 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19166 drop(second);
19167 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
19168
19169 assert_eq!(
19170 registry.current().map(|(id, _)| id),
19171 Some(fallback_id),
19172 "selection must retry after reentrant removals and preserve the healthy fallback"
19173 );
19174 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19175 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19176 let state = registry
19177 .state
19178 .lock()
19179 .unwrap_or_else(std::sync::PoisonError::into_inner);
19180 assert_eq!(state.entries.len(), 1);
19181 assert!(state.entries.contains_key(&fallback_id));
19182 assert!(state.dispatches.is_empty());
19183 return;
19184 }
19185
19186 let output = std::process::Command::new(std::env::current_exe().unwrap())
19187 .arg("--exact")
19188 .arg(
19189 "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
19190 )
19191 .arg("--nocapture")
19192 .env(CHILD_ENV, "1")
19193 .output()
19194 .expect("the hostile task-driver snapshot subprocess test must start");
19195 assert!(
19196 output.status.success(),
19197 "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19198 output.status.code(),
19199 String::from_utf8_lossy(&output.stdout),
19200 String::from_utf8_lossy(&output.stderr)
19201 );
19202 }
19203
19204 #[cfg(not(target_family = "wasm"))]
19205 #[test]
19206 fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
19207 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
19208
19209 if std::env::var_os(CHILD_ENV).is_some() {
19210 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19211
19212 let first_drops = Arc::new(AtomicUsize::new(0));
19213 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19214 drops: Arc::clone(&first_drops),
19215 });
19216 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19217 drop(first);
19218
19219 let second_drops = Arc::new(AtomicUsize::new(0));
19220 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19221 drops: Arc::clone(&second_drops),
19222 });
19223 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19224 drop(second);
19225
19226 let reentrant_drops = Arc::new(AtomicUsize::new(0));
19227 let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
19228 registry: Arc::downgrade(®istry),
19229 registrations: Mutex::new(Vec::new()),
19230 drops: Arc::clone(&reentrant_drops),
19231 });
19232 let reentrant_id =
19233 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19234 *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
19235 drop(reentrant);
19236
19237 let dispatch = 59;
19238 let failure = current_thread_dispatch_failure(dispatch, 0);
19239 assert_eq!(
19240 registry.dispatch(dispatch),
19241 CurrentThreadHostDispatchResult::Failed(failure),
19242 "reentrant removal of every provisional host must report accepted delivery failure"
19243 );
19244 assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
19245 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19246 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19247 registry.finish_cancellation(failure);
19248 let state = registry
19249 .state
19250 .lock()
19251 .unwrap_or_else(std::sync::PoisonError::into_inner);
19252 assert!(state.entries.is_empty());
19253 assert!(state.dispatches.is_empty());
19254 return;
19255 }
19256
19257 let output = std::process::Command::new(std::env::current_exe().unwrap())
19258 .arg("--exact")
19259 .arg(
19260 "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
19261 )
19262 .arg("--nocapture")
19263 .env(CHILD_ENV, "1")
19264 .output()
19265 .expect("the hostile task-driver delivery subprocess test must start");
19266 assert!(
19267 output.status.success(),
19268 "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19269 output.status.code(),
19270 String::from_utf8_lossy(&output.stdout),
19271 String::from_utf8_lossy(&output.stderr)
19272 );
19273 }
19274
19275 #[cfg(napi_runtime_os_threads)]
19278 #[test]
19279 fn dropping_join_handle_detaches_task_like_tokio() {
19280 let completed = Arc::new(AtomicBool::new(false));
19281 let completed_task = Arc::clone(&completed);
19282 let (runnable, task) = async_task::spawn(
19283 async move {
19284 completed_task.store(true, Ordering::SeqCst);
19285 Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
19286 },
19287 |_| {},
19288 );
19289 let handle = JoinHandle(JoinHandleInner::Task {
19290 task: task.fallible(),
19291 awaiter: None,
19292 dependency: Arc::new(TaskDependency::default()),
19293 });
19294
19295 drop(handle);
19296 runnable.run();
19297 assert!(completed.load(Ordering::SeqCst));
19298 }
19299
19300 #[cfg(not(target_family = "wasm"))]
19301 #[test]
19302 fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
19303 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
19304
19305 if std::env::var_os(CHILD_ENV).is_some() {
19306 let wakes = Arc::new(AtomicUsize::new(0));
19307 let drops = Arc::new(AtomicUsize::new(0));
19308 let (runnable, task) = async_task::spawn(
19309 async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
19310 |_| {},
19311 );
19312 let mut handle = JoinHandle(JoinHandleInner::Task {
19313 task: task.fallible(),
19314 awaiter: None,
19315 dependency: Arc::new(TaskDependency::default()),
19316 });
19317 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19318 {
19319 let mut cx = Context::from_waker(&waker);
19320 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19321 }
19322 drop(waker);
19323
19324 runnable.run();
19325
19326 let waker = futures::task::noop_waker();
19327 let mut cx = Context::from_waker(&waker);
19328 assert!(matches!(
19329 Pin::new(&mut handle).poll(&mut cx),
19330 Poll::Ready(Ok(()))
19331 ));
19332 assert_eq!(
19333 drops.load(Ordering::SeqCst),
19334 1,
19335 "a completed handle must not retain the caller's awaiter"
19336 );
19337 drop(handle);
19338 assert!(wakes.load(Ordering::SeqCst) >= 1);
19339 assert_eq!(drops.load(Ordering::SeqCst), 1);
19340 return;
19341 }
19342
19343 let output = std::process::Command::new(std::env::current_exe().unwrap())
19344 .arg("--exact")
19345 .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
19346 .arg("--nocapture")
19347 .env(CHILD_ENV, "1")
19348 .output()
19349 .expect("the JoinHandle completion awaiter subprocess must start");
19350 assert!(
19351 output.status.success(),
19352 "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19353 output.status.code(),
19354 String::from_utf8_lossy(&output.stdout),
19355 String::from_utf8_lossy(&output.stderr)
19356 );
19357 }
19358
19359 #[cfg(not(target_family = "wasm"))]
19360 #[test]
19361 fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
19362 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
19363
19364 if std::env::var_os(CHILD_ENV).is_some() {
19365 let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
19366 let wakes = Arc::new(AtomicUsize::new(0));
19367 let drops = Arc::new(AtomicUsize::new(0));
19368 let mut handle = controller.spawn(std::future::pending::<()>());
19369 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19370 {
19371 let mut cx = Context::from_waker(&waker);
19372 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19373 }
19374 drop(waker);
19375
19376 controller
19377 .shutdown()
19378 .expect("shutdown must survive the hostile awaiter");
19379 drop(handle);
19380 assert!(wakes.load(Ordering::SeqCst) >= 1);
19381 assert_eq!(drops.load(Ordering::SeqCst), 1);
19382 return;
19383 }
19384
19385 let output = std::process::Command::new(std::env::current_exe().unwrap())
19386 .arg("--exact")
19387 .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
19388 .arg("--nocapture")
19389 .env(CHILD_ENV, "1")
19390 .output()
19391 .expect("the JoinHandle shutdown awaiter subprocess must start");
19392 assert!(
19393 output.status.success(),
19394 "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19395 output.status.code(),
19396 String::from_utf8_lossy(&output.stdout),
19397 String::from_utf8_lossy(&output.stderr)
19398 );
19399 }
19400
19401 #[cfg(napi_runtime_os_threads)]
19402 #[test]
19403 fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
19404 let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
19405 let mut handle = controller.spawn(std::future::pending::<()>());
19406 let old_generation = controller.backend().generation();
19407 let state = Arc::new(JoinAwaiterWakerState {
19408 events: Mutex::new(Vec::new()),
19409 });
19410 let waker = {
19411 let _generation = RuntimeGenerationGuard::enter(old_generation);
19412 join_awaiter_waker(Arc::clone(&state))
19413 };
19414 {
19415 let _generation = RuntimeGenerationGuard::enter(old_generation);
19416 let mut cx = Context::from_waker(&waker);
19417 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19418 }
19419 {
19420 let _generation = RuntimeGenerationGuard::enter(old_generation);
19421 drop(waker);
19422 }
19423
19424 controller
19425 .shutdown()
19426 .expect("the first generation must stop");
19427 controller.start().expect("the runtime must restart");
19428 let new_generation = controller.backend().generation();
19429 assert_ne!(new_generation, old_generation);
19430 state.clear();
19431
19432 let replacement = futures::task::noop_waker();
19433 {
19434 let _generation = RuntimeGenerationGuard::enter(new_generation);
19435 let mut cx = Context::from_waker(&replacement);
19436 assert!(matches!(
19437 Pin::new(&mut handle).poll(&mut cx),
19438 Poll::Ready(Err(_))
19439 ));
19440 }
19441
19442 let events = state.snapshot();
19443 assert!(
19444 !events.is_empty(),
19445 "completion must retire the registered awaiter waker"
19446 );
19447 assert!(
19448 events.iter().all(|(kind, generation)| {
19449 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19450 }),
19451 "old-generation awaiter retirement escaped its generation: {events:?}"
19452 );
19453 controller
19454 .shutdown()
19455 .expect("the replacement generation must stop");
19456 }
19457
19458 #[cfg(napi_runtime_os_threads)]
19459 #[test]
19460 fn dropping_join_handle_retains_awaiter_generation_after_restart() {
19461 let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
19462 let mut handle = controller.spawn(std::future::pending::<()>());
19463 let old_generation = controller.backend().generation();
19464 let state = Arc::new(JoinAwaiterWakerState {
19465 events: Mutex::new(Vec::new()),
19466 });
19467 let waker = {
19468 let _generation = RuntimeGenerationGuard::enter(old_generation);
19469 join_awaiter_waker(Arc::clone(&state))
19470 };
19471 {
19472 let _generation = RuntimeGenerationGuard::enter(old_generation);
19473 let mut cx = Context::from_waker(&waker);
19474 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19475 }
19476 {
19477 let _generation = RuntimeGenerationGuard::enter(old_generation);
19478 drop(waker);
19479 }
19480
19481 controller
19482 .shutdown()
19483 .expect("the first generation must stop");
19484 controller.start().expect("the runtime must restart");
19485 let new_generation = controller.backend().generation();
19486 assert_ne!(new_generation, old_generation);
19487 state.clear();
19488
19489 {
19490 let _generation = RuntimeGenerationGuard::enter(new_generation);
19491 drop(handle);
19492 }
19493
19494 let events = state.snapshot();
19495 assert!(
19496 !events.is_empty(),
19497 "handle drop must retire the registered awaiter waker"
19498 );
19499 assert!(
19500 events.iter().all(|(kind, generation)| {
19501 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19502 }),
19503 "old-generation awaiter destruction escaped its generation: {events:?}"
19504 );
19505 controller
19506 .shutdown()
19507 .expect("the replacement generation must stop");
19508 }
19509
19510 #[cfg(napi_runtime_os_threads)]
19511 #[test]
19512 fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
19513 use std::{sync::mpsc, time::Duration};
19514
19515 let options = RuntimeOptions {
19516 flavor: RuntimeFlavor::MultiThread,
19517 worker_threads: 2,
19518 max_blocking_tasks: 1,
19519 thread_name_prefix: "worker-classification".to_string(),
19520 park_deadline: None,
19521 drain_linger: DEFAULT_DRAIN_LINGER,
19522 };
19523 let executor =
19524 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19525 let id = executor.id;
19526 let (tx, rx) = mpsc::sync_channel(1);
19527
19528 executor.pool.spawn(move || {
19529 tx.send((
19530 ON_POOL_WORKER.with(std::cell::Cell::get),
19531 IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
19532 ))
19533 .unwrap();
19534 });
19535
19536 let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
19537 assert_eq!(
19538 worker,
19539 Some(id),
19540 "nested Rayon work must use cooperative block_on"
19541 );
19542 assert_eq!(
19543 driver, None,
19544 "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
19545 );
19546 }
19547
19548 #[cfg(napi_runtime_os_threads)]
19549 #[test]
19550 fn multi_thread_block_on_does_not_park_pool_worker() {
19551 use std::sync::{Barrier, mpsc};
19556 use std::time::Duration;
19557
19558 let (done_tx, done_rx) = mpsc::channel();
19559 let runner = std::thread::spawn(move || {
19560 let metrics = Arc::new(RuntimeMetrics::default());
19561 let options = RuntimeOptions {
19562 flavor: RuntimeFlavor::MultiThread,
19563 worker_threads: 2,
19564 max_blocking_tasks: 2,
19565 thread_name_prefix: "rd1-test".to_string(),
19566 park_deadline: None,
19567 drain_linger: DEFAULT_DRAIN_LINGER,
19568 };
19569 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19570
19571 let task_count = 2usize;
19572 let barrier = Arc::new(Barrier::new(task_count));
19575
19576 let mut tasks = Vec::new();
19577 for _ in 0..task_count {
19578 let exec = Arc::clone(&executor);
19579 let barrier = Arc::clone(&barrier);
19580 let body = async move {
19581 barrier.wait();
19582 let inner_exec = Arc::clone(&exec);
19583 let mut inner = std::pin::pin!(async move {
19584 let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
19585 assert_eq!(value, 7);
19586 });
19587 exec.block_on(inner.as_mut());
19589 };
19590 let scheduler = Arc::clone(&executor);
19591 let (runnable, task) =
19592 async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
19593 executor.schedule(runnable);
19594 tasks.push(task);
19595 }
19596
19597 for task in tasks {
19598 futures::executor::block_on(task);
19599 }
19600 let _ = done_tx.send(());
19601 });
19602
19603 match done_rx.recv_timeout(Duration::from_secs(10)) {
19604 Ok(()) => runner.join().unwrap(),
19605 Err(error) => panic!(
19606 "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
19607 ),
19608 }
19609 }
19610
19611 #[cfg(napi_runtime_os_threads)]
19612 #[test]
19613 fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
19614 use std::sync::mpsc;
19633
19634 for round in 0..3 {
19635 let metrics = Arc::new(RuntimeMetrics::default());
19636 let options = RuntimeOptions {
19637 flavor: RuntimeFlavor::MultiThread,
19638 worker_threads: 2,
19639 max_blocking_tasks: 1,
19640 thread_name_prefix: format!("linger-starve-{round}"),
19641 park_deadline: None,
19642 drain_linger: DEFAULT_DRAIN_LINGER,
19643 };
19644 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19645
19646 let r_done = Arc::new(AtomicBool::new(false));
19647 let give_up = Arc::new(AtomicBool::new(false));
19648 let (done_tx, done_rx) = mpsc::channel::<Duration>();
19649
19650 let t1_executor = Arc::clone(&executor);
19656 let t1_started = Arc::new(AtomicBool::new(false));
19657 let t1_started_setter = Arc::clone(&t1_started);
19658 let t1_body = async move {
19659 t1_started_setter.store(true, Ordering::SeqCst);
19660 let deadline = Instant::now() + Duration::from_secs(10);
19661 while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
19662 if Instant::now() >= deadline {
19663 return;
19665 }
19666 std::hint::spin_loop();
19667 }
19668 };
19669 let t1_scheduler = Arc::clone(&executor);
19670 let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
19671 executor.schedule(t1_runnable);
19672 wait_until("the worker-pinning task to start", || {
19673 t1_started.load(Ordering::SeqCst)
19674 });
19675
19676 let blocking_executor = Arc::clone(&executor);
19678 let r_done_setter = Arc::clone(&r_done);
19679 let started = Instant::now();
19680 let handle = executor.schedule_blocking(move || {
19681 let (r_tx, r_rx) = oneshot::channel::<()>();
19682 blocking_executor.pool.spawn(move || {
19683 r_done_setter.store(true, Ordering::SeqCst);
19684 let _ = r_tx.send(());
19685 });
19686 let mut wait_for_r = std::pin::pin!(async move {
19687 let _ = r_rx.await;
19688 });
19689 blocking_executor.block_on(wait_for_r.as_mut());
19690 let _ = done_tx.send(started.elapsed());
19691 });
19692
19693 let arm_deadline = Instant::now() + Duration::from_secs(15);
19697 while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
19698 && !r_done.load(Ordering::SeqCst)
19699 {
19700 assert!(
19701 Instant::now() < arm_deadline,
19702 "timed out arming the linger-starvation interleaving"
19703 );
19704 std::thread::yield_now();
19705 }
19706
19707 let producer_executor = Arc::clone(&executor);
19710 let producer_r_done = Arc::clone(&r_done);
19711 let producer_give_up = Arc::clone(&give_up);
19712 let producer = std::thread::spawn(move || {
19713 let cadence = Duration::from_micros(200);
19714 let mut next_tick = Instant::now();
19715 while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
19716 let scheduler = Arc::clone(&producer_executor);
19717 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
19718 producer_executor.schedule(runnable);
19719 task.detach();
19720 next_tick += cadence;
19721 while Instant::now() < next_tick {
19722 std::hint::spin_loop();
19723 }
19724 }
19725 });
19726
19727 let outcome = done_rx.recv_timeout(Duration::from_secs(5));
19728 give_up.store(true, Ordering::SeqCst);
19729 producer.join().unwrap();
19730 match outcome {
19731 Ok(elapsed) => {
19732 futures::executor::block_on(handle).expect("blocking closure join");
19733 drop(t1_task);
19734 eprintln!("round {round}: completed in {elapsed:?}");
19735 }
19736 Err(error) => panic!(
19737 "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
19738 sub-budget periodic work must not keep a drain frame resident forever"
19739 ),
19740 }
19741 }
19742 }
19743
19744 #[cfg(napi_runtime_os_threads)]
19745 #[test]
19746 #[ignore = "microbenchmark: run explicitly in release mode"]
19747 fn parked_drivers_registry_cycle_microbench() {
19748 for &n in &[8usize, 64, 256] {
19755 let registry = ParkedDrivers::default();
19756 let parkers: Vec<Arc<DriverParker>> =
19757 (0..n).map(|_| Arc::new(DriverParker::default())).collect();
19758 for parker in &parkers {
19759 registry.register_with_role(parker, true, None);
19760 }
19761 let hot = &parkers[n - 1];
19762
19763 let iters = 2_000_000u32;
19764 let start = Instant::now();
19765 for _ in 0..iters {
19766 assert!(registry.wake_one());
19767 registry.deregister(hot);
19768 assert!(hot.consume_permit());
19769 registry.register_with_role(hot, true, None);
19770 }
19771 let wake_cycle = start.elapsed() / iters;
19772
19773 let start = Instant::now();
19774 for _ in 0..iters {
19775 registry.deregister(hot);
19776 registry.register_with_role(hot, true, None);
19777 }
19778 let timeout_cycle = start.elapsed() / iters;
19779
19780 let owner = BlockingOwnerToken {
19781 executor_id: u64::MAX,
19782 frame: u64::MAX,
19783 };
19784 let start = Instant::now();
19785 for _ in 0..iters {
19786 assert!(!registry.wake_one_owner_dependency(owner));
19787 }
19788 let owner_sweep = start.elapsed() / iters;
19789
19790 eprintln!(
19791 "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
19792 timeout-deregister+re-register {timeout_cycle:?}, \
19793 owner-dependency sweep {owner_sweep:?}"
19794 );
19795 }
19796 }
19797
19798 #[cfg(napi_runtime_os_threads)]
19799 #[test]
19800 fn multi_thread_block_on_from_caller_thread_still_parks() {
19801 let metrics = Arc::new(RuntimeMetrics::default());
19804 let options = RuntimeOptions {
19805 flavor: RuntimeFlavor::MultiThread,
19806 worker_threads: 3,
19807 max_blocking_tasks: 2,
19808 thread_name_prefix: "rd1-caller".to_string(),
19809 park_deadline: None,
19810 drain_linger: DEFAULT_DRAIN_LINGER,
19811 };
19812 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19813
19814 let mut output = None;
19815 let mut future = std::pin::pin!(async {
19816 output = Some(41usize + 1);
19817 });
19818 executor.block_on(future.as_mut());
19819 assert_eq!(output, Some(42));
19820 }
19821
19822 #[cfg(napi_runtime_os_threads)]
19823 #[test]
19824 fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
19825 use std::sync::mpsc;
19848 use std::time::Duration;
19849
19850 struct CallerProbe {
19855 task: Task<usize>,
19856 parked_tx: Option<mpsc::Sender<()>>,
19857 }
19858 impl Future for CallerProbe {
19859 type Output = usize;
19860 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
19861 let poll = Pin::new(&mut self.task).poll(cx);
19863 if poll.is_pending()
19864 && let Some(tx) = self.parked_tx.take()
19865 {
19866 let _ = tx.send(());
19868 }
19869 poll
19870 }
19871 }
19872
19873 let (done_tx, done_rx) = mpsc::channel();
19874 let runner = std::thread::spawn(move || {
19875 let metrics = Arc::new(RuntimeMetrics::default());
19876 let options = RuntimeOptions {
19877 flavor: RuntimeFlavor::MultiThread,
19878 worker_threads: 2,
19879 max_blocking_tasks: 2,
19880 thread_name_prefix: "rd10-caller".to_string(),
19881 park_deadline: None,
19882 drain_linger: DEFAULT_DRAIN_LINGER,
19883 };
19884 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19885
19886 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
19891 let scheduler = Arc::clone(&executor);
19892 let (runnable, task) = async_task::spawn(
19893 async move { gate_rx.await.expect("gate sender dropped") },
19894 move |r| {
19895 scheduler.schedule(r);
19896 },
19897 );
19898 executor.schedule(runnable);
19899
19900 let (parked_tx, parked_rx) = mpsc::channel::<()>();
19904 let releaser = std::thread::spawn(move || {
19905 if parked_rx.recv().is_ok() {
19906 let _ = gate_tx.send(42usize);
19907 }
19908 });
19909
19910 let mut output = None;
19913 {
19914 let mut future = std::pin::pin!(async {
19915 output = Some(
19916 CallerProbe {
19917 task,
19918 parked_tx: Some(parked_tx),
19919 }
19920 .await,
19921 );
19922 });
19923 executor.block_on(future.as_mut());
19924 }
19925
19926 releaser.join().unwrap();
19927 assert_eq!(
19928 output,
19929 Some(42usize),
19930 "block_on must return the pool-computed value"
19931 );
19932 let _ = done_tx.send(());
19933 });
19934
19935 match done_rx.recv_timeout(Duration::from_secs(10)) {
19936 Ok(()) => runner.join().unwrap(),
19937 Err(error) => panic!(
19938 "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
19939 ),
19940 }
19941 }
19942
19943 #[cfg(napi_runtime_os_threads)]
19944 #[test]
19945 fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
19946 use std::sync::{Barrier, mpsc};
19952 use std::time::Duration;
19953
19954 let (done_tx, done_rx) = mpsc::channel();
19955 let runner = std::thread::spawn(move || {
19956 let metrics = Arc::new(RuntimeMetrics::default());
19957 let options = RuntimeOptions {
19958 flavor: RuntimeFlavor::MultiThread,
19959 worker_threads: 2,
19960 max_blocking_tasks: 2,
19961 thread_name_prefix: "rd1-park".to_string(),
19962 park_deadline: None,
19963 drain_linger: DEFAULT_DRAIN_LINGER,
19964 };
19965 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19966
19967 let task_count = 2usize;
19968 let barrier = Arc::new(Barrier::new(task_count));
19969
19970 let mut senders = Vec::new();
19971 let mut tasks = Vec::new();
19972 for _ in 0..task_count {
19973 let (tx, rx) = oneshot::channel::<usize>();
19974 senders.push(tx);
19975 let exec = Arc::clone(&executor);
19976 let barrier = Arc::clone(&barrier);
19977 let body = async move {
19978 barrier.wait();
19980 let child_exec = Arc::clone(&exec);
19983 let (child_runnable, child_task) =
19984 async_task::spawn(async move { rx.await.unwrap() }, move |r| {
19985 child_exec.schedule(r)
19986 });
19987 exec.schedule(child_runnable);
19988 let mut inner = std::pin::pin!(async move {
19989 assert_eq!(child_task.await, 9usize);
19990 });
19991 exec.block_on(inner.as_mut());
19992 };
19993 let scheduler = Arc::clone(&executor);
19994 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19995 executor.schedule(runnable);
19996 tasks.push(task);
19997 }
19998
19999 std::thread::sleep(Duration::from_millis(200));
20001 for tx in senders {
20002 let _ = tx.send(9usize);
20003 }
20004
20005 for task in tasks {
20006 futures::executor::block_on(task);
20007 }
20008 let _ = done_tx.send(());
20009 });
20010
20011 match done_rx.recv_timeout(Duration::from_secs(10)) {
20012 Ok(()) => runner.join().unwrap(),
20013 Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
20014 }
20015 }
20016
20017 #[cfg(napi_runtime_os_threads)]
20023 fn wait_until(what: &str, condition: impl Fn() -> bool) {
20024 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
20025 while !condition() {
20026 assert!(
20027 std::time::Instant::now() < deadline,
20028 "timed out waiting for: {what}"
20029 );
20030 std::thread::yield_now();
20031 }
20032 }
20033
20034 #[cfg(napi_runtime_os_threads)]
20043 fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
20044 let deadline = std::time::Instant::now() + timeout;
20045 while !handle.is_finished() {
20046 assert!(
20047 std::time::Instant::now() < deadline,
20048 "thread wedged past its {timeout:?} bound while: {what}"
20049 );
20050 std::thread::sleep(Duration::from_millis(10));
20051 }
20052 handle.join().unwrap();
20053 }
20054
20055 #[cfg(napi_runtime_wasi_threads)]
20063 fn std_runs_tls_destructors_before_join() -> bool {
20064 struct Probe(Arc<AtomicBool>);
20065 impl Drop for Probe {
20066 fn drop(&mut self) {
20067 self.0.store(true, Ordering::SeqCst);
20068 }
20069 }
20070 thread_local! {
20071 static PROBE: std::cell::RefCell<Option<Probe>> = const { std::cell::RefCell::new(None) };
20072 }
20073 let ran = Arc::new(AtomicBool::new(false));
20074 let probe = Probe(Arc::clone(&ran));
20075 std::thread::spawn(move || PROBE.with(|slot| *slot.borrow_mut() = Some(probe)))
20076 .join()
20077 .unwrap();
20078 ran.load(Ordering::SeqCst)
20079 }
20080
20081 #[cfg(napi_runtime_os_threads)]
20082 fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
20083 executor: &Arc<MultiThreadExecutor>,
20084 start_counter: &AtomicU64,
20085 claim: impl FnOnce() -> T + Send + 'static,
20086 ) -> T {
20087 use std::sync::mpsc;
20088
20089 let (claimed_tx, claimed_rx) = mpsc::channel();
20090 let (release_claim_tx, release_claim_rx) = mpsc::channel();
20091 let (started_tx, started_rx) = mpsc::channel();
20092 let (release_start_tx, release_start_rx) = mpsc::channel();
20093 let starts_before = start_counter.load(Ordering::SeqCst);
20094 let worker = std::thread::spawn(move || {
20095 MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
20096 *slot.borrow_mut() = Some(Box::new(move || {
20097 claimed_tx.send(()).unwrap();
20098 release_claim_rx.recv().unwrap();
20099 }));
20100 });
20101 MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
20102 *slot.borrow_mut() = Some(Box::new(move || {
20103 started_tx.send(()).unwrap();
20104 release_start_rx.recv().unwrap();
20105 }));
20106 });
20107 claim()
20108 });
20109 claimed_rx
20110 .recv_timeout(Duration::from_secs(2))
20111 .expect("the scheduler claim must pause after removing work");
20112
20113 let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20114 let verdict = executor.try_begin_deadlock_verdict();
20115 let claim_verdict_rejected = verdict.is_none();
20116 drop(verdict);
20117 release_claim_tx.send(()).unwrap();
20118
20119 started_rx
20120 .recv_timeout(Duration::from_secs(2))
20121 .expect("the scheduler claim must pause after publishing its start");
20122 let admission_active_after_start =
20123 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20124 let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
20125 let verdict = executor.try_begin_deadlock_verdict();
20126 let start_verdict_rejected = verdict.is_none();
20127 drop(verdict);
20128 release_start_tx.send(()).unwrap();
20129 let output = worker.join().unwrap();
20130
20131 assert!(
20132 admission_active,
20133 "removing scheduler work must retain a detector admission until start accounting"
20134 );
20135 assert!(
20136 claim_verdict_rejected,
20137 "a deadline verdict must not close while removed work has not published its start"
20138 );
20139 assert!(
20140 start_published,
20141 "work start accounting must publish before admission retires"
20142 );
20143 assert!(
20144 admission_active_after_start,
20145 "the detector admission must remain active until after start accounting"
20146 );
20147 assert!(
20148 start_verdict_rejected,
20149 "a deadline verdict must not close between start accounting and admission retirement"
20150 );
20151 output
20152 }
20153
20154 #[cfg(napi_runtime_os_threads)]
20155 #[test]
20156 fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
20157 let executor =
20158 multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
20159
20160 let fifo_ran = Arc::new(AtomicBool::new(false));
20161 let fifo_task_ran = Arc::clone(&fifo_ran);
20162 let (fifo, fifo_task) = async_task::spawn(
20163 async move {
20164 fifo_task_ran.store(true, Ordering::SeqCst);
20165 },
20166 |_| {},
20167 );
20168 executor.metrics.runnable_scheduled();
20169 executor.push_fifo_raw(fifo);
20170 let fifo_executor = Arc::clone(&executor);
20171 assert!(run_claim_with_deadlock_gate_assertion(
20172 &executor,
20173 &executor.metrics.runnable_polls,
20174 move || {
20175 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20176 fifo_executor.run_one_fifo_runnable()
20177 }
20178 ));
20179 futures::executor::block_on(fifo_task);
20180 assert!(fifo_ran.load(Ordering::SeqCst));
20181
20182 let fifo_order = Arc::new(Mutex::new(Vec::new()));
20183 let mut fifo_tasks = Vec::new();
20184 for name in ["first", "second"] {
20185 let task_order = Arc::clone(&fifo_order);
20186 let (runnable, task) = async_task::spawn(
20187 async move {
20188 task_order.lock().unwrap().push(name);
20189 },
20190 |_| {},
20191 );
20192 executor.metrics.runnable_scheduled();
20193 executor.push_fifo_raw(runnable);
20194 fifo_tasks.push(task);
20195 }
20196 for _ in 0..2 {
20197 let fifo_executor = Arc::clone(&executor);
20198 assert!(run_claim_with_deadlock_gate_assertion(
20199 &executor,
20200 &executor.metrics.runnable_polls,
20201 move || {
20202 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20203 fifo_executor.run_one_runnable()
20204 }
20205 ));
20206 }
20207 for task in fifo_tasks {
20208 futures::executor::block_on(task);
20209 }
20210 assert_eq!(
20211 *fifo_order.lock().unwrap(),
20212 ["first", "second"],
20213 "the detector-enabled normal fallback must preserve FIFO order"
20214 );
20215
20216 let lifo_ran = Arc::new(AtomicBool::new(false));
20217 let lifo_task_ran = Arc::clone(&lifo_ran);
20218 let (lifo, lifo_task) = async_task::spawn(
20219 async move {
20220 lifo_task_ran.store(true, Ordering::SeqCst);
20221 },
20222 |_| {},
20223 );
20224 executor.metrics.runnable_scheduled();
20225 let lifo_executor = Arc::clone(&executor);
20226 assert!(run_claim_with_deadlock_gate_assertion(
20227 &executor,
20228 &executor.metrics.runnable_polls,
20229 move || {
20230 let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
20231 LIFO_SLOT.with(|slot| {
20232 assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
20233 });
20234 lifo_executor.run_one_runnable()
20235 }
20236 ));
20237 futures::executor::block_on(lifo_task);
20238 assert!(lifo_ran.load(Ordering::SeqCst));
20239
20240 executor.begin_shutdown();
20241 executor.wait_until_scheduler_idle();
20242 }
20243
20244 #[cfg(napi_runtime_os_threads)]
20245 #[test]
20246 fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
20247 let executor =
20248 multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
20249
20250 let fifo_ran = Arc::new(AtomicBool::new(false));
20251 let fifo_job_ran = Arc::clone(&fifo_ran);
20252 executor.metrics.blocking_scheduled();
20253 executor
20254 .blocking_queue
20255 .lock()
20256 .unwrap()
20257 .push(QueuedBlocking {
20258 id: BlockingJobId(u64::MAX - 90),
20259 run: Box::new(move || {
20260 fifo_job_ran.store(true, Ordering::SeqCst);
20261 }),
20262 });
20263 let fifo_executor = Arc::clone(&executor);
20264 assert!(run_claim_with_deadlock_gate_assertion(
20265 &executor,
20266 &executor.metrics.blocking_tasks_started,
20267 move || {
20268 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20269 fifo_executor.run_one_blocking()
20270 }
20271 ));
20272 assert!(fifo_ran.load(Ordering::SeqCst));
20273
20274 let owner = executor.fresh_owner_token();
20275 let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20276 let exact_job = BlockingJobId(u64::MAX - 91);
20277 let exact_ran = Arc::new(AtomicBool::new(false));
20278 let exact_job_ran = Arc::clone(&exact_ran);
20279 executor.metrics.blocking_scheduled();
20280 executor
20281 .blocking_queue
20282 .lock()
20283 .unwrap()
20284 .push(QueuedBlocking {
20285 id: exact_job,
20286 run: Box::new(move || {
20287 exact_job_ran.store(true, Ordering::SeqCst);
20288 }),
20289 });
20290 let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
20291 dependency.set_owned(BlockingDependency {
20292 job: exact_job,
20293 owner: Some(owner),
20294 });
20295 let exact_executor = Arc::clone(&executor);
20296 assert!(run_claim_with_deadlock_gate_assertion(
20297 &executor,
20298 &executor.metrics.blocking_tasks_started,
20299 move || {
20300 let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
20301 let _owner = BlockingOwnerGuard::enter(Some(owner));
20302 exact_executor.try_owned_blocking_over_cap(&dependency)
20303 }
20304 ));
20305 assert!(exact_ran.load(Ordering::SeqCst));
20306
20307 drop(active_owner);
20308 executor.begin_shutdown();
20309 executor.wait_until_scheduler_idle();
20310 }
20311
20312 #[cfg(napi_runtime_os_threads)]
20313 #[test]
20314 fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
20315 use std::sync::mpsc;
20323 use std::time::Duration;
20324
20325 let registry = Arc::new(ParkedDrivers::default());
20326
20327 let guard = registry.parked.lock().unwrap();
20329 let (done_tx, done_rx) = mpsc::channel();
20330 let waker_thread = {
20331 let registry = Arc::clone(®istry);
20332 std::thread::spawn(move || {
20333 done_tx.send(registry.wake_one()).unwrap();
20334 })
20335 };
20336 let woke = done_rx
20337 .recv_timeout(Duration::from_secs(5))
20338 .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
20339 assert!(!woke, "an empty registry has nobody to wake");
20340 waker_thread.join().unwrap();
20341 drop(guard);
20342
20343 let parker = Arc::new(DriverParker::default());
20347 registry.register(&parker);
20348 let guard = registry.parked.lock().unwrap();
20349 let (done_tx, done_rx) = mpsc::channel();
20350 let waker_thread = {
20351 let registry = Arc::clone(®istry);
20352 std::thread::spawn(move || {
20353 done_tx.send(registry.wake_one()).unwrap();
20354 })
20355 };
20356 assert!(
20357 done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20358 "wake_one with a registered parker must take the slow path (block on the held lock)"
20359 );
20360 drop(guard);
20361 let woke = done_rx
20362 .recv_timeout(Duration::from_secs(5))
20363 .expect("slow-path wake_one must complete once the lock is released");
20364 assert!(
20365 woke,
20366 "the slow path must pop and wake the registered parker"
20367 );
20368 waker_thread.join().unwrap();
20369 parker.park();
20372 assert_eq!(
20373 registry.count.load(Ordering::SeqCst),
20374 0,
20375 "wake_one deregisters what it pops"
20376 );
20377 }
20378
20379 #[cfg(napi_runtime_os_threads)]
20380 #[test]
20381 fn driver_parker_permit_granted_while_running_is_not_lost() {
20382 use std::sync::mpsc;
20386 use std::time::Duration;
20387
20388 let parker = Arc::new(DriverParker::default());
20389 parker.unpark();
20391 parker.unpark();
20392
20393 let (tx, rx) = mpsc::channel();
20394 let driver = {
20395 let parker = Arc::clone(&parker);
20396 std::thread::spawn(move || {
20397 parker.park(); tx.send(1).unwrap();
20399 parker.park(); tx.send(2).unwrap();
20401 })
20402 };
20403
20404 assert_eq!(
20405 rx.recv_timeout(Duration::from_secs(5))
20406 .expect("a stored permit must not be lost"),
20407 1
20408 );
20409 assert!(
20410 rx.recv_timeout(Duration::from_millis(300)).is_err(),
20411 "two pre-park wakes must coalesce into one permit (second park must sleep)"
20412 );
20413 parker.unpark();
20414 assert_eq!(
20415 rx.recv_timeout(Duration::from_secs(5))
20416 .expect("unpark must wake a sleeping driver"),
20417 2
20418 );
20419 driver.join().unwrap();
20420 }
20421
20422 #[cfg(napi_runtime_os_threads)]
20423 #[test]
20424 fn driver_parker_unrepresentable_timeout_remains_wakeable() {
20425 use std::sync::mpsc;
20426
20427 let parker = Arc::new(DriverParker::default());
20428 assert!(
20429 Instant::now().checked_add(Duration::MAX).is_none(),
20430 "Duration::MAX must exercise the unrepresentable-deadline path"
20431 );
20432 let (done_tx, done_rx) = mpsc::channel();
20433 let driver = {
20434 let parker = Arc::clone(&parker);
20435 std::thread::spawn(move || {
20436 done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
20437 })
20438 };
20439 wait_until("the unbounded timeout driver to sleep", || {
20440 parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
20441 });
20442 assert!(
20443 done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
20444 "an unrepresentable timeout must not expire immediately"
20445 );
20446
20447 let unparker = {
20448 let parker = Arc::clone(&parker);
20449 std::thread::spawn(move || parker.unpark())
20450 };
20451 assert!(
20452 done_rx
20453 .recv_timeout(Duration::from_secs(1))
20454 .expect("an unrepresentable timeout must remain wakeable"),
20455 "a permit must be reported as a wake"
20456 );
20457 unparker.join().unwrap();
20458 driver.join().unwrap();
20459 }
20460
20461 #[cfg(napi_runtime_os_threads)]
20462 #[test]
20463 fn parked_drivers_wake_one_wakes_exactly_one() {
20464 use std::sync::mpsc;
20468 use std::time::Duration;
20469
20470 let registry = Arc::new(ParkedDrivers::default());
20471 let (woke_tx, woke_rx) = mpsc::channel();
20472 let mut drivers = Vec::new();
20473 for index in 0..2 {
20474 let registry = Arc::clone(®istry);
20475 let woke_tx = woke_tx.clone();
20476 drivers.push(std::thread::spawn(move || {
20477 let parker = Arc::new(DriverParker::default());
20478 registry.register(&parker);
20479 parker.park();
20480 registry.deregister(&parker);
20481 woke_tx.send(index).unwrap();
20482 }));
20483 }
20484 wait_until("both drivers registered as parked", || {
20485 registry.count.load(Ordering::SeqCst) == 2
20486 });
20487
20488 assert!(registry.wake_one(), "wake_one must wake a parked driver");
20489 woke_rx
20490 .recv_timeout(Duration::from_secs(5))
20491 .expect("exactly one driver must wake");
20492 assert!(
20493 woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20494 "the second driver must stay parked after a single wake_one"
20495 );
20496 assert_eq!(
20497 registry.count.load(Ordering::SeqCst),
20498 1,
20499 "one driver must remain registered"
20500 );
20501
20502 assert!(
20503 registry.wake_one(),
20504 "a second wake_one must wake the remaining driver"
20505 );
20506 woke_rx
20507 .recv_timeout(Duration::from_secs(5))
20508 .expect("the second driver must wake");
20509 for driver in drivers {
20510 driver.join().unwrap();
20511 }
20512 }
20513
20514 #[cfg(napi_runtime_os_threads)]
20515 #[test]
20516 fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
20517 use std::sync::mpsc;
20518
20519 struct ReenterRegistryOnDrop {
20520 registry: Arc<ParkedDrivers>,
20521 dropped: mpsc::Sender<()>,
20522 }
20523
20524 #[expect(
20525 clippy::manual_noop_waker,
20526 reason = "the test observes the waker payload's Drop"
20527 )]
20528 impl std::task::Wake for ReenterRegistryOnDrop {
20529 fn wake(self: Arc<Self>) {}
20530 }
20531
20532 impl Drop for ReenterRegistryOnDrop {
20533 fn drop(&mut self) {
20534 let parker = Arc::new(DriverParker::default());
20535 self.registry.register(&parker);
20536 self.registry.deregister(&parker);
20537 self.dropped.send(()).unwrap();
20538 }
20539 }
20540
20541 let registry = Arc::new(ParkedDrivers::default());
20542 let dependency = Arc::new(TaskDependency::default());
20543 let (dropped_tx, dropped_rx) = mpsc::channel();
20544 let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
20545 registry: Arc::clone(®istry),
20546 dropped: dropped_tx,
20547 }));
20548 dependency.register_waiter(&waiter);
20549 drop(waiter);
20550
20551 let parker = Arc::new(DriverParker::default());
20552 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20553 drop(dependency);
20554
20555 let retiring_registry = Arc::clone(®istry);
20556 let retiring_parker = Arc::clone(&parker);
20557 let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
20558 dropped_rx
20559 .recv_timeout(Duration::from_secs(1))
20560 .expect("retained dependency waiters must be destroyed after releasing the registry lock");
20561 retiring.join().unwrap();
20562 }
20563
20564 #[cfg(napi_runtime_os_threads)]
20565 #[test]
20566 fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
20567 use std::sync::mpsc;
20568
20569 let registry = Arc::new(ParkedDrivers::default());
20570 let owner = BlockingOwnerToken {
20571 executor_id: u64::MAX - 70,
20572 frame: u64::MAX - 71,
20573 };
20574 let dependency = Arc::new(TaskDependency::default());
20575 dependency.set_owned(BlockingDependency {
20576 job: BlockingJobId(u64::MAX - 72),
20577 owner: Some(owner),
20578 });
20579 let parker = Arc::new(DriverParker::default());
20580 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20581
20582 let (predicate_tx, predicate_rx) = mpsc::channel();
20583 let (release_tx, release_rx) = mpsc::channel();
20584 let selecting_registry = Arc::clone(®istry);
20585 let selector = std::thread::spawn(move || {
20586 DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
20587 *slot.borrow_mut() = Some(Box::new(move || {
20588 predicate_tx.send(()).unwrap();
20589 release_rx.recv().unwrap();
20590 }));
20591 });
20592 selecting_registry.wake_one_owner_dependency(owner)
20593 });
20594
20595 predicate_rx
20596 .recv_timeout(Duration::from_secs(2))
20597 .expect("owner selection must reach the dependency predicate");
20598 let registry_was_unlocked = registry.parked.try_lock().is_ok();
20599 release_tx.send(()).unwrap();
20600 assert!(
20601 selector.join().unwrap(),
20602 "the exact-owner parker must still be selected"
20603 );
20604
20605 assert!(
20606 registry_was_unlocked,
20607 "owner dependency predicates must not run while the parked-driver registry is locked"
20608 );
20609 assert!(parker.consume_permit());
20610 assert_eq!(parker.take_owner_handoff(), Some(owner));
20611 }
20612
20613 #[cfg(napi_runtime_os_threads)]
20614 #[test]
20615 fn owner_handoff_skips_newer_untagged_dependency() {
20616 let registry = ParkedDrivers::default();
20617 let owner = BlockingOwnerToken {
20618 executor_id: u64::MAX - 1,
20619 frame: u64::MAX - 2,
20620 };
20621 let owned_dependency = Arc::new(TaskDependency::default());
20622 owned_dependency.set_owned(BlockingDependency {
20623 job: BlockingJobId(u64::MAX - 3),
20624 owner: Some(owner),
20625 });
20626 let untagged_dependency = Arc::new(TaskDependency::default());
20627 untagged_dependency.set_owned(BlockingDependency {
20628 job: BlockingJobId(u64::MAX - 4),
20629 owner: None,
20630 });
20631 let owned = Arc::new(DriverParker::default());
20632 let untagged = Arc::new(DriverParker::default());
20633
20634 registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
20635 registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
20638
20639 assert!(registry.wake_one_owner_dependency(owner));
20640 assert!(
20641 owned.consume_permit(),
20642 "the exact owner dependency must receive the retry"
20643 );
20644 assert!(
20645 !untagged.consume_permit(),
20646 "an untagged dependency must not consume an exact owner handoff"
20647 );
20648 registry.deregister(&untagged);
20649 }
20650
20651 #[cfg(napi_runtime_os_threads)]
20652 #[test]
20653 fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
20654 let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
20655 let owner = executor.fresh_owner_token();
20656 let valid_dependency = Arc::new(TaskDependency::default());
20657 valid_dependency.set_owned(BlockingDependency {
20658 job: BlockingJobId(u64::MAX - 45),
20659 owner: Some(owner),
20660 });
20661 let withdrawn_dependency = Arc::new(TaskDependency::default());
20662 withdrawn_dependency.set_owned(BlockingDependency {
20663 job: BlockingJobId(u64::MAX - 46),
20664 owner: Some(owner),
20665 });
20666 let valid = Arc::new(DriverParker::default());
20667 let withdrawn = Arc::new(DriverParker::default());
20668
20669 executor
20670 .parked_drivers
20671 .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
20672 executor.parked_drivers.register_with_role(
20673 &withdrawn,
20674 true,
20675 Some(Arc::clone(&withdrawn_dependency)),
20676 );
20677 assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
20678 withdrawn_dependency.clear();
20679
20680 assert_eq!(
20681 executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
20682 Some(false),
20683 "a selected dependency withdrawn before retry must forward the owner handoff"
20684 );
20685 assert!(
20686 valid.consume_permit(),
20687 "the next live same-owner dependency must receive the forwarded wake"
20688 );
20689 assert_eq!(
20690 valid.take_owner_handoff(),
20691 Some(owner),
20692 "the forwarded wake must retain the exact owner identity"
20693 );
20694 }
20695
20696 #[cfg(napi_runtime_os_threads)]
20697 #[test]
20698 fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
20699 use std::sync::mpsc;
20700
20701 let executor = multi_thread_executor(2, None, "owner-handoff-publication");
20702 let owner = executor.fresh_owner_token();
20703 let fallback_dependency = Arc::new(TaskDependency::default());
20704 fallback_dependency.set_owned(BlockingDependency {
20705 job: BlockingJobId(u64::MAX - 47),
20706 owner: Some(owner),
20707 });
20708 let exiting_dependency = Arc::new(TaskDependency::default());
20709 exiting_dependency.set_owned(BlockingDependency {
20710 job: BlockingJobId(u64::MAX - 48),
20711 owner: Some(owner),
20712 });
20713 let fallback = Arc::new(DriverParker::default());
20714 let exiting = Arc::new(DriverParker::default());
20715
20716 executor.parked_drivers.register_with_role(
20717 &fallback,
20718 true,
20719 Some(Arc::clone(&fallback_dependency)),
20720 );
20721 executor.parked_drivers.register_with_role(
20722 &exiting,
20723 true,
20724 Some(Arc::clone(&exiting_dependency)),
20725 );
20726
20727 let (selected_tx, selected_rx) = mpsc::channel();
20728 let (release_selection_tx, release_selection_rx) = mpsc::channel();
20729 let selector_executor = Arc::clone(&executor);
20730 let selector = std::thread::spawn(move || {
20731 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
20732 *slot.borrow_mut() = Some(Box::new(move || {
20733 selected_tx.send(()).unwrap();
20734 release_selection_rx.recv().unwrap();
20735 }));
20736 });
20737 assert!(
20738 selector_executor
20739 .parked_drivers
20740 .wake_one_owner_dependency(owner)
20741 );
20742 });
20743 selected_rx
20744 .recv_timeout(Duration::from_secs(2))
20745 .expect("selection must pause after removing the exact-owner parker");
20746
20747 let (exited_tx, exited_rx) = mpsc::channel();
20748 let exiting_executor = Arc::clone(&executor);
20749 let exiting_driver = Arc::clone(&exiting);
20750 let driver = std::thread::spawn(move || {
20751 exiting_driver.park();
20752 exiting_executor.parked_drivers.deregister(&exiting_driver);
20753 exiting_executor.forward_owner_handoff(&exiting_driver);
20754 exited_tx.send(()).unwrap();
20755 });
20756 exiting.unpark();
20757 exited_rx
20758 .recv_timeout(Duration::from_secs(2))
20759 .expect("the direct future wake must let the selected driver exit");
20760
20761 release_selection_tx.send(()).unwrap();
20762 selector.join().unwrap();
20763 driver.join().unwrap();
20764
20765 assert!(
20766 fallback.consume_permit(),
20767 "an exiting selected driver must forward the owner handoff to the next dependency"
20768 );
20769 assert_eq!(
20770 fallback.take_owner_handoff(),
20771 Some(owner),
20772 "the forwarded handoff must retain the exact owner identity"
20773 );
20774 }
20775
20776 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20777 #[test]
20778 fn cooperative_panic_forwards_pending_owner_handoff() {
20779 use std::sync::mpsc;
20780
20781 struct PanicAfterWake {
20782 dependency: BlockingDependency,
20783 first_poll: Option<mpsc::Sender<()>>,
20784 }
20785
20786 impl Future for PanicAfterWake {
20787 type Output = ();
20788
20789 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20790 if let Some(first_poll) = self.first_poll.take() {
20791 let mut published = PublishedBlockingDependencies::default();
20794 record_blocking_dependency(&mut published, self.dependency);
20795 first_poll.send(()).unwrap();
20796 return Poll::Pending;
20797 }
20798 panic!("intentional cooperative driver panic after owner handoff");
20799 }
20800 }
20801
20802 let executor = multi_thread_executor(2, None, "panic-owner-handoff");
20803 let owner = executor.fresh_owner_token();
20804 let fallback_dependency = Arc::new(TaskDependency::default());
20805 fallback_dependency.set_owned(BlockingDependency {
20806 job: BlockingJobId(u64::MAX - 49),
20807 owner: Some(owner),
20808 });
20809 let fallback = Arc::new(DriverParker::default());
20810 executor.parked_drivers.register_with_role(
20811 &fallback,
20812 true,
20813 Some(Arc::clone(&fallback_dependency)),
20814 );
20815
20816 let (first_poll_tx, first_poll_rx) = mpsc::channel();
20817 let (panicked_tx, panicked_rx) = mpsc::channel();
20818 let driver_executor = Arc::clone(&executor);
20819 let driver = std::thread::spawn(move || {
20820 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20821 let mut future = std::pin::pin!(PanicAfterWake {
20822 dependency: BlockingDependency {
20823 job: BlockingJobId(u64::MAX - 50),
20824 owner: Some(owner)
20825 },
20826 first_poll: Some(first_poll_tx),
20827 });
20828 let panicked = catch_unwind(AssertUnwindSafe(|| {
20829 driver_executor.block_on(future.as_mut());
20830 }))
20831 .is_err();
20832 panicked_tx.send(panicked).unwrap();
20833 });
20834
20835 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20836 wait_until("the panicking cooperative driver to park", || {
20837 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20838 });
20839 assert!(
20840 executor.parked_drivers.wake_one_owner_dependency(owner),
20841 "the exact owner handoff must select the cooperative driver"
20842 );
20843 assert!(
20844 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20845 "the test future must unwind from its second poll"
20846 );
20847 driver.join().unwrap();
20848
20849 assert!(
20850 fallback.consume_permit(),
20851 "panic cleanup must forward the unconsumed owner handoff"
20852 );
20853 assert_eq!(
20854 fallback.take_owner_handoff(),
20855 Some(owner),
20856 "panic cleanup must preserve the exact owner identity"
20857 );
20858 }
20859
20860 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20861 #[test]
20862 fn cooperative_panic_flushes_lifo_slot() {
20863 struct ScheduleThenPanic {
20864 executor: Arc<MultiThreadExecutor>,
20865 ran: Arc<AtomicBool>,
20866 }
20867
20868 impl Future for ScheduleThenPanic {
20869 type Output = ();
20870
20871 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20872 let ran = Arc::clone(&self.ran);
20873 let scheduler = Arc::clone(&self.executor);
20874 let (runnable, task) = async_task::spawn(
20875 async move {
20876 ran.store(true, Ordering::SeqCst);
20877 },
20878 move |runnable| scheduler.schedule(runnable),
20879 );
20880 self.executor.schedule(runnable);
20881 task.detach();
20882 panic!("intentional cooperative driver panic with a populated LIFO slot");
20883 }
20884 }
20885
20886 let executor = multi_thread_executor(2, None, "panic-lifo-flush");
20887 executor
20888 .active_drainers
20889 .store(executor.max_drainers, Ordering::SeqCst);
20890 let ran = Arc::new(AtomicBool::new(false));
20891 let panicked = {
20892 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20893 let mut future = std::pin::pin!(ScheduleThenPanic {
20894 executor: Arc::clone(&executor),
20895 ran: Arc::clone(&ran),
20896 });
20897 catch_unwind(AssertUnwindSafe(|| {
20898 executor.block_on(future.as_mut());
20899 }))
20900 .is_err()
20901 };
20902
20903 assert!(panicked, "the test future must unwind from its first poll");
20904 assert!(
20905 executor.pop_lifo_slot().is_none(),
20906 "panic cleanup must empty the LIFO slot"
20907 );
20908 assert_eq!(
20909 executor.injector.len(),
20910 1,
20911 "panic cleanup must move the slot runnable to the shared FIFO"
20912 );
20913 assert!(
20914 executor.run_one(),
20915 "the flushed runnable must remain executable"
20916 );
20917 assert!(ran.load(Ordering::SeqCst));
20918 executor.active_drainers.store(0, Ordering::SeqCst);
20919 }
20920
20921 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20922 #[test]
20923 fn cooperative_panic_compensates_absorbed_queue_wake() {
20924 use std::sync::mpsc;
20925
20926 struct PanicAfterWake {
20927 first_poll: Option<mpsc::Sender<()>>,
20928 }
20929
20930 impl Future for PanicAfterWake {
20931 type Output = ();
20932
20933 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20934 if let Some(first_poll) = self.first_poll.take() {
20935 first_poll.send(()).unwrap();
20936 return Poll::Pending;
20937 }
20938 panic!("intentional cooperative driver panic after a queue wake");
20939 }
20940 }
20941
20942 let executor = multi_thread_executor(2, None, "panic-wake-compensation");
20943 executor
20944 .active_drainers
20945 .store(executor.max_drainers, Ordering::SeqCst);
20946 let fallback = Arc::new(DriverParker::default());
20947 executor.parked_drivers.register(&fallback);
20948 let (first_poll_tx, first_poll_rx) = mpsc::channel();
20949 let (panicked_tx, panicked_rx) = mpsc::channel();
20950 let driver_executor = Arc::clone(&executor);
20951 let driver = std::thread::spawn(move || {
20952 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20953 let mut future = std::pin::pin!(PanicAfterWake {
20954 first_poll: Some(first_poll_tx)
20955 });
20956 let panicked = catch_unwind(AssertUnwindSafe(|| {
20957 driver_executor.block_on(future.as_mut());
20958 }))
20959 .is_err();
20960 panicked_tx.send(panicked).unwrap();
20961 });
20962
20963 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20964 wait_until(
20965 "the panicking cooperative driver to park after the fallback",
20966 || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
20967 );
20968
20969 let ran = Arc::new(AtomicBool::new(false));
20970 let ran_task = Arc::clone(&ran);
20971 let scheduler = Arc::clone(&executor);
20972 let (runnable, task) = async_task::spawn(
20973 async move {
20974 ran_task.store(true, Ordering::SeqCst);
20975 },
20976 move |runnable| scheduler.schedule(runnable),
20977 );
20978 task.detach();
20979 executor.metrics.runnable_scheduled();
20980 executor.push_fifo_raw(runnable);
20981 assert!(
20982 executor.parked_drivers.wake_one(),
20983 "the queued runnable's wake must be absorbed by the cooperative driver"
20984 );
20985
20986 assert!(
20987 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20988 "the test future must unwind after absorbing the queue wake"
20989 );
20990 driver.join().unwrap();
20991 assert!(
20992 fallback.consume_permit(),
20993 "panic cleanup must compensate the absorbed queue wake to the fallback driver"
20994 );
20995 assert!(
20996 executor.run_one(),
20997 "the queued runnable must remain executable"
20998 );
20999 assert!(ran.load(Ordering::SeqCst));
21000 executor.active_drainers.store(0, Ordering::SeqCst);
21001 }
21002
21003 #[cfg(napi_runtime_os_threads)]
21004 #[test]
21005 fn multi_thread_future_wake_targets_its_own_parked_driver() {
21006 use std::sync::mpsc;
21014 use std::time::Duration;
21015
21016 let (done_tx, done_rx) = mpsc::channel();
21017 let runner = std::thread::spawn(move || {
21018 let metrics = Arc::new(RuntimeMetrics::default());
21019 let options = RuntimeOptions {
21020 flavor: RuntimeFlavor::MultiThread,
21021 worker_threads: 2,
21022 max_blocking_tasks: 2,
21023 thread_name_prefix: "wake-target".to_string(),
21024 park_deadline: None,
21025 drain_linger: DEFAULT_DRAIN_LINGER,
21026 };
21027 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21028
21029 let (tx1, rx1) = oneshot::channel::<usize>();
21030 let (tx2, rx2) = oneshot::channel::<usize>();
21031 let t1_done = Arc::new(AtomicBool::new(false));
21032 let t2_done = Arc::new(AtomicBool::new(false));
21033
21034 let t1_exec = Arc::clone(&executor);
21036 let t1_flag = Arc::clone(&t1_done);
21037 let t1_body = async move {
21038 let mut inner = std::pin::pin!(async move {
21039 assert_eq!(rx1.await.unwrap(), 7usize);
21040 });
21041 t1_exec.block_on(inner.as_mut());
21042 t1_flag.store(true, Ordering::SeqCst);
21043 };
21044 let sched1 = Arc::clone(&executor);
21045 let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
21046 executor.schedule(runnable1);
21047 wait_until("driver 1 parked", || {
21048 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21049 });
21050
21051 let t2_exec = Arc::clone(&executor);
21055 let t2_flag = Arc::clone(&t2_done);
21056 let t2_body = async move {
21057 let mut inner = std::pin::pin!(async move {
21058 assert_eq!(rx2.await.unwrap(), 9usize);
21059 });
21060 t2_exec.block_on(inner.as_mut());
21061 t2_flag.store(true, Ordering::SeqCst);
21062 };
21063 let sched2 = Arc::clone(&executor);
21064 let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
21065 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(runnable2);
21067 executor.ensure_drainer();
21068 wait_until("driver 2 parked", || {
21069 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
21070 });
21071
21072 tx1.send(7usize).unwrap();
21075 wait_until("T1 completed via its own driver's wake", || {
21076 t1_done.load(Ordering::SeqCst)
21077 });
21078 futures::executor::block_on(task1);
21079 assert!(
21080 !t2_done.load(Ordering::SeqCst),
21081 "D2 must still be parked: its future was never completed"
21082 );
21083 wait_until(
21100 "the retiring drain frame registered its linger parker",
21101 || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
21102 );
21103 wait_until("the lingering drainer's idle budget expired", || {
21104 executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
21105 });
21106 assert_eq!(
21107 executor.parked_drivers.count.load(Ordering::SeqCst),
21108 1,
21109 "exactly the untargeted driver must remain parked after the linger expired"
21110 );
21111 assert!(
21112 !t2_done.load(Ordering::SeqCst),
21113 "D2 must still be parked after the lingering drainer expired"
21114 );
21115
21116 tx2.send(9usize).unwrap();
21118 futures::executor::block_on(task2);
21119 assert!(t2_done.load(Ordering::SeqCst));
21120 let _ = done_tx.send(());
21121 });
21122
21123 match done_rx.recv_timeout(Duration::from_secs(10)) {
21124 Ok(()) => runner.join().unwrap(),
21125 Err(error) => panic!(
21126 "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
21127 ),
21128 }
21129 }
21130
21131 #[cfg(napi_runtime_os_threads)]
21132 #[test]
21133 fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
21134 use std::sync::mpsc;
21142 use std::time::Duration;
21143
21144 let (done_tx, done_rx) = mpsc::channel();
21145 let runner = std::thread::spawn(move || {
21146 let metrics = Arc::new(RuntimeMetrics::default());
21147 let options = RuntimeOptions {
21148 flavor: RuntimeFlavor::MultiThread,
21149 worker_threads: 1,
21150 max_blocking_tasks: 1,
21151 thread_name_prefix: "wake-compensate".to_string(),
21152 park_deadline: None,
21153 drain_linger: DEFAULT_DRAIN_LINGER,
21154 };
21155 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21156
21157 let (gate_tx, gate_rx) = oneshot::channel::<()>();
21159 let td_exec = Arc::clone(&executor);
21160 let td_body = async move {
21161 let mut inner = std::pin::pin!(async move {
21162 gate_rx.await.unwrap();
21163 });
21164 td_exec.block_on(inner.as_mut());
21165 };
21166 let sched = Arc::clone(&executor);
21167 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
21168 executor.schedule(td_runnable);
21169 wait_until("the sole driver parked", || {
21170 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21171 });
21172
21173 let (ran_tx, ran_rx) = mpsc::channel::<()>();
21176 let sched_stranded = Arc::clone(&executor);
21177 let (stranded_runnable, stranded_task) = async_task::spawn(
21178 async move {
21179 ran_tx.send(()).unwrap();
21180 },
21181 move |r| sched_stranded.schedule(r),
21182 );
21183 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(stranded_runnable);
21185 stranded_task.detach();
21186
21187 {
21192 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21193 let mut ready = std::pin::pin!(async {});
21194 executor.block_on(ready.as_mut());
21195 }
21196
21197 ran_rx.recv_timeout(Duration::from_secs(5)).expect(
21198 "exit compensation must wake the parked driver to run the stranded queued runnable",
21199 );
21200
21201 gate_tx.send(()).unwrap();
21203 futures::executor::block_on(td_task);
21204 let _ = done_tx.send(());
21205 });
21206
21207 match done_rx.recv_timeout(Duration::from_secs(10)) {
21208 Ok(()) => runner.join().unwrap(),
21209 Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
21210 }
21211 }
21212
21213 #[cfg(napi_runtime_os_threads)]
21214 #[test]
21215 fn cooperative_exit_compensates_blocking_only_residue() {
21216 use std::sync::mpsc;
21217
21218 struct Controlled {
21219 ready: Arc<AtomicBool>,
21220 entered: Option<mpsc::Sender<()>>,
21221 }
21222
21223 impl Future for Controlled {
21224 type Output = ();
21225
21226 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21227 if let Some(entered) = self.entered.take() {
21228 entered.send(()).unwrap();
21229 }
21230 if self.ready.load(Ordering::SeqCst) {
21231 Poll::Ready(())
21232 } else {
21233 Poll::Pending
21234 }
21235 }
21236 }
21237
21238 let options = RuntimeOptions {
21239 flavor: RuntimeFlavor::MultiThread,
21240 worker_threads: 2,
21241 max_blocking_tasks: 1,
21242 thread_name_prefix: "blocking-exit-compensation".to_string(),
21243 park_deadline: None,
21244 drain_linger: DEFAULT_DRAIN_LINGER,
21245 };
21246 let executor =
21247 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21248 let ready = Arc::new(AtomicBool::new(false));
21249 let (entered_tx, entered_rx) = mpsc::channel();
21250 let (returned_tx, returned_rx) = mpsc::channel();
21251 let (release_tx, release_rx) = mpsc::channel();
21252 let task_exec = Arc::clone(&executor);
21253 let task_ready = Arc::clone(&ready);
21254 let scheduler = Arc::clone(&executor);
21255 let (runnable, task) = async_task::spawn(
21256 async move {
21257 let mut future = std::pin::pin!(Controlled {
21258 ready: task_ready,
21259 entered: Some(entered_tx)
21260 });
21261 task_exec.block_on(future.as_mut());
21262 returned_tx.send(()).unwrap();
21263 release_rx.recv().unwrap();
21264 },
21265 move |runnable| scheduler.schedule(runnable),
21266 );
21267 executor.schedule(runnable);
21268 entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
21269
21270 wait_until("the cooperative frame parked", || {
21280 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21281 });
21282
21283 let timer_exec = Arc::clone(&executor);
21284 let timer_scheduler = Arc::clone(&executor);
21285 let (timer_runnable, timer_task) = async_task::spawn(
21286 async move {
21287 heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
21288 },
21289 move |runnable| timer_scheduler.schedule(runnable),
21290 );
21291 executor.schedule(timer_runnable);
21292 wait_until("the dedicated timekeeper parked", || {
21293 executor
21294 .timers
21295 .inner
21296 .lock()
21297 .unwrap()
21298 .timekeeper_parker
21299 .is_some()
21300 });
21301 wait_until(
21306 "the cooperative frame re-parked in its timer-bounded park",
21307 || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
21308 );
21309
21310 ready.store(true, Ordering::SeqCst);
21311 let (blocking_tx, blocking_rx) = mpsc::channel();
21312 let blocking = executor.schedule_blocking(move || {
21313 blocking_tx.send(()).unwrap();
21314 });
21315 blocking_rx
21318 .recv_timeout(Duration::from_secs(10))
21319 .expect("blocking-only residue must be serviced by a Rayon worker");
21320 returned_rx
21321 .recv_timeout(Duration::from_secs(10))
21322 .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
21323 release_tx.send(()).unwrap();
21324 futures::executor::block_on(task);
21325 futures::executor::block_on(blocking).unwrap();
21326 executor.shutdown_timers();
21327 futures::executor::block_on(timer_task);
21328 }
21329
21330 #[cfg(napi_runtime_os_threads)]
21331 #[test]
21332 fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
21333 let options = RuntimeOptions {
21334 flavor: RuntimeFlavor::MultiThread,
21335 worker_threads: 2,
21336 max_blocking_tasks: 1,
21337 thread_name_prefix: "stopped-exit-compensation".to_string(),
21338 park_deadline: None,
21339 drain_linger: DEFAULT_DRAIN_LINGER,
21340 };
21341 let executor =
21342 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21343 let ran = Arc::new(AtomicBool::new(false));
21344 let blocking_ran = Arc::clone(&ran);
21345
21346 executor
21350 .active_drainers
21351 .store(executor.max_drainers, Ordering::SeqCst);
21352 let blocking = executor.schedule_blocking(move || {
21353 blocking_ran.store(true, Ordering::SeqCst);
21354 });
21355 assert!(!executor.blocking_queue.lock().unwrap().is_empty());
21356
21357 executor.stop.begin_shutdown();
21358 executor.compensate_queued_work_wake(true);
21359 let ran_after_stop = ran.load(Ordering::SeqCst);
21360
21361 executor.active_drainers.store(0, Ordering::SeqCst);
21362 executor.begin_shutdown();
21363 let blocking_result = futures::executor::block_on(blocking);
21364
21365 assert!(
21366 !ran_after_stop,
21367 "exit compensation started queued blocking work after stop"
21368 );
21369 assert!(
21370 blocking_result.is_err(),
21371 "shutdown must cancel the queued blocking job"
21372 );
21373 }
21374
21375 #[cfg(napi_runtime_os_threads)]
21376 #[test]
21377 fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
21378 use std::sync::mpsc;
21387 use std::time::Duration;
21388
21389 const ROUNDS: usize = 20_000;
21390
21391 let (done_tx, done_rx) = mpsc::channel();
21392 let registry = Arc::new(ParkedDrivers::default());
21393 let work = Arc::new(AtomicBool::new(false));
21394
21395 let consumer = {
21396 let registry = Arc::clone(®istry);
21397 let work = Arc::clone(&work);
21398 std::thread::spawn(move || {
21399 let parker = Arc::new(DriverParker::default());
21400 for _ in 0..ROUNDS {
21401 loop {
21402 if work.swap(false, Ordering::SeqCst) {
21403 break;
21404 }
21405 registry.register(&parker);
21408 if work.load(Ordering::SeqCst) {
21409 registry.deregister(&parker);
21410 continue;
21411 }
21412 parker.park();
21413 registry.deregister(&parker);
21414 }
21415 }
21416 done_tx.send(()).unwrap();
21417 })
21418 };
21419
21420 for _ in 0..ROUNDS {
21421 work.store(true, Ordering::SeqCst);
21422 let _ = registry.wake_one();
21426 while work.load(Ordering::SeqCst) {
21427 std::hint::spin_loop();
21428 }
21429 }
21430
21431 done_rx
21432 .recv_timeout(Duration::from_secs(30))
21433 .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
21434 consumer.join().unwrap();
21435 }
21436
21437 #[cfg(napi_runtime_os_threads)]
21438 #[test]
21439 fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
21440 let options = RuntimeOptions {
21444 flavor: RuntimeFlavor::MultiThread,
21445 worker_threads: 2,
21446 max_blocking_tasks: 2,
21447 thread_name_prefix: "lifo-order".to_string(),
21448 park_deadline: None,
21449 drain_linger: DEFAULT_DRAIN_LINGER,
21450 };
21451 let executor =
21452 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21453 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21454
21455 let old_order = Arc::clone(&order);
21458 let sched = Arc::clone(&executor);
21459 let (old_runnable, old_task) = async_task::spawn(
21460 async move {
21461 old_order.lock().unwrap().push("old");
21462 },
21463 move |r| sched.schedule(r),
21464 );
21465 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(old_runnable);
21467 old_task.detach();
21468
21469 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21472 let new_order = Arc::clone(&order);
21473 let sched = Arc::clone(&executor);
21474 let (new_runnable, new_task) = async_task::spawn(
21475 async move {
21476 new_order.lock().unwrap().push("new");
21477 },
21478 move |r| sched.schedule(r),
21479 );
21480 executor.schedule(new_runnable);
21481 new_task.detach();
21482 assert_eq!(
21483 executor.injector.len(),
21484 1,
21485 "the slot-path schedule must not push to the shared FIFO"
21486 );
21487
21488 assert!(executor.run_one(), "run_one must pop the slot first");
21489 assert!(executor.run_one(), "run_one must then pop the FIFO");
21490 assert!(!executor.run_one(), "no work must remain");
21491 assert_eq!(
21492 *order.lock().unwrap(),
21493 vec!["new", "old"],
21494 "the slot runnable (newest) must run before older FIFO work"
21495 );
21496 }
21497
21498 #[cfg(napi_runtime_os_threads)]
21499 #[test]
21500 fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
21501 let options = RuntimeOptions {
21502 flavor: RuntimeFlavor::MultiThread,
21503 worker_threads: 2,
21504 max_blocking_tasks: 1,
21505 thread_name_prefix: "fifo-owner".to_string(),
21506 park_deadline: None,
21507 drain_linger: DEFAULT_DRAIN_LINGER,
21508 };
21509 let executor =
21510 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21511 let observed_owner = Arc::new(Mutex::new(None));
21512 let observed = Arc::clone(&observed_owner);
21513 let (runnable, task) = async_task::spawn(
21514 async move {
21515 *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21516 },
21517 |_| {},
21518 );
21519 task.detach();
21520 executor.metrics.runnable_scheduled();
21521 executor.push_fifo_raw(runnable);
21522 executor.active_drainers.store(1, Ordering::Release);
21523
21524 let owner = executor.fresh_owner_token();
21525 let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
21526 Arc::clone(&executor).drain();
21527
21528 assert_eq!(
21529 *observed_owner.lock().unwrap(),
21530 Some(None),
21531 "FIFO runnables must not borrow a blocking closure's over-cap privilege"
21532 );
21533 assert_eq!(
21534 BLOCKING_OWNER.with(std::cell::Cell::get),
21535 Some(owner),
21536 "the ambient blocking owner must be restored after the runnable"
21537 );
21538 }
21539
21540 #[cfg(napi_runtime_os_threads)]
21541 #[test]
21542 fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
21543 use std::sync::mpsc;
21547 use std::time::Duration;
21548
21549 let (done_tx, done_rx) = mpsc::channel();
21550 let runner = std::thread::spawn(move || {
21551 let options = RuntimeOptions {
21552 flavor: RuntimeFlavor::MultiThread,
21553 worker_threads: 1,
21554 max_blocking_tasks: 1,
21555 thread_name_prefix: "lifo-drain".to_string(),
21556 park_deadline: None,
21557 drain_linger: DEFAULT_DRAIN_LINGER,
21558 };
21559 let executor =
21560 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21561 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21562
21563 let p_exec = Arc::clone(&executor);
21564 let p_order = Arc::clone(&order);
21565 let p_body = async move {
21566 p_order.lock().unwrap().push("P");
21567 let c_order = Arc::clone(&p_order);
21569 let sched = Arc::clone(&p_exec);
21570 let (c_runnable, c_task) = async_task::spawn(
21571 async move {
21572 c_order.lock().unwrap().push("C");
21573 },
21574 move |r| sched.schedule(r),
21575 );
21576 p_exec.schedule(c_runnable);
21577 c_task.detach();
21578 };
21579
21580 let mut tasks = Vec::new();
21584 let sched = Arc::clone(&executor);
21585 let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
21586 executor.metrics.runnable_scheduled();
21587 executor.push_fifo_raw(p_runnable);
21588 tasks.push(p_task);
21589 for name in ["T1", "T2"] {
21590 let t_order = Arc::clone(&order);
21591 let sched = Arc::clone(&executor);
21592 let (t_runnable, t_task) = async_task::spawn(
21593 async move {
21594 t_order.lock().unwrap().push(name);
21595 },
21596 move |r| sched.schedule(r),
21597 );
21598 executor.metrics.runnable_scheduled();
21599 executor.push_fifo_raw(t_runnable);
21600 tasks.push(t_task);
21601 }
21602 executor.ensure_drainer();
21603
21604 for task in tasks {
21605 futures::executor::block_on(task);
21606 }
21607 wait_until("the detached child C ran", || {
21608 order.lock().unwrap().len() == 4
21609 });
21610 assert_eq!(
21611 *order.lock().unwrap(),
21612 vec!["P", "C", "T1", "T2"],
21613 "the slot child C must run right after its scheduler P, ahead of older FIFO work"
21614 );
21615 let _ = done_tx.send(());
21616 });
21617
21618 match done_rx.recv_timeout(Duration::from_secs(10)) {
21619 Ok(()) => runner.join().unwrap(),
21620 Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
21621 }
21622 }
21623
21624 #[cfg(napi_runtime_os_threads)]
21625 #[test]
21626 fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
21627 let options = RuntimeOptions {
21633 flavor: RuntimeFlavor::MultiThread,
21634 worker_threads: 2,
21635 max_blocking_tasks: 2,
21636 thread_name_prefix: "lifo-displace".to_string(),
21637 park_deadline: None,
21638 drain_linger: DEFAULT_DRAIN_LINGER,
21639 };
21640 let executor =
21641 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21642 executor
21643 .active_drainers
21644 .store(executor.max_drainers, Ordering::SeqCst);
21645 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21646
21647 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21648 for name in ["a", "b"] {
21649 let n_order = Arc::clone(&order);
21650 let sched = Arc::clone(&executor);
21651 let (runnable, task) = async_task::spawn(
21652 async move {
21653 n_order.lock().unwrap().push(name);
21654 },
21655 move |r| sched.schedule(r),
21656 );
21657 executor.schedule(runnable);
21658 task.detach();
21659 }
21660 assert_eq!(
21661 executor.injector.len(),
21662 1,
21663 "the displaced occupant (a) must have been pushed to the shared FIFO"
21664 );
21665
21666 assert!(executor.run_one(), "run_one must pop the slot (b) first");
21667 assert!(
21668 executor.run_one(),
21669 "run_one must then pop the displaced (a) from the FIFO"
21670 );
21671 assert!(!executor.run_one(), "no work must remain");
21672 assert_eq!(
21673 *order.lock().unwrap(),
21674 vec!["b", "a"],
21675 "the newest schedule must win the slot; the displaced one must survive via the FIFO"
21676 );
21677 executor.active_drainers.store(0, Ordering::SeqCst);
21678 }
21679
21680 #[cfg(napi_runtime_os_threads)]
21681 #[test]
21682 fn lifo_slot_is_executor_scoped() {
21683 let options = RuntimeOptions {
21688 flavor: RuntimeFlavor::MultiThread,
21689 worker_threads: 2,
21690 max_blocking_tasks: 2,
21691 thread_name_prefix: "lifo-scope".to_string(),
21692 park_deadline: None,
21693 drain_linger: DEFAULT_DRAIN_LINGER,
21694 };
21695 let exec1 =
21696 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21697 let exec2 =
21698 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21699 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21700 exec1
21701 .active_drainers
21702 .store(exec1.max_drainers, Ordering::SeqCst);
21703 exec2
21704 .active_drainers
21705 .store(exec2.max_drainers, Ordering::SeqCst);
21706 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21707
21708 let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
21710 let r1_order = Arc::clone(&order);
21711 let sched = Arc::clone(&exec1);
21712 let (r1, t1) = async_task::spawn(
21713 async move {
21714 r1_order.lock().unwrap().push("one");
21715 },
21716 move |r| sched.schedule(r),
21717 );
21718 exec1.schedule(r1);
21719 t1.detach();
21720 assert!(
21721 exec1.injector.is_empty(),
21722 "r1 must sit in the slot, not exec1's FIFO"
21723 );
21724
21725 {
21726 let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
21729 let r2_order = Arc::clone(&order);
21730 let sched = Arc::clone(&exec2);
21731 let (r2, t2) = async_task::spawn(
21732 async move {
21733 r2_order.lock().unwrap().push("two");
21734 },
21735 move |r| sched.schedule(r),
21736 );
21737 exec2.schedule(r2);
21738 t2.detach();
21739 assert_eq!(
21740 exec2.injector.len(),
21741 1,
21742 "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
21743 );
21744 assert!(
21745 exec2.pop_lifo_slot().is_none(),
21746 "exec2 must not pop exec1's slot entry (executor-scoped pop)"
21747 );
21748 assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
21749 }
21750
21751 assert!(
21753 exec1.run_one(),
21754 "exec1's slot entry must have survived the foreign activity"
21755 );
21756 assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
21757 exec1.active_drainers.store(0, Ordering::SeqCst);
21758 exec2.active_drainers.store(0, Ordering::SeqCst);
21759 }
21760
21761 #[cfg(napi_runtime_os_threads)]
21762 #[test]
21763 fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
21764 use std::sync::mpsc;
21771 use std::time::Duration;
21772
21773 const CHAIN: u64 = 200; fn spawn_link(
21776 executor: &Arc<MultiThreadExecutor>,
21777 remaining: u64,
21778 counter: Arc<AtomicU64>,
21779 done: mpsc::Sender<()>,
21780 ) {
21781 let body_exec = Arc::clone(executor);
21782 let body = async move {
21783 counter.fetch_add(1, Ordering::SeqCst);
21784 if remaining > 0 {
21785 spawn_link(&body_exec, remaining - 1, counter, done);
21786 } else {
21787 done.send(()).unwrap();
21788 }
21789 };
21790 let sched = Arc::clone(executor);
21791 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21792 executor.schedule(runnable);
21793 task.detach();
21794 }
21795
21796 let (done_tx, done_rx) = mpsc::channel();
21797 let runner = std::thread::spawn(move || {
21798 let metrics = Arc::new(RuntimeMetrics::default());
21799 let options = RuntimeOptions {
21800 flavor: RuntimeFlavor::MultiThread,
21801 worker_threads: 1,
21802 max_blocking_tasks: 1,
21803 thread_name_prefix: "lifo-budget".to_string(),
21804 park_deadline: None,
21805 drain_linger: DEFAULT_DRAIN_LINGER,
21806 };
21807 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21808
21809 let counter = Arc::new(AtomicU64::new(0));
21810 let (chain_tx, chain_rx) = mpsc::channel();
21811 spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
21812
21813 chain_rx
21814 .recv_timeout(Duration::from_secs(10))
21815 .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
21816 assert_eq!(
21817 counter.load(Ordering::SeqCst),
21818 CHAIN,
21819 "every chain link must have run"
21820 );
21821 wait_until("runnable counters settle to zero (no slot leak)", || {
21822 metrics.queued_runnables.load(Ordering::Relaxed) == 0
21823 && metrics.active_runnables.load(Ordering::Relaxed) == 0
21824 });
21825 let _ = done_tx.send(());
21826 });
21827
21828 match done_rx.recv_timeout(Duration::from_secs(15)) {
21829 Ok(()) => runner.join().unwrap(),
21830 Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
21831 }
21832 }
21833
21834 #[cfg(napi_runtime_os_threads)]
21835 #[test]
21836 fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
21837 let options = RuntimeOptions {
21844 flavor: RuntimeFlavor::MultiThread,
21845 worker_threads: 2,
21846 max_blocking_tasks: 2,
21847 thread_name_prefix: "lifo-flush".to_string(),
21848 park_deadline: None,
21849 drain_linger: DEFAULT_DRAIN_LINGER,
21850 };
21851 let executor =
21852 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21853 executor
21854 .active_drainers
21855 .store(executor.max_drainers, Ordering::SeqCst);
21856 let ran = Arc::new(AtomicBool::new(false));
21857
21858 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21859 let ran_flag = Arc::clone(&ran);
21860 let sched = Arc::clone(&executor);
21861 let (runnable, task) = async_task::spawn(
21862 async move {
21863 ran_flag.store(true, Ordering::SeqCst);
21864 },
21865 move |r| sched.schedule(r),
21866 );
21867 executor.schedule(runnable);
21868 task.detach();
21869 assert!(
21870 executor.injector.is_empty(),
21871 "the runnable must start in the slot"
21872 );
21873
21874 executor.flush_lifo_slot();
21875 assert!(
21876 executor.pop_lifo_slot().is_none(),
21877 "the slot must be empty after a flush"
21878 );
21879 assert_eq!(
21880 executor.injector.len(),
21881 1,
21882 "the flushed runnable must be on the shared FIFO"
21883 );
21884 assert!(executor.run_one(), "the flushed runnable must still run");
21885 assert!(
21886 ran.load(Ordering::SeqCst),
21887 "the flushed task must complete, not be dropped"
21888 );
21889 executor.active_drainers.store(0, Ordering::SeqCst);
21890 }
21891
21892 #[cfg(napi_runtime_os_threads)]
21893 #[test]
21894 fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
21895 let options = RuntimeOptions {
21900 flavor: RuntimeFlavor::MultiThread,
21901 worker_threads: 2,
21902 max_blocking_tasks: 2,
21903 thread_name_prefix: "lifo-owner".to_string(),
21904 park_deadline: None,
21905 drain_linger: DEFAULT_DRAIN_LINGER,
21906 };
21907 let executor =
21908 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21909 let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
21910
21911 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21912
21913 let observed_slot = Arc::clone(&observed);
21917 let sched = Arc::clone(&executor);
21918 let (runnable, task) = async_task::spawn(
21919 async move {
21920 *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21921 },
21922 move |r| sched.schedule(r),
21923 );
21924 executor.schedule(runnable);
21925 task.detach();
21926 assert!(
21927 executor.injector.is_empty(),
21928 "the runnable must sit in the slot"
21929 );
21930
21931 let token = executor.fresh_owner_token();
21935 let _owner = BlockingOwnerGuard::enter(Some(token));
21936 assert!(executor.run_one(), "the slot runnable must run");
21937 assert_eq!(
21938 *observed.lock().unwrap(),
21939 Some(None),
21940 "a slot runnable must observe a CLEARED blocking owner"
21941 );
21942 assert_eq!(
21943 BLOCKING_OWNER.with(std::cell::Cell::get),
21944 Some(token),
21945 "the ambient owner frame must be restored after the slot runnable"
21946 );
21947 }
21948
21949 #[cfg(napi_runtime_os_threads)]
21955 struct PingPongShared {
21956 count: AtomicU64,
21957 stop: AtomicBool,
21958 stop_after: u64,
21959 wakers: Mutex<[Option<std::task::Waker>; 2]>,
21960 }
21961
21962 #[cfg(napi_runtime_os_threads)]
21963 struct PingPong {
21964 shared: Arc<PingPongShared>,
21965 me: usize,
21966 }
21967
21968 #[cfg(napi_runtime_os_threads)]
21969 impl Future for PingPong {
21970 type Output = ();
21971 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21972 let shared = &self.shared;
21973 let polls = shared.count.fetch_add(1, Ordering::SeqCst);
21974 if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
21975 let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
21976 if let Some(partner) = partner {
21977 partner.wake();
21978 }
21979 return Poll::Ready(());
21980 }
21981 let mut wakers = shared.wakers.lock().unwrap();
21982 wakers[self.me] = Some(cx.waker().clone());
21983 if let Some(partner) = wakers[1 - self.me].take() {
21984 drop(wakers);
21985 partner.wake();
21986 }
21987 Poll::Pending
21988 }
21989 }
21990
21991 #[cfg(napi_runtime_os_threads)]
21992 fn spawn_ping_pong_pair(
21993 executor: &Arc<MultiThreadExecutor>,
21994 shared: &Arc<PingPongShared>,
21995 ) -> Vec<Task<()>> {
21996 let mut tasks = Vec::new();
21997 for me in 0..2 {
21998 let sched = Arc::clone(executor);
21999 let (runnable, task) = async_task::spawn(
22000 PingPong {
22001 shared: Arc::clone(shared),
22002 me,
22003 },
22004 move |r| sched.schedule(r),
22005 );
22006 executor.schedule(runnable);
22007 tasks.push(task);
22008 }
22009 tasks
22010 }
22011
22012 #[cfg(napi_runtime_os_threads)]
22013 #[test]
22014 fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
22015 use std::sync::mpsc;
22022 use std::time::Duration;
22023
22024 let (done_tx, done_rx) = mpsc::channel();
22025 let runner = std::thread::spawn(move || {
22026 let options = RuntimeOptions {
22027 flavor: RuntimeFlavor::MultiThread,
22028 worker_threads: 1,
22029 max_blocking_tasks: 0,
22030 thread_name_prefix: "lifo-streak".to_string(),
22031 park_deadline: None,
22032 drain_linger: DEFAULT_DRAIN_LINGER,
22033 };
22034 let executor =
22035 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22036
22037 let shared = Arc::new(PingPongShared {
22038 count: AtomicU64::new(0),
22039 stop: AtomicBool::new(false),
22040 stop_after: u64::MAX,
22041 wakers: Mutex::new([None, None]),
22042 });
22043 let pair = spawn_ping_pong_pair(&executor, &shared);
22044
22045 let (t3_tx, t3_rx) = mpsc::channel::<()>();
22047 let sched = Arc::clone(&executor);
22048 let (t3_runnable, t3_task) = async_task::spawn(
22049 async move {
22050 t3_tx.send(()).unwrap();
22051 },
22052 move |r| sched.schedule(r),
22053 );
22054 executor.schedule(t3_runnable);
22055
22056 t3_rx
22057 .recv_timeout(Duration::from_secs(10))
22058 .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
22059 assert!(
22060 shared.count.load(Ordering::SeqCst) >= 32,
22061 "the pair must have been bouncing through the slot before T3 got its turn"
22062 );
22063
22064 shared.stop.store(true, Ordering::SeqCst);
22066 let stored: Vec<_> = shared
22067 .wakers
22068 .lock()
22069 .unwrap()
22070 .iter_mut()
22071 .filter_map(Option::take)
22072 .collect();
22073 for waker in stored {
22074 waker.wake();
22075 }
22076 for task in pair {
22077 futures::executor::block_on(task);
22078 }
22079 futures::executor::block_on(t3_task);
22080 let _ = done_tx.send(());
22081 });
22082
22083 match done_rx.recv_timeout(Duration::from_secs(15)) {
22084 Ok(()) => runner.join().unwrap(),
22085 Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
22086 }
22087 }
22088
22089 #[cfg(napi_runtime_os_threads)]
22090 #[test]
22091 fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
22092 use std::sync::mpsc;
22099 use std::time::Duration;
22100
22101 const BOUNCES: u64 = 2_000; let (done_tx, done_rx) = mpsc::channel();
22104 let runner = std::thread::spawn(move || {
22105 let options = RuntimeOptions {
22106 flavor: RuntimeFlavor::MultiThread,
22107 worker_threads: 1,
22108 max_blocking_tasks: 1,
22109 thread_name_prefix: "lifo-blocking".to_string(),
22110 park_deadline: None,
22111 drain_linger: DEFAULT_DRAIN_LINGER,
22112 };
22113 let executor =
22114 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22115
22116 let shared = Arc::new(PingPongShared {
22117 count: AtomicU64::new(0),
22118 stop: AtomicBool::new(false),
22119 stop_after: BOUNCES,
22120 wakers: Mutex::new([None, None]),
22121 });
22122 let pair = spawn_ping_pong_pair(&executor, &shared);
22123
22124 let blocking = executor.schedule_blocking(|| 11usize);
22126
22127 assert_eq!(
22128 futures::executor::block_on(blocking).unwrap(),
22129 11,
22130 "the queued blocking job must complete despite the hot LIFO pair"
22131 );
22132 for task in pair {
22133 futures::executor::block_on(task);
22134 }
22135 assert!(
22136 shared.count.load(Ordering::SeqCst) >= BOUNCES,
22137 "the pair must have actually run its full bounce quota"
22138 );
22139 let _ = done_tx.send(());
22140 });
22141
22142 match done_rx.recv_timeout(Duration::from_secs(15)) {
22143 Ok(()) => runner.join().unwrap(),
22144 Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
22145 }
22146 }
22147
22148 #[cfg(napi_runtime_os_threads)]
22149 #[test]
22150 fn infinite_hot_runnable_yields_to_blocking_fifo() {
22151 use std::sync::mpsc;
22152
22153 struct HotFuture {
22154 stop: Arc<AtomicBool>,
22155 }
22156
22157 impl Future for HotFuture {
22158 type Output = ();
22159
22160 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22161 if self.stop.load(Ordering::SeqCst) {
22162 Poll::Ready(())
22163 } else {
22164 cx.waker().wake_by_ref();
22165 Poll::Pending
22166 }
22167 }
22168 }
22169
22170 let options = RuntimeOptions {
22171 flavor: RuntimeFlavor::MultiThread,
22172 worker_threads: 2,
22173 max_blocking_tasks: 1,
22174 thread_name_prefix: "blocking-fairness".to_string(),
22175 park_deadline: None,
22176 drain_linger: DEFAULT_DRAIN_LINGER,
22177 };
22178 let executor =
22179 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22180 let stop = Arc::new(AtomicBool::new(false));
22181 let scheduler = Arc::clone(&executor);
22182 let (runnable, task) = async_task::spawn(
22183 HotFuture {
22184 stop: Arc::clone(&stop),
22185 },
22186 move |r| scheduler.schedule(r),
22187 );
22188 task.detach();
22189 let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
22190 executor
22191 .blocking_queue
22192 .lock()
22193 .unwrap()
22194 .push(QueuedBlocking {
22195 id: BlockingJobId(u64::MAX),
22196 run: Box::new(move || blocking_tx.send(()).unwrap()),
22197 });
22198
22199 let _driver = OnPoolWorkerGuard::enter(executor.id);
22200 executor.schedule(runnable);
22201 let mut runnable_streak = 0;
22202 for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
22203 assert!(executor.run_one_fair(&mut runnable_streak));
22204 }
22205 blocking_rx
22206 .try_recv()
22207 .expect("an infinite self-waker must yield a bounded quantum to blocking work");
22208
22209 stop.store(true, Ordering::SeqCst);
22210 assert!(executor.run_one_fair(&mut runnable_streak));
22211 }
22212
22213 #[cfg(napi_runtime_os_threads)]
22214 #[test]
22215 fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22216 use std::sync::mpsc;
22217
22218 struct HotFuture {
22219 stop: Arc<AtomicBool>,
22220 }
22221
22222 impl Future for HotFuture {
22223 type Output = ();
22224
22225 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22226 if self.stop.load(Ordering::SeqCst) {
22227 Poll::Ready(())
22228 } else {
22229 cx.waker().wake_by_ref();
22230 Poll::Pending
22231 }
22232 }
22233 }
22234
22235 let options = RuntimeOptions {
22236 flavor: RuntimeFlavor::MultiThread,
22237 worker_threads: 1,
22238 max_blocking_tasks: 1,
22239 thread_name_prefix: "exact-dependency-fairness".to_string(),
22240 park_deadline: None,
22241 drain_linger: DEFAULT_DRAIN_LINGER,
22242 };
22243 let executor =
22244 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22245 let stop = Arc::new(AtomicBool::new(false));
22246 let (queued_tx, queued_rx) = mpsc::channel();
22247 let (exact_tx, exact_rx) = mpsc::channel();
22248 let owner_executor = Arc::clone(&executor);
22249 let owner_stop = Arc::clone(&stop);
22250 let owner = executor.schedule_blocking(move || {
22251 let scheduler = Arc::clone(&owner_executor);
22252 let hot_stop = Arc::clone(&owner_stop);
22253 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22254 scheduler.schedule(runnable);
22255 });
22256 owner_executor.schedule(runnable);
22257
22258 let exact_stop = Arc::clone(&owner_stop);
22259 let exact = owner_executor.schedule_blocking(move || {
22260 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22261 41usize
22262 });
22263 queued_tx.send(()).unwrap();
22264
22265 let mut value = None;
22266 {
22267 let mut future = std::pin::pin!(async {
22268 value = Some(exact.await.unwrap());
22269 hot_task.await;
22270 });
22271 assert_eq!(
22272 owner_executor.block_on(future.as_mut()),
22273 BlockOnOutcome::Completed
22274 );
22275 }
22276 value.unwrap()
22277 });
22278
22279 queued_rx
22280 .recv_timeout(Duration::from_secs(2))
22281 .expect("the exact dependency must be queued behind the saturated owner");
22282 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22283 stop.store(true, Ordering::SeqCst);
22284 assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
22285 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22286 assert_eq!(
22287 exact_before_stop,
22288 Some(true),
22289 "a hot local runnable must yield to the exact dependency before it is stopped"
22290 );
22291 }
22292
22293 #[cfg(napi_runtime_os_threads)]
22294 #[test]
22295 fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
22296 use std::sync::mpsc;
22297
22298 struct BlockingPoll {
22299 started: Option<mpsc::Sender<()>>,
22300 released: Arc<(Mutex<bool>, Condvar)>,
22301 }
22302
22303 impl Future for BlockingPoll {
22304 type Output = ();
22305
22306 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22307 if let Some(started) = self.started.take() {
22308 started.send(()).unwrap();
22309 }
22310 let (released, changed) = &*self.released;
22311 let mut released = released
22312 .lock()
22313 .unwrap_or_else(std::sync::PoisonError::into_inner);
22314 while !*released {
22315 released = changed
22316 .wait(released)
22317 .unwrap_or_else(std::sync::PoisonError::into_inner);
22318 }
22319 Poll::Ready(())
22320 }
22321 }
22322
22323 let options = RuntimeOptions {
22324 flavor: RuntimeFlavor::MultiThread,
22325 worker_threads: 3,
22326 max_blocking_tasks: 2,
22327 thread_name_prefix: "exact-before-last-slot".to_string(),
22328 park_deadline: None,
22329 drain_linger: DEFAULT_DRAIN_LINGER,
22330 };
22331 let executor =
22332 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22333
22334 let released = Arc::new((Mutex::new(false), Condvar::new()));
22335 let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
22336 let mut blockers = Vec::new();
22337 for _ in 0..2 {
22338 let scheduler = Arc::clone(&executor);
22339 let blocker_released = Arc::clone(&released);
22340 let started = blocker_started_tx.clone();
22341 let (runnable, task) = async_task::spawn(
22342 BlockingPoll {
22343 started: Some(started),
22344 released: blocker_released,
22345 },
22346 move |runnable| scheduler.schedule(runnable),
22347 );
22348 executor.schedule(runnable);
22349 blockers.push(task);
22350 }
22351 drop(blocker_started_tx);
22352 for _ in 0..2 {
22353 blocker_started_rx
22354 .recv_timeout(Duration::from_secs(2))
22355 .expect("two workers must be gated before the owner starts");
22356 }
22357
22358 let dependency_started = Arc::new(AtomicBool::new(false));
22359 let owner_executor = Arc::clone(&executor);
22360 let owner_dependency_started = Arc::clone(&dependency_started);
22361 let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
22362 let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
22363 let owner_signal = Arc::clone(&dependency_signal);
22364 let owner = executor.schedule_blocking(move || {
22365 let unrelated_signal = Arc::clone(&owner_signal);
22366 let unrelated_started = Arc::clone(&owner_dependency_started);
22367 let unrelated = owner_executor.schedule_blocking(move || {
22368 let observed = unrelated_started.load(Ordering::SeqCst);
22369 unrelated_observed_tx.send(observed).unwrap();
22370 let (ready, changed) = &*unrelated_signal;
22371 let mut ready = ready
22372 .lock()
22373 .unwrap_or_else(std::sync::PoisonError::into_inner);
22374 while !*ready {
22375 ready = changed
22376 .wait(ready)
22377 .unwrap_or_else(std::sync::PoisonError::into_inner);
22378 }
22379 });
22380
22381 let exact_signal = Arc::clone(&owner_signal);
22382 let exact_started = Arc::clone(&owner_dependency_started);
22383 let exact = owner_executor.schedule_blocking(move || {
22384 exact_started.store(true, Ordering::SeqCst);
22385 let (ready, changed) = &*exact_signal;
22386 *ready
22387 .lock()
22388 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22389 changed.notify_all();
22390 41usize
22391 });
22392
22393 let mut output = None;
22394 {
22395 let mut future = std::pin::pin!(async {
22396 output = Some(exact.await.unwrap());
22397 unrelated.await.unwrap();
22398 });
22399 assert_eq!(
22400 owner_executor.block_on(future.as_mut()),
22401 BlockOnOutcome::Completed
22402 );
22403 }
22404 output.unwrap()
22405 });
22406
22407 let (owner_done_tx, owner_done_rx) = mpsc::channel();
22408 let owner_waiter = std::thread::spawn(move || {
22409 owner_done_tx
22410 .send(futures::executor::block_on(owner))
22411 .unwrap();
22412 });
22413 let (completed_while_gated, owner_result) =
22414 match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
22415 Ok(result) => (true, result),
22416 Err(_) => {
22417 let (released, changed) = &*released;
22420 *released
22421 .lock()
22422 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22423 changed.notify_all();
22424 (
22425 false,
22426 owner_done_rx
22427 .recv_timeout(Duration::from_secs(2))
22428 .expect("releasing the other workers must let the broken ordering recover"),
22429 )
22430 }
22431 };
22432 let unrelated_observed = unrelated_observed_rx
22433 .recv_timeout(Duration::from_secs(2))
22434 .expect("the unrelated FIFO job must eventually run");
22435
22436 {
22437 let (released, changed) = &*released;
22438 *released
22439 .lock()
22440 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22441 changed.notify_all();
22442 }
22443 owner_waiter.join().unwrap();
22444 for blocker in blockers {
22445 futures::executor::block_on(blocker);
22446 }
22447 executor.begin_shutdown();
22448 executor.wait_until_scheduler_idle();
22449
22450 assert!(
22451 completed_while_gated,
22452 "the exact dependency must complete while unrelated workers remain gated"
22453 );
22454 assert_eq!(owner_result.unwrap(), 41);
22455 assert!(
22456 unrelated_observed,
22457 "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
22458 );
22459 }
22460
22461 #[cfg(napi_runtime_os_threads)]
22462 #[test]
22463 fn blocking_saturation_preserves_a_runnable_execution_lane() {
22464 use std::{sync::mpsc, time::Duration};
22465
22466 let options = RuntimeOptions {
22467 flavor: RuntimeFlavor::MultiThread,
22468 worker_threads: 2,
22469 max_blocking_tasks: 2,
22470 thread_name_prefix: "blocking-reserve".to_string(),
22471 park_deadline: None,
22472 drain_linger: DEFAULT_DRAIN_LINGER,
22473 }
22474 .validate()
22475 .expect("production options must reserve a runnable lane");
22476 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
22477 let executor =
22478 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22479 let (release_tx, release_rx) = mpsc::channel();
22480 let (started_tx, started_rx) = mpsc::channel();
22481 let first = executor.schedule_blocking(move || {
22482 started_tx.send(()).unwrap();
22483 release_rx.recv().unwrap();
22484 });
22485 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
22486
22487 let (queued_tx, queued_rx) = mpsc::channel();
22488 let second = executor.schedule_blocking(move || {
22489 queued_tx.send(()).unwrap();
22490 });
22491 assert!(
22492 queued_rx.try_recv().is_err(),
22493 "the second blocking job must remain queued while the sole blocking slot is occupied"
22494 );
22495
22496 let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
22497 let scheduler = Arc::clone(&executor);
22498 let (runnable, task) = async_task::spawn(
22499 async move {
22500 runnable_tx.send(()).unwrap();
22501 },
22502 move |r| scheduler.schedule(r),
22503 );
22504 task.detach();
22505 executor.schedule(runnable);
22506 runnable_rx
22507 .recv_timeout(Duration::from_secs(1))
22508 .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
22509
22510 release_tx.send(()).unwrap();
22511 futures::executor::block_on(first).unwrap();
22512 futures::executor::block_on(second).unwrap();
22513 queued_rx
22514 .recv_timeout(Duration::from_secs(1))
22515 .expect("queued blocking work must run after the occupied slot is released");
22516 }
22517
22518 #[cfg(napi_runtime_os_threads)]
22519 #[test]
22520 fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
22521 use std::sync::mpsc;
22529 use std::time::Duration;
22530
22531 struct SpawnOnReady {
22532 executor: Arc<MultiThreadExecutor>,
22533 child_tx: Option<mpsc::Sender<u64>>,
22534 }
22535 impl Future for SpawnOnReady {
22536 type Output = ();
22537 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22538 let child_tx = self.child_tx.take().expect("polled once");
22539 let sched = Arc::clone(&self.executor);
22540 let (runnable, task) = async_task::spawn(
22541 async move {
22542 child_tx.send(21).unwrap();
22543 },
22544 move |r| sched.schedule(r),
22545 );
22546 self.executor.schedule(runnable);
22548 task.detach();
22549 Poll::Ready(())
22550 }
22551 }
22552
22553 let (done_tx, done_rx) = mpsc::channel();
22554 let runner = std::thread::spawn(move || {
22555 let options = RuntimeOptions {
22556 flavor: RuntimeFlavor::MultiThread,
22557 worker_threads: 2,
22558 max_blocking_tasks: 2,
22559 thread_name_prefix: "exit-flush".to_string(),
22560 park_deadline: None,
22561 drain_linger: DEFAULT_DRAIN_LINGER,
22562 };
22563 let executor =
22564 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22565
22566 let (child_tx, child_rx) = mpsc::channel::<u64>();
22567 {
22568 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
22571 let mut future = std::pin::pin!(SpawnOnReady {
22572 executor: Arc::clone(&executor),
22573 child_tx: Some(child_tx)
22574 });
22575 executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
22576 }
22577
22578 let value = child_rx
22582 .recv_timeout(Duration::from_secs(5))
22583 .expect("the child scheduled by the final poll was stranded in the LIFO slot");
22584 assert_eq!(value, 21);
22585 let _ = done_tx.send(());
22586 });
22587
22588 match done_rx.recv_timeout(Duration::from_secs(10)) {
22589 Ok(()) => runner.join().unwrap(),
22590 Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
22591 }
22592 }
22593
22594 #[cfg(napi_runtime_os_threads)]
22598 fn spawn_hot_chain(
22599 executor: &Arc<MultiThreadExecutor>,
22600 stop: Arc<AtomicBool>,
22601 hops: Arc<AtomicU64>,
22602 ) {
22603 if stop.load(Ordering::SeqCst) {
22604 return;
22605 }
22606 let link_exec = Arc::clone(executor);
22607 let body = async move {
22608 hops.fetch_add(1, Ordering::SeqCst);
22609 spawn_hot_chain(&link_exec, stop, hops);
22610 };
22611 let sched = Arc::clone(executor);
22612 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
22613 executor.schedule(runnable);
22614 task.detach();
22615 }
22616
22617 #[cfg(napi_runtime_os_threads)]
22618 #[test]
22619 fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
22620 use std::sync::mpsc;
22629 use std::time::Duration;
22630
22631 let (done_tx, done_rx) = mpsc::channel();
22632 let runner = std::thread::spawn(move || {
22633 let options = RuntimeOptions {
22634 flavor: RuntimeFlavor::MultiThread,
22635 worker_threads: 1,
22636 max_blocking_tasks: 0,
22639 thread_name_prefix: "coop-budget".to_string(),
22640 park_deadline: None,
22641 drain_linger: DEFAULT_DRAIN_LINGER,
22642 };
22643 let executor =
22644 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22645
22646 let stop = Arc::new(AtomicBool::new(false));
22647 let hops = Arc::new(AtomicU64::new(0));
22648
22649 let m_exec = Arc::clone(&executor);
22653 let m_stop = Arc::clone(&stop);
22654 let m_hops = Arc::clone(&hops);
22655 let m_body = async move {
22656 let (tx, rx) = oneshot::channel::<u64>();
22657 let sched_f = Arc::clone(&m_exec);
22659 let (f_runnable, f_task) = async_task::spawn(
22660 async move {
22661 let _ = tx.send(7);
22662 },
22663 move |r| sched_f.schedule(r),
22664 );
22665 m_exec.schedule(f_runnable);
22666 f_task.detach();
22667 spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
22670
22671 let mut inner = std::pin::pin!(async move {
22672 assert_eq!(
22673 rx.await.unwrap(),
22674 7,
22675 "F must run and complete the awaited future"
22676 );
22677 });
22678 m_exec.block_on(inner.as_mut());
22679 m_stop.store(true, Ordering::SeqCst);
22680 };
22681 let sched_m = Arc::clone(&executor);
22682 let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
22683 executor.schedule(m_runnable);
22684
22685 futures::executor::block_on(m_task);
22686 assert!(
22687 hops.load(Ordering::SeqCst) >= 64,
22688 "the chain must have been genuinely hot (>= one budget window) before F ran"
22689 );
22690 let _ = done_tx.send(());
22691 });
22692
22693 match done_rx.recv_timeout(Duration::from_secs(10)) {
22694 Ok(()) => runner.join().unwrap(),
22695 Err(error) => panic!(
22696 "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
22697 ),
22698 }
22699 }
22700
22701 #[cfg(napi_runtime_os_threads)]
22702 #[test]
22703 fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
22704 use std::sync::mpsc;
22705
22706 struct RefillLifo {
22707 executor: Arc<MultiThreadExecutor>,
22708 fifo_ran: Arc<AtomicBool>,
22709 }
22710
22711 impl Future for RefillLifo {
22712 type Output = ();
22713
22714 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22715 if self.fifo_ran.load(Ordering::SeqCst) {
22716 return Poll::Ready(());
22717 }
22718 let scheduler = Arc::clone(&self.executor);
22719 let (runnable, task) =
22720 async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
22721 self.executor.schedule(runnable);
22722 task.detach();
22723 Poll::Pending
22724 }
22725 }
22726
22727 let (done_tx, done_rx) = mpsc::channel();
22728 let runner = std::thread::spawn(move || {
22729 let options = RuntimeOptions {
22730 flavor: RuntimeFlavor::MultiThread,
22731 worker_threads: 1,
22732 max_blocking_tasks: 0,
22733 thread_name_prefix: "forced-fifo".to_string(),
22734 park_deadline: None,
22735 drain_linger: DEFAULT_DRAIN_LINGER,
22736 };
22737 let executor =
22738 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22739 executor
22740 .active_drainers
22741 .store(executor.max_drainers, Ordering::SeqCst);
22742
22743 let fifo_ran = Arc::new(AtomicBool::new(false));
22744 let fifo_flag = Arc::clone(&fifo_ran);
22745 let scheduler = Arc::clone(&executor);
22746 let (fifo, task) = async_task::spawn(
22747 async move {
22748 fifo_flag.store(true, Ordering::SeqCst);
22749 },
22750 move |runnable| scheduler.schedule(runnable),
22751 );
22752 task.detach();
22753 executor.metrics.runnable_scheduled();
22754 executor.push_fifo_raw(fifo);
22755
22756 {
22757 let _driver = OnPoolWorkerGuard::enter(executor.id);
22758 let mut future = std::pin::pin!(RefillLifo {
22759 executor: Arc::clone(&executor),
22760 fifo_ran: Arc::clone(&fifo_ran)
22761 });
22762 executor.block_on(future.as_mut());
22763 while executor.run_one() {}
22764 }
22765 executor.active_drainers.store(0, Ordering::SeqCst);
22766 assert!(fifo_ran.load(Ordering::SeqCst));
22767 done_tx.send(()).unwrap();
22768 });
22769 done_rx
22770 .recv_timeout(Duration::from_secs(3))
22771 .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
22772 runner.join().unwrap();
22773 }
22774
22775 #[cfg(napi_runtime_os_threads)]
22776 #[test]
22777 fn blocking_closure_spawn_is_not_stranded_in_slot() {
22778 use std::sync::mpsc;
22785 use std::time::Duration;
22786
22787 let (done_tx, done_rx) = mpsc::channel();
22788 let runner = std::thread::spawn(move || {
22789 let options = RuntimeOptions {
22790 flavor: RuntimeFlavor::MultiThread,
22791 worker_threads: 2,
22792 max_blocking_tasks: 2,
22793 thread_name_prefix: "blk-spawn-wait".to_string(),
22794 park_deadline: None,
22795 drain_linger: DEFAULT_DRAIN_LINGER,
22796 };
22797 let executor =
22798 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22799
22800 let blk_exec = Arc::clone(&executor);
22803 let handle = executor.schedule_blocking(move || {
22804 let (tx, rx) = mpsc::channel::<u64>();
22805 let sched = Arc::clone(&blk_exec);
22806 let (runnable, task) = async_task::spawn(
22807 async move {
22808 tx.send(21).unwrap();
22809 },
22810 move |r| sched.schedule(r),
22811 );
22812 blk_exec.schedule(runnable);
22813 task.detach();
22814 rx.recv_timeout(Duration::from_secs(5))
22815 .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
22816 });
22817 assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
22818
22819 let (v_tx, v_rx) = mpsc::channel::<u64>();
22822 let v_exec = Arc::clone(&executor);
22823 let v_handle = executor.schedule_blocking(move || {
22824 let sched = Arc::clone(&v_exec);
22825 let (runnable, task) = async_task::spawn(
22826 async move {
22827 v_tx.send(42).unwrap();
22828 },
22829 move |r| sched.schedule(r),
22830 );
22831 v_exec.schedule(runnable);
22832 task.detach();
22833 1u64
22834 });
22835 assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
22836 assert_eq!(
22837 v_rx
22838 .recv_timeout(Duration::from_secs(5))
22839 .expect("fire-and-forget child must complete"),
22840 42
22841 );
22842 let _ = done_tx.send(());
22843 });
22844
22845 match done_rx.recv_timeout(Duration::from_secs(15)) {
22846 Ok(()) => runner.join().unwrap(),
22847 Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
22848 }
22849 }
22850
22851 #[cfg(napi_runtime_os_threads)]
22852 #[test]
22853 fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
22854 use std::sync::{Barrier, mpsc};
22860 use std::time::Duration;
22861
22862 let (done_tx, done_rx) = mpsc::channel();
22863 let runner = std::thread::spawn(move || {
22864 let metrics = Arc::new(RuntimeMetrics::default());
22865 let options = RuntimeOptions {
22866 flavor: RuntimeFlavor::MultiThread,
22867 worker_threads: 2,
22868 max_blocking_tasks: 2,
22869 thread_name_prefix: "rd1-nested-blk".to_string(),
22870 park_deadline: None,
22871 drain_linger: DEFAULT_DRAIN_LINGER,
22872 };
22873 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
22874
22875 let outer_count = 2usize; let barrier = Arc::new(Barrier::new(outer_count));
22877
22878 let mut handles = Vec::new();
22879 for _ in 0..outer_count {
22880 let exec = Arc::clone(&executor);
22881 let barrier = Arc::clone(&barrier);
22882 let handle = executor.schedule_blocking(move || {
22883 barrier.wait();
22885 let inner_exec = Arc::clone(&exec);
22886 let mut value = 0usize;
22887 {
22888 let mut inner = std::pin::pin!(async {
22889 value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
22890 });
22891 exec.block_on(inner.as_mut());
22892 }
22893 value
22894 });
22895 handles.push(handle);
22896 }
22897
22898 for handle in handles {
22899 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
22900 }
22901 let _ = done_tx.send(());
22902 });
22903
22904 match done_rx.recv_timeout(Duration::from_secs(10)) {
22905 Ok(()) => runner.join().unwrap(),
22906 Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
22907 }
22908 }
22909
22910 #[cfg(napi_runtime_os_threads)]
22911 #[test]
22912 fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
22913 let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
22914 let owner = executor.fresh_owner_token();
22915 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22916 let job = BlockingJobId(u64::MAX - 5);
22917 let dependency = TaskDependency::default();
22918 dependency.set_owned(BlockingDependency { job, owner: None });
22919 let ran = Arc::new(AtomicBool::new(false));
22920 let job_ran = Arc::clone(&ran);
22921 executor
22922 .blocking_queue
22923 .lock()
22924 .unwrap()
22925 .push(QueuedBlocking {
22926 id: job,
22927 run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
22928 });
22929
22930 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22931 assert!(
22932 !executor.try_owned_blocking_over_cap(&dependency),
22933 "an untagged worker must not infer ancestry from a sole active owner"
22934 );
22935 assert!(!ran.load(Ordering::SeqCst));
22936 assert!(
22937 executor
22938 .blocking_queue
22939 .lock()
22940 .unwrap()
22941 .jobs
22942 .contains_key(&job),
22943 "rejected owner inference must leave the exact job queued"
22944 );
22945 }
22946
22947 #[cfg(napi_runtime_os_threads)]
22948 #[test]
22949 fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
22950 use std::sync::{Barrier, mpsc};
22951
22952 let options = RuntimeOptions {
22953 flavor: RuntimeFlavor::MultiThread,
22954 worker_threads: 2,
22955 max_blocking_tasks: 2,
22956 thread_name_prefix: "exact-owner-frame".to_string(),
22957 park_deadline: None,
22958 drain_linger: DEFAULT_DRAIN_LINGER,
22959 };
22960 let executor =
22961 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22962 let owners_saturated = Arc::new(Barrier::new(3));
22963 let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
22964 let begin_lending = Arc::new(Barrier::new(3));
22965 let release_dependencies = Arc::new(Barrier::new(3));
22966 let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
22967 let mut owners = Vec::new();
22968
22969 for owner_index in 0..2 {
22970 let owner_executor = Arc::clone(&executor);
22971 let owners_saturated = Arc::clone(&owners_saturated);
22972 let dependency_queued_tx = dependency_queued_tx.clone();
22973 let begin_lending = Arc::clone(&begin_lending);
22974 let release_dependencies = Arc::clone(&release_dependencies);
22975 let dependency_started_tx = dependency_started_tx.clone();
22976 owners.push(executor.schedule_blocking(move || {
22977 let owner_thread = std::thread::current().id();
22978 owners_saturated.wait();
22979 let child_started = dependency_started_tx.clone();
22980 let child_release = Arc::clone(&release_dependencies);
22981 let child = owner_executor.schedule_blocking(move || {
22982 assert_eq!(
22983 std::thread::current().id(),
22984 owner_thread,
22985 "an owner frame must execute only its own exact dependency on its lent lane"
22986 );
22987 child_started.send(owner_index).unwrap();
22988 child_release.wait();
22989 owner_index
22990 });
22991 dependency_queued_tx.send(()).unwrap();
22992 begin_lending.wait();
22993
22994 let mut result = None;
22995 {
22996 let mut future = std::pin::pin!(async {
22997 result = Some(child.await.unwrap());
22998 });
22999 assert_eq!(
23000 owner_executor.block_on(future.as_mut()),
23001 BlockOnOutcome::Completed
23002 );
23003 }
23004 result.unwrap()
23005 }));
23006 }
23007 drop(dependency_queued_tx);
23008 drop(dependency_started_tx);
23009 owners_saturated.wait();
23010
23011 for _ in 0..2 {
23012 dependency_queued_rx
23013 .recv_timeout(Duration::from_secs(2))
23014 .expect("both owner-scoped dependencies must be queued while the cap is saturated");
23015 }
23016 let (unrelated_tx, unrelated_rx) = mpsc::channel();
23017 let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
23018 begin_lending.wait();
23019
23020 let mut started = Vec::new();
23021 for _ in 0..2 {
23022 started.push(
23023 dependency_started_rx
23024 .recv_timeout(Duration::from_secs(2))
23025 .expect("each owner must service its own exact dependency"),
23026 );
23027 }
23028 started.sort_unstable();
23029 assert_eq!(started, vec![0, 1]);
23030 assert!(
23031 unrelated_rx.try_recv().is_err(),
23032 "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
23033 );
23034
23035 release_dependencies.wait();
23036 for (owner_index, owner) in owners.into_iter().enumerate() {
23037 assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
23038 }
23039 unrelated_rx
23040 .recv_timeout(Duration::from_secs(2))
23041 .expect("the unrelated job must run after an owner releases a counted lane");
23042 futures::executor::block_on(unrelated).unwrap();
23043 }
23044
23045 #[cfg(napi_runtime_os_threads)]
23046 #[test]
23047 fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
23048 use std::sync::{Mutex, mpsc};
23049
23050 const DEPENDENCY_COUNT: usize = 4;
23051
23052 let options = RuntimeOptions {
23053 flavor: RuntimeFlavor::MultiThread,
23054 worker_threads: 3,
23055 max_blocking_tasks: 1,
23056 thread_name_prefix: "parked-unrelated-worker".to_string(),
23057 park_deadline: None,
23058 drain_linger: DEFAULT_DRAIN_LINGER,
23059 };
23060 let executor =
23061 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23062 let (parked_tx, parked_rx) = mpsc::channel();
23063 let (release_parked_tx, release_parked_rx) = mpsc::channel();
23064 executor.pool.spawn(move || {
23065 parked_tx.send(()).unwrap();
23066 release_parked_rx.recv().unwrap();
23067 });
23068 parked_rx
23069 .recv_timeout(Duration::from_secs(2))
23070 .expect("the unrelated Rayon worker must remain occupied");
23071
23072 let owner_executor = Arc::clone(&executor);
23073 let owner = executor.schedule_blocking(move || {
23074 let owner = BLOCKING_OWNER
23075 .with(std::cell::Cell::get)
23076 .expect("the blocking closure must carry its exact owner frame");
23077 let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
23078 let results = Arc::new(Mutex::new(Vec::new()));
23079 let descendant_results = Arc::clone(&results);
23080 let descendant_executor = Arc::clone(&owner_executor);
23081 rayon::scope(move |scope| {
23082 scope.spawn(move |_| {
23083 assert_eq!(
23084 BLOCKING_OWNER.with(std::cell::Cell::get),
23085 None,
23086 "the parked owner must force its descendant onto the remaining worker"
23087 );
23088 let _owner = BlockingOwnerGuard::enter(Some(owner));
23089 descendant_started_tx.send(()).unwrap();
23090 for value in 0..DEPENDENCY_COUNT {
23091 let handle = descendant_executor.schedule_blocking(move || value);
23092 let mut result = None;
23093 {
23094 let mut future = std::pin::pin!(async {
23095 result = Some(handle.await.unwrap());
23096 });
23097 assert_eq!(
23098 descendant_executor.block_on(future.as_mut()),
23099 BlockOnOutcome::Completed
23100 );
23101 }
23102 descendant_results.lock().unwrap().push(result.unwrap());
23103 }
23104 });
23105 descendant_started_rx
23106 .recv_timeout(Duration::from_secs(2))
23107 .expect("the nested descendant must start on the only unoccupied worker");
23108 });
23109 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
23110 });
23111
23112 assert_eq!(
23113 futures::executor::block_on(owner).unwrap(),
23114 (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
23115 );
23116
23117 let (idle_tx, idle_rx) = mpsc::channel();
23118 let idle_executor = Arc::clone(&executor);
23119 let idle_waiter = std::thread::spawn(move || {
23120 idle_executor.begin_shutdown();
23121 idle_executor.wait_until_scheduler_idle();
23122 idle_tx.send(()).unwrap();
23123 });
23124 idle_rx
23125 .recv_timeout(Duration::from_secs(2))
23126 .expect("an unrelated parked worker must not retain owner-probe idle accounting");
23127
23128 release_parked_tx.send(()).unwrap();
23129 idle_waiter.join().unwrap();
23130 }
23131
23132 #[cfg(napi_runtime_os_threads)]
23133 #[test]
23134 fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
23135 use std::sync::{Condvar, Mutex, mpsc};
23136
23137 let options = RuntimeOptions {
23138 flavor: RuntimeFlavor::MultiThread,
23139 worker_threads: 3,
23140 max_blocking_tasks: 1,
23141 thread_name_prefix: "same-owner-rearm".to_string(),
23142 park_deadline: None,
23143 drain_linger: DEFAULT_DRAIN_LINGER,
23144 };
23145 let executor =
23146 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23147 let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
23148 let (job_started_tx, job_started_rx) = mpsc::channel();
23149 let owner_executor = Arc::clone(&executor);
23150 let owner_releases = Arc::clone(&releases);
23151 let owner = executor.schedule_blocking(move || {
23152 let owner = BLOCKING_OWNER
23153 .with(std::cell::Cell::get)
23154 .expect("the blocking closure must carry its exact owner frame");
23155 let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
23156 let results = Arc::new(Mutex::new(Vec::new()));
23157 let scope_results = Arc::clone(&results);
23158 rayon::scope(move |scope| {
23159 for index in 0..2 {
23160 let descendant_ready_tx = descendant_ready_tx.clone();
23161 let descendant_executor = Arc::clone(&owner_executor);
23162 let descendant_results = Arc::clone(&scope_results);
23163 let job_started_tx = job_started_tx.clone();
23164 let releases = Arc::clone(&owner_releases);
23165 scope.spawn(move |_| {
23166 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
23167 let _owner = BlockingOwnerGuard::enter(Some(owner));
23168 descendant_ready_tx.send(()).unwrap();
23169 let handle = descendant_executor.schedule_blocking(move || {
23170 job_started_tx.send(index).unwrap();
23171 let (released, changed) = &*releases;
23172 let mut state = released.lock().unwrap();
23173 while !state[index] {
23174 state = changed.wait(state).unwrap();
23175 }
23176 index
23177 });
23178 let mut result = None;
23179 {
23180 let mut future = std::pin::pin!(async {
23181 result = Some(handle.await.unwrap());
23182 });
23183 assert_eq!(
23184 descendant_executor.block_on(future.as_mut()),
23185 BlockOnOutcome::Completed
23186 );
23187 }
23188 descendant_results.lock().unwrap().push(result.unwrap());
23189 });
23190 }
23191 drop(descendant_ready_tx);
23192 for _ in 0..2 {
23193 descendant_ready_rx
23194 .recv_timeout(Duration::from_secs(2))
23195 .expect("both descendants must be stolen before the owner leaves its Rayon scope");
23196 }
23197 });
23198 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
23199 });
23200
23201 let first = job_started_rx
23202 .recv_timeout(Duration::from_secs(2))
23203 .expect("one exact dependency must borrow the owner lane");
23204 assert!(
23205 job_started_rx
23206 .recv_timeout(Duration::from_millis(100))
23207 .is_err(),
23208 "one owner frame must not service two dependencies concurrently"
23209 );
23210 let unrelated_parker = Arc::new(DriverParker::default());
23211 executor
23212 .parked_drivers
23213 .register_with_role(&unrelated_parker, true, None);
23214 {
23215 let (released, changed) = &*releases;
23216 released.lock().unwrap()[first] = true;
23217 changed.notify_all();
23218 }
23219
23220 let second = job_started_rx
23221 .recv_timeout(Duration::from_secs(2))
23222 .expect("completion must rearm one parked same-owner dependency");
23223 assert_ne!(second, first);
23224 assert!(
23225 !unrelated_parker.consume_permit(),
23226 "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
23227 );
23228 executor.parked_drivers.deregister(&unrelated_parker);
23229 {
23230 let (released, changed) = &*releases;
23231 released.lock().unwrap()[second] = true;
23232 changed.notify_all();
23233 }
23234
23235 let mut results = futures::executor::block_on(owner).unwrap();
23236 results.sort_unstable();
23237 assert_eq!(results, vec![0, 1]);
23238 }
23239
23240 #[cfg(napi_runtime_os_threads)]
23241 #[test]
23242 fn failed_exact_claim_rearms_parked_same_owner_dependency() {
23243 let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
23244 let owner = executor.fresh_owner_token();
23245 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23246 let failed_dependency = Arc::new(TaskDependency::default());
23247 failed_dependency.set_owned(BlockingDependency {
23248 job: BlockingJobId(u64::MAX - 21),
23249 owner: Some(owner),
23250 });
23251 let parked_dependency = Arc::new(TaskDependency::default());
23252 parked_dependency.set_owned(BlockingDependency {
23253 job: BlockingJobId(u64::MAX - 22),
23254 owner: Some(owner),
23255 });
23256 let parker = Arc::new(DriverParker::default());
23257
23258 let queue = executor.blocking_queue.lock().unwrap();
23259 let claimant_executor = Arc::clone(&executor);
23260 let claimant_dependency = Arc::clone(&failed_dependency);
23261 let claimant = std::thread::spawn(move || {
23262 let _owner = BlockingOwnerGuard::enter(Some(owner));
23263 claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
23264 });
23265 wait_until("the failed claimant to reserve the owner lane", || {
23266 !executor.active_blocking_owners.is_available(owner)
23267 });
23268 executor
23269 .parked_drivers
23270 .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
23271 failed_dependency.clear();
23272 drop(queue);
23273
23274 assert!(
23275 !claimant.join().unwrap(),
23276 "the cancelled dependency must not execute"
23277 );
23278 assert!(
23279 parker.consume_permit(),
23280 "releasing a failed exact claim must hand the owner lane to another parked dependency"
23281 );
23282 }
23283
23284 #[cfg(napi_runtime_os_threads)]
23285 #[test]
23286 fn exact_owner_lending_does_not_start_blocking_after_stop() {
23287 use std::sync::mpsc;
23288
23289 let options = RuntimeOptions {
23290 flavor: RuntimeFlavor::MultiThread,
23291 worker_threads: 2,
23292 max_blocking_tasks: 1,
23293 thread_name_prefix: "stopped-exact-owner".to_string(),
23294 park_deadline: None,
23295 drain_linger: DEFAULT_DRAIN_LINGER,
23296 };
23297 let executor =
23298 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23299 let ran = Arc::new(AtomicBool::new(false));
23300 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
23301 let (release_claim_tx, release_claim_rx) = mpsc::channel();
23302 let owner_executor = Arc::clone(&executor);
23303 let child_ran = Arc::clone(&ran);
23304 let owner = executor.schedule_blocking(move || {
23305 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
23306 *slot.borrow_mut() = Some(Box::new(move || {
23307 claim_entered_tx.send(()).unwrap();
23308 release_claim_rx.recv().unwrap();
23309 }));
23310 });
23311 let child = owner_executor.schedule_blocking(move || {
23312 child_ran.store(true, Ordering::SeqCst);
23313 });
23314 let mut future = std::pin::pin!(async move {
23315 let _ = child.await;
23316 });
23317 owner_executor.block_on(future.as_mut())
23318 });
23319
23320 claim_entered_rx
23321 .recv_timeout(Duration::from_secs(2))
23322 .expect("the cooperative driver must pause before the exact-owner queue claim");
23323 assert_eq!(
23324 executor.blocking_queue.lock().unwrap().jobs.len(),
23325 1,
23326 "the exact dependency must remain queued before stop publication"
23327 );
23328
23329 executor.stop.begin_shutdown();
23332 release_claim_tx.send(()).unwrap();
23333
23334 assert_eq!(
23335 futures::executor::block_on(owner).unwrap(),
23336 BlockOnOutcome::Stopped,
23337 "the cooperative driver must stop instead of executing its queued dependency"
23338 );
23339 assert!(
23340 !ran.load(Ordering::SeqCst),
23341 "exact-owner lending started blocking work after stop"
23342 );
23343 assert_eq!(
23344 executor.blocking_queue.lock().unwrap().jobs.len(),
23345 1,
23346 "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
23347 );
23348
23349 executor.begin_shutdown();
23350 executor.wait_until_scheduler_idle();
23351 assert!(executor.blocking_queue.lock().unwrap().is_empty());
23352 assert!(!ran.load(Ordering::SeqCst));
23353 }
23354
23355 #[cfg(napi_runtime_os_threads)]
23356 #[test]
23357 fn exact_owner_lending_attempts_scale_linearly_with_topology() {
23358 const DEPENDENCY_COUNT: usize = 1024;
23359
23360 let options = RuntimeOptions {
23361 flavor: RuntimeFlavor::MultiThread,
23362 worker_threads: 2,
23363 max_blocking_tasks: 1,
23364 thread_name_prefix: "linear-owner-lending".to_string(),
23365 park_deadline: None,
23366 drain_linger: DEFAULT_DRAIN_LINGER,
23367 };
23368 let executor =
23369 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23370 let completed = Arc::new(AtomicUsize::new(0));
23371
23372 for index in 0..DEPENDENCY_COUNT {
23373 let owner = executor.fresh_owner_token();
23374 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23375 let dependency = TaskDependency::new(u64::MAX, Some(owner));
23376 let blocking = BlockingDependency {
23377 job: BlockingJobId(u64::MAX - index as u64),
23378 owner: Some(owner),
23379 };
23380 dependency.set_owned(blocking);
23381 let completed = Arc::clone(&completed);
23382 executor
23383 .blocking_queue
23384 .lock()
23385 .unwrap()
23386 .push(QueuedBlocking {
23387 id: blocking.job,
23388 run: Box::new(move || {
23389 completed.fetch_add(1, Ordering::SeqCst);
23390 }),
23391 });
23392
23393 let _owner = BlockingOwnerGuard::enter(Some(owner));
23394 assert!(executor.try_owned_blocking_over_cap(&dependency));
23395 }
23396
23397 assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
23398 assert_eq!(
23399 executor.owner_lending_attempts.load(Ordering::Relaxed),
23400 DEPENDENCY_COUNT,
23401 "targeted lending must perform one O(1) claim attempt per exact dependency"
23402 );
23403 let queue = executor.blocking_queue.lock().unwrap();
23404 assert!(queue.is_empty());
23405 assert_eq!(queue.head, None);
23406 assert_eq!(queue.tail, None);
23407 }
23408
23409 #[cfg(napi_runtime_os_threads)]
23410 #[test]
23411 fn one_dependency_interspersed_owner_publications_stays_linear() {
23412 const PUBLICATION_COUNT: usize = 1024;
23413
23414 let executor = multi_thread_executor(2, None, "linear-publication-lending");
23415 let owner = executor.fresh_owner_token();
23416 let wrong_owner = executor.fresh_owner_token();
23417 let dependency = TaskDependency::new(u64::MAX, Some(owner));
23418 let completed = Arc::new(AtomicUsize::new(0));
23419 for index in 0..PUBLICATION_COUNT {
23420 let cancelled_source = TaskDependency::default();
23421 cancelled_source.set_owned(BlockingDependency {
23422 job: BlockingJobId(u64::MAX - 100_000 - index as u64),
23423 owner: Some(owner),
23424 });
23425 dependency.set_borrowed(
23426 &cancelled_source
23427 .get()
23428 .expect("the source publication must initially be live"),
23429 );
23430 cancelled_source.clear();
23431
23432 dependency.set_owned(BlockingDependency {
23433 job: BlockingJobId(u64::MAX - 200_000 - index as u64),
23434 owner: Some(wrong_owner),
23435 });
23436 let blocking = BlockingDependency {
23437 job: BlockingJobId(u64::MAX - 1_000 - index as u64),
23438 owner: Some(owner),
23439 };
23440 dependency.set_owned(blocking);
23441 let completed = Arc::clone(&completed);
23442 executor
23443 .blocking_queue
23444 .lock()
23445 .unwrap()
23446 .push(QueuedBlocking {
23447 id: blocking.job,
23448 run: Box::new(move || {
23449 completed.fetch_add(1, Ordering::SeqCst);
23450 }),
23451 });
23452 }
23453 dependency.reset_entry_visits();
23454
23455 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23456 let _owner = BlockingOwnerGuard::enter(Some(owner));
23457 for _ in 0..PUBLICATION_COUNT {
23458 assert!(
23459 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23460 "the owner-handoff predicate must find the next exact publication"
23461 );
23462 assert!(
23463 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23464 "the park-availability predicate must find the next serviceable publication"
23465 );
23466 assert!(executor.try_owned_blocking_over_cap(&dependency));
23467 }
23468
23469 assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
23470 assert!(
23471 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23472 "wrong-owner publications must not satisfy an exact handoff"
23473 );
23474 assert!(
23475 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23476 "wrong-owner publications must not make the owner's lane appear serviceable"
23477 );
23478 assert!(
23479 dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
23480 "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
23481 dependency.entry_visits()
23482 );
23483 assert!(executor.blocking_queue.lock().unwrap().is_empty());
23484 }
23485
23486 #[cfg(napi_runtime_os_threads)]
23487 #[test]
23488 fn binding_unowned_dependency_updates_exact_owner_predicate() {
23489 let owner = BlockingOwnerToken {
23490 executor_id: u64::MAX - 200,
23491 frame: u64::MAX - 201,
23492 };
23493 let dependency = TaskDependency::default();
23494 let blocking = BlockingDependency {
23495 job: BlockingJobId(u64::MAX - 202),
23496 owner: None,
23497 };
23498 dependency.set_owned(blocking);
23499
23500 assert!(
23501 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23502 "an unowned publication must not satisfy an exact owner handoff"
23503 );
23504 assert!(
23505 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23506 "an unowned publication is available to the ambient owner"
23507 );
23508
23509 let selected = dependency
23510 .select_for_owner(owner)
23511 .expect("the owner must bind the unowned publication");
23512 assert_eq!(selected.publication.dependency.owner, Some(owner));
23513 assert!(
23514 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23515 "binding must immediately publish the entry to exact-owner predicates"
23516 );
23517 assert!(dependency.try_claim(&selected));
23518 assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23519 }
23520
23521 #[cfg(napi_runtime_os_threads)]
23522 #[test]
23523 fn owner_selection_preserves_unowned_and_exact_publication_order() {
23524 let owner = BlockingOwnerToken {
23525 executor_id: u64::MAX - 210,
23526 frame: u64::MAX - 211,
23527 };
23528 let dependency = TaskDependency::default();
23529 let first = BlockingDependency {
23530 job: BlockingJobId(u64::MAX - 212),
23531 owner: None,
23532 };
23533 let second = BlockingDependency {
23534 job: BlockingJobId(u64::MAX - 213),
23535 owner: Some(owner),
23536 };
23537 dependency.set_owned(first);
23538 dependency.set_owned(second);
23539
23540 let selected = dependency
23541 .select_for_owner(owner)
23542 .expect("the first publication must be found");
23543 assert_eq!(
23544 selected.publication.dependency,
23545 BlockingDependency {
23546 owner: Some(owner),
23547 ..first
23548 }
23549 );
23550 assert!(dependency.try_claim(&selected));
23551
23552 let selected = dependency
23553 .select_for_owner(owner)
23554 .expect("the second publication must remain eligible");
23555 assert_eq!(selected.publication.dependency, second);
23556 }
23557
23558 #[cfg(napi_runtime_os_threads)]
23559 #[test]
23560 fn blocking_queue_exact_removal_reclaims_fifo_links() {
23561 const JOB_COUNT: u64 = 4096;
23562
23563 let mut queue = BlockingQueue {
23564 closed: false,
23565 head: None,
23566 tail: None,
23567 jobs: FxHashMap::default(),
23568 };
23569 for id in 1..=JOB_COUNT {
23570 queue.push(QueuedBlocking {
23571 id: BlockingJobId(id),
23572 run: Box::new(|| {}),
23573 });
23574 }
23575 for id in (2..=JOB_COUNT).step_by(2) {
23576 drop(
23577 queue
23578 .remove(BlockingJobId(id))
23579 .expect("even job must remain indexed"),
23580 );
23581 }
23582 for id in (1..=JOB_COUNT).step_by(2) {
23583 drop(
23584 queue
23585 .remove(BlockingJobId(id))
23586 .expect("odd job must remain indexed"),
23587 );
23588 }
23589 assert!(queue.jobs.is_empty());
23590 assert_eq!(queue.head, None);
23591 assert_eq!(queue.tail, None);
23592
23593 let order = Arc::new(Mutex::new(Vec::new()));
23594 for id in 1..=3 {
23595 let order = Arc::clone(&order);
23596 queue.push(QueuedBlocking {
23597 id: BlockingJobId(id),
23598 run: Box::new(move || order.lock().unwrap().push(id)),
23599 });
23600 }
23601 drop(
23602 queue
23603 .remove(BlockingJobId(2))
23604 .expect("middle job must be removable"),
23605 );
23606 queue.pop_front().expect("FIFO head must remain linked")();
23607 queue.pop_front().expect("FIFO tail must remain linked")();
23608 assert!(queue.pop_front().is_none());
23609 assert_eq!(*order.lock().unwrap(), vec![1, 3]);
23610 assert_eq!(queue.head, None);
23611 assert_eq!(queue.tail, None);
23612 }
23613
23614 #[cfg(not(target_family = "wasm"))]
23615 #[test]
23616 fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
23617 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
23618
23619 if std::env::var_os(CHILD_ENV).is_some() {
23620 configure(RuntimeOptions {
23621 flavor: RuntimeFlavor::MultiThread,
23622 worker_threads: 2,
23623 max_blocking_tasks: 1,
23624 thread_name_prefix: "dependency-lending-metrics".to_string(),
23625 park_deadline: None,
23626 drain_linger: DEFAULT_DRAIN_LINGER,
23627 })
23628 .unwrap();
23629
23630 let outer = spawn_blocking(|| {
23631 let inner = spawn_blocking(|| 42usize);
23632 block_on(inner).unwrap()
23633 });
23634 assert_eq!(block_on(outer).unwrap(), 42);
23635
23636 shutdown().unwrap();
23641 let snapshot = metrics();
23642 assert_eq!(snapshot.blocking_tasks_started, 2);
23643 assert_eq!(snapshot.blocking_tasks_completed, 2);
23644 assert_eq!(snapshot.active_blocking_tasks, 0);
23645 assert_eq!(
23646 snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
23647 "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
23648 );
23649 return;
23650 }
23651
23652 let output = std::process::Command::new(std::env::current_exe().unwrap())
23653 .arg("--exact")
23654 .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
23655 .arg("--nocapture")
23656 .env(CHILD_ENV, "1")
23657 .output()
23658 .expect("the dependency-lending metrics subprocess must start");
23659 assert!(
23660 output.status.success(),
23661 "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
23662 output.status.code(),
23663 String::from_utf8_lossy(&output.stdout),
23664 String::from_utf8_lossy(&output.stderr)
23665 );
23666 }
23667
23668 #[cfg(napi_runtime_os_threads)]
23669 #[test]
23670 fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
23671 use std::sync::mpsc;
23672
23673 let (done_tx, done_rx) = mpsc::channel();
23674 let runner = std::thread::spawn(move || {
23675 let options = RuntimeOptions {
23676 flavor: RuntimeFlavor::MultiThread,
23677 worker_threads: 2,
23678 max_blocking_tasks: 1,
23679 thread_name_prefix: "exact-blocking-job".to_string(),
23680 park_deadline: None,
23681 drain_linger: DEFAULT_DRAIN_LINGER,
23682 };
23683 let executor =
23684 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23685 let outer_exec = Arc::clone(&executor);
23686 let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
23687 let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
23688 let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
23689 let outer = executor.schedule_blocking(move || {
23690 let sibling = outer_exec.schedule_blocking(move || {
23691 sibling_started_tx.send(()).unwrap();
23692 release_sibling_rx.recv().unwrap();
23693 });
23694 let awaited = outer_exec.schedule_blocking(move || {
23695 awaited_ran_tx.send(()).unwrap();
23696 17usize
23697 });
23698 let mut value = None;
23699 {
23700 let mut future = std::pin::pin!(async {
23701 value = Some(awaited.await.unwrap());
23702 });
23703 outer_exec.block_on(future.as_mut());
23704 }
23705 (sibling, value.unwrap())
23706 });
23707
23708 awaited_ran_rx
23709 .recv_timeout(Duration::from_secs(2))
23710 .expect("the exact awaited job must bypass the earlier detached sibling");
23711 let (sibling, value) = futures::executor::block_on(outer).unwrap();
23712 assert_eq!(value, 17);
23713 sibling_started_rx
23714 .recv_timeout(Duration::from_secs(2))
23715 .expect("the sibling must run after the owner releases its counted slot");
23716 release_sibling_tx.send(()).unwrap();
23717 futures::executor::block_on(sibling).unwrap();
23718 done_tx.send(()).unwrap();
23719 });
23720 done_rx
23721 .recv_timeout(Duration::from_secs(5))
23722 .expect("same-owner sibling selection deadlocked the exact awaited job");
23723 runner.join().unwrap();
23724 }
23725
23726 #[cfg(napi_runtime_os_threads)]
23727 #[test]
23728 fn blocking_dependency_propagates_through_async_join_lineage() {
23729 use std::sync::mpsc;
23730
23731 let (done_tx, done_rx) = mpsc::channel();
23732 let runner = std::thread::spawn(move || {
23733 let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
23734 let owner_controller = Arc::clone(&controller);
23735 let (child_tx, child_rx) = mpsc::channel();
23736 let outer = controller
23737 .try_spawn_blocking(move || {
23738 let async_controller = Arc::clone(&owner_controller);
23739 let async_child = owner_controller
23740 .try_spawn(async move {
23741 async_controller
23742 .try_spawn_blocking(move || {
23743 child_tx.send(()).unwrap();
23744 23usize
23745 })
23746 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
23747 .await
23748 .unwrap()
23749 })
23750 .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
23751 let mut value = None;
23752 {
23753 let mut future = std::pin::pin!(async {
23754 value = Some(async_child.await.unwrap());
23755 });
23756 owner_controller.block_on(future.as_mut());
23757 }
23758 value.unwrap()
23759 })
23760 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23761
23762 child_rx
23763 .recv_timeout(Duration::from_secs(2))
23764 .expect("the async descendant's exact blocking dependency must inherit the owner lane");
23765 assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
23766 controller.shutdown().unwrap();
23767 done_tx.send(()).unwrap();
23768 });
23769 done_rx
23770 .recv_timeout(Duration::from_secs(5))
23771 .expect("blocking dependency lineage deadlocked");
23772 runner.join().unwrap();
23773 }
23774
23775 #[cfg(napi_runtime_os_threads)]
23776 #[test]
23777 fn blocking_dependency_does_not_escape_a_serviced_runnable() {
23778 use std::sync::mpsc;
23779
23780 let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
23781 let outer_controller = Arc::clone(&controller);
23782 let (child_queued_tx, child_queued_rx) = mpsc::channel();
23783 let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
23784 let (release_outer_tx, release_outer_rx) = oneshot::channel();
23785 let outer = controller
23786 .try_spawn_blocking(move || {
23787 let child_controller = Arc::clone(&outer_controller);
23788 let child = outer_controller
23789 .try_spawn(async move {
23790 let blocking = child_controller
23791 .try_spawn_blocking(move || {
23792 unrelated_ran_tx.send(()).unwrap();
23793 31usize
23794 })
23795 .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
23796 child_queued_tx.send(()).unwrap();
23797 blocking.await.unwrap()
23798 })
23799 .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
23800 let mut released = false;
23801 {
23802 let mut future = std::pin::pin!(async {
23803 release_outer_rx.await.unwrap();
23804 released = true;
23805 });
23806 outer_controller.block_on(future.as_mut());
23807 }
23808 assert!(released);
23809 child
23810 })
23811 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23812
23813 child_queued_rx
23814 .recv_timeout(Duration::from_secs(2))
23815 .expect("the cooperative driver must service the unrelated async runnable");
23816 assert!(
23817 unrelated_ran_rx
23818 .recv_timeout(Duration::from_millis(200))
23819 .is_err(),
23820 "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
23821 );
23822
23823 release_outer_tx.send(()).unwrap();
23824 let child = futures::executor::block_on(outer).unwrap();
23825 unrelated_ran_rx
23826 .recv_timeout(Duration::from_secs(2))
23827 .expect("the unrelated blocking job must run after the owner releases its counted slot");
23828 assert_eq!(futures::executor::block_on(child).unwrap(), 31);
23829 controller.shutdown().unwrap();
23830 }
23831
23832 #[cfg(napi_runtime_os_threads)]
23833 #[test]
23834 fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
23835 let parent = Arc::new(TaskDependency::default());
23836 let _context = TaskDependencyGuard::enter(&parent);
23837 let dependency = BlockingDependency {
23838 job: BlockingJobId(u64::MAX),
23839 owner: None,
23840 };
23841 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23842 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23843 receiver,
23844 dependency,
23845 published: PublishedBlockingDependencies::default(),
23846 });
23847 let waker = futures::task::noop_waker();
23848 let mut cx = Context::from_waker(&waker);
23849
23850 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23851 assert_eq!(parent.get_dependency(), Some(dependency));
23852 drop(handle);
23853 assert_eq!(
23854 parent.get_dependency(),
23855 None,
23856 "an abandoned JoinHandle must clear stale dependency lineage"
23857 );
23858 drop(sender);
23859 }
23860
23861 #[cfg(napi_runtime_os_threads)]
23862 #[test]
23863 fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
23864 let owner = BlockingOwnerToken {
23865 executor_id: u64::MAX - 40,
23866 frame: u64::MAX - 41,
23867 };
23868 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23869 let _context = TaskDependencyGuard::enter(&parent);
23870 let dependency = BlockingDependency {
23871 job: BlockingJobId(u64::MAX - 42),
23872 owner: None,
23873 };
23874 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23875 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23876 receiver,
23877 dependency,
23878 published: PublishedBlockingDependencies::default(),
23879 });
23880 let waker = futures::task::noop_waker();
23881 let mut cx = Context::from_waker(&waker);
23882
23883 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23884 assert_eq!(
23885 parent.get_dependency(),
23886 Some(BlockingDependency {
23887 owner: Some(owner),
23888 ..dependency
23889 }),
23890 "the ambient task owner must enrich an untagged direct blocking dependency"
23891 );
23892 drop(handle);
23893 assert_eq!(
23894 parent.get_dependency(),
23895 None,
23896 "detach cleanup must match the stable job id after owner enrichment"
23897 );
23898 drop(sender);
23899 }
23900
23901 #[cfg(napi_runtime_os_threads)]
23902 #[test]
23903 fn one_poll_preserves_every_observed_blocking_dependency() {
23904 let parent = Arc::new(TaskDependency::default());
23905 let _context = TaskDependencyGuard::enter(&parent);
23906 let first = BlockingDependency {
23907 job: BlockingJobId(u64::MAX - 60),
23908 owner: None,
23909 };
23910 let second = BlockingDependency {
23911 job: BlockingJobId(u64::MAX - 61),
23912 owner: None,
23913 };
23914 let (first_sender, first_receiver) =
23915 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23916 let (second_sender, second_receiver) =
23917 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23918 let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
23919 receiver: first_receiver,
23920 dependency: first,
23921 published: PublishedBlockingDependencies::default(),
23922 });
23923 let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
23924 receiver: second_receiver,
23925 dependency: second,
23926 published: PublishedBlockingDependencies::default(),
23927 });
23928 let waker = futures::task::noop_waker();
23929 let mut cx = Context::from_waker(&waker);
23930
23931 assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
23932 assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
23933 assert_eq!(
23934 parent.get_dependencies(),
23935 vec![first, second],
23936 "publishing a later dependency in one user poll must not replace an earlier dependency"
23937 );
23938
23939 drop(first_handle);
23940 drop(second_handle);
23941 drop(first_sender);
23942 drop(second_sender);
23943 }
23944
23945 #[cfg(napi_runtime_os_threads)]
23946 #[test]
23947 fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
23948 let owner = BlockingOwnerToken {
23949 executor_id: u64::MAX - 900,
23950 frame: u64::MAX - 901,
23951 };
23952 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23953 let _context = TaskDependencyGuard::enter(&parent);
23954 let waker = futures::task::noop_waker();
23955 let mut cx = Context::from_waker(&waker);
23956
23957 let j1 = BlockingDependency {
23958 job: BlockingJobId(u64::MAX - 910),
23959 owner: Some(owner),
23960 };
23961 let j2 = BlockingDependency {
23962 job: BlockingJobId(u64::MAX - 911),
23963 owner: Some(owner),
23964 };
23965
23966 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23967 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23968
23969 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23970 receiver: r1,
23971 dependency: j1,
23972 published: PublishedBlockingDependencies::default(),
23973 });
23974 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23977 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23978 let entries_after_double_poll = parent
23979 .state
23980 .lock()
23981 .unwrap_or_else(std::sync::PoisonError::into_inner)
23982 .current
23983 .len();
23984 drop(h1);
23985
23986 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23987 receiver: r2,
23988 dependency: j2,
23989 published: PublishedBlockingDependencies::default(),
23990 });
23991 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23992
23993 assert_eq!(
23994 entries_after_double_poll, 1,
23995 "a repeat Pending poll must supersede the handle's own previous publication"
23996 );
23997 let selected = parent
23998 .select_for_owner(owner)
23999 .expect("a live dependency must be selectable");
24000 assert_eq!(
24001 selected.publication.dependency.job, j2.job,
24002 "a DETACHED job must not be selected for owner lending ahead of the live one"
24003 );
24004 drop((s1, s2));
24005 }
24006
24007 #[cfg(napi_runtime_os_threads)]
24008 #[test]
24009 fn handle_polled_under_a_nested_frame_retires_both_publications() {
24010 let owner = BlockingOwnerToken {
24011 executor_id: u64::MAX - 920,
24012 frame: u64::MAX - 921,
24013 };
24014 let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24015 let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24016 let waker = futures::task::noop_waker();
24017 let mut cx = Context::from_waker(&waker);
24018
24019 let j1 = BlockingDependency {
24020 job: BlockingJobId(u64::MAX - 930),
24021 owner: Some(owner),
24022 };
24023 let j2 = BlockingDependency {
24024 job: BlockingJobId(u64::MAX - 931),
24025 owner: Some(owner),
24026 };
24027 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24028 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24029 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
24030 receiver: r1,
24031 dependency: j1,
24032 published: Default::default(),
24033 });
24034
24035 {
24036 let _outer_ctx = TaskDependencyGuard::enter(&outer);
24037 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24041 {
24042 let _inner_ctx = TaskDependencyGuard::enter(&inner);
24043 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24044 }
24045 drop(h1);
24046 assert!(
24047 outer.get_dependencies().is_empty(),
24048 "detaching must retire the publication made in the outer frame too"
24049 );
24050 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
24051 receiver: r2,
24052 dependency: j2,
24053 published: Default::default(),
24054 });
24055 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
24056 let selected = outer.select_for_owner(owner).expect("selectable");
24057 assert_eq!(
24058 selected.publication.dependency.job, j2.job,
24059 "a detached job published in an outer frame must not outrank a live one"
24060 );
24061 drop(h2);
24062 }
24063 drop((s1, s2));
24064 }
24065
24066 #[cfg(napi_runtime_os_threads)]
24067 #[test]
24068 fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
24069 const FRAME_COUNT: usize = 64;
24070 let waker = futures::task::noop_waker();
24071 let mut cx = Context::from_waker(&waker);
24072 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24073 let mut handle = JoinHandle(JoinHandleInner::Blocking {
24074 receiver,
24075 dependency: BlockingDependency {
24076 job: BlockingJobId(u64::MAX - 940),
24077 owner: None,
24078 },
24079 published: PublishedBlockingDependencies::default(),
24080 });
24081 let mut frames = Vec::new();
24082 for _ in 0..FRAME_COUNT {
24083 let frame = Arc::new(TaskDependency::default());
24084 frames.push(Arc::downgrade(&frame));
24085 let _context = TaskDependencyGuard::enter(&frame);
24086 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24087 }
24088 let retained = frames
24093 .iter()
24094 .filter(|frame| frame.strong_count() > 0)
24095 .count();
24096 assert_eq!(
24097 retained, 0,
24098 "a completed frame must not be kept alive by a pending handle's publication history"
24099 );
24100 drop(handle);
24101 drop(sender);
24102 }
24103
24104 #[cfg(napi_runtime_os_threads)]
24105 #[test]
24106 fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
24107 let owner = BlockingOwnerToken {
24112 executor_id: u64::MAX - 950,
24113 frame: u64::MAX - 951,
24114 };
24115 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24116 let _context = TaskDependencyGuard::enter(&parent);
24117 let waker = futures::task::noop_waker();
24118 let mut cx = Context::from_waker(&waker);
24119
24120 let j1 = BlockingDependency {
24121 job: BlockingJobId(u64::MAX - 960),
24122 owner: Some(owner),
24123 };
24124 let j2 = BlockingDependency {
24125 job: BlockingJobId(u64::MAX - 961),
24126 owner: Some(owner),
24127 };
24128 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24129 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24130 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
24131 receiver: r1,
24132 dependency: j1,
24133 published: PublishedBlockingDependencies::default(),
24134 });
24135 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
24136 receiver: r2,
24137 dependency: j2,
24138 published: PublishedBlockingDependencies::default(),
24139 });
24140
24141 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24142 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
24143 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24144
24145 let selected = parent
24146 .select_for_owner(owner)
24147 .expect("a live dependency must be selectable");
24148 assert_eq!(
24149 selected.publication.dependency.job, j1.job,
24150 "re-polling a handle must not push its dependency behind one published later"
24151 );
24152 drop((h1, h2, s1, s2));
24153 }
24154
24155 #[cfg(napi_runtime_os_threads)]
24156 #[test]
24157 fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
24158 const FRAME_COUNT: usize = 256;
24165 let waker = futures::task::noop_waker();
24166 let mut cx = Context::from_waker(&waker);
24167 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24168 let mut handle = JoinHandle(JoinHandleInner::Blocking {
24169 receiver,
24170 dependency: BlockingDependency {
24171 job: BlockingJobId(u64::MAX - 970),
24172 owner: None,
24173 },
24174 published: PublishedBlockingDependencies::default(),
24175 });
24176 let mut retained = Vec::new();
24177 for _ in 0..FRAME_COUNT {
24178 let frame = Arc::new(TaskDependency::default());
24179 retained.push(Arc::clone(&frame));
24180 let _context = TaskDependencyGuard::enter(&frame);
24181 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24182 frame.begin_poll();
24184 }
24185 let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
24186 panic!("the handle must still be a blocking handle");
24187 };
24188 let remembered = usize::from(published.first.is_some()) + published.rest.len();
24189 assert!(
24190 remembered <= 2,
24191 "a drained frame must not stay in the publication history, remembered={remembered}"
24192 );
24193 drop(handle);
24194 drop(sender);
24195 }
24196
24197 #[cfg(napi_runtime_os_threads)]
24198 #[test]
24199 fn mass_detach_of_pending_blocking_handles_stays_linear() {
24200 const HANDLE_COUNT: usize = 512;
24211
24212 let parent = Arc::new(TaskDependency::default());
24213 let _context = TaskDependencyGuard::enter(&parent);
24214 let waker = futures::task::noop_waker();
24215 let mut cx = Context::from_waker(&waker);
24216
24217 let mut senders = Vec::with_capacity(HANDLE_COUNT);
24218 let mut handles = Vec::with_capacity(HANDLE_COUNT);
24219 for index in 0..HANDLE_COUNT {
24220 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24221 senders.push(sender);
24222 handles.push(JoinHandle(JoinHandleInner::Blocking {
24223 receiver,
24224 dependency: BlockingDependency {
24225 job: BlockingJobId(u64::MAX - 300_000 - index as u64),
24226 owner: None,
24227 },
24228 published: PublishedBlockingDependencies::default(),
24229 }));
24230 }
24231 for handle in &mut handles {
24232 assert!(Pin::new(handle).poll(&mut cx).is_pending());
24233 }
24234 assert_eq!(
24235 parent
24236 .state
24237 .lock()
24238 .unwrap_or_else(std::sync::PoisonError::into_inner)
24239 .current
24240 .len(),
24241 HANDLE_COUNT,
24242 "every handle must be registered and still pending before the detach"
24243 );
24244
24245 parent.reset_entry_visits();
24246 drop(handles);
24247
24248 assert!(
24249 parent.entry_visits() <= HANDLE_COUNT * 4,
24250 "mass-detach must visit O(n) entries across n handles, visits={}",
24251 parent.entry_visits()
24252 );
24253 assert_eq!(
24254 parent.get_dependency(),
24255 None,
24256 "every detached handle must have retired its own publication"
24257 );
24258 drop(senders);
24259 }
24260
24261 #[cfg(napi_runtime_os_threads)]
24262 #[test]
24263 fn dependency_tls_borrow_is_released_before_reentrant_wake() {
24264 use std::sync::mpsc;
24265
24266 struct ReenterDependencyContext {
24267 dependency: Arc<TaskDependency>,
24268 woke: mpsc::Sender<()>,
24269 }
24270
24271 impl std::task::Wake for ReenterDependencyContext {
24272 fn wake(self: Arc<Self>) {
24273 let _context = TaskDependencyGuard::enter(&self.dependency);
24274 self.woke.send(()).unwrap();
24275 }
24276 }
24277
24278 let parent = Arc::new(TaskDependency::default());
24279 let nested = Arc::new(TaskDependency::default());
24280 let (woke_tx, woke_rx) = mpsc::channel();
24281 let waiter = Waker::from(Arc::new(ReenterDependencyContext {
24282 dependency: Arc::clone(&nested),
24283 woke: woke_tx,
24284 }));
24285 parent.register_waiter(&waiter);
24286 let blocking = BlockingDependency {
24287 job: BlockingJobId(u64::MAX - 62),
24288 owner: None,
24289 };
24290
24291 let _context = TaskDependencyGuard::enter(&parent);
24292 let mut published = PublishedBlockingDependencies::default();
24293 record_blocking_dependency(&mut published, blocking);
24294 woke_rx
24295 .recv_timeout(Duration::from_secs(1))
24296 .expect("dependency notification must permit reentrant TLS context access");
24297
24298 parent.register_waiter(&waiter);
24299 clear_blocking_dependency(published);
24300 woke_rx
24301 .recv_timeout(Duration::from_secs(1))
24302 .expect("dependency clearing must permit reentrant TLS context access");
24303 }
24304
24305 #[cfg(napi_runtime_os_threads)]
24306 #[test]
24307 fn dependency_free_begin_poll_does_not_lock_state() {
24308 use std::sync::mpsc;
24309
24310 let dependency = Arc::new(TaskDependency::default());
24311 let state = dependency.state.lock().unwrap();
24312 let (completed_tx, completed_rx) = mpsc::channel();
24313 let polling_dependency = Arc::clone(&dependency);
24314 let polling = std::thread::spawn(move || {
24315 polling_dependency.begin_poll();
24316 completed_tx.send(()).unwrap();
24317 });
24318
24319 let completed_without_lock = completed_rx
24320 .recv_timeout(Duration::from_millis(200))
24321 .is_ok();
24322 drop(state);
24323 polling.join().unwrap();
24324 assert!(
24325 completed_without_lock,
24326 "a task with no published dependency must skip the dependency-state mutex"
24327 );
24328 }
24329
24330 #[cfg(not(target_family = "wasm"))]
24331 #[test]
24332 fn deep_dependency_liveness_chain_is_iterative() {
24333 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
24334
24335 if std::env::var_os(CHILD_ENV).is_some() {
24336 const DEPTH: usize = 100_000;
24337
24338 let mut links = Vec::with_capacity(DEPTH + 1);
24339 links.push(DependencyLink::root());
24340 for _ in 0..DEPTH {
24341 links.push(DependencyLink::derived(links.last().unwrap()));
24342 }
24343 assert!(links.last().unwrap().is_live());
24344 links[0].cancel();
24345 assert!(!links.last().unwrap().is_live());
24346 return;
24347 }
24348
24349 let output = std::process::Command::new(std::env::current_exe().unwrap())
24350 .arg("--exact")
24351 .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
24352 .arg("--nocapture")
24353 .env(CHILD_ENV, "1")
24354 .output()
24355 .expect("the subprocess test must start");
24356 assert!(
24357 output.status.success(),
24358 "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
24359 output.status.code(),
24360 String::from_utf8_lossy(&output.stdout),
24361 String::from_utf8_lossy(&output.stderr)
24362 );
24363 }
24364
24365 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24366 #[test]
24367 fn task_dependency_set_contains_panicking_waiter() {
24368 struct PanicWake;
24369 impl std::task::Wake for PanicWake {
24370 fn wake(self: Arc<Self>) {
24371 panic!("intentional TaskDependency::set waiter panic");
24372 }
24373 }
24374
24375 let dependency = TaskDependency::default();
24376 let blocking = BlockingDependency {
24377 job: BlockingJobId(u64::MAX),
24378 owner: None,
24379 };
24380 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24381
24382 let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
24383 assert!(
24384 result.is_ok(),
24385 "set notification must not unwind its caller"
24386 );
24387 assert_eq!(
24388 dependency.get_dependency(),
24389 Some(blocking),
24390 "set must commit before notifying its waiter"
24391 );
24392 }
24393
24394 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24395 #[test]
24396 fn task_dependency_clear_contains_panicking_waiter() {
24397 struct PanicWake;
24398 impl std::task::Wake for PanicWake {
24399 fn wake(self: Arc<Self>) {
24400 panic!("intentional TaskDependency::clear waiter panic");
24401 }
24402 }
24403
24404 let dependency = TaskDependency::default();
24405 dependency.set_owned(BlockingDependency {
24406 job: BlockingJobId(u64::MAX),
24407 owner: None,
24408 });
24409 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24410
24411 let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
24412 assert!(
24413 result.is_ok(),
24414 "clear notification must not unwind its caller"
24415 );
24416 assert_eq!(
24417 dependency.get_dependency(),
24418 None,
24419 "clear must commit before notifying its waiter"
24420 );
24421 }
24422
24423 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24424 #[test]
24425 fn task_dependency_clear_if_contains_panicking_waiter() {
24426 struct PanicWake;
24427 impl std::task::Wake for PanicWake {
24428 fn wake(self: Arc<Self>) {
24429 panic!("intentional TaskDependency::clear_if waiter panic");
24430 }
24431 }
24432
24433 let dependency = TaskDependency::default();
24434 let blocking = BlockingDependency {
24435 job: BlockingJobId(u64::MAX),
24436 owner: None,
24437 };
24438 let sequence = dependency.set_owned(blocking);
24439 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24440
24441 let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
24442 assert!(
24443 result.is_ok(),
24444 "retire notification must not unwind its caller"
24445 );
24446 assert_eq!(
24447 dependency.get_dependency(),
24448 None,
24449 "retire must commit before notifying its waiter"
24450 );
24451 }
24452
24453 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24454 #[test]
24455 fn task_dependency_waiter_replacement_contains_panicking_drop() {
24456 use std::sync::mpsc;
24457
24458 const GENERATION: u64 = u64::MAX - 20;
24459
24460 struct PanicOnDropWake {
24461 dropped: mpsc::Sender<Option<u64>>,
24462 }
24463
24464 #[expect(
24465 clippy::manual_noop_waker,
24466 reason = "the test observes the waker payload's Drop"
24467 )]
24468 impl std::task::Wake for PanicOnDropWake {
24469 fn wake(self: Arc<Self>) {}
24470 }
24471
24472 impl Drop for PanicOnDropWake {
24473 fn drop(&mut self) {
24474 self
24475 .dropped
24476 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24477 .unwrap();
24478 panic!("intentional replaced dependency waiter destructor panic");
24479 }
24480 }
24481
24482 let dependency = TaskDependency::new(GENERATION, None);
24483 let (dropped_tx, dropped_rx) = mpsc::channel();
24484 let waiter = Waker::from(Arc::new(PanicOnDropWake {
24485 dropped: dropped_tx,
24486 }));
24487 dependency.register_waiter(&waiter);
24488 drop(waiter);
24489
24490 let replacement = futures::task::noop_waker();
24491 let result = catch_unwind(AssertUnwindSafe(|| {
24492 dependency.register_waiter(&replacement)
24493 }));
24494 assert!(
24495 result.is_ok(),
24496 "replacing a hostile waiter must not unwind registration"
24497 );
24498 assert_eq!(
24499 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24500 Some(GENERATION),
24501 "the replaced waiter must be destroyed under its runtime generation"
24502 );
24503 }
24504
24505 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24506 #[test]
24507 fn task_dependency_retirement_contains_stored_waiter_drop() {
24508 use std::sync::mpsc;
24509
24510 const GENERATION: u64 = u64::MAX - 21;
24511
24512 struct PanicOnDropWake {
24513 dropped: mpsc::Sender<Option<u64>>,
24514 }
24515
24516 #[expect(
24517 clippy::manual_noop_waker,
24518 reason = "the test observes the waker payload's Drop"
24519 )]
24520 impl std::task::Wake for PanicOnDropWake {
24521 fn wake(self: Arc<Self>) {}
24522 }
24523
24524 impl Drop for PanicOnDropWake {
24525 fn drop(&mut self) {
24526 self
24527 .dropped
24528 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24529 .unwrap();
24530 panic!("intentional stored dependency waiter destructor panic");
24531 }
24532 }
24533
24534 let dependency = TaskDependency::new(GENERATION, None);
24535 let (dropped_tx, dropped_rx) = mpsc::channel();
24536 let waiter = Waker::from(Arc::new(PanicOnDropWake {
24537 dropped: dropped_tx,
24538 }));
24539 dependency.register_waiter(&waiter);
24540 drop(waiter);
24541
24542 let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
24543 assert!(
24544 result.is_ok(),
24545 "retiring a hostile stored waiter must not unwind"
24546 );
24547 assert_eq!(
24548 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24549 Some(GENERATION),
24550 "the retained waiter must be destroyed under its runtime generation"
24551 );
24552 }
24553
24554 #[cfg(napi_runtime_os_threads)]
24555 #[test]
24556 fn detaching_task_clears_dependency_waiter_parent_retention() {
24557 struct RetainingWake {
24558 retained_parent: Arc<()>,
24559 }
24560
24561 impl std::task::Wake for RetainingWake {
24562 fn wake(self: Arc<Self>) {
24563 let _ = &self.retained_parent;
24564 }
24565 }
24566
24567 let dependency = Arc::new(TaskDependency::default());
24568 let retained_parent = Arc::new(());
24569 let retained_parent_weak = Arc::downgrade(&retained_parent);
24570 let waiter = Waker::from(Arc::new(RetainingWake {
24571 retained_parent: Arc::clone(&retained_parent),
24572 }));
24573 dependency.register_waiter(&waiter);
24574 drop(waiter);
24575 drop(retained_parent);
24576 assert!(
24577 retained_parent_weak.upgrade().is_some(),
24578 "the stored dependency waiter must retain its parent before detachment"
24579 );
24580
24581 let (runnable, task) = async_task::spawn(
24582 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24583 |_| {},
24584 );
24585 drop(JoinHandle(JoinHandleInner::Task {
24586 task: task.fallible(),
24587 awaiter: None,
24588 dependency: Arc::clone(&dependency),
24589 }));
24590 assert!(
24591 retained_parent_weak.upgrade().is_none(),
24592 "detachment must clear the dependency waiter instead of retaining its parent task"
24593 );
24594 drop(runnable);
24595 }
24596
24597 #[cfg(napi_runtime_os_threads)]
24598 #[test]
24599 fn detaching_task_clears_many_parent_publications_linearly() {
24600 const PUBLICATION_COUNT: usize = 1024;
24601
24602 struct CountWake(Arc<AtomicUsize>);
24603
24604 impl std::task::Wake for CountWake {
24605 fn wake(self: Arc<Self>) {
24606 self.0.fetch_add(1, Ordering::SeqCst);
24607 }
24608 }
24609
24610 fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
24611 dependency
24612 .state
24613 .lock()
24614 .unwrap_or_else(std::sync::PoisonError::into_inner)
24615 .current
24616 .back()
24617 .expect("the sentinel publication must be appended")
24618 .publication
24619 .clone()
24620 }
24621
24622 let owner = BlockingOwnerToken {
24623 executor_id: u64::MAX - 300,
24624 frame: u64::MAX - 301,
24625 };
24626 let other_owner = BlockingOwnerToken {
24627 executor_id: u64::MAX - 302,
24628 frame: u64::MAX - 303,
24629 };
24630 let child = Arc::new(TaskDependency::default());
24631 for index in 0..PUBLICATION_COUNT {
24632 child.set_owned(BlockingDependency {
24633 job: BlockingJobId(1_000_000 + index as u64),
24634 owner: (index % 2 == 0).then_some(owner),
24635 });
24636 }
24637 let child_publications = child.snapshot_all_live();
24638 let source = child_publications[0].clone();
24639
24640 let parent = Arc::new(TaskDependency::default());
24641 let _context = TaskDependencyGuard::enter(&parent);
24642 let (runnable, task) = async_task::spawn(
24643 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24644 |_| {},
24645 );
24646 let mut handle = JoinHandle(JoinHandleInner::Task {
24647 task: task.fallible(),
24648 awaiter: None,
24649 dependency: Arc::clone(&child),
24650 });
24651 let waker = futures::task::noop_waker();
24652 let mut cx = Context::from_waker(&waker);
24653 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24654 assert_eq!(
24655 parent.get_dependencies().len(),
24656 PUBLICATION_COUNT,
24657 "polling the child handle must propagate every current publication"
24658 );
24659
24660 let mut wrong_claim_source = source.clone();
24661 wrong_claim_source.claim = Arc::new(DependencyClaim::new());
24662 parent.set_borrowed(&wrong_claim_source);
24663 let wrong_claim_publication = latest_publication(&parent);
24664
24665 let mut wrong_job_source = source.clone();
24666 wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
24667 parent.set_borrowed(&wrong_job_source);
24668 let wrong_job_publication = latest_publication(&parent);
24669
24670 let wrong_immediate_source = source.derived(None);
24671 parent.set_borrowed(&wrong_immediate_source);
24672 let wrong_immediate_publication = latest_publication(&parent);
24673
24674 let wakes = Arc::new(AtomicUsize::new(0));
24675 parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
24676 parent.reset_entry_visits();
24677 drop(handle);
24678
24679 assert_eq!(
24680 wakes.load(Ordering::SeqCst),
24681 1,
24682 "one batched parent cleanup must emit one waiter notification"
24683 );
24684 assert!(
24685 parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
24686 "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
24687 parent.entry_visits()
24688 );
24689 assert!(
24690 wrong_claim_publication.is_live(),
24691 "claim identity must remain part of the match"
24692 );
24693 assert!(
24694 wrong_job_publication.is_live(),
24695 "job identity must remain part of the match"
24696 );
24697 assert!(
24698 wrong_immediate_publication.is_live(),
24699 "only an exact immediate-parent link may be retired"
24700 );
24701
24702 {
24703 let state = parent
24704 .state
24705 .lock()
24706 .unwrap_or_else(std::sync::PoisonError::into_inner);
24707 assert_eq!(state.current.len(), 3);
24708 assert!(
24715 state
24716 .current
24717 .iter()
24718 .all(|entry| entry.publication.is_live()),
24719 "only the three non-matching sentinels may remain, all still live"
24720 );
24721 assert!(state.unowned_sequences.is_empty());
24722 assert_eq!(state.owner_sequences.len(), 1);
24723 assert_eq!(
24724 state.owner_sequences.get(&owner).map(VecDeque::len),
24725 Some(3)
24726 );
24727 }
24728 assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
24729 assert!(
24730 !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
24731 "cancelled unowned publications must not survive in availability predicates"
24732 );
24733 drop(runnable);
24734 }
24735
24736 #[cfg(not(target_family = "wasm"))]
24737 #[test]
24738 fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
24739 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
24740
24741 if std::env::var_os(CHILD_ENV).is_some() {
24742 struct PanicWake;
24743 impl std::task::Wake for PanicWake {
24744 fn wake(self: Arc<Self>) {
24745 panic!("intentional dependency waker panic");
24746 }
24747 }
24748
24749 struct PanicOnDrop;
24750 impl Drop for PanicOnDrop {
24751 fn drop(&mut self) {
24752 panic!("intentional buffered blocking result destructor panic");
24753 }
24754 }
24755
24756 let parent = Arc::new(TaskDependency::default());
24757 let dependency = BlockingDependency {
24758 job: BlockingJobId(u64::MAX - 1),
24759 owner: None,
24760 };
24761 let sequence = parent.set_owned(dependency);
24765 parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
24766 let _context = TaskDependencyGuard::enter(&parent);
24767 let (sender, receiver) = oneshot::channel();
24768 let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
24769 ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
24770 ));
24771 drop(JoinHandle(JoinHandleInner::Blocking {
24772 receiver,
24773 dependency,
24774 published: PublishedBlockingDependencies {
24775 first: Some(PublishedBlockingDependency {
24776 state: Arc::downgrade(&parent),
24777 sequence,
24778 }),
24779 rest: Vec::new(),
24780 },
24781 }));
24782 assert_eq!(parent.get_dependency(), None);
24783 return;
24784 }
24785
24786 let output = std::process::Command::new(std::env::current_exe().unwrap())
24787 .arg("--exact")
24788 .arg(
24789 "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
24790 )
24791 .arg("--nocapture")
24792 .env(CHILD_ENV, "1")
24793 .output()
24794 .expect("the subprocess test must start");
24795 assert!(
24796 output.status.success(),
24797 "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
24798 output.status.code(),
24799 String::from_utf8_lossy(&output.stdout),
24800 String::from_utf8_lossy(&output.stderr)
24801 );
24802 }
24803
24804 #[cfg(napi_runtime_os_threads)]
24805 #[test]
24806 fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
24807 use std::sync::{Barrier, mpsc};
24816 use std::time::Duration;
24817
24818 let (done_tx, done_rx) = mpsc::channel();
24819 let peak_out = Arc::new(Mutex::new(None));
24820 let peak_slot = Arc::clone(&peak_out);
24821 let runner = std::thread::spawn(move || {
24822 let metrics = Arc::new(RuntimeMetrics::default());
24823 let options = RuntimeOptions {
24824 flavor: RuntimeFlavor::MultiThread,
24825 worker_threads: 4,
24826 max_blocking_tasks: 2,
24827 thread_name_prefix: "rd1-cap".to_string(),
24828 park_deadline: None,
24829 drain_linger: DEFAULT_DRAIN_LINGER,
24830 };
24831 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24832
24833 let holder_count = 2usize; let driver_count = 2usize;
24835
24836 let (held_tx, held_rx) = mpsc::channel();
24838 let release = Arc::new(Barrier::new(holder_count + 1));
24839
24840 let mut holder_handles = Vec::new();
24841 for _ in 0..holder_count {
24842 let held_tx = held_tx.clone();
24843 let release = Arc::clone(&release);
24844 let handle = executor.schedule_blocking(move || {
24845 held_tx.send(()).unwrap();
24846 release.wait();
24847 0usize
24848 });
24849 holder_handles.push(handle);
24850 }
24851 for _ in 0..holder_count {
24853 held_rx.recv().unwrap();
24854 }
24855
24856 let mut driver_tasks = Vec::new();
24859 for _ in 0..driver_count {
24860 let exec = Arc::clone(&executor);
24861 let body = async move {
24862 let inner_exec = Arc::clone(&exec);
24863 let mut inner = std::pin::pin!(async move {
24864 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24865 assert_eq!(v, 5);
24866 });
24867 exec.block_on(inner.as_mut());
24868 };
24869 let scheduler = Arc::clone(&executor);
24870 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
24871 executor.schedule(runnable);
24872 driver_tasks.push(task);
24873 }
24874
24875 std::thread::sleep(Duration::from_millis(300));
24878
24879 release.wait();
24881
24882 for task in driver_tasks {
24883 futures::executor::block_on(task);
24884 }
24885 for handle in holder_handles {
24886 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24887 }
24888
24889 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24890 let _ = done_tx.send(());
24891 });
24892
24893 match done_rx.recv_timeout(Duration::from_secs(10)) {
24894 Ok(()) => runner.join().unwrap(),
24895 Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
24896 }
24897 let peak = peak_out.lock().unwrap().expect("peak captured");
24898 assert!(
24899 peak <= 2,
24900 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24901 );
24902 }
24903
24904 #[cfg(napi_runtime_os_threads)]
24905 #[test]
24906 fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
24907 use std::sync::{Barrier, mpsc};
24923 use std::time::Duration;
24924
24925 let (done_tx, done_rx) = mpsc::channel();
24926 let peak_out = Arc::new(Mutex::new(None));
24927 let peak_slot = Arc::clone(&peak_out);
24928 let runner = std::thread::spawn(move || {
24929 let metrics = Arc::new(RuntimeMetrics::default());
24930 let options = RuntimeOptions {
24931 flavor: RuntimeFlavor::MultiThread,
24932 worker_threads: 2,
24933 max_blocking_tasks: 2,
24934 thread_name_prefix: "rd1-owner-runnable".to_string(),
24935 park_deadline: None,
24936 drain_linger: DEFAULT_DRAIN_LINGER,
24937 };
24938 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24939
24940 let saturated = Arc::new(Barrier::new(2));
24941 let release = Arc::new(Barrier::new(2));
24942
24943 let h2_saturated = Arc::clone(&saturated);
24945 let h2_release = Arc::clone(&release);
24946 let h2 = executor.schedule_blocking(move || {
24947 h2_saturated.wait();
24948 h2_release.wait();
24949 0usize
24950 });
24951
24952 let o_exec = Arc::clone(&executor);
24955 let o_saturated = Arc::clone(&saturated);
24956 let o = executor.schedule_blocking(move || {
24957 o_saturated.wait(); let r_exec = Arc::clone(&o_exec);
24960 let body = async move {
24961 let inner_exec = Arc::clone(&r_exec);
24962 let mut inner = std::pin::pin!(async move {
24963 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24964 assert_eq!(v, 5);
24965 });
24966 r_exec.block_on(inner.as_mut());
24967 };
24968 let sched = Arc::clone(&o_exec);
24969 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
24970 o_exec.schedule(runnable);
24971
24972 let mut wait_r = std::pin::pin!(async move {
24973 task.await;
24974 });
24975 o_exec.block_on(wait_r.as_mut());
24976 1usize
24977 });
24978
24979 std::thread::sleep(Duration::from_millis(300));
24982 release.wait();
24984
24985 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24986 assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
24987
24988 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24989 let _ = done_tx.send(());
24990 });
24991
24992 match done_rx.recv_timeout(Duration::from_secs(10)) {
24993 Ok(()) => runner.join().unwrap(),
24994 Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
24995 }
24996 let peak = peak_out.lock().unwrap().expect("peak captured");
24997 assert!(
24998 peak <= 2,
24999 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
25000 );
25001 }
25002
25003 #[cfg(napi_runtime_os_threads)]
25004 #[test]
25005 fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
25006 use std::sync::{Barrier, mpsc};
25015
25016 let (done_tx, done_rx) = mpsc::channel();
25017 let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
25018 let order_read = Arc::clone(&order_out);
25019 let runner = std::thread::spawn(move || {
25020 let metrics = Arc::new(RuntimeMetrics::default());
25021 let options = RuntimeOptions {
25022 flavor: RuntimeFlavor::MultiThread,
25023 worker_threads: 2,
25024 max_blocking_tasks: 2,
25025 thread_name_prefix: "rd1-unrelated".to_string(),
25026 park_deadline: None,
25027 drain_linger: DEFAULT_DRAIN_LINGER,
25028 };
25029 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
25030
25031 let (held_tx, held_rx) = mpsc::channel();
25034 let u_ready = Arc::new(Barrier::new(2)); let h_release = Arc::new(Barrier::new(2)); let h_held = held_tx.clone();
25039 let h_rel = Arc::clone(&h_release);
25040 let h = executor.schedule_blocking(move || {
25041 h_held.send(()).unwrap();
25042 h_rel.wait();
25043 0usize
25044 });
25045
25046 let o_exec = Arc::clone(&executor);
25049 let o_ready = Arc::clone(&u_ready);
25050 let o_order = Arc::clone(&order_out);
25051 let o = executor.schedule_blocking(move || {
25052 held_tx.send(()).unwrap();
25053 o_ready.wait(); let inner_exec = Arc::clone(&o_exec);
25055 let d_order = Arc::clone(&o_order);
25056 let mut inner = std::pin::pin!(async move {
25057 let v = inner_exec
25058 .schedule_blocking(move || {
25059 d_order.lock().unwrap().push("D");
25060 5usize
25061 })
25062 .await
25063 .unwrap();
25064 assert_eq!(v, 5);
25065 });
25066 o_exec.block_on(inner.as_mut());
25067 1usize
25068 });
25069
25070 held_rx.recv().unwrap();
25072 held_rx.recv().unwrap();
25073
25074 let u_order = Arc::clone(&order_out);
25076 let u = executor.schedule_blocking(move || {
25077 u_order.lock().unwrap().push("U");
25078 2usize
25079 });
25080
25081 u_ready.wait();
25083
25084 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
25086 assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
25087
25088 h_release.wait();
25090 assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
25091
25092 let _ = done_tx.send(());
25093 });
25094
25095 match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
25096 Ok(()) => runner.join().unwrap(),
25097 Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
25098 }
25099 let order = order_read.lock().unwrap().clone();
25100 assert_eq!(
25101 order,
25102 vec!["D", "U"],
25103 "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
25104 );
25105 }
25106
25107 #[cfg(napi_runtime_os_threads)]
25108 #[test]
25109 fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
25110 use std::sync::{Barrier, mpsc};
25111
25112 let opts = RuntimeOptions {
25113 flavor: RuntimeFlavor::MultiThread,
25114 worker_threads: 2,
25115 max_blocking_tasks: 2,
25116 thread_name_prefix: "rd1-scope".to_string(),
25117 park_deadline: None,
25118 drain_linger: DEFAULT_DRAIN_LINGER,
25119 };
25120 let exec1 =
25121 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25122 let exec2 =
25123 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25124 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25125
25126 let holder_count = opts.max_blocking_tasks;
25127 let (held_tx, held_rx) = mpsc::channel();
25128 let release = Arc::new(Barrier::new(holder_count + 1));
25129 let mut holder_handles = Vec::new();
25130 for _ in 0..holder_count {
25131 let held_tx = held_tx.clone();
25132 let release = Arc::clone(&release);
25133 holder_handles.push(exec2.schedule_blocking(move || {
25134 held_tx.send(()).unwrap();
25135 release.wait();
25136 0usize
25137 }));
25138 }
25139 for _ in 0..holder_count {
25140 held_rx.recv().unwrap();
25141 }
25142 assert_eq!(
25143 exec2.active_blocking.load(Ordering::Acquire),
25144 opts.max_blocking_tasks
25145 );
25146
25147 let exact = BlockingJobId(u64::MAX);
25148 let ran = Arc::new(AtomicBool::new(false));
25149 let ran_job = Arc::clone(&ran);
25150 exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
25151 id: exact,
25152 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25153 });
25154 let dependency = TaskDependency::default();
25155
25156 let token1 = exec1.fresh_owner_token();
25157 {
25158 let _owner = BlockingOwnerGuard::enter(Some(token1));
25159 dependency.set_owned(BlockingDependency {
25160 job: exact,
25161 owner: Some(token1),
25162 });
25163 assert!(
25164 !exec2.try_owned_blocking_over_cap(&dependency),
25165 "a foreign-executor ambient token must fail the executor gate"
25166 );
25167 }
25168
25169 let token2 = exec2.fresh_owner_token();
25170 {
25171 let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
25172 let _owner = BlockingOwnerGuard::enter(Some(token2));
25173 dependency.set_owned(BlockingDependency {
25174 job: BlockingJobId(u64::MAX - 1),
25175 owner: Some(token2),
25176 });
25177 assert!(
25178 !exec2.try_owned_blocking_over_cap(&dependency),
25179 "a foreign or unrelated dependency id must fail the exact-job gate"
25180 );
25181 dependency.set_owned(BlockingDependency {
25182 job: BlockingJobId(exact.0 - 2),
25183 owner: Some(token2),
25184 });
25185 assert!(
25186 !exec2.try_owned_blocking_over_cap(&dependency),
25187 "a different job id must not consume the queued dependency"
25188 );
25189 dependency.set_owned(BlockingDependency {
25190 job: exact,
25191 owner: Some(token2),
25192 });
25193 assert!(
25194 exec2.try_owned_blocking_over_cap(&dependency),
25195 "the exact awaited job must run over cap"
25196 );
25197 }
25198 assert!(
25199 ran.load(Ordering::SeqCst),
25200 "the exact dependency must have run"
25201 );
25202
25203 release.wait();
25204 for handle in holder_handles {
25205 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
25206 }
25207 }
25208
25209 #[cfg(napi_runtime_os_threads)]
25210 #[test]
25211 fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
25212 let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
25213 let first_owner = executor.fresh_owner_token();
25214 let second_owner = executor.fresh_owner_token();
25215 let _first_owner =
25216 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
25217 let _second_owner =
25218 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
25219 let exact = BlockingJobId(u64::MAX - 30);
25220 let dependency = TaskDependency::default();
25221 dependency.set_owned(BlockingDependency {
25222 job: exact,
25223 owner: None,
25224 });
25225 let ran = Arc::new(AtomicBool::new(false));
25226 let ran_job = Arc::clone(&ran);
25227 executor
25228 .blocking_queue
25229 .lock()
25230 .unwrap()
25231 .push(QueuedBlocking {
25232 id: exact,
25233 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25234 });
25235
25236 assert!(
25237 !executor.try_owned_blocking_over_cap(&dependency),
25238 "an untagged driver must not choose between multiple active owner frames"
25239 );
25240 assert!(!ran.load(Ordering::SeqCst));
25241 let queued = executor.blocking_queue.lock().unwrap().remove(exact);
25242 assert!(
25243 queued.is_some(),
25244 "ambiguous ownership must leave the exact job queued"
25245 );
25246 }
25247
25248 #[cfg(napi_runtime_os_threads)]
25249 #[test]
25250 fn owner_lending_claims_live_dependency_not_stale_snapshot() {
25251 let executor = multi_thread_executor(2, None, "live-parked-dependency");
25252 let owner = executor.fresh_owner_token();
25253 let dependency = TaskDependency::new(u64::MAX, Some(owner));
25254 let stale = BlockingJobId(u64::MAX - 10);
25255 let live = BlockingJobId(u64::MAX - 9);
25256 dependency.set_owned(BlockingDependency {
25257 job: stale,
25258 owner: Some(owner),
25259 });
25260 dependency.begin_poll();
25261 dependency.set_owned(BlockingDependency {
25262 job: live,
25263 owner: Some(owner),
25264 });
25265
25266 let stale_ran = Arc::new(AtomicBool::new(false));
25267 let stale_job_ran = Arc::clone(&stale_ran);
25268 let live_ran = Arc::new(AtomicBool::new(false));
25269 let live_job_ran = Arc::clone(&live_ran);
25270 {
25271 let mut queue = executor
25272 .blocking_queue
25273 .lock()
25274 .unwrap_or_else(std::sync::PoisonError::into_inner);
25275 queue.push(QueuedBlocking {
25276 id: stale,
25277 run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
25278 });
25279 queue.push(QueuedBlocking {
25280 id: live,
25281 run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
25282 });
25283 }
25284
25285 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25286 let _owner = BlockingOwnerGuard::enter(Some(owner));
25287 assert!(
25288 executor.try_owned_blocking_over_cap(&dependency),
25289 "the owner lane must claim the currently published dependency"
25290 );
25291 assert!(
25292 live_ran.load(Ordering::SeqCst),
25293 "the live dependency must run"
25294 );
25295 assert!(
25296 !stale_ran.load(Ordering::SeqCst),
25297 "the stale registered id must remain queued"
25298 );
25299 let remaining = executor
25300 .blocking_queue
25301 .lock()
25302 .unwrap_or_else(std::sync::PoisonError::into_inner)
25303 .jobs
25304 .keys()
25305 .copied()
25306 .collect::<Vec<_>>();
25307 assert_eq!(remaining, vec![stale]);
25308 }
25309
25310 #[cfg(napi_runtime_os_threads)]
25311 #[test]
25312 fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
25313 let executor = multi_thread_executor(2, None, "cancelled-child-publication");
25314 let owner = executor.fresh_owner_token();
25315 let child = TaskDependency::new_task(u64::MAX);
25316 let parent = TaskDependency::new(u64::MAX, Some(owner));
25317 let exact = BlockingDependency {
25318 job: BlockingJobId(u64::MAX - 31),
25319 owner: Some(owner),
25320 };
25321
25322 child.set_owned(exact);
25323 let stale = child
25324 .get()
25325 .expect("the child must publish its direct dependency");
25326 child.begin_poll();
25327 parent.set_borrowed(&stale);
25328
25329 let ran = Arc::new(AtomicBool::new(false));
25330 let ran_job = Arc::clone(&ran);
25331 executor
25332 .blocking_queue
25333 .lock()
25334 .unwrap()
25335 .push(QueuedBlocking {
25336 id: exact.job,
25337 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25338 });
25339 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25340 let _owner = BlockingOwnerGuard::enter(Some(owner));
25341 assert!(
25342 !executor.try_owned_blocking_over_cap(&parent),
25343 "a publication copied before child cancellation must remain cancelled after parent install"
25344 );
25345 assert!(!ran.load(Ordering::SeqCst));
25346
25347 child.set_owned(exact);
25348 parent.set_borrowed(
25349 &child
25350 .get()
25351 .expect("the replacement publication must be live"),
25352 );
25353 assert!(
25354 executor.try_owned_blocking_over_cap(&parent),
25355 "a fresh child publication must still authorize its exact queued job"
25356 );
25357 assert!(ran.load(Ordering::SeqCst));
25358 }
25359
25360 #[cfg(napi_runtime_os_threads)]
25361 #[test]
25362 fn intermediate_repoll_does_not_cancel_child_source_claim() {
25363 let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
25364 let owner = executor.fresh_owner_token();
25365 let child = TaskDependency::new_task(u64::MAX);
25366 let intermediate = TaskDependency::new_task(u64::MAX);
25367 let parent = TaskDependency::new(u64::MAX, Some(owner));
25368 let exact = BlockingDependency {
25369 job: BlockingJobId(u64::MAX - 43),
25370 owner: Some(owner),
25371 };
25372
25373 child.set_owned(exact);
25374 intermediate.set_borrowed(&child.get().expect("the child source must be live"));
25375 parent.set_borrowed(
25376 &intermediate
25377 .get()
25378 .expect("the intermediate publication must be live"),
25379 );
25380
25381 intermediate.begin_poll();
25382 assert!(
25383 child.get().is_some(),
25384 "withdrawing an intermediate hop must not cancel the child's source claim"
25385 );
25386 assert!(
25387 parent.get().is_none(),
25388 "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
25389 );
25390
25391 intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
25392 parent.set_borrowed(
25393 &intermediate
25394 .get()
25395 .expect("the intermediate must republish the still-live source"),
25396 );
25397 let ran = Arc::new(AtomicBool::new(false));
25398 let ran_job = Arc::clone(&ran);
25399 executor
25400 .blocking_queue
25401 .lock()
25402 .unwrap()
25403 .push(QueuedBlocking {
25404 id: exact.job,
25405 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25406 });
25407
25408 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25409 let _owner = BlockingOwnerGuard::enter(Some(owner));
25410 assert!(
25411 executor.try_owned_blocking_over_cap(&parent),
25412 "a fresh chain through the repolled intermediate must retain the exact source claim"
25413 );
25414 assert!(ran.load(Ordering::SeqCst));
25415 }
25416
25417 #[cfg(napi_runtime_os_threads)]
25418 #[test]
25419 fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
25420 use std::sync::mpsc;
25421
25422 let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
25423 let owner = executor.fresh_owner_token();
25424 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25425 let child = Arc::new(TaskDependency::new_task(u64::MAX));
25426 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25427 let exact = BlockingDependency {
25428 job: BlockingJobId(u64::MAX - 44),
25429 owner: Some(owner),
25430 };
25431 child.set_owned(exact);
25432 parent.set_borrowed(&child.get().expect("the child source must be live"));
25433
25434 let ran = Arc::new(AtomicBool::new(false));
25435 let ran_job = Arc::clone(&ran);
25436 executor
25437 .blocking_queue
25438 .lock()
25439 .unwrap()
25440 .push(QueuedBlocking {
25441 id: exact.job,
25442 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25443 });
25444
25445 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
25446 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25447 let claimant_executor = Arc::clone(&executor);
25448 let claimant_parent = Arc::clone(&parent);
25449 let claimant = std::thread::spawn(move || {
25450 DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
25451 *slot.borrow_mut() = Some(Box::new(move || {
25452 claim_entered_tx.send(()).unwrap();
25453 release_claim_rx.recv().unwrap();
25454 }));
25455 });
25456 let _owner = BlockingOwnerGuard::enter(Some(owner));
25457 claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
25458 });
25459 claim_entered_rx
25460 .recv_timeout(Duration::from_secs(2))
25461 .expect("the exact claimant must hold the shared transition lock");
25462
25463 let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
25464 let withdrawing_child = Arc::clone(&child);
25465 let withdrawal = std::thread::spawn(move || {
25466 withdrawing_child.begin_poll();
25467 withdrawn_tx.send(()).unwrap();
25468 });
25469 let withdrew_before_claim = withdrawn_rx
25470 .recv_timeout(Duration::from_millis(200))
25471 .is_ok();
25472 release_claim_tx.send(()).unwrap();
25473
25474 assert!(
25475 claimant.join().unwrap(),
25476 "the claim that linearized first must execute the exact job"
25477 );
25478 withdrawal.join().unwrap();
25479 assert!(
25480 !withdrew_before_claim,
25481 "source withdrawal must not interleave after claim validation but before claim commit"
25482 );
25483 assert!(ran.load(Ordering::SeqCst));
25484 }
25485
25486 #[cfg(napi_runtime_os_threads)]
25487 #[test]
25488 fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
25489 struct PendThenReady {
25510 polls: usize,
25511 }
25512 impl Future for PendThenReady {
25513 type Output = ();
25514 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25515 self.polls += 1;
25516 if self.polls >= 2 {
25517 Poll::Ready(())
25518 } else {
25519 cx.waker().wake_by_ref();
25523 Poll::Pending
25524 }
25525 }
25526 }
25527
25528 fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
25531 let scheduler = Arc::clone(target);
25532 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
25533 target.push_fifo_raw(runnable);
25534 task.detach();
25535 }
25536
25537 let opts = RuntimeOptions {
25538 flavor: RuntimeFlavor::MultiThread,
25539 worker_threads: 2,
25540 max_blocking_tasks: 2,
25541 thread_name_prefix: "rd-onpool-scope".to_string(),
25542 park_deadline: None,
25543 drain_linger: DEFAULT_DRAIN_LINGER,
25544 };
25545 let exec1 =
25546 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25547 let exec2 =
25548 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25549 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25550
25551 let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
25553
25554 enqueue_runnable(&exec2);
25558 {
25559 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25560 exec2.block_on(probe.as_mut());
25561 }
25562 assert_eq!(
25563 exec2.metrics.runnable_polls.load(Ordering::Relaxed),
25564 0,
25565 "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
25566 );
25567 assert_eq!(
25568 exec2.injector.len(),
25569 1,
25570 "the foreign call must leave exec2's runnable untouched on its queue"
25571 );
25572
25573 enqueue_runnable(&exec1);
25577 {
25578 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25579 exec1.block_on(probe.as_mut());
25580 }
25581 assert_eq!(
25582 exec1.metrics.runnable_polls.load(Ordering::Relaxed),
25583 1,
25584 "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
25585 );
25586 assert!(
25587 exec1.injector.is_empty(),
25588 "the cooperative branch must have drained exec1's queued runnable"
25589 );
25590
25591 while exec2.steal_one().is_some() {}
25594 }
25595
25596 #[cfg(napi_runtime_os_threads)]
25597 #[test]
25598 fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
25599 use std::sync::mpsc;
25603 use std::time::Duration;
25604
25605 const PRODUCER_COUNT: usize = 8;
25606
25607 fn schedule_pool_workload(
25608 executor: &Arc<MultiThreadExecutor>,
25609 tx: &mpsc::Sender<Option<usize>>,
25610 count: usize,
25611 ) {
25612 for i in 0..count {
25613 let tx = tx.clone();
25614 let scheduler = Arc::clone(executor);
25615 let (runnable, task) =
25616 async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
25617 scheduler.schedule(r);
25618 });
25619 executor.schedule(runnable);
25620 task.detach();
25621 }
25622 let tx = tx.clone();
25623 let scheduler = Arc::clone(executor);
25624 let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
25625 scheduler.schedule(r)
25626 });
25627 executor.schedule(runnable);
25628 task.detach();
25629 }
25630
25631 let expected_sum: usize = (0..PRODUCER_COUNT).sum();
25632 let (done_tx, done_rx) = mpsc::sync_channel(1);
25633 let runner = std::thread::spawn(move || {
25634 let options = RuntimeOptions {
25635 flavor: RuntimeFlavor::MultiThread,
25636 worker_threads: 1,
25637 max_blocking_tasks: 1,
25638 thread_name_prefix: "rd2-pre-fix".to_string(),
25639 park_deadline: None,
25640 drain_linger: DEFAULT_DRAIN_LINGER,
25641 }
25642 .validate()
25643 .expect("production MultiThread options must provide a reserve lane");
25644 let executor =
25645 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25646
25647 let (tx, rx) = mpsc::channel::<Option<usize>>();
25648 let (started_tx, started_rx) = mpsc::sync_channel(1);
25649 let handle = executor.schedule_blocking(move || {
25650 started_tx.send(()).unwrap();
25651 let mut sum = 0usize;
25652 while let Ok(msg) = rx.recv() {
25653 match msg {
25654 Some(value) => sum += value,
25655 None => break,
25656 }
25657 }
25658 sum
25659 });
25660 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
25661 schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
25662 let sum = futures::executor::block_on(handle).unwrap();
25663 done_tx.send(sum).unwrap();
25664 });
25665
25666 match done_rx.recv_timeout(Duration::from_secs(10)) {
25667 Ok(sum) => {
25668 runner.join().unwrap();
25669 assert_eq!(sum, expected_sum);
25670 }
25671 Err(error) => {
25672 panic!("a saturated blocking lane starved the async producers that feed it ({error})")
25673 }
25674 }
25675 }
25676
25677 #[test]
25678 fn current_thread_blocking_work_completes_inline() {
25679 let metrics = Arc::new(RuntimeMetrics::default());
25680 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
25681 CurrentThreadExecutor::new(Arc::clone(&metrics)),
25682 )));
25683 let registration = backend.work.try_register_work().unwrap();
25684
25685 let value =
25686 futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
25687 .expect("blocking task should complete");
25688
25689 assert_eq!(value, 7);
25690 assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
25691 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25692 }
25693
25694 #[cfg(napi_runtime_os_threads)]
25695 #[test]
25696 fn current_thread_blocking_work_obeys_single_lane_across_callers() {
25697 use std::sync::mpsc;
25698
25699 let controller = Arc::new(current_thread_controller(
25700 "current-thread-blocking-admission",
25701 ));
25702 let backend = controller.backend();
25703 let (first_started_tx, first_started_rx) = mpsc::channel();
25704 let (release_first_tx, release_first_rx) = mpsc::channel();
25705 let first_controller = Arc::clone(&controller);
25706 let first = std::thread::spawn(move || {
25707 let handle = first_controller
25708 .try_spawn_blocking(move || {
25709 first_started_tx.send(()).unwrap();
25710 release_first_rx.recv().unwrap();
25711 1usize
25712 })
25713 .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
25714 futures::executor::block_on(handle).unwrap()
25715 });
25716 first_started_rx
25717 .recv_timeout(Duration::from_secs(1))
25718 .unwrap();
25719
25720 let (second_started_tx, second_started_rx) = mpsc::channel();
25721 let second_controller = Arc::clone(&controller);
25722 let second = std::thread::spawn(move || {
25723 let handle = second_controller
25724 .try_spawn_blocking(move || {
25725 second_started_tx.send(()).unwrap();
25726 2usize
25727 })
25728 .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
25729 futures::executor::block_on(handle).unwrap()
25730 });
25731
25732 wait_until("both CurrentThread blocking calls to be accepted", || {
25733 backend
25734 .work
25735 .state
25736 .lock()
25737 .unwrap_or_else(std::sync::PoisonError::into_inner)
25738 .active
25739 == 2
25740 });
25741 assert!(
25742 second_started_rx
25743 .recv_timeout(Duration::from_millis(100))
25744 .is_err(),
25745 "CurrentThread must not execute two blocking closures concurrently"
25746 );
25747
25748 release_first_tx.send(()).unwrap();
25749 second_started_rx
25750 .recv_timeout(Duration::from_secs(1))
25751 .expect("the waiting blocking call must acquire the released lane");
25752 assert_eq!(first.join().unwrap(), 1);
25753 assert_eq!(second.join().unwrap(), 2);
25754 assert_eq!(
25755 controller
25756 .metrics
25757 .blocking_tasks_started
25758 .load(Ordering::Relaxed),
25759 2
25760 );
25761 assert_eq!(
25762 controller
25763 .metrics
25764 .blocking_tasks_completed
25765 .load(Ordering::Relaxed),
25766 2
25767 );
25768 assert_eq!(
25769 controller
25770 .metrics
25771 .active_blocking_tasks
25772 .load(Ordering::Relaxed),
25773 0
25774 );
25775 assert_eq!(
25776 controller
25777 .metrics
25778 .max_active_blocking_tasks
25779 .load(Ordering::Relaxed),
25780 1,
25781 "the public active-lane metric must remain within CurrentThread's reported capacity"
25782 );
25783 controller.shutdown().unwrap();
25784 }
25785
25786 #[cfg(napi_runtime_os_threads)]
25787 #[test]
25788 fn current_thread_nested_blocking_reuses_the_active_lane() {
25789 use std::sync::mpsc;
25790
25791 let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
25792 let (done_tx, done_rx) = mpsc::channel();
25793 let runner_controller = Arc::clone(&controller);
25794 let runner = std::thread::spawn(move || {
25795 let nested_controller = Arc::clone(&runner_controller);
25796 let outer = runner_controller
25797 .try_spawn_blocking(move || {
25798 let inner = nested_controller
25799 .try_spawn_blocking(|| 41usize)
25800 .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
25801 futures::executor::block_on(inner).unwrap() + 1
25802 })
25803 .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
25804 done_tx
25805 .send(futures::executor::block_on(outer).unwrap())
25806 .unwrap();
25807 });
25808
25809 assert_eq!(
25810 done_rx
25811 .recv_timeout(Duration::from_secs(2))
25812 .expect("a nested CurrentThread blocking call must not wait on its own lane"),
25813 42
25814 );
25815 runner.join().unwrap();
25816 assert_eq!(
25817 controller
25818 .metrics
25819 .blocking_tasks_started
25820 .load(Ordering::Relaxed),
25821 2
25822 );
25823 assert_eq!(
25824 controller
25825 .metrics
25826 .blocking_tasks_completed
25827 .load(Ordering::Relaxed),
25828 2
25829 );
25830 assert_eq!(
25831 controller
25832 .metrics
25833 .active_blocking_tasks
25834 .load(Ordering::Relaxed),
25835 0
25836 );
25837 assert_eq!(
25838 controller
25839 .metrics
25840 .max_active_blocking_tasks
25841 .load(Ordering::Relaxed),
25842 1,
25843 "nested work borrows the outer lane instead of reporting a second physical lane"
25844 );
25845 controller.shutdown().unwrap();
25846 }
25847
25848 #[cfg(napi_runtime_os_threads)]
25849 #[test]
25850 fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
25851 use std::sync::mpsc;
25852
25853 struct HotFuture {
25854 stop: Arc<AtomicBool>,
25855 }
25856
25857 impl Future for HotFuture {
25858 type Output = ();
25859
25860 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25861 if self.stop.load(Ordering::SeqCst) {
25862 Poll::Ready(())
25863 } else {
25864 cx.waker().wake_by_ref();
25865 Poll::Pending
25866 }
25867 }
25868 }
25869
25870 let stop = Arc::new(AtomicBool::new(false));
25871 let (ready_tx, ready_rx) = mpsc::channel();
25872 let (exact_tx, exact_rx) = mpsc::channel();
25873 let (done_tx, done_rx) = mpsc::channel();
25874 let runner_stop = Arc::clone(&stop);
25875 let runner = std::thread::spawn(move || {
25876 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25877 RuntimeMetrics::default(),
25878 )));
25879 let owner = executor.fresh_owner_token();
25880 let job = BlockingJobId(u64::MAX - 70);
25881 let (result_tx, result_rx) = oneshot::channel();
25882 {
25883 let mut state = executor
25884 .blocking_admission
25885 .state
25886 .lock()
25887 .unwrap_or_else(std::sync::PoisonError::into_inner);
25888 state.owner = Some(owner);
25889 let exact_stop = Arc::clone(&runner_stop);
25890 state.queue.push(QueuedBlocking {
25891 id: job,
25892 run: Box::new(move || {
25893 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
25894 let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
25895 }),
25896 });
25897 }
25898 let exact = JoinHandle(JoinHandleInner::Blocking {
25899 receiver: result_rx,
25900 dependency: BlockingDependency {
25901 job,
25902 owner: Some(owner),
25903 },
25904 published: PublishedBlockingDependencies::default(),
25905 });
25906 let _owner = executor.enter_blocking_owner(owner, false);
25907
25908 let scheduler = Arc::clone(&executor);
25909 let hot_stop = Arc::clone(&runner_stop);
25910 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
25911 scheduler.schedule(runnable);
25912 });
25913 executor.schedule(runnable);
25914 ready_tx.send(()).unwrap();
25915
25916 let mut value = None;
25917 {
25918 let mut future = std::pin::pin!(async {
25919 value = Some(exact.await.unwrap());
25920 hot_task.await;
25921 });
25922 assert_eq!(
25923 executor.block_on(future.as_mut()),
25924 BlockOnOutcome::Completed
25925 );
25926 }
25927 executor
25928 .blocking_admission
25929 .state
25930 .lock()
25931 .unwrap_or_else(std::sync::PoisonError::into_inner)
25932 .owner = None;
25933 done_tx.send(value.unwrap()).unwrap();
25934 });
25935
25936 ready_rx
25937 .recv_timeout(Duration::from_secs(2))
25938 .expect("the fairness driver must start");
25939 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
25940 stop.store(true, Ordering::SeqCst);
25941 assert_eq!(
25942 done_rx
25943 .recv_timeout(Duration::from_secs(2))
25944 .expect("the CurrentThread driver must retire"),
25945 41
25946 );
25947 runner.join().unwrap();
25948 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
25949 assert_eq!(
25950 exact_before_stop,
25951 Some(true),
25952 "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
25953 );
25954 }
25955
25956 #[cfg(napi_runtime_os_threads)]
25957 #[test]
25958 fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
25959 use std::sync::mpsc;
25960
25961 let controller = Arc::new(current_thread_controller(
25962 "current-thread-cross-driver-blocking",
25963 ));
25964 let backend = controller.backend();
25965 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25966 panic!("configured CurrentThread backend must create a CurrentThread executor");
25967 };
25968 let executor = Arc::clone(executor);
25969 drop(backend);
25970
25971 let (release_driver_tx, release_driver_rx) = oneshot::channel();
25972 let (driver_started_tx, driver_started_rx) = mpsc::channel();
25973 let driver_controller = Arc::clone(&controller);
25974 let driver = std::thread::spawn(move || {
25975 driver_started_tx.send(std::thread::current().id()).unwrap();
25976 block_on_with_controller(&driver_controller, async {
25977 release_driver_rx
25978 .await
25979 .expect("driver release sender must remain alive");
25980 });
25981 });
25982 let driver_thread = driver_started_rx
25983 .recv_timeout(Duration::from_secs(1))
25984 .unwrap();
25985 wait_until("the secondary CurrentThread driver to park", || {
25986 executor
25987 .stop
25988 .parkers
25989 .lock()
25990 .unwrap_or_else(std::sync::PoisonError::into_inner)
25991 .iter()
25992 .filter_map(Weak::upgrade)
25993 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25994 });
25995
25996 let (child_polled_tx, child_polled_rx) = mpsc::channel();
25997 let (release_child_tx, release_child_rx) = mpsc::channel();
25998 let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
25999 let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
26000 let (owner_done_tx, owner_done_rx) = mpsc::channel();
26001 let owner_controller = Arc::clone(&controller);
26002 let owner = std::thread::spawn(move || {
26003 let owner_thread = std::thread::current().id();
26004 let await_controller = Arc::clone(&owner_controller);
26005 let child_controller = Arc::clone(&owner_controller);
26006 let outer = owner_controller
26007 .try_spawn_blocking(move || {
26008 let spawn_controller = Arc::clone(&child_controller);
26009 let child = spawn_controller
26010 .try_spawn(async move {
26011 child_polled_tx.send(std::thread::current().id()).unwrap();
26012 release_child_rx.recv().unwrap();
26013 child_controller
26014 .try_spawn_blocking(move || {
26015 blocking_ran_tx.send(std::thread::current().id()).unwrap();
26016 41usize
26017 })
26018 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
26019 .await
26020 .unwrap()
26021 + 1
26022 })
26023 .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
26024
26025 assert_eq!(
26026 child_polled_rx
26027 .recv_timeout(Duration::from_secs(1))
26028 .unwrap(),
26029 driver_thread,
26030 "the secondary driver must own the child poll that reaches blocking admission"
26031 );
26032 owner_awaiting_tx.send(owner_thread).unwrap();
26033 block_on_with_controller(&await_controller, child).unwrap()
26034 })
26035 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
26036 owner_done_tx
26037 .send(futures::executor::block_on(outer))
26038 .unwrap();
26039 });
26040
26041 let owner_thread = owner_awaiting_rx
26042 .recv_timeout(Duration::from_secs(1))
26043 .unwrap();
26044 wait_until(
26045 "the blocking owner to park while awaiting the cross-driver child",
26046 || {
26047 executor
26048 .stop
26049 .parkers
26050 .lock()
26051 .unwrap_or_else(std::sync::PoisonError::into_inner)
26052 .iter()
26053 .filter_map(Weak::upgrade)
26054 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
26055 },
26056 );
26057 release_child_tx.send(()).unwrap();
26058
26059 assert_eq!(
26060 blocking_ran_rx
26061 .recv_timeout(Duration::from_secs(2))
26062 .unwrap(),
26063 owner_thread,
26064 "the exact descendant must be serviced by the lane-owning driver"
26065 );
26066 assert_eq!(
26067 owner_done_rx
26068 .recv_timeout(Duration::from_secs(2))
26069 .expect("cross-driver blocking dependency must not deadlock")
26070 .unwrap(),
26071 42
26072 );
26073 release_driver_tx.send(()).unwrap();
26074 owner.join().unwrap();
26075 driver.join().unwrap();
26076 controller.shutdown().unwrap();
26077 assert_eq!(
26078 controller
26079 .metrics
26080 .blocking_tasks_started
26081 .load(Ordering::Relaxed),
26082 2
26083 );
26084 assert_eq!(
26085 controller
26086 .metrics
26087 .blocking_tasks_completed
26088 .load(Ordering::Relaxed),
26089 2
26090 );
26091 assert_eq!(
26092 controller
26093 .metrics
26094 .active_blocking_tasks
26095 .load(Ordering::Relaxed),
26096 0
26097 );
26098 assert_eq!(
26099 controller
26100 .metrics
26101 .max_active_blocking_tasks
26102 .load(Ordering::Relaxed),
26103 1,
26104 "cross-driver dependency service must reuse the owner's physical lane"
26105 );
26106 }
26107
26108 #[cfg(napi_runtime_os_threads)]
26109 #[test]
26110 fn current_thread_blocking_admission_rechecks_stop_under_lock() {
26111 use std::sync::mpsc;
26112
26113 let controller = Arc::new(current_thread_controller(
26114 "current-thread-blocking-stop-race",
26115 ));
26116 let backend = controller.backend();
26117 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26118 panic!("configured CurrentThread backend must create a CurrentThread executor");
26119 };
26120 let executor = Arc::clone(executor);
26121 drop(backend);
26122
26123 let ran = Arc::new(AtomicBool::new(false));
26124 let blocking_ran = Arc::clone(&ran);
26125 let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
26126 let (release_admission_tx, release_admission_rx) = mpsc::channel();
26127 let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
26128 let submit_controller = Arc::clone(&controller);
26129 let submitter = std::thread::spawn(move || {
26130 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26131 *slot.borrow_mut() = Some(Box::new(move || {
26132 admission_locked_tx.send(()).unwrap();
26133 release_admission_rx.recv().unwrap();
26134 }));
26135 });
26136 let handle = submit_controller
26137 .try_spawn_blocking(move || {
26138 blocking_ran.store(true, Ordering::SeqCst);
26139 })
26140 .unwrap_or_else(|_| {
26141 panic!("the submission linearized before shutdown and must be accepted")
26142 });
26143 blocking_result_tx
26144 .send(futures::executor::block_on(handle))
26145 .unwrap();
26146 });
26147 admission_locked_rx
26148 .recv_timeout(Duration::from_secs(2))
26149 .expect("the blocking submitter must pause while holding the admission lock");
26150
26151 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
26152 let shutdown_controller = Arc::clone(&controller);
26153 let shutdown = std::thread::spawn(move || {
26154 shutdown_result_tx
26155 .send(shutdown_controller.shutdown())
26156 .unwrap();
26157 });
26158 wait_until("CurrentThread shutdown to publish generation stop", || {
26159 executor.stop.is_stopping()
26160 });
26161 release_admission_tx.send(()).unwrap();
26162
26163 let blocking_error = blocking_result_rx
26164 .recv_timeout(Duration::from_secs(2))
26165 .expect("the admitted blocking handle must resolve")
26166 .expect_err("stop publication must reject the blocking lane claim");
26167 assert_eq!(
26168 blocking_error.to_string(),
26169 "the async runtime stopped before the blocking task could start"
26170 );
26171 assert!(
26172 !ran.load(Ordering::SeqCst),
26173 "blocking work must not start after stop publication"
26174 );
26175 shutdown_result_rx
26176 .recv_timeout(Duration::from_secs(2))
26177 .expect("shutdown must finish after the rejected admission retires")
26178 .unwrap();
26179 submitter.join().unwrap();
26180 shutdown.join().unwrap();
26181 }
26182
26183 #[cfg(napi_runtime_os_threads)]
26184 #[test]
26185 fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
26186 use std::sync::mpsc;
26187
26188 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26189 RuntimeMetrics::default(),
26190 )));
26191 let ran = Arc::new(AtomicBool::new(false));
26192 let job_ran = Arc::clone(&ran);
26193 {
26194 let mut state = executor
26195 .blocking_admission
26196 .state
26197 .lock()
26198 .unwrap_or_else(std::sync::PoisonError::into_inner);
26199 state.queue.push(QueuedBlocking {
26200 id: BlockingJobId(u64::MAX - 71),
26201 run: Box::new(move || {
26202 job_ran.store(true, Ordering::SeqCst);
26203 }),
26204 });
26205 }
26206
26207 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26208 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26209 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26210 let claim_executor = Arc::clone(&executor);
26211 let claimant = std::thread::spawn(move || {
26212 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26213 *slot.borrow_mut() = Some(Box::new(move || {
26214 claim_locked_tx.send(()).unwrap();
26215 release_claim_rx.recv().unwrap();
26216 }));
26217 });
26218 claim_result_tx
26219 .send(claim_executor.run_one_blocking())
26220 .unwrap();
26221 });
26222 claim_locked_rx
26223 .recv_timeout(Duration::from_secs(2))
26224 .expect("the FIFO claimant must pause while holding the admission lock");
26225
26226 executor.stop.begin_shutdown();
26227 release_claim_tx.send(()).unwrap();
26228 assert!(
26229 !claim_result_rx
26230 .recv_timeout(Duration::from_secs(2))
26231 .expect("the stopped FIFO claim must finish"),
26232 "stop publication must reject the queued FIFO claim"
26233 );
26234 claimant.join().unwrap();
26235 assert!(
26236 !ran.load(Ordering::SeqCst),
26237 "queued FIFO work must not start after stop publication"
26238 );
26239 assert!(
26240 !executor
26241 .blocking_admission
26242 .state
26243 .lock()
26244 .unwrap_or_else(std::sync::PoisonError::into_inner)
26245 .queue
26246 .is_empty(),
26247 "the rejected FIFO job must remain queued for shutdown cancellation"
26248 );
26249
26250 executor.close_blocking_admission();
26251 assert!(
26252 executor
26253 .blocking_admission
26254 .state
26255 .lock()
26256 .unwrap_or_else(std::sync::PoisonError::into_inner)
26257 .queue
26258 .is_empty()
26259 );
26260 }
26261
26262 #[cfg(napi_runtime_os_threads)]
26263 #[test]
26264 fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26265 use std::sync::mpsc;
26266
26267 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26268 RuntimeMetrics::default(),
26269 )));
26270 let ran = Arc::new(AtomicBool::new(false));
26271 let job_ran = Arc::clone(&ran);
26272 {
26273 let mut state = executor
26274 .blocking_admission
26275 .state
26276 .lock()
26277 .unwrap_or_else(std::sync::PoisonError::into_inner);
26278 state.queue.push(QueuedBlocking {
26279 id: BlockingJobId(u64::MAX - 73),
26280 run: Box::new(move || {
26281 job_ran.store(true, Ordering::SeqCst);
26282 }),
26283 });
26284 }
26285
26286 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26287 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26288 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26289 let claim_executor = Arc::clone(&executor);
26290 let claimant = std::thread::spawn(move || {
26291 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26292 *slot.borrow_mut() = Some(Box::new(move || {
26293 claim_locked_tx.send(()).unwrap();
26294 release_claim_rx.recv().unwrap();
26295 }));
26296 });
26297 claim_result_tx
26298 .send(claim_executor.run_one_blocking())
26299 .unwrap();
26300 });
26301 claim_locked_rx
26302 .recv_timeout(Duration::from_secs(2))
26303 .expect("the FIFO claimant must pause while holding the admission lock");
26304
26305 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26306 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26307 let cancelling_executor = Arc::clone(&executor);
26308 let cancellation = std::thread::spawn(move || {
26309 let cancelling = cancelling_executor
26310 .begin_failed_dispatch_cancellation()
26311 .expect("the test must publish terminal dispatch cancellation");
26312 cancellation_published_tx.send(()).unwrap();
26313 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26314 cancellation_done_tx.send(()).unwrap();
26315 });
26316 cancellation_published_rx
26317 .recv_timeout(Duration::from_secs(2))
26318 .expect("terminal dispatch cancellation must publish before queue cancellation");
26319 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26320 assert!(
26321 matches!(
26322 cancellation_done_rx.try_recv(),
26323 Err(mpsc::TryRecvError::Empty)
26324 ),
26325 "terminal cancellation must wait for the blocking-admission lock"
26326 );
26327
26328 release_claim_tx.send(()).unwrap();
26329 assert!(
26330 !claim_result_rx
26331 .recv_timeout(Duration::from_secs(2))
26332 .expect("the cancelled FIFO claim must finish"),
26333 "terminal cancellation publication must reject the queued FIFO claim"
26334 );
26335 cancellation_done_rx
26336 .recv_timeout(Duration::from_secs(2))
26337 .expect("terminal cancellation must drain the rejected FIFO job");
26338 claimant.join().unwrap();
26339 cancellation.join().unwrap();
26340
26341 assert!(
26342 !ran.load(Ordering::SeqCst),
26343 "terminally cancelled FIFO work must not start"
26344 );
26345 assert!(
26346 executor
26347 .blocking_admission
26348 .state
26349 .lock()
26350 .unwrap_or_else(std::sync::PoisonError::into_inner)
26351 .queue
26352 .is_empty(),
26353 "terminal cancellation must drain the rejected FIFO job"
26354 );
26355 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26356 }
26357
26358 #[cfg(napi_runtime_os_threads)]
26359 #[test]
26360 fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
26361 use std::sync::mpsc;
26362
26363 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26364 RuntimeMetrics::default(),
26365 )));
26366 let owner = executor.fresh_owner_token();
26367 let job = BlockingJobId(u64::MAX - 72);
26368 let ran = Arc::new(AtomicBool::new(false));
26369 let job_ran = Arc::clone(&ran);
26370 {
26371 let mut state = executor
26372 .blocking_admission
26373 .state
26374 .lock()
26375 .unwrap_or_else(std::sync::PoisonError::into_inner);
26376 state.owner = Some(owner);
26377 state.queue.push(QueuedBlocking {
26378 id: job,
26379 run: Box::new(move || {
26380 job_ran.store(true, Ordering::SeqCst);
26381 }),
26382 });
26383 }
26384 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26385 dependency.set_owned(BlockingDependency {
26386 job,
26387 owner: Some(owner),
26388 });
26389
26390 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26391 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26392 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26393 let claim_executor = Arc::clone(&executor);
26394 let claim_dependency = Arc::clone(&dependency);
26395 let claimant = std::thread::spawn(move || {
26396 let _owner = claim_executor.enter_blocking_owner(owner, false);
26397 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26398 *slot.borrow_mut() = Some(Box::new(move || {
26399 claim_locked_tx.send(()).unwrap();
26400 release_claim_rx.recv().unwrap();
26401 }));
26402 });
26403 claim_result_tx
26404 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26405 .unwrap();
26406 });
26407 claim_locked_rx
26408 .recv_timeout(Duration::from_secs(2))
26409 .expect("the owner claimant must pause while holding the admission lock");
26410
26411 executor.stop.begin_shutdown();
26412 release_claim_tx.send(()).unwrap();
26413 assert!(
26414 !claim_result_rx
26415 .recv_timeout(Duration::from_secs(2))
26416 .expect("the stopped owner claim must finish"),
26417 "stop publication must reject the exact-owner claim"
26418 );
26419 claimant.join().unwrap();
26420 assert!(
26421 !ran.load(Ordering::SeqCst),
26422 "queued exact-owner work must not start after stop publication"
26423 );
26424 assert!(
26425 !executor
26426 .blocking_admission
26427 .state
26428 .lock()
26429 .unwrap_or_else(std::sync::PoisonError::into_inner)
26430 .queue
26431 .is_empty(),
26432 "the rejected exact-owner job must remain queued for shutdown cancellation"
26433 );
26434
26435 executor
26436 .blocking_admission
26437 .state
26438 .lock()
26439 .unwrap_or_else(std::sync::PoisonError::into_inner)
26440 .owner = None;
26441 executor.close_blocking_admission();
26442 assert!(
26443 executor
26444 .blocking_admission
26445 .state
26446 .lock()
26447 .unwrap_or_else(std::sync::PoisonError::into_inner)
26448 .queue
26449 .is_empty()
26450 );
26451 }
26452
26453 #[cfg(napi_runtime_os_threads)]
26454 #[test]
26455 fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26456 use std::sync::mpsc;
26457
26458 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26459 RuntimeMetrics::default(),
26460 )));
26461 let owner = executor.fresh_owner_token();
26462 let job = BlockingJobId(u64::MAX - 74);
26463 let ran = Arc::new(AtomicBool::new(false));
26464 let job_ran = Arc::clone(&ran);
26465 {
26466 let mut state = executor
26467 .blocking_admission
26468 .state
26469 .lock()
26470 .unwrap_or_else(std::sync::PoisonError::into_inner);
26471 state.owner = Some(owner);
26472 state.queue.push(QueuedBlocking {
26473 id: job,
26474 run: Box::new(move || {
26475 job_ran.store(true, Ordering::SeqCst);
26476 }),
26477 });
26478 }
26479 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26480 dependency.set_owned(BlockingDependency {
26481 job,
26482 owner: Some(owner),
26483 });
26484
26485 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26486 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26487 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26488 let claim_executor = Arc::clone(&executor);
26489 let claim_dependency = Arc::clone(&dependency);
26490 let claimant = std::thread::spawn(move || {
26491 let _owner = claim_executor.enter_blocking_owner(owner, false);
26492 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26493 *slot.borrow_mut() = Some(Box::new(move || {
26494 claim_locked_tx.send(()).unwrap();
26495 release_claim_rx.recv().unwrap();
26496 }));
26497 });
26498 claim_result_tx
26499 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26500 .unwrap();
26501 });
26502 claim_locked_rx
26503 .recv_timeout(Duration::from_secs(2))
26504 .expect("the owner claimant must pause while holding the admission lock");
26505
26506 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26507 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26508 let cancelling_executor = Arc::clone(&executor);
26509 let cancellation = std::thread::spawn(move || {
26510 let cancelling = cancelling_executor
26511 .begin_failed_dispatch_cancellation()
26512 .expect("the test must publish terminal dispatch cancellation");
26513 cancellation_published_tx.send(()).unwrap();
26514 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26515 cancellation_done_tx.send(()).unwrap();
26516 });
26517 cancellation_published_rx
26518 .recv_timeout(Duration::from_secs(2))
26519 .expect("terminal dispatch cancellation must publish before queue cancellation");
26520 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26521 assert!(
26522 matches!(
26523 cancellation_done_rx.try_recv(),
26524 Err(mpsc::TryRecvError::Empty)
26525 ),
26526 "terminal cancellation must wait for the blocking-admission lock"
26527 );
26528
26529 release_claim_tx.send(()).unwrap();
26530 assert!(
26531 !claim_result_rx
26532 .recv_timeout(Duration::from_secs(2))
26533 .expect("the cancelled owner claim must finish"),
26534 "terminal cancellation publication must reject the exact-owner claim"
26535 );
26536 cancellation_done_rx
26537 .recv_timeout(Duration::from_secs(2))
26538 .expect("terminal cancellation must drain the rejected exact-owner job");
26539 claimant.join().unwrap();
26540 cancellation.join().unwrap();
26541
26542 assert!(
26543 !ran.load(Ordering::SeqCst),
26544 "terminally cancelled exact-owner work must not start"
26545 );
26546 assert!(
26547 executor
26548 .blocking_admission
26549 .state
26550 .lock()
26551 .unwrap_or_else(std::sync::PoisonError::into_inner)
26552 .queue
26553 .is_empty(),
26554 "terminal cancellation must drain the rejected exact-owner job"
26555 );
26556 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26557 }
26558
26559 #[cfg(napi_runtime_os_threads)]
26560 #[test]
26561 fn current_thread_shutdown_cancels_waiting_blocking_work() {
26562 use std::sync::mpsc;
26563
26564 struct DropSignal(mpsc::Sender<()>);
26565
26566 impl Drop for DropSignal {
26567 fn drop(&mut self) {
26568 let _ = self.0.send(());
26569 }
26570 }
26571
26572 let controller = Arc::new(current_thread_controller(
26573 "current-thread-blocking-shutdown",
26574 ));
26575 let backend = controller.backend();
26576 let (first_started_tx, first_started_rx) = mpsc::channel();
26577 let (release_first_tx, release_first_rx) = mpsc::channel();
26578 let first_controller = Arc::clone(&controller);
26579 let first = std::thread::spawn(move || {
26580 let handle = first_controller
26581 .try_spawn_blocking(move || {
26582 first_started_tx.send(()).unwrap();
26583 release_first_rx.recv().unwrap();
26584 })
26585 .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
26586 futures::executor::block_on(handle)
26587 });
26588 first_started_rx
26589 .recv_timeout(Duration::from_secs(1))
26590 .unwrap();
26591
26592 let waiting_ran = Arc::new(AtomicBool::new(false));
26593 let waiting_ran_task = Arc::clone(&waiting_ran);
26594 let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
26595 let waiting_drop = DropSignal(waiting_dropped_tx);
26596 let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
26597 let waiting_controller = Arc::clone(&controller);
26598 let waiting = std::thread::spawn(move || {
26599 let handle = waiting_controller
26600 .try_spawn_blocking(move || {
26601 let _waiting_drop = waiting_drop;
26602 waiting_ran_task.store(true, Ordering::SeqCst);
26603 })
26604 .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
26605 waiting_result_tx
26606 .send(futures::executor::block_on(handle))
26607 .unwrap();
26608 });
26609 wait_until("both CurrentThread blocking calls to be accepted", || {
26610 backend
26611 .work
26612 .state
26613 .lock()
26614 .unwrap_or_else(std::sync::PoisonError::into_inner)
26615 .active
26616 == 2
26617 });
26618
26619 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26620 let shutdown_controller = Arc::clone(&controller);
26621 let shutdown = std::thread::spawn(move || {
26622 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26623 });
26624 waiting_dropped_rx
26625 .recv_timeout(Duration::from_secs(1))
26626 .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
26627 assert!(
26628 !waiting_ran.load(Ordering::SeqCst),
26629 "waiting blocking work must not start"
26630 );
26631 assert!(
26632 waiting_result_rx
26633 .recv_timeout(Duration::from_secs(1))
26634 .unwrap()
26635 .is_err(),
26636 "the waiting blocking handle must resolve as cancelled"
26637 );
26638 assert!(
26639 shutdown_rx
26640 .recv_timeout(Duration::from_millis(100))
26641 .is_err(),
26642 "shutdown must still wait for the blocking closure that already owns the lane"
26643 );
26644
26645 release_first_tx.send(()).unwrap();
26646 first.join().unwrap().unwrap();
26647 waiting.join().unwrap();
26648 shutdown_rx
26649 .recv_timeout(Duration::from_secs(1))
26650 .unwrap()
26651 .unwrap();
26652 shutdown.join().unwrap();
26653 assert_eq!(
26654 controller
26655 .metrics
26656 .blocking_tasks_started
26657 .load(Ordering::Relaxed),
26658 1
26659 );
26660 assert_eq!(
26661 controller
26662 .metrics
26663 .blocking_tasks_completed
26664 .load(Ordering::Relaxed),
26665 1
26666 );
26667 assert_eq!(
26668 controller
26669 .metrics
26670 .active_blocking_tasks
26671 .load(Ordering::Relaxed),
26672 0
26673 );
26674 }
26675
26676 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
26677 #[test]
26678 fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
26679 use std::{panic::panic_any, sync::mpsc};
26680
26681 struct ReentrantPayload {
26682 controller: Arc<RuntimeController>,
26683 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26684 }
26685
26686 impl Drop for ReentrantPayload {
26687 fn drop(&mut self) {
26688 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26689 let shutdown = self.controller.shutdown();
26690 self.result.send((generation, shutdown)).unwrap();
26691 }
26692 }
26693
26694 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26695
26696 impl Drop for PanicPayloadOnDrop {
26697 fn drop(&mut self) {
26698 panic_any(self.0.take().expect("the result must panic only once"));
26699 }
26700 }
26701
26702 let controller = Arc::new(current_thread_controller(
26703 "current-thread-panic-payload-generation",
26704 ));
26705 let backend = controller.backend();
26706 let generation = backend.generation();
26707 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26708 panic!("configured CurrentThread backend must create a CurrentThread executor");
26709 };
26710 let executor = Arc::clone(executor);
26711 drop(backend);
26712
26713 let owner = executor.fresh_owner_token();
26716 executor
26717 .blocking_admission
26718 .state
26719 .lock()
26720 .unwrap_or_else(std::sync::PoisonError::into_inner)
26721 .owner = Some(owner);
26722 let (result_tx, result_rx) = mpsc::channel();
26723 let payload_controller = Arc::clone(&controller);
26724 let handle = controller
26725 .try_spawn_blocking(move || {
26726 PanicPayloadOnDrop(Some(ReentrantPayload {
26727 controller: payload_controller,
26728 result: result_tx,
26729 }))
26730 })
26731 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26732 drop(handle);
26733 executor
26734 .blocking_admission
26735 .state
26736 .lock()
26737 .unwrap_or_else(std::sync::PoisonError::into_inner)
26738 .owner = None;
26739 let dispatch = current_thread_dispatch(&executor);
26740 controller.drive_current_thread_dispatch(dispatch, false);
26741
26742 let (observed_generation, shutdown) = result_rx
26743 .recv_timeout(Duration::from_secs(1))
26744 .expect("the caught result panic payload must be destroyed in the scheduler turn");
26745 assert_eq!(observed_generation, Some(generation));
26746 let error = shutdown.expect_err("payload destruction is still work in the active generation");
26747 assert!(error.to_string().contains("work running on that runtime"));
26748 controller.shutdown().unwrap();
26749 }
26750
26751 #[cfg(panic = "unwind")]
26752 #[test]
26753 fn current_thread_detached_blocking_result_drop_is_contained() {
26754 use std::sync::mpsc;
26755
26756 struct PanicOnDrop(mpsc::Sender<()>);
26757
26758 impl Drop for PanicOnDrop {
26759 fn drop(&mut self) {
26760 self.0.send(()).unwrap();
26761 panic!("intentional CurrentThread blocking result destructor panic");
26762 }
26763 }
26764
26765 let controller = current_thread_controller("current-thread-blocking-result-drop");
26766 let (dropped_tx, dropped_rx) = mpsc::channel();
26767 let handle = controller
26768 .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
26769 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26770
26771 let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26772 dropped_rx
26773 .recv_timeout(Duration::from_secs(1))
26774 .expect("dropping the detached handle must reclaim its blocking result");
26775 assert!(
26776 result.is_ok(),
26777 "the blocking result destructor panic must be contained"
26778 );
26779 controller.shutdown().unwrap();
26780 }
26781
26782 #[test]
26787 fn validate_rejects_zero_worker_threads() {
26788 let error = RuntimeOptions {
26789 flavor: RuntimeFlavor::MultiThread,
26790 worker_threads: 0,
26791 max_blocking_tasks: 1,
26792 thread_name_prefix: "rd8".to_string(),
26793 park_deadline: None,
26794 drain_linger: DEFAULT_DRAIN_LINGER,
26795 }
26796 .validate()
26797 .expect_err("worker_threads == 0 must be rejected");
26798 assert_eq!(
26799 error.to_string(),
26800 "worker_threads must be greater than zero"
26801 );
26802 }
26803
26804 #[test]
26805 fn validate_rejects_zero_max_blocking_tasks() {
26806 let error = RuntimeOptions {
26807 flavor: RuntimeFlavor::MultiThread,
26808 worker_threads: 2,
26809 max_blocking_tasks: 0,
26810 thread_name_prefix: "rd8".to_string(),
26811 park_deadline: None,
26812 drain_linger: DEFAULT_DRAIN_LINGER,
26813 }
26814 .validate()
26815 .expect_err("max_blocking_tasks == 0 must be rejected");
26816 assert_eq!(
26817 error.to_string(),
26818 "max_blocking_tasks must be greater than zero"
26819 );
26820 }
26821
26822 #[test]
26823 fn validate_current_thread_coerces_worker_threads_to_one() {
26824 let validated = RuntimeOptions {
26827 flavor: RuntimeFlavor::CurrentThread,
26828 worker_threads: 8,
26829 max_blocking_tasks: 8,
26830 thread_name_prefix: "rd8".to_string(),
26831 park_deadline: None,
26832 drain_linger: DEFAULT_DRAIN_LINGER,
26833 }
26834 .validate()
26835 .expect("CurrentThread options must validate");
26836 assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
26837 assert_eq!(validated.worker_threads, 1);
26838 assert_eq!(validated.max_blocking_tasks, 1);
26839 }
26840
26841 #[cfg(napi_runtime_os_threads)]
26845 #[test]
26846 fn validate_reserves_one_worker_from_blocking_admission() {
26847 let validated = RuntimeOptions {
26848 flavor: RuntimeFlavor::MultiThread,
26849 worker_threads: 2,
26850 max_blocking_tasks: 8,
26851 thread_name_prefix: "rd8".to_string(),
26852 park_deadline: None,
26853 drain_linger: DEFAULT_DRAIN_LINGER,
26854 }
26855 .validate()
26856 .expect("MultiThread options must validate");
26857 assert_eq!(validated.worker_threads, 2);
26858 assert_eq!(validated.max_blocking_tasks, 1);
26859 }
26860
26861 #[cfg(napi_runtime_os_threads)]
26862 #[test]
26863 fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
26864 let validated = RuntimeOptions {
26865 flavor: RuntimeFlavor::MultiThread,
26866 worker_threads: 1,
26867 max_blocking_tasks: 8,
26868 thread_name_prefix: "rd8-minimum".to_string(),
26869 park_deadline: None,
26870 drain_linger: DEFAULT_DRAIN_LINGER,
26871 }
26872 .validate()
26873 .expect("MultiThread options must validate");
26874 assert_eq!(validated.worker_threads, 2);
26875 assert_eq!(validated.max_blocking_tasks, 1);
26876 }
26877
26878 #[cfg(napi_runtime_os_threads)]
26879 #[test]
26880 fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
26881 let simulated_32_bit = RuntimeOptions {
26882 flavor: RuntimeFlavor::MultiThread,
26883 worker_threads: 256,
26884 max_blocking_tasks: 256,
26885 thread_name_prefix: "rd8-rayon-cap-32".to_string(),
26886 park_deadline: None,
26887 drain_linger: DEFAULT_DRAIN_LINGER,
26888 }
26889 .validate_with_rayon_max_threads(Some(255))
26890 .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
26891 assert_eq!(simulated_32_bit.worker_threads, 255);
26892 assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
26893
26894 let rayon_max_threads = max_num_threads();
26895 let requested_threads = rayon_max_threads
26896 .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
26897 .checked_add(1)
26898 .expect("supported worker counts must leave room for an oversized request");
26899 let controller = RuntimeController::new();
26900 controller
26901 .configure(RuntimeOptions {
26902 flavor: RuntimeFlavor::MultiThread,
26903 worker_threads: requested_threads,
26904 max_blocking_tasks: requested_threads,
26905 thread_name_prefix: "rd8-rayon-cap".to_string(),
26906 park_deadline: None,
26907 drain_linger: DEFAULT_DRAIN_LINGER,
26908 })
26909 .expect("an oversized request must clamp to the production worker ceiling");
26910
26911 let reported = controller.options();
26912 let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
26913 assert_eq!(reported.worker_threads, expected_workers);
26914 assert_eq!(
26915 reported.max_blocking_tasks,
26916 expected_workers - 1,
26917 "the blocking cap must reserve a lane from the physically realizable worker count"
26918 );
26919 }
26920
26921 #[cfg(napi_runtime_os_threads)]
26922 #[test]
26923 fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
26924 let extreme = Duration::from_micros(u64::MAX);
26931 let validated = RuntimeOptions {
26932 flavor: RuntimeFlavor::MultiThread,
26933 worker_threads: 2,
26934 max_blocking_tasks: 1,
26935 thread_name_prefix: "linger-clamp".to_string(),
26936 park_deadline: None,
26937 drain_linger: extreme,
26938 }
26939 .validate_with_rayon_max_threads(Some(2))
26940 .expect("an extreme linger budget must clamp, not fail validation");
26941 assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
26942
26943 let controller = RuntimeController::new();
26944 controller
26945 .configure_partial(RuntimeOptionsPatch {
26946 drain_linger: Some(extreme),
26947 ..RuntimeOptionsPatch::default()
26948 })
26949 .expect("an extreme linger patch must clamp, not fail validation");
26950 assert_eq!(
26951 controller.options().drain_linger,
26952 MAX_DRAIN_LINGER,
26953 "introspection must report the clamped budget, not the requested one"
26954 );
26955 controller
26956 .configure_partial(RuntimeOptionsPatch {
26957 drain_linger: Some(Duration::ZERO),
26958 ..RuntimeOptionsPatch::default()
26959 })
26960 .expect("ZERO (lingering disabled) must remain a valid budget");
26961 assert_eq!(controller.options().drain_linger, Duration::ZERO);
26962
26963 let metrics = Arc::new(RuntimeMetrics::default());
26964 let unvalidated = RuntimeOptions {
26965 flavor: RuntimeFlavor::MultiThread,
26966 worker_threads: 2,
26967 max_blocking_tasks: 2,
26968 thread_name_prefix: "linger-clamp-exec".to_string(),
26969 park_deadline: None,
26970 drain_linger: extreme,
26971 };
26972 let executor = MultiThreadExecutor::new(&unvalidated, metrics)
26973 .expect("the executor must construct with an extreme unvalidated budget");
26974 assert_eq!(
26975 executor.drain_linger,
26976 Some(MAX_DRAIN_LINGER),
26977 "the constructor's defensive clamp must bound unvalidated budgets"
26978 );
26979
26980 let metrics = Arc::new(RuntimeMetrics::default());
26981 let disabled = RuntimeOptions {
26982 flavor: RuntimeFlavor::MultiThread,
26983 worker_threads: 2,
26984 max_blocking_tasks: 2,
26985 thread_name_prefix: "linger-clamp-off".to_string(),
26986 park_deadline: None,
26987 drain_linger: Duration::ZERO,
26988 };
26989 let executor = MultiThreadExecutor::new(&disabled, metrics)
26990 .expect("the executor must construct with lingering disabled");
26991 assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
26992 }
26993
26994 #[cfg(not(target_family = "wasm"))]
26995 #[test]
26996 fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
26997 let controller = RuntimeController::new();
26998 let options = controller.options();
26999 assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
27000 assert_eq!(
27001 options.max_blocking_tasks,
27002 options.worker_threads.saturating_sub(1).max(1),
27003 "the unconfigured Rust API must use the same validated reserve as the binding"
27004 );
27005 }
27006
27007 #[cfg(napi_runtime_os_threads)]
27008 #[test]
27009 fn concurrent_partial_configuration_merges_from_latest_committed_options() {
27010 use std::{sync::mpsc, time::Duration};
27011
27012 let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
27013 let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
27014 let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
27015 let first_controller = Arc::clone(&controller);
27016 let first = std::thread::spawn(move || {
27017 first_controller
27018 .configure_partial_inner(
27019 RuntimeOptionsPatch {
27020 max_blocking_tasks: Some(3),
27021 ..RuntimeOptionsPatch::default()
27022 },
27023 |candidate| {
27024 assert_eq!(
27025 (candidate.worker_threads, candidate.max_blocking_tasks),
27026 (8, 3)
27027 );
27028 first_merged_tx
27029 .send(())
27030 .expect("the race coordinator must still be listening");
27031 release_first_rx
27032 .recv_timeout(Duration::from_secs(5))
27033 .expect("the first partial configuration must be released");
27034 },
27035 )
27036 .expect("the first partial configuration must commit");
27037 });
27038 first_merged_rx
27039 .recv_timeout(Duration::from_secs(5))
27040 .expect("the first partial configuration must hold the controller lock after merging");
27041
27042 let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
27043 let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
27044 let second_controller = Arc::clone(&controller);
27045 let second = std::thread::spawn(move || {
27046 second_started_tx
27047 .send(())
27048 .expect("the race coordinator must still be listening");
27049 second_controller
27050 .configure_partial_inner(
27051 RuntimeOptionsPatch {
27052 worker_threads: Some(6),
27053 ..RuntimeOptionsPatch::default()
27054 },
27055 |candidate| {
27056 second_candidate_tx
27057 .send((candidate.worker_threads, candidate.max_blocking_tasks))
27058 .expect("the race coordinator must still be listening");
27059 },
27060 )
27061 .expect("the second partial configuration must commit");
27062 });
27063 second_started_rx
27064 .recv_timeout(Duration::from_secs(5))
27065 .expect("the second partial configuration thread must start");
27066 release_first_tx
27067 .send(())
27068 .expect("the first partial configuration must still be waiting");
27069
27070 first
27071 .join()
27072 .expect("the first configuration thread must not panic");
27073 second
27074 .join()
27075 .expect("the second configuration thread must not panic");
27076 assert_eq!(
27077 second_candidate_rx
27078 .recv_timeout(Duration::from_secs(5))
27079 .expect("the second partial configuration must expose its merged candidate"),
27080 (6, 3),
27081 "the second caller must merge against the first caller's committed options"
27082 );
27083
27084 let options = controller.options();
27085 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
27086 assert_eq!(options.thread_name_prefix, "partial-config-race");
27087 }
27088
27089 #[cfg(napi_runtime_os_threads)]
27090 #[test]
27091 fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
27092 use std::{sync::mpsc, time::Duration};
27093
27094 let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
27095 let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
27096 let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
27097 let invalid_controller = Arc::clone(&controller);
27098 let invalid = std::thread::spawn(move || {
27099 invalid_controller
27100 .configure_partial_inner(
27101 RuntimeOptionsPatch {
27102 worker_threads: Some(0),
27103 ..RuntimeOptionsPatch::default()
27104 },
27105 |candidate| {
27106 assert_eq!(
27107 (candidate.worker_threads, candidate.max_blocking_tasks),
27108 (0, 2)
27109 );
27110 invalid_merged_tx
27111 .send(())
27112 .expect("the race coordinator must still be listening");
27113 release_invalid_rx
27114 .recv_timeout(Duration::from_secs(5))
27115 .expect("the invalid partial configuration must be released");
27116 },
27117 )
27118 .expect_err("the invalid partial configuration must be rejected")
27119 .to_string()
27120 });
27121 invalid_merged_rx
27122 .recv_timeout(Duration::from_secs(5))
27123 .expect("the invalid partial configuration must hold the lock after merging");
27124
27125 let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
27126 let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
27127 let valid_controller = Arc::clone(&controller);
27128 let valid = std::thread::spawn(move || {
27129 valid_started_tx
27130 .send(())
27131 .expect("the race coordinator must still be listening");
27132 valid_controller
27133 .configure_partial_inner(
27134 RuntimeOptionsPatch {
27135 max_blocking_tasks: Some(4),
27136 ..RuntimeOptionsPatch::default()
27137 },
27138 |candidate| {
27139 valid_candidate_tx
27140 .send((candidate.worker_threads, candidate.max_blocking_tasks))
27141 .expect("the race coordinator must still be listening");
27142 },
27143 )
27144 .expect("the valid partial configuration must commit");
27145 });
27146 valid_started_rx
27147 .recv_timeout(Duration::from_secs(5))
27148 .expect("the valid partial configuration thread must start");
27149 release_invalid_tx
27150 .send(())
27151 .expect("the invalid partial configuration must still be waiting");
27152
27153 assert_eq!(
27154 invalid
27155 .join()
27156 .expect("the invalid configuration thread must not panic"),
27157 "worker_threads must be greater than zero"
27158 );
27159 valid
27160 .join()
27161 .expect("the valid configuration thread must not panic");
27162 assert_eq!(
27163 valid_candidate_rx
27164 .recv_timeout(Duration::from_secs(5))
27165 .expect("the valid partial configuration must expose its merged candidate"),
27166 (6, 4),
27167 "the valid caller must merge against the last committed options, not the rejected candidate"
27168 );
27169
27170 let options = controller.options();
27171 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
27172 assert_eq!(options.thread_name_prefix, "partial-config-rollback");
27173 }
27174
27175 #[test]
27176 fn configure_after_backend_started_is_rejected() {
27177 let controller = RuntimeController::new();
27180 controller
27181 .configure(RuntimeOptions {
27182 flavor: RuntimeFlavor::CurrentThread,
27183 worker_threads: 1,
27184 max_blocking_tasks: 1,
27185 thread_name_prefix: "rd8".to_string(),
27186 park_deadline: None,
27187 drain_linger: DEFAULT_DRAIN_LINGER,
27188 })
27189 .expect("first configure before the backend exists must succeed");
27190
27191 let _backend = controller.backend();
27193
27194 let error = controller
27195 .configure(RuntimeOptions::default())
27196 .expect_err("configure after the backend started must be rejected");
27197 assert_eq!(
27198 error.to_string(),
27199 "the async runtime configuration is frozen; configure it before the first async call"
27200 );
27201 }
27202
27203 #[test]
27204 fn partial_configuration_after_backend_started_is_rejected() {
27205 let controller = current_thread_controller("partial-config-frozen");
27206 let _backend = controller.backend();
27207
27208 let error = controller
27209 .configure_partial(RuntimeOptionsPatch {
27210 worker_threads: Some(2),
27211 ..RuntimeOptionsPatch::default()
27212 })
27213 .expect_err("partial configuration after the backend started must be rejected");
27214 assert_eq!(
27215 error.to_string(),
27216 "the async runtime configuration is frozen; configure it before the first async call"
27217 );
27218 }
27219
27220 fn multi_thread_options(worker_threads: usize) -> RuntimeOptions {
27221 RuntimeOptions {
27222 flavor: RuntimeFlavor::MultiThread,
27223 worker_threads,
27224 max_blocking_tasks: worker_threads,
27225 thread_name_prefix: "pool-workers".to_string(),
27226 park_deadline: None,
27227 drain_linger: DEFAULT_DRAIN_LINGER,
27228 }
27229 }
27230
27231 #[test]
27232 fn pool_workers_follow_the_validated_flavor_and_worker_count() {
27233 let validate = |options: RuntimeOptions| {
27234 options
27235 .validate_with_rayon_max_threads(Some(MAX_ASYNC_RUNTIME_WORKER_THREADS))
27236 .expect("options must validate")
27237 };
27238 assert_eq!(pool_workers_for(&validate(multi_thread_options(3))), 3);
27239 assert_eq!(pool_workers_for(&validate(multi_thread_options(1))), 2);
27241 assert_eq!(
27243 pool_workers_for(&validate(RuntimeOptions {
27244 flavor: RuntimeFlavor::CurrentThread,
27245 ..multi_thread_options(8)
27246 })),
27247 0
27248 );
27249 }
27250
27251 #[cfg(napi_runtime_os_threads)]
27252 #[test]
27253 fn pool_workers_mirror_every_successful_configure() {
27254 let controller = RuntimeController::new();
27255 assert_eq!(
27256 controller.pool_workers(),
27257 pool_workers_for(&controller.options()),
27258 "the initial value mirrors the default options"
27259 );
27260
27261 controller
27262 .configure(multi_thread_options(3))
27263 .expect("configure before the backend exists must succeed");
27264 assert_eq!(controller.pool_workers(), 3);
27265
27266 controller
27267 .configure_partial(RuntimeOptionsPatch {
27268 worker_threads: Some(5),
27269 ..RuntimeOptionsPatch::default()
27270 })
27271 .expect("partial configure before the backend exists must succeed");
27272 assert_eq!(controller.pool_workers(), 5);
27273
27274 controller
27275 .configure_partial(RuntimeOptionsPatch {
27276 worker_threads: Some(1),
27277 ..RuntimeOptionsPatch::default()
27278 })
27279 .expect("a MultiThread count below two is clamped, not rejected");
27280 assert_eq!(controller.pool_workers(), 2);
27281
27282 controller
27283 .configure_partial(RuntimeOptionsPatch {
27284 flavor: Some(RuntimeFlavor::CurrentThread),
27285 ..RuntimeOptionsPatch::default()
27286 })
27287 .expect("switching to CurrentThread must succeed");
27288 assert_eq!(controller.pool_workers(), 0);
27289
27290 controller
27291 .configure(multi_thread_options(4))
27292 .expect("configure before the backend exists must succeed");
27293 assert_eq!(controller.pool_workers(), 4);
27294
27295 controller
27297 .configure(multi_thread_options(0))
27298 .expect_err("zero workers must be rejected");
27299 controller
27300 .configure_partial(RuntimeOptionsPatch {
27301 worker_threads: Some(0),
27302 ..RuntimeOptionsPatch::default()
27303 })
27304 .expect_err("zero workers must be rejected");
27305 assert_eq!(controller.pool_workers(), 4);
27306 }
27307
27308 #[test]
27309 fn pool_workers_survive_a_frozen_configure() {
27310 let controller = current_thread_controller("pool-workers-frozen");
27311 assert_eq!(controller.pool_workers(), 0);
27312 let _backend = controller.backend();
27313
27314 controller
27315 .configure(multi_thread_options(3))
27316 .expect_err("configure after the backend started must be rejected");
27317 controller
27318 .configure_partial(RuntimeOptionsPatch {
27319 flavor: Some(RuntimeFlavor::MultiThread),
27320 worker_threads: Some(3),
27321 ..RuntimeOptionsPatch::default()
27322 })
27323 .expect_err("partial configuration after the backend started must be rejected");
27324 assert_eq!(controller.pool_workers(), 0);
27325 assert_eq!(controller.options().flavor, RuntimeFlavor::CurrentThread);
27326 }
27327
27328 #[cfg(napi_runtime_os_threads)]
27329 #[test]
27330 fn pool_workers_drop_to_zero_once_the_backend_started() {
27331 let controller = RuntimeController::new();
27332 controller
27333 .configure(multi_thread_options(3))
27334 .expect("configure before the backend exists must succeed");
27335 assert_eq!(controller.pool_workers(), 3);
27336
27337 drop(controller.backend());
27339 assert_eq!(controller.pool_workers(), 0);
27340
27341 controller
27343 .shutdown()
27344 .expect("the first generation must stop");
27345 assert_eq!(controller.pool_workers(), 0);
27346 controller.start().expect("the runtime must restart");
27347 assert_eq!(controller.pool_workers(), 0);
27348 controller
27349 .shutdown()
27350 .expect("the restarted runtime must stop");
27351 }
27352
27353 fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
27354 let controller = RuntimeController::new();
27355 controller
27356 .configure(RuntimeOptions {
27357 flavor: RuntimeFlavor::CurrentThread,
27358 worker_threads: 1,
27359 max_blocking_tasks: 1,
27360 thread_name_prefix: thread_name_prefix.to_string(),
27361 park_deadline: None,
27362 drain_linger: DEFAULT_DRAIN_LINGER,
27363 })
27364 .expect("CurrentThread test configuration must be accepted");
27365 controller
27366 }
27367
27368 #[cfg(napi_runtime_os_threads)]
27369 fn multi_thread_controller(
27370 thread_name_prefix: &str,
27371 worker_threads: usize,
27372 max_blocking_tasks: usize,
27373 ) -> RuntimeController {
27374 let controller = RuntimeController::new();
27375 controller
27376 .configure(RuntimeOptions {
27377 flavor: RuntimeFlavor::MultiThread,
27378 worker_threads,
27379 max_blocking_tasks,
27380 thread_name_prefix: thread_name_prefix.to_string(),
27381 park_deadline: None,
27382 drain_linger: DEFAULT_DRAIN_LINGER,
27383 })
27384 .expect("MultiThread test configuration must be accepted");
27385 controller
27386 }
27387
27388 #[test]
27389 fn blocking_backend_creation_failure_preserves_diagnostic() {
27390 let controller = current_thread_controller("blocking-backend-creation-failure");
27391 let ran = Arc::new(AtomicBool::new(false));
27392 let work_ran = Arc::clone(&ran);
27393
27394 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27395 let Err((error, work)) = controller.try_spawn_blocking(move || {
27396 work_ran.store(true, Ordering::SeqCst);
27397 17usize
27398 }) else {
27399 panic!("the injected backend creation failure must reject blocking work");
27400 };
27401 assert_eq!(
27402 error.to_string(),
27403 "injected async runtime backend creation failure"
27404 );
27405 assert!(
27406 !ran.load(Ordering::SeqCst),
27407 "rejected blocking work must not run"
27408 );
27409 assert_eq!(
27410 work(),
27411 17,
27412 "the rejected blocking closure must remain usable"
27413 );
27414 assert!(ran.load(Ordering::SeqCst));
27415
27416 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27417 let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
27418 .expect_err("infallible blocking submission must return a failed join handle");
27419 assert_eq!(
27420 error.to_string(),
27421 "injected async runtime backend creation failure"
27422 );
27423
27424 let handle = controller
27425 .try_spawn_blocking(|| 31usize)
27426 .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
27427 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27428 controller.shutdown().unwrap();
27429 }
27430
27431 #[cfg(not(target_family = "wasm"))]
27432 #[test]
27433 fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
27434 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
27435
27436 if std::env::var_os(CHILD_ENV).is_some() {
27437 struct PollProbe {
27438 polls: Arc<AtomicUsize>,
27439 }
27440
27441 impl Future for PollProbe {
27442 type Output = ();
27443
27444 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27445 self.polls.fetch_add(1, Ordering::SeqCst);
27446 Poll::Ready(())
27447 }
27448 }
27449
27450 shutdown().expect("the isolated runtime must stop before block_on admission");
27451 let polls = Arc::new(AtomicUsize::new(0));
27452 let mut future = std::pin::pin!(PollProbe {
27453 polls: Arc::clone(&polls)
27454 });
27455 let error = try_block_on_dyn(future.as_mut())
27456 .expect_err("the stopped runtime must reject the borrowed future");
27457 assert_eq!(
27458 error.to_string(),
27459 "the async runtime is stopped; call start before submitting work"
27460 );
27461 assert_eq!(
27462 polls.load(Ordering::SeqCst),
27463 0,
27464 "rejected block_on must not poll its future"
27465 );
27466
27467 start().expect("the isolated runtime must restart");
27468 try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
27469 assert_eq!(polls.load(Ordering::SeqCst), 1);
27470 shutdown().expect("the restarted isolated runtime must stop");
27471 return;
27472 }
27473
27474 let output = std::process::Command::new(std::env::current_exe().unwrap())
27475 .arg("--exact")
27476 .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
27477 .arg("--nocapture")
27478 .env(CHILD_ENV, "1")
27479 .output()
27480 .expect("the stopped block_on subprocess must start");
27481 assert!(
27482 output.status.success(),
27483 "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
27484 output.status.code(),
27485 String::from_utf8_lossy(&output.stdout),
27486 String::from_utf8_lossy(&output.stderr)
27487 );
27488 }
27489
27490 #[cfg(not(target_family = "wasm"))]
27491 #[test]
27492 fn try_block_on_dyn_reports_concurrent_shutdown() {
27493 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
27494
27495 if std::env::var_os(CHILD_ENV).is_some() {
27496 use std::sync::mpsc;
27497
27498 struct PendingProbe {
27499 entered: Option<mpsc::Sender<()>>,
27500 complete: Arc<AtomicBool>,
27501 }
27502
27503 impl Future for PendingProbe {
27504 type Output = ();
27505
27506 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27507 if let Some(entered) = self.entered.take() {
27508 entered.send(()).unwrap();
27509 }
27510 if self.complete.load(Ordering::SeqCst) {
27511 Poll::Ready(())
27512 } else {
27513 Poll::Pending
27514 }
27515 }
27516 }
27517
27518 configure(RuntimeOptions {
27519 flavor: RuntimeFlavor::CurrentThread,
27520 worker_threads: 1,
27521 max_blocking_tasks: 1,
27522 thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
27523 park_deadline: None,
27524 drain_linger: DEFAULT_DRAIN_LINGER,
27525 })
27526 .expect("the isolated runtime must accept CurrentThread configuration");
27527 start().expect("the isolated runtime must start");
27528
27529 let (entered_tx, entered_rx) = mpsc::channel();
27530 let (error_tx, error_rx) = mpsc::channel();
27531 let (retry_tx, retry_rx) = mpsc::channel();
27532 let complete = Arc::new(AtomicBool::new(false));
27533 let future_complete = Arc::clone(&complete);
27534 let runner = std::thread::spawn(move || {
27535 let mut future = std::pin::pin!(PendingProbe {
27536 entered: Some(entered_tx),
27537 complete: future_complete
27538 });
27539 let error = try_block_on_dyn(future.as_mut())
27540 .expect_err("concurrent shutdown must interrupt the borrowed future")
27541 .to_string();
27542 error_tx.send(error).unwrap();
27543 retry_rx.recv().unwrap();
27544 try_block_on_dyn(future.as_mut())
27545 .expect("restart must accept the same interrupted borrowed future");
27546 });
27547 entered_rx
27548 .recv_timeout(Duration::from_secs(2))
27549 .expect("the borrowed future must be polled before shutdown");
27550 shutdown().expect("shutdown must wait for the active block_on scope to retire");
27551 assert_eq!(
27552 error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27553 "the async runtime stopped before block_on completed its future"
27554 );
27555 start().expect("the isolated runtime must restart after the interrupted drive");
27556 complete.store(true, Ordering::SeqCst);
27557 retry_tx.send(()).unwrap();
27558 runner.join().unwrap();
27559 shutdown().expect("the retried isolated runtime must stop");
27560 return;
27561 }
27562
27563 let output = std::process::Command::new(std::env::current_exe().unwrap())
27564 .arg("--exact")
27565 .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
27566 .arg("--nocapture")
27567 .env(CHILD_ENV, "1")
27568 .output()
27569 .expect("the concurrent block_on shutdown subprocess must start");
27570 assert!(
27571 output.status.success(),
27572 "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
27573 output.status.code(),
27574 String::from_utf8_lossy(&output.stdout),
27575 String::from_utf8_lossy(&output.stderr)
27576 );
27577 }
27578
27579 #[test]
27580 fn initial_lifecycle_start_preserves_lazy_configuration() {
27581 let controller = RuntimeController::new();
27582 controller
27583 .start()
27584 .expect("initial lifecycle start must succeed");
27585 controller
27586 .configure(RuntimeOptions {
27587 flavor: RuntimeFlavor::CurrentThread,
27588 worker_threads: 1,
27589 max_blocking_tasks: 1,
27590 thread_name_prefix: "lifecycle-initial-start".to_string(),
27591 park_deadline: None,
27592 drain_linger: DEFAULT_DRAIN_LINGER,
27593 })
27594 .expect("module-registration start must not consume the configuration window");
27595
27596 let state = controller
27597 .state
27598 .lock()
27599 .unwrap_or_else(std::sync::PoisonError::into_inner);
27600 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27601 }
27602
27603 #[test]
27604 fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
27605 let controller = RuntimeController::new();
27606
27607 for _ in 0..3 {
27608 controller
27609 .start()
27610 .expect("zero-work lifecycle start must succeed");
27611 assert!(matches!(
27612 &controller
27613 .state
27614 .lock()
27615 .unwrap_or_else(std::sync::PoisonError::into_inner)
27616 .lifecycle,
27617 RuntimeLifecycle::Initial
27618 ));
27619
27620 controller
27621 .shutdown()
27622 .expect("zero-work lifecycle shutdown must succeed");
27623 assert!(matches!(
27624 &controller
27625 .state
27626 .lock()
27627 .unwrap_or_else(std::sync::PoisonError::into_inner)
27628 .lifecycle,
27629 RuntimeLifecycle::StoppedBeforeFirstUse
27630 ));
27631 }
27632
27633 controller
27634 .start()
27635 .expect("a later zero-work restart must remain lazy");
27636 controller
27637 .configure(RuntimeOptions {
27638 flavor: RuntimeFlavor::CurrentThread,
27639 worker_threads: 1,
27640 max_blocking_tasks: 1,
27641 thread_name_prefix: "lifecycle-zero-work-config".to_string(),
27642 park_deadline: None,
27643 drain_linger: DEFAULT_DRAIN_LINGER,
27644 })
27645 .expect("zero-work lifecycle cycles must not consume the configuration window");
27646
27647 let state = controller
27648 .state
27649 .lock()
27650 .unwrap_or_else(std::sync::PoisonError::into_inner);
27651 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27652 }
27653
27654 #[cfg(napi_runtime_os_threads)]
27655 #[test]
27656 fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
27657 use std::sync::mpsc;
27658
27659 struct BlockingDropFuture {
27660 entered: mpsc::Sender<()>,
27661 release: mpsc::Receiver<()>,
27662 }
27663
27664 impl Future for BlockingDropFuture {
27665 type Output = ();
27666
27667 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27668 Poll::Pending
27669 }
27670 }
27671
27672 impl Drop for BlockingDropFuture {
27673 fn drop(&mut self) {
27674 self.entered.send(()).unwrap();
27675 self.release.recv().unwrap();
27676 }
27677 }
27678
27679 let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
27680 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27681 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27682 let submit_controller = Arc::clone(&controller);
27683 let submitter = std::thread::spawn(move || {
27684 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27685 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27686 entered: drop_entered_tx,
27687 release: release_drop_rx,
27688 }));
27689 assert!(
27690 result.is_err(),
27691 "the injected backend creation failure must reject the submission"
27692 );
27693 });
27694 drop_entered_rx
27695 .recv_timeout(Duration::from_secs(2))
27696 .expect("the rejected future destructor must start");
27697
27698 let retry_ran = Arc::new(AtomicBool::new(false));
27699 let retry_ran_task = Arc::clone(&retry_ran);
27700 let Err(retry) = controller.try_spawn_detached(async move {
27701 retry_ran_task.store(true, Ordering::SeqCst);
27702 }) else {
27703 panic!("backend creation must remain closed during rejected destruction");
27704 };
27705 drop(retry);
27706 assert!(!retry_ran.load(Ordering::SeqCst));
27707
27708 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27709 let shutdown_controller = Arc::clone(&controller);
27710 let shutdown = std::thread::spawn(move || {
27711 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27712 });
27713 wait_until("initial shutdown to close runtime admission", || {
27714 matches!(
27715 controller
27716 .state
27717 .lock()
27718 .unwrap_or_else(std::sync::PoisonError::into_inner)
27719 .lifecycle,
27720 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
27721 )
27722 });
27723 assert!(
27724 shutdown_rx
27725 .recv_timeout(Duration::from_millis(200))
27726 .is_err(),
27727 "initial shutdown must wait for rejected user state destruction"
27728 );
27729 assert!(
27730 controller.try_spawn_detached(async {}).is_err(),
27731 "shutdown must keep first-generation admission closed while it waits"
27732 );
27733
27734 let (start_tx, start_rx) = mpsc::channel();
27735 let start_controller = Arc::clone(&controller);
27736 let starter = std::thread::spawn(move || {
27737 start_tx.send(start_controller.start()).unwrap();
27738 });
27739 assert!(
27740 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27741 "start must wait for the zero-backend shutdown transition to finish"
27742 );
27743
27744 release_drop_tx.send(()).unwrap();
27745 submitter.join().unwrap();
27746 shutdown_rx
27747 .recv_timeout(Duration::from_secs(2))
27748 .unwrap()
27749 .unwrap();
27750 shutdown.join().unwrap();
27751 start_rx
27752 .recv_timeout(Duration::from_secs(2))
27753 .unwrap()
27754 .unwrap();
27755 starter.join().unwrap();
27756
27757 let ran = Arc::new(AtomicBool::new(false));
27758 let ran_task = Arc::clone(&ran);
27759 controller
27760 .try_spawn_detached(async move {
27761 ran_task.store(true, Ordering::SeqCst);
27762 })
27763 .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
27764 let dispatch = controller_current_thread_dispatch(&controller);
27765 controller.drive_current_thread_dispatch(dispatch, false);
27766 assert!(ran.load(Ordering::SeqCst));
27767 controller.shutdown().unwrap();
27768 }
27769
27770 #[cfg(napi_runtime_os_threads)]
27771 #[test]
27772 fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
27773 use std::sync::mpsc;
27774
27775 struct BlockingDropFuture {
27776 entered: mpsc::Sender<()>,
27777 release: mpsc::Receiver<()>,
27778 }
27779
27780 impl Future for BlockingDropFuture {
27781 type Output = ();
27782
27783 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27784 Poll::Pending
27785 }
27786 }
27787
27788 impl Drop for BlockingDropFuture {
27789 fn drop(&mut self) {
27790 self.entered.send(()).unwrap();
27791 self.release.recv().unwrap();
27792 }
27793 }
27794
27795 let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
27796 let old_generation = controller.backend().generation();
27797 controller.shutdown().unwrap();
27798
27799 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27800 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27801 let submit_controller = Arc::clone(&controller);
27802 let submitter = std::thread::spawn(move || {
27803 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27804 entered: drop_entered_tx,
27805 release: release_drop_rx,
27806 }));
27807 assert!(
27808 result.is_err(),
27809 "the stopped runtime must reject the submission"
27810 );
27811 });
27812 drop_entered_rx
27813 .recv_timeout(Duration::from_secs(2))
27814 .unwrap();
27815
27816 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27817 let shutdown_controller = Arc::clone(&controller);
27818 let shutdown = std::thread::spawn(move || {
27819 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27820 });
27821 wait_until(
27822 "stopped shutdown to enter its zero-backend transition",
27823 || {
27824 matches!(
27825 controller
27826 .state
27827 .lock()
27828 .unwrap_or_else(std::sync::PoisonError::into_inner)
27829 .lifecycle,
27830 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
27831 )
27832 },
27833 );
27834
27835 let (start_tx, start_rx) = mpsc::channel();
27836 let start_controller = Arc::clone(&controller);
27837 let starter = std::thread::spawn(move || {
27838 start_tx.send(start_controller.start()).unwrap();
27839 });
27840 assert!(
27841 shutdown_rx
27842 .recv_timeout(Duration::from_millis(200))
27843 .is_err(),
27844 "stopped shutdown must wait for rejected destruction"
27845 );
27846 assert!(
27847 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27848 "restart must wait for the stopped zero-backend transition"
27849 );
27850
27851 release_drop_tx.send(()).unwrap();
27852 submitter.join().unwrap();
27853 shutdown_rx
27854 .recv_timeout(Duration::from_secs(2))
27855 .unwrap()
27856 .unwrap();
27857 shutdown.join().unwrap();
27858 start_rx
27859 .recv_timeout(Duration::from_secs(2))
27860 .unwrap()
27861 .unwrap();
27862 starter.join().unwrap();
27863
27864 let new_generation = controller.backend().generation();
27865 assert_ne!(new_generation, old_generation);
27866 controller.shutdown().unwrap();
27867 }
27868
27869 #[test]
27870 fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
27871 use std::sync::mpsc;
27872
27873 struct ShutdownOnDrop {
27874 controller: Arc<RuntimeController>,
27875 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27876 }
27877
27878 impl Future for ShutdownOnDrop {
27879 type Output = ();
27880
27881 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27882 Poll::Pending
27883 }
27884 }
27885
27886 impl Drop for ShutdownOnDrop {
27887 fn drop(&mut self) {
27888 self.result.send(self.controller.shutdown()).unwrap();
27889 }
27890 }
27891
27892 let controller = Arc::new(current_thread_controller(
27893 "failed-initial-reentrant-shutdown",
27894 ));
27895 let (result_tx, result_rx) = mpsc::channel();
27896 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27897 let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
27898 controller: Arc::clone(&controller),
27899 result: result_tx,
27900 }));
27901 assert!(rejected.is_err());
27902
27903 let error = result_rx
27904 .recv_timeout(Duration::from_secs(1))
27905 .expect("the rejected destructor must not self-deadlock")
27906 .expect_err("rejected destruction cannot wait for its own shutdown");
27907 assert!(error.to_string().contains("rejected submission"));
27908 {
27909 let state = controller
27910 .state
27911 .lock()
27912 .unwrap_or_else(std::sync::PoisonError::into_inner);
27913 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27914 assert_eq!(state.rejected_drops, 0);
27915 }
27916
27917 let handle = controller
27918 .try_spawn(async { 23usize })
27919 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27920 let dispatch = controller_current_thread_dispatch(&controller);
27921 controller.drive_current_thread_dispatch(dispatch, false);
27922 assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
27923 controller.shutdown().unwrap();
27924 }
27925
27926 #[cfg(napi_runtime_os_threads)]
27927 #[test]
27928 fn failed_initial_submission_destructor_cannot_reenter_start() {
27929 use std::sync::mpsc;
27930
27931 struct StartOnDrop {
27932 controller: Arc<RuntimeController>,
27933 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27934 release: mpsc::Receiver<()>,
27935 }
27936
27937 impl Future for StartOnDrop {
27938 type Output = ();
27939
27940 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27941 Poll::Pending
27942 }
27943 }
27944
27945 impl Drop for StartOnDrop {
27946 fn drop(&mut self) {
27947 self.result.send(self.controller.start()).unwrap();
27948 self.release.recv().unwrap();
27949 }
27950 }
27951
27952 let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
27953 let (result_tx, result_rx) = mpsc::channel();
27954 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27955 let (submission_tx, submission_rx) = mpsc::channel();
27956 let submit_controller = Arc::clone(&controller);
27957 let submitter = std::thread::spawn(move || {
27958 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27959 let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
27960 controller: Arc::clone(&submit_controller),
27961 result: result_tx,
27962 release: release_drop_rx,
27963 }));
27964 submission_tx.send(rejected).unwrap();
27965 });
27966
27967 let error = result_rx
27968 .recv_timeout(Duration::from_secs(1))
27969 .expect("the rejected destructor must not self-deadlock")
27970 .expect_err("rejected destruction must not report a successful runtime start");
27971 assert!(error.to_string().contains("rejected submission"));
27972 {
27973 let state = controller
27974 .state
27975 .lock()
27976 .unwrap_or_else(std::sync::PoisonError::into_inner);
27977 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27978 assert_eq!(state.rejected_drops, 1);
27979 }
27980
27981 let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
27982 let (external_start_tx, external_start_rx) = mpsc::channel();
27983 let start_controller = Arc::clone(&controller);
27984 let starter = std::thread::spawn(move || {
27985 BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
27986 *slot.borrow_mut() = Some(Box::new(move || {
27987 wait_entered_tx.send(()).unwrap();
27988 }));
27989 });
27990 external_start_tx.send(start_controller.start()).unwrap();
27991 });
27992 wait_entered_rx
27993 .recv_timeout(Duration::from_secs(1))
27994 .expect("external start must reach the Initial rejected-destruction wait");
27995 assert!(
27996 external_start_rx.try_recv().is_err(),
27997 "external start must not return while rejected destruction remains active"
27998 );
27999
28000 release_drop_tx.send(()).unwrap();
28001 assert!(
28002 submission_rx
28003 .recv_timeout(Duration::from_secs(2))
28004 .unwrap()
28005 .is_err()
28006 );
28007 submitter.join().unwrap();
28008 external_start_rx
28009 .recv_timeout(Duration::from_secs(2))
28010 .unwrap()
28011 .unwrap();
28012 starter.join().unwrap();
28013
28014 {
28015 let state = controller
28016 .state
28017 .lock()
28018 .unwrap_or_else(std::sync::PoisonError::into_inner);
28019 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
28020 assert_eq!(state.rejected_drops, 0);
28021 }
28022
28023 let handle = controller
28024 .try_spawn(async { 31usize })
28025 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
28026 let dispatch = controller_current_thread_dispatch(&controller);
28027 controller.drive_current_thread_dispatch(dispatch, false);
28028 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
28029 controller.shutdown().unwrap();
28030 }
28031
28032 #[cfg(panic = "unwind")]
28033 #[test]
28034 fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
28035 use std::sync::mpsc;
28036
28037 struct BlockingDropFuture {
28038 controller: Arc<RuntimeController>,
28039 entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28040 release: mpsc::Receiver<()>,
28041 }
28042
28043 impl Future for BlockingDropFuture {
28044 type Output = ();
28045
28046 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28047 Poll::Pending
28048 }
28049 }
28050
28051 impl Drop for BlockingDropFuture {
28052 fn drop(&mut self) {
28053 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
28054 self
28055 .entered
28056 .send((generation, self.controller.shutdown()))
28057 .unwrap();
28058 self.release.recv().unwrap();
28059 }
28060 }
28061
28062 let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
28063 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28064 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28065 let (runner_done_tx, runner_done_rx) = mpsc::channel();
28066 let runner_controller = Arc::clone(&controller);
28067 let runner = std::thread::spawn(move || {
28068 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
28069 let result = catch_unwind(AssertUnwindSafe(|| {
28070 block_on_with_controller(
28071 &runner_controller,
28072 BlockingDropFuture {
28073 controller: Arc::clone(&runner_controller),
28074 entered: drop_entered_tx,
28075 release: release_drop_rx,
28076 },
28077 );
28078 }));
28079 runner_done_tx.send(result.is_err()).unwrap();
28080 });
28081
28082 let (generation, reentrant_shutdown) = drop_entered_rx
28083 .recv_timeout(Duration::from_secs(2))
28084 .expect("failed block_on acquisition must destroy its owned future");
28085 assert_eq!(generation, None, "no runtime generation was created");
28086 let error = reentrant_shutdown
28087 .expect_err("rejected block_on destruction cannot wait for its own shutdown");
28088 assert!(error.to_string().contains("rejected submission"));
28089
28090 let Err(retry_error) = controller.try_backend() else {
28091 panic!("backend retry must remain closed during rejected block_on destruction");
28092 };
28093 assert!(retry_error.to_string().contains("rejected submission"));
28094
28095 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28096 let shutdown_controller = Arc::clone(&controller);
28097 let shutdown = std::thread::spawn(move || {
28098 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28099 });
28100 assert!(
28101 shutdown_rx
28102 .recv_timeout(Duration::from_millis(200))
28103 .is_err(),
28104 "initial shutdown must wait for rejected block_on destruction"
28105 );
28106
28107 release_drop_tx.send(()).unwrap();
28108 assert!(
28109 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
28110 "block_on must preserve its infallible panic contract after contained cleanup"
28111 );
28112 runner.join().unwrap();
28113 shutdown_rx
28114 .recv_timeout(Duration::from_secs(2))
28115 .unwrap()
28116 .unwrap();
28117 shutdown.join().unwrap();
28118
28119 controller
28120 .start()
28121 .expect("zero-generation shutdown must remain restartable");
28122 let ran = Arc::new(AtomicBool::new(false));
28123 let ran_task = Arc::clone(&ran);
28124 controller
28125 .try_spawn_detached(async move {
28126 ran_task.store(true, Ordering::SeqCst);
28127 })
28128 .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
28129 let dispatch = controller_current_thread_dispatch(&controller);
28130 controller.drive_current_thread_dispatch(dispatch, false);
28131 assert!(ran.load(Ordering::SeqCst));
28132 controller.shutdown().unwrap();
28133 }
28134
28135 #[cfg(not(target_family = "wasm"))]
28136 #[test]
28137 fn public_block_on_contains_poll_and_drop_double_panic() {
28138 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
28139
28140 if std::env::var_os(CHILD_ENV).is_some() {
28141 struct PanicInPollAndDrop;
28142
28143 impl Future for PanicInPollAndDrop {
28144 type Output = ();
28145
28146 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28147 panic!("intentional block_on poll panic");
28148 }
28149 }
28150
28151 impl Drop for PanicInPollAndDrop {
28152 fn drop(&mut self) {
28153 panic!("intentional block_on future destructor panic");
28154 }
28155 }
28156
28157 let controller = current_thread_controller("block-on-double-panic");
28158 assert!(
28159 catch_unwind(AssertUnwindSafe(|| {
28160 block_on_with_controller(&controller, PanicInPollAndDrop);
28161 }))
28162 .is_err(),
28163 "the original poll panic must still reach the caller"
28164 );
28165 controller
28166 .shutdown()
28167 .expect("contained future destruction must not strand the runtime");
28168 return;
28169 }
28170
28171 let output = std::process::Command::new(std::env::current_exe().unwrap())
28172 .arg("--exact")
28173 .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
28174 .arg("--nocapture")
28175 .env(CHILD_ENV, "1")
28176 .output()
28177 .expect("the subprocess test must start");
28178 assert!(
28179 output.status.success(),
28180 "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
28181 output.status.code(),
28182 String::from_utf8_lossy(&output.stdout),
28183 String::from_utf8_lossy(&output.stderr)
28184 );
28185 }
28186
28187 #[test]
28188 fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
28189 let controller = RuntimeController::new();
28190 controller
28191 .start()
28192 .expect("initial lifecycle start must succeed");
28193 controller
28194 .shutdown()
28195 .expect("zero-work lifecycle shutdown must succeed");
28196 controller
28197 .start()
28198 .expect("zero-work lifecycle restart must remain lazy");
28199 controller
28200 .configure(RuntimeOptions {
28201 flavor: RuntimeFlavor::CurrentThread,
28202 worker_threads: 1,
28203 max_blocking_tasks: 1,
28204 thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
28205 park_deadline: None,
28206 drain_linger: DEFAULT_DRAIN_LINGER,
28207 })
28208 .expect("configuration before first real async use must succeed");
28209
28210 let first_generation = controller.backend().generation();
28211 controller
28212 .shutdown()
28213 .expect("the first real generation must stop");
28214 assert!(matches!(
28215 &controller
28216 .state
28217 .lock()
28218 .unwrap_or_else(std::sync::PoisonError::into_inner)
28219 .lifecycle,
28220 RuntimeLifecycle::Stopped
28221 ));
28222 let error = controller
28223 .configure(RuntimeOptions::default())
28224 .expect_err("a real generation must permanently freeze configuration");
28225 assert_eq!(
28226 error.to_string(),
28227 "the async runtime configuration is frozen; configure it before the first async call"
28228 );
28229
28230 controller
28231 .start()
28232 .expect("the stopped real generation must restart");
28233 let restarted_generation = {
28234 let state = controller
28235 .state
28236 .lock()
28237 .unwrap_or_else(std::sync::PoisonError::into_inner);
28238 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
28239 panic!("a real generation restart must eagerly create its replacement backend");
28240 };
28241 backend.generation()
28242 };
28243 assert_ne!(
28244 restarted_generation, first_generation,
28245 "a real generation restart must not reuse the retired backend"
28246 );
28247 controller
28248 .shutdown()
28249 .expect("the replacement generation must stop");
28250 }
28251
28252 #[test]
28253 fn runtime_controller_rejects_submissions_after_shutdown() {
28254 let controller = current_thread_controller("lifecycle-reject");
28255 controller.start().expect("runtime must start");
28256 drop(controller.backend());
28257 controller.shutdown().expect("runtime must stop");
28258
28259 let async_ran = Arc::new(AtomicBool::new(false));
28260 let async_ran_task = Arc::clone(&async_ran);
28261 let Err(rejected_future) = controller.try_spawn_detached(async move {
28262 async_ran_task.store(true, Ordering::SeqCst);
28263 }) else {
28264 panic!("shutdown must reject async work");
28265 };
28266 assert!(
28267 !async_ran.load(Ordering::SeqCst),
28268 "a rejected future must not be polled"
28269 );
28270 futures::executor::block_on(rejected_future);
28271 assert!(
28272 async_ran.load(Ordering::SeqCst),
28273 "the original future must remain usable"
28274 );
28275
28276 let blocking_ran = Arc::new(AtomicBool::new(false));
28277 let blocking_ran_task = Arc::clone(&blocking_ran);
28278 let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
28279 blocking_ran_task.store(true, Ordering::SeqCst);
28280 7
28281 }) else {
28282 panic!("shutdown must reject blocking work");
28283 };
28284 assert_eq!(
28285 blocking_error.to_string(),
28286 "the async runtime is stopped; call start before submitting work"
28287 );
28288 assert!(
28289 !blocking_ran.load(Ordering::SeqCst),
28290 "rejected blocking work must not run"
28291 );
28292 assert_eq!(
28293 rejected_work(),
28294 7,
28295 "the original closure must remain usable"
28296 );
28297
28298 let config_error = controller
28299 .configure(RuntimeOptions::default())
28300 .expect_err("shutdown must not make runtime configuration mutable again");
28301 assert_eq!(
28302 config_error.to_string(),
28303 "the async runtime configuration is frozen; configure it before the first async call"
28304 );
28305
28306 let state = controller
28307 .state
28308 .lock()
28309 .unwrap_or_else(std::sync::PoisonError::into_inner);
28310 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
28311 }
28312
28313 #[test]
28314 fn rejected_future_can_be_resubmitted_after_restart() {
28315 let controller = current_thread_controller("lifecycle-retry-future");
28316 controller.start().expect("runtime must start");
28317 drop(controller.backend());
28318 controller.shutdown().expect("runtime must stop");
28319
28320 let runs = Arc::new(AtomicUsize::new(0));
28321 let runs_task = Arc::clone(&runs);
28322 let future = async move {
28323 runs_task.fetch_add(1, Ordering::SeqCst);
28324 23
28325 };
28326 let Err((error, future)) = controller.try_spawn(future) else {
28327 panic!("stopped runtime must reject the future");
28328 };
28329 assert!(error.to_string().contains("runtime is stopped"));
28330 assert_eq!(runs.load(Ordering::SeqCst), 0);
28331
28332 controller.start().expect("runtime must restart");
28333 let handle = controller
28334 .try_spawn(future)
28335 .unwrap_or_else(|_| panic!("restart must accept retained future"));
28336 let dispatch = controller_current_thread_dispatch(&controller);
28337 controller.drive_current_thread_dispatch(dispatch, false);
28338 assert_eq!(
28339 futures::executor::block_on(handle).expect("retained future must complete"),
28340 23
28341 );
28342 assert_eq!(runs.load(Ordering::SeqCst), 1);
28343 controller
28344 .shutdown()
28345 .expect("replacement runtime must stop");
28346 }
28347
28348 #[test]
28349 fn runtime_controller_start_after_shutdown_accepts_work() {
28350 let controller = current_thread_controller("lifecycle-restart");
28351 controller.start().expect("runtime must start");
28352 drop(controller.backend());
28353 controller.shutdown().expect("runtime must stop");
28354 controller.start().expect("runtime must restart");
28355
28356 let async_ran = Arc::new(AtomicBool::new(false));
28357 let async_ran_task = Arc::clone(&async_ran);
28358 controller
28359 .try_spawn_detached(async move {
28360 async_ran_task.store(true, Ordering::SeqCst);
28361 })
28362 .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
28363 assert!(
28364 !async_ran.load(Ordering::SeqCst),
28365 "hostless CurrentThread submission must remain enqueue-only"
28366 );
28367 let dispatch = controller_current_thread_dispatch(&controller);
28368 controller.drive_current_thread_dispatch(dispatch, false);
28369 assert!(
28370 async_ran.load(Ordering::SeqCst),
28371 "restarted runtime must poll submitted work"
28372 );
28373
28374 let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
28375 panic!("restarted runtime must accept blocking work");
28376 };
28377 assert_eq!(
28378 futures::executor::block_on(handle).expect("blocking work must complete after restart"),
28379 11
28380 );
28381 }
28382
28383 #[test]
28384 fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
28385 let controller = current_thread_controller("stale-host-generation");
28386 let old_generation = controller.backend().generation();
28387 let old_dispatch = controller_current_thread_dispatch(&controller);
28388 controller.shutdown().unwrap();
28389 controller.start().unwrap();
28390 let new_generation = controller.backend().generation();
28391 assert_ne!(new_generation, old_generation);
28392
28393 let ran = Arc::new(AtomicBool::new(false));
28394 let ran_task = Arc::clone(&ran);
28395 controller
28396 .try_spawn_detached(async move {
28397 ran_task.store(true, Ordering::SeqCst);
28398 })
28399 .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
28400 let new_dispatch = controller_current_thread_dispatch(&controller);
28401 assert_ne!(new_dispatch, old_dispatch);
28402
28403 controller.drive_current_thread_dispatch(old_dispatch, false);
28404 assert!(
28405 !ran.load(Ordering::SeqCst),
28406 "a callback from the retired generation must not touch the restarted executor"
28407 );
28408 controller.drive_current_thread_dispatch(new_dispatch, false);
28409 assert!(ran.load(Ordering::SeqCst));
28410 controller.shutdown().unwrap();
28411 }
28412
28413 #[test]
28414 fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
28415 let controller = current_thread_controller("stale-host-cancellation");
28416 let old_generation = controller.backend().generation();
28417 let old_dispatch = controller_current_thread_dispatch(&controller);
28418 controller.shutdown().unwrap();
28419 controller.start().unwrap();
28420 let backend = controller.backend();
28421 let new_generation = backend.generation();
28422 assert_ne!(new_generation, old_generation);
28423 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28424 panic!("configured CurrentThread backend must create a CurrentThread executor");
28425 };
28426 let new_dispatch = current_thread_dispatch(executor);
28427 assert_ne!(new_dispatch, old_dispatch);
28428
28429 controller.cancel_current_thread_dispatch_for_test(old_dispatch);
28430 assert_eq!(
28431 executor.dispatch_pending.load(Ordering::Acquire),
28432 new_dispatch,
28433 "a delayed failure from the retired generation must not release the replacement latch"
28434 );
28435
28436 controller.cancel_current_thread_dispatch_for_test(new_dispatch);
28437 assert_eq!(
28438 executor.dispatch_pending.load(Ordering::Acquire),
28439 0,
28440 "the live generation must be able to cancel its accepted dispatch"
28441 );
28442 drop(backend);
28443 controller.shutdown().unwrap();
28444 }
28445
28446 #[cfg(napi_runtime_os_threads)]
28447 #[test]
28448 fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
28449 use std::sync::mpsc;
28450
28451 type ProbeResult = (
28452 &'static str,
28453 Option<u64>,
28454 Result<(), String>,
28455 Result<(), String>,
28456 Result<(), String>,
28457 );
28458
28459 struct StaleGenerationProbe {
28460 label: &'static str,
28461 controller: Arc<RuntimeController>,
28462 result: mpsc::Sender<ProbeResult>,
28463 }
28464
28465 impl Drop for StaleGenerationProbe {
28466 fn drop(&mut self) {
28467 let generation = active_runtime_generation();
28468 let submission = match self.controller.try_spawn(async {}) {
28469 Ok(handle) => {
28470 drop(handle);
28471 Ok(())
28472 }
28473 Err((error, future)) => {
28474 drop(future);
28475 Err(error.to_string())
28476 }
28477 };
28478 let start = self.controller.start().map_err(|error| error.to_string());
28479 let shutdown = self
28480 .controller
28481 .shutdown()
28482 .map_err(|error| error.to_string());
28483 self
28484 .result
28485 .send((self.label, generation, submission, start, shutdown))
28486 .unwrap();
28487 }
28488 }
28489
28490 let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
28491 let old_generation = controller.backend().generation();
28492 let (result_tx, result_rx) = mpsc::channel();
28493
28494 let async_controller = Arc::clone(&controller);
28495 let async_result = result_tx.clone();
28496 let async_handle = controller
28497 .try_spawn(async move {
28498 StaleGenerationProbe {
28499 label: "async",
28500 controller: async_controller,
28501 result: async_result,
28502 }
28503 })
28504 .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
28505 let old_dispatch = controller_current_thread_dispatch(&controller);
28506 controller.drive_current_thread_dispatch(old_dispatch, false);
28507
28508 let ready_controller = Arc::clone(&controller);
28509 let ready_handle = controller
28510 .try_spawn_blocking(move || StaleGenerationProbe {
28511 label: "ready",
28512 controller: ready_controller,
28513 result: result_tx,
28514 })
28515 .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
28516
28517 controller.shutdown().unwrap();
28518 controller.start().unwrap();
28519 let new_generation = controller.backend().generation();
28520 assert_ne!(new_generation, old_generation);
28521
28522 drop(async_handle);
28523 let async_result = result_rx
28524 .recv_timeout(Duration::from_secs(2))
28525 .expect("the retained async output must be destroyed without waiting on the replacement");
28526 drop(ready_handle);
28527 let ready_result = result_rx
28528 .recv_timeout(Duration::from_secs(2))
28529 .expect("the retained immediate output must be destroyed without touching the replacement");
28530
28531 for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
28532 let (label, generation, submission, start, shutdown) = result;
28533 assert_eq!(label, expected_label);
28534 assert_eq!(generation, Some(old_generation));
28535 for error in [submission, start, shutdown] {
28536 assert!(
28537 error
28538 .expect_err("retired-generation output destruction must be rejected")
28539 .contains("retired generation")
28540 );
28541 }
28542 }
28543
28544 let survivor = controller
28545 .try_spawn(async { 29usize })
28546 .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
28547 let new_dispatch = controller_current_thread_dispatch(&controller);
28548 controller.drive_current_thread_dispatch(new_dispatch, false);
28549 assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
28550 controller.shutdown().unwrap();
28551 }
28552
28553 #[cfg(napi_runtime_os_threads)]
28554 #[test]
28555 fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
28556 use std::sync::mpsc;
28557
28558 struct ShutdownOnDrop {
28559 controller: Arc<RuntimeController>,
28560 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28561 }
28562
28563 impl Drop for ShutdownOnDrop {
28564 fn drop(&mut self) {
28565 self
28566 .result
28567 .send((active_runtime_generation(), self.controller.shutdown()))
28568 .unwrap();
28569 }
28570 }
28571
28572 let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
28573 let old_generation = controller.backend().generation();
28574 let (result_tx, result_rx) = mpsc::channel();
28575 let old_handle = controller
28576 .try_spawn_blocking({
28577 let controller = Arc::clone(&controller);
28578 move || ShutdownOnDrop {
28579 controller,
28580 result: result_tx,
28581 }
28582 })
28583 .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
28584 wait_until(
28585 "the first-generation blocking result to be buffered",
28586 || {
28587 controller
28588 .metrics
28589 .blocking_tasks_completed
28590 .load(Ordering::Acquire)
28591 == 1
28592 },
28593 );
28594 controller.shutdown().unwrap();
28595 controller.start().unwrap();
28596 let new_generation = controller.backend().generation();
28597 assert_ne!(new_generation, old_generation);
28598
28599 let (holder_started_tx, holder_started_rx) = mpsc::channel();
28600 let (release_holder_tx, release_holder_rx) = mpsc::channel();
28601 let holder = controller
28602 .try_spawn_blocking(move || {
28603 holder_started_tx.send(()).unwrap();
28604 release_holder_rx.recv().unwrap();
28605 })
28606 .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
28607 holder_started_rx
28608 .recv_timeout(Duration::from_secs(2))
28609 .unwrap();
28610
28611 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28612 let shutdown_controller = Arc::clone(&controller);
28613 let shutdown = std::thread::spawn(move || {
28614 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28615 });
28616 wait_until("the replacement generation to enter Stopping", || {
28617 matches!(
28618 controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
28619 RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
28620 )
28621 });
28622
28623 let dropper = std::thread::spawn(move || drop(old_handle));
28624 let (observed_generation, reentrant_shutdown) =
28625 match result_rx.recv_timeout(Duration::from_secs(2)) {
28626 Ok(result) => result,
28627 Err(error) => {
28628 release_holder_tx.send(()).unwrap();
28629 let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
28630 let _ = dropper.join();
28631 let _ = shutdown.join();
28632 panic!("retained first-generation output waited on replacement shutdown: {error}");
28633 }
28634 };
28635 assert_eq!(observed_generation, Some(old_generation));
28636 let error = reentrant_shutdown
28637 .expect_err("a retained first-generation output must not control replacement shutdown");
28638 assert!(error.to_string().contains("retired generation"));
28639
28640 release_holder_tx.send(()).unwrap();
28641 shutdown_rx
28642 .recv_timeout(Duration::from_secs(2))
28643 .unwrap()
28644 .unwrap();
28645 dropper.join().unwrap();
28646 shutdown.join().unwrap();
28647 futures::executor::block_on(holder).unwrap();
28648 }
28649
28650 #[cfg(napi_runtime_os_threads)]
28651 #[test]
28652 fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
28653 use std::sync::mpsc;
28654
28655 let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
28656 let generation = controller.backend().generation();
28657 let dispatch = controller_current_thread_dispatch(&controller);
28658 let (admitted_tx, admitted_rx) = mpsc::channel();
28659 let (release_tx, release_rx) = mpsc::channel();
28660 let driver_controller = Arc::clone(&controller);
28661 let driver = std::thread::spawn(move || {
28662 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
28663 *slot.borrow_mut() = Some(Box::new(move || {
28664 admitted_tx.send(()).unwrap();
28665 release_rx.recv().unwrap();
28666 }));
28667 });
28668 driver_controller.drive_current_thread_dispatch(dispatch, false);
28669 });
28670 admitted_rx
28671 .recv_timeout(Duration::from_secs(2))
28672 .expect("the host turn must claim its scheduler role before execution");
28673
28674 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28675 let shutdown_controller = Arc::clone(&controller);
28676 let shutdown = std::thread::spawn(move || {
28677 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28678 });
28679 wait_until(
28680 "shutdown to close the admitted CurrentThread generation",
28681 || {
28682 let state = controller
28683 .state
28684 .lock()
28685 .unwrap_or_else(std::sync::PoisonError::into_inner);
28686 matches!(
28687 &state.lifecycle,
28688 RuntimeLifecycle::Stopping(identity) if identity.generation == generation
28689 )
28690 },
28691 );
28692 assert!(
28693 shutdown_rx
28694 .recv_timeout(Duration::from_millis(200))
28695 .is_err(),
28696 "shutdown must wait for an admitted host callback even before it starts draining"
28697 );
28698
28699 release_tx.send(()).unwrap();
28700 driver.join().unwrap();
28701 shutdown_rx
28702 .recv_timeout(Duration::from_secs(2))
28703 .unwrap()
28704 .unwrap();
28705 shutdown.join().unwrap();
28706 }
28707
28708 #[cfg(napi_runtime_os_threads)]
28709 #[test]
28710 fn spawn_racing_shutdown_cannot_recreate_backend() {
28711 use std::sync::mpsc;
28712
28713 let controller = Arc::new(current_thread_controller("lifecycle-race"));
28714 controller.start().expect("runtime must start");
28715 drop(controller.backend());
28716
28717 let (at_hook_tx, at_hook_rx) = mpsc::channel();
28718 let (release_tx, release_rx) = mpsc::channel();
28719 let ran = Arc::new(AtomicBool::new(false));
28720 let ran_task = Arc::clone(&ran);
28721 let worker_controller = Arc::clone(&controller);
28722 let worker = std::thread::spawn(move || {
28723 BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
28724 *slot.borrow_mut() = Some(Box::new(move || {
28725 at_hook_tx
28726 .send(())
28727 .expect("test coordinator must still be listening");
28728 release_rx
28729 .recv()
28730 .expect("test coordinator must release the submission");
28731 }));
28732 });
28733
28734 let future = async move {
28735 ran_task.store(true, Ordering::SeqCst);
28736 };
28737 match worker_controller.try_spawn_detached(future) {
28738 Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
28739 Err(future) => drop(future),
28740 }
28741 });
28742
28743 at_hook_rx
28744 .recv_timeout(Duration::from_secs(5))
28745 .expect("submitting thread must stop before taking the controller lock");
28746 controller.shutdown().expect("runtime must stop");
28747 release_tx
28748 .send(())
28749 .expect("submitting thread must still be waiting");
28750 worker.join().expect("submitting thread must not panic");
28751
28752 assert!(
28753 !ran.load(Ordering::SeqCst),
28754 "the rejected task must never run"
28755 );
28756 let state = controller
28757 .state
28758 .lock()
28759 .unwrap_or_else(std::sync::PoisonError::into_inner);
28760 assert!(
28761 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
28762 "the rejected submission must not lazily recreate a backend"
28763 );
28764 }
28765
28766 #[cfg(napi_runtime_os_threads)]
28767 #[test]
28768 fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
28769 use std::sync::mpsc;
28770
28771 struct BlockingDropFuture {
28772 work: Arc<GenerationWork>,
28773 entered: mpsc::Sender<(Option<u64>, usize)>,
28774 release: mpsc::Receiver<()>,
28775 }
28776
28777 impl Future for BlockingDropFuture {
28778 type Output = ();
28779
28780 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28781 Poll::Pending
28782 }
28783 }
28784
28785 impl Drop for BlockingDropFuture {
28786 fn drop(&mut self) {
28787 let active = self
28788 .work
28789 .state
28790 .lock()
28791 .unwrap_or_else(std::sync::PoisonError::into_inner)
28792 .active;
28793 self
28794 .entered
28795 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28796 .unwrap();
28797 self.release.recv().unwrap();
28798 }
28799 }
28800
28801 let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
28802 let backend = controller.backend();
28803 let old_generation = backend.generation();
28804 let work = Arc::clone(&backend.work);
28805 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28806 panic!("configured CurrentThread backend must create a CurrentThread executor");
28807 };
28808 let executor = Arc::clone(executor);
28809 drop(backend);
28810
28811 let (registered_tx, registered_rx) = mpsc::channel();
28812 let (release_submission_tx, release_submission_rx) = mpsc::channel();
28813 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28814 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28815 let submit_controller = Arc::clone(&controller);
28816 let submit_work = Arc::clone(&work);
28817 let submitter = std::thread::spawn(move || {
28818 AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28819 *slot.borrow_mut() = Some(Box::new(move || {
28820 registered_tx.send(()).unwrap();
28821 release_submission_rx.recv().unwrap();
28822 }));
28823 });
28824 submit_controller
28825 .try_spawn_detached(BlockingDropFuture {
28826 work: submit_work,
28827 entered: drop_entered_tx,
28828 release: release_drop_rx,
28829 })
28830 .unwrap_or_else(|_| {
28831 panic!("the submission linearized before shutdown and must be accepted")
28832 });
28833 });
28834 registered_rx
28835 .recv_timeout(Duration::from_secs(2))
28836 .expect("the submitting thread must register generation work before shutdown");
28837
28838 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28839 let shutdown_controller = Arc::clone(&controller);
28840 let shutdown = std::thread::spawn(move || {
28841 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28842 });
28843 wait_until("CurrentThread shutdown to close the runnable queue", || {
28844 executor
28845 .queue
28846 .lock()
28847 .unwrap_or_else(std::sync::PoisonError::into_inner)
28848 .closed
28849 });
28850 release_submission_tx.send(()).unwrap();
28851
28852 let (drop_generation, active_during_drop) = drop_entered_rx
28853 .recv_timeout(Duration::from_secs(2))
28854 .expect("the closed queue must destroy the accepted unpolled future");
28855 assert_eq!(drop_generation, Some(old_generation));
28856 assert_eq!(
28857 active_during_drop, 1,
28858 "accepted generation work must remain registered through user future destruction"
28859 );
28860
28861 let (start_tx, start_rx) = mpsc::channel();
28862 let start_controller = Arc::clone(&controller);
28863 let starter = std::thread::spawn(move || {
28864 start_tx.send(start_controller.start()).unwrap();
28865 });
28866 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28867 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28868 let shutdown_overlapped = early_shutdown.is_some();
28869 let restart_overlapped = early_start.is_some();
28870
28871 release_drop_tx.send(()).unwrap();
28872 submitter.join().unwrap();
28873 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28874 shutdown_rx
28875 .recv_timeout(Duration::from_secs(2))
28876 .expect("shutdown must finish after drop")
28877 });
28878 shutdown_result.unwrap();
28879 shutdown.join().unwrap();
28880 let start_result = early_start.unwrap_or_else(|| {
28881 start_rx
28882 .recv_timeout(Duration::from_secs(2))
28883 .expect("restart must follow shutdown")
28884 });
28885 start_result.unwrap();
28886 starter.join().unwrap();
28887
28888 assert!(
28889 !shutdown_overlapped,
28890 "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
28891 );
28892 assert!(
28893 !restart_overlapped,
28894 "restart must not create a new generation while an accepted old-generation future is being destroyed"
28895 );
28896 assert_ne!(controller.backend().generation(), old_generation);
28897 controller.shutdown().unwrap();
28898 }
28899
28900 #[cfg(napi_runtime_os_threads)]
28901 #[test]
28902 fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
28903 use std::sync::mpsc;
28904
28905 struct BlockingDrop {
28906 work: Arc<GenerationWork>,
28907 entered: mpsc::Sender<(Option<u64>, usize)>,
28908 release: mpsc::Receiver<()>,
28909 }
28910
28911 impl Drop for BlockingDrop {
28912 fn drop(&mut self) {
28913 let active = self
28914 .work
28915 .state
28916 .lock()
28917 .unwrap_or_else(std::sync::PoisonError::into_inner)
28918 .active;
28919 self
28920 .entered
28921 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28922 .unwrap();
28923 self.release.recv().unwrap();
28924 }
28925 }
28926
28927 let controller = Arc::new(multi_thread_controller(
28928 "accepted-blocking-submission-drop",
28929 2,
28930 1,
28931 ));
28932 let backend = controller.backend();
28933 let old_generation = backend.generation();
28934 let work = Arc::clone(&backend.work);
28935 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28936 panic!("configured MultiThread backend must create a MultiThread executor");
28937 };
28938 let executor = Arc::downgrade(executor);
28939 drop(backend);
28940
28941 let (registered_tx, registered_rx) = mpsc::channel();
28942 let (release_submission_tx, release_submission_rx) = mpsc::channel();
28943 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28944 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28945 let submit_controller = Arc::clone(&controller);
28946 let submitter = std::thread::spawn(move || {
28947 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28948 *slot.borrow_mut() = Some(Box::new(move || {
28949 registered_tx.send(()).unwrap();
28950 release_submission_rx.recv().unwrap();
28951 }));
28952 });
28953 let drop_signal = BlockingDrop {
28954 work,
28955 entered: drop_entered_tx,
28956 release: release_drop_rx,
28957 };
28958 let handle = submit_controller
28959 .try_spawn_blocking(move || {
28960 let _drop_signal = drop_signal;
28961 })
28962 .unwrap_or_else(|_| {
28963 panic!("the submission linearized before shutdown and must be accepted")
28964 });
28965 assert!(
28966 futures::executor::block_on(handle).is_err(),
28967 "the executor must cancel work that reaches its queue after shutdown closed it"
28968 );
28969 });
28970 registered_rx
28971 .recv_timeout(Duration::from_secs(2))
28972 .expect("the submitting thread must register generation work before shutdown");
28973
28974 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28975 let shutdown_controller = Arc::clone(&controller);
28976 let shutdown = std::thread::spawn(move || {
28977 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28978 });
28979 wait_until("MultiThread shutdown to close the blocking queue", || {
28980 executor
28981 .upgrade()
28982 .expect("the retiring executor must remain alive until shutdown completes")
28983 .blocking_queue
28984 .lock()
28985 .unwrap_or_else(std::sync::PoisonError::into_inner)
28986 .closed
28987 });
28988 release_submission_tx.send(()).unwrap();
28989
28990 let (drop_generation, active_during_drop) = drop_entered_rx
28991 .recv_timeout(Duration::from_secs(2))
28992 .expect("the closed queue must destroy the accepted blocking closure");
28993 assert_eq!(drop_generation, Some(old_generation));
28994 assert_eq!(
28995 active_during_drop, 1,
28996 "accepted generation work must remain registered through blocking closure destruction"
28997 );
28998
28999 let (start_tx, start_rx) = mpsc::channel();
29000 let start_controller = Arc::clone(&controller);
29001 let starter = std::thread::spawn(move || {
29002 start_tx.send(start_controller.start()).unwrap();
29003 });
29004 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29005 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
29006 let shutdown_overlapped = early_shutdown.is_some();
29007 let restart_overlapped = early_start.is_some();
29008
29009 release_drop_tx.send(()).unwrap();
29010 submitter.join().unwrap();
29011 let shutdown_result = early_shutdown.unwrap_or_else(|| {
29012 shutdown_rx
29013 .recv_timeout(Duration::from_secs(2))
29014 .expect("shutdown must finish after drop")
29015 });
29016 shutdown_result.unwrap();
29017 shutdown.join().unwrap();
29018 let start_result = early_start.unwrap_or_else(|| {
29019 start_rx
29020 .recv_timeout(Duration::from_secs(2))
29021 .expect("restart must follow shutdown")
29022 });
29023 start_result.unwrap();
29024 starter.join().unwrap();
29025
29026 assert!(
29027 !shutdown_overlapped,
29028 "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
29029 );
29030 assert!(
29031 !restart_overlapped,
29032 "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
29033 );
29034 assert_ne!(controller.backend().generation(), old_generation);
29035 controller.shutdown().unwrap();
29036 }
29037
29038 #[cfg(napi_runtime_os_threads)]
29039 #[test]
29040 fn multi_thread_controller_reports_its_exact_physical_worker_count() {
29041 let controller = multi_thread_controller("lifecycle-physical", 1, 8);
29042 let options = controller.options();
29043 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
29044
29045 let backend = controller.backend();
29046 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29047 panic!("configured MultiThread backend must create a Rayon executor");
29048 };
29049 assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
29050 let lifecycle = Arc::clone(&executor.worker_lifecycle);
29051 drop(backend);
29052
29053 controller.shutdown().expect("runtime must stop");
29054 assert_eq!(
29055 lifecycle.remaining(),
29056 0,
29057 "shutdown must observe every physical worker exit"
29058 );
29059 }
29060
29061 #[cfg(napi_runtime_os_threads)]
29062 #[test]
29063 fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
29064 use std::sync::mpsc;
29081
29082 if !platform_runs_thread_local_destructors() {
29083 eprintln!(
29084 "skipping: std does not run thread_local! destructors at thread exit on this target \
29085 (rust-lang/rust#163748), so there is no destructor completion for the join barrier to include"
29086 );
29087 return;
29088 }
29089
29090 let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
29091 let backend = controller.backend();
29092 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29093 panic!("configured MultiThread backend must create a Rayon executor");
29094 };
29095 let worker_count = executor.pool.current_num_threads();
29096 let completed = Arc::new(AtomicUsize::new(0));
29097 let probe_completed = Arc::clone(&completed);
29098 executor.pool.broadcast(move |_| {
29099 WORKER_TLS_DROP_PROBE.with(|slot| {
29100 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
29101 action: Some(WorkerTlsDropAction::Record {
29102 completed: Arc::clone(&probe_completed),
29103 }),
29104 });
29105 assert!(
29106 previous.is_none(),
29107 "each worker must install exactly one TLS drop probe"
29108 );
29109 });
29110 });
29111 drop(backend);
29112
29113 assert_eq!(
29114 completed.load(Ordering::SeqCst),
29115 0,
29116 "no worker TLS destructor may run before shutdown retires the workers"
29117 );
29118
29119 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29120 let shutdown_controller = Arc::clone(&controller);
29121 let sample_completed = Arc::clone(&completed);
29122 let shutdown = std::thread::spawn(move || {
29123 let result = shutdown_controller.shutdown();
29124 let seen_at_return = sample_completed.load(Ordering::SeqCst);
29128 shutdown_tx.send((result, seen_at_return)).unwrap();
29129 });
29130 let (shutdown_result, seen_at_return) = shutdown_rx
29131 .recv_timeout(Duration::from_secs(10))
29132 .expect("shutdown must finish once worker TLS destructors have run");
29133 shutdown_result.unwrap();
29134 #[cfg(not(napi_runtime_wasi_threads))]
29137 let destructors_run = true;
29138 #[cfg(napi_runtime_wasi_threads)]
29139 let destructors_run = std_runs_tls_destructors_before_join();
29140 if destructors_run {
29141 assert_eq!(
29142 seen_at_return, worker_count,
29143 "every worker TLS destructor must have completed before shutdown() returned: \
29144 the worker-join barrier must include TLS-destructor completion"
29145 );
29146 }
29147 join_within(
29148 "the shutdown thread exits after teardown",
29149 shutdown,
29150 Duration::from_secs(30),
29151 );
29152 }
29153
29154 #[cfg(not(target_family = "wasm"))]
29155 #[test]
29156 fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
29157 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
29158
29159 if std::env::var_os(CHILD_ENV).is_some() {
29160 use std::sync::mpsc;
29161
29162 let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
29163 let backend = controller.backend();
29164 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29165 panic!("configured MultiThread backend must create a Rayon executor");
29166 };
29167 let installed = Arc::new(AtomicBool::new(false));
29168 let (completed_tx, completed_rx) = mpsc::channel();
29169 let probe_controller = Arc::clone(&controller);
29170 let probe_installed = Arc::clone(&installed);
29171 executor.pool.broadcast(move |_| {
29172 if probe_installed.swap(true, Ordering::SeqCst) {
29173 return;
29174 }
29175 WORKER_TLS_DROP_PROBE.with(|slot| {
29176 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
29177 action: Some(WorkerTlsDropAction::ReenterLifecycle {
29178 controller: Arc::clone(&probe_controller),
29179 completed: completed_tx.clone(),
29180 }),
29181 });
29182 assert!(
29183 previous.is_none(),
29184 "the selected worker must have no prior TLS probe"
29185 );
29186 });
29187 });
29188 assert!(
29189 installed.load(Ordering::SeqCst),
29190 "one worker must install the lifecycle probe"
29191 );
29192 drop(backend);
29193
29194 controller
29195 .shutdown()
29196 .expect("external shutdown must join the retiring workers");
29197 let (start, shutdown) = completed_rx
29198 .recv_timeout(Duration::from_secs(2))
29199 .expect("the TLS destructor must complete both lifecycle reentry attempts");
29200 assert_eq!(
29201 start.unwrap_err(),
29202 "cannot restart the async runtime from work in the generation being stopped"
29203 );
29204 assert_eq!(
29205 shutdown.unwrap_err(),
29206 "cannot wait for async runtime shutdown from work in the generation being stopped"
29207 );
29208 return;
29209 }
29210
29211 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
29212 .arg("--exact")
29213 .arg(
29214 "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
29215 )
29216 .arg("--nocapture")
29217 .env(CHILD_ENV, "1")
29218 .stdout(std::process::Stdio::piped())
29219 .stderr(std::process::Stdio::piped())
29220 .spawn()
29221 .expect("the TLS lifecycle-reentry subprocess must start");
29222 let deadline = Instant::now() + Duration::from_secs(5);
29223 let status = loop {
29224 if let Some(status) = child
29225 .try_wait()
29226 .expect("the subprocess status must be readable")
29227 {
29228 break status;
29229 }
29230 if Instant::now() >= deadline {
29231 let _ = child.kill();
29232 let output = child
29233 .wait_with_output()
29234 .expect("the timed-out subprocess must be reaped");
29235 panic!(
29236 "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
29237 String::from_utf8_lossy(&output.stdout),
29238 String::from_utf8_lossy(&output.stderr)
29239 );
29240 }
29241 std::thread::sleep(Duration::from_millis(10));
29242 };
29243 let output = child
29244 .wait_with_output()
29245 .expect("the completed subprocess output must be read");
29246 assert!(
29247 status.success(),
29248 "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
29249 status.code(),
29250 String::from_utf8_lossy(&output.stdout),
29251 String::from_utf8_lossy(&output.stderr)
29252 );
29253 }
29254
29255 #[cfg(napi_runtime_os_threads)]
29256 #[test]
29257 fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
29258 use std::sync::mpsc;
29259
29260 struct DropSignal(mpsc::Sender<()>);
29261 impl Drop for DropSignal {
29262 fn drop(&mut self) {
29263 let _ = self.0.send(());
29264 }
29265 }
29266
29267 let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
29268 let (polled_tx, polled_rx) = mpsc::channel();
29269 let (dropped_tx, dropped_rx) = mpsc::channel();
29270 controller
29271 .try_spawn_detached(async move {
29272 let _drop_signal = DropSignal(dropped_tx);
29273 polled_tx.send(()).unwrap();
29274 std::future::pending::<()>().await;
29275 })
29276 .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
29277 polled_rx
29278 .recv_timeout(Duration::from_secs(5))
29279 .expect("the pending task must be polled before shutdown");
29280
29281 controller
29282 .shutdown()
29283 .expect("shutdown must cancel accepted async work");
29284 dropped_rx
29285 .recv_timeout(Duration::from_secs(5))
29286 .expect("shutdown must drop the accepted task future before returning");
29287 }
29288
29289 #[cfg(napi_runtime_os_threads)]
29290 #[test]
29291 fn current_thread_shutdown_outranks_perpetual_self_wake() {
29292 use std::sync::mpsc;
29293
29294 struct SelfWakingPending {
29295 entered: Option<mpsc::Sender<()>>,
29296 }
29297
29298 impl Future for SelfWakingPending {
29299 type Output = ();
29300
29301 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29302 if let Some(entered) = self.entered.take() {
29303 entered.send(()).unwrap();
29304 }
29305 cx.waker().wake_by_ref();
29306 Poll::Pending
29307 }
29308 }
29309
29310 let controller = Arc::new(current_thread_controller(
29311 "current-thread-self-wake-shutdown",
29312 ));
29313 let (entered_tx, entered_rx) = mpsc::channel();
29314 let (outcome_tx, outcome_rx) = mpsc::channel();
29315 let block_on_controller = Arc::clone(&controller);
29316 let runner = std::thread::spawn(move || {
29317 let mut future = std::pin::pin!(SelfWakingPending {
29318 entered: Some(entered_tx)
29319 });
29320 outcome_tx
29321 .send(block_on_controller.block_on(future.as_mut()))
29322 .unwrap();
29323 });
29324 entered_rx
29325 .recv_timeout(Duration::from_secs(2))
29326 .expect("the self-waking future must be polled before shutdown");
29327
29328 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29329 let shutdown_controller = Arc::clone(&controller);
29330 let shutdown = std::thread::spawn(move || {
29331 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29332 });
29333 shutdown_rx
29334 .recv_timeout(Duration::from_secs(2))
29335 .expect("shutdown must outrank a stored self-wake permit")
29336 .unwrap();
29337 assert_eq!(
29338 outcome_rx
29339 .recv_timeout(Duration::from_secs(2))
29340 .expect("the raw block_on driver must report shutdown"),
29341 BlockOnOutcome::Stopped
29342 );
29343 runner.join().unwrap();
29344 shutdown.join().unwrap();
29345 }
29346
29347 #[cfg(napi_runtime_os_threads)]
29348 #[test]
29349 fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
29350 use std::sync::mpsc;
29351
29352 let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
29353 let backend = controller.backend();
29354 let executor = match &backend.executor {
29355 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29356 RuntimeExecutor::CurrentThread(_) => unreachable!(),
29357 };
29358 let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
29359 let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
29360 for _ in 0..2 {
29361 let worker_ready_tx = worker_ready_tx.clone();
29362 let release_workers = Arc::clone(&release_workers);
29363 executor.pool.spawn(move || {
29364 worker_ready_tx.send(()).unwrap();
29365 let (released, changed) = &*release_workers;
29366 let mut released = released
29367 .lock()
29368 .unwrap_or_else(std::sync::PoisonError::into_inner);
29369 while !*released {
29370 released = changed
29371 .wait(released)
29372 .unwrap_or_else(std::sync::PoisonError::into_inner);
29373 }
29374 });
29375 }
29376 drop(worker_ready_tx);
29377 for _ in 0..2 {
29378 worker_ready_rx
29379 .recv_timeout(Duration::from_secs(5))
29380 .expect("both workers must be occupied before the FIFO runnable is submitted");
29381 }
29382 drop(executor);
29383 drop(backend);
29384
29385 let polls = Arc::new(AtomicUsize::new(0));
29386 let handle = controller.spawn(StopPollProbe {
29387 polls: Arc::clone(&polls),
29388 first_poll: None,
29389 });
29390 wait_until("the FIFO runnable to be queued", || {
29391 controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
29392 });
29393
29394 let observed_queue = Arc::new(Mutex::new(None));
29395 let hook_observed_queue = Arc::clone(&observed_queue);
29396 let hook_metrics = Arc::clone(&controller.metrics);
29397 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29398 *slot.borrow_mut() = Some(Box::new(move || {
29399 let (released, changed) = &*release_workers;
29400 *released
29401 .lock()
29402 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29403 changed.notify_all();
29404 wait_until("the stopped FIFO runnable claim", || {
29405 hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
29406 });
29407 *hook_observed_queue
29408 .lock()
29409 .unwrap_or_else(std::sync::PoisonError::into_inner) =
29410 Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
29411 }));
29412 });
29413
29414 controller
29415 .shutdown()
29416 .expect("shutdown must cancel the stopped FIFO claim");
29417 assert_eq!(polls.load(Ordering::SeqCst), 0);
29418 assert_eq!(
29419 *observed_queue
29420 .lock()
29421 .unwrap_or_else(std::sync::PoisonError::into_inner),
29422 Some(0)
29423 );
29424 assert_eq!(
29425 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29426 0
29427 );
29428 drop(handle);
29429 }
29430
29431 #[cfg(napi_runtime_os_threads)]
29432 #[test]
29433 fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
29434 use std::sync::mpsc;
29435
29436 let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
29437 let backend = controller.backend();
29438 let executor = match &backend.executor {
29439 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29440 RuntimeExecutor::CurrentThread(_) => unreachable!(),
29441 };
29442 let polls = Arc::new(AtomicUsize::new(0));
29443 let scheduler = Arc::downgrade(&executor);
29444 let (runnable, task) = async_task::spawn(
29445 StopPollProbe {
29446 polls: Arc::clone(&polls),
29447 first_poll: None,
29448 },
29449 move |runnable| {
29450 if let Some(executor) = scheduler.upgrade() {
29451 executor.schedule(runnable);
29452 }
29453 },
29454 );
29455 let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
29456 let (release_slot_tx, release_slot_rx) = mpsc::channel();
29457 let (claim_done_tx, claim_done_rx) = mpsc::channel();
29458 let worker_executor = Arc::clone(&executor);
29459 let worker_metrics = Arc::clone(&controller.metrics);
29460 executor.pool.spawn(move || {
29461 let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
29462 worker_executor.schedule(runnable);
29463 slot_ready_tx.send(()).unwrap();
29464 release_slot_rx.recv().unwrap();
29465 let claimed = worker_executor.run_one_runnable();
29466 claim_done_tx
29467 .send((
29468 claimed,
29469 worker_metrics.queued_runnables.load(Ordering::SeqCst),
29470 ))
29471 .unwrap();
29472 });
29473 slot_ready_rx
29474 .recv_timeout(Duration::from_secs(5))
29475 .expect("the worker must publish the LIFO runnable before shutdown");
29476 assert_eq!(
29477 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29478 1
29479 );
29480 drop(executor);
29481 drop(backend);
29482
29483 let observed_claim = Arc::new(Mutex::new(None));
29484 let hook_observed_claim = Arc::clone(&observed_claim);
29485 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29486 *slot.borrow_mut() = Some(Box::new(move || {
29487 release_slot_tx.send(()).unwrap();
29488 let result = claim_done_rx
29489 .recv_timeout(Duration::from_secs(5))
29490 .expect("the stopped LIFO runnable claim must complete before abort");
29491 *hook_observed_claim
29492 .lock()
29493 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
29494 }));
29495 });
29496
29497 controller
29498 .shutdown()
29499 .expect("shutdown must cancel the stopped LIFO claim");
29500 assert_eq!(polls.load(Ordering::SeqCst), 0);
29501 assert_eq!(
29502 *observed_claim
29503 .lock()
29504 .unwrap_or_else(std::sync::PoisonError::into_inner),
29505 Some((true, 0))
29506 );
29507 assert_eq!(
29508 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29509 0
29510 );
29511 drop(task);
29512 }
29513
29514 #[cfg(napi_runtime_os_threads)]
29515 #[test]
29516 fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
29517 use std::sync::mpsc;
29518
29519 struct BlockingDrop {
29520 entered: mpsc::Sender<()>,
29521 release: mpsc::Receiver<()>,
29522 }
29523
29524 impl Drop for BlockingDrop {
29525 fn drop(&mut self) {
29526 self.entered.send(()).unwrap();
29527 self.release.recv().unwrap();
29528 }
29529 }
29530
29531 struct TwoPollProbe {
29532 first_poll: Option<mpsc::Sender<()>>,
29533 release_first_poll: mpsc::Receiver<()>,
29534 second_poll: mpsc::Sender<()>,
29535 }
29536
29537 impl Future for TwoPollProbe {
29538 type Output = ();
29539
29540 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29541 if let Some(first_poll) = self.first_poll.take() {
29542 first_poll.send(()).unwrap();
29543 self.release_first_poll.recv().unwrap();
29544 } else {
29545 self.second_poll.send(()).unwrap();
29546 }
29547 cx.waker().wake_by_ref();
29548 Poll::Pending
29549 }
29550 }
29551
29552 let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
29553 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29554 let (release_drop_tx, release_drop_rx) = mpsc::channel();
29555 let blocking_drop = BlockingDrop {
29556 entered: drop_entered_tx,
29557 release: release_drop_rx,
29558 };
29559 controller
29560 .try_spawn_detached(async move {
29561 let _drop = blocking_drop;
29562 std::future::pending::<()>().await;
29563 })
29564 .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
29565
29566 let (first_poll_tx, first_poll_rx) = mpsc::channel();
29567 let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
29568 let (second_poll_tx, second_poll_rx) = mpsc::channel();
29569 let (outcome_tx, outcome_rx) = mpsc::channel();
29570 let runner_controller = Arc::clone(&controller);
29571 let runner = std::thread::spawn(move || {
29572 let mut future = std::pin::pin!(TwoPollProbe {
29573 first_poll: Some(first_poll_tx),
29574 release_first_poll: release_first_poll_rx,
29575 second_poll: second_poll_tx,
29576 });
29577 outcome_tx
29578 .send(runner_controller.block_on(future.as_mut()))
29579 .unwrap();
29580 });
29581 first_poll_rx
29582 .recv_timeout(Duration::from_secs(2))
29583 .expect("the active block_on poll must enter before shutdown");
29584
29585 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29586 let shutdown_controller = Arc::clone(&controller);
29587 let shutdown = std::thread::spawn(move || {
29588 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29589 });
29590 drop_entered_rx
29591 .recv_timeout(Duration::from_secs(2))
29592 .expect("shutdown must begin destroying queued work");
29593
29594 release_first_poll_tx.send(()).unwrap();
29595 let polled_again = second_poll_rx
29596 .recv_timeout(Duration::from_millis(200))
29597 .is_ok();
29598 release_drop_tx.send(()).unwrap();
29599
29600 assert!(
29601 !polled_again,
29602 "an active block_on driver must observe generation stop before queued user cleanup blocks"
29603 );
29604 assert_eq!(
29605 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29606 BlockOnOutcome::Stopped
29607 );
29608 shutdown_rx
29609 .recv_timeout(Duration::from_secs(2))
29610 .unwrap()
29611 .unwrap();
29612 runner.join().unwrap();
29613 shutdown.join().unwrap();
29614 }
29615
29616 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29617 #[test]
29618 fn shutdown_cancels_nested_block_on_within_deadline() {
29619 use std::sync::mpsc;
29620
29621 let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
29622 let task_controller = Arc::clone(&controller);
29623 let (entered_tx, entered_rx) = mpsc::channel();
29624 let task = controller
29625 .try_spawn(async move {
29626 entered_tx.send(()).unwrap();
29627 block_on_with_controller(&task_controller, std::future::pending::<()>());
29628 })
29629 .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
29630 entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29631
29632 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29633 let shutdown_controller = Arc::clone(&controller);
29634 let shutdown = std::thread::spawn(move || {
29635 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29636 });
29637 shutdown_rx
29638 .recv_timeout(Duration::from_secs(2))
29639 .expect("shutdown must wake and cancel an active nested block_on")
29640 .unwrap();
29641 shutdown.join().unwrap();
29642 assert!(
29643 futures::executor::block_on(task).is_err(),
29644 "the cancelled nested block_on task must resolve as stopped"
29645 );
29646 }
29647
29648 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29649 #[test]
29650 fn stopped_public_block_on_drops_future_before_generation_retirement() {
29651 use std::sync::mpsc;
29652
29653 struct PendingDrop {
29654 entered: Option<mpsc::Sender<()>>,
29655 dropped: mpsc::Sender<Option<u64>>,
29656 release: mpsc::Receiver<()>,
29657 }
29658
29659 impl Future for PendingDrop {
29660 type Output = ();
29661
29662 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29663 if let Some(entered) = self.entered.take() {
29664 entered.send(()).unwrap();
29665 }
29666 Poll::Pending
29667 }
29668 }
29669
29670 impl Drop for PendingDrop {
29671 fn drop(&mut self) {
29672 self
29673 .dropped
29674 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29675 .unwrap();
29676 self.release.recv().unwrap();
29677 }
29678 }
29679
29680 let controller = Arc::new(current_thread_controller(
29681 "public-block-on-generation-destruction",
29682 ));
29683 let generation = controller.backend().generation();
29684 let (entered_tx, entered_rx) = mpsc::channel();
29685 let (dropped_tx, dropped_rx) = mpsc::channel();
29686 let (release_tx, release_rx) = mpsc::channel();
29687 let (runner_done_tx, runner_done_rx) = mpsc::channel();
29688 let block_controller = Arc::clone(&controller);
29689 let runner = std::thread::spawn(move || {
29690 let result = catch_unwind(AssertUnwindSafe(|| {
29691 block_on_with_controller(
29692 &block_controller,
29693 PendingDrop {
29694 entered: Some(entered_tx),
29695 dropped: dropped_tx,
29696 release: release_rx,
29697 },
29698 );
29699 }));
29700 runner_done_tx.send(result.is_err()).unwrap();
29701 });
29702 entered_rx
29703 .recv_timeout(Duration::from_secs(2))
29704 .expect("the public block_on future must be polled before shutdown");
29705
29706 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29707 let shutdown_controller = Arc::clone(&controller);
29708 let shutdown = std::thread::spawn(move || {
29709 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29710 });
29711
29712 assert_eq!(
29713 dropped_rx
29714 .recv_timeout(Duration::from_secs(2))
29715 .expect("shutdown must wake block_on and begin destroying its future"),
29716 Some(generation),
29717 "the stopped future must be destroyed under its retiring generation identity"
29718 );
29719 assert!(
29720 shutdown_rx
29721 .recv_timeout(Duration::from_millis(200))
29722 .is_err(),
29723 "shutdown must not publish Stopped while the public block_on future destructor is running"
29724 );
29725
29726 release_tx.send(()).unwrap();
29727 assert!(
29728 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29729 "the public helper must retain its existing early-stop panic behavior"
29730 );
29731 shutdown_rx
29732 .recv_timeout(Duration::from_secs(2))
29733 .unwrap()
29734 .unwrap();
29735 runner.join().unwrap();
29736 shutdown.join().unwrap();
29737 }
29738
29739 #[cfg(not(target_family = "wasm"))]
29740 #[test]
29741 fn shutdown_contains_panicking_nested_block_on_future_drop() {
29742 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
29743
29744 if std::env::var_os(CHILD_ENV).is_some() {
29745 use std::sync::mpsc;
29746
29747 struct PendingPanicOnDrop {
29748 entered: Option<mpsc::Sender<()>>,
29749 }
29750
29751 impl Future for PendingPanicOnDrop {
29752 type Output = ();
29753
29754 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29755 if let Some(entered) = self.entered.take() {
29756 entered.send(()).unwrap();
29757 }
29758 Poll::Pending
29759 }
29760 }
29761
29762 impl Drop for PendingPanicOnDrop {
29763 fn drop(&mut self) {
29764 panic!("intentional nested block_on future destructor panic");
29765 }
29766 }
29767
29768 let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
29769 let task_controller = Arc::clone(&controller);
29770 let (entered_tx, entered_rx) = mpsc::channel();
29771 let task = controller
29772 .try_spawn(async move {
29773 block_on_with_controller(
29774 &task_controller,
29775 PendingPanicOnDrop {
29776 entered: Some(entered_tx),
29777 },
29778 );
29779 })
29780 .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
29781 entered_rx
29782 .recv_timeout(Duration::from_secs(2))
29783 .expect("the nested block_on future must be polled before shutdown");
29784
29785 controller
29786 .shutdown()
29787 .expect("shutdown must contain nested future destruction");
29788 assert!(
29789 futures::executor::block_on(task).is_err(),
29790 "shutdown cancellation must still surface as a task error"
29791 );
29792 return;
29793 }
29794
29795 let output = std::process::Command::new(std::env::current_exe().unwrap())
29796 .arg("--exact")
29797 .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
29798 .arg("--nocapture")
29799 .env(CHILD_ENV, "1")
29800 .output()
29801 .expect("the subprocess test must start");
29802 assert!(
29803 output.status.success(),
29804 "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
29805 output.status.code(),
29806 String::from_utf8_lossy(&output.stdout),
29807 String::from_utf8_lossy(&output.stderr)
29808 );
29809 }
29810
29811 #[cfg(not(target_family = "wasm"))]
29812 #[test]
29813 fn panicking_future_drop_is_contained_before_async_task_drop() {
29814 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
29815
29816 if std::env::var_os(CHILD_ENV).is_some() {
29817 struct PanicOnDrop;
29818 impl Drop for PanicOnDrop {
29819 fn drop(&mut self) {
29820 panic!("intentional future destructor panic");
29821 }
29822 }
29823
29824 let controller = current_thread_controller("future-drop-subprocess");
29825 let bomb = PanicOnDrop;
29826 controller
29827 .try_spawn_detached(async move {
29828 let _bomb = bomb;
29829 std::future::pending::<()>().await;
29830 })
29831 .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
29832 controller
29833 .shutdown()
29834 .expect("shutdown must contain the future destructor panic");
29835 return;
29836 }
29837
29838 let output = std::process::Command::new(std::env::current_exe().unwrap())
29839 .arg("--exact")
29840 .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
29841 .arg("--nocapture")
29842 .env(CHILD_ENV, "1")
29843 .output()
29844 .expect("the subprocess test must start");
29845 assert!(
29846 output.status.success(),
29847 "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29848 output.status.code(),
29849 String::from_utf8_lossy(&output.stdout),
29850 String::from_utf8_lossy(&output.stderr)
29851 );
29852 }
29853
29854 #[cfg(not(target_family = "wasm"))]
29855 #[test]
29856 fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
29857 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
29858
29859 if std::env::var_os(CHILD_ENV).is_some() {
29860 struct PanicOnDrop;
29861 impl Drop for PanicOnDrop {
29862 fn drop(&mut self) {
29863 panic!("intentional detached output destructor panic");
29864 }
29865 }
29866
29867 let controller = current_thread_controller("output-drop-subprocess");
29868 let handle = controller
29869 .try_spawn(async { PanicOnDrop })
29870 .unwrap_or_else(|_| panic!("runtime must accept the task"));
29871 drop(handle);
29872 let dispatch = controller_current_thread_dispatch(&controller);
29873 controller.drive_current_thread_dispatch(dispatch, false);
29874 controller.shutdown().unwrap();
29875 return;
29876 }
29877
29878 let output = std::process::Command::new(std::env::current_exe().unwrap())
29879 .arg("--exact")
29880 .arg(
29881 "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
29882 )
29883 .arg("--nocapture")
29884 .env(CHILD_ENV, "1")
29885 .output()
29886 .expect("the subprocess test must start");
29887 assert!(
29888 output.status.success(),
29889 "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29890 output.status.code(),
29891 String::from_utf8_lossy(&output.stdout),
29892 String::from_utf8_lossy(&output.stderr)
29893 );
29894 }
29895
29896 #[cfg(napi_runtime_os_threads)]
29897 #[test]
29898 fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
29899 use std::sync::mpsc;
29900
29901 struct BlockingDrop {
29902 controller: Arc<RuntimeController>,
29903 entered: mpsc::Sender<Option<u64>>,
29904 release: mpsc::Receiver<()>,
29905 reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
29906 }
29907
29908 impl Drop for BlockingDrop {
29909 fn drop(&mut self) {
29910 self
29911 .entered
29912 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29913 .unwrap();
29914 self.release.recv().unwrap();
29915 self
29916 .reentrant_shutdown
29917 .send(self.controller.shutdown())
29918 .unwrap();
29919 }
29920 }
29921
29922 let controller = Arc::new(current_thread_controller(
29923 "current-thread-host-drain-output-retirement",
29924 ));
29925 let old_generation = controller.backend().generation();
29926 let (entered_tx, entered_rx) = mpsc::channel();
29927 let (release_tx, release_rx) = mpsc::channel();
29928 let (reentrant_tx, reentrant_rx) = mpsc::channel();
29929 let output_controller = Arc::clone(&controller);
29930 let handle = controller
29931 .try_spawn(async move {
29932 BlockingDrop {
29933 controller: output_controller,
29934 entered: entered_tx,
29935 release: release_rx,
29936 reentrant_shutdown: reentrant_tx,
29937 }
29938 })
29939 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
29940 drop(handle);
29941 let dispatch = controller_current_thread_dispatch(&controller);
29942
29943 let driver_controller = Arc::clone(&controller);
29944 let driver =
29945 std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
29946 assert_eq!(
29947 entered_rx
29948 .recv_timeout(Duration::from_secs(2))
29949 .expect("the host turn must begin destroying the detached output"),
29950 Some(old_generation),
29951 "detached output destruction must retain the generation that produced it"
29952 );
29953
29954 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29955 let shutdown_controller = Arc::clone(&controller);
29956 let shutdown = std::thread::spawn(move || {
29957 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29958 });
29959 wait_until(
29960 "CurrentThread shutdown to enter the old generation's stopping state",
29961 || {
29962 let state = controller
29963 .state
29964 .lock()
29965 .unwrap_or_else(std::sync::PoisonError::into_inner);
29966 matches!(
29967 &state.lifecycle,
29968 RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
29969 )
29970 },
29971 );
29972
29973 let (restart_tx, restart_rx) = mpsc::channel();
29974 let restart_controller = Arc::clone(&controller);
29975 let restart = std::thread::spawn(move || {
29976 restart_controller.start().unwrap();
29977 restart_tx
29978 .send(restart_controller.backend().generation())
29979 .unwrap();
29980 });
29981
29982 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29983 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
29984 let shutdown_overlapped = early_shutdown.is_some();
29985 let restart_overlapped = early_restart.is_some();
29986 release_tx.send(()).unwrap();
29987
29988 let reentrant_error = reentrant_rx
29989 .recv_timeout(Duration::from_secs(2))
29990 .expect("the retiring output destructor must finish its lifecycle check")
29991 .expect_err("old-generation output destruction must not shut down a replacement runtime");
29992 assert!(
29993 reentrant_error
29994 .to_string()
29995 .contains("generation being stopped"),
29996 "the destructor must still observe its old generation as Stopping: {reentrant_error}"
29997 );
29998
29999 let shutdown_result = early_shutdown.unwrap_or_else(|| {
30000 shutdown_rx
30001 .recv_timeout(Duration::from_secs(2))
30002 .expect("shutdown must finish after detached output destruction retires")
30003 });
30004 shutdown_result.unwrap();
30005 let new_generation = early_restart.unwrap_or_else(|| {
30006 restart_rx
30007 .recv_timeout(Duration::from_secs(2))
30008 .expect("restart must finish after the old host turn retires")
30009 });
30010
30011 driver.join().unwrap();
30012 shutdown.join().unwrap();
30013 restart.join().unwrap();
30014 assert!(
30015 !shutdown_overlapped,
30016 "shutdown must not publish Stopped while a host turn is destroying detached output"
30017 );
30018 assert!(
30019 !restart_overlapped,
30020 "restart must not overlap a host turn from the retiring generation"
30021 );
30022 assert_ne!(new_generation, old_generation);
30023 controller.shutdown().unwrap();
30024 }
30025
30026 #[cfg(napi_runtime_os_threads)]
30027 #[test]
30028 fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
30029 use std::sync::mpsc;
30030
30031 struct ShutdownOnDrop {
30032 controller: Arc<RuntimeController>,
30033 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
30034 }
30035
30036 impl Drop for ShutdownOnDrop {
30037 fn drop(&mut self) {
30038 let _ = self.result.send(self.controller.shutdown());
30039 }
30040 }
30041
30042 let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
30043 let (running_tx, running_rx) = mpsc::channel();
30044 let (release_tx, release_rx) = mpsc::channel();
30045 let running = controller
30046 .try_spawn_blocking(move || {
30047 running_tx.send(()).unwrap();
30048 release_rx.recv().unwrap();
30049 })
30050 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
30051 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30052
30053 let (reentrant_tx, reentrant_rx) = mpsc::channel();
30054 let bomb = ShutdownOnDrop {
30055 controller: Arc::clone(&controller),
30056 result: reentrant_tx,
30057 };
30058 let queued = controller
30059 .try_spawn_blocking(move || {
30060 let _bomb = &bomb;
30061 })
30062 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
30063
30064 let (shutdown_tx, shutdown_rx) = mpsc::channel();
30065 let shutdown_controller = Arc::clone(&controller);
30066 let shutdown = std::thread::spawn(move || {
30067 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30068 });
30069 let error = reentrant_rx
30070 .recv_timeout(Duration::from_secs(2))
30071 .expect("queued destruction must not self-deadlock")
30072 .expect_err("the retiring generation must reject reentrant shutdown");
30073 assert!(error.to_string().contains("generation being stopped"));
30074 release_tx.send(()).unwrap();
30075 shutdown_rx
30076 .recv_timeout(Duration::from_secs(2))
30077 .unwrap()
30078 .unwrap();
30079 shutdown.join().unwrap();
30080 futures::executor::block_on(running).unwrap();
30081 assert!(futures::executor::block_on(queued).is_err());
30082 }
30083
30084 #[cfg(napi_runtime_os_threads)]
30085 #[test]
30086 fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
30087 use std::sync::mpsc;
30088
30089 struct ShutdownAfterRelease {
30090 controller: Arc<RuntimeController>,
30091 entered: mpsc::Sender<()>,
30092 release: mpsc::Receiver<()>,
30093 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
30094 }
30095
30096 impl Drop for ShutdownAfterRelease {
30097 fn drop(&mut self) {
30098 self.entered.send(()).unwrap();
30099 self.release.recv().unwrap();
30100 self.result.send(self.controller.shutdown()).unwrap();
30101 }
30102 }
30103
30104 let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
30105 let (holder_started_tx, holder_started_rx) = mpsc::channel();
30106 let (release_holder_tx, release_holder_rx) = mpsc::channel();
30107 let holder = controller
30108 .try_spawn_blocking(move || {
30109 holder_started_tx.send(()).unwrap();
30110 release_holder_rx.recv().unwrap();
30111 })
30112 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
30113 holder_started_rx
30114 .recv_timeout(Duration::from_secs(2))
30115 .unwrap();
30116
30117 let (shutdown_tx, shutdown_rx) = mpsc::channel();
30118 let shutdown_controller = Arc::clone(&controller);
30119 let shutdown = std::thread::spawn(move || {
30120 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30121 });
30122 wait_until("the runtime entered Stopping", || {
30123 matches!(
30124 controller
30125 .state
30126 .lock()
30127 .unwrap_or_else(std::sync::PoisonError::into_inner)
30128 .lifecycle,
30129 RuntimeLifecycle::Stopping(_)
30130 )
30131 });
30132
30133 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
30134 let (release_drop_tx, release_drop_rx) = mpsc::channel();
30135 let (reentrant_tx, reentrant_rx) = mpsc::channel();
30136 let rejected_controller = Arc::clone(&controller);
30137 let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
30138 let rejected = std::thread::spawn(move || {
30139 let bomb = ShutdownAfterRelease {
30140 controller: Arc::clone(&rejected_controller),
30141 entered: drop_entered_tx,
30142 release: release_drop_rx,
30143 result: reentrant_tx,
30144 };
30145 let handle = rejected_controller.spawn(async move {
30146 let _bomb = bomb;
30147 std::future::pending::<()>().await;
30148 });
30149 rejected_done_tx
30150 .send(futures::executor::block_on(handle))
30151 .unwrap();
30152 });
30153 drop_entered_rx
30154 .recv_timeout(Duration::from_secs(2))
30155 .unwrap();
30156
30157 release_holder_tx.send(()).unwrap();
30158 assert!(
30159 shutdown_rx
30160 .recv_timeout(Duration::from_millis(200))
30161 .is_err(),
30162 "shutdown crossed into Stopped before rejected destruction finished"
30163 );
30164
30165 let (start_tx, start_rx) = mpsc::channel();
30166 let start_controller = Arc::clone(&controller);
30167 let starter = std::thread::spawn(move || {
30168 start_tx.send(start_controller.start()).unwrap();
30169 });
30170 assert!(
30171 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
30172 "restart crossed into a new generation before rejected destruction finished"
30173 );
30174
30175 release_drop_tx.send(()).unwrap();
30176 let error = reentrant_rx
30177 .recv_timeout(Duration::from_secs(2))
30178 .expect("the rejected destructor must complete")
30179 .expect_err("the retiring generation must reject the destructor's shutdown");
30180 assert!(error.to_string().contains("generation being stopped"));
30181 assert!(
30182 rejected_done_rx
30183 .recv_timeout(Duration::from_secs(2))
30184 .unwrap()
30185 .is_err()
30186 );
30187 rejected.join().unwrap();
30188
30189 shutdown_rx
30190 .recv_timeout(Duration::from_secs(2))
30191 .unwrap()
30192 .unwrap();
30193 shutdown.join().unwrap();
30194 start_rx
30195 .recv_timeout(Duration::from_secs(2))
30196 .unwrap()
30197 .unwrap();
30198 starter.join().unwrap();
30199 futures::executor::block_on(holder).unwrap();
30200 controller.shutdown().unwrap();
30201 }
30202
30203 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30204 #[test]
30205 fn shutdown_contains_hostile_caught_panic_payload() {
30206 use std::{panic::panic_any, sync::mpsc};
30207
30208 struct PanicAgain;
30209 impl Drop for PanicAgain {
30210 fn drop(&mut self) {
30211 panic!("caught panic payload destructor escaped");
30212 }
30213 }
30214
30215 struct PanicPayloadOnDrop(mpsc::Sender<()>);
30216 impl Drop for PanicPayloadOnDrop {
30217 fn drop(&mut self) {
30218 let _ = self.0.send(());
30219 panic_any(PanicAgain);
30220 }
30221 }
30222
30223 let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
30224 let (running_tx, running_rx) = mpsc::channel();
30225 let (release_tx, release_rx) = mpsc::channel();
30226 let running = controller
30227 .try_spawn_blocking(move || {
30228 running_tx.send(()).unwrap();
30229 release_rx.recv().unwrap();
30230 })
30231 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
30232 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30233
30234 let (drop_tx, drop_rx) = mpsc::channel();
30235 let payload = PanicPayloadOnDrop(drop_tx);
30236 let queued = controller
30237 .try_spawn_blocking(move || {
30238 let _payload = &payload;
30239 })
30240 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
30241
30242 let (shutdown_tx, shutdown_rx) = mpsc::channel();
30243 let shutdown_controller = Arc::clone(&controller);
30244 let shutdown = std::thread::spawn(move || {
30245 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30246 });
30247 drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
30248 release_tx.send(()).unwrap();
30249 shutdown_rx
30250 .recv_timeout(Duration::from_secs(2))
30251 .expect("hostile panic payload destruction must remain contained")
30252 .unwrap();
30253 shutdown
30254 .join()
30255 .expect("the caught payload must not panic during destruction");
30256 futures::executor::block_on(running).unwrap();
30257 assert!(futures::executor::block_on(queued).is_err());
30258 }
30259
30260 #[cfg(napi_runtime_os_threads)]
30261 #[test]
30262 fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
30263 use std::sync::mpsc;
30264
30265 struct DropSignal(mpsc::Sender<()>);
30266 impl Drop for DropSignal {
30267 fn drop(&mut self) {
30268 let _ = self.0.send(());
30269 }
30270 }
30271
30272 let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
30273 let (running_tx, running_rx) = mpsc::channel();
30274 let (release_tx, release_rx) = mpsc::channel();
30275 let running = controller
30276 .try_spawn_blocking(move || {
30277 running_tx.send(()).unwrap();
30278 release_rx.recv().unwrap();
30279 7usize
30280 })
30281 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30282 running_rx
30283 .recv_timeout(Duration::from_secs(5))
30284 .expect("the first blocking job must hold the only blocking slot");
30285
30286 let queued_ran = Arc::new(AtomicBool::new(false));
30287 let queued_ran_work = Arc::clone(&queued_ran);
30288 let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
30289 let queued_drop_signal = DropSignal(queued_dropped_tx);
30290 let queued = controller
30291 .try_spawn_blocking(move || {
30292 let _drop_signal = queued_drop_signal;
30293 queued_ran_work.store(true, Ordering::SeqCst);
30294 11usize
30295 })
30296 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30297
30298 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30299 let shutdown_controller = Arc::clone(&controller);
30300 let shutdown = std::thread::spawn(move || {
30301 shutdown_controller.shutdown().unwrap();
30302 shutdown_done_tx.send(()).unwrap();
30303 });
30304
30305 queued_dropped_rx
30306 .recv_timeout(Duration::from_secs(5))
30307 .expect("shutdown must drop queued blocking work");
30308 assert!(
30309 !queued_ran.load(Ordering::SeqCst),
30310 "queued blocking work must not execute"
30311 );
30312 assert!(
30313 shutdown_done_rx.try_recv().is_err(),
30314 "shutdown must wait while an accepted blocking closure is still running"
30315 );
30316
30317 release_tx.send(()).unwrap();
30318 shutdown_done_rx
30319 .recv_timeout(Duration::from_secs(5))
30320 .expect("shutdown must finish after running blocking work retires");
30321 shutdown.join().unwrap();
30322 assert_eq!(futures::executor::block_on(running).unwrap(), 7);
30323 assert!(
30324 futures::executor::block_on(queued).is_err(),
30325 "the queued blocking handle must resolve as cancelled"
30326 );
30327 }
30328
30329 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30330 #[test]
30331 fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
30332 use std::sync::mpsc;
30333
30334 struct PanicOnDrop(mpsc::Sender<()>);
30335 impl Drop for PanicOnDrop {
30336 fn drop(&mut self) {
30337 let _ = self.0.send(());
30338 panic!("intentional queued blocking destructor panic");
30339 }
30340 }
30341
30342 let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
30343 let (running_tx, running_rx) = mpsc::channel();
30344 let (release_tx, release_rx) = mpsc::channel();
30345 let running = controller
30346 .try_spawn_blocking(move || {
30347 running_tx.send(()).unwrap();
30348 release_rx.recv().unwrap();
30349 })
30350 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30351 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30352
30353 let (drop_tx, drop_rx) = mpsc::channel();
30354 let panic_on_drop = PanicOnDrop(drop_tx);
30355 let queued = controller
30356 .try_spawn_blocking(move || {
30357 let _keep_captured_until_run = &panic_on_drop;
30358 })
30359 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30360
30361 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30362 let shutdown_controller = Arc::clone(&controller);
30363 let shutdown = std::thread::spawn(move || {
30364 shutdown_controller.shutdown().unwrap();
30365 shutdown_done_tx.send(()).unwrap();
30366 });
30367
30368 drop_rx
30369 .recv_timeout(Duration::from_secs(5))
30370 .expect("shutdown must attempt to drop the queued closure");
30371 release_tx.send(()).unwrap();
30372 shutdown_done_rx
30373 .recv_timeout(Duration::from_secs(5))
30374 .expect("a panicking queued destructor must not strand shutdown");
30375 shutdown
30376 .join()
30377 .expect("shutdown must contain the destructor panic");
30378 futures::executor::block_on(running).unwrap();
30379 assert!(futures::executor::block_on(queued).is_err());
30380
30381 controller
30382 .start()
30383 .expect("the stopped controller must restart");
30384 let restarted = controller
30385 .try_spawn_blocking(|| 17usize)
30386 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30387 assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
30388 controller.shutdown().unwrap();
30389 }
30390
30391 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30392 #[test]
30393 fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
30394 use std::{panic::panic_any, sync::mpsc};
30395
30396 struct ReentrantPayload {
30397 controller: Arc<RuntimeController>,
30398 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
30399 }
30400
30401 impl Drop for ReentrantPayload {
30402 fn drop(&mut self) {
30403 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
30404 let shutdown = self.controller.shutdown();
30405 self.result.send((generation, shutdown)).unwrap();
30406 }
30407 }
30408
30409 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
30410
30411 impl Drop for PanicPayloadOnDrop {
30412 fn drop(&mut self) {
30413 panic_any(self.0.take().expect("the result must panic only once"));
30414 }
30415 }
30416
30417 let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
30418 let generation = controller.backend().generation();
30419 let (started_tx, started_rx) = mpsc::channel();
30420 let (release_tx, release_rx) = mpsc::channel();
30421 let (result_tx, result_rx) = mpsc::channel();
30422 let payload_controller = Arc::clone(&controller);
30423 let handle = controller
30424 .try_spawn_blocking(move || {
30425 started_tx.send(()).unwrap();
30426 release_rx.recv().unwrap();
30427 PanicPayloadOnDrop(Some(ReentrantPayload {
30428 controller: payload_controller,
30429 result: result_tx,
30430 }))
30431 })
30432 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30433 started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30434
30435 drop(handle);
30438 release_tx.send(()).unwrap();
30439 let (observed_generation, reentrant_shutdown) = result_rx
30440 .recv_timeout(Duration::from_secs(5))
30441 .expect("the dropped result panic payload must not self-deadlock");
30442 assert_eq!(observed_generation, Some(generation));
30443 let error =
30444 reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
30445 assert!(error.to_string().contains("work running on that runtime"));
30446
30447 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30448 let shutdown_controller = Arc::clone(&controller);
30449 let shutdown = std::thread::spawn(move || {
30450 shutdown_controller.shutdown().unwrap();
30451 shutdown_done_tx.send(()).unwrap();
30452 });
30453 shutdown_done_rx
30454 .recv_timeout(Duration::from_secs(5))
30455 .expect("a dropped-result destructor panic must not strand shutdown");
30456 shutdown
30457 .join()
30458 .expect("shutdown must contain the destructor panic");
30459
30460 controller
30461 .start()
30462 .expect("the stopped controller must restart");
30463 let restarted = controller
30464 .try_spawn_blocking(|| 19usize)
30465 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30466 assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
30467 controller.shutdown().unwrap();
30468 }
30469
30470 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30471 #[test]
30472 fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
30473 use std::sync::mpsc;
30474
30475 struct PanicOnDrop(mpsc::Sender<()>);
30476
30477 impl Drop for PanicOnDrop {
30478 fn drop(&mut self) {
30479 self.0.send(()).unwrap();
30480 panic!("intentional buffered blocking result destructor panic");
30481 }
30482 }
30483
30484 let options = RuntimeOptions {
30485 flavor: RuntimeFlavor::MultiThread,
30486 worker_threads: 2,
30487 max_blocking_tasks: 1,
30488 thread_name_prefix: "buffered-blocking-result-drop".to_string(),
30489 park_deadline: None,
30490 drain_linger: DEFAULT_DRAIN_LINGER,
30491 };
30492 let executor =
30493 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30494 let (started_tx, started_rx) = mpsc::channel();
30495 let (dropped_tx, dropped_rx) = mpsc::channel();
30496 let handle = executor.schedule_blocking(move || {
30497 started_tx.send(()).unwrap();
30498 PanicOnDrop(dropped_tx)
30499 });
30500 started_rx
30501 .recv_timeout(Duration::from_secs(2))
30502 .expect("the blocking closure must run");
30503 wait_until("the completed blocking result to be buffered", || {
30504 executor.active_blocking.load(Ordering::Acquire) == 0
30505 });
30506 assert!(
30507 dropped_rx.try_recv().is_err(),
30508 "the buffered result must remain owned by the live receiver"
30509 );
30510
30511 let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
30512 assert!(
30513 dropped.is_ok(),
30514 "dropping the buffered result must contain its destructor panic"
30515 );
30516 dropped_rx
30517 .recv_timeout(Duration::from_secs(2))
30518 .expect("dropping the receiver must reclaim the buffered result");
30519 }
30520
30521 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30522 #[test]
30523 fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
30524 use std::sync::mpsc;
30525
30526 struct PanicOnDrop(mpsc::Sender<()>);
30527 impl Drop for PanicOnDrop {
30528 fn drop(&mut self) {
30529 let _ = self.0.send(());
30530 panic!("intentional late blocking destructor panic");
30531 }
30532 }
30533
30534 let executor = multi_thread_executor(2, None, "closed-queue-drop");
30535 executor.begin_shutdown();
30536
30537 let (drop_tx, drop_rx) = mpsc::channel();
30538 let panic_on_drop = PanicOnDrop(drop_tx);
30539 let handle = executor.schedule_blocking(move || {
30540 let _keep_captured_until_run = &panic_on_drop;
30541 });
30542
30543 drop_rx
30544 .recv_timeout(Duration::from_secs(5))
30545 .expect("the closed queue must retire the rejected closure");
30546 assert!(
30547 futures::executor::block_on(handle).is_err(),
30548 "the rejected blocking handle must resolve as cancelled"
30549 );
30550 }
30551
30552 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30553 #[test]
30554 fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
30555 struct PanicWake;
30556 impl std::task::Wake for PanicWake {
30557 fn wake(self: Arc<Self>) {
30558 panic!("intentional timer waker panic");
30559 }
30560 }
30561
30562 let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
30563 let backend = controller.backend();
30564 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30565 panic!("configured MultiThread backend must create a Rayon executor");
30566 };
30567 let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
30568 let waker = Waker::from(Arc::new(PanicWake));
30569 let mut cx = Context::from_waker(&waker);
30570 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30571 drop(backend);
30572
30573 controller
30574 .shutdown()
30575 .expect("shutdown must contain the timer waker panic");
30576 controller
30577 .start()
30578 .expect("the stopped controller must restart");
30579 let restarted = controller
30580 .try_spawn_blocking(|| 23usize)
30581 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30582 assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
30583 controller.shutdown().unwrap();
30584 }
30585
30586 #[cfg(napi_runtime_os_threads)]
30587 #[test]
30588 fn multi_thread_restart_waits_for_old_generation_worker_exit() {
30589 use std::sync::mpsc;
30590
30591 let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
30592 let backend = controller.backend();
30593 let old_generation = backend.generation();
30594 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30595 panic!("configured MultiThread backend must create a Rayon executor");
30596 };
30597 let old_workers = Arc::clone(&executor.worker_lifecycle);
30598 drop(backend);
30599
30600 let (running_tx, running_rx) = mpsc::channel();
30601 let (release_tx, release_rx) = mpsc::channel();
30602 let running = controller
30603 .try_spawn_blocking(move || {
30604 running_tx.send(()).unwrap();
30605 release_rx.recv().unwrap();
30606 })
30607 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30608 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30609
30610 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30611 let shutdown_controller = Arc::clone(&controller);
30612 let shutdown = std::thread::spawn(move || {
30613 shutdown_controller.shutdown().unwrap();
30614 shutdown_done_tx.send(()).unwrap();
30615 });
30616 wait_until("controller entered Stopping", || {
30617 let state = controller
30618 .state
30619 .lock()
30620 .unwrap_or_else(std::sync::PoisonError::into_inner);
30621 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
30622 });
30623
30624 let (start_done_tx, start_done_rx) = mpsc::channel();
30625 let start_controller = Arc::clone(&controller);
30626 let starter = std::thread::spawn(move || {
30627 start_controller.start().unwrap();
30628 start_done_tx.send(()).unwrap();
30629 });
30630 assert!(
30631 start_done_rx.try_recv().is_err(),
30632 "start must wait while shutdown is quiescing"
30633 );
30634 assert!(
30635 old_workers.remaining() > 0,
30636 "the held old generation must still own workers"
30637 );
30638
30639 release_tx.send(()).unwrap();
30640 futures::executor::block_on(running).unwrap();
30641 shutdown_done_rx
30642 .recv_timeout(Duration::from_secs(5))
30643 .unwrap();
30644 start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30645 shutdown.join().unwrap();
30646 starter.join().unwrap();
30647 assert_eq!(
30648 old_workers.remaining(),
30649 0,
30650 "restart may begin only after every old worker exits"
30651 );
30652
30653 let backend = controller.backend();
30654 assert_ne!(backend.generation(), old_generation);
30655 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30656 panic!("restarted backend must remain MultiThread");
30657 };
30658 assert_eq!(
30659 executor.pool.current_num_threads(),
30660 controller.options().worker_threads
30661 );
30662 drop(backend);
30663 controller.shutdown().unwrap();
30664 }
30665
30666 #[cfg(napi_runtime_os_threads)]
30667 #[test]
30668 fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
30669 let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
30670 let task_controller = Arc::clone(&controller);
30671 let handle = controller
30672 .try_spawn(async move {
30673 task_controller
30674 .shutdown()
30675 .expect_err("runtime work must not wait for its own generation")
30676 .to_string()
30677 })
30678 .unwrap_or_else(|_| panic!("running runtime must accept the task"));
30679 let message = futures::executor::block_on(handle).unwrap();
30680 assert!(message.contains("cannot shut down"));
30681 controller.shutdown().unwrap();
30682 }
30683
30684 #[cfg(napi_runtime_os_threads)]
30695 #[test]
30696 fn two_phase_shutdown_matches_single_call() {
30697 let controller = multi_thread_controller("two-phase-shutdown", 2, 1);
30701 let entered = Arc::new(AtomicBool::new(false));
30702 let release = Arc::new(AtomicBool::new(false));
30703 let job_entered = Arc::clone(&entered);
30704 let job_release = Arc::clone(&release);
30705 let handle = controller
30706 .try_spawn_blocking(move || {
30707 job_entered.store(true, Ordering::SeqCst);
30708 while !job_release.load(Ordering::SeqCst) {
30709 std::hint::spin_loop();
30710 }
30711 7usize
30712 })
30713 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30714 while !entered.load(Ordering::SeqCst) {
30715 std::thread::yield_now();
30716 }
30717
30718 assert!(
30719 controller
30720 .begin_shutdown()
30721 .expect("phase 1 on a running generation must be accepted"),
30722 "phase 1 must report the still-running blocking closure instead of waiting for it"
30723 );
30724 assert!(
30725 controller.runtime_work_pending(),
30726 "the parked closure holds its generation guard"
30727 );
30728 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30731 panic!("a submission between the phases must be rejected");
30732 };
30733 assert_eq!(
30734 error.to_string(),
30735 "the async runtime is stopped; call start before submitting work"
30736 );
30737
30738 release.store(true, Ordering::SeqCst);
30739 let deadline = Instant::now() + Duration::from_secs(5);
30740 while controller.runtime_work_pending() {
30741 assert!(
30742 Instant::now() < deadline,
30743 "the released closure must retire and the poll must answer false"
30744 );
30745 std::thread::yield_now();
30746 }
30747 assert_eq!(
30748 futures::executor::block_on(handle).unwrap(),
30749 7usize,
30750 "the closure that phase 1 refused to wait for still delivers its result"
30751 );
30752
30753 controller
30754 .finish_shutdown()
30755 .expect("phase 2 must complete the outstanding phase 1");
30756 {
30757 let state = controller
30758 .state
30759 .lock()
30760 .unwrap_or_else(std::sync::PoisonError::into_inner);
30761 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30762 assert!(
30763 state.draining.is_none(),
30764 "phase 2 must consume the phase-1 handoff"
30765 );
30766 }
30767
30768 controller.start().expect("a stopped runtime must restart");
30770 drop(controller.backend());
30771 controller
30772 .shutdown()
30773 .expect("the single call must still shut the restarted runtime down");
30774 }
30775
30776 #[cfg(napi_runtime_os_threads)]
30777 #[test]
30778 fn concurrent_shutdown_during_two_phase_still_reports_success() {
30779 let controller = Arc::new(multi_thread_controller("two-phase-concurrent", 2, 1));
30790 let entered = Arc::new(AtomicBool::new(false));
30791 let committed = Arc::new(AtomicBool::new(false));
30792 let job_entered = Arc::clone(&entered);
30793 let job_committed = Arc::clone(&committed);
30794 let handle = controller
30798 .try_spawn_blocking(move || {
30799 job_entered.store(true, Ordering::SeqCst);
30800 while !job_committed.load(Ordering::SeqCst) {
30801 std::hint::spin_loop();
30802 }
30803 3usize
30804 })
30805 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30806 wait_until("the blocking closure to park", || {
30807 entered.load(Ordering::SeqCst)
30808 });
30809 assert!(
30810 controller
30811 .begin_shutdown()
30812 .expect("phase 1 must be accepted")
30813 );
30814
30815 let (concurrent_tx, concurrent_rx) = std::sync::mpsc::channel();
30816 let hook_controller = Arc::clone(&controller);
30817 let hook_committed = Arc::clone(&committed);
30818 let owner_took_the_drain = Arc::new(AtomicBool::new(false));
30819 let hook_took_the_drain = Arc::clone(&owner_took_the_drain);
30820 AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30821 *slot.borrow_mut() = Some(Box::new(move || {
30822 hook_took_the_drain.store(true, Ordering::SeqCst);
30823 let concurrent = std::thread::spawn(move || {
30824 hook_committed.store(true, Ordering::SeqCst);
30825 hook_controller.shutdown()
30826 });
30827 concurrent_tx
30828 .send(concurrent)
30829 .expect("the test thread must still be waiting for the concurrent handle");
30830 }));
30831 });
30832
30833 controller
30834 .finish_shutdown()
30835 .expect("phase 2 must complete the handshake");
30836 assert!(
30837 owner_took_the_drain.load(Ordering::SeqCst),
30838 "the phase-1 owner must be the thread that took the handoff"
30839 );
30840 concurrent_rx
30841 .recv()
30842 .expect("the hook must have started the concurrent shutdown")
30843 .join()
30844 .expect("the concurrent shutdown thread must not panic")
30845 .expect("a concurrent single-call shutdown must still succeed");
30846
30847 assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30848 let state = controller
30849 .state
30850 .lock()
30851 .unwrap_or_else(std::sync::PoisonError::into_inner);
30852 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30853 assert!(state.draining.is_none());
30854 }
30855
30856 #[cfg(napi_runtime_os_threads)]
30857 #[test]
30858 fn a_phase_one_during_the_in_flight_drain_answers_without_waiting() {
30859 let controller = Arc::new(multi_thread_controller("two-phase-inflight-drain", 2, 1));
30877 let entered = Arc::new(AtomicBool::new(false));
30878 let release = Arc::new(AtomicBool::new(false));
30879 let job_entered = Arc::clone(&entered);
30880 let job_release = Arc::clone(&release);
30881 let handle = controller
30882 .try_spawn_blocking(move || {
30883 job_entered.store(true, Ordering::SeqCst);
30884 while !job_release.load(Ordering::SeqCst) {
30885 std::hint::spin_loop();
30886 }
30887 11usize
30888 })
30889 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30890 wait_until("the blocking closure to park", || {
30891 entered.load(Ordering::SeqCst)
30892 });
30893 assert!(
30894 controller
30895 .begin_shutdown()
30896 .expect("phase 1 must be accepted"),
30897 "the parked closure must be reported as live"
30898 );
30899
30900 let (probe_tx, probe_rx) = std::sync::mpsc::channel();
30901 let hook_controller = Arc::clone(&controller);
30902 let hook_release = Arc::clone(&release);
30903 AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30904 *slot.borrow_mut() = Some(Box::new(move || {
30905 let probe_controller = Arc::clone(&hook_controller);
30906 let probe = std::thread::spawn(move || {
30907 let pending = probe_controller.runtime_work_pending();
30908 (pending, probe_controller.begin_shutdown())
30909 });
30910 let watchdog = std::thread::spawn(move || {
30911 let deadline = Instant::now() + Duration::from_secs(5);
30912 while !probe.is_finished() && Instant::now() < deadline {
30913 std::thread::yield_now();
30914 }
30915 let answered_during_the_drain = probe.is_finished();
30919 hook_release.store(true, Ordering::SeqCst);
30920 (answered_during_the_drain, probe.join())
30921 });
30922 probe_tx
30923 .send(watchdog)
30924 .expect("the test thread must still be waiting for the probe handle");
30925 }));
30926 });
30927
30928 controller
30929 .finish_shutdown()
30930 .expect("phase 2 must complete the outstanding phase 1");
30931 let (answered_during_the_drain, probe) = probe_rx
30932 .recv()
30933 .expect("the hook must have started the probe")
30934 .join()
30935 .expect("the watchdog thread must not panic");
30936 let (pending, verdict) = probe.expect("the probe thread must not panic");
30937 assert!(
30938 answered_during_the_drain,
30939 "a phase 1 re-entered during the in-flight drain must answer without waiting for the join"
30940 );
30941 assert!(
30942 pending,
30943 "the poll must still see the parked closure while the drain that is waiting for it runs"
30944 );
30945 assert!(
30946 verdict.expect("a phase 1 during the in-flight drain must be accepted"),
30947 "the re-entering phase 1 must report the parked closure as live"
30948 );
30949
30950 assert_eq!(futures::executor::block_on(handle).unwrap(), 11usize);
30951 {
30952 let state = controller
30953 .state
30954 .lock()
30955 .unwrap_or_else(std::sync::PoisonError::into_inner);
30956 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30957 assert!(
30958 state.draining.is_none(),
30959 "publishing `Stopped` must retire the in-flight drain marker too"
30960 );
30961 }
30962 assert!(
30963 !controller.runtime_work_pending(),
30964 "a stopped runtime has no pending work"
30965 );
30966
30967 controller.start().expect("a stopped runtime must restart");
30968 drop(controller.backend());
30969 controller
30970 .shutdown()
30971 .expect("the single call must still shut the restarted runtime down");
30972 }
30973
30974 #[cfg(napi_runtime_os_threads)]
30975 #[test]
30976 fn phase_one_owner_finishes_after_a_concurrent_shutdown_drains() {
30977 let controller = Arc::new(multi_thread_controller("two-phase-owner-finish", 2, 1));
30984 let entered = Arc::new(AtomicBool::new(false));
30985 let release = Arc::new(AtomicBool::new(false));
30986 let job_entered = Arc::clone(&entered);
30987 let job_release = Arc::clone(&release);
30988 let handle = controller
30989 .try_spawn_blocking(move || {
30990 job_entered.store(true, Ordering::SeqCst);
30991 while !job_release.load(Ordering::SeqCst) {
30992 std::hint::spin_loop();
30993 }
30994 3usize
30995 })
30996 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30997 wait_until("the blocking closure to park", || {
30998 entered.load(Ordering::SeqCst)
30999 });
31000 assert!(
31001 controller
31002 .begin_shutdown()
31003 .expect("phase 1 must be accepted"),
31004 "the parked closure must be reported as live"
31005 );
31006
31007 let single_call = Arc::clone(&controller);
31008 let concurrent = std::thread::spawn(move || single_call.shutdown());
31009 release.store(true, Ordering::SeqCst);
31010 concurrent
31011 .join()
31012 .expect("the concurrent shutdown thread must not panic")
31013 .expect("a concurrent single-call shutdown must still succeed");
31014 {
31015 let state = controller
31016 .state
31017 .lock()
31018 .unwrap_or_else(std::sync::PoisonError::into_inner);
31019 assert!(
31020 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31021 "the concurrent call must have published the stop"
31022 );
31023 assert!(
31024 state.draining.is_none(),
31025 "the concurrent call must have consumed the phase-1 handoff"
31026 );
31027 }
31028
31029 controller
31030 .finish_shutdown()
31031 .expect("the thread that began phase 1 must still be told its shutdown completed");
31032 assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
31033
31034 controller.start().expect("a stopped runtime must restart");
31039 drop(controller.backend());
31040 controller
31041 .finish_shutdown()
31042 .expect("phase 2 after a restart is a no-op, not an error");
31043 {
31044 let state = controller
31045 .state
31046 .lock()
31047 .unwrap_or_else(std::sync::PoisonError::into_inner);
31048 assert!(
31049 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31050 "the no-op phase 2 must not stop the restarted generation"
31051 );
31052 }
31053 controller
31054 .shutdown()
31055 .expect("the single call must still shut the restarted runtime down");
31056 }
31057
31058 #[test]
31059 fn begin_shutdown_twice_does_not_block() {
31060 let controller = current_thread_controller("two-phase-reentrant-begin");
31068 drop(controller.backend());
31069
31070 assert!(
31071 !controller
31072 .begin_shutdown()
31073 .expect("phase 1 on an idle generation must be accepted"),
31074 "an idle generation has nothing for the host to wait for"
31075 );
31076 {
31077 let state = controller
31078 .state
31079 .lock()
31080 .unwrap_or_else(std::sync::PoisonError::into_inner);
31081 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_)));
31082 }
31083 assert!(
31084 !controller
31085 .begin_shutdown()
31086 .expect("a second phase 1 must answer immediately"),
31087 "the second phase 1 must report the same verdict"
31088 );
31089
31090 controller
31091 .finish_shutdown()
31092 .expect("one phase 2 completes the single outstanding phase 1");
31093 {
31094 let state = controller
31095 .state
31096 .lock()
31097 .unwrap_or_else(std::sync::PoisonError::into_inner);
31098 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31099 assert!(state.draining.is_none());
31100 }
31101
31102 controller
31103 .finish_shutdown()
31104 .expect("a second phase 2 repeats the completed stop instead of erroring");
31105 {
31106 let state = controller
31107 .state
31108 .lock()
31109 .unwrap_or_else(std::sync::PoisonError::into_inner);
31110 assert!(
31111 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31112 "the repeated phase 2 must change nothing"
31113 );
31114 }
31115 }
31116
31117 #[test]
31118 fn current_thread_two_phase_is_idle_immediately() {
31119 let controller = current_thread_controller("two-phase-current-thread");
31122 drop(controller.backend());
31123 assert!(
31124 !controller.runtime_work_pending(),
31125 "an idle running generation has no pending work"
31126 );
31127
31128 assert!(
31129 !controller
31130 .begin_shutdown()
31131 .expect("phase 1 must be accepted"),
31132 "phase 1 on an idle generation must report nothing to wait for"
31133 );
31134 assert!(
31135 !controller.runtime_work_pending(),
31136 "the poll must stay false across the window"
31137 );
31138 controller.finish_shutdown().expect("phase 2 must complete");
31139 {
31140 let state = controller
31141 .state
31142 .lock()
31143 .unwrap_or_else(std::sync::PoisonError::into_inner);
31144 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31145 }
31146
31147 controller.start().expect("a stopped runtime must restart");
31148 drop(controller.backend());
31149 controller
31150 .shutdown()
31151 .expect("the single call must still work");
31152 }
31153
31154 #[test]
31155 fn finish_shutdown_without_begin_is_an_error() {
31156 let controller = current_thread_controller("two-phase-orphan-finish");
31157 drop(controller.backend());
31158 let error = controller
31159 .finish_shutdown()
31160 .expect_err("phase 2 without an outstanding phase 1 must be rejected");
31161 assert_eq!(
31162 error.to_string(),
31163 "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown"
31164 );
31165 controller
31166 .shutdown()
31167 .expect("the rejected phase 2 must leave the runtime shuttable");
31168 }
31169
31170 #[test]
31171 fn a_restart_does_not_inherit_a_settled_shutdown_handoff() {
31172 let controller = current_thread_controller("two-phase-stale-settled");
31180 drop(controller.backend());
31181 controller
31182 .shutdown()
31183 .expect("the first shutdown must complete");
31184
31185 assert!(
31186 !controller
31187 .begin_shutdown()
31188 .expect("phase 1 on a stopped runtime must be accepted"),
31189 "a stopped runtime has no work for the host to wait for"
31190 );
31191 controller.start().expect("a stopped runtime must restart");
31192 {
31193 let state = controller
31194 .state
31195 .lock()
31196 .unwrap_or_else(std::sync::PoisonError::into_inner);
31197 assert!(
31198 state.draining.is_none(),
31199 "a fresh generation must not inherit the settled handoff"
31200 );
31201 }
31202 drop(controller.backend());
31203
31204 controller
31205 .shutdown()
31206 .expect("the second shutdown must stop the restarted generation");
31207 {
31208 let state = controller
31209 .state
31210 .lock()
31211 .unwrap_or_else(std::sync::PoisonError::into_inner);
31212 assert!(
31213 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31214 "the restarted generation must really be stopped, not reported stopped"
31215 );
31216 assert!(state.draining.is_none());
31217 }
31218 assert!(
31219 controller.current_backend.load().is_none(),
31220 "the stopped generation's backend must be gone"
31221 );
31222 assert!(
31223 !controller.runtime_work_pending(),
31224 "a stopped runtime has no pending work"
31225 );
31226 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31227 panic!("a submission after the second shutdown must be rejected");
31228 };
31229 assert_eq!(
31230 error.to_string(),
31231 "the async runtime is stopped; call start before submitting work"
31232 );
31233 }
31234
31235 #[test]
31236 fn a_settled_phase_one_is_reported_by_every_finish_across_a_restart() {
31237 let controller = current_thread_controller("two-phase-settled-idempotent");
31245 drop(controller.backend());
31246 controller
31247 .shutdown()
31248 .expect("the first shutdown must complete");
31249 assert!(
31250 !controller
31251 .begin_shutdown()
31252 .expect("phase 1 on a stopped runtime must be accepted")
31253 );
31254 controller.start().expect("a stopped runtime must restart");
31255 {
31256 let state = controller
31257 .state
31258 .lock()
31259 .unwrap_or_else(std::sync::PoisonError::into_inner);
31260 assert!(
31261 state.draining.is_none(),
31262 "the restart must drop the settled handoff"
31263 );
31264 assert!(
31265 state.phase_ever_opened,
31266 "the restart must not erase the fact that a stop was announced"
31267 );
31268 }
31269
31270 controller
31271 .finish_shutdown()
31272 .expect("the announced stop must be reported to the caller that opened it");
31273 controller
31274 .finish_shutdown()
31275 .expect("and to every phase 2 after it -- the call is idempotent");
31276 {
31277 let state = controller
31278 .state
31279 .lock()
31280 .unwrap_or_else(std::sync::PoisonError::into_inner);
31281 assert!(
31282 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31283 "neither phase 2 may stop the generation the restart published"
31284 );
31285 }
31286 drop(controller.backend());
31287 controller
31288 .shutdown()
31289 .expect("the no-op phase 2s must leave the runtime shuttable");
31290 }
31291
31292 #[test]
31293 fn shutdown_racing_a_start_still_reports_ok() {
31294 let controller = Arc::new(current_thread_controller("shutdown-racing-start"));
31306 drop(controller.backend());
31307 controller
31308 .shutdown()
31309 .expect("the first shutdown must complete");
31310
31311 let start_controller = Arc::clone(&controller);
31312 let started = Arc::new(AtomicBool::new(false));
31313 let hook_started = Arc::clone(&started);
31314 BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31315 *slot.borrow_mut() = Some(Box::new(move || {
31316 start_controller
31317 .start()
31318 .expect("a stopped runtime must restart");
31319 hook_started.store(true, Ordering::SeqCst);
31320 }));
31321 });
31322 controller
31323 .shutdown()
31324 .expect("a shutdown must report the phase it opened, even across a racing start");
31325 assert!(
31326 started.load(Ordering::SeqCst),
31327 "the hook must have run the start inside the window"
31328 );
31329
31330 {
31331 let state = controller
31332 .state
31333 .lock()
31334 .unwrap_or_else(std::sync::PoisonError::into_inner);
31335 assert!(
31336 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31337 "the racing start wins the window: its generation is live, not stopped"
31338 );
31339 assert!(
31340 state.draining.is_none(),
31341 "the start must have dropped the settled handoff"
31342 );
31343 }
31344
31345 let ran = Arc::new(AtomicBool::new(false));
31348 let ran_task = Arc::clone(&ran);
31349 controller
31350 .try_spawn_detached(async move {
31351 ran_task.store(true, Ordering::SeqCst);
31352 })
31353 .unwrap_or_else(|_| panic!("the generation the start published must accept work"));
31354 let dispatch = controller_current_thread_dispatch(&controller);
31355 controller.drive_current_thread_dispatch(dispatch, false);
31356 assert!(ran.load(Ordering::SeqCst));
31357 controller
31358 .shutdown()
31359 .expect("the restarted generation must still shut down");
31360 let state = controller
31361 .state
31362 .lock()
31363 .unwrap_or_else(std::sync::PoisonError::into_inner);
31364 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31365 assert!(state.draining.is_none());
31366 }
31367
31368 #[cfg(napi_runtime_os_threads)]
31372 #[test]
31373 fn concurrent_split_finish_racing_a_single_call_shutdown() {
31374 let controller = Arc::new(current_thread_controller("split-finish-vs-single"));
31384 drop(controller.backend());
31385
31386 let split_controller = Arc::clone(&controller);
31387 let (tx, rx) = std::sync::mpsc::channel();
31388 BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31389 *slot.borrow_mut() = Some(Box::new(move || {
31390 let split = std::thread::spawn(move || {
31391 let pending = split_controller
31392 .begin_shutdown()
31393 .expect("a second phase 1 must re-report the outstanding handoff");
31394 split_controller
31395 .finish_shutdown()
31396 .expect("an in-protocol phase 2 must complete the outstanding stop");
31397 pending
31398 });
31399 tx.send(
31400 split
31401 .join()
31402 .expect("the split shutdown thread must not panic"),
31403 )
31404 .expect("the test thread must still be waiting for the split result");
31405 }));
31406 });
31407
31408 controller
31409 .shutdown()
31410 .expect("the single call must report the stop it announced, whoever joined it");
31411 assert!(
31412 !rx
31413 .try_recv()
31414 .expect("the hook must have run the split pair inside the window"),
31415 "an idle generation has nothing for the host to wait for"
31416 );
31417
31418 let state = controller
31419 .state
31420 .lock()
31421 .unwrap_or_else(std::sync::PoisonError::into_inner);
31422 assert!(
31423 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31424 "the generation must really be stopped, not reported stopped"
31425 );
31426 assert!(state.draining.is_none());
31427 }
31428
31429 #[test]
31430 fn repeated_begin_start_pairs_leave_no_residue() {
31431 const PAIRS: usize = 50;
31436 let controller = current_thread_controller("begin-start-residue");
31437 drop(controller.backend());
31438 for _ in 0..PAIRS {
31439 controller
31440 .shutdown()
31441 .expect("each shutdown must stop the live generation");
31442 assert!(
31443 !controller
31444 .begin_shutdown()
31445 .expect("phase 1 on a stopped runtime must be accepted"),
31446 "a stopped runtime has no work for the host to wait for"
31447 );
31448 controller
31449 .start()
31450 .expect("a zero-backend window must let a restart through");
31451 let state = controller
31452 .state
31453 .lock()
31454 .unwrap_or_else(std::sync::PoisonError::into_inner);
31455 assert!(
31456 state.draining.is_none(),
31457 "the restart must drop the settled handoff"
31458 );
31459 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Running(_)));
31460 }
31461
31462 controller
31463 .finish_shutdown()
31464 .expect("a phase 2 with nothing outstanding is a no-op Ok");
31465 {
31466 let state = controller
31467 .state
31468 .lock()
31469 .unwrap_or_else(std::sync::PoisonError::into_inner);
31470 assert!(
31471 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31472 "the no-op phase 2 must not stop the generation the last restart published"
31473 );
31474 }
31475
31476 controller
31477 .shutdown()
31478 .expect("a full shutdown after the begin/start pairs must still work");
31479 let state = controller
31480 .state
31481 .lock()
31482 .unwrap_or_else(std::sync::PoisonError::into_inner);
31483 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31484 assert!(state.draining.is_none());
31485 }
31486
31487 #[cfg(napi_runtime_os_threads)]
31488 #[test]
31489 fn a_restart_after_a_settled_phase_one_still_joins_its_workers() {
31490 let controller = multi_thread_controller("two-phase-stale-settled-mt", 2, 1);
31494 drop(controller.backend());
31495 controller
31496 .shutdown()
31497 .expect("the first shutdown must complete");
31498 assert!(
31499 !controller
31500 .begin_shutdown()
31501 .expect("phase 1 on a stopped runtime must be accepted")
31502 );
31503 controller.start().expect("a stopped runtime must restart");
31504
31505 let backend = controller.backend();
31506 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31507 panic!("configured MultiThread backend must create a Rayon executor");
31508 };
31509 let workers = Arc::clone(&executor.worker_lifecycle);
31510 drop(backend);
31511 let handle = controller
31512 .try_spawn_blocking(|| 5usize)
31513 .unwrap_or_else(|_| panic!("the restarted generation must accept work"));
31514 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
31515
31516 controller
31517 .shutdown()
31518 .expect("the second shutdown must stop the restarted generation");
31519 assert_eq!(
31520 workers.remaining(),
31521 0,
31522 "phase 2 must join the restarted generation's workers"
31523 );
31524 {
31525 let state = controller
31526 .state
31527 .lock()
31528 .unwrap_or_else(std::sync::PoisonError::into_inner);
31529 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31530 assert!(state.draining.is_none());
31531 }
31532 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31533 panic!("a submission after the second shutdown must be rejected");
31534 };
31535 assert_eq!(
31536 error.to_string(),
31537 "the async runtime is stopped; call start before submitting work"
31538 );
31539 }
31540
31541 #[cfg(napi_runtime_os_threads)]
31545 struct ParkedRejectedDropFuture {
31546 entered: std::sync::mpsc::Sender<()>,
31547 release: std::sync::mpsc::Receiver<()>,
31548 }
31549
31550 #[cfg(napi_runtime_os_threads)]
31551 impl Future for ParkedRejectedDropFuture {
31552 type Output = ();
31553
31554 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31555 Poll::Pending
31556 }
31557 }
31558
31559 #[cfg(napi_runtime_os_threads)]
31560 impl Drop for ParkedRejectedDropFuture {
31561 fn drop(&mut self) {
31562 self.entered.send(()).unwrap();
31563 self.release.recv().unwrap();
31564 }
31565 }
31566
31567 #[cfg(napi_runtime_os_threads)]
31571 fn park_a_rejected_destructor(
31572 controller: &Arc<RuntimeController>,
31573 fail_backend_creation: bool,
31574 ) -> (std::thread::JoinHandle<()>, std::sync::mpsc::Sender<()>) {
31575 use std::sync::mpsc;
31576
31577 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
31578 let (release_drop_tx, release_drop_rx) = mpsc::channel();
31579 let submit_controller = Arc::clone(controller);
31580 let submitter = std::thread::spawn(move || {
31581 if fail_backend_creation {
31582 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
31583 }
31584 let result = futures::executor::block_on(submit_controller.spawn(ParkedRejectedDropFuture {
31585 entered: drop_entered_tx,
31586 release: release_drop_rx,
31587 }));
31588 assert!(
31589 result.is_err(),
31590 "the submission must be rejected, not accepted"
31591 );
31592 });
31593 drop_entered_rx
31594 .recv_timeout(Duration::from_secs(2))
31595 .expect("the rejected future destructor must start");
31596 (submitter, release_drop_tx)
31597 }
31598
31599 #[cfg(napi_runtime_os_threads)]
31605 fn begin_shutdown_within(
31606 controller: &Arc<RuntimeController>,
31607 what: &str,
31608 timeout: Duration,
31609 ) -> Result<bool, RuntimeConfigError> {
31610 use std::sync::mpsc;
31611
31612 let (tx, rx) = mpsc::channel();
31613 let begin_controller = Arc::clone(controller);
31614 let beginner = std::thread::spawn(move || {
31615 tx.send(begin_controller.begin_shutdown()).unwrap();
31616 });
31617 let answer = rx
31618 .recv_timeout(timeout)
31619 .unwrap_or_else(|_| panic!("phase 1 must not wait: {what}"));
31620 join_within(what, beginner, Duration::from_secs(2));
31621 answer
31622 }
31623
31624 #[cfg(napi_runtime_os_threads)]
31625 #[test]
31626 fn begin_shutdown_does_not_wait_for_rejected_destruction_on_a_stopped_runtime() {
31627 use std::sync::mpsc;
31635
31636 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-stopped"));
31637 drop(controller.backend());
31638 controller
31639 .shutdown()
31640 .expect("the first shutdown must complete");
31641
31642 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31643
31644 assert!(
31645 begin_shutdown_within(
31646 &controller,
31647 "phase 1 on a stopped runtime with a rejected destructor outstanding",
31648 Duration::from_secs(2),
31649 )
31650 .expect("phase 1 on a stopped runtime must be accepted"),
31651 "the outstanding rejected destruction is a wait phase 2 still owes"
31652 );
31653 assert!(
31654 controller.runtime_work_pending(),
31655 "the poll must report the destruction, or the host stops turning the loop that frees it"
31656 );
31657 {
31658 let state = controller
31659 .state
31660 .lock()
31661 .unwrap_or_else(std::sync::PoisonError::into_inner);
31662 assert!(matches!(
31663 &state.lifecycle,
31664 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
31665 ));
31666 assert!(matches!(
31667 &state.draining,
31668 Some(ShutdownDrain::ZeroBackend(
31669 ZeroBackendShutdownTarget::Stopped
31670 ))
31671 ));
31672 }
31673
31674 assert!(
31675 begin_shutdown_within(
31676 &controller,
31677 "a second phase 1 inside the zero-backend window",
31678 Duration::from_secs(2),
31679 )
31680 .expect("a second phase 1 must answer from the outstanding handoff"),
31681 "the second phase 1 must repeat the same verdict"
31682 );
31683
31684 let (finish_tx, finish_rx) = mpsc::channel();
31685 let finish_controller = Arc::clone(&controller);
31686 let finisher = std::thread::spawn(move || {
31687 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31688 });
31689 assert!(
31690 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31691 "phase 2 owns the wait phase 1 refused"
31692 );
31693
31694 release_drop_tx.send(()).unwrap();
31695 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31696 finish_rx
31697 .recv_timeout(Duration::from_secs(2))
31698 .expect("phase 2 must return once the destructor retires")
31699 .expect("phase 2 must complete the announced stop");
31700 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31701
31702 {
31703 let state = controller
31704 .state
31705 .lock()
31706 .unwrap_or_else(std::sync::PoisonError::into_inner);
31707 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31708 assert!(state.draining.is_none());
31709 }
31710 }
31711
31712 #[cfg(napi_runtime_os_threads)]
31713 #[test]
31714 fn begin_shutdown_does_not_wait_for_rejected_destruction_on_an_initial_runtime() {
31715 use std::sync::mpsc;
31721
31722 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-initial"));
31723 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, true);
31724
31725 assert!(
31726 begin_shutdown_within(
31727 &controller,
31728 "phase 1 on an initial runtime with a rejected destructor outstanding",
31729 Duration::from_secs(2),
31730 )
31731 .expect("phase 1 on an initial runtime must be accepted"),
31732 "the outstanding rejected destruction is a wait phase 2 still owes"
31733 );
31734 assert!(controller.runtime_work_pending());
31735 {
31736 let state = controller
31737 .state
31738 .lock()
31739 .unwrap_or_else(std::sync::PoisonError::into_inner);
31740 assert!(matches!(
31741 &state.lifecycle,
31742 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
31743 ));
31744 assert!(matches!(
31745 &state.draining,
31746 Some(ShutdownDrain::ZeroBackend(
31747 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
31748 ))
31749 ));
31750 }
31751 assert!(
31752 controller.try_spawn_detached(async {}).is_err(),
31753 "the announced stop must keep first-generation admission closed"
31754 );
31755
31756 let (finish_tx, finish_rx) = mpsc::channel();
31757 let finish_controller = Arc::clone(&controller);
31758 let finisher = std::thread::spawn(move || {
31759 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31760 });
31761 assert!(
31762 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31763 "phase 2 owns the wait phase 1 refused"
31764 );
31765
31766 release_drop_tx.send(()).unwrap();
31767 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31768 finish_rx
31769 .recv_timeout(Duration::from_secs(2))
31770 .expect("phase 2 must return once the destructor retires")
31771 .expect("phase 2 must complete the announced stop");
31772 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31773
31774 {
31775 let state = controller
31776 .state
31777 .lock()
31778 .unwrap_or_else(std::sync::PoisonError::into_inner);
31779 assert!(
31780 matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse),
31781 "a generation that never had a backend must stop before first use"
31782 );
31783 assert!(state.draining.is_none());
31784 }
31785 }
31786
31787 #[cfg(napi_runtime_os_threads)]
31788 #[test]
31789 fn start_waits_out_a_zero_backend_phase_one() {
31790 use std::sync::mpsc;
31798
31799 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-start"));
31800 let old_generation = controller.backend().generation();
31801 controller
31802 .shutdown()
31803 .expect("the first shutdown must complete");
31804
31805 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31806 assert!(
31807 begin_shutdown_within(
31808 &controller,
31809 "phase 1 before the restart",
31810 Duration::from_secs(2),
31811 )
31812 .expect("phase 1 on a stopped runtime must be accepted")
31813 );
31814
31815 let (start_tx, start_rx) = mpsc::channel();
31816 let start_controller = Arc::clone(&controller);
31817 let starter = std::thread::spawn(move || {
31818 start_tx.send(start_controller.start()).unwrap();
31819 });
31820 assert!(
31821 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31822 "a restart must wait for the announced zero-backend stop to be published"
31823 );
31824
31825 let (finish_tx, finish_rx) = mpsc::channel();
31826 let finish_controller = Arc::clone(&controller);
31827 let finisher = std::thread::spawn(move || {
31828 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31829 });
31830 assert!(
31831 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31832 "phase 2 must still be waiting on the destructor"
31833 );
31834
31835 release_drop_tx.send(()).unwrap();
31836 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31837 finish_rx
31838 .recv_timeout(Duration::from_secs(2))
31839 .expect("phase 2 must return once the destructor retires")
31840 .expect("phase 2 must complete the announced stop");
31841 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31842 start_rx
31843 .recv_timeout(Duration::from_secs(2))
31844 .expect("the restart must be admitted once phase 2 has published")
31845 .expect("a stopped runtime must restart after phase 2");
31846 join_within("the restart thread", starter, Duration::from_secs(2));
31847
31848 let new_generation = controller.backend().generation();
31849 assert_ne!(
31850 new_generation, old_generation,
31851 "the restart must publish a fresh generation"
31852 );
31853 controller
31854 .shutdown()
31855 .expect("the restarted generation must still shut down");
31856 }
31857
31858 #[cfg(napi_runtime_os_threads)]
31859 #[test]
31860 fn zero_backend_phase_one_settles_through_the_poll_before_finish() {
31861 use std::sync::mpsc;
31871
31872 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-poll"));
31873 drop(controller.backend());
31874 controller
31875 .shutdown()
31876 .expect("the first shutdown must complete");
31877
31878 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31879 assert!(
31880 begin_shutdown_within(
31881 &controller,
31882 "phase 1 before the loader's poll loop",
31883 Duration::from_secs(2),
31884 )
31885 .expect("phase 1 on a stopped runtime must be accepted"),
31886 "the outstanding rejected destruction is a wait phase 2 still owes"
31887 );
31888 assert!(
31889 controller.runtime_work_pending(),
31890 "the loader gates its poll loop on this, so it must report the destruction first"
31891 );
31892
31893 release_drop_tx.send(()).unwrap();
31894 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31895
31896 let deadline = std::time::Instant::now() + Duration::from_secs(5);
31899 while controller.runtime_work_pending() {
31900 assert!(
31901 std::time::Instant::now() < deadline,
31902 "the poll must stop reporting work once the rejected destructor retires"
31903 );
31904 std::thread::sleep(Duration::from_millis(1));
31905 }
31906
31907 let (finish_tx, finish_rx) = mpsc::channel();
31908 let finish_controller = Arc::clone(&controller);
31909 let finisher = std::thread::spawn(move || {
31910 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31911 });
31912 finish_rx
31913 .recv_timeout(Duration::from_millis(200))
31914 .expect("phase 2 entered after the poll cleared must not block")
31915 .expect("phase 2 must complete the announced stop");
31916 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31917
31918 {
31919 let state = controller
31920 .state
31921 .lock()
31922 .unwrap_or_else(std::sync::PoisonError::into_inner);
31923 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31924 assert!(state.draining.is_none());
31925 }
31926 }
31927
31928 #[cfg(napi_runtime_os_threads)]
31939 fn assert_poll_reports_pending_while_held(
31940 controller: RuntimeController,
31941 what: &str,
31942 hold: impl FnOnce(&RuntimeController, &RuntimeBackend),
31943 ) {
31944 use std::sync::mpsc;
31945
31946 let controller = Arc::new(controller);
31947 let backend = controller.backend();
31948 assert!(
31949 !controller
31950 .begin_shutdown()
31951 .expect("phase 1 on an idle generation must be accepted"),
31952 "an idle generation has nothing for the host to wait for"
31953 );
31954 assert!(
31955 !controller.runtime_work_pending(),
31956 "with every lock free the poll must read the idle generation"
31957 );
31958
31959 hold(&controller, &backend);
31960 let (pending_tx, pending_rx) = mpsc::channel();
31961 let poll_controller = Arc::clone(&controller);
31962 let poller = std::thread::spawn(move || {
31963 pending_tx
31964 .send(poll_controller.runtime_work_pending())
31965 .unwrap();
31966 });
31967 let pending = pending_rx
31968 .recv_timeout(Duration::from_secs(5))
31969 .unwrap_or_else(|_| panic!("the work-pending poll must not wait for {what}"));
31970 join_within(what, poller, Duration::from_secs(2));
31971 assert!(
31972 pending,
31973 "a poll that cannot read {what} must answer pending, not idle"
31974 );
31975
31976 std::mem::forget(backend);
31977 std::mem::forget(controller);
31978 }
31979
31980 #[cfg(napi_runtime_os_threads)]
31981 fn current_thread_executor(backend: &RuntimeBackend) -> &CurrentThreadExecutor {
31982 match &backend.executor {
31983 RuntimeExecutor::CurrentThread(executor) => executor,
31984 RuntimeExecutor::MultiThread(_) => panic!("expected a CurrentThread backend"),
31985 }
31986 }
31987
31988 #[cfg(napi_runtime_os_threads)]
31989 #[test]
31990 fn work_pending_poll_does_not_wait_for_a_held_controller_lock() {
31991 for controller in [
31992 current_thread_controller("poll-held-controller-lock-ct"),
31993 multi_thread_controller("poll-held-controller-lock-mt", 2, 1),
31994 ] {
31995 assert_poll_reports_pending_while_held(
31996 controller,
31997 "the controller state lock",
31998 |controller, _| {
31999 std::mem::forget(controller.state.lock().unwrap());
32000 },
32001 );
32002 }
32003 }
32004
32005 #[cfg(napi_runtime_os_threads)]
32006 #[test]
32007 fn work_pending_poll_does_not_wait_for_a_held_generation_lock() {
32008 for controller in [
32009 current_thread_controller("poll-held-generation-lock-ct"),
32010 multi_thread_controller("poll-held-generation-lock-mt", 2, 1),
32011 ] {
32012 assert_poll_reports_pending_while_held(
32013 controller,
32014 "the generation state lock",
32015 |_, backend| {
32016 std::mem::forget(backend.work.state.lock().unwrap());
32017 },
32018 );
32019 }
32020 }
32021
32022 #[cfg(napi_runtime_os_threads)]
32023 #[test]
32024 fn current_thread_work_pending_poll_does_not_wait_for_a_held_queue_lock() {
32025 assert_poll_reports_pending_while_held(
32026 current_thread_controller("poll-held-queue-lock"),
32027 "the CurrentThread queue lock",
32028 |_, backend| {
32029 std::mem::forget(current_thread_executor(backend).queue.lock().unwrap());
32030 },
32031 );
32032 }
32033
32034 #[cfg(napi_runtime_os_threads)]
32035 #[test]
32036 fn current_thread_work_pending_poll_does_not_wait_for_a_held_scheduler_lock() {
32037 assert_poll_reports_pending_while_held(
32038 current_thread_controller("poll-held-scheduler-lock"),
32039 "the CurrentThread scheduler lock",
32040 |_, backend| {
32041 std::mem::forget(
32042 current_thread_executor(backend)
32043 .scheduler_idle_lock
32044 .lock()
32045 .unwrap(),
32046 );
32047 },
32048 );
32049 }
32050
32051 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32061 fn assert_shutdown_phase_gives_up_after_a_thread_crash(
32062 what: &str,
32063 controller: Arc<RuntimeController>,
32064 phase: impl FnOnce(&RuntimeController) + Send + 'static,
32065 ) {
32066 use std::sync::mpsc;
32067
32068 let crashed = Arc::new(AtomicBool::new(false));
32069 let (outcome_tx, outcome_rx) = mpsc::channel();
32070 let js_thread = {
32071 let controller = Arc::clone(&controller);
32072 let crashed = Arc::clone(&crashed);
32073 std::thread::spawn(move || {
32074 crate::sync::watch_crash_flag_for_test(crashed);
32075 let outcome = match catch_unwind(AssertUnwindSafe(|| phase(&controller))) {
32076 Ok(()) => "returned",
32077 Err(payload) if payload.is::<crate::sync::GaveUpAfterThreadCrash>() => "gave up",
32078 Err(_) => "panicked",
32079 };
32080 outcome_tx.send(outcome).unwrap();
32081 })
32082 };
32083 assert!(
32084 outcome_rx.recv_timeout(Duration::from_millis(200)).is_err(),
32085 "{what}: with no thread crashed, the phase must keep waiting"
32086 );
32087 crashed.store(true, Ordering::SeqCst);
32088 let outcome = outcome_rx
32089 .recv_timeout(Duration::from_secs(5))
32090 .unwrap_or_else(|_| panic!("{what}: the phase must stop waiting once a thread crashed"));
32091 join_within(what, js_thread, Duration::from_secs(2));
32092 assert_eq!(outcome, "gave up", "{what}");
32093 std::mem::forget(controller);
32094 }
32095
32096 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32097 #[test]
32098 fn begin_shutdown_gives_up_on_a_held_lock_after_a_thread_crash() {
32099 type Hold = fn(&RuntimeController, &RuntimeBackend);
32100 let holds: [(&str, Hold); 2] = [
32101 ("the controller state lock", |controller, _| {
32102 std::mem::forget(controller.state.lock().unwrap());
32103 }),
32104 ("the generation state lock", |_, backend| {
32106 std::mem::forget(backend.work.state.lock().unwrap());
32107 }),
32108 ];
32109 for (what, hold) in holds {
32110 for controller in [
32111 current_thread_controller("crash-begin-ct"),
32112 multi_thread_controller("crash-begin-mt", 2, 1),
32113 ] {
32114 let controller = Arc::new(controller);
32115 let backend = controller.backend();
32116 hold(&controller, &backend);
32117 assert_shutdown_phase_gives_up_after_a_thread_crash(
32118 &format!("begin_shutdown on {what}"),
32119 controller,
32120 |controller| {
32121 let _ = controller.begin_shutdown();
32122 },
32123 );
32124 std::mem::forget(backend);
32125 }
32126 }
32127 }
32128
32129 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32130 #[test]
32131 fn finish_shutdown_gives_up_after_a_thread_crash() {
32132 for controller in [
32133 current_thread_controller("crash-finish-work-ct"),
32134 multi_thread_controller("crash-finish-work-mt", 2, 1),
32135 ] {
32136 let controller = Arc::new(controller);
32139 let backend = controller.backend();
32140 let work = backend
32141 .work
32142 .try_register_work()
32143 .expect("an open generation must accept work");
32144 assert!(
32145 controller
32146 .begin_shutdown()
32147 .expect("phase 1 must be accepted"),
32148 "phase 1 must report the registered work"
32149 );
32150 std::mem::forget(work);
32151 assert_shutdown_phase_gives_up_after_a_thread_crash(
32152 "finish_shutdown on work that never retires",
32153 controller,
32154 |controller| {
32155 let _ = controller.finish_shutdown();
32156 },
32157 );
32158 std::mem::forget(backend);
32159 }
32160 for controller in [
32161 current_thread_controller("crash-finish-lock-ct"),
32162 multi_thread_controller("crash-finish-lock-mt", 2, 1),
32163 ] {
32164 let controller = Arc::new(controller);
32165 let backend = controller.backend();
32166 assert!(
32167 !controller
32168 .begin_shutdown()
32169 .expect("phase 1 on an idle generation must be accepted"),
32170 "an idle generation has nothing for the host to wait for"
32171 );
32172 std::mem::forget(backend.work.state.lock().unwrap());
32173 assert_shutdown_phase_gives_up_after_a_thread_crash(
32174 "finish_shutdown on the generation state lock",
32175 controller,
32176 |controller| {
32177 let _ = controller.finish_shutdown();
32178 },
32179 );
32180 std::mem::forget(backend);
32181 }
32182 }
32183
32184 #[cfg(napi_runtime_os_threads)]
32191 #[test]
32192 fn multi_thread_blocking_active_never_exceeds_max_blocking() {
32193 use std::sync::{Barrier, mpsc};
32215 use std::time::Duration;
32216
32217 let (done_tx, done_rx) = mpsc::channel();
32218 let peak_out = Arc::new(Mutex::new(None));
32219 let peak_slot = Arc::clone(&peak_out);
32220 let runner = std::thread::spawn(move || {
32221 let metrics = Arc::new(RuntimeMetrics::default());
32222 let options = RuntimeOptions {
32223 flavor: RuntimeFlavor::MultiThread,
32224 worker_threads: 4,
32225 max_blocking_tasks: 2,
32226 thread_name_prefix: "rd9-blk-cap".to_string(),
32227 park_deadline: None,
32228 drain_linger: DEFAULT_DRAIN_LINGER,
32229 };
32230 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32231
32232 let holder_count = options.max_blocking_tasks; let extra_count = 4usize; let (held_tx, held_rx) = mpsc::channel();
32237 let release = Arc::new(Barrier::new(holder_count + 1));
32238 let mut holder_handles = Vec::new();
32239 for _ in 0..holder_count {
32240 let held_tx = held_tx.clone();
32241 let release = Arc::clone(&release);
32242 holder_handles.push(executor.schedule_blocking(move || {
32243 held_tx.send(()).unwrap();
32244 release.wait();
32245 0usize
32246 }));
32247 }
32248 for _ in 0..holder_count {
32250 held_rx.recv().unwrap();
32251 }
32252
32253 let (extra_started_tx, extra_started_rx) = mpsc::channel();
32255 let mut extra_handles = Vec::new();
32256 for i in 0..extra_count {
32257 let extra_started_tx = extra_started_tx.clone();
32258 extra_handles.push(executor.schedule_blocking(move || {
32259 extra_started_tx.send(i).unwrap();
32260 i * 2
32261 }));
32262 }
32263
32264 assert!(
32267 matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
32268 "a beyond-cap blocking job ran before the cap was released (cap check broken)"
32269 );
32270
32271 release.wait();
32273
32274 for handle in holder_handles {
32275 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
32276 }
32277 for (i, handle) in extra_handles.into_iter().enumerate() {
32280 assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
32281 }
32282
32283 assert_eq!(
32286 metrics.blocking_tasks_started.load(Ordering::Relaxed),
32287 (holder_count + extra_count) as u64
32288 );
32289
32290 *peak_slot
32291 .lock()
32292 .unwrap_or_else(std::sync::PoisonError::into_inner) =
32293 Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
32294 let _ = done_tx.send(());
32295 });
32296
32297 match done_rx.recv_timeout(Duration::from_secs(10)) {
32298 Ok(()) => runner.join().unwrap(),
32299 Err(error) => panic!("blocking-cap test did not complete ({error})"),
32300 }
32301 let peak = peak_out
32302 .lock()
32303 .unwrap_or_else(std::sync::PoisonError::into_inner)
32304 .expect("peak captured");
32305 assert!(
32306 peak >= 1,
32307 "at least one blocking job must have run (peak {peak})"
32308 );
32309 assert!(
32310 peak <= 2,
32311 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
32312 );
32313 }
32314
32315 #[cfg(napi_runtime_os_threads)]
32316 #[test]
32317 fn multi_thread_drain_budget_completes_large_runnable_batch() {
32318 use std::sync::mpsc;
32323 use std::time::{Duration, Instant};
32324
32325 let (done_tx, done_rx) = mpsc::channel();
32326 let runner = std::thread::spawn(move || {
32327 let metrics = Arc::new(RuntimeMetrics::default());
32328 let options = RuntimeOptions {
32329 flavor: RuntimeFlavor::MultiThread,
32330 worker_threads: 2,
32331 max_blocking_tasks: 2,
32332 thread_name_prefix: "rd9-budget".to_string(),
32333 park_deadline: None,
32334 drain_linger: DEFAULT_DRAIN_LINGER,
32335 };
32336 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32337
32338 let task_count = 200usize; let completed = Arc::new(AtomicU64::new(0));
32340 let mut tasks = Vec::new();
32341 for _ in 0..task_count {
32342 let completed = Arc::clone(&completed);
32343 let scheduler = Arc::clone(&executor);
32344 let (runnable, task) = async_task::spawn(
32345 async move {
32346 completed.fetch_add(1, Ordering::Relaxed);
32347 },
32348 move |r| scheduler.schedule(r),
32349 );
32350 executor.schedule(runnable);
32351 tasks.push(task);
32352 }
32353 for task in tasks {
32354 futures::executor::block_on(task);
32355 }
32356
32357 assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
32360 assert_eq!(
32361 metrics.runnable_polls.load(Ordering::Relaxed),
32362 task_count as u64
32363 );
32364
32365 let deadline = Instant::now() + Duration::from_secs(5);
32369 loop {
32370 let active = metrics.active_runnables.load(Ordering::Relaxed);
32371 let queued = metrics.queued_runnables.load(Ordering::Relaxed);
32372 if active == 0 && queued == 0 {
32373 break;
32374 }
32375 assert!(
32376 Instant::now() < deadline,
32377 "runnable counters did not settle to 0: active={active} queued={queued}"
32378 );
32379 std::thread::yield_now();
32380 }
32381 let _ = done_tx.send(());
32382 });
32383
32384 match done_rx.recv_timeout(Duration::from_secs(15)) {
32385 Ok(()) => runner.join().unwrap(),
32386 Err(error) => panic!("drain-budget test did not complete ({error})"),
32387 }
32388 }
32389
32390 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32391 #[test]
32392 fn multi_thread_spawned_panic_surfaces_as_join_error() {
32393 use std::sync::mpsc;
32400 use std::time::Duration;
32401
32402 let (done_tx, done_rx) = mpsc::channel();
32403 let runner = std::thread::spawn(move || {
32404 let metrics = Arc::new(RuntimeMetrics::default());
32405 let options = RuntimeOptions {
32406 flavor: RuntimeFlavor::MultiThread,
32407 worker_threads: 2,
32408 max_blocking_tasks: 2,
32409 thread_name_prefix: "rd9-panic".to_string(),
32410 park_deadline: None,
32411 drain_linger: DEFAULT_DRAIN_LINGER,
32412 };
32413 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
32414
32415 let scheduler = Arc::clone(&executor);
32416 let wrapped = async {
32417 AssertUnwindSafe(async { panic!("x") })
32418 .catch_unwind()
32419 .await
32420 .map(|()| ContainedTaskOutput::new((), u64::MAX))
32421 .map_err(panic_payload_to_join_error)
32422 };
32423 let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
32424 executor.schedule(runnable);
32425
32426 let result: Result<(), JoinError> =
32427 futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
32428 task: task.fallible(),
32429 awaiter: None,
32430 dependency: Arc::new(TaskDependency::default()),
32431 }));
32432 let _ = done_tx.send(result);
32433 });
32434
32435 let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
32436 Ok(result) => {
32437 runner.join().unwrap();
32438 result
32439 }
32440 Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
32441 };
32442 let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
32443 assert_eq!(error.to_string(), "x");
32444 }
32445
32446 #[cfg(napi_runtime_os_threads)]
32447 #[test]
32448 fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
32449 use std::sync::mpsc;
32450
32451 let metrics = Arc::new(RuntimeMetrics::default());
32452 metrics.tasks_spawned.store(1, Ordering::Relaxed);
32453 metrics.tasks_completed.store(2, Ordering::Relaxed);
32454 metrics.tasks_panicked.store(3, Ordering::Relaxed);
32455 metrics.runnable_schedules.store(4, Ordering::Relaxed);
32456 metrics.runnable_polls.store(5, Ordering::Relaxed);
32457 metrics
32458 .blocking_tasks_scheduled
32459 .store(14, Ordering::Relaxed);
32460 metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
32461 metrics
32462 .blocking_tasks_completed
32463 .store(11, Ordering::Relaxed);
32464 metrics.runnable_scheduled();
32465 let (guards_live_tx, guards_live_rx) = mpsc::channel();
32466 let (release_tx, release_rx) = mpsc::channel();
32467 let guard_metrics = Arc::clone(&metrics);
32468 let guard_thread = std::thread::spawn(move || {
32469 let runnable_guard = guard_metrics.runnable_started();
32470 let blocking_guard = guard_metrics.blocking_started(true);
32471 guards_live_tx.send(()).unwrap();
32472 release_rx.recv().unwrap();
32473 drop(runnable_guard);
32474 drop(blocking_guard);
32475 });
32476 guards_live_rx.recv().unwrap();
32477 let fingerprint_before_reset = metrics.progress_fingerprint();
32478
32479 metrics.reset();
32480
32481 assert!(
32482 metrics.progress_fingerprint() != fingerprint_before_reset,
32483 "a reset must advance the deadlock detector generation even if counters later repeat"
32484 );
32485 assert_eq!(
32486 metrics.tasks_spawned.load(Ordering::Relaxed),
32487 0,
32488 "tasks_spawned"
32489 );
32490 assert_eq!(
32491 metrics.tasks_completed.load(Ordering::Relaxed),
32492 0,
32493 "tasks_completed"
32494 );
32495 assert_eq!(
32496 metrics.tasks_panicked.load(Ordering::Relaxed),
32497 0,
32498 "tasks_panicked"
32499 );
32500 assert_eq!(
32501 metrics.runnable_schedules.load(Ordering::Relaxed),
32502 0,
32503 "runnable_schedules"
32504 );
32505 assert_eq!(
32506 metrics.runnable_polls.load(Ordering::Relaxed),
32507 0,
32508 "runnable_polls"
32509 );
32510 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
32511 assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
32512 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
32513 assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
32514 assert_eq!(
32515 metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
32516 0,
32517 "blocking_tasks_scheduled"
32518 );
32519 assert_eq!(
32520 metrics.blocking_tasks_started.load(Ordering::Relaxed),
32521 0,
32522 "blocking_tasks_started"
32523 );
32524 assert_eq!(
32525 metrics.blocking_tasks_completed.load(Ordering::Relaxed),
32526 0,
32527 "blocking_tasks_completed"
32528 );
32529 assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
32530 assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
32531
32532 release_tx.send(()).unwrap();
32533 guard_thread.join().unwrap();
32534 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
32535 assert_eq!(
32536 metrics.active_blocking_tasks.load(Ordering::Relaxed),
32537 0,
32538 "a live blocking guard completed after reset without unsigned underflow"
32539 );
32540 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
32541 }
32542
32543 #[cfg(napi_runtime_os_threads)]
32544 #[test]
32545 fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
32546 use std::sync::mpsc;
32547
32548 fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
32549 RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
32550 *slot.borrow_mut() = Some(Box::new(move || {
32551 entered.send(()).unwrap();
32552 release.recv().unwrap();
32553 }));
32554 });
32555 }
32556
32557 let options = RuntimeOptions {
32558 flavor: RuntimeFlavor::CurrentThread,
32559 worker_threads: 1,
32560 max_blocking_tasks: 1,
32561 thread_name_prefix: "metrics-snapshot".to_string(),
32562 park_deadline: None,
32563 drain_linger: DEFAULT_DRAIN_LINGER,
32564 };
32565
32566 let queued_metrics = Arc::new(RuntimeMetrics::default());
32567 let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
32568 let (queued_release_tx, queued_release_rx) = mpsc::channel();
32569 let queued_writer_metrics = Arc::clone(&queued_metrics);
32570 let queued_writer = std::thread::spawn(move || {
32571 pause_next_high_water_update(queued_entered_tx, queued_release_rx);
32572 queued_writer_metrics.runnable_scheduled();
32573 });
32574 queued_entered_rx.recv().unwrap();
32575 assert_eq!(
32576 queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
32577 0
32578 );
32579 let snapshot = queued_metrics.snapshot(&options);
32580 assert_eq!(snapshot.queued_runnables, 1);
32581 assert_eq!(snapshot.max_queued_runnables, 1);
32582 queued_release_tx.send(()).unwrap();
32583 queued_writer.join().unwrap();
32584 queued_metrics.runnable_cancelled();
32585
32586 let active_metrics = Arc::new(RuntimeMetrics::default());
32587 active_metrics.queued_runnables.store(1, Ordering::Relaxed);
32588 let (active_entered_tx, active_entered_rx) = mpsc::channel();
32589 let (active_release_tx, active_release_rx) = mpsc::channel();
32590 let active_writer_metrics = Arc::clone(&active_metrics);
32591 let active_writer = std::thread::spawn(move || {
32592 pause_next_high_water_update(active_entered_tx, active_release_rx);
32593 let _active = active_writer_metrics.runnable_started();
32594 });
32595 active_entered_rx.recv().unwrap();
32596 assert_eq!(
32597 active_metrics.max_active_runnables.load(Ordering::Relaxed),
32598 0
32599 );
32600 let snapshot = active_metrics.snapshot(&options);
32601 assert_eq!(snapshot.active_runnables, 1);
32602 assert_eq!(snapshot.max_active_runnables, 1);
32603 active_release_tx.send(()).unwrap();
32604 active_writer.join().unwrap();
32605
32606 let blocking_metrics = Arc::new(RuntimeMetrics::default());
32607 let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
32608 let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
32609 let blocking_writer_metrics = Arc::clone(&blocking_metrics);
32610 let blocking_writer = std::thread::spawn(move || {
32611 pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
32612 let _active = blocking_writer_metrics.blocking_started(true);
32613 });
32614 blocking_entered_rx.recv().unwrap();
32615 assert_eq!(
32616 blocking_metrics
32617 .max_active_blocking_tasks
32618 .load(Ordering::Relaxed),
32619 0
32620 );
32621 let snapshot = blocking_metrics.snapshot(&options);
32622 assert_eq!(snapshot.active_blocking_tasks, 1);
32623 assert_eq!(snapshot.max_active_blocking_tasks, 1);
32624 blocking_release_tx.send(()).unwrap();
32625 blocking_writer.join().unwrap();
32626 }
32627
32628 #[cfg(panic = "unwind")]
32629 #[test]
32630 fn runtime_metrics_reset_fails_before_generation_wraparound() {
32631 let metrics = RuntimeMetrics::default();
32632 metrics
32633 .reset_generation
32634 .store(u64::MAX - 1, Ordering::SeqCst);
32635 metrics.tasks_spawned.store(7, Ordering::Relaxed);
32636
32637 let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
32638 assert!(exhausted.is_err());
32639 assert!(!metrics.reset_lock.is_poisoned());
32640 assert_eq!(
32641 metrics.reset_generation.load(Ordering::SeqCst),
32642 u64::MAX - 1
32643 );
32644 assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
32645 }
32646
32647 struct NeverReady;
32659 impl Future for NeverReady {
32660 type Output = ();
32661 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32662 Poll::Pending
32663 }
32664 }
32665
32666 #[cfg(napi_runtime_os_threads)]
32667 struct ReadyAfterOwnerLanePublication {
32668 dependency: BlockingDependency,
32669 ready: Arc<AtomicBool>,
32670 }
32671
32672 #[cfg(napi_runtime_os_threads)]
32673 impl Future for ReadyAfterOwnerLanePublication {
32674 type Output = ();
32675
32676 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32677 if self.ready.load(Ordering::SeqCst) {
32678 Poll::Ready(())
32679 } else {
32680 let mut published = PublishedBlockingDependencies::default();
32683 record_blocking_dependency(&mut published, self.dependency);
32684 Poll::Pending
32685 }
32686 }
32687 }
32688
32689 #[cfg(panic = "unwind")]
32690 #[test]
32691 fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
32692 use std::sync::mpsc;
32699
32700 let (done_tx, done_rx) = mpsc::channel();
32701 let runner = std::thread::spawn(move || {
32702 let metrics = Arc::new(RuntimeMetrics::default());
32703 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32706
32707 let payload = catch_unwind(AssertUnwindSafe(|| {
32708 let mut future = std::pin::pin!(NeverReady);
32709 executor.block_on(future.as_mut());
32710 }))
32711 .expect_err("a threadless park with an empty queue and no pending wake must panic");
32712 let diagnostic = payload
32713 .downcast_ref::<BlockOnDeadlock>()
32714 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
32715 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
32716 assert_eq!(
32717 diagnostic.park_deadline, None,
32718 "the certain case carries no deadline"
32719 );
32720 let message = diagnostic.to_string();
32721 assert!(
32722 message.contains("block_on") && message.contains("JS"),
32723 "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
32724 );
32725 let _ = done_tx.send(());
32726 });
32727
32728 match done_rx.recv_timeout(Duration::from_secs(10)) {
32729 Ok(()) => runner.join().unwrap(),
32730 Err(error) => panic!(
32731 "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
32732 ),
32733 }
32734 }
32735
32736 #[cfg(napi_runtime_os_threads)]
32737 #[test]
32738 fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
32739 use std::sync::mpsc;
32745
32746 struct PendThenReady {
32747 polls: usize,
32748 }
32749 impl Future for PendThenReady {
32750 type Output = ();
32751 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
32752 self.polls += 1;
32753 if self.polls >= 2 {
32754 Poll::Ready(())
32755 } else {
32756 cx.waker().wake_by_ref();
32757 Poll::Pending
32758 }
32759 }
32760 }
32761
32762 let (done_tx, done_rx) = mpsc::channel();
32763 let runner = std::thread::spawn(move || {
32764 let metrics = Arc::new(RuntimeMetrics::default());
32765 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32766 let mut future = std::pin::pin!(PendThenReady { polls: 0 });
32767 executor.block_on(future.as_mut());
32768 assert_eq!(
32769 future.polls, 2,
32770 "the self-woken future must have been re-polled to completion"
32771 );
32772 let _ = done_tx.send(());
32773 });
32774
32775 match done_rx.recv_timeout(Duration::from_secs(10)) {
32776 Ok(()) => runner.join().unwrap(),
32777 Err(error) => panic!(
32778 "a self-waking Pending future must complete without panicking on a threadless build ({error})"
32779 ),
32780 }
32781 }
32782
32783 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32784 #[test]
32785 fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
32786 use std::sync::mpsc;
32791
32792 let (done_tx, done_rx) = mpsc::channel();
32793 let runner = std::thread::spawn(move || {
32794 let metrics = Arc::new(RuntimeMetrics::default());
32795 let executor = Arc::new(CurrentThreadExecutor::with_detection(
32796 metrics,
32797 false,
32798 Some(Duration::from_millis(50)),
32799 ));
32800
32801 let payload = catch_unwind(AssertUnwindSafe(|| {
32802 let mut future = std::pin::pin!(NeverReady);
32803 executor.block_on(future.as_mut());
32804 }))
32805 .expect_err("a dead native CT park must fire the armed deadline");
32806 let diagnostic = payload
32807 .downcast_ref::<BlockOnDeadlock>()
32808 .expect("typed payload");
32809 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
32810 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32811 drop(executor);
32814 let _ = done_tx.send(());
32815 });
32816
32817 match done_rx.recv_timeout(Duration::from_secs(10)) {
32818 Ok(()) => join_within(
32819 "the CT deadline-fire runner exits after teardown",
32820 runner,
32821 Duration::from_secs(30),
32822 ),
32823 Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
32824 }
32825 }
32826
32827 #[cfg(napi_runtime_os_threads)]
32828 #[test]
32829 fn current_thread_native_park_deadline_resets_on_runtime_progress() {
32830 use std::sync::mpsc;
32847
32848 let deadline = Duration::from_millis(1000);
32849 let metrics = Arc::new(RuntimeMetrics::default());
32850 let executor = Arc::new(CurrentThreadExecutor::with_detection(
32851 Arc::clone(&metrics),
32852 false,
32853 Some(deadline),
32854 ));
32855
32856 let (value_tx, value_rx) = oneshot::channel::<usize>();
32857 let (done_tx, done_rx) = mpsc::channel();
32858 let runner = std::thread::spawn(move || {
32859 let mut output = None;
32860 {
32861 let mut future = std::pin::pin!(async {
32862 output = Some(value_rx.await.unwrap());
32863 });
32864 executor.block_on(future.as_mut());
32865 }
32866 assert_eq!(output, Some(9));
32867 drop(executor);
32869 let _ = done_tx.send(());
32870 });
32871
32872 let hold_until = Instant::now() + deadline * 5 / 2;
32878 while Instant::now() < hold_until {
32879 std::thread::sleep(Duration::from_millis(25));
32880 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32881 }
32882 value_tx.send(9).unwrap();
32883
32884 match done_rx.recv_timeout(Duration::from_secs(30)) {
32885 Ok(()) => join_within(
32886 "the CT deadline-reset runner exits after teardown",
32887 runner,
32888 Duration::from_secs(30),
32889 ),
32890 Err(error) => panic!(
32891 "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
32892 ),
32893 }
32894 }
32895
32896 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32897 #[test]
32898 fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
32899 use std::sync::mpsc;
32904
32905 let (done_tx, done_rx) = mpsc::channel();
32906 let runner = std::thread::spawn(move || {
32907 let options = RuntimeOptions {
32908 flavor: RuntimeFlavor::MultiThread,
32909 worker_threads: 1,
32910 max_blocking_tasks: 1,
32911 thread_name_prefix: "deadline-fire".to_string(),
32912 park_deadline: Some(Duration::from_millis(50)),
32913 drain_linger: DEFAULT_DRAIN_LINGER,
32914 };
32915 let executor =
32916 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32917
32918 let payload = {
32919 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32920 catch_unwind(AssertUnwindSafe(|| {
32921 let mut future = std::pin::pin!(NeverReady);
32922 executor.block_on(future.as_mut());
32923 }))
32924 .expect_err("a dead cooperative park must fire the armed deadline")
32925 };
32926 let diagnostic = payload
32927 .downcast_ref::<BlockOnDeadlock>()
32928 .expect("typed payload");
32929 assert_eq!(
32930 diagnostic.kind,
32931 BlockOnDeadlockKind::MultiThreadCooperativeDeadline
32932 );
32933 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32934 let message = diagnostic.to_string();
32935 assert!(
32936 message.contains("block_on"),
32937 "diagnostic must name the hazard, got: {message}"
32938 );
32939 assert_eq!(
32940 executor.parked_drivers.count.load(Ordering::SeqCst),
32941 0,
32942 "the firing driver must deregister itself before panicking"
32943 );
32944 drop(executor);
32946 let _ = done_tx.send(());
32947 });
32948
32949 match done_rx.recv_timeout(Duration::from_secs(10)) {
32950 Ok(()) => join_within(
32951 "the MT deadline-fire runner exits after executor teardown",
32952 runner,
32953 Duration::from_secs(30),
32954 ),
32955 Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
32956 }
32957 }
32958
32959 #[cfg(napi_runtime_os_threads)]
32960 #[test]
32961 fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
32962 use std::sync::mpsc;
32978
32979 let (done_tx, done_rx) = mpsc::channel();
32980 let runner = std::thread::spawn(move || {
32981 let deadline = Duration::from_millis(1000);
32982 let metrics = Arc::new(RuntimeMetrics::default());
32983 let options = RuntimeOptions {
32984 flavor: RuntimeFlavor::MultiThread,
32985 worker_threads: 1,
32986 max_blocking_tasks: 1,
32987 thread_name_prefix: "deadline-reset".to_string(),
32988 park_deadline: Some(deadline),
32989 drain_linger: DEFAULT_DRAIN_LINGER,
32990 };
32991 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32992
32993 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32996 let td_exec = Arc::clone(&executor);
32997 let completed = Arc::new(AtomicBool::new(false));
32998 let td_flag = Arc::clone(&completed);
32999 let td_body = async move {
33000 let mut inner = std::pin::pin!(async move {
33001 assert_eq!(gate_rx.await.unwrap(), 9);
33002 });
33003 td_exec.block_on(inner.as_mut());
33004 td_flag.store(true, Ordering::SeqCst);
33005 };
33006 let sched = Arc::clone(&executor);
33007 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
33008 executor.schedule(td_runnable);
33009 wait_until("the sole driver parked", || {
33010 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
33011 });
33012
33013 let hold_until = Instant::now() + deadline * 5 / 2;
33019 while Instant::now() < hold_until {
33020 std::thread::sleep(Duration::from_millis(25));
33021 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
33022 }
33023 assert!(
33024 !completed.load(Ordering::SeqCst),
33025 "the driver must still be parked awaiting its late wake"
33026 );
33027 gate_tx.send(9).unwrap();
33028 futures::executor::block_on(td_task);
33029 assert!(
33030 completed.load(Ordering::SeqCst),
33031 "the late wake must complete the parked driver"
33032 );
33033 drop(executor);
33035 let _ = done_tx.send(());
33036 });
33037
33038 match done_rx.recv_timeout(Duration::from_secs(30)) {
33039 Ok(()) => join_within(
33040 "the MT deadline-reset runner exits after executor teardown",
33041 runner,
33042 Duration::from_secs(30),
33043 ),
33044 Err(error) => panic!(
33045 "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
33046 ),
33047 }
33048 }
33049
33050 #[cfg(napi_runtime_os_threads)]
33051 #[test]
33052 fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
33053 use std::sync::mpsc;
33058
33059 let (done_tx, done_rx) = mpsc::channel();
33060 let runner = std::thread::spawn(move || {
33061 let options = RuntimeOptions {
33062 flavor: RuntimeFlavor::MultiThread,
33063 worker_threads: 2,
33064 max_blocking_tasks: 2,
33065 thread_name_prefix: "foreign-exempt".to_string(),
33066 park_deadline: Some(Duration::from_millis(40)),
33067 drain_linger: DEFAULT_DRAIN_LINGER,
33068 };
33069 let executor =
33070 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33071
33072 let (value_tx, value_rx) = oneshot::channel::<usize>();
33073 let waker_thread = std::thread::spawn(move || {
33074 std::thread::sleep(Duration::from_millis(300));
33076 let _ = value_tx.send(7);
33077 });
33078
33079 let started = Instant::now();
33080 let mut output = None;
33081 {
33082 let mut future = std::pin::pin!(async {
33083 output = Some(value_rx.await.unwrap());
33084 });
33085 executor.block_on(future.as_mut());
33087 }
33088 assert_eq!(output, Some(7));
33089 assert!(
33090 started.elapsed() >= Duration::from_millis(250),
33091 "the park must genuinely have outlived the 40ms deadline many times over"
33092 );
33093 waker_thread.join().unwrap();
33094 drop(executor);
33099 let _ = done_tx.send(());
33100 });
33101
33102 match done_rx.recv_timeout(Duration::from_secs(10)) {
33103 Ok(()) => join_within(
33104 "the exempt foreign-park runner exits after executor teardown",
33105 runner,
33106 Duration::from_secs(30),
33107 ),
33108 Err(error) => {
33109 panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
33110 }
33111 }
33112 }
33113
33114 #[cfg(napi_runtime_os_threads)]
33115 #[test]
33116 fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
33117 use std::sync::mpsc;
33132
33133 let (done_tx, done_rx) = mpsc::channel();
33134 let runner = std::thread::spawn(move || {
33135 let options = RuntimeOptions {
33136 flavor: RuntimeFlavor::MultiThread,
33137 worker_threads: 1,
33138 max_blocking_tasks: 1,
33139 thread_name_prefix: "edge-wake".to_string(),
33140 park_deadline: Some(Duration::from_millis(40)),
33141 drain_linger: DEFAULT_DRAIN_LINGER,
33142 };
33143 let executor =
33144 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33145
33146 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
33148 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
33150
33151 let hook_exec = Arc::clone(&executor);
33152 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
33153 *slot.borrow_mut() = Some(Box::new(move || {
33154 let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
33155 let handle = hook_exec.schedule_blocking(move || {
33159 let _ = gate_tx.send(5usize);
33160 0usize
33161 });
33162 drop(handle); let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
33164 hook_saw_tx.send((before, after)).unwrap();
33165 }));
33166 });
33167
33168 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33171 let mut output = None;
33172 {
33173 let mut future = std::pin::pin!(async {
33174 output = Some(gate_rx.await.unwrap());
33175 });
33176 executor.block_on(future.as_mut());
33177 }
33178 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
33183 assert_eq!(
33184 output,
33185 Some(5),
33186 "the edge-delivered wake's job must have run and completed the future"
33187 );
33188
33189 let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
33190 assert_eq!(
33191 before, 1,
33192 "the hook must observe the parker STILL registered (race window)"
33193 );
33194 assert_eq!(
33195 after, 0,
33196 "the racing wake_one must have popped the parker (wake DELIVERED)"
33197 );
33198 drop(executor);
33200 let _ = done_tx.send(());
33201 });
33202
33203 match done_rx.recv_timeout(Duration::from_secs(10)) {
33204 Ok(()) => join_within(
33205 "the expiry-edge-wake runner exits after executor teardown",
33206 runner,
33207 Duration::from_secs(30),
33208 ),
33209 Err(error) => panic!(
33210 "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
33211 ),
33212 }
33213 }
33214
33215 #[cfg(napi_runtime_os_threads)]
33216 #[test]
33217 fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
33218 use std::sync::mpsc;
33230
33231 let (done_tx, done_rx) = mpsc::channel();
33232 let runner = std::thread::spawn(move || {
33233 let options = RuntimeOptions {
33234 flavor: RuntimeFlavor::MultiThread,
33235 worker_threads: 1,
33236 max_blocking_tasks: 1,
33237 thread_name_prefix: "verdict-blocking".to_string(),
33238 park_deadline: Some(Duration::from_millis(40)),
33239 drain_linger: DEFAULT_DRAIN_LINGER,
33240 };
33241 let executor =
33242 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33243 executor
33244 .active_drainers
33245 .store(executor.max_drainers, Ordering::SeqCst);
33246
33247 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
33249 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
33252
33253 let hook_exec = Arc::clone(&executor);
33254 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
33255 *slot.borrow_mut() = Some(Box::new(move || {
33256 let handle = hook_exec.schedule_blocking(move || {
33257 let _ = gate_tx.send(11usize);
33258 0usize
33259 });
33260 drop(handle); hook_saw_tx
33262 .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
33263 .unwrap();
33264 }));
33265 });
33266
33267 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33270 let mut output = None;
33271 {
33272 let mut future = std::pin::pin!(async {
33273 output = Some(gate_rx.await.unwrap());
33274 });
33275 executor.block_on(future.as_mut());
33276 }
33277 assert_eq!(
33278 output,
33279 Some(11),
33280 "the enqueued job must have been run by the surviving driver via run_one"
33281 );
33282
33283 let registered_at_hook = hook_saw_rx
33284 .recv()
33285 .expect("the verdict hook must have fired");
33286 assert_eq!(
33287 registered_at_hook, 0,
33288 "the hook fires post-deregister: no permit can be delivered, only the fingerprint's enqueue counter can catch this submission"
33289 );
33290 executor.active_drainers.store(0, Ordering::SeqCst);
33291 let _ = done_tx.send(());
33292 });
33293
33294 match done_rx.recv_timeout(Duration::from_secs(10)) {
33295 Ok(()) => runner.join().unwrap(),
33296 Err(error) => panic!(
33297 "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
33298 ),
33299 }
33300 }
33301
33302 #[cfg(napi_runtime_os_threads)]
33303 #[test]
33304 fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
33305 use std::sync::mpsc;
33311
33312 struct ReadyAfterPublication(Arc<AtomicBool>);
33313
33314 impl Future for ReadyAfterPublication {
33315 type Output = ();
33316
33317 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
33318 if self.0.load(Ordering::SeqCst) {
33319 Poll::Ready(())
33320 } else {
33321 Poll::Pending
33322 }
33323 }
33324 }
33325
33326 let (done_tx, done_rx) = mpsc::channel();
33327 let runner = std::thread::spawn(move || {
33328 let options = RuntimeOptions {
33329 flavor: RuntimeFlavor::MultiThread,
33330 worker_threads: 1,
33331 max_blocking_tasks: 1,
33332 thread_name_prefix: "final-verdict-publication".to_string(),
33333 park_deadline: Some(Duration::from_millis(40)),
33334 drain_linger: DEFAULT_DRAIN_LINGER,
33335 };
33336 let executor =
33337 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33338
33339 let ready = Arc::new(AtomicBool::new(false));
33340 let baseline_fingerprint = executor.metrics.progress_fingerprint();
33341 let baseline_publications = executor.deadlock_state.publications();
33342 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
33343 let hook_exec = Arc::clone(&executor);
33344 let hook_ready = Arc::clone(&ready);
33345 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33346 *slot.borrow_mut() = Some(Box::new(move || {
33347 let publication_executor = Arc::clone(&hook_exec);
33348 std::thread::spawn(move || {
33349 let mut admission = publication_executor
33350 .begin_deadlock_admission()
33351 .expect("the deadline detector must enable admission accounting");
33352 hook_ready.store(true, Ordering::SeqCst);
33353 admission.mark_published();
33354 })
33355 .join()
33356 .unwrap();
33357 hook_saw_tx
33358 .send((
33359 hook_exec.deadlock_state.publications(),
33360 !hook_exec.has_queued_runnable(),
33361 hook_exec
33362 .blocking_queue
33363 .lock()
33364 .unwrap_or_else(std::sync::PoisonError::into_inner)
33365 .is_empty(),
33366 hook_exec.next_timer_deadline().is_none(),
33367 hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
33368 hook_exec.parked_drivers.count.load(Ordering::SeqCst),
33369 ))
33370 .unwrap();
33371 }));
33372 });
33373
33374 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33375 let mut future = std::pin::pin!(ReadyAfterPublication(ready));
33376 assert_eq!(
33377 executor.block_on(future.as_mut()),
33378 BlockOnOutcome::Completed,
33379 "the publication epoch must veto the first verdict and permit the next poll"
33380 );
33381 let (
33382 publications,
33383 runnable_queue_empty,
33384 blocking_queue_empty,
33385 timer_heap_empty,
33386 fingerprint_unchanged,
33387 parked_drivers,
33388 ) = hook_saw_rx
33389 .recv()
33390 .expect("the final-verdict hook must have fired");
33391 assert_eq!(
33392 publications,
33393 baseline_publications
33394 .checked_add(1)
33395 .expect("test publication epoch must not overflow"),
33396 "the cross-thread admission must publish exactly one epoch"
33397 );
33398 assert!(
33399 runnable_queue_empty,
33400 "the publication test must not queue a runnable"
33401 );
33402 assert!(
33403 blocking_queue_empty,
33404 "the publication test must not queue blocking work"
33405 );
33406 assert!(
33407 timer_heap_empty,
33408 "the publication test must not register a timer"
33409 );
33410 assert!(
33411 fingerprint_unchanged,
33412 "ordinary metrics progress must not veto the publication-only verdict"
33413 );
33414 assert_eq!(
33415 parked_drivers, 0,
33416 "the publication must land after the cooperative parker was deregistered"
33417 );
33418 let _ = done_tx.send(());
33419 });
33420
33421 match done_rx.recv_timeout(Duration::from_secs(10)) {
33422 Ok(()) => runner.join().unwrap(),
33423 Err(error) => panic!(
33424 "a publication-only cross-thread admission was not observed by the final verdict ({error})"
33425 ),
33426 }
33427 }
33428
33429 #[cfg(napi_runtime_os_threads)]
33430 #[test]
33431 fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
33432 use std::sync::mpsc;
33440
33441 let executor = multi_thread_executor(
33442 1,
33443 Some(Duration::from_millis(40)),
33444 "deadline-selected-handoff",
33445 );
33446 let owner = executor.fresh_owner_token();
33447 executor.active_blocking_owners.register(owner);
33448 executor
33449 .active_blocking
33450 .store(executor.max_blocking, Ordering::SeqCst);
33451
33452 let ready = Arc::new(AtomicBool::new(false));
33453 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33454 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33455 let (selected_tx, selected_rx) = mpsc::channel();
33456 let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
33457 let (released_tx, released_rx) = mpsc::channel();
33458 let (renewed_tx, renewed_rx) = mpsc::channel();
33459 let release_executor = Arc::clone(&executor);
33460 let renewal_executor = Arc::clone(&executor);
33461 let release_ready = Arc::clone(&ready);
33462 let releaser = std::thread::spawn(move || {
33463 let reservation =
33464 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33465 .expect("the synthetic exact-owner lane must start reserved");
33466 reservation_ready_tx.send(()).unwrap();
33467 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
33468 *slot.borrow_mut() = Some(Box::new(move || {
33469 selected_tx.send(()).unwrap();
33470 allow_unpark_rx.recv().unwrap();
33471 let renewed = renewal_executor
33472 .active_blocking_owners
33473 .reserve(owner)
33474 .expect("the released exact-owner lane must be reservable before unpark");
33475 release_ready.store(true, Ordering::SeqCst);
33476 renewed_tx.send(renewed).unwrap();
33477 }));
33478 });
33479 release_reservation_rx.recv().unwrap();
33480 released_tx
33481 .send(release_executor.release_owner_lane_and_wake(reservation))
33482 .unwrap();
33483 });
33484 reservation_ready_rx
33485 .recv_timeout(Duration::from_secs(2))
33486 .expect("the exact-owner reservation must be established before the park");
33487
33488 let baseline_fingerprint = executor.metrics.progress_fingerprint();
33489 let baseline_publications = executor.deadlock_state.publications();
33490 let expiry_executor = Arc::clone(&executor);
33491 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
33492 *slot.borrow_mut() = Some(Box::new(move || {
33493 release_reservation_tx.send(()).unwrap();
33494 selected_rx
33495 .recv_timeout(Duration::from_secs(2))
33496 .expect("reservation release must select the still-registered exact-owner driver");
33497 assert_eq!(
33498 expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
33499 0,
33500 "handoff selection must remove the driver before pausing ahead of unpark"
33501 );
33502 }));
33503 });
33504
33505 let verdict_executor = Arc::clone(&executor);
33506 let verdict_ready = Arc::clone(&ready);
33507 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33508 *slot.borrow_mut() = Some(Box::new(move || {
33509 assert!(
33510 verdict_executor.active_blocking_owners.is_available(owner),
33511 "reservation release must publish lane availability before handoff selection"
33512 );
33513 assert!(!verdict_executor.has_queued_runnable());
33514 assert!(
33515 verdict_executor
33516 .blocking_queue
33517 .lock()
33518 .unwrap_or_else(std::sync::PoisonError::into_inner)
33519 .is_empty()
33520 );
33521 assert!(verdict_executor.next_timer_deadline().is_none());
33522 assert!(
33523 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33524 "ordinary metrics progress must not save the selected-handoff verdict"
33525 );
33526 assert_eq!(
33527 verdict_executor.deadlock_state.publications(),
33528 baseline_publications
33529 );
33530 assert!(
33531 !verdict_ready.load(Ordering::SeqCst),
33532 "the selected handoff must still be paused before unpark at the gated verdict"
33533 );
33534 allow_unpark_tx.send(()).unwrap();
33535 }));
33536 });
33537 let exact_owner_executor = Arc::clone(&executor);
33538 DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
33539 *slot.borrow_mut() = Some(Box::new(move |parker| {
33540 assert!(
33541 parker.consume_permit(),
33542 "the selected handoff must unpark before exact-owner final predicates run"
33543 );
33544 assert!(
33545 parker.has_owner_handoff(),
33546 "consuming the ordinary permit must leave the exact-owner handoff pending"
33547 );
33548 assert!(
33549 !exact_owner_executor
33550 .active_blocking_owners
33551 .is_available(owner),
33552 "the renewed reservation must prevent lane availability from saving the verdict"
33553 );
33554 }));
33555 });
33556
33557 {
33558 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33559 let _owner = BlockingOwnerGuard::enter(Some(owner));
33560 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33561 dependency: BlockingDependency {
33562 job: BlockingJobId(u64::MAX - 80),
33563 owner: Some(owner)
33564 },
33565 ready: Arc::clone(&ready),
33566 });
33567 assert_eq!(
33568 executor.block_on(future.as_mut()),
33569 BlockOnOutcome::Completed
33570 );
33571 }
33572 assert!(
33573 released_rx
33574 .recv_timeout(Duration::from_secs(2))
33575 .expect("the selected handoff publication must finish"),
33576 "reservation release must have selected the cooperative driver"
33577 );
33578 let renewed = renewed_rx
33579 .recv_timeout(Duration::from_secs(2))
33580 .expect("the selected handoff must renew the owner reservation before unpark");
33581 releaser.join().unwrap();
33582
33583 executor.active_blocking_owners.release(owner, renewed);
33584 executor.active_blocking.store(0, Ordering::SeqCst);
33585 executor.active_blocking_owners.unregister(owner);
33586 executor.begin_shutdown();
33587 executor.wait_until_scheduler_idle();
33588 }
33589
33590 #[cfg(napi_runtime_os_threads)]
33591 #[test]
33592 fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
33593 use std::sync::mpsc;
33598
33599 let executor = multi_thread_executor(
33600 1,
33601 Some(Duration::from_millis(40)),
33602 "deadline-owner-no-parker",
33603 );
33604 let owner = executor.fresh_owner_token();
33605 executor.active_blocking_owners.register(owner);
33606 executor
33607 .active_blocking
33608 .store(executor.max_blocking, Ordering::SeqCst);
33609
33610 let ready = Arc::new(AtomicBool::new(false));
33611 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33612 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33613 let (released_tx, released_rx) = mpsc::channel();
33614 let release_executor = Arc::clone(&executor);
33615 let release_ready = Arc::clone(&ready);
33616 let releaser = std::thread::spawn(move || {
33617 let reservation =
33618 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33619 .expect("the synthetic exact-owner lane must start reserved");
33620 reservation_ready_tx.send(()).unwrap();
33621 release_reservation_rx.recv().unwrap();
33622 let woke = release_executor.release_owner_lane_and_wake(reservation);
33623 release_ready.store(true, Ordering::SeqCst);
33624 released_tx.send(woke).unwrap();
33625 });
33626 reservation_ready_rx
33627 .recv_timeout(Duration::from_secs(2))
33628 .expect("the exact-owner reservation must be established before the park");
33629
33630 let baseline_fingerprint = executor.metrics.progress_fingerprint();
33631 let baseline_publications = executor.deadlock_state.publications();
33632 let verdict_executor = Arc::clone(&executor);
33633 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33634 *slot.borrow_mut() = Some(Box::new(move || {
33635 assert_eq!(
33636 verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
33637 0,
33638 "the release must begin only after deadline deregistration"
33639 );
33640 release_reservation_tx.send(()).unwrap();
33641 assert!(
33642 !released_rx
33643 .recv_timeout(Duration::from_secs(2))
33644 .expect("the no-parker owner release must finish"),
33645 "no registered parker may receive the released owner lane"
33646 );
33647 assert!(verdict_executor.active_blocking_owners.is_available(owner));
33648 assert!(!verdict_executor.has_queued_runnable());
33649 assert!(
33650 verdict_executor
33651 .blocking_queue
33652 .lock()
33653 .unwrap_or_else(std::sync::PoisonError::into_inner)
33654 .is_empty()
33655 );
33656 assert!(verdict_executor.next_timer_deadline().is_none());
33657 assert!(
33658 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33659 "ordinary metrics progress must not save the no-parker release verdict"
33660 );
33661 assert_eq!(
33662 verdict_executor.deadlock_state.publications(),
33663 baseline_publications
33664 );
33665 }));
33666 });
33667
33668 {
33669 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33670 let _owner = BlockingOwnerGuard::enter(Some(owner));
33671 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33672 dependency: BlockingDependency {
33673 job: BlockingJobId(u64::MAX - 81),
33674 owner: Some(owner)
33675 },
33676 ready: Arc::clone(&ready),
33677 });
33678 assert_eq!(
33679 executor.block_on(future.as_mut()),
33680 BlockOnOutcome::Completed
33681 );
33682 }
33683 releaser.join().unwrap();
33684
33685 executor.active_blocking.store(0, Ordering::SeqCst);
33686 executor.active_blocking_owners.unregister(owner);
33687 executor.begin_shutdown();
33688 executor.wait_until_scheduler_idle();
33689 }
33690
33691 #[cfg(napi_runtime_os_threads)]
33692 #[test]
33693 fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
33694 use std::sync::mpsc;
33708
33709 let controller = Arc::new(RuntimeController::new());
33710 controller
33711 .configure(RuntimeOptions {
33712 flavor: RuntimeFlavor::MultiThread,
33713 worker_threads: 2,
33714 max_blocking_tasks: 1,
33715 thread_name_prefix: "shutdown-gated-verdict".to_string(),
33716 park_deadline: Some(Duration::from_millis(40)),
33717 drain_linger: DEFAULT_DRAIN_LINGER,
33718 })
33719 .expect("the shutdown race test configuration must be accepted");
33720
33721 let backend = controller.backend();
33722 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33723 panic!("the configured backend must be MultiThread");
33724 };
33725 let executor_id = executor.id;
33726 let weak_executor = Arc::downgrade(executor);
33727 drop(backend);
33728
33729 let (task_polled_tx, task_polled_rx) = mpsc::channel();
33730 let task = controller
33731 .try_spawn(async move {
33732 task_polled_tx.send(()).unwrap();
33733 std::future::pending::<()>().await;
33734 })
33735 .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
33736 task_polled_rx
33737 .recv_timeout(Duration::from_secs(2))
33738 .expect("the accepted task must publish an abortable pending waker");
33739
33740 let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
33741 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33742 let (driver_result_tx, driver_result_rx) = mpsc::channel();
33743 let driver_controller = Arc::clone(&controller);
33744 let driver = std::thread::spawn(move || {
33745 DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
33746 *slot.borrow_mut() = Some(Box::new(move || {
33747 gate_closed_tx.send(()).unwrap();
33748 release_verdict_rx.recv().unwrap();
33749 }));
33750 });
33751
33752 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33753 let mut pending = std::pin::pin!(NeverReady);
33754 let result = catch_unwind(AssertUnwindSafe(|| {
33755 driver_controller.block_on(pending.as_mut())
33756 }));
33757 let result = match result {
33758 Ok(outcome) => Ok(outcome),
33759 Err(payload) => {
33760 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33761 discard_panic_payload(payload);
33762 Err(was_deadlock)
33763 }
33764 };
33765 driver_result_tx.send(result).unwrap();
33766 });
33767
33768 gate_closed_rx
33769 .recv_timeout(Duration::from_secs(2))
33770 .expect("the cooperative driver must pause with the verdict gate closed");
33771
33772 let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
33773 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33774 let shutdown_controller = Arc::clone(&controller);
33775 let shutdown = std::thread::spawn(move || {
33776 DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
33777 *slot.borrow_mut() = Some(Box::new(move || {
33778 abort_blocked_tx.send(()).unwrap();
33779 }));
33780 });
33781 shutdown_result_tx
33782 .send(shutdown_controller.shutdown())
33783 .unwrap();
33784 });
33785
33786 abort_blocked_rx
33787 .recv_timeout(Duration::from_secs(2))
33788 .expect("the accepted task's abort wake must reach the closed verdict gate");
33789 let lifecycle_is_stopping = {
33790 let state = controller
33791 .state
33792 .lock()
33793 .unwrap_or_else(std::sync::PoisonError::into_inner);
33794 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
33795 };
33796 let stop_was_published = weak_executor
33797 .upgrade()
33798 .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
33799
33800 release_verdict_tx.send(()).unwrap();
33801
33802 let driver_result = driver_result_rx
33803 .recv_timeout(Duration::from_secs(2))
33804 .expect("the gated driver must resolve after observing shutdown");
33805 let shutdown_result = shutdown_result_rx
33806 .recv_timeout(Duration::from_secs(5))
33807 .expect("shutdown must finish after the verdict gate reopens");
33808 join_within(
33813 "the gated cooperative driver exits after teardown",
33814 driver,
33815 Duration::from_secs(30),
33816 );
33817 join_within(
33818 "the shutdown thread exits after teardown",
33819 shutdown,
33820 Duration::from_secs(30),
33821 );
33822
33823 assert!(
33824 lifecycle_is_stopping,
33825 "the abort wake must occur after Running -> Stopping"
33826 );
33827 assert!(
33828 stop_was_published,
33829 "generation stop must be visible before shutdown invokes accepted-task aborts"
33830 );
33831 assert_eq!(
33832 driver_result,
33833 Ok(BlockOnOutcome::Stopped),
33834 "shutdown must not be reported as a gated BlockOnDeadlock"
33835 );
33836 shutdown_result.unwrap();
33837 assert!(
33838 futures::executor::block_on(task).is_err(),
33839 "the accepted pending task must resolve as cancelled"
33840 );
33841 }
33842
33843 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
33844 #[test]
33845 fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
33846 use std::sync::mpsc;
33851
33852 let controller = Arc::new(RuntimeController::new());
33853 controller
33854 .configure(RuntimeOptions {
33855 flavor: RuntimeFlavor::MultiThread,
33856 worker_threads: 2,
33857 max_blocking_tasks: 1,
33858 thread_name_prefix: "verdict-before-shutdown".to_string(),
33859 park_deadline: Some(Duration::from_millis(40)),
33860 drain_linger: DEFAULT_DRAIN_LINGER,
33861 })
33862 .expect("the verdict-first test configuration must be accepted");
33863
33864 let backend = controller.backend();
33865 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33866 panic!("the configured backend must be MultiThread");
33867 };
33868 let executor_id = executor.id;
33869 drop(backend);
33870
33871 let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
33872 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33873 let (driver_result_tx, driver_result_rx) = mpsc::channel();
33874 let driver_controller = Arc::clone(&controller);
33875 let driver = std::thread::spawn(move || {
33876 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33877 *slot.borrow_mut() = Some(Box::new(move || {
33878 verdict_locked_tx.send(()).unwrap();
33879 release_verdict_rx.recv().unwrap();
33880 }));
33881 });
33882
33883 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33884 let mut pending = std::pin::pin!(NeverReady);
33885 let result = catch_unwind(AssertUnwindSafe(|| {
33886 driver_controller.block_on(pending.as_mut())
33887 }));
33888 let result = match result {
33889 Ok(outcome) => Ok(outcome),
33890 Err(payload) => {
33891 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33892 discard_panic_payload(payload);
33893 Err(was_deadlock)
33894 }
33895 };
33896 driver_result_tx.send(result).unwrap();
33897 });
33898
33899 verdict_locked_rx
33900 .recv_timeout(Duration::from_secs(2))
33901 .expect("the verdict must hold the synchronized stop-publication guard");
33902
33903 let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
33904 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33905 let shutdown_controller = Arc::clone(&controller);
33906 let shutdown = std::thread::spawn(move || {
33907 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
33908 *slot.borrow_mut() = Some(Box::new(move || {
33909 shutdown_waiting_tx.send(()).unwrap();
33910 }));
33911 });
33912 shutdown_result_tx
33913 .send(shutdown_controller.shutdown())
33914 .unwrap();
33915 });
33916
33917 shutdown_waiting_rx
33918 .recv_timeout(Duration::from_secs(2))
33919 .expect("shutdown must reach the lifecycle-to-publication lock edge");
33920 let early_shutdown = shutdown_result_rx
33921 .recv_timeout(Duration::from_millis(100))
33922 .ok();
33923 let shutdown_was_blocked = early_shutdown.is_none();
33924 release_verdict_tx.send(()).unwrap();
33925
33926 let driver_result = driver_result_rx
33927 .recv_timeout(Duration::from_secs(2))
33928 .expect("the verdict-first driver must finish after release");
33929 let shutdown_result = early_shutdown.unwrap_or_else(|| {
33930 shutdown_result_rx
33931 .recv_timeout(Duration::from_secs(5))
33932 .expect("shutdown must finish after the verdict releases publication")
33933 });
33934 driver.join().unwrap();
33935 shutdown.join().unwrap();
33936
33937 assert!(
33938 shutdown_was_blocked,
33939 "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
33940 );
33941 assert_eq!(
33942 driver_result,
33943 Err(true),
33944 "a verdict that linearizes before shutdown must retain the typed deadlock"
33945 );
33946 shutdown_result.unwrap();
33947 }
33948
33949 #[cfg(not(target_family = "wasm"))]
33950 #[test]
33951 fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
33952 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
33953
33954 if std::env::var_os(CHILD_ENV).is_some() {
33955 let executor =
33956 multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
33957 executor
33960 .active_drainers
33961 .store(executor.max_drainers, Ordering::SeqCst);
33962
33963 let hook_returned = Arc::new(AtomicBool::new(false));
33964 let hook_flag = Arc::clone(&hook_returned);
33965 let hook_executor = Arc::clone(&executor);
33966 let previous_hook = std::panic::take_hook();
33967 std::panic::set_hook(Box::new(move |_| {
33968 let handle = hook_executor.schedule_blocking(|| ());
33969 drop(handle);
33970 hook_flag.store(true, Ordering::SeqCst);
33971 }));
33972
33973 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33974 let result = catch_unwind(AssertUnwindSafe(|| {
33975 let mut pending = std::pin::pin!(std::future::pending::<()>());
33976 executor.block_on(pending.as_mut());
33977 }));
33978 std::panic::set_hook(previous_hook);
33979
33980 let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
33981 assert!(
33982 payload.downcast_ref::<BlockOnDeadlock>().is_some(),
33983 "the deadline must retain its structured diagnostic"
33984 );
33985 assert!(
33986 hook_returned.load(Ordering::SeqCst),
33987 "the panic hook's scheduler submission must return before unwind"
33988 );
33989
33990 executor.active_drainers.store(0, Ordering::SeqCst);
33991 executor.begin_shutdown();
33992 executor.wait_until_scheduler_idle();
33993 return;
33994 }
33995
33996 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
33997 .arg("--exact")
33998 .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
33999 .arg("--nocapture")
34000 .env(CHILD_ENV, "1")
34001 .stdout(std::process::Stdio::piped())
34002 .stderr(std::process::Stdio::piped())
34003 .spawn()
34004 .expect("the panic-hook subprocess must start");
34005 let deadline = Instant::now() + Duration::from_secs(5);
34006 let status = loop {
34007 if let Some(status) = child
34008 .try_wait()
34009 .expect("the subprocess status must be readable")
34010 {
34011 break status;
34012 }
34013 if Instant::now() >= deadline {
34014 let _ = child.kill();
34015 let output = child
34016 .wait_with_output()
34017 .expect("the timed-out subprocess must be reaped");
34018 panic!(
34019 "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
34020 String::from_utf8_lossy(&output.stdout),
34021 String::from_utf8_lossy(&output.stderr)
34022 );
34023 }
34024 std::thread::sleep(Duration::from_millis(10));
34025 };
34026 let output = child
34027 .wait_with_output()
34028 .expect("the completed subprocess output must be read");
34029 assert!(
34030 status.success(),
34031 "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
34032 status.code(),
34033 String::from_utf8_lossy(&output.stdout),
34034 String::from_utf8_lossy(&output.stderr)
34035 );
34036 }
34037
34038 #[cfg(panic = "unwind")]
34049 #[test]
34050 fn contained_waker_separates_wake_and_drop_panics() {
34051 struct PanicWakeAndDrop;
34052
34053 impl std::task::Wake for PanicWakeAndDrop {
34054 fn wake(self: Arc<Self>) {
34055 panic!("intentional consuming wake panic");
34056 }
34057
34058 fn wake_by_ref(self: &Arc<Self>) {
34059 panic!("intentional borrowed wake panic");
34060 }
34061 }
34062
34063 impl Drop for PanicWakeAndDrop {
34064 fn drop(&mut self) {
34065 panic!("intentional RawWaker destructor panic");
34066 }
34067 }
34068
34069 wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
34070 }
34071
34072 struct TestHostileRawWakerState {
34073 clones: AtomicUsize,
34074 drop_panics: AtomicUsize,
34075 panic_on_clone: Option<usize>,
34076 }
34077
34078 struct TestHostileRawWakerToken {
34079 state: Arc<TestHostileRawWakerState>,
34080 panic_on_drop: bool,
34081 }
34082
34083 unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
34084 let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
34085 let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
34086 assert_ne!(
34087 token.state.panic_on_clone,
34088 Some(clone),
34089 "intentional RawWaker clone panic"
34090 );
34091 let clone = Box::new(TestHostileRawWakerToken {
34092 state: Arc::clone(&token.state),
34093 panic_on_drop: true,
34094 });
34095 std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
34096 }
34097
34098 unsafe fn wake_test_hostile_waker(data: *const ()) {
34099 unsafe { drop_test_hostile_waker(data) };
34100 }
34101
34102 unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
34103
34104 unsafe fn drop_test_hostile_waker(data: *const ()) {
34105 let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
34106 if token.panic_on_drop {
34107 token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
34108 panic!("intentional cloned RawWaker destructor panic");
34109 }
34110 }
34111
34112 static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
34113 clone_test_hostile_waker,
34114 wake_test_hostile_waker,
34115 wake_test_hostile_waker_by_ref,
34116 drop_test_hostile_waker,
34117 );
34118
34119 fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
34120 let token = Box::new(TestHostileRawWakerToken {
34121 state,
34122 panic_on_drop: false,
34123 });
34124 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
34125 unsafe { Waker::from_raw(raw) }
34126 }
34127
34128 struct CountingWake(AtomicUsize);
34130 impl std::task::Wake for CountingWake {
34131 fn wake(self: Arc<Self>) {
34132 self.0.fetch_add(1, Ordering::SeqCst);
34133 }
34134 }
34135
34136 type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
34138
34139 struct StubHostTimerDriver {
34143 timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
34144 }
34145
34146 impl StubHostTimerDriver {
34147 fn new() -> Self {
34148 Self {
34149 timers: Mutex::new(FxHashMap::default()),
34150 }
34151 }
34152 }
34153
34154 impl TimerDriver for StubHostTimerDriver {
34155 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
34156 let mut timers = self.timers.lock().unwrap();
34157 if let Some(cell) = timers.get(&id) {
34158 *cell.0.lock().unwrap() = waker;
34159 return;
34160 }
34161 let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
34162 timers.insert(id, Arc::clone(&cell));
34163 std::thread::spawn(move || {
34164 let now = Instant::now();
34165 if deadline > now {
34166 std::thread::sleep(deadline - now);
34167 }
34168 if !cell.1.load(Ordering::SeqCst) {
34169 cell.0.lock().unwrap().wake_by_ref();
34170 }
34171 });
34172 }
34173
34174 fn cancel(&self, id: TimerId) {
34175 let removed = self.timers.lock().unwrap().remove(&id);
34176 if let Some(cell) = removed {
34177 cell.1.store(true, Ordering::SeqCst);
34178 }
34179 }
34180 }
34181
34182 struct ManualStubDriver {
34188 live: AtomicBool,
34189 registers: Mutex<Vec<(TimerId, Instant)>>,
34190 cancels: Mutex<Vec<TimerId>>,
34191 pending: Mutex<FxHashMap<TimerId, Waker>>,
34192 swept: AtomicBool,
34193 }
34194
34195 impl ManualStubDriver {
34196 fn new() -> Arc<Self> {
34197 Arc::new(Self {
34198 live: AtomicBool::new(true),
34199 registers: Mutex::new(Vec::new()),
34200 cancels: Mutex::new(Vec::new()),
34201 pending: Mutex::new(FxHashMap::default()),
34202 swept: AtomicBool::new(false),
34203 })
34204 }
34205
34206 fn kill(&self) {
34207 self.live.store(false, Ordering::SeqCst);
34208 }
34209 }
34210
34211 impl TimerDriver for ManualStubDriver {
34212 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
34213 self.registers.lock().unwrap().push((id, deadline));
34214 self.pending.lock().unwrap().insert(id, waker);
34215 }
34216
34217 fn cancel(&self, id: TimerId) {
34218 self.cancels.lock().unwrap().push(id);
34219 self.pending.lock().unwrap().remove(&id);
34220 }
34221
34222 fn is_live(&self) -> bool {
34223 self.live.load(Ordering::SeqCst)
34224 }
34225
34226 fn on_swept(&self) {
34227 self.swept.store(true, Ordering::SeqCst);
34230 let wakers: Vec<Waker> = self
34231 .pending
34232 .lock()
34233 .unwrap()
34234 .drain()
34235 .map(|(_, w)| w)
34236 .collect();
34237 for waker in wakers {
34238 waker.wake();
34239 }
34240 }
34241 }
34242
34243 struct PanickingTimerDriver {
34244 swept: AtomicBool,
34245 }
34246
34247 impl TimerDriver for PanickingTimerDriver {
34248 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34249
34250 fn cancel(&self, _id: TimerId) {}
34251
34252 fn is_live(&self) -> bool {
34253 panic!("intentional timer-driver liveness panic");
34254 }
34255
34256 fn on_swept(&self) {
34257 self.swept.store(true, Ordering::SeqCst);
34258 panic!("intentional timer-driver sweep panic");
34259 }
34260 }
34261
34262 struct PanickingRegisterTimerDriver {
34263 registers: AtomicUsize,
34264 cancels: Mutex<Vec<TimerId>>,
34265 pending: Mutex<FxHashMap<TimerId, Waker>>,
34266 }
34267
34268 impl PanickingRegisterTimerDriver {
34269 fn new() -> Arc<Self> {
34270 Arc::new(Self {
34271 registers: AtomicUsize::new(0),
34272 cancels: Mutex::new(Vec::new()),
34273 pending: Mutex::new(FxHashMap::default()),
34274 })
34275 }
34276 }
34277
34278 impl TimerDriver for PanickingRegisterTimerDriver {
34279 fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
34280 self.registers.fetch_add(1, Ordering::SeqCst);
34281 self.pending.lock().unwrap().insert(id, waker);
34282 panic!("intentional timer-driver register panic after arming");
34283 }
34284
34285 fn cancel(&self, id: TimerId) {
34286 self.cancels.lock().unwrap().push(id);
34287 self.pending.lock().unwrap().remove(&id);
34288 }
34289 }
34290
34291 struct PanicBeforeRetainingTimerDriver {
34292 registers: AtomicUsize,
34293 }
34294
34295 impl TimerDriver for PanicBeforeRetainingTimerDriver {
34296 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34297 self.registers.fetch_add(1, Ordering::SeqCst);
34298 panic!("intentional timer-driver register panic before retaining its waker");
34299 }
34300
34301 fn cancel(&self, _id: TimerId) {}
34302 }
34303
34304 struct PanickingCancelDropTimerDriver {
34305 cancels: Arc<AtomicUsize>,
34306 drops: Arc<AtomicUsize>,
34307 }
34308
34309 impl TimerDriver for PanickingCancelDropTimerDriver {
34310 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34311
34312 fn cancel(&self, _id: TimerId) {
34313 self.cancels.fetch_add(1, Ordering::SeqCst);
34314 panic!("intentional timer-driver cancel panic");
34315 }
34316 }
34317
34318 impl Drop for PanickingCancelDropTimerDriver {
34319 fn drop(&mut self) {
34320 self.drops.fetch_add(1, Ordering::SeqCst);
34321 panic!("intentional timer-driver destructor panic");
34322 }
34323 }
34324
34325 struct UnregisterLaterThenPanicTimerDriver {
34326 registry: Weak<TimerDriverRegistry>,
34327 later: Mutex<Option<TimerDriverId>>,
34328 registers: AtomicUsize,
34329 }
34330
34331 impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
34332 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34333 self.registers.fetch_add(1, Ordering::SeqCst);
34334 let later = *self.later.lock().unwrap();
34335 if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
34336 registry.unregister(later);
34337 }
34338 panic!("intentional earlier timer-driver panic after unregistering a later driver");
34339 }
34340
34341 fn cancel(&self, _id: TimerId) {}
34342 }
34343
34344 struct ReentrantLivenessDriver {
34345 registry: std::sync::Weak<TimerDriverRegistry>,
34346 registration: Mutex<Option<TimerDriverId>>,
34347 }
34348
34349 impl TimerDriver for ReentrantLivenessDriver {
34350 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34351
34352 fn cancel(&self, _id: TimerId) {}
34353
34354 fn is_live(&self) -> bool {
34355 let registration = *self.registration.lock().unwrap();
34356 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
34357 registry.unregister(id);
34358 }
34359 false
34360 }
34361 }
34362
34363 struct ReentrantMultiUnregisterTimerDriver {
34364 registry: std::sync::Weak<TimerDriverRegistry>,
34365 registrations: Mutex<Vec<TimerDriverId>>,
34366 }
34367
34368 impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
34369 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34370
34371 fn cancel(&self, _id: TimerId) {}
34372
34373 fn is_live(&self) -> bool {
34374 if let Some(registry) = self.registry.upgrade() {
34375 for registration in self.registrations.lock().unwrap().iter().copied() {
34376 registry.unregister(registration);
34377 }
34378 }
34379 false
34380 }
34381 }
34382
34383 struct ReentrantSweepTimerDriver {
34384 registry: std::sync::Weak<TimerDriverRegistry>,
34385 registration: Mutex<Option<TimerDriverId>>,
34386 sweeps: AtomicUsize,
34387 removed_before_sweep: AtomicBool,
34388 }
34389
34390 impl TimerDriver for ReentrantSweepTimerDriver {
34391 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34392
34393 fn cancel(&self, _id: TimerId) {}
34394
34395 fn is_live(&self) -> bool {
34396 false
34397 }
34398
34399 fn on_swept(&self) {
34400 self.sweeps.fetch_add(1, Ordering::SeqCst);
34401 let registration = self.registration.lock().unwrap().take();
34402 if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
34403 let state = registry
34404 .state
34405 .try_lock()
34406 .expect("the sweep callback must run outside the timer-driver registry lock");
34407 assert!(
34408 !state.entries.contains_key(®istration),
34409 "the dead driver must be unlinked before its sweep callback runs"
34410 );
34411 self.removed_before_sweep.store(true, Ordering::SeqCst);
34412 drop(state);
34413 registry.unregister(registration);
34416 }
34417 }
34418 }
34419
34420 struct ReentrantDropDriver {
34421 registry: std::sync::Weak<TimerDriverRegistry>,
34422 fallback: TimerDriverId,
34423 }
34424
34425 impl TimerDriver for ReentrantDropDriver {
34426 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34427
34428 fn cancel(&self, _id: TimerId) {}
34429 }
34430
34431 impl Drop for ReentrantDropDriver {
34432 fn drop(&mut self) {
34433 if let Some(registry) = self.registry.upgrade() {
34434 registry.unregister(self.fallback);
34435 }
34436 }
34437 }
34438
34439 #[derive(Default)]
34442 struct WakeFlag(AtomicBool);
34443
34444 impl std::task::Wake for WakeFlag {
34445 fn wake(self: Arc<Self>) {
34446 self.0.store(true, Ordering::SeqCst);
34447 }
34448 }
34449
34450 #[cfg(napi_runtime_os_threads)]
34451 fn multi_thread_executor(
34452 workers: usize,
34453 park_deadline: Option<Duration>,
34454 prefix: &str,
34455 ) -> Arc<MultiThreadExecutor> {
34456 let options = RuntimeOptions {
34457 flavor: RuntimeFlavor::MultiThread,
34458 worker_threads: workers,
34459 max_blocking_tasks: workers,
34460 thread_name_prefix: prefix.to_string(),
34461 park_deadline,
34462 drain_linger: DEFAULT_DRAIN_LINGER,
34463 };
34464 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
34465 }
34466
34467 fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
34470 let registry = Arc::new(TimerDriverRegistry::default());
34471 registry.register(driver);
34472 registry
34473 }
34474
34475 #[cfg(napi_runtime_os_threads)]
34476 fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
34477 make_sleep(
34479 &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
34480 &Arc::new(TimerDriverRegistry::default()),
34481 deadline,
34482 )
34483 }
34484
34485 #[cfg(napi_runtime_os_threads)]
34486 #[test]
34487 fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
34488 use std::sync::mpsc;
34489
34490 let executor = multi_thread_executor(
34491 1,
34492 Some(Duration::from_secs(1)),
34493 "timer-registration-admission",
34494 );
34495 let (entered_tx, entered_rx) = mpsc::channel();
34496 let (release_tx, release_rx) = mpsc::channel();
34497 let registration_executor = Arc::clone(&executor);
34498 let registration = std::thread::spawn(move || {
34499 TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
34500 *slot.borrow_mut() = Some(Box::new(move || {
34501 entered_tx.send(()).unwrap();
34502 release_rx.recv().unwrap();
34503 }));
34504 });
34505 registration_executor.register_timer(
34506 1,
34507 Instant::now() + Duration::from_secs(60),
34508 futures::task::noop_waker(),
34509 &Arc::new(AtomicBool::new(false)),
34510 );
34511 });
34512
34513 entered_rx
34514 .recv_timeout(Duration::from_secs(2))
34515 .expect("timer registration must pause after entering verdict admission");
34516 let verdict = executor.try_begin_deadlock_verdict();
34517 let admission_blocked_verdict = verdict.is_none();
34518 drop(verdict);
34519 release_tx.send(()).unwrap();
34520 registration.join().unwrap();
34521
34522 assert!(
34523 admission_blocked_verdict,
34524 "a timer registration must remain announced until its heap entry is visible"
34525 );
34526 assert!(
34527 executor.next_timer_deadline().is_some(),
34528 "the admitted registration must publish its timer after release"
34529 );
34530 executor.begin_shutdown();
34531 executor.wait_until_scheduler_idle();
34532 }
34533
34534 #[cfg(napi_runtime_os_threads)]
34535 #[derive(Clone, Copy, Debug)]
34536 enum TimerFireAdmissionPath {
34537 Timekeeper,
34538 Cooperative,
34539 Shutdown,
34540 }
34541
34542 #[cfg(napi_runtime_os_threads)]
34543 struct ScheduleAfterTimerWakeRelease {
34544 executor: Arc<MultiThreadExecutor>,
34545 runnable: Mutex<Option<Runnable>>,
34546 entered: std::sync::mpsc::Sender<()>,
34547 release: Mutex<std::sync::mpsc::Receiver<()>>,
34548 scheduled: std::sync::mpsc::Sender<()>,
34549 }
34550
34551 #[cfg(napi_runtime_os_threads)]
34552 impl ScheduleAfterTimerWakeRelease {
34553 fn publish(&self) {
34554 self.entered.send(()).unwrap();
34555 self
34556 .release
34557 .lock()
34558 .unwrap_or_else(std::sync::PoisonError::into_inner)
34559 .recv()
34560 .unwrap();
34561 let runnable = self
34562 .runnable
34563 .lock()
34564 .unwrap_or_else(std::sync::PoisonError::into_inner)
34565 .take();
34566 if let Some(runnable) = runnable {
34567 self.executor.schedule(runnable);
34568 }
34569 self.scheduled.send(()).unwrap();
34570 }
34571 }
34572
34573 #[cfg(napi_runtime_os_threads)]
34574 impl std::task::Wake for ScheduleAfterTimerWakeRelease {
34575 fn wake(self: Arc<Self>) {
34576 self.publish();
34577 }
34578
34579 fn wake_by_ref(self: &Arc<Self>) {
34580 self.publish();
34581 }
34582 }
34583
34584 #[cfg(napi_runtime_os_threads)]
34585 fn insert_timer_for_fire_admission_test(
34586 executor: &MultiThreadExecutor,
34587 id: TimerId,
34588 deadline: Instant,
34589 waker: Waker,
34590 fired: Arc<AtomicBool>,
34591 ) {
34592 let mut inner = executor
34593 .timers
34594 .inner
34595 .lock()
34596 .unwrap_or_else(std::sync::PoisonError::into_inner);
34597 inner.entries.insert(id, HeapTimerEntry { waker, fired });
34598 inner.queue.push(deadline, id);
34599 }
34600
34601 #[cfg(napi_runtime_os_threads)]
34602 fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
34603 use std::sync::mpsc;
34604
34605 let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
34606 let (runnable, task) = async_task::spawn(async {}, |_| {});
34607 task.detach();
34608 let (entered_tx, entered_rx) = mpsc::channel();
34609 let (release_tx, release_rx) = mpsc::channel();
34610 let (scheduled_tx, scheduled_rx) = mpsc::channel();
34611 let fired = Arc::new(AtomicBool::new(false));
34612 let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
34613 executor: Arc::clone(&executor),
34614 runnable: Mutex::new(Some(runnable)),
34615 entered: entered_tx,
34616 release: Mutex::new(release_rx),
34617 scheduled: scheduled_tx,
34618 }));
34619
34620 let fire_thread = match path {
34621 TimerFireAdmissionPath::Timekeeper => {
34622 executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
34623 None
34624 }
34625 TimerFireAdmissionPath::Cooperative => {
34626 insert_timer_for_fire_admission_test(
34627 &executor,
34628 1,
34629 Instant::now(),
34630 waker,
34631 Arc::clone(&fired),
34632 );
34633 let fire_executor = Arc::clone(&executor);
34634 Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
34635 }
34636 TimerFireAdmissionPath::Shutdown => {
34637 insert_timer_for_fire_admission_test(
34638 &executor,
34639 1,
34640 Instant::now() + Duration::from_secs(60),
34641 waker,
34642 Arc::clone(&fired),
34643 );
34644 let fire_executor = Arc::clone(&executor);
34645 Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
34646 }
34647 };
34648
34649 entered_rx
34653 .recv_timeout(Duration::from_secs(10))
34654 .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
34655 assert!(
34656 executor.next_timer_deadline().is_none(),
34657 "{path:?} must exercise the remove-before-wake window"
34658 );
34659 assert!(
34660 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
34661 "{path:?} must retain admission while its waker is publishing"
34662 );
34663 let verdict = executor.try_begin_deadlock_verdict();
34664 let admission_blocked_verdict = verdict.is_none();
34665 drop(verdict);
34666
34667 release_tx.send(()).unwrap();
34668 scheduled_rx
34669 .recv_timeout(Duration::from_secs(10))
34670 .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
34671 if let Some(fire_thread) = fire_thread {
34672 fire_thread.join().unwrap();
34673 }
34674 assert!(
34675 admission_blocked_verdict,
34676 "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
34677 );
34678
34679 executor.begin_shutdown();
34680 executor.wait_until_scheduler_idle();
34681 }
34682
34683 #[cfg(napi_runtime_os_threads)]
34684 #[test]
34685 fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
34686 for path in [
34687 TimerFireAdmissionPath::Timekeeper,
34688 TimerFireAdmissionPath::Cooperative,
34689 TimerFireAdmissionPath::Shutdown,
34690 ] {
34691 assert_timer_fire_path_holds_deadlock_admission(path);
34692 }
34693 }
34694
34695 #[cfg(napi_runtime_os_threads)]
34696 #[test]
34697 fn multi_thread_sleep_fires_from_foreign_block_on() {
34698 use std::sync::mpsc;
34703
34704 let (done_tx, done_rx) = mpsc::channel();
34705 let runner = std::thread::spawn(move || {
34706 let executor = multi_thread_executor(2, None, "timer-foreign");
34707 let started = Instant::now();
34708 let mut future = std::pin::pin!(async {
34709 heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
34710 });
34711 executor.block_on(future.as_mut());
34712 assert!(
34713 started.elapsed() >= Duration::from_millis(80),
34714 "the sleep must not resolve before its deadline"
34715 );
34716 let _ = done_tx.send(());
34717 });
34718
34719 match done_rx.recv_timeout(Duration::from_secs(10)) {
34720 Ok(()) => runner.join().unwrap(),
34721 Err(error) => {
34722 panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
34723 }
34724 }
34725 }
34726
34727 #[cfg(napi_runtime_os_threads)]
34728 #[test]
34729 fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
34730 use std::sync::mpsc;
34733
34734 let (done_tx, done_rx) = mpsc::channel();
34735 let runner = std::thread::spawn(move || {
34736 let executor = multi_thread_executor(1, None, "timer-drainer");
34737
34738 let sleep_exec = Arc::clone(&executor);
34741 let sleep_done = Arc::new(AtomicBool::new(false));
34742 let sleep_flag = Arc::clone(&sleep_done);
34743 let sched = Arc::clone(&executor);
34744 let (sleep_runnable, sleep_task) = async_task::spawn(
34745 async move {
34746 heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
34747 sleep_flag.store(true, Ordering::SeqCst);
34748 },
34749 move |r| sched.schedule(r),
34750 );
34751 executor.schedule(sleep_runnable);
34752 wait_until("the timekeeper parked on the 60s deadline", || {
34753 executor
34754 .timers
34755 .inner
34756 .lock()
34757 .unwrap()
34758 .timekeeper_parker
34759 .is_some()
34760 });
34761
34762 let counter = Arc::new(AtomicUsize::new(0));
34764 let mut tasks = Vec::new();
34765 for _ in 0..32 {
34766 let counter = Arc::clone(&counter);
34767 let sched = Arc::clone(&executor);
34768 let (runnable, task) = async_task::spawn(
34769 async move {
34770 counter.fetch_add(1, Ordering::SeqCst);
34771 },
34772 move |r| sched.schedule(r),
34773 );
34774 executor.schedule(runnable);
34775 tasks.push(task);
34776 }
34777 for task in tasks {
34778 futures::executor::block_on(task);
34779 }
34780 assert_eq!(counter.load(Ordering::SeqCst), 32);
34781 assert!(
34782 !sleep_done.load(Ordering::SeqCst),
34783 "the 60s timer must still be pending: the work ran DURING the timer wait"
34784 );
34785 executor.shutdown_timers();
34787 futures::executor::block_on(sleep_task);
34788 let _ = done_tx.send(());
34789 });
34790
34791 match done_rx.recv_timeout(Duration::from_secs(15)) {
34792 Ok(()) => runner.join().unwrap(),
34793 Err(error) => {
34794 panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
34795 }
34796 }
34797 }
34798
34799 #[cfg(napi_runtime_os_threads)]
34800 #[test]
34801 fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
34802 let executor = multi_thread_executor(2, None, "timer-lazy-start");
34803 assert!(
34804 executor.timers.thread.lock().unwrap().is_none(),
34805 "constructing a runtime without timers must not create an extra OS thread"
34806 );
34807
34808 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34809 assert!(
34810 executor.timers.thread.lock().unwrap().is_none(),
34811 "an unpolled sleep has not registered a deadline"
34812 );
34813 let waker = futures::task::noop_waker();
34814 let mut cx = Context::from_waker(&waker);
34815 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34816 assert!(
34817 executor.timers.thread.lock().unwrap().is_some(),
34818 "the first timer registration must start the dedicated service"
34819 );
34820 }
34821
34822 #[cfg(napi_runtime_os_threads)]
34823 #[test]
34824 fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
34825 use std::sync::mpsc;
34826
34827 let executor = multi_thread_executor(1, None, "timer-closed-start");
34828 executor.shutdown_timers();
34829 let handle_guard = executor.timers.thread.lock().unwrap();
34830 let ensure_executor = Arc::clone(&executor);
34831 let (completed_tx, completed_rx) = mpsc::channel();
34832 let ensure = std::thread::spawn(move || {
34833 ensure_executor.timers.ensure_started().unwrap();
34834 completed_tx.send(()).unwrap();
34835 });
34836
34837 completed_rx.recv_timeout(Duration::from_secs(10)).expect(
34840 "ensure_started consulted or joined the timer handle before observing the closed heap",
34841 );
34842 drop(handle_guard);
34843 ensure.join().unwrap();
34844 }
34845
34846 #[cfg(napi_runtime_os_threads)]
34847 #[test]
34848 fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
34849 const TIMERS: u64 = 4096;
34850
34851 let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
34852 let base = Instant::now() + Duration::from_secs(3600);
34853 for id in 0..TIMERS {
34854 executor.register_timer(
34855 id,
34856 base + Duration::from_secs(id),
34857 futures::task::noop_waker(),
34858 &Arc::new(AtomicBool::new(false)),
34859 );
34860 }
34861 {
34862 let inner = executor.timers.inner.lock().unwrap();
34863 assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
34864 assert_eq!(inner.queue.len(), inner.entries.len());
34865 }
34866
34867 for id in 1..TIMERS {
34870 executor.cancel_timer(id);
34871 }
34872 {
34873 let inner = executor.timers.inner.lock().unwrap();
34874 assert_eq!(inner.entries.len(), 1);
34875 assert_eq!(inner.queue.len(), 1);
34876 assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
34877 }
34878
34879 executor.cancel_timer(0);
34880 {
34881 let inner = executor.timers.inner.lock().unwrap();
34882 assert!(inner.entries.is_empty());
34883 assert!(inner.queue.is_empty());
34884 }
34885 executor.shutdown_timers();
34886 executor.timers.join();
34887 }
34888
34889 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
34890 #[test]
34891 fn timer_registration_is_transactional_when_timekeeper_start_fails() {
34892 let executor = multi_thread_executor(2, None, "timer-start-failure");
34893 executor
34894 .timers
34895 .fail_next_start
34896 .store(true, Ordering::SeqCst);
34897
34898 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34899 let waker = Waker::from(Arc::clone(&wake_count));
34900 let baseline_waker_refs = Arc::strong_count(&wake_count);
34901 let mut cx = Context::from_waker(&waker);
34902 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34903
34904 let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
34905 assert!(
34906 failure.is_err(),
34907 "the injected timer-thread start failure must panic the poll"
34908 );
34909 {
34910 let inner = executor.timers.inner.lock().unwrap();
34911 assert!(
34912 inner.entries.is_empty(),
34913 "a failed start must not retain a timer entry"
34914 );
34915 assert!(
34916 inner.queue.is_empty(),
34917 "a failed start must not retain a stale heap node"
34918 );
34919 assert!(
34920 inner.timekeeper_parker.is_none(),
34921 "a failed start must not publish a timekeeper parker"
34922 );
34923 }
34924 assert!(
34925 executor.timers.thread.lock().unwrap().is_none(),
34926 "a failed start must not retain a thread handle"
34927 );
34928 assert_eq!(
34929 Arc::strong_count(&wake_count),
34930 baseline_waker_refs,
34931 "a failed start must release the cloned timer waker"
34932 );
34933
34934 assert!(
34935 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34936 "the same Sleep must be safely retryable after a transient start failure"
34937 );
34938 {
34939 let inner = executor.timers.inner.lock().unwrap();
34940 assert_eq!(
34941 inner.entries.len(),
34942 1,
34943 "the retry must publish exactly one timer entry"
34944 );
34945 assert_eq!(
34946 inner.queue.len(),
34947 1,
34948 "the retry must publish exactly one heap node"
34949 );
34950 }
34951 assert!(
34952 executor.timers.thread.lock().unwrap().is_some(),
34953 "the retry must start the dedicated timekeeper"
34954 );
34955
34956 drop(sleep);
34957 assert!(
34958 executor.timers.inner.lock().unwrap().entries.is_empty(),
34959 "dropping the retried Sleep must cancel its registration"
34960 );
34961 executor.shutdown_timers();
34962 executor.wait_until_scheduler_idle();
34963 assert!(
34964 executor.timers.thread.lock().unwrap().is_none(),
34965 "shutdown after a recovered start failure must join the timekeeper"
34966 );
34967 }
34968
34969 #[cfg(napi_runtime_os_threads)]
34970 #[test]
34971 fn pending_timer_preserves_every_configured_rayon_worker() {
34972 use std::sync::mpsc;
34973
34974 let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
34975 let waker = futures::task::noop_waker();
34976 let mut cx = Context::from_waker(&waker);
34977 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34978 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34979 wait_until("the dedicated timer thread to park", || {
34980 executor
34981 .timers
34982 .inner
34983 .lock()
34984 .unwrap()
34985 .timekeeper_parker
34986 .is_some()
34987 });
34988
34989 let released = Arc::new((Mutex::new(false), Condvar::new()));
34990 let (started_tx, started_rx) = mpsc::channel();
34991 let (completed_tx, completed_rx) = mpsc::channel();
34992 for _ in 0..2 {
34993 let released = Arc::clone(&released);
34994 let started_tx = started_tx.clone();
34995 let completed_tx = completed_tx.clone();
34996 executor.pool.spawn(move || {
34997 started_tx.send(()).unwrap();
34998 let (lock, changed) = &*released;
34999 let mut released = lock
35000 .lock()
35001 .unwrap_or_else(std::sync::PoisonError::into_inner);
35002 while !*released {
35003 released = changed
35004 .wait(released)
35005 .unwrap_or_else(std::sync::PoisonError::into_inner);
35006 }
35007 completed_tx.send(()).unwrap();
35008 });
35009 }
35010 drop(started_tx);
35011 drop(completed_tx);
35012
35013 let first = started_rx.recv_timeout(Duration::from_secs(2));
35014 let second = started_rx.recv_timeout(Duration::from_secs(2));
35015 {
35016 let (lock, changed) = &*released;
35017 *lock
35018 .lock()
35019 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
35020 changed.notify_all();
35021 }
35022 drop(sleep);
35023 for _ in 0..2 {
35024 completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
35025 }
35026
35027 assert!(
35028 first.is_ok(),
35029 "the first configured Rayon worker did not start"
35030 );
35031 assert!(
35032 second.is_ok(),
35033 "a pending timer consumed one of the two configured Rayon workers"
35034 );
35035 }
35036
35037 #[cfg(napi_runtime_os_threads)]
35038 #[test]
35039 fn timekeeper_never_enters_a_blocking_closure() {
35040 use std::{
35041 sync::{Barrier, mpsc},
35042 time::Duration,
35043 };
35044
35045 let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
35046 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
35047 let waker = Waker::from(Arc::clone(&wake_count));
35048 let mut cx = Context::from_waker(&waker);
35049 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
35050 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35051 wait_until(
35052 "the timekeeper parked before blocking work was submitted",
35053 || {
35054 executor
35055 .timers
35056 .inner
35057 .lock()
35058 .unwrap()
35059 .timekeeper_parker
35060 .is_some()
35061 },
35062 );
35063
35064 let release = Arc::new(Barrier::new(2));
35065 let blocker_release = Arc::clone(&release);
35066 let (started_tx, started_rx) = mpsc::sync_channel(1);
35067 let blocking = executor.schedule_blocking(move || {
35068 started_tx.send(()).unwrap();
35069 blocker_release.wait();
35070 });
35071 started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
35074
35075 wait_until(
35076 "the timer waker to fire while the Rayon worker is blocked",
35077 || wake_count.0.load(Ordering::SeqCst) != 0,
35078 );
35079 release.wait();
35080 futures::executor::block_on(blocking).unwrap();
35081 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
35082 }
35083
35084 #[cfg(napi_runtime_os_threads)]
35085 #[test]
35086 fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
35087 use std::sync::mpsc;
35088
35089 struct PendingEntered(Option<mpsc::Sender<()>>);
35090
35091 impl Future for PendingEntered {
35092 type Output = ();
35093
35094 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
35095 if let Some(entered) = self.0.take() {
35096 entered.send(()).unwrap();
35097 }
35098 Poll::Pending
35099 }
35100 }
35101
35102 let options = RuntimeOptions {
35103 flavor: RuntimeFlavor::MultiThread,
35104 worker_threads: 2,
35105 max_blocking_tasks: 1,
35106 thread_name_prefix: "nested-timer-wake".to_string(),
35107 park_deadline: None,
35108 drain_linger: DEFAULT_DRAIN_LINGER,
35109 };
35110 let executor =
35111 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
35112
35113 let timer_exec = Arc::clone(&executor);
35114 let (timer_tx, timer_rx) = mpsc::channel();
35115 let timer_scheduler = Arc::clone(&executor);
35116 let (timer_runnable, timer_task) = async_task::spawn(
35117 async move {
35118 heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
35119 timer_tx.send(()).unwrap();
35120 },
35121 move |runnable| timer_scheduler.schedule(runnable),
35122 );
35123 executor.schedule(timer_runnable);
35124 wait_until(
35125 "the dedicated timekeeper parked before nested block_on",
35126 || {
35127 executor
35128 .timers
35129 .inner
35130 .lock()
35131 .unwrap()
35132 .timekeeper_parker
35133 .is_some()
35134 },
35135 );
35136
35137 let (entered_tx, entered_rx) = mpsc::channel();
35138 let nested_exec = Arc::clone(&executor);
35139 let nested_scheduler = Arc::clone(&executor);
35140 let (nested_runnable, nested_task) = async_task::spawn(
35141 async move {
35142 let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
35143 let outcome = nested_exec.block_on(pending.as_mut());
35144 drop(pending);
35145 assert_eq!(
35146 outcome,
35147 BlockOnOutcome::Stopped,
35148 "shutdown must stop the raw nested block_on driver"
35149 );
35150 },
35151 move |runnable| nested_scheduler.schedule(runnable),
35152 );
35153 executor.schedule(nested_runnable);
35154 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
35155
35156 let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
35157 let (release_tx, release_rx) = mpsc::channel();
35158 let blocking = executor.schedule_blocking(move || {
35159 blocking_started_tx.send(()).unwrap();
35160 release_rx.recv().unwrap();
35161 });
35162 blocking_started_rx
35163 .recv_timeout(Duration::from_secs(2))
35164 .expect("a general worker must service blocking work");
35165 timer_rx
35166 .recv_timeout(Duration::from_secs(2))
35167 .expect("the timer wake must reach the nested cooperative driver");
35168
35169 release_tx.send(()).unwrap();
35170 futures::executor::block_on(blocking).unwrap();
35171 executor.begin_shutdown();
35172 assert_eq!(
35173 futures::executor::block_on(nested_task.fallible()),
35174 Some(())
35175 );
35176 futures::executor::block_on(timer_task);
35177 }
35178
35179 #[cfg(napi_runtime_os_threads)]
35180 #[test]
35181 fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
35182 use std::sync::mpsc;
35186
35187 let (done_tx, done_rx) = mpsc::channel();
35188 let runner = std::thread::spawn(move || {
35189 let executor = multi_thread_executor(2, None, "timer-rearm");
35190
35191 let long_exec = Arc::clone(&executor);
35192 let long_done = Arc::new(AtomicBool::new(false));
35193 let long_flag = Arc::clone(&long_done);
35194 let sched = Arc::clone(&executor);
35195 let (long_runnable, long_task) = async_task::spawn(
35196 async move {
35197 heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
35198 long_flag.store(true, Ordering::SeqCst);
35199 },
35200 move |r| sched.schedule(r),
35201 );
35202 executor.schedule(long_runnable);
35203 wait_until("the timekeeper parked on the 60s sentinel", || {
35204 executor
35205 .timers
35206 .inner
35207 .lock()
35208 .unwrap()
35209 .timekeeper_parker
35210 .is_some()
35211 });
35212
35213 let started = Instant::now();
35214 let mut future = std::pin::pin!(async {
35215 heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
35216 });
35217 executor.block_on(future.as_mut());
35218 let elapsed = started.elapsed();
35219 assert!(
35220 elapsed >= Duration::from_millis(60),
35221 "fired before its deadline: {elapsed:?}"
35222 );
35223 assert!(
35224 !long_done.load(Ordering::SeqCst),
35225 "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
35226 timekeeper, not by the sentinel expiring"
35227 );
35228 executor.shutdown_timers();
35229 futures::executor::block_on(long_task);
35230 let _ = done_tx.send(());
35231 });
35232
35233 match done_rx.recv_timeout(Duration::from_secs(15)) {
35234 Ok(()) => runner.join().unwrap(),
35235 Err(error) => panic!(
35236 "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
35237 ),
35238 }
35239 }
35240
35241 #[cfg(napi_runtime_os_threads)]
35242 #[test]
35243 fn multi_thread_sleep_cancels_registration_on_drop() {
35244 let executor = multi_thread_executor(1, None, "timer-cancel");
35247 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
35248 let waker = Waker::from(Arc::clone(&wake_count));
35249 let mut cx = Context::from_waker(&waker);
35250
35251 let deadline = Instant::now() + Duration::from_millis(250);
35255 let mut sleep = heap_sleep(&executor, deadline);
35256 assert!(
35257 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
35258 "must register, not resolve"
35259 );
35260 assert!(
35261 executor.next_timer_deadline().is_some(),
35262 "the registration must be in the heap"
35263 );
35264 drop(sleep);
35265 assert!(
35266 executor.next_timer_deadline().is_none(),
35267 "cancel-on-drop must retire the heap registration"
35268 );
35269 while Instant::now() < deadline + Duration::from_millis(25) {
35274 std::thread::sleep(Duration::from_millis(10));
35275 }
35276 executor.fire_due_timers();
35277 assert_eq!(
35278 wake_count.0.load(Ordering::SeqCst),
35279 0,
35280 "a dropped Sleep's waker must never fire"
35281 );
35282 }
35283
35284 #[cfg(napi_runtime_os_threads)]
35285 #[test]
35286 fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
35287 let executor = multi_thread_executor(2, None, "timer-cancel-retire");
35288 let waker = futures::task::noop_waker();
35289 let mut cx = Context::from_waker(&waker);
35290 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
35291
35292 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35293 wait_until(
35294 "the timekeeper parked on the timer that will be cancelled",
35295 || {
35296 executor
35297 .timers
35298 .inner
35299 .lock()
35300 .unwrap()
35301 .timekeeper_parker
35302 .is_some()
35303 },
35304 );
35305
35306 drop(sleep);
35307 wait_until(
35308 "the cancelled timer's timekeeper returned to its idle wait",
35309 || {
35310 executor.next_timer_deadline().is_none()
35311 && executor
35312 .timers
35313 .inner
35314 .lock()
35315 .unwrap()
35316 .timekeeper_parker
35317 .is_none()
35318 },
35319 );
35320 }
35321
35322 #[cfg(napi_runtime_os_threads)]
35323 #[test]
35324 fn timer_waker_replacement_drops_outside_heap_lock() {
35325 use std::sync::mpsc;
35326
35327 struct DropOtherTimer(Mutex<Option<Sleep>>);
35328
35329 #[expect(
35330 clippy::manual_noop_waker,
35331 reason = "the no-op wake type has a reentrant Drop used by this regression"
35332 )]
35333 impl std::task::Wake for DropOtherTimer {
35334 fn wake(self: Arc<Self>) {}
35335 }
35336
35337 impl Drop for DropOtherTimer {
35338 fn drop(&mut self) {
35339 drop(self.0.lock().unwrap().take());
35340 }
35341 }
35342
35343 let (done_tx, done_rx) = mpsc::channel();
35344 let runner = std::thread::spawn(move || {
35345 let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
35346 let deadline = Instant::now() + Duration::from_secs(120);
35347 let mut other = heap_sleep(&executor, deadline);
35348 let noop = futures::task::noop_waker();
35349 let mut noop_cx = Context::from_waker(&noop);
35350 assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
35351
35352 let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
35353 let mut primary = heap_sleep(&executor, deadline);
35354 let mut old_cx = Context::from_waker(&old);
35355 assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
35356 drop(old);
35357
35358 let replacement = futures::task::noop_waker();
35359 let mut replacement_cx = Context::from_waker(&replacement);
35360 assert!(
35361 Pin::new(&mut primary)
35362 .poll(&mut replacement_cx)
35363 .is_pending()
35364 );
35365 drop(primary);
35366 executor.shutdown_timers();
35367 done_tx.send(()).unwrap();
35368 });
35369 done_rx
35372 .recv_timeout(Duration::from_secs(10))
35373 .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
35374 runner.join().unwrap();
35375 }
35376
35377 #[cfg(napi_runtime_os_threads)]
35378 #[test]
35379 fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
35380 use std::sync::mpsc;
35381
35382 struct DropFinalExecutorOnWake {
35383 executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
35384 completed: mpsc::Sender<()>,
35385 }
35386
35387 impl std::task::Wake for DropFinalExecutorOnWake {
35388 fn wake(self: Arc<Self>) {
35389 let executor = self
35390 .executor
35391 .lock()
35392 .unwrap_or_else(std::sync::PoisonError::into_inner)
35393 .take()
35394 .expect("the timer waker must own the final executor reference");
35395 assert_eq!(
35396 ON_TIMER_THREAD.with(std::cell::Cell::get),
35397 Some(executor.id),
35398 "the dedicated timer thread must deliver the timer wake"
35399 );
35400 drop(executor);
35401 self
35402 .completed
35403 .send(())
35404 .expect("the test receiver must outlive the timer-thread callback");
35405 }
35406 }
35407
35408 let executor = multi_thread_executor(1, None, "timer-final-arc");
35409 let (completed_tx, completed_rx) = mpsc::channel();
35410 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(3600));
35416 {
35417 let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
35418 executor: Mutex::new(Some(Arc::clone(&executor))),
35419 completed: completed_tx,
35420 }));
35421 let mut cx = Context::from_waker(&waker);
35422 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35423 }
35424 wait_until(
35428 "the timer thread parked before owning the final executor Arc",
35429 || {
35430 executor
35431 .timers
35432 .inner
35433 .lock()
35434 .unwrap()
35435 .timekeeper_parker
35436 .as_ref()
35437 .is_some_and(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
35438 },
35439 );
35440 let parker = {
35441 let mut inner = executor.timers.inner.lock().unwrap();
35442 let (_, id) = inner.queue.peek().expect("the sleep's timer must be armed");
35443 inner.queue.remove(id);
35444 inner.queue.push(Instant::now(), id);
35445 Arc::clone(inner.timekeeper_parker.as_ref().unwrap())
35446 };
35447
35448 drop(executor);
35452 parker.unpark();
35453 completed_rx
35456 .recv_timeout(Duration::from_secs(10))
35457 .expect("dropping the final executor Arc on the timer thread attempted to join itself");
35458 }
35459
35460 #[cfg(not(target_family = "wasm"))]
35461 #[test]
35462 fn multi_thread_shutdown_with_pending_timers_drain_fires() {
35463 use std::sync::mpsc;
35468
35469 let (done_tx, done_rx) = mpsc::channel();
35470 let runner = std::thread::spawn(move || {
35471 let controller = Arc::new(RuntimeController::new());
35472 let backend = controller.backend();
35474 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
35475 panic!("default native backend must be MultiThread");
35476 };
35477 let executor = Arc::downgrade(executor);
35478 let mut late = heap_sleep(
35479 &executor
35480 .upgrade()
35481 .expect("the initial executor must be live"),
35482 Instant::now() + Duration::from_secs(120),
35483 );
35484 drop(backend);
35485
35486 let (parked_tx, parked_rx) = mpsc::channel();
35487 let block_controller = Arc::clone(&controller);
35488 let block_exec = Weak::clone(&executor);
35489 let blocker = std::thread::spawn(move || {
35490 let mut future = std::pin::pin!(async {
35491 let executor = block_exec
35492 .upgrade()
35493 .expect("the runtime must remain live while blocking");
35494 let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
35495 drop(executor);
35496 parked_tx.send(()).unwrap();
35497 sleep.await;
35498 });
35499 block_controller.block_on(future.as_mut());
35500 });
35501 parked_rx.recv().unwrap();
35502 wait_until("the 120s sleep registered in the heap", || {
35503 executor
35504 .upgrade()
35505 .is_some_and(|executor| executor.next_timer_deadline().is_some())
35506 });
35507
35508 controller
35510 .shutdown()
35511 .expect("runtime shutdown must succeed");
35512 blocker
35513 .join()
35514 .expect("the blocked sleep must be drain-fired by shutdown, not hang");
35515
35516 let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
35519 let mut cx = Context::from_waker(&waker);
35520 assert!(
35521 Pin::new(&mut late).poll(&mut cx).is_ready(),
35522 "a Sleep polled after shutdown must fire early, never park a closing runtime"
35523 );
35524 let _ = done_tx.send(());
35525 });
35526
35527 match done_rx.recv_timeout(Duration::from_secs(10)) {
35528 Ok(()) => runner.join().unwrap(),
35529 Err(error) => {
35530 panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
35531 }
35532 }
35533 }
35534
35535 #[cfg(napi_runtime_os_threads)]
35536 #[test]
35537 fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
35538 use std::sync::mpsc;
35547
35548 let (done_tx, done_rx) = mpsc::channel();
35549 let runner = std::thread::spawn(move || {
35550 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
35551
35552 let task_exec = Arc::clone(&executor);
35553 let body = async move {
35554 let started = Instant::now();
35555 let mut inner = std::pin::pin!(async {
35556 heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
35557 });
35558 task_exec.block_on(inner.as_mut());
35560 assert!(started.elapsed() >= Duration::from_millis(250));
35561 };
35562 let sched = Arc::clone(&executor);
35563 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
35564 executor.schedule(runnable);
35565
35566 futures::executor::block_on(task);
35570 drop(executor);
35576 let _ = done_tx.send(());
35577 });
35578
35579 match done_rx.recv_timeout(Duration::from_secs(10)) {
35580 Ok(()) => join_within(
35581 "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
35582 runner,
35583 Duration::from_secs(30),
35584 ),
35585 Err(error) => {
35586 panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
35587 }
35588 }
35589 }
35590
35591 #[cfg(napi_runtime_os_threads)]
35592 #[test]
35593 fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
35594 use std::sync::mpsc;
35601
35602 struct GateWake {
35605 gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
35606 }
35607 impl std::task::Wake for GateWake {
35608 fn wake(self: Arc<Self>) {
35609 let gate = self.gate.lock().unwrap().take();
35610 if let Some(gate) = gate {
35611 let _ = gate.send(23);
35612 }
35613 }
35614 }
35615
35616 let (done_tx, done_rx) = mpsc::channel();
35617 let runner = std::thread::spawn(move || {
35618 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
35619 executor
35623 .active_drainers
35624 .store(executor.max_drainers, Ordering::SeqCst);
35625
35626 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
35629 let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
35630
35631 let hook_exec = Arc::clone(&executor);
35632 let hook_gate = Arc::clone(&gate_tx);
35633 let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
35636 let hook_sleep_slot = Arc::clone(&parked_sleep);
35637 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
35638 *slot.borrow_mut() = Some(Box::new(move || {
35639 let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
35640 let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
35641 let mut cx = Context::from_waker(&waker);
35642 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35643 *hook_sleep_slot.lock().unwrap() = Some(sleep);
35644 }));
35645 });
35646
35647 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
35650 let mut output = None;
35651 {
35652 let mut future = std::pin::pin!(async {
35653 output = Some(gate_rx.await.unwrap());
35654 });
35655 executor.block_on(future.as_mut());
35656 }
35657 DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
35663 assert_eq!(
35664 output,
35665 Some(23),
35666 "the edge-registered timer must have fired and released the gate"
35667 );
35668 executor.active_drainers.store(0, Ordering::SeqCst);
35669 drop(executor);
35672 let _ = done_tx.send(());
35673 });
35674
35675 match done_rx.recv_timeout(Duration::from_secs(10)) {
35676 Ok(()) => join_within(
35677 "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
35678 runner,
35679 Duration::from_secs(30),
35680 ),
35681 Err(error) => panic!(
35682 "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
35683 ),
35684 }
35685 }
35686
35687 #[cfg(panic = "unwind")]
35688 #[test]
35689 fn current_thread_sleep_without_host_driver_fails_loud() {
35690 let metrics = Arc::new(RuntimeMetrics::default());
35694 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35695 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
35696
35697 let payload = catch_unwind(AssertUnwindSafe(|| {
35698 let _sleep = make_sleep(
35699 &backend,
35700 &Arc::new(TimerDriverRegistry::default()),
35701 Instant::now() + Duration::from_millis(10),
35702 );
35703 }))
35704 .expect_err("sleep_until on CurrentThread without a driver must panic");
35705 let message = payload
35706 .downcast_ref::<String>()
35707 .cloned()
35708 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
35709 .expect("panic payload must be a message");
35710 assert!(
35711 message.contains("CurrentThread runtime has no live timer driver registered"),
35712 "the diagnostic must name the missing driver, got: {message}"
35713 );
35714 }
35715
35716 #[test]
35717 fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
35718 let registry = TimerDriverRegistry::default();
35722 let a = ManualStubDriver::new();
35723 let b = ManualStubDriver::new();
35724 let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
35725 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
35726 assert_ne!(a_id, b_id, "registration handles must be distinct");
35727 assert_eq!(
35728 registry.current().map(|(id, _)| id),
35729 Some(b_id),
35730 "newest live registrant serves"
35731 );
35732 assert!(registry.has_live_driver());
35733
35734 b.kill();
35737 assert_eq!(
35738 registry.current().map(|(id, _)| id),
35739 Some(a_id),
35740 "dead newest must be skipped"
35741 );
35742 b.live.store(true, Ordering::SeqCst);
35745 assert_eq!(
35746 registry.current().map(|(id, _)| id),
35747 Some(a_id),
35748 "swept entries stay gone"
35749 );
35750
35751 registry.unregister(a_id);
35754 assert!(registry.current().is_none());
35755 assert!(
35756 !registry.has_live_driver(),
35757 "an emptied registry must not report a live driver"
35758 );
35759 }
35760
35761 #[cfg(panic = "unwind")]
35762 #[test]
35763 fn timer_driver_registry_contains_callback_panics_and_falls_back() {
35764 let registry = TimerDriverRegistry::default();
35765 let fallback = ManualStubDriver::new();
35766 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35767 let panicking = Arc::new(PanickingTimerDriver {
35768 swept: AtomicBool::new(false),
35769 });
35770 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35771
35772 assert_eq!(
35773 registry.current().map(|(id, _)| id),
35774 Some(fallback_id),
35775 "a panicking liveness callback must be treated as a dead driver"
35776 );
35777 assert!(
35778 panicking.swept.load(Ordering::SeqCst),
35779 "a panicking liveness callback must still run its contained sweep hook"
35780 );
35781 assert!(
35782 registry.has_live_driver(),
35783 "the healthy fallback must remain selectable"
35784 );
35785 }
35786
35787 #[cfg(napi_runtime_os_threads)]
35788 #[test]
35789 fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
35790 use std::{sync::mpsc, time::Duration};
35791
35792 let registry = Arc::new(TimerDriverRegistry::default());
35793 let fallback = ManualStubDriver::new();
35794 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35795 let reentrant = Arc::new(ReentrantLivenessDriver {
35796 registry: Arc::downgrade(®istry),
35797 registration: Mutex::new(None),
35798 });
35799 let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35800 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
35801
35802 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
35803 let selection_registry = Arc::clone(®istry);
35804 let selection_thread = std::thread::spawn(move || {
35805 selection_tx
35806 .send(selection_registry.current().map(|(id, _)| id))
35807 .unwrap();
35808 });
35809 assert_eq!(
35810 selection_rx
35811 .recv_timeout(Duration::from_secs(10))
35812 .expect("is_live re-entry deadlocked"),
35813 Some(fallback_id)
35814 );
35815 selection_thread.join().unwrap();
35816
35817 let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
35818 registry: Arc::downgrade(®istry),
35819 fallback: fallback_id,
35820 });
35821 let drop_id = registry.register(Arc::clone(&drop_driver));
35822 drop(drop_driver);
35823
35824 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
35825 let drop_registry = Arc::clone(®istry);
35826 let drop_thread = std::thread::spawn(move || {
35827 drop_registry.unregister(drop_id);
35828 drop_tx.send(()).unwrap();
35829 });
35830 drop_rx
35831 .recv_timeout(Duration::from_secs(10))
35832 .expect("driver destructor re-entry deadlocked");
35833 drop_thread.join().unwrap();
35834 assert!(
35835 registry.current().is_none(),
35836 "the destructor must be able to unregister the fallback"
35837 );
35838 }
35839
35840 #[test]
35841 fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
35842 const DRIVER_PAIRS: usize = 2_048;
35843
35844 let registry = Arc::new(TimerDriverRegistry::default());
35845 let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
35846 let mut live = Vec::with_capacity(DRIVER_PAIRS);
35847 let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
35848 for _ in 0..DRIVER_PAIRS {
35849 let driver = Arc::new(ReentrantSweepTimerDriver {
35850 registry: Arc::downgrade(®istry),
35851 registration: Mutex::new(None),
35852 sweeps: AtomicUsize::new(0),
35853 removed_before_sweep: AtomicBool::new(false),
35854 });
35855 let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35856 *driver.registration.lock().unwrap() = Some(id);
35857 dead.push(driver);
35858
35859 let driver = ManualStubDriver::new();
35860 live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
35861 live.push(driver);
35862 }
35863 let dead_tail = Arc::new(ReentrantSweepTimerDriver {
35864 registry: Arc::downgrade(®istry),
35865 registration: Mutex::new(None),
35866 sweeps: AtomicUsize::new(0),
35867 removed_before_sweep: AtomicBool::new(false),
35868 });
35869 let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
35870 *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
35871 dead.push(dead_tail);
35872
35873 let selection = registry.live();
35874 assert_eq!(
35875 selection
35876 .as_slice()
35877 .iter()
35878 .map(|(id, _)| *id)
35879 .collect::<Vec<_>>(),
35880 live_ids,
35881 "sweeping every interleaved dead host must retain live registration order"
35882 );
35883 drop(selection);
35884 assert!(
35885 dead.iter().all(|driver| {
35886 driver.sweeps.load(Ordering::SeqCst) == 1
35887 && driver.removed_before_sweep.load(Ordering::SeqCst)
35888 && driver.registration.lock().unwrap().is_none()
35889 }),
35890 "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
35891 );
35892
35893 {
35894 let state = registry
35895 .state
35896 .lock()
35897 .unwrap_or_else(std::sync::PoisonError::into_inner);
35898 assert_eq!(state.entries.len(), DRIVER_PAIRS);
35899 assert_eq!(state.oldest, live_ids.first().copied());
35900 assert_eq!(state.newest, live_ids.last().copied());
35901 assert_eq!(
35902 state
35903 .ordered_snapshot()
35904 .iter()
35905 .map(|(id, _)| *id)
35906 .collect::<Vec<_>>(),
35907 live_ids,
35908 "keyed removals must preserve the linked order of every live host"
35909 );
35910 }
35911 let second_selection = registry.live();
35912 assert_eq!(
35913 second_selection
35914 .as_slice()
35915 .iter()
35916 .map(|(id, _)| *id)
35917 .collect::<Vec<_>>(),
35918 live_ids,
35919 "a second selection must retain the same live order"
35920 );
35921 drop(second_selection);
35922 assert!(
35923 dead
35924 .iter()
35925 .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
35926 "permanently swept hosts must not receive repeated eviction callbacks"
35927 );
35928 assert_eq!(
35929 registry.current().map(|(id, _)| id),
35930 live_ids.last().copied(),
35931 "newest-live compatibility selection must survive the mass sweep"
35932 );
35933
35934 drop(live);
35935 drop(dead);
35936 }
35937
35938 #[cfg(not(target_family = "wasm"))]
35939 #[test]
35940 fn timer_driver_registry_snapshot_contains_each_driver_drop() {
35941 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
35942
35943 if std::env::var_os(CHILD_ENV).is_some() {
35944 let registry = Arc::new(TimerDriverRegistry::default());
35945 let fallback = ManualStubDriver::new();
35946 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35947 let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
35948 registry: Arc::downgrade(®istry),
35949 registrations: Mutex::new(Vec::new()),
35950 });
35951 registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35952
35953 let first_cancels = Arc::new(AtomicUsize::new(0));
35954 let first_drops = Arc::new(AtomicUsize::new(0));
35955 let first = Arc::new(PanickingCancelDropTimerDriver {
35956 cancels: Arc::clone(&first_cancels),
35957 drops: Arc::clone(&first_drops),
35958 });
35959 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35960 drop(first);
35961
35962 let second_cancels = Arc::new(AtomicUsize::new(0));
35963 let second_drops = Arc::new(AtomicUsize::new(0));
35964 let second = Arc::new(PanickingCancelDropTimerDriver {
35965 cancels: Arc::clone(&second_cancels),
35966 drops: Arc::clone(&second_drops),
35967 });
35968 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35969 drop(second);
35970 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
35971
35972 assert_eq!(
35973 registry.current().map(|(id, _)| id),
35974 Some(fallback_id),
35975 "selection must retry after reentrant removals and preserve the healthy fallback"
35976 );
35977 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35978 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35979 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35980 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35981 assert_eq!(
35982 registry
35983 .state
35984 .lock()
35985 .unwrap_or_else(std::sync::PoisonError::into_inner)
35986 .entries
35987 .len(),
35988 1
35989 );
35990 return;
35991 }
35992
35993 let output = std::process::Command::new(std::env::current_exe().unwrap())
35994 .arg("--exact")
35995 .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
35996 .arg("--nocapture")
35997 .env(CHILD_ENV, "1")
35998 .output()
35999 .expect("the hostile timer-driver snapshot subprocess test must start");
36000 assert!(
36001 output.status.success(),
36002 "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
36003 output.status.code(),
36004 String::from_utf8_lossy(&output.stdout),
36005 String::from_utf8_lossy(&output.stderr)
36006 );
36007 }
36008
36009 #[cfg(not(target_family = "wasm"))]
36010 #[test]
36011 fn timer_driver_registry_live_result_contains_each_driver_drop() {
36012 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
36013
36014 if std::env::var_os(CHILD_ENV).is_some() {
36015 let registry = TimerDriverRegistry::default();
36016
36017 let first_cancels = Arc::new(AtomicUsize::new(0));
36018 let first_drops = Arc::new(AtomicUsize::new(0));
36019 let first = Arc::new(PanickingCancelDropTimerDriver {
36020 cancels: Arc::clone(&first_cancels),
36021 drops: Arc::clone(&first_drops),
36022 });
36023 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
36024 drop(first);
36025
36026 let second_cancels = Arc::new(AtomicUsize::new(0));
36027 let second_drops = Arc::new(AtomicUsize::new(0));
36028 let second = Arc::new(PanickingCancelDropTimerDriver {
36029 cancels: Arc::clone(&second_cancels),
36030 drops: Arc::clone(&second_drops),
36031 });
36032 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
36033 drop(second);
36034
36035 let live = registry.live();
36036 assert_eq!(live.len(), 2);
36037 registry.unregister(first_id);
36038 registry.unregister(second_id);
36039 drop(live);
36040
36041 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
36042 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
36043 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
36044 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
36045 assert!(
36046 registry
36047 .state
36048 .lock()
36049 .unwrap_or_else(std::sync::PoisonError::into_inner)
36050 .entries
36051 .is_empty()
36052 );
36053 return;
36054 }
36055
36056 let output = std::process::Command::new(std::env::current_exe().unwrap())
36057 .arg("--exact")
36058 .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
36059 .arg("--nocapture")
36060 .env(CHILD_ENV, "1")
36061 .output()
36062 .expect("the hostile live timer-driver snapshot subprocess test must start");
36063 assert!(
36064 output.status.success(),
36065 "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
36066 output.status.code(),
36067 String::from_utf8_lossy(&output.stdout),
36068 String::from_utf8_lossy(&output.stderr)
36069 );
36070 }
36071
36072 #[cfg(not(target_family = "wasm"))]
36073 #[test]
36074 fn timer_driver_registry_drop_contains_each_driver_destructor() {
36075 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
36076
36077 if std::env::var_os(CHILD_ENV).is_some() {
36078 let first_drops = Arc::new(AtomicUsize::new(0));
36079 let second_drops = Arc::new(AtomicUsize::new(0));
36080 let registry = TimerDriverRegistry::default();
36081 let first = Arc::new(PanickingCancelDropTimerDriver {
36082 cancels: Arc::new(AtomicUsize::new(0)),
36083 drops: Arc::clone(&first_drops),
36084 });
36085 let second = Arc::new(PanickingCancelDropTimerDriver {
36086 cancels: Arc::new(AtomicUsize::new(0)),
36087 drops: Arc::clone(&second_drops),
36088 });
36089 registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
36090 registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
36091 drop(first);
36092 drop(second);
36093
36094 drop(registry);
36095
36096 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
36097 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
36098 return;
36099 }
36100
36101 let output = std::process::Command::new(std::env::current_exe().unwrap())
36102 .arg("--exact")
36103 .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
36104 .arg("--nocapture")
36105 .env(CHILD_ENV, "1")
36106 .output()
36107 .expect("the hostile timer-driver registry drop subprocess test must start");
36108 assert!(
36109 output.status.success(),
36110 "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
36111 output.status.code(),
36112 String::from_utf8_lossy(&output.stdout),
36113 String::from_utf8_lossy(&output.stderr)
36114 );
36115 }
36116
36117 #[test]
36118 fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
36119 const DRIVERS: usize = 512;
36120
36121 let metrics = Arc::new(RuntimeMetrics::default());
36122 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36123 let backend =
36124 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36125 let registry = Arc::new(TimerDriverRegistry::default());
36126 let drivers = (0..DRIVERS)
36127 .map(|_| {
36128 let driver = ManualStubDriver::new();
36129 registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36130 driver
36131 })
36132 .collect::<Vec<_>>();
36133 let mut sleep = make_sleep(
36134 &backend,
36135 ®istry,
36136 Instant::now() + Duration::from_secs(60),
36137 );
36138 let mut cx = Context::from_waker(Waker::noop());
36139
36140 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36141 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36142 let timer_id = drivers[0].registers.lock().unwrap()[0].0;
36143 match &sleep.inner {
36144 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
36145 #[cfg(napi_runtime_os_threads)]
36146 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
36147 }
36148 for driver in &drivers {
36149 assert_eq!(
36150 driver.registers.lock().unwrap().len(),
36151 2,
36152 "a re-poll must refresh each live driver exactly once"
36153 );
36154 }
36155
36156 for driver in drivers.iter().step_by(2) {
36157 driver.kill();
36158 }
36159 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36160 match &sleep.inner {
36161 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
36162 #[cfg(napi_runtime_os_threads)]
36163 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
36164 }
36165 for (index, driver) in drivers.iter().enumerate() {
36166 let registers = driver.registers.lock().unwrap().len();
36167 let cancels = driver.cancels.lock().unwrap().clone();
36168 if index % 2 == 0 {
36169 assert_eq!(registers, 2);
36170 assert_eq!(cancels, vec![timer_id]);
36171 } else {
36172 assert_eq!(registers, 3);
36173 assert!(cancels.is_empty());
36174 }
36175 }
36176
36177 drop(sleep);
36178 for driver in drivers {
36179 assert_eq!(
36180 driver.cancels.lock().unwrap().as_slice(),
36181 &[timer_id],
36182 "every physical arm must be cancelled exactly once"
36183 );
36184 }
36185 assert_eq!(registry.pending_len(), 0);
36186 assert!(!executor.host_timers.has_pending());
36187 }
36188
36189 #[cfg(panic = "unwind")]
36190 #[test]
36191 fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
36192 let metrics = Arc::new(RuntimeMetrics::default());
36193 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36194 let backend =
36195 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36196 let healthy = ManualStubDriver::new();
36197 let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
36198 let deadline = Instant::now() + Duration::from_secs(60);
36199 let mut sleep = make_sleep(&backend, ®istry, deadline);
36200 let mut cx = Context::from_waker(Waker::noop());
36201
36202 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36203 let timer_id = healthy.registers.lock().unwrap()[0].0;
36204
36205 let panicking = PanickingRegisterTimerDriver::new();
36206 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
36207 let result = catch_unwind(AssertUnwindSafe(|| {
36208 let _ = Pin::new(&mut sleep).poll(&mut cx);
36209 }));
36210 assert!(
36211 result.is_err(),
36212 "the hostile register panic must retain its normal propagation"
36213 );
36214 assert_eq!(
36215 healthy.registers.lock().unwrap().len(),
36216 2,
36217 "the previously armed driver must have completed its refresh before the later panic"
36218 );
36219 assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
36220 assert!(
36221 panicking.pending.lock().unwrap().contains_key(&timer_id),
36222 "the hostile driver must model an arm committed before it panics"
36223 );
36224
36225 drop(sleep);
36226 assert_eq!(
36227 healthy.cancels.lock().unwrap().as_slice(),
36228 &[timer_id],
36229 "unwind must preserve cancellation ownership for the previously armed driver"
36230 );
36231 assert_eq!(
36232 panicking.cancels.lock().unwrap().as_slice(),
36233 &[timer_id],
36234 "unwind must preserve cancellation ownership for the driver that armed and then panicked"
36235 );
36236 assert!(panicking.pending.lock().unwrap().is_empty());
36237 assert_eq!(registry.pending_len(), 0);
36238 assert!(!executor.host_timers.has_pending());
36239 }
36240
36241 #[cfg(not(target_family = "wasm"))]
36242 #[test]
36243 fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
36244 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
36245
36246 if std::env::var_os(CHILD_ENV).is_some() {
36247 let metrics = Arc::new(RuntimeMetrics::default());
36248 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36249 let backend =
36250 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36251 let registry = Arc::new(TimerDriverRegistry::default());
36252 let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
36253 registry: Arc::downgrade(®istry),
36254 later: Mutex::new(None),
36255 registers: AtomicUsize::new(0),
36256 });
36257 registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
36258
36259 let later_cancels = Arc::new(AtomicUsize::new(0));
36260 let later_drops = Arc::new(AtomicUsize::new(0));
36261 let later = Arc::new(PanickingCancelDropTimerDriver {
36262 cancels: Arc::clone(&later_cancels),
36263 drops: Arc::clone(&later_drops),
36264 });
36265 let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
36266 *earlier.later.lock().unwrap() = Some(later_id);
36267 drop(later);
36268
36269 let mut sleep = make_sleep(
36270 &backend,
36271 ®istry,
36272 Instant::now() + Duration::from_secs(60),
36273 );
36274 let mut cx = Context::from_waker(Waker::noop());
36275 let result = catch_unwind(AssertUnwindSafe(|| {
36276 let _ = Pin::new(&mut sleep).poll(&mut cx);
36277 }));
36278
36279 assert!(
36280 result.is_err(),
36281 "the earlier driver's register panic must still propagate"
36282 );
36283 assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
36284 assert_eq!(
36285 later_drops.load(Ordering::SeqCst),
36286 0,
36287 "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
36288 );
36289
36290 drop(sleep);
36291 assert_eq!(
36292 later_cancels.load(Ordering::SeqCst),
36293 1,
36294 "the later driver must remain owned for cancellation despite its registry removal"
36295 );
36296 assert_eq!(
36297 later_drops.load(Ordering::SeqCst),
36298 1,
36299 "the later driver's final destructor must run once behind containment"
36300 );
36301 assert_eq!(registry.pending_len(), 0);
36302 assert!(!executor.host_timers.has_pending());
36303 return;
36304 }
36305
36306 let output = std::process::Command::new(std::env::current_exe().unwrap())
36307 .arg("--exact")
36308 .arg(
36309 "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
36310 )
36311 .arg("--nocapture")
36312 .env(CHILD_ENV, "1")
36313 .output()
36314 .expect("the hostile host timer snapshot subprocess test must start");
36315 assert!(
36316 output.status.success(),
36317 "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
36318 output.status.code(),
36319 String::from_utf8_lossy(&output.stdout),
36320 String::from_utf8_lossy(&output.stderr)
36321 );
36322 }
36323
36324 #[cfg(not(target_family = "wasm"))]
36325 #[test]
36326 fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
36327 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
36328
36329 if std::env::var_os(CHILD_ENV).is_some() {
36330 let metrics = Arc::new(RuntimeMetrics::default());
36331 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36332 let backend =
36333 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36334 let cancels = Arc::new(AtomicUsize::new(0));
36335 let drops = Arc::new(AtomicUsize::new(0));
36336 let hostile = Arc::new(PanickingCancelDropTimerDriver {
36337 cancels: Arc::clone(&cancels),
36338 drops: Arc::clone(&drops),
36339 });
36340 let healthy = ManualStubDriver::new();
36341 let registry = Arc::new(TimerDriverRegistry::default());
36342 let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
36343 registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
36344 let mut sleep = make_sleep(
36345 &backend,
36346 ®istry,
36347 Instant::now() + Duration::from_secs(60),
36348 );
36349 let mut cx = Context::from_waker(Waker::noop());
36350
36351 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36352 let timer_id = healthy.registers.lock().unwrap()[0].0;
36353
36354 registry.unregister(hostile_id);
36357 drop(hostile);
36358 drop(sleep);
36359 assert_eq!(cancels.load(Ordering::SeqCst), 1);
36360 assert_eq!(drops.load(Ordering::SeqCst), 1);
36361 assert_eq!(
36362 healthy.cancels.lock().unwrap().as_slice(),
36363 &[timer_id],
36364 "one hostile cancellation must not skip later armed drivers"
36365 );
36366 assert_eq!(registry.pending_len(), 0);
36367 assert!(!executor.host_timers.has_pending());
36368 return;
36369 }
36370
36371 let output = std::process::Command::new(std::env::current_exe().unwrap())
36372 .arg("--exact")
36373 .arg(
36374 "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
36375 )
36376 .arg("--nocapture")
36377 .env(CHILD_ENV, "1")
36378 .output()
36379 .expect("the hostile timer-driver cancellation subprocess test must start");
36380 assert!(
36381 output.status.success(),
36382 "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
36383 output.status.code(),
36384 String::from_utf8_lossy(&output.stdout),
36385 String::from_utf8_lossy(&output.stderr)
36386 );
36387 }
36388
36389 #[cfg(not(target_family = "wasm"))]
36390 #[test]
36391 fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
36392 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
36393
36394 if std::env::var_os(CHILD_ENV).is_some() {
36395 let metrics = Arc::new(RuntimeMetrics::default());
36396 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36397 let backend =
36398 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36399 let driver = ManualStubDriver::new();
36400 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36401 let state = Arc::new(TestHostileRawWakerState {
36402 clones: AtomicUsize::new(0),
36403 drop_panics: AtomicUsize::new(0),
36404 panic_on_clone: Some(2),
36405 });
36406
36407 let result = catch_unwind(AssertUnwindSafe(|| {
36408 let mut sleep = make_sleep(
36409 &backend,
36410 ®istry,
36411 Instant::now() + Duration::from_secs(60),
36412 );
36413 let waker = test_hostile_waker(Arc::clone(&state));
36414 let mut cx = Context::from_waker(&waker);
36415 let _ = Pin::new(&mut sleep).poll(&mut cx);
36416 }));
36417 assert!(
36418 result.is_err(),
36419 "the original RawWaker clone panic must still propagate"
36420 );
36421 assert_eq!(state.clones.load(Ordering::SeqCst), 2);
36422 assert_eq!(
36423 state.drop_panics.load(Ordering::SeqCst),
36424 1,
36425 "the first clone must be destroyed once behind a contained boundary"
36426 );
36427 assert!(driver.registers.lock().unwrap().is_empty());
36428 assert_eq!(registry.pending_len(), 0);
36429 assert!(!executor.host_timers.has_pending());
36430 return;
36431 }
36432
36433 let output = std::process::Command::new(std::env::current_exe().unwrap())
36434 .arg("--exact")
36435 .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
36436 .arg("--nocapture")
36437 .env(CHILD_ENV, "1")
36438 .output()
36439 .expect("the hostile RawWaker subprocess test must start");
36440 assert!(
36441 output.status.success(),
36442 "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
36443 output.status.code(),
36444 String::from_utf8_lossy(&output.stdout),
36445 String::from_utf8_lossy(&output.stderr)
36446 );
36447 }
36448
36449 #[cfg(not(target_family = "wasm"))]
36450 #[test]
36451 fn host_sleep_register_unwind_contains_unretained_waker_drop() {
36452 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
36453
36454 if std::env::var_os(CHILD_ENV).is_some() {
36455 let metrics = Arc::new(RuntimeMetrics::default());
36456 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36457 let backend =
36458 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36459 let driver = Arc::new(PanicBeforeRetainingTimerDriver {
36460 registers: AtomicUsize::new(0),
36461 });
36462 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36463 let state = Arc::new(TestHostileRawWakerState {
36464 clones: AtomicUsize::new(0),
36465 drop_panics: AtomicUsize::new(0),
36466 panic_on_clone: None,
36467 });
36468 let mut sleep = make_sleep(
36469 &backend,
36470 ®istry,
36471 Instant::now() + Duration::from_secs(60),
36472 );
36473 let waker = test_hostile_waker(Arc::clone(&state));
36474 let mut cx = Context::from_waker(&waker);
36475
36476 let result = catch_unwind(AssertUnwindSafe(|| {
36477 let _ = Pin::new(&mut sleep).poll(&mut cx);
36478 }));
36479 assert!(
36480 result.is_err(),
36481 "the timer-driver register panic must still propagate"
36482 );
36483 assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
36484 assert_eq!(state.clones.load(Ordering::SeqCst), 3);
36485 assert_eq!(
36486 state.drop_panics.load(Ordering::SeqCst),
36487 1,
36488 "the unretained register argument must be destroyed once behind the proxy boundary"
36489 );
36490 assert_eq!(registry.pending_len(), 1);
36491 assert!(executor.host_timers.has_pending());
36492
36493 drop(sleep);
36494 assert_eq!(
36495 state.drop_panics.load(Ordering::SeqCst),
36496 3,
36497 "repoll and registry wakers must retain their existing contained cleanup"
36498 );
36499 assert_eq!(registry.pending_len(), 0);
36500 assert!(!executor.host_timers.has_pending());
36501 return;
36502 }
36503
36504 let output = std::process::Command::new(std::env::current_exe().unwrap())
36505 .arg("--exact")
36506 .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
36507 .arg("--nocapture")
36508 .env(CHILD_ENV, "1")
36509 .output()
36510 .expect("the hostile register-waker subprocess test must start");
36511 assert!(
36512 output.status.success(),
36513 "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
36514 output.status.code(),
36515 String::from_utf8_lossy(&output.stdout),
36516 String::from_utf8_lossy(&output.stderr)
36517 );
36518 }
36519
36520 #[test]
36521 fn host_sleep_rearms_on_next_live_driver_after_eviction() {
36522 let metrics = Arc::new(RuntimeMetrics::default());
36528 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36529 let backend =
36530 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36531
36532 let a = ManualStubDriver::new();
36533 let b = ManualStubDriver::new();
36534 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36535 let deadline = Instant::now() + Duration::from_secs(60);
36536
36537 let (timer_id, b_id) = {
36538 let mut sleep = std::pin::pin!(make_sleep(&backend, ®istry, deadline));
36539 let mut cx = Context::from_waker(Waker::noop());
36540
36541 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36543 let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
36544 assert_eq!(armed_deadline, deadline);
36545 assert!(
36546 executor.host_timers.has_pending(),
36547 "first poll must arm the wake-token"
36548 );
36549
36550 a.kill();
36554 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36555 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36556 assert_eq!(
36557 b.registers.lock().unwrap().as_slice(),
36558 &[(timer_id, deadline)],
36559 "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
36560 );
36561 assert_eq!(
36562 a.cancels.lock().unwrap().as_slice(),
36563 &[timer_id],
36564 "the dead driver gets a best-effort cancel for the old arm"
36565 );
36566 assert_eq!(
36567 registry.current().map(|(id, _)| id),
36568 Some(b_id),
36569 "the dead driver was evicted"
36570 );
36571 assert!(
36572 executor.host_timers.has_pending(),
36573 "the wake-token survives the re-arm (still exactly this sleep's entry)"
36574 );
36575 (timer_id, b_id)
36576 };
36577
36578 let _ = b_id;
36581 assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
36582 assert!(
36583 !executor.host_timers.has_pending(),
36584 "drop must retire the wake-token"
36585 );
36586 }
36587
36588 #[cfg(napi_runtime_os_threads)]
36589 #[test]
36590 fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
36591 let metrics = Arc::new(RuntimeMetrics::default());
36592 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36593 let backend =
36594 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36595 let responsive = Arc::new(StubHostTimerDriver::new());
36596 let starved = ManualStubDriver::new();
36597 let registry = Arc::new(TimerDriverRegistry::default());
36598 registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
36599 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36602
36603 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
36604 let waker = Waker::from(Arc::clone(&wake_count));
36605 let mut cx = Context::from_waker(&waker);
36606 let deadline = Instant::now() + Duration::from_millis(500);
36611 let mut sleep = make_sleep(&backend, ®istry, deadline);
36612 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36613
36614 let responsive_timer = *responsive
36615 .timers
36616 .lock()
36617 .unwrap()
36618 .keys()
36619 .next()
36620 .expect("responsive timer must be armed");
36621 assert_eq!(
36622 starved.registers.lock().unwrap().as_slice(),
36623 &[(responsive_timer, deadline)],
36624 "every live host must receive the same timer identity and absolute deadline"
36625 );
36626
36627 let fire_bound = Instant::now() + Duration::from_secs(30);
36634 while wake_count.0.load(Ordering::SeqCst) == 0 {
36635 assert!(
36636 Instant::now() < fire_bound,
36637 "the responsive host must wake a sleep whose newest host is live but starved"
36638 );
36639 std::thread::sleep(Duration::from_millis(10));
36640 }
36641 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36644 assert_eq!(
36645 starved.cancels.lock().unwrap().as_slice(),
36646 &[responsive_timer],
36647 "completion through the winning host must cancel the starved losing arm"
36648 );
36649 }
36650
36651 #[cfg(napi_runtime_os_threads)]
36652 #[test]
36653 fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
36654 use std::sync::mpsc;
36655
36656 struct ReentrantTimerRegistrationWake {
36657 registry: Weak<TimerDriverRegistry>,
36658 wakes: AtomicUsize,
36659 reentered: AtomicBool,
36660 }
36661
36662 impl std::task::Wake for ReentrantTimerRegistrationWake {
36663 fn wake(self: Arc<Self>) {
36664 self.wakes.fetch_add(1, Ordering::SeqCst);
36665 if let Some(registry) = self.registry.upgrade() {
36666 let _ = registry.has_live_driver();
36667 self.reentered.store(true, Ordering::SeqCst);
36668 }
36669 }
36670 }
36671
36672 let metrics = Arc::new(RuntimeMetrics::default());
36673 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36674 let backend =
36675 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36676 let registry = Arc::new(TimerDriverRegistry::default());
36677 let starved = ManualStubDriver::new();
36678 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36679
36680 let deadline = Instant::now() + Duration::from_secs(2);
36687 let mut first = make_sleep(&backend, ®istry, deadline);
36688 let mut second = make_sleep(&backend, ®istry, deadline);
36689 let first_wake = Arc::new(ReentrantTimerRegistrationWake {
36690 registry: Arc::downgrade(®istry),
36691 wakes: AtomicUsize::new(0),
36692 reentered: AtomicBool::new(false),
36693 });
36694 let second_wake = Arc::new(ReentrantTimerRegistrationWake {
36695 registry: Arc::downgrade(®istry),
36696 wakes: AtomicUsize::new(0),
36697 reentered: AtomicBool::new(false),
36698 });
36699 let first_waker = Waker::from(Arc::clone(&first_wake));
36700 let second_waker = Waker::from(Arc::clone(&second_wake));
36701 let mut first_cx = Context::from_waker(&first_waker);
36702 let mut second_cx = Context::from_waker(&second_waker);
36703
36704 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36705 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36706 let initial = starved.registers.lock().unwrap().clone();
36707 let first_id = initial[0].0;
36708 let second_id = initial[1].0;
36709 assert_ne!(first_id, second_id);
36710 assert_eq!(
36711 registry.pending_len(),
36712 2,
36713 "one stable repoll entry is retained per sleep"
36714 );
36715
36716 let responsive = Arc::new(StubHostTimerDriver::new());
36717 let (registered_tx, registered_rx) = mpsc::sync_channel(1);
36718 let registration_registry = Arc::clone(®istry);
36719 let registration_driver = Arc::clone(&responsive);
36720 let registration = std::thread::spawn(move || {
36721 let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
36722 registered_tx.send(id).unwrap();
36723 });
36724 registered_rx
36728 .recv_timeout(Duration::from_secs(10))
36729 .expect("late driver registration or repoll callback re-entry deadlocked");
36730 registration.join().unwrap();
36731
36732 assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
36733 assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
36734 assert!(first_wake.reentered.load(Ordering::SeqCst));
36735 assert!(second_wake.reentered.load(Ordering::SeqCst));
36736 assert!(
36737 responsive.timers.lock().unwrap().is_empty(),
36738 "registration requests bounded re-polls instead of invoking driver callbacks inline"
36739 );
36740
36741 assert!(
36746 Instant::now() < deadline,
36747 "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
36748 this runner is starved beyond the margin this test budgets for"
36749 );
36750 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36751 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36752 let responsive_ids = responsive
36753 .timers
36754 .lock()
36755 .unwrap()
36756 .keys()
36757 .copied()
36758 .collect::<FxHashSet<_>>();
36759 assert_eq!(
36760 responsive_ids,
36761 FxHashSet::from_iter([first_id, second_id]),
36762 "every pending sleep must re-arm its existing identity on the new driver"
36763 );
36764 assert_eq!(
36765 starved
36766 .registers
36767 .lock()
36768 .unwrap()
36769 .iter()
36770 .filter(|(id, _)| *id == first_id)
36771 .count(),
36772 2,
36773 "the old host receives only the normal waker refresh on the one re-poll"
36774 );
36775 assert_eq!(
36776 starved
36777 .registers
36778 .lock()
36779 .unwrap()
36780 .iter()
36781 .filter(|(id, _)| *id == second_id)
36782 .count(),
36783 2,
36784 "the old host receives only the normal waker refresh on the one re-poll"
36785 );
36786 assert_eq!(
36787 registry.pending_len(),
36788 2,
36789 "late registration must not duplicate pending state"
36790 );
36791
36792 let fire_bound = Instant::now() + Duration::from_secs(30);
36799 while first_wake.wakes.load(Ordering::SeqCst) < 2
36800 || second_wake.wakes.load(Ordering::SeqCst) < 2
36801 {
36802 assert!(
36803 Instant::now() < fire_bound,
36804 "the late responsive host must fire both timers while the original host remains starved"
36805 );
36806 std::thread::sleep(Duration::from_millis(10));
36807 }
36808 assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
36809 assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
36810
36811 let mut cancelled = starved.cancels.lock().unwrap().clone();
36812 cancelled.sort_unstable();
36813 let mut expected = vec![first_id, second_id];
36814 expected.sort_unstable();
36815 assert_eq!(
36816 cancelled, expected,
36817 "completion must cancel both losing starved-host arms"
36818 );
36819 assert_eq!(
36820 registry.pending_len(),
36821 0,
36822 "completion retires every repoll entry"
36823 );
36824 assert!(!executor.host_timers.has_pending());
36825 }
36826
36827 #[cfg(panic = "unwind")]
36828 #[test]
36829 fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
36830 let metrics = Arc::new(RuntimeMetrics::default());
36834 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36835 let backend =
36836 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36837
36838 let a = ManualStubDriver::new();
36839 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36840 let mut sleep = std::pin::pin!(make_sleep(
36841 &backend,
36842 ®istry,
36843 Instant::now() + Duration::from_secs(60)
36844 ));
36845 let mut cx = Context::from_waker(Waker::noop());
36846 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36847
36848 a.kill();
36849 let payload = catch_unwind(AssertUnwindSafe(|| {
36850 let _ = sleep.as_mut().poll(&mut cx);
36851 }))
36852 .expect_err("a sleep with every driver dead must panic, not pend silently");
36853 let message = payload
36854 .downcast_ref::<String>()
36855 .cloned()
36856 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
36857 .expect("panic payload must be a message");
36858 assert!(
36859 message.contains("lost every live timer driver mid-sleep"),
36860 "the diagnostic must name the mid-flight driver loss, got: {message}"
36861 );
36862 }
36863
36864 #[test]
36865 fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
36866 let metrics = Arc::new(RuntimeMetrics::default());
36873 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36874 let backend =
36875 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36876
36877 let a = ManualStubDriver::new();
36878 let b = ManualStubDriver::new();
36879 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36880 let mut sleep = std::pin::pin!(make_sleep(
36881 &backend,
36882 ®istry,
36883 Instant::now() + Duration::from_secs(60)
36884 ));
36885
36886 let flag = Arc::new(WakeFlag::default());
36888 let waker = Waker::from(Arc::clone(&flag));
36889 let mut cx = Context::from_waker(&waker);
36890 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36891 assert!(!flag.0.load(Ordering::SeqCst));
36892
36893 a.kill();
36896 registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36897 let _ = registry.has_live_driver();
36898
36899 assert!(
36900 a.swept.load(Ordering::SeqCst),
36901 "the sweep must notify the swept driver"
36902 );
36903 assert!(
36904 flag.0.load(Ordering::SeqCst),
36905 "a sleep armed on the swept driver must be woken into re-selection, not stranded"
36906 );
36907
36908 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36910 assert_eq!(
36911 b.registers.lock().unwrap().len(),
36912 1,
36913 "the re-poll must re-arm on the live driver"
36914 );
36915 }
36916
36917 #[cfg(napi_runtime_os_threads)]
36918 #[test]
36919 fn current_thread_sleep_fires_through_stub_host_driver() {
36920 use std::sync::mpsc;
36923
36924 let (done_tx, done_rx) = mpsc::channel();
36925 let runner = std::thread::spawn(move || {
36926 let metrics = Arc::new(RuntimeMetrics::default());
36927 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36928 let backend =
36929 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36930 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36931
36932 let started = Instant::now();
36933 let mut future = std::pin::pin!(async {
36934 make_sleep(
36935 &backend,
36936 ®istry_with(driver),
36937 Instant::now() + Duration::from_millis(60),
36938 )
36939 .await;
36940 });
36941 executor.block_on(future.as_mut());
36942 assert!(started.elapsed() >= Duration::from_millis(60));
36943 assert!(
36944 !executor.host_timers.has_pending(),
36945 "a completed sleep must retire its pending-registry entry"
36946 );
36947 let _ = done_tx.send(());
36948 });
36949
36950 match done_rx.recv_timeout(Duration::from_secs(10)) {
36951 Ok(()) => runner.join().unwrap(),
36952 Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
36953 }
36954 }
36955
36956 #[test]
36957 fn current_thread_timer_shutdown_retires_pending_repoll_state() {
36958 let metrics = Arc::new(RuntimeMetrics::default());
36959 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36960 let backend =
36961 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36962 let drivers = Arc::new(TimerDriverRegistry::default());
36963 let driver = ManualStubDriver::new();
36964 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36965 let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
36966 let mut cx = Context::from_waker(Waker::noop());
36967
36968 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36969 let timer_id = driver.registers.lock().unwrap()[0].0;
36970 assert_eq!(drivers.pending_len(), 1);
36971
36972 executor.host_timers.shutdown();
36973 assert_eq!(
36974 drivers.pending_len(),
36975 0,
36976 "shutdown must retire the registration-repoll index before the Sleep is dropped"
36977 );
36978 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36979 assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
36980 }
36981
36982 #[cfg(napi_runtime_os_threads)]
36983 #[test]
36984 fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
36985 use std::sync::mpsc;
36986
36987 let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
36988 let backend = controller.backend();
36989 let drivers = Arc::new(TimerDriverRegistry::default());
36990 let driver = ManualStubDriver::new();
36991 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36992 let sleep = make_sleep(
36993 &backend,
36994 &drivers,
36995 Instant::now() + Duration::from_secs(120),
36996 );
36997 drop(backend);
36998
36999 let (polled_tx, polled_rx) = mpsc::channel();
37000 let block_controller = Arc::clone(&controller);
37001 let blocker = std::thread::spawn(move || {
37002 let mut announced = false;
37003 let mut sleep = std::pin::pin!(sleep);
37004 let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
37005 let poll = sleep.as_mut().poll(cx);
37006 if !announced {
37007 announced = true;
37008 polled_tx.send(()).unwrap();
37009 }
37010 poll
37011 }));
37012 block_controller.block_on(future.as_mut());
37013 });
37014 polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
37015
37016 let (shutdown_tx, shutdown_rx) = mpsc::channel();
37017 let shutdown_controller = Arc::clone(&controller);
37018 let shutdown = std::thread::spawn(move || {
37019 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
37020 });
37021 shutdown_rx
37022 .recv_timeout(Duration::from_secs(2))
37023 .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
37024 .unwrap();
37025 shutdown.join().unwrap();
37026 blocker.join().unwrap();
37027 assert_eq!(driver.cancels.lock().unwrap().len(), 1);
37028 }
37029
37030 #[cfg(panic = "unwind")]
37031 #[test]
37032 fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
37033 use std::sync::mpsc;
37044
37045 let (done_tx, done_rx) = mpsc::channel();
37046 let runner = std::thread::spawn(move || {
37047 let metrics = Arc::new(RuntimeMetrics::default());
37048 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
37052 let backend =
37053 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
37054 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
37055
37056 let payload = catch_unwind(AssertUnwindSafe(|| {
37057 let mut future = std::pin::pin!(async {
37058 make_sleep(
37059 &backend,
37060 ®istry_with(driver),
37061 Instant::now() + Duration::from_millis(60),
37062 )
37063 .await;
37064 });
37065 executor.block_on(future.as_mut());
37066 }))
37067 .expect_err("a threadless park must self-detect even while a host timer is pending");
37068 let diagnostic = payload
37069 .downcast_ref::<BlockOnDeadlock>()
37070 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
37071 assert_eq!(
37072 diagnostic.kind,
37073 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
37074 );
37075 assert_eq!(
37076 diagnostic.park_deadline, None,
37077 "the certain case carries no deadline"
37078 );
37079 let message = diagnostic.to_string();
37080 assert!(
37081 message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
37082 "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
37083 );
37084 let _ = done_tx.send(());
37085 });
37086
37087 match done_rx.recv_timeout(Duration::from_secs(10)) {
37088 Ok(()) => runner.join().unwrap(),
37089 Err(error) => panic!(
37090 "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})"
37091 ),
37092 }
37093 }
37094
37095 #[cfg(panic = "unwind")]
37096 #[test]
37097 fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
37098 use std::sync::mpsc;
37107
37108 let (done_tx, done_rx) = mpsc::channel();
37109 let runner = std::thread::spawn(move || {
37110 let metrics = Arc::new(RuntimeMetrics::default());
37111 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
37112 let backend =
37113 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
37114 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
37115
37116 let _unpolled = make_sleep(
37119 &backend,
37120 ®istry_with(driver),
37121 Instant::now() + Duration::from_secs(60),
37122 );
37123
37124 let payload = catch_unwind(AssertUnwindSafe(|| {
37125 let mut future = std::pin::pin!(NeverReady);
37126 executor.block_on(future.as_mut());
37127 }))
37128 .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
37129 let diagnostic = payload
37130 .downcast_ref::<BlockOnDeadlock>()
37131 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
37132 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
37133 let _ = done_tx.send(());
37134 });
37135
37136 match done_rx.recv_timeout(Duration::from_secs(10)) {
37137 Ok(()) => runner.join().unwrap(),
37138 Err(error) => panic!(
37139 "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
37140 ),
37141 }
37142 }
37143
37144 #[cfg(napi_runtime_os_threads)]
37145 #[test]
37146 fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
37147 use std::sync::mpsc;
37152
37153 let (done_tx, done_rx) = mpsc::channel();
37154 let runner = std::thread::spawn(move || {
37155 let metrics = Arc::new(RuntimeMetrics::default());
37156 let executor = Arc::new(CurrentThreadExecutor::with_detection(
37168 metrics,
37169 false,
37170 Some(Duration::from_secs(2)),
37171 ));
37172 let backend =
37173 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
37174 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
37175
37176 let started = Instant::now();
37177 let mut future = std::pin::pin!(async {
37178 make_sleep(
37179 &backend,
37180 ®istry_with(driver),
37181 Instant::now() + Duration::from_millis(200),
37182 )
37183 .await;
37184 });
37185 executor.block_on(future.as_mut());
37186 assert!(started.elapsed() >= Duration::from_millis(200));
37187 let _ = done_tx.send(());
37188 });
37189
37190 match done_rx.recv_timeout(Duration::from_secs(30)) {
37191 Ok(()) => runner.join().unwrap(),
37192 Err(error) => {
37193 panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
37194 }
37195 }
37196 }
37197
37198 #[test]
37199 fn host_timer_unrepresentable_grace_remains_live() {
37200 let registry = HostTimerRegistry::default();
37201 let fired = Arc::new(AtomicBool::new(false));
37202 let drivers = Arc::new(TimerDriverRegistry::default());
37203 let repoll = Arc::new(HostTimerRepoll::default());
37204 assert!(
37205 Instant::now().checked_add(Duration::MAX).is_none(),
37206 "Duration::MAX must exercise the unrepresentable-grace path"
37207 );
37208 assert!(
37209 registry.register(
37210 1,
37211 Instant::now(),
37212 futures::task::noop_waker(),
37213 &fired,
37214 &drivers,
37215 &repoll,
37216 ),
37217 "the open registry must accept the timer"
37218 );
37219
37220 assert!(
37221 registry.has_live_wait(Duration::MAX),
37222 "an unrepresentable grace deadline is effectively unbounded"
37223 );
37224 registry.remove(1);
37225 }
37226
37227 #[cfg(napi_runtime_os_threads)]
37230 #[test]
37231 fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
37232 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
37237 let expected = diagnostic.to_string();
37238 let payload: Box<dyn Any + Send> = Box::new(diagnostic);
37239 assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
37240 }
37241 #[cfg(napi_runtime_wasi_threads)]
37249 #[test]
37250 fn wasi_threads_reports_a_thread_capable_worker_ceiling() {
37251 let ceiling = crate::max_async_runtime_worker_threads();
37255 assert_eq!(
37256 ceiling, 255,
37257 "the threaded WASI target must report Rayon's realizable ceiling"
37258 );
37259 assert!(
37260 ceiling >= 2,
37261 "a ceiling below two would make MultiThread unconfigurable"
37262 );
37263 }
37264
37265 #[cfg(napi_runtime_wasi_threads)]
37266 #[test]
37267 fn wasi_threads_default_flavor_is_still_current_thread() {
37268 assert_eq!(
37272 RuntimeOptions::default().flavor,
37273 RuntimeFlavor::CurrentThread,
37274 "the threaded WASI target must not silently default to MultiThread"
37275 );
37276 }
37277
37278 #[cfg(napi_runtime_wasi_threads)]
37279 #[test]
37280 fn wasi_threads_multi_thread_validates_to_the_two_worker_minimum() {
37281 let validated = RuntimeOptions {
37287 flavor: RuntimeFlavor::MultiThread,
37288 ..RuntimeOptions::default()
37289 }
37290 .validate()
37291 .expect("MultiThread must validate on the threaded WASI target");
37292 assert_eq!(validated.worker_threads, 2);
37293 assert_eq!(validated.max_blocking_tasks, 1);
37294 }
37295
37296 #[cfg(napi_runtime_wasi_threads)]
37297 #[test]
37298 fn threadless_build_is_false_on_the_threads_target() {
37299 assert!(
37304 !THREADLESS_BUILD,
37305 "the threaded WASI target has real OS threads"
37306 );
37307 assert!(
37308 cfg!(napi_runtime_os_threads),
37309 "the threaded WASI target must build the OS-thread machinery"
37310 );
37311 }
37312
37313 #[cfg(napi_runtime_wasi_threads)]
37325 fn grow_one_page_elsewhere() -> usize {
37326 std::thread::spawn(|| {
37327 let old = core::arch::wasm32::memory_grow::<0>(1);
37328 assert_ne!(old, usize::MAX, "the WASI host must let the memory grow");
37329 napi_sys::wasi_heap_sync::grow_zero()
37330 })
37331 .join()
37332 .expect("the growing thread must finish")
37333 }
37334
37335 #[cfg(napi_runtime_wasi_threads)]
37336 #[test]
37337 fn wasi_threads_multi_thread_task_polls_refresh_the_memory_size() {
37338 let controller = multi_thread_controller("wasi-handoff-task", 2, 1);
37341 let pages = grow_one_page_elsewhere();
37342 let refreshes = napi_sys::wasi_heap_sync::handoff_refreshes();
37343 let seen = futures::executor::block_on(
37344 controller.spawn(async { napi_sys::wasi_heap_sync::local_pages() }),
37345 )
37346 .expect("the task must finish");
37347 assert!(
37348 seen >= pages,
37349 "the worker polled with a stale memory size: {seen} < {pages} pages"
37350 );
37351 assert!(
37352 napi_sys::wasi_heap_sync::handoff_refreshes() > refreshes,
37353 "the handoff refresh must be counted"
37354 );
37355 controller.shutdown().expect("runtime must stop");
37356 }
37357
37358 #[cfg(napi_runtime_wasi_threads)]
37359 #[test]
37360 fn wasi_threads_current_thread_task_polls_refresh_the_memory_size() {
37361 use std::sync::mpsc;
37364
37365 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37366 RuntimeMetrics::default(),
37367 )));
37368 let (seen_tx, seen_rx) = mpsc::channel();
37369 let wake_executor = Arc::clone(&executor);
37370 let (runnable, task) = async_task::spawn(
37371 async move {
37372 let _ = seen_tx.send(napi_sys::wasi_heap_sync::local_pages());
37373 },
37374 move |runnable| wake_executor.schedule(runnable),
37375 );
37376 executor.schedule(runnable);
37377 let pages = grow_one_page_elsewhere();
37378 executor.drive_host_turn();
37379 let seen = seen_rx
37380 .try_recv()
37381 .expect("one host turn must poll the task");
37382 futures::executor::block_on(task);
37383 assert!(
37384 seen >= pages,
37385 "the host turn polled with a stale memory size: {seen} < {pages} pages"
37386 );
37387 }
37388
37389 #[cfg(napi_runtime_wasi_threads)]
37390 #[test]
37391 fn wasi_threads_block_on_polls_refresh_the_memory_size() {
37392 for controller in [
37396 current_thread_controller("wasi-handoff-block-on"),
37397 multi_thread_controller("wasi-handoff-parking-block-on", 2, 1),
37398 ] {
37399 let pages = grow_one_page_elsewhere();
37400 let mut seen = 0;
37401 {
37402 let mut future = std::pin::pin!(async {
37403 seen = napi_sys::wasi_heap_sync::local_pages();
37404 });
37405 assert_eq!(
37406 controller.block_on(future.as_mut()),
37407 BlockOnOutcome::Completed
37408 );
37409 }
37410 assert!(
37411 seen >= pages,
37412 "block_on polled with a stale memory size: {seen} < {pages} pages"
37413 );
37414 controller.shutdown().expect("runtime must stop");
37415 }
37416 }
37417
37418 #[cfg(napi_runtime_wasi_threads)]
37419 #[test]
37420 fn wasi_threads_blocking_closures_refresh_the_memory_size() {
37421 let controller = Arc::new(multi_thread_controller("wasi-handoff-blocking", 2, 1));
37425 let pages = grow_one_page_elsewhere();
37426 let block_on_controller = Arc::clone(&controller);
37427 let (start_seen, block_on_pages, block_on_seen) =
37428 futures::executor::block_on(controller.spawn_blocking(move || {
37429 let start_seen = napi_sys::wasi_heap_sync::local_pages();
37430 let block_on_pages = grow_one_page_elsewhere();
37431 let mut block_on_seen = 0;
37432 {
37433 let mut future = std::pin::pin!(async {
37434 block_on_seen = napi_sys::wasi_heap_sync::local_pages();
37435 });
37436 assert_eq!(
37437 block_on_controller.block_on(future.as_mut()),
37438 BlockOnOutcome::Completed
37439 );
37440 }
37441 (start_seen, block_on_pages, block_on_seen)
37442 }))
37443 .expect("the blocking closure must finish");
37444 assert!(
37445 start_seen >= pages,
37446 "the blocking closure started with a stale memory size: {start_seen} < {pages} pages"
37447 );
37448 assert!(
37449 block_on_seen >= block_on_pages,
37450 "the cooperative block_on polled with a stale memory size: \
37451 {block_on_seen} < {block_on_pages} pages"
37452 );
37453 controller.shutdown().expect("runtime must stop");
37454 }
37455
37456 #[cfg(napi_runtime_wasi_threads)]
37457 #[test]
37458 fn wasi_threads_inline_blocking_refreshes_the_memory_size() {
37459 let controller = current_thread_controller("wasi-handoff-inline-blocking");
37462 let pages = grow_one_page_elsewhere();
37463 let seen =
37464 futures::executor::block_on(controller.spawn_blocking(napi_sys::wasi_heap_sync::local_pages))
37465 .expect("the inline blocking closure must finish");
37466 assert!(
37467 seen >= pages,
37468 "the inline blocking closure ran with a stale memory size: {seen} < {pages} pages"
37469 );
37470 controller.shutdown().expect("runtime must stop");
37471 }
37472
37473 #[cfg(not(napi_runtime_os_threads))]
37477 #[test]
37478 fn threadless_wasm_still_rejects_multi_thread() {
37479 let error = RuntimeOptions {
37480 flavor: RuntimeFlavor::MultiThread,
37481 ..RuntimeOptions::default()
37482 }
37483 .validate()
37484 .expect_err("threadless WebAssembly cannot run the multi-thread runtime");
37485 let message = error.to_string();
37486 assert!(
37487 message.contains("threadless"),
37488 "the rejection must name the property that causes it, not the family: {message}"
37489 );
37490 assert!(
37491 message.contains("wasm32-wasip1-threads"),
37492 "the rejection must name the target that does work: {message}"
37493 );
37494 }
37495
37496 #[cfg(napi_runtime_os_threads)]
37497 #[test]
37498 fn multi_thread_pool_starts_and_joins_every_worker() {
37499 let options = RuntimeOptions {
37503 flavor: RuntimeFlavor::MultiThread,
37504 worker_threads: 3,
37505 max_blocking_tasks: 2,
37506 thread_name_prefix: "rd-os-threads-join".to_string(),
37507 park_deadline: None,
37508 drain_linger: DEFAULT_DRAIN_LINGER,
37509 };
37510 let executor = Arc::new(
37511 MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default()))
37512 .expect("a three-worker pool must build"),
37513 );
37514 assert_eq!(executor.pool.current_num_threads(), 3);
37515
37516 let ran = Arc::new(AtomicBool::new(false));
37517 let task_ran = Arc::clone(&ran);
37518 let (runnable, task) = async_task::spawn(
37519 async move {
37520 task_ran.store(true, Ordering::SeqCst);
37521 },
37522 |_| {},
37523 );
37524 executor.schedule(runnable);
37525 futures::executor::block_on(task);
37526 assert!(
37527 ran.load(Ordering::SeqCst),
37528 "the pool must run scheduled work"
37529 );
37530
37531 let lifecycle = Arc::clone(&executor.worker_lifecycle);
37532 executor.begin_shutdown();
37533 executor.wait_until_scheduler_idle();
37534 let deadline = Instant::now() + Duration::from_secs(10);
37540 while Arc::strong_count(&executor) > 1 && Instant::now() < deadline {
37541 std::thread::yield_now();
37542 }
37543 let executor = Arc::into_inner(executor)
37544 .expect("no drainer may outlive `wait_until_scheduler_idle` for long");
37545 drop(executor);
37546 lifecycle.wait_for_all_workers();
37547 assert_eq!(
37548 lifecycle.remaining(),
37549 0,
37550 "every physical worker must have exited before the join returns"
37551 );
37552 }
37553
37554 #[cfg(napi_runtime_os_threads)]
37555 #[test]
37556 fn current_thread_queues_concurrent_blocking_admission() {
37557 use std::sync::mpsc;
37564
37565 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37566 RuntimeMetrics::default(),
37567 )));
37568 let work = GenerationWork::new();
37569 let (outcome_tx, outcome_rx) = mpsc::channel();
37570
37571 let owner_registration = work
37572 .try_register_work()
37573 .expect("the lane owner's blocking job must be accepted");
37574 let concurrent_work = Arc::clone(&work);
37575 let concurrent_executor = Arc::clone(&executor);
37576 let task_executor = Arc::clone(&executor);
37577 let wake_executor = Arc::clone(&executor);
37578 let (runnable, task) = async_task::spawn(
37579 async move {
37580 let concurrent = task_executor
37581 .schedule_blocking(
37582 move || {
37583 std::thread::spawn(move || {
37584 let registration = concurrent_work
37585 .try_register_work()
37586 .expect("the concurrent blocking job must be accepted");
37587 concurrent_executor.schedule_blocking(|| 7usize, registration)
37588 })
37589 .join()
37590 .expect("the concurrent submitter thread must finish")
37591 },
37592 owner_registration,
37593 )
37594 .await
37595 .expect("the lane owner's blocking job must finish");
37596 let _ = outcome_tx.send(concurrent.await);
37597 },
37598 move |runnable| wake_executor.schedule(runnable),
37599 );
37600
37601 executor.schedule(runnable);
37602 let mut turns = 0usize;
37603 let outcome = loop {
37604 executor.drive_host_turn();
37605 if let Ok(outcome) = outcome_rx.try_recv() {
37606 break outcome;
37607 }
37608 turns += 1;
37609 assert!(
37610 turns <= 8,
37611 "queued blocking admission must make bounded host-turn progress"
37612 );
37613 };
37614 futures::executor::block_on(task);
37615 assert_eq!(
37616 outcome.expect("a concurrent blocking submission must be queued, not rejected"),
37617 7
37618 );
37619 }
37620}