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, 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))]
916fn run_dependency_claim_test_hook() {
917 if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
918 hook();
919 }
920}
921
922#[cfg(all(test, napi_runtime_os_threads))]
923fn run_current_thread_dispatch_claim_test_hook() {
924 if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
925 {
926 hook();
927 }
928}
929
930#[cfg(all(test, napi_runtime_os_threads))]
931fn run_current_thread_dispatch_publication_blocked_test_hook() {
932 if let Some(hook) =
933 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
934 {
935 hook();
936 }
937}
938
939#[cfg(all(test, napi_runtime_os_threads))]
940fn run_current_thread_dispatch_recovery_test_hook() {
941 if let Some(hook) =
942 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
943 {
944 hook();
945 }
946}
947
948#[cfg(all(test, napi_runtime_os_threads))]
949fn run_current_thread_failed_publication_retired_test_hook() {
950 if let Some(hook) =
951 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
952 {
953 hook();
954 }
955}
956
957#[cfg(all(test, napi_runtime_os_threads))]
958fn run_current_thread_dispatch_call_test_hook() {
959 if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
960 hook();
961 }
962}
963
964#[cfg(all(test, napi_runtime_os_threads))]
965fn run_current_thread_dispatch_returned_test_hook() {
966 if let Some(hook) =
967 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
968 {
969 hook();
970 }
971}
972
973#[cfg(all(test, napi_runtime_os_threads))]
974fn run_current_thread_blocking_claim_test_hook() {
975 if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
976 {
977 hook();
978 }
979}
980
981#[cfg(all(test, napi_runtime_os_threads))]
982fn run_current_thread_runnable_claim_test_hook() {
983 if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
984 {
985 hook();
986 }
987}
988
989#[cfg(all(test, napi_runtime_os_threads))]
990fn run_multi_thread_work_claimed_test_hook() {
991 if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
992 hook();
993 }
994}
995
996#[cfg(all(test, napi_runtime_os_threads))]
997fn run_multi_thread_work_started_test_hook() {
998 if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
999 hook();
1000 }
1001}
1002
1003#[cfg(all(test, napi_runtime_os_threads))]
1004fn run_dependency_predicate_test_hook() {
1005 if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1006 hook();
1007 }
1008}
1009
1010#[cfg(all(test, napi_runtime_os_threads))]
1011fn run_before_owned_blocking_queue_claim_test_hook() {
1012 if let Some(hook) =
1013 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1014 {
1015 hook();
1016 }
1017}
1018
1019#[cfg(napi_runtime_os_threads)]
1020#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1021struct BlockingDependency {
1022 job: BlockingJobId,
1023 owner: Option<BlockingOwnerToken>,
1024}
1025
1026#[cfg(napi_runtime_os_threads)]
1027#[derive(Debug)]
1028struct DependencyClaim {
1029 claimed: AtomicBool,
1030 transition: Mutex<()>,
1031}
1032
1033#[cfg(napi_runtime_os_threads)]
1034impl DependencyClaim {
1035 fn new() -> Self {
1036 Self {
1037 claimed: AtomicBool::new(false),
1038 transition: Mutex::new(()),
1039 }
1040 }
1041
1042 fn is_live(&self) -> bool {
1043 !self.claimed.load(Ordering::Acquire)
1044 }
1045
1046 fn cancel_link(&self, link: &DependencyLink) {
1047 let _transition = self
1048 .transition
1049 .lock()
1050 .unwrap_or_else(std::sync::PoisonError::into_inner);
1051 link.cancel();
1052 }
1053
1054 fn try_claim(&self, link: &DependencyLink) -> bool {
1055 let _transition = self
1056 .transition
1057 .lock()
1058 .unwrap_or_else(std::sync::PoisonError::into_inner);
1059 #[cfg(test)]
1060 run_dependency_claim_test_hook();
1061 if self.claimed.load(Ordering::Acquire) || !link.is_live() {
1062 return false;
1063 }
1064 self.claimed.store(true, Ordering::Release);
1065 true
1066 }
1067}
1068
1069#[cfg(napi_runtime_os_threads)]
1070#[derive(Debug)]
1071struct DependencyLink {
1072 live: AtomicBool,
1073 parent: Option<Weak<DependencyLink>>,
1074}
1075
1076#[cfg(napi_runtime_os_threads)]
1077impl DependencyLink {
1078 fn root() -> Arc<Self> {
1079 Arc::new(Self {
1080 live: AtomicBool::new(true),
1081 parent: None,
1082 })
1083 }
1084
1085 fn derived(parent: &Arc<Self>) -> Arc<Self> {
1086 Arc::new(Self {
1087 live: AtomicBool::new(true),
1088 parent: Some(Arc::downgrade(parent)),
1089 })
1090 }
1091
1092 fn is_live(&self) -> bool {
1093 if !self.live.load(Ordering::Acquire) {
1094 return false;
1095 }
1096 let Some(parent) = &self.parent else {
1097 return true;
1098 };
1099 let Some(mut current) = parent.upgrade() else {
1100 return false;
1101 };
1102 loop {
1103 if !current.live.load(Ordering::Acquire) {
1104 return false;
1105 }
1106 let Some(parent) = ¤t.parent else {
1107 return true;
1108 };
1109 let Some(next) = parent.upgrade() else {
1110 return false;
1111 };
1112 current = next;
1113 }
1114 }
1115
1116 fn cancel(&self) {
1117 self.live.store(false, Ordering::Release);
1118 }
1119}
1120
1121#[cfg(napi_runtime_os_threads)]
1122#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1123struct DependencyPublicationIdentity {
1124 job: BlockingJobId,
1125 claim: *const DependencyClaim,
1126 link: *const DependencyLink,
1127}
1128
1129#[cfg(napi_runtime_os_threads)]
1130#[derive(Clone, Debug)]
1131struct DependencyPublication {
1132 dependency: BlockingDependency,
1133 claim: Arc<DependencyClaim>,
1134 link: Arc<DependencyLink>,
1135}
1136
1137#[cfg(napi_runtime_os_threads)]
1138impl DependencyPublication {
1139 fn owned(dependency: BlockingDependency) -> Self {
1140 Self {
1141 dependency,
1142 claim: Arc::new(DependencyClaim::new()),
1143 link: DependencyLink::root(),
1144 }
1145 }
1146
1147 fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1148 Self {
1149 dependency: BlockingDependency {
1150 owner: self.dependency.owner.or(owner),
1151 ..self.dependency
1152 },
1153 claim: Arc::clone(&self.claim),
1154 link: DependencyLink::derived(&self.link),
1155 }
1156 }
1157
1158 fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1159 self.dependency.owner = self.dependency.owner.or(owner);
1160 self
1161 }
1162
1163 fn same_as(&self, other: &Self) -> bool {
1164 self.dependency == other.dependency
1165 && Arc::ptr_eq(&self.claim, &other.claim)
1166 && Arc::ptr_eq(&self.link, &other.link)
1167 }
1168
1169 fn identity(&self) -> DependencyPublicationIdentity {
1170 DependencyPublicationIdentity {
1171 job: self.dependency.job,
1172 claim: Arc::as_ptr(&self.claim),
1173 link: Arc::as_ptr(&self.link),
1174 }
1175 }
1176
1177 fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1178 Some(DependencyPublicationIdentity {
1179 job: self.dependency.job,
1180 claim: Arc::as_ptr(&self.claim),
1181 link: self.link.parent.as_ref()?.as_ptr(),
1182 })
1183 }
1184
1185 fn is_derived_from(&self, source: &Self) -> bool {
1186 self.immediate_source_identity() == Some(source.identity())
1187 }
1188
1189 fn is_live(&self) -> bool {
1190 self.claim.is_live() && self.link.is_live()
1191 }
1192
1193 fn cancel(&self) {
1194 self.claim.cancel_link(&self.link);
1195 }
1196
1197 fn try_claim(&self) -> bool {
1198 self.claim.try_claim(&self.link)
1199 }
1200}
1201
1202#[cfg(napi_runtime_os_threads)]
1203struct TaskDependencyEntry {
1204 sequence: u64,
1205 publication: DependencyPublication,
1206}
1207
1208#[cfg(napi_runtime_os_threads)]
1209struct SelectedDependencyPublication {
1210 sequence: u64,
1211 publication: DependencyPublication,
1212}
1213
1214#[cfg(napi_runtime_os_threads)]
1215#[derive(Clone, Copy)]
1216enum OwnerDependencyMode {
1217 Available,
1218 Exact,
1219}
1220
1221#[cfg(napi_runtime_os_threads)]
1222#[derive(Default)]
1223struct TaskDependencyState {
1224 current: VecDeque<TaskDependencyEntry>,
1225 next_sequence: u64,
1226 unowned_sequences: VecDeque<u64>,
1227 owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1228 waiter: Option<Waker>,
1238}
1239
1240#[cfg(napi_runtime_os_threads)]
1241impl TaskDependencyState {
1242 fn append(&mut self, publication: DependencyPublication) -> u64 {
1245 let sequence = self.next_sequence;
1246 self.next_sequence = self
1247 .next_sequence
1248 .checked_add(1)
1249 .expect("task dependency sequence space exhausted");
1250 match publication.dependency.owner {
1251 Some(owner) => self
1252 .owner_sequences
1253 .entry(owner)
1254 .or_default()
1255 .push_back(sequence),
1256 None => self.unowned_sequences.push_back(sequence),
1257 }
1258 self.current.push_back(TaskDependencyEntry {
1259 sequence,
1260 publication,
1261 });
1262 sequence
1263 }
1264
1265 fn entry_in(
1266 current: &VecDeque<TaskDependencyEntry>,
1267 sequence: u64,
1268 ) -> Option<&TaskDependencyEntry> {
1269 let front = current.front()?.sequence;
1270 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1271 let entry = current.get(index)?;
1272 (entry.sequence == sequence).then_some(entry)
1273 }
1274
1275 fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1276 Self::entry_in(&self.current, sequence)
1277 }
1278
1279 fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1280 let front = self.current.front()?.sequence;
1281 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1282 let entry = self.current.get_mut(index)?;
1283 (entry.sequence == sequence).then_some(entry)
1284 }
1285
1286 fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1287 match dependency.owner {
1288 Some(owner) => {
1289 let remove_owner = self
1290 .owner_sequences
1291 .get_mut(&owner)
1292 .is_some_and(|sequences| {
1293 if sequences.front() == Some(&sequence) {
1294 sequences.pop_front();
1295 }
1296 sequences.is_empty()
1297 });
1298 if remove_owner {
1299 self.owner_sequences.remove(&owner);
1300 }
1301 }
1302 None => {
1303 if self.unowned_sequences.front() == Some(&sequence) {
1304 self.unowned_sequences.pop_front();
1305 }
1306 }
1307 }
1308 }
1309
1310 fn clear_indexes(&mut self) {
1311 self.unowned_sequences.clear();
1312 self.owner_sequences.clear();
1313 }
1314}
1315
1316#[cfg(napi_runtime_os_threads)]
1317struct TaskDependency {
1318 state: Mutex<TaskDependencyState>,
1319 owner_hint: Option<BlockingOwnerToken>,
1320 has_current: AtomicBool,
1321 generation: u64,
1322 #[cfg(test)]
1323 entry_visits: AtomicUsize,
1324}
1325
1326#[cfg(napi_runtime_os_threads)]
1327impl Default for TaskDependency {
1328 fn default() -> Self {
1329 Self::new(u64::MAX, None)
1330 }
1331}
1332
1333#[cfg(napi_runtime_os_threads)]
1334impl TaskDependency {
1335 fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1336 Self {
1337 state: Mutex::new(TaskDependencyState::default()),
1338 owner_hint,
1339 has_current: AtomicBool::new(false),
1340 generation,
1341 #[cfg(test)]
1342 entry_visits: AtomicUsize::new(0),
1343 }
1344 }
1345
1346 fn new_task(generation: u64) -> Self {
1347 Self::new(generation, None)
1348 }
1349
1350 fn notify_waiter(&self, waiter: Option<Waker>) {
1351 if let Some(waiter) = waiter {
1352 let _generation = RuntimeGenerationGuard::enter(self.generation);
1353 wake_waker_contained(waiter);
1354 }
1355 }
1356
1357 fn clear_waiter(&self) {
1358 let waiter = self
1359 .state
1360 .lock()
1361 .unwrap_or_else(std::sync::PoisonError::into_inner)
1362 .waiter
1363 .take();
1364 if let Some(waiter) = waiter {
1365 let _generation = RuntimeGenerationGuard::enter(self.generation);
1366 let _ = catch_unwind_contained(|| drop(waiter));
1367 }
1368 }
1369
1370 fn clear(&self) {
1371 let waiter = {
1372 let mut state = self
1373 .state
1374 .lock()
1375 .unwrap_or_else(std::sync::PoisonError::into_inner);
1376 if state.current.is_empty() {
1377 None
1378 } else {
1379 for current in state.current.drain(..) {
1380 current.publication.cancel();
1381 }
1382 state.clear_indexes();
1383 self.has_current.store(false, Ordering::Release);
1384 state.waiter.take()
1385 }
1386 };
1387 self.notify_waiter(waiter);
1388 }
1389
1390 fn retire(&self, sequence: u64) {
1400 let waiter = {
1401 let mut state = self
1402 .state
1403 .lock()
1404 .unwrap_or_else(std::sync::PoisonError::into_inner);
1405 let cancelled = match state.entry_mut(sequence) {
1406 Some(current) => {
1407 self.note_entry_visit();
1408 current.publication.cancel();
1409 true
1410 }
1411 None => false,
1412 };
1413 if cancelled {
1414 self.prune_stale_prefix(&mut state);
1415 state.waiter.take()
1416 } else {
1417 None
1418 }
1419 };
1420 self.notify_waiter(waiter);
1421 }
1422
1423 fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1424 let Some(first_publication) = publications.first() else {
1425 return;
1426 };
1427 let publication_identities = (publications.len() > 1).then(|| {
1428 publications
1429 .iter()
1430 .map(DependencyPublication::identity)
1431 .collect::<FxHashSet<_>>()
1432 });
1433 let waiter = {
1434 let mut state = self
1435 .state
1436 .lock()
1437 .unwrap_or_else(std::sync::PoisonError::into_inner);
1438 let removed = state
1439 .current
1440 .iter_mut()
1441 .filter_map(|current| {
1442 self.note_entry_visit();
1443 let is_derived = publication_identities.as_ref().map_or_else(
1444 || current.publication.is_derived_from(first_publication),
1445 |identities| {
1446 current
1447 .publication
1448 .immediate_source_identity()
1449 .is_some_and(|identity| identities.contains(&identity))
1450 },
1451 );
1452 if !is_derived {
1453 return None;
1454 }
1455 current.publication.cancel();
1456 Some(current.sequence)
1457 })
1458 .collect::<Vec<_>>();
1459 if removed.is_empty() {
1462 None
1463 } else {
1464 self.prune_stale_prefix(&mut state);
1465 state.waiter.take()
1466 }
1467 };
1468 self.notify_waiter(waiter);
1469 }
1470
1471 fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1474 let publication = DependencyPublication::owned(BlockingDependency {
1475 owner: dependency.owner.or(self.owner_hint),
1476 ..dependency
1477 });
1478 self.set_entry(publication)
1479 }
1480
1481 fn holds_live(&self, sequence: u64) -> bool {
1487 let state = self
1488 .state
1489 .lock()
1490 .unwrap_or_else(std::sync::PoisonError::into_inner);
1491 TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1492 self.note_entry_visit();
1493 entry.publication.is_live()
1494 })
1495 }
1496
1497 fn set_borrowed(&self, publication: &DependencyPublication) {
1498 let _sequence = self.set_entry(publication.derived(self.owner_hint));
1499 }
1500
1501 fn set_entry(&self, publication: DependencyPublication) -> u64 {
1502 let (sequence, waiter) = {
1503 let mut state = self
1504 .state
1505 .lock()
1506 .unwrap_or_else(std::sync::PoisonError::into_inner);
1507 let sequence = state.append(publication);
1510 self.has_current.store(true, Ordering::Release);
1511 (sequence, state.waiter.take())
1512 };
1513 self.notify_waiter(waiter);
1514 sequence
1515 }
1516
1517 fn note_entry_visit(&self) {
1518 #[cfg(test)]
1519 self.entry_visits.fetch_add(1, Ordering::Relaxed);
1520 }
1521
1522 fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1523 while state.current.front().is_some_and(|current| {
1524 self.note_entry_visit();
1525 !current.publication.is_live()
1526 }) {
1527 let current = state
1528 .current
1529 .pop_front()
1530 .expect("the stale prefix entry must exist");
1531 state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1532 }
1533 self
1534 .has_current
1535 .store(!state.current.is_empty(), Ordering::Release);
1536 }
1537
1538 fn first_live_sequence(
1539 &self,
1540 current: &VecDeque<TaskDependencyEntry>,
1541 sequences: &mut VecDeque<u64>,
1542 ) -> Option<u64> {
1543 loop {
1544 let sequence = *sequences.front()?;
1545 let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1546 sequences.pop_front();
1547 continue;
1548 };
1549 self.note_entry_visit();
1550 if entry.publication.is_live() {
1551 return Some(sequence);
1552 }
1553 sequences.pop_front();
1554 }
1555 }
1556
1557 fn first_live_owner_sequence(
1558 &self,
1559 state: &mut TaskDependencyState,
1560 owner: BlockingOwnerToken,
1561 ) -> Option<u64> {
1562 let sequence = {
1563 let current = &state.current;
1564 state
1565 .owner_sequences
1566 .get_mut(&owner)
1567 .and_then(|sequences| self.first_live_sequence(current, sequences))
1568 };
1569 if sequence.is_none() {
1570 state.owner_sequences.remove(&owner);
1571 }
1572 sequence
1573 }
1574
1575 fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1576 self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1577 }
1578
1579 #[cfg(test)]
1580 fn get(&self) -> Option<DependencyPublication> {
1581 self
1582 .state
1583 .lock()
1584 .unwrap_or_else(std::sync::PoisonError::into_inner)
1585 .current
1586 .iter()
1587 .find(|current| current.publication.is_live())
1588 .map(|current| current.publication.clone())
1589 }
1590
1591 fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1592 self
1593 .state
1594 .lock()
1595 .unwrap_or_else(std::sync::PoisonError::into_inner)
1596 .current
1597 .iter()
1598 .filter(|current| {
1599 self.note_entry_visit();
1600 current.publication.is_live()
1601 })
1602 .map(|current| current.publication.clone())
1603 .collect()
1604 }
1605
1606 fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1607 let mut state = self
1608 .state
1609 .lock()
1610 .unwrap_or_else(std::sync::PoisonError::into_inner);
1611 self.prune_stale_prefix(&mut state);
1612 let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1613 let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1614 let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1615 (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1616 (Some(_) | None, Some(unowned)) => (unowned, true),
1617 (Some(exact), None) => (exact, false),
1618 (None, None) => return None,
1619 };
1620 if bind_owner {
1621 let removed = state.unowned_sequences.pop_front();
1622 debug_assert_eq!(removed, Some(sequence));
1623 let current = state
1624 .entry_mut(sequence)
1625 .expect("the selected dependency must remain indexed");
1626 debug_assert_eq!(current.publication.dependency.owner, None);
1627 current.publication.dependency.owner = Some(owner);
1628 state
1632 .owner_sequences
1633 .entry(owner)
1634 .or_default()
1635 .push_front(sequence);
1636 }
1637 let publication = state
1638 .entry(sequence)
1639 .expect("the selected dependency must remain indexed")
1640 .publication
1641 .clone();
1642 Some(SelectedDependencyPublication {
1643 sequence,
1644 publication,
1645 })
1646 }
1647
1648 fn has_live_owner_dependency(
1649 &self,
1650 owner: BlockingOwnerToken,
1651 mode: OwnerDependencyMode,
1652 ) -> bool {
1653 #[cfg(all(test, napi_runtime_os_threads))]
1654 run_dependency_predicate_test_hook();
1655 let mut state = self
1656 .state
1657 .lock()
1658 .unwrap_or_else(std::sync::PoisonError::into_inner);
1659 self.prune_stale_prefix(&mut state);
1660 if self.first_live_owner_sequence(&mut state, owner).is_some() {
1661 return true;
1662 }
1663 matches!(mode, OwnerDependencyMode::Available)
1664 && self.first_live_unowned_sequence(&mut state).is_some()
1665 }
1666
1667 #[cfg(test)]
1668 fn get_dependency(&self) -> Option<BlockingDependency> {
1669 self.get().map(|publication| publication.dependency)
1670 }
1671
1672 #[cfg(test)]
1673 fn get_dependencies(&self) -> Vec<BlockingDependency> {
1674 self
1675 .snapshot_all_live()
1676 .into_iter()
1677 .map(|publication| publication.dependency)
1678 .collect()
1679 }
1680
1681 #[cfg(test)]
1682 fn reset_entry_visits(&self) {
1683 self.entry_visits.store(0, Ordering::Relaxed);
1684 }
1685
1686 #[cfg(test)]
1687 fn entry_visits(&self) -> usize {
1688 self.entry_visits.load(Ordering::Relaxed)
1689 }
1690
1691 fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1695 let mut state = self
1696 .state
1697 .lock()
1698 .unwrap_or_else(std::sync::PoisonError::into_inner);
1699 let Some(current) = state.entry(selected.sequence) else {
1700 return false;
1701 };
1702 self.note_entry_visit();
1703 if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1704 return false;
1705 }
1706 let dependency = current.publication.dependency;
1707 state
1708 .entry_mut(selected.sequence)
1709 .expect("the claimed dependency must remain indexed")
1710 .publication
1711 .cancel();
1712 state.remove_sequence_indexes(selected.sequence, dependency);
1713 self.prune_stale_prefix(&mut state);
1714 true
1715 }
1716
1717 fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1718 let _generation = RuntimeGenerationGuard::enter(self.generation);
1719 let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1720 return Vec::new();
1721 };
1722 let (previous, unused, current) = {
1723 let mut state = self
1724 .state
1725 .lock()
1726 .unwrap_or_else(std::sync::PoisonError::into_inner);
1727 let current = state
1728 .current
1729 .iter()
1730 .filter(|current| current.publication.is_live())
1731 .map(|current| current.publication.clone())
1732 .collect();
1733 if state
1734 .waiter
1735 .as_ref()
1736 .is_some_and(|previous| previous.will_wake(&waker))
1737 {
1738 (None, Some(waker), current)
1739 } else {
1740 (state.waiter.replace(waker), None, current)
1741 }
1742 };
1743 if let Some(previous) = previous {
1744 let _ = catch_unwind_contained(|| drop(previous));
1745 }
1746 if let Some(unused) = unused {
1747 let _ = catch_unwind_contained(|| drop(unused));
1748 }
1749 current
1750 }
1751
1752 fn begin_poll(&self) {
1753 if !self.has_current.load(Ordering::Acquire) {
1754 return;
1755 }
1756 let waiter = {
1757 let mut state = self
1758 .state
1759 .lock()
1760 .unwrap_or_else(std::sync::PoisonError::into_inner);
1761 if state.current.is_empty() {
1762 self.has_current.store(false, Ordering::Release);
1763 None
1764 } else {
1765 for previous in state.current.drain(..) {
1766 previous.publication.cancel();
1767 }
1768 state.clear_indexes();
1769 self.has_current.store(false, Ordering::Release);
1770 state.waiter.take()
1771 }
1772 };
1773 self.notify_waiter(waiter);
1774 }
1775
1776 #[cfg(test)]
1777 fn register_waiter(&self, waker: &Waker) {
1778 let _ = self.register_waiter_and_get(waker);
1779 }
1780}
1781
1782#[cfg(napi_runtime_os_threads)]
1783impl Drop for TaskDependency {
1784 fn drop(&mut self) {
1785 let state = self
1786 .state
1787 .get_mut()
1788 .unwrap_or_else(std::sync::PoisonError::into_inner);
1789 for current in state.current.drain(..) {
1790 current.publication.cancel();
1791 }
1792 if let Some(waiter) = state.waiter.take() {
1793 let _generation = RuntimeGenerationGuard::enter(self.generation);
1794 let _ = catch_unwind_contained(|| drop(waiter));
1795 }
1796 }
1797}
1798
1799#[cfg(napi_runtime_os_threads)]
1800thread_local! {
1801 static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1802 const { std::cell::RefCell::new(Vec::new()) };
1803}
1804
1805#[cfg(napi_runtime_os_threads)]
1806struct TaskDependencyGuard(Arc<TaskDependency>);
1807
1808#[cfg(napi_runtime_os_threads)]
1809impl TaskDependencyGuard {
1810 fn enter(dependency: &Arc<TaskDependency>) -> Self {
1811 let dependency = Arc::clone(dependency);
1812 CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1813 Self(dependency)
1814 }
1815}
1816
1817#[cfg(napi_runtime_os_threads)]
1818impl Drop for TaskDependencyGuard {
1819 fn drop(&mut self) {
1820 CURRENT_DEPENDENCY_STACK.with(|stack| {
1821 let current = stack
1822 .borrow_mut()
1823 .pop()
1824 .expect("blocking dependency context underflow");
1825 debug_assert!(Arc::ptr_eq(¤t, &self.0));
1826 });
1827 }
1828}
1829
1830#[cfg(napi_runtime_os_threads)]
1831struct BlockOnDependencyGuard {
1832 dependency: Arc<TaskDependency>,
1833 _context: TaskDependencyGuard,
1834}
1835
1836#[cfg(napi_runtime_os_threads)]
1837impl BlockOnDependencyGuard {
1838 fn enter(generation: u64) -> Self {
1839 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1840 let dependency = Arc::new(TaskDependency::new(generation, owner));
1841 let context = TaskDependencyGuard::enter(&dependency);
1842 Self {
1843 dependency,
1844 _context: context,
1845 }
1846 }
1847
1848 fn clear(&self) {
1849 self.dependency.clear();
1850 }
1851}
1852
1853#[cfg(napi_runtime_os_threads)]
1868struct PublishedBlockingDependency {
1869 state: Weak<TaskDependency>,
1870 sequence: u64,
1871}
1872
1873#[cfg(napi_runtime_os_threads)]
1892#[derive(Default)]
1893struct PublishedBlockingDependencies {
1894 first: Option<PublishedBlockingDependency>,
1895 rest: Vec<PublishedBlockingDependency>,
1896}
1897
1898#[cfg(napi_runtime_os_threads)]
1899impl PublishedBlockingDependency {
1900 fn still_stands(&self) -> bool {
1915 self
1916 .state
1917 .upgrade()
1918 .is_some_and(|state| state.holds_live(self.sequence))
1919 }
1920}
1921
1922#[cfg(napi_runtime_os_threads)]
1923impl PublishedBlockingDependencies {
1924 fn prune(&mut self) {
1949 if self.rest.is_empty() {
1950 if self
1951 .first
1952 .as_ref()
1953 .is_some_and(|first| first.state.strong_count() == 0)
1954 {
1955 self.first = None;
1956 }
1957 return;
1958 }
1959 self.rest.retain(PublishedBlockingDependency::still_stands);
1960 if self
1961 .first
1962 .as_ref()
1963 .is_some_and(|first| !first.still_stands())
1964 {
1965 self.first = self.rest.pop();
1966 }
1967 }
1968
1969 fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1970 self.prune();
1971 let target = Arc::as_ptr(&state);
1972 if let Some(existing) = self
1973 .first
1974 .iter_mut()
1975 .chain(self.rest.iter_mut())
1976 .find(|published| std::ptr::eq(published.state.as_ptr(), target))
1977 {
1978 if state.holds_live(existing.sequence) {
1989 return;
1990 }
1991 existing.sequence = state.set_owned(dependency);
1995 return;
1996 }
1997 let sequence = state.set_owned(dependency);
1998 let published = PublishedBlockingDependency {
1999 state: Arc::downgrade(&state),
2000 sequence,
2001 };
2002 match &mut self.first {
2003 slot @ None => *slot = Some(published),
2004 Some(_) => self.rest.push(published),
2005 }
2006 }
2007
2008 fn retire_all(self) {
2018 for published in self.first.into_iter().chain(self.rest) {
2019 if let Some(state) = published.state.upgrade() {
2020 state.retire(published.sequence);
2021 }
2022 }
2023 }
2024}
2025
2026#[cfg(napi_runtime_os_threads)]
2027fn record_blocking_dependency(
2028 published: &mut PublishedBlockingDependencies,
2029 dependency: BlockingDependency,
2030) {
2031 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2032 if let Some(current) = current {
2033 let dependency = BlockingDependency {
2034 owner: dependency
2035 .owner
2036 .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
2037 ..dependency
2038 };
2039 published.publish(current, dependency);
2040 }
2041}
2042
2043#[cfg(napi_runtime_os_threads)]
2044fn propagate_blocking_dependency(publication: DependencyPublication) {
2045 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2046 if let Some(current) = current {
2047 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
2048 let publication = publication.with_owner_if_none(owner);
2049 current.set_borrowed(&publication);
2050 }
2051}
2052
2053#[cfg(napi_runtime_os_threads)]
2054fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
2055 published.retire_all();
2059}
2060
2061#[cfg(napi_runtime_os_threads)]
2062fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
2063 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2064 if let Some(current) = current {
2065 current.clear_if_publications(publications);
2066 }
2067}
2068
2069enum JoinHandleInner<T> {
2070 Task {
2071 task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2072 awaiter: Option<PanicContainedWakerCache>,
2073 #[cfg(napi_runtime_os_threads)]
2074 dependency: Arc<TaskDependency>,
2075 },
2076 #[cfg(napi_runtime_os_threads)]
2077 Blocking {
2078 receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2079 dependency: BlockingDependency,
2080 published: PublishedBlockingDependencies,
2083 },
2084 Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2085}
2086
2087pub struct JoinHandle<T>(JoinHandleInner<T>);
2088
2089impl<T> Unpin for JoinHandle<T> {}
2090
2091impl<T> JoinHandle<T> {
2092 fn detach_task(&mut self) {
2093 let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2094 match inner {
2095 JoinHandleInner::Task {
2096 task,
2097 awaiter: _,
2098 #[cfg(napi_runtime_os_threads)]
2099 dependency,
2100 } => {
2101 #[cfg(napi_runtime_os_threads)]
2102 let current_dependencies = dependency.snapshot_all_live();
2103 #[cfg(napi_runtime_os_threads)]
2104 dependency.clear_waiter();
2105 task.detach();
2106 #[cfg(napi_runtime_os_threads)]
2107 let _ = catch_unwind_contained(|| {
2111 clear_propagated_blocking_dependencies(¤t_dependencies);
2112 });
2113 }
2114 #[cfg(napi_runtime_os_threads)]
2115 JoinHandleInner::Blocking {
2116 receiver,
2117 dependency: _,
2118 published,
2119 } => {
2120 let _ = catch_unwind_contained(|| drop(receiver));
2124 let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2127 }
2128 JoinHandleInner::Ready(result) => {
2129 let _ = catch_unwind_contained(|| drop(result));
2130 }
2131 }
2132 }
2133
2134 pub fn detach(mut self) {
2135 self.detach_task();
2136 }
2137}
2138
2139impl<T> Drop for JoinHandle<T> {
2140 fn drop(&mut self) {
2141 self.detach_task();
2145 }
2146}
2147
2148impl<T> Future for JoinHandle<T> {
2149 type Output = Result<T, JoinError>;
2150
2151 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2152 match &mut self.get_mut().0 {
2153 JoinHandleInner::Task {
2154 task,
2155 awaiter: awaiter_slot,
2156 #[cfg(napi_runtime_os_threads)]
2157 dependency,
2158 } => {
2159 let awaiter = match awaiter_slot {
2160 Some(awaiter) => {
2161 awaiter.update(cx.waker());
2162 awaiter
2163 }
2164 None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2165 };
2166 let contained_waker = awaiter.contained();
2167 let mut contained_cx = Context::from_waker(contained_waker);
2168 let poll = Pin::new(task).poll(&mut contained_cx);
2169 #[cfg(napi_runtime_os_threads)]
2170 if poll.is_pending() {
2171 for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2172 propagate_blocking_dependency(current_dependency);
2173 }
2174 } else {
2175 dependency.clear_waiter();
2176 }
2177 if poll.is_ready() {
2178 *awaiter_slot = None;
2179 }
2180 match poll {
2181 Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2182 Poll::Ready(None) => Poll::Ready(Err(JoinError {
2183 message: "async runtime stopped before the task completed".to_string(),
2184 })),
2185 Poll::Pending => Poll::Pending,
2186 }
2187 }
2188 #[cfg(napi_runtime_os_threads)]
2189 JoinHandleInner::Blocking {
2190 receiver,
2191 dependency,
2192 published,
2193 } => {
2194 let poll = Pin::new(receiver).poll(cx);
2195 if poll.is_pending() {
2196 record_blocking_dependency(published, *dependency);
2197 }
2198 match poll {
2199 Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2200 Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2201 message: "async runtime stopped before the blocking task completed".to_string(),
2202 })),
2203 Poll::Pending => Poll::Pending,
2204 }
2205 }
2206 JoinHandleInner::Ready(result) => Poll::Ready(
2207 result
2208 .take()
2209 .expect("JoinHandle polled after completion")
2210 .map(ContainedTaskOutput::into_inner),
2211 ),
2212 }
2213 }
2214}
2215
2216#[derive(Debug, Clone, Copy)]
2217pub struct RuntimeMetricsSnapshot {
2218 pub flavor: RuntimeFlavor,
2219 pub worker_threads: usize,
2220 pub max_blocking_tasks: usize,
2221 pub tasks_spawned: u64,
2222 pub tasks_completed: u64,
2223 pub tasks_panicked: u64,
2224 pub runnable_schedules: u64,
2225 pub runnable_polls: u64,
2226 pub queued_runnables: u64,
2227 pub max_queued_runnables: u64,
2228 pub active_runnables: u64,
2229 pub max_active_runnables: u64,
2230 pub blocking_tasks_started: u64,
2231 pub blocking_tasks_completed: u64,
2232 pub active_blocking_tasks: u64,
2233 pub max_active_blocking_tasks: u64,
2234}
2235
2236#[derive(Default)]
2237struct RuntimeMetrics {
2238 tasks_spawned: AtomicU64,
2239 tasks_completed: AtomicU64,
2240 tasks_panicked: AtomicU64,
2241 runnable_schedules: AtomicU64,
2242 runnable_polls: AtomicU64,
2243 queued_runnables: AtomicU64,
2244 max_queued_runnables: AtomicU64,
2245 active_runnables: AtomicU64,
2246 max_active_runnables: AtomicU64,
2247 blocking_tasks_scheduled: AtomicU64,
2253 blocking_tasks_started: AtomicU64,
2254 blocking_tasks_completed: AtomicU64,
2255 active_blocking_tasks: AtomicU64,
2256 max_active_blocking_tasks: AtomicU64,
2257 reset_generation: AtomicU64,
2261 reset_lock: Mutex<()>,
2262}
2263
2264#[cfg(test)]
2265thread_local! {
2266 static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2267 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2268 const { std::cell::RefCell::new(None) };
2269}
2270
2271#[cfg(test)]
2272fn run_runtime_metrics_high_water_test_hook() {
2273 if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2274 hook();
2275 }
2276}
2277
2278impl RuntimeMetrics {
2279 fn runnable_scheduled(&self) {
2280 self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2281 let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2282 #[cfg(test)]
2283 run_runtime_metrics_high_water_test_hook();
2284 self
2285 .max_queued_runnables
2286 .fetch_max(queued, Ordering::Relaxed);
2287 }
2288
2289 fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2290 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2291 self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2292 let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2293 #[cfg(test)]
2294 run_runtime_metrics_high_water_test_hook();
2295 self
2296 .max_active_runnables
2297 .fetch_max(active, Ordering::Relaxed);
2298 ActiveRunnableGuard { metrics: self }
2299 }
2300
2301 #[cfg(any(napi_runtime_os_threads, test))]
2302 fn runnable_cancelled(&self) {
2303 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2304 }
2305
2306 fn blocking_scheduled(&self) {
2310 self
2311 .blocking_tasks_scheduled
2312 .fetch_add(1, Ordering::Relaxed);
2313 }
2314
2315 fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2316 self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2317 if count_active_lane {
2318 let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2319 #[cfg(test)]
2320 run_runtime_metrics_high_water_test_hook();
2321 self
2322 .max_active_blocking_tasks
2323 .fetch_max(active, Ordering::Relaxed);
2324 }
2325 BlockingTaskGuard {
2326 metrics: Arc::clone(self),
2327 count_active_lane,
2328 }
2329 }
2330
2331 fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2332 let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2333 let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2334 let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2335 RuntimeMetricsSnapshot {
2336 flavor: options.flavor,
2337 worker_threads: options.worker_threads,
2338 max_blocking_tasks: options.max_blocking_tasks,
2339 tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2340 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2341 tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2342 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2343 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2344 queued_runnables,
2345 max_queued_runnables: self
2346 .max_queued_runnables
2347 .load(Ordering::Relaxed)
2348 .max(queued_runnables),
2349 active_runnables,
2350 max_active_runnables: self
2351 .max_active_runnables
2352 .load(Ordering::Relaxed)
2353 .max(active_runnables),
2354 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2355 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2356 active_blocking_tasks,
2357 max_active_blocking_tasks: self
2358 .max_active_blocking_tasks
2359 .load(Ordering::Relaxed)
2360 .max(active_blocking_tasks),
2361 }
2362 }
2363
2364 fn progress_fingerprint(&self) -> ProgressFingerprint {
2373 loop {
2374 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2375 if !reset_generation.is_multiple_of(2) {
2376 std::hint::spin_loop();
2377 continue;
2378 }
2379 let fingerprint = ProgressFingerprint {
2380 reset_generation,
2381 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2382 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2383 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2384 blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2385 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2386 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2387 };
2388 if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2389 return fingerprint;
2390 }
2391 }
2392 }
2393
2394 fn reset(&self) {
2395 let reset_guard = self
2396 .reset_lock
2397 .lock()
2398 .unwrap_or_else(std::sync::PoisonError::into_inner);
2399 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2407 let Some(resetting_generation) = reset_generation.checked_add(1) else {
2408 drop(reset_guard);
2409 panic!("runtime metrics reset generation space exhausted");
2410 };
2411 let Some(settled_generation) = resetting_generation.checked_add(1) else {
2412 drop(reset_guard);
2413 panic!("runtime metrics reset generation space exhausted");
2414 };
2415 self
2416 .reset_generation
2417 .store(resetting_generation, Ordering::SeqCst);
2418 self.tasks_spawned.store(0, Ordering::Relaxed);
2419 self.tasks_completed.store(0, Ordering::Relaxed);
2420 self.tasks_panicked.store(0, Ordering::Relaxed);
2421 self.runnable_schedules.store(0, Ordering::Relaxed);
2422 self.runnable_polls.store(0, Ordering::Relaxed);
2423 self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2424 self.blocking_tasks_started.store(0, Ordering::Relaxed);
2425 self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2426 self
2427 .reset_generation
2428 .store(settled_generation, Ordering::SeqCst);
2429 }
2430}
2431
2432#[derive(Clone, Copy, PartialEq, Eq)]
2434struct ProgressFingerprint {
2435 reset_generation: u64,
2436 runnable_schedules: u64,
2437 runnable_polls: u64,
2438 tasks_completed: u64,
2439 blocking_tasks_scheduled: u64,
2440 blocking_tasks_started: u64,
2441 blocking_tasks_completed: u64,
2442}
2443
2444struct ActiveRunnableGuard<'a> {
2445 metrics: &'a RuntimeMetrics,
2446}
2447
2448impl Drop for ActiveRunnableGuard<'_> {
2449 fn drop(&mut self) {
2450 self
2451 .metrics
2452 .active_runnables
2453 .fetch_sub(1, Ordering::Relaxed);
2454 }
2455}
2456
2457struct BlockingTaskGuard {
2458 metrics: Arc<RuntimeMetrics>,
2459 count_active_lane: bool,
2460}
2461
2462impl Drop for BlockingTaskGuard {
2463 fn drop(&mut self) {
2464 if self.count_active_lane {
2465 self
2466 .metrics
2467 .active_blocking_tasks
2468 .fetch_sub(1, Ordering::Relaxed);
2469 }
2470 self
2471 .metrics
2472 .blocking_tasks_completed
2473 .fetch_add(1, Ordering::Relaxed);
2474 }
2475}
2476
2477static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2478
2479fn next_runtime_generation() -> u64 {
2480 next_unique_id(
2481 &NEXT_RUNTIME_GENERATION,
2482 "async runtime generation id space exhausted",
2483 )
2484}
2485
2486thread_local! {
2487 static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2488 const { std::cell::Cell::new(None) };
2489}
2490
2491fn active_runtime_generation() -> Option<u64> {
2492 ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2493}
2494
2495struct RuntimeGenerationGuard {
2496 previous: Option<u64>,
2497}
2498
2499impl RuntimeGenerationGuard {
2500 fn enter(generation: u64) -> Self {
2501 Self::enter_optional(Some(generation))
2502 }
2503
2504 fn enter_optional(generation: Option<u64>) -> Self {
2505 Self {
2506 previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2507 }
2508 }
2509}
2510
2511impl Drop for RuntimeGenerationGuard {
2512 fn drop(&mut self) {
2513 ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2514 }
2515}
2516
2517struct ContainedFuture<F> {
2518 future: ManuallyDrop<F>,
2519 generation: u64,
2520}
2521
2522impl<F> ContainedFuture<F> {
2523 fn new(future: F, generation: u64) -> Self {
2524 Self {
2525 future: ManuallyDrop::new(future),
2526 generation,
2527 }
2528 }
2529}
2530
2531impl<F: Future> Future for ContainedFuture<F> {
2532 type Output = F::Output;
2533
2534 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2535 let this = unsafe { self.get_unchecked_mut() };
2537 let _generation = RuntimeGenerationGuard::enter(this.generation);
2538 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2541 }
2542}
2543
2544impl<F> Drop for ContainedFuture<F> {
2545 fn drop(&mut self) {
2546 let _generation = RuntimeGenerationGuard::enter(self.generation);
2547 let _ = catch_unwind_contained(|| {
2548 unsafe { ManuallyDrop::drop(&mut self.future) };
2550 });
2551 }
2552}
2553
2554#[derive(Default)]
2555struct GenerationStop {
2556 stopping: AtomicBool,
2557 publication: Mutex<()>,
2558 parkers: Mutex<Vec<Weak<DriverParker>>>,
2559}
2560
2561impl GenerationStop {
2562 fn is_stopping(&self) -> bool {
2563 self.stopping.load(Ordering::Acquire)
2564 }
2565
2566 fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2567 #[cfg(all(test, napi_runtime_os_threads))]
2568 run_before_generation_stop_publication_lock_test_hook();
2569 self.runnable_claim_guard()
2570 }
2571
2572 fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2573 GenerationStopPublicationGuard {
2574 stop: self,
2575 _publication: self
2576 .publication
2577 .lock()
2578 .unwrap_or_else(std::sync::PoisonError::into_inner),
2579 }
2580 }
2581
2582 fn publish_stopping(&self) {
2583 self.publication_guard().publish_stopping();
2584 }
2585
2586 fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2587 {
2588 let mut parkers = self
2589 .parkers
2590 .lock()
2591 .unwrap_or_else(std::sync::PoisonError::into_inner);
2592 parkers.retain(|candidate| candidate.strong_count() != 0);
2593 parkers.push(Arc::downgrade(parker));
2594 }
2595 if self.is_stopping() {
2596 parker.unpark();
2597 }
2598 GenerationStopGuard {
2599 stop: Arc::clone(self),
2600 parker: Arc::downgrade(parker),
2601 }
2602 }
2603
2604 fn wake_all(&self) -> usize {
2609 let mut count = 0;
2610 let mut registered = self
2611 .parkers
2612 .lock()
2613 .unwrap_or_else(std::sync::PoisonError::into_inner);
2614 registered.retain(|candidate| {
2615 let Some(parker) = candidate.upgrade() else {
2616 return false;
2617 };
2618 count += 1;
2619 parker.unpark();
2622 true
2623 });
2624 count
2625 }
2626
2627 fn begin_shutdown(&self) {
2628 self.publish_stopping();
2629 let parkers = {
2630 let mut registered = self
2631 .parkers
2632 .lock()
2633 .unwrap_or_else(std::sync::PoisonError::into_inner);
2634 let parkers = registered
2635 .iter()
2636 .filter_map(Weak::upgrade)
2637 .collect::<Vec<_>>();
2638 registered.clear();
2639 parkers
2640 };
2641 for parker in parkers {
2642 parker.unpark();
2643 }
2644 }
2645}
2646
2647struct GenerationStopPublicationGuard<'a> {
2648 stop: &'a GenerationStop,
2649 _publication: MutexGuard<'a, ()>,
2650}
2651
2652impl GenerationStopPublicationGuard<'_> {
2653 fn is_stopping(&self) -> bool {
2654 self.stop.is_stopping()
2655 }
2656
2657 fn publish_stopping(&self) {
2658 self.stop.stopping.store(true, Ordering::Release);
2659 }
2660}
2661
2662struct GenerationStopGuard {
2663 stop: Arc<GenerationStop>,
2664 parker: Weak<DriverParker>,
2665}
2666
2667impl Drop for GenerationStopGuard {
2668 fn drop(&mut self) {
2669 let Some(parker) = self.parker.upgrade() else {
2670 return;
2671 };
2672 let mut registered = self
2673 .stop
2674 .parkers
2675 .lock()
2676 .unwrap_or_else(std::sync::PoisonError::into_inner);
2677 if let Some(index) = registered.iter().position(|candidate| {
2678 candidate
2679 .upgrade()
2680 .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2681 }) {
2682 registered.swap_remove(index);
2683 }
2684 }
2685}
2686
2687#[derive(Clone, Copy)]
2689enum PendingRead {
2690 Locked,
2693 NonBlocking,
2698}
2699
2700impl PendingRead {
2701 fn read<T>(self, mutex: &Mutex<T>, pending: impl FnOnce(&T) -> bool) -> bool {
2705 let state = match self {
2706 Self::Locked => mutex.lock(),
2707 Self::NonBlocking => match mutex.try_lock() {
2708 Ok(state) => Ok(state),
2709 Err(std::sync::TryLockError::Poisoned(poisoned)) => Err(poisoned),
2710 Err(std::sync::TryLockError::WouldBlock) => return true,
2711 },
2712 };
2713 pending(&state.unwrap_or_else(std::sync::PoisonError::into_inner))
2714 }
2715}
2716
2717struct GenerationWorkState {
2718 closed: bool,
2719 next_task_id: u64,
2720 active: usize,
2721 abort_handles: FxHashMap<u64, AbortHandle>,
2722}
2723
2724struct GenerationWork {
2725 id: u64,
2726 state: Mutex<GenerationWorkState>,
2727 idle: Condvar,
2728}
2729
2730impl GenerationWork {
2731 fn new() -> Arc<Self> {
2732 Arc::new(Self {
2733 id: next_runtime_generation(),
2734 state: Mutex::new(GenerationWorkState {
2735 closed: false,
2736 next_task_id: 0,
2737 active: 0,
2738 abort_handles: FxHashMap::default(),
2739 }),
2740 idle: Condvar::new(),
2741 })
2742 }
2743
2744 fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2745 let (abort_handle, abort_registration) = AbortHandle::new_pair();
2746 let mut state = self
2747 .state
2748 .lock()
2749 .unwrap_or_else(std::sync::PoisonError::into_inner);
2750 if state.closed {
2751 return None;
2752 }
2753 let task_id = state.next_task_id;
2754 let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2755 drop(state);
2756 panic!("async runtime generation task id space exhausted");
2757 };
2758 state.next_task_id = next_task_id;
2759 state.active += 1;
2760 state.abort_handles.insert(task_id, abort_handle);
2761 Some((
2762 abort_registration,
2763 GenerationWorkGuard {
2764 work: Arc::clone(self),
2765 task_id: Some(task_id),
2766 },
2767 ))
2768 }
2769
2770 fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2771 let mut state = self
2772 .state
2773 .lock()
2774 .unwrap_or_else(std::sync::PoisonError::into_inner);
2775 if state.closed {
2776 return None;
2777 }
2778 state.active += 1;
2779 Some(GenerationWorkGuard {
2780 work: Arc::clone(self),
2781 task_id: None,
2782 })
2783 }
2784
2785 fn close_and_abort(&self) {
2786 let abort_handles = {
2791 let mut state = self
2792 .state
2793 .lock()
2794 .unwrap_or_else(std::sync::PoisonError::into_inner);
2795 state.closed = true;
2796 std::mem::take(&mut state.abort_handles)
2797 };
2798 for abort_handle in abort_handles.values() {
2799 abort_handle.abort();
2800 }
2801 }
2802
2803 fn wait_until_idle(&self) {
2804 self.wait_until_idle_retiring(|| false, || {});
2805 }
2806
2807 fn has_active_work(&self, read: PendingRead) -> bool {
2810 read.read(&self.state, |state| state.active != 0)
2811 }
2812
2813 fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2821 let mut state = self
2822 .state
2823 .lock()
2824 .unwrap_or_else(std::sync::PoisonError::into_inner);
2825 while state.active != 0 {
2826 if has_deferred() {
2827 drop(state);
2828 retire();
2829 state = self
2830 .state
2831 .lock()
2832 .unwrap_or_else(std::sync::PoisonError::into_inner);
2833 continue;
2834 }
2835 state = self
2836 .idle
2837 .wait(state)
2838 .unwrap_or_else(std::sync::PoisonError::into_inner);
2839 }
2840 }
2841
2842 fn notify_idle_waiters(&self) {
2843 let _state = self
2844 .state
2845 .lock()
2846 .unwrap_or_else(std::sync::PoisonError::into_inner);
2847 self.idle.notify_all();
2848 }
2849}
2850
2851struct GenerationWorkGuard {
2852 work: Arc<GenerationWork>,
2853 task_id: Option<u64>,
2854}
2855
2856impl Drop for GenerationWorkGuard {
2857 fn drop(&mut self) {
2858 let abort_handle = {
2864 let mut state = self
2865 .work
2866 .state
2867 .lock()
2868 .unwrap_or_else(std::sync::PoisonError::into_inner);
2869 let abort_handle = self
2870 .task_id
2871 .and_then(|task_id| state.abort_handles.remove(&task_id));
2872 state.active = state
2873 .active
2874 .checked_sub(1)
2875 .expect("generation work count underflow");
2876 if state.active == 0 {
2877 self.work.idle.notify_all();
2878 }
2879 abort_handle
2880 };
2881 drop(abort_handle);
2882 }
2883}
2884
2885struct RegisteredTaskFuture<F> {
2891 future: ManuallyDrop<F>,
2892 registration: Option<GenerationWorkGuard>,
2893 generation: u64,
2894}
2895
2896impl<F> RegisteredTaskFuture<F> {
2897 fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2898 Self {
2899 future: ManuallyDrop::new(future),
2900 registration: Some(registration),
2901 generation,
2902 }
2903 }
2904}
2905
2906impl<F: Future> Future for RegisteredTaskFuture<F> {
2907 type Output = F::Output;
2908
2909 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2910 let this = unsafe { self.get_unchecked_mut() };
2912 let _generation = RuntimeGenerationGuard::enter(this.generation);
2913 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2916 }
2917}
2918
2919impl<F> Drop for RegisteredTaskFuture<F> {
2920 fn drop(&mut self) {
2921 let _generation = RuntimeGenerationGuard::enter(self.generation);
2922 let _ = catch_unwind_contained(|| {
2923 unsafe { ManuallyDrop::drop(&mut self.future) };
2925 });
2926 drop(self.registration.take());
2929 }
2930}
2931
2932#[cfg(napi_runtime_os_threads)]
2933struct RegisteredBlockingFunction<F> {
2934 function: Option<F>,
2935 registration: Option<GenerationWorkGuard>,
2936 generation: u64,
2937}
2938
2939#[cfg(napi_runtime_os_threads)]
2940impl<F> RegisteredBlockingFunction<F> {
2941 fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2942 Self {
2943 function: Some(function),
2944 registration: Some(registration),
2945 generation,
2946 }
2947 }
2948
2949 fn run<T>(mut self) -> Result<T, JoinError>
2950 where
2951 F: FnOnce() -> T,
2952 {
2953 on_thread_handoff();
2954 let _generation = RuntimeGenerationGuard::enter(self.generation);
2955 let result = catch_unwind_join_error(
2956 self
2957 .function
2958 .take()
2959 .expect("blocking function must be available"),
2960 );
2961 drop(self.registration.take());
2962 result
2963 }
2964}
2965
2966#[cfg(napi_runtime_os_threads)]
2967impl<F> Drop for RegisteredBlockingFunction<F> {
2968 fn drop(&mut self) {
2969 let _generation = RuntimeGenerationGuard::enter(self.generation);
2970 if let Some(function) = self.function.take() {
2971 let _ = catch_unwind_contained(|| drop(function));
2972 }
2973 drop(self.registration.take());
2977 }
2978}
2979
2980fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2981 on_thread_handoff();
2982 let _active = metrics.runnable_started();
2983 let _ = catch_unwind_contained(|| runnable.run());
2984}
2985
2986#[cfg(napi_runtime_os_threads)]
2987enum RunnableClaim {
2988 Empty,
2989 Run(Runnable),
2990 Cancel(Runnable),
2991}
2992
2993static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2994static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2995
2996fn next_current_thread_dispatch_id() -> u64 {
2997 next_unique_id(
2998 &NEXT_CURRENT_THREAD_DISPATCH_ID,
2999 "CurrentThread dispatch id space exhausted",
3000 )
3001 .checked_add(1)
3002 .expect("CurrentThread dispatch id space exhausted")
3003}
3004
3005struct CurrentThreadBlockingAdmission {
3006 state: Mutex<CurrentThreadBlockingAdmissionState>,
3007}
3008
3009struct CurrentThreadBlockingAdmissionState {
3010 owner: Option<BlockingOwnerToken>,
3011 closed: bool,
3012 #[cfg(napi_runtime_os_threads)]
3013 queue: BlockingQueue,
3014}
3015
3016struct CurrentThreadExecutor {
3017 id: u64,
3018 generation: u64,
3019 stop: Arc<GenerationStop>,
3020 queue: Mutex<CurrentThreadQueue>,
3021 blocking_admission: CurrentThreadBlockingAdmission,
3022 draining: AtomicBool,
3023 scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
3024 scheduler_idle: Condvar,
3025 dispatch_pending: AtomicU64,
3029 recovery_dispatch: AtomicU64,
3033 cancelling_failed_dispatch: AtomicBool,
3039 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3040 task_drivers: &'static CurrentThreadTaskDriverRegistry,
3041 metrics: Arc<RuntimeMetrics>,
3042 threadless: bool,
3050 park_deadline: Option<Duration>,
3051 host_timers: Arc<HostTimerRegistry>,
3061 generation_work: ArcSwapOption<GenerationWork>,
3066}
3067
3068impl Drop for CurrentThreadExecutor {
3069 fn drop(&mut self) {
3070 self.retire_rejected_work();
3073 }
3074}
3075
3076struct CurrentThreadQueue {
3077 closed: bool,
3078 runnables: VecDeque<Runnable>,
3079 rejected: Vec<Runnable>,
3094 #[cfg(napi_runtime_os_threads)]
3098 rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
3099}
3100
3101#[derive(Default)]
3102struct CurrentThreadSchedulerState {
3103 active_host_turns: usize,
3104 active_dispatch_calls: usize,
3105 dispatch_publications: Vec<CurrentThreadDispatchPublication>,
3106}
3107
3108struct CurrentThreadDispatchPublication {
3109 dispatch: u64,
3110 republish_requested: bool,
3111}
3112
3113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3114enum CurrentThreadDispatchRequest {
3115 Accepted,
3116 Coalesced,
3117 Unavailable,
3118}
3119
3120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3121struct CurrentThreadTaskDispatchFailure {
3122 dispatch: u64,
3123 epoch: u64,
3124}
3125
3126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3127enum CurrentThreadHostDispatchResult {
3128 Accepted,
3129 Unavailable,
3130 Failed(CurrentThreadTaskDispatchFailure),
3131}
3132
3133enum CurrentThreadDispatchPublicationStep {
3134 Republish,
3135 Complete(Option<CurrentThreadHostDispatchResult>),
3136}
3137
3138struct CurrentThreadHostTurn {
3139 executor: Arc<CurrentThreadExecutor>,
3140 draining: bool,
3141 _generation: RuntimeGenerationGuard,
3142}
3143
3144impl CurrentThreadHostTurn {
3145 fn drive(&mut self) {
3146 let executor = Arc::clone(&self.executor);
3147 executor.drive_admitted_host_turn(self);
3148 }
3149
3150 fn release_draining(&mut self) {
3151 if !self.draining {
3152 return;
3153 }
3154 let _scheduler = self
3155 .executor
3156 .scheduler_idle_lock
3157 .lock()
3158 .unwrap_or_else(std::sync::PoisonError::into_inner);
3159 self.executor.draining.store(false, Ordering::Release);
3160 self.draining = false;
3161 }
3162}
3163
3164impl Drop for CurrentThreadHostTurn {
3165 fn drop(&mut self) {
3166 let mut scheduler = self
3167 .executor
3168 .scheduler_idle_lock
3169 .lock()
3170 .unwrap_or_else(std::sync::PoisonError::into_inner);
3171 if self.draining {
3172 self.executor.draining.store(false, Ordering::Release);
3173 self.draining = false;
3174 }
3175 scheduler.active_host_turns = scheduler
3176 .active_host_turns
3177 .checked_sub(1)
3178 .expect("CurrentThread active host-turn count underflow");
3179 self.executor.scheduler_idle.notify_all();
3180 }
3181}
3182
3183#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3190pub struct CurrentThreadTaskCallbackLease {
3191 _host_turn: CurrentThreadHostTurn,
3192 _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3193}
3194
3195impl CurrentThreadTaskCallbackLease {
3196 fn new(host_turn: CurrentThreadHostTurn) -> Self {
3197 Self {
3198 _host_turn: host_turn,
3199 _not_send: std::marker::PhantomData,
3200 }
3201 }
3202}
3203
3204struct CurrentThreadDispatchCall<'a> {
3205 executor: &'a CurrentThreadExecutor,
3206 publication: Option<u64>,
3207 _generation: RuntimeGenerationGuard,
3208}
3209
3210impl CurrentThreadDispatchCall<'_> {
3211 fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3212 let dispatch = self
3213 .publication
3214 .expect("the CurrentThread dispatch call must own a publication");
3215 let index = scheduler
3216 .dispatch_publications
3217 .iter()
3218 .position(|publication| publication.dispatch == dispatch)
3219 .expect("the CurrentThread dispatch publication must remain registered");
3220 scheduler.dispatch_publications.swap_remove(index);
3221 self.publication = None;
3222 }
3223}
3224
3225impl Drop for CurrentThreadDispatchCall<'_> {
3226 fn drop(&mut self) {
3227 let mut scheduler = self
3228 .executor
3229 .scheduler_idle_lock
3230 .lock()
3231 .unwrap_or_else(std::sync::PoisonError::into_inner);
3232 if let Some(dispatch) = self.publication.take()
3233 && let Some(index) = scheduler
3234 .dispatch_publications
3235 .iter()
3236 .position(|publication| publication.dispatch == dispatch)
3237 {
3238 scheduler.dispatch_publications.swap_remove(index);
3239 }
3240 scheduler.active_dispatch_calls = scheduler
3241 .active_dispatch_calls
3242 .checked_sub(1)
3243 .expect("CurrentThread active dispatch-call count underflow");
3244 self.executor.scheduler_idle.notify_all();
3245 }
3246}
3247
3248thread_local! {
3249 static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3260}
3261
3262struct CurrentThreadScheduleScope;
3265
3266impl CurrentThreadScheduleScope {
3267 fn enter() -> Self {
3268 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3269 depth.set(
3270 depth
3271 .get()
3272 .checked_add(1)
3273 .expect("CurrentThread schedule depth exhausted"),
3274 );
3275 });
3276 Self
3277 }
3278
3279 fn is_active() -> bool {
3281 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3282 }
3283}
3284
3285impl Drop for CurrentThreadScheduleScope {
3286 fn drop(&mut self) {
3287 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3288 depth.set(
3289 depth
3290 .get()
3291 .checked_sub(1)
3292 .expect("CurrentThread schedule depth underflow"),
3293 );
3294 });
3295 }
3296}
3297
3298thread_local! {
3299 static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3320 const { std::cell::Cell::new(false) };
3321}
3322
3323struct CurrentThreadRejectedWorkRetirementScope;
3326
3327impl CurrentThreadRejectedWorkRetirementScope {
3328 fn enter() -> Option<Self> {
3332 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3333 if active.replace(true) {
3334 return None;
3338 }
3339 Some(Self)
3340 })
3341 }
3342}
3343
3344impl Drop for CurrentThreadRejectedWorkRetirementScope {
3345 fn drop(&mut self) {
3346 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3347 }
3348}
3349
3350struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3351
3352impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3353 fn drop(&mut self) {
3354 self
3355 .0
3356 .cancelling_failed_dispatch
3357 .store(false, Ordering::Release);
3358 }
3359}
3360
3361enum CurrentThreadDispatchFailureAction<'a> {
3362 None,
3363 Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3364 Publish {
3365 dispatch: u64,
3366 call: CurrentThreadDispatchCall<'a>,
3367 },
3368}
3369
3370struct CurrentThreadDispatchFailureRecovery<'a> {
3371 action: CurrentThreadDispatchFailureAction<'a>,
3372 _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3373}
3374
3375struct CurrentThreadRegistryCancellationGuard<'a> {
3376 registry: &'a CurrentThreadTaskDriverRegistry,
3377 failure: Option<CurrentThreadTaskDispatchFailure>,
3378}
3379
3380impl CurrentThreadRegistryCancellationGuard<'_> {
3381 fn new(
3382 registry: &CurrentThreadTaskDriverRegistry,
3383 failure: CurrentThreadTaskDispatchFailure,
3384 ) -> CurrentThreadRegistryCancellationGuard<'_> {
3385 CurrentThreadRegistryCancellationGuard {
3386 registry,
3387 failure: Some(failure),
3388 }
3389 }
3390}
3391
3392impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3393 fn drop(&mut self) {
3394 if let Some(failure) = self.failure.take() {
3395 let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3396 }
3397 }
3398}
3399
3400struct CurrentThreadBlockingGuard<'a> {
3401 executor: &'a CurrentThreadExecutor,
3402 owner: BlockingOwnerToken,
3403 previous_owner: Option<BlockingOwnerToken>,
3404 owns_lane: bool,
3405}
3406
3407impl CurrentThreadBlockingGuard<'_> {
3408 fn counts_active_lane(&self) -> bool {
3409 self.owns_lane
3410 }
3411}
3412
3413impl Drop for CurrentThreadBlockingGuard<'_> {
3414 fn drop(&mut self) {
3415 BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3416 if !self.owns_lane {
3417 return;
3418 }
3419 let mut state = self
3420 .executor
3421 .blocking_admission
3422 .state
3423 .lock()
3424 .unwrap_or_else(std::sync::PoisonError::into_inner);
3425 debug_assert_eq!(state.owner, Some(self.owner));
3426 state.owner = None;
3427 drop(state);
3428 self.executor.stop.wake_all();
3429 }
3430}
3431
3432impl CurrentThreadExecutor {
3433 const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3436 #[cfg(napi_runtime_os_threads)]
3438 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3439 const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3443
3444 #[cfg(test)]
3448 fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3449 Self::with_detection(metrics, THREADLESS_BUILD, None)
3450 }
3451
3452 #[cfg(test)]
3458 fn with_detection(
3459 metrics: Arc<RuntimeMetrics>,
3460 threadless: bool,
3461 park_deadline: Option<Duration>,
3462 ) -> Self {
3463 Self::with_generation(
3464 metrics,
3465 threadless,
3466 park_deadline,
3467 u64::MAX,
3468 Arc::new(GenerationStop::default()),
3469 )
3470 }
3471
3472 fn with_generation(
3473 metrics: Arc<RuntimeMetrics>,
3474 threadless: bool,
3475 park_deadline: Option<Duration>,
3476 generation: u64,
3477 stop: Arc<GenerationStop>,
3478 ) -> Self {
3479 Self {
3480 id: next_unique_id(
3481 &NEXT_RUNTIME_EXECUTOR_ID,
3482 "async runtime executor id space exhausted",
3483 ),
3484 generation,
3485 stop,
3486 queue: Mutex::new(CurrentThreadQueue {
3487 closed: false,
3488 runnables: VecDeque::new(),
3489 rejected: Vec::new(),
3490 #[cfg(napi_runtime_os_threads)]
3491 rejected_blocking: Vec::new(),
3492 }),
3493 blocking_admission: CurrentThreadBlockingAdmission {
3494 state: Mutex::new(CurrentThreadBlockingAdmissionState {
3495 owner: None,
3496 closed: false,
3497 #[cfg(napi_runtime_os_threads)]
3498 queue: BlockingQueue {
3499 closed: false,
3500 head: None,
3501 tail: None,
3502 jobs: FxHashMap::default(),
3503 },
3504 }),
3505 },
3506 draining: AtomicBool::new(false),
3507 scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3508 scheduler_idle: Condvar::new(),
3509 dispatch_pending: AtomicU64::new(0),
3510 recovery_dispatch: AtomicU64::new(0),
3511 cancelling_failed_dispatch: AtomicBool::new(false),
3512 task_dispatch: dispatch_current_thread_tasks,
3513 task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3514 metrics,
3515 threadless,
3516 park_deadline,
3517 host_timers: Arc::new(HostTimerRegistry::default()),
3518 generation_work: ArcSwapOption::empty(),
3519 }
3520 }
3521
3522 fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3523 self.generation_work.store(Some(Arc::clone(work)));
3524 }
3525
3526 fn has_rejected_work(&self) -> bool {
3529 self.rejected_work_pending(PendingRead::Locked)
3530 }
3531
3532 fn rejected_work_pending(&self, read: PendingRead) -> bool {
3535 read.read(&self.queue, |queue| {
3536 #[cfg(napi_runtime_os_threads)]
3537 let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3538 #[cfg(not(napi_runtime_os_threads))]
3539 let has_rejected_blocking = false;
3540 !queue.rejected.is_empty() || has_rejected_blocking
3541 })
3542 }
3543
3544 fn scheduler_work_pending(&self, read: PendingRead) -> bool {
3547 read.read(&self.scheduler_idle_lock, |idle| {
3548 idle.active_host_turns != 0 || idle.active_dispatch_calls != 0
3549 })
3550 }
3551
3552 fn retire_rejected_work(&self) {
3561 if !self.has_rejected_work() {
3562 return;
3563 }
3564 let _generation = RuntimeGenerationGuard::enter(self.generation);
3565 loop {
3566 #[cfg(napi_runtime_os_threads)]
3567 let (rejected, rejected_blocking) = {
3568 let mut queue = self
3569 .queue
3570 .lock()
3571 .unwrap_or_else(std::sync::PoisonError::into_inner);
3572 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3573 return;
3574 }
3575 (
3576 std::mem::take(&mut queue.rejected),
3577 std::mem::take(&mut queue.rejected_blocking),
3578 )
3579 };
3580 #[cfg(not(napi_runtime_os_threads))]
3581 let rejected = {
3582 let mut queue = self
3583 .queue
3584 .lock()
3585 .unwrap_or_else(std::sync::PoisonError::into_inner);
3586 if queue.rejected.is_empty() {
3587 return;
3588 }
3589 std::mem::take(&mut queue.rejected)
3590 };
3591 for runnable in rejected {
3592 let _ = catch_unwind_contained(|| drop(runnable));
3593 }
3594 #[cfg(napi_runtime_os_threads)]
3595 for job in rejected_blocking {
3596 let _ = catch_unwind_contained(|| drop(job));
3597 }
3598 }
3599 }
3600
3601 fn retire_rejected_work_off_waker_stack(&self) {
3607 if CurrentThreadScheduleScope::is_active() {
3608 return;
3609 }
3610 self.retire_rejected_work();
3611 }
3612
3613 fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3623 self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3624 }
3625
3626 fn request_rejected_work_retirement(self: &Arc<Self>) {
3727 if !self.has_rejected_work() {
3728 return;
3729 }
3730 let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3731 return;
3736 };
3737 for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3738 if !self.needs_rejected_work_retirement_dispatch() {
3739 return;
3740 }
3741 if matches!(
3742 self.request_drain_with(next_current_thread_dispatch_id),
3743 CurrentThreadDispatchRequest::Unavailable
3744 ) {
3745 return;
3747 }
3748 }
3749 if self.publish_final_rejected_work_retirement()
3750 && self.needs_rejected_work_retirement_dispatch()
3751 {
3752 let _ = self.request_drain_with(next_current_thread_dispatch_id);
3771 }
3772 }
3773
3774 fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3801 if !self.needs_rejected_work_retirement_dispatch() {
3802 return false;
3803 }
3804 let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3805 return false;
3808 };
3809 let dispatch = next_current_thread_dispatch_id();
3810 let dispatch_call = {
3811 let mut scheduler = self.lock_scheduler_for_publication();
3812 self.dispatch_pending.store(dispatch, Ordering::Release);
3813 self
3814 .begin_host_dispatch_publication(&mut scheduler, dispatch)
3815 .expect("a fresh CurrentThread retirement dispatch must own its publication")
3816 };
3817 let result = self.publish_host_dispatch(dispatch, dispatch_call);
3818 {
3819 let _queue = self
3823 .queue
3824 .lock()
3825 .unwrap_or_else(std::sync::PoisonError::into_inner);
3826 drop(cancelling);
3827 }
3828 if let Some(work) = self.generation_work.load_full() {
3831 work.notify_idle_waiters();
3832 }
3833 result == CurrentThreadHostDispatchResult::Accepted
3834 }
3835
3836 fn fresh_owner_token(&self) -> BlockingOwnerToken {
3837 BlockingOwnerToken {
3838 executor_id: self.id,
3839 frame: next_unique_id(
3840 &NEXT_BLOCKING_FRAME,
3841 "blocking owner frame id space exhausted",
3842 ),
3843 }
3844 }
3845
3846 fn enter_blocking_owner(
3847 &self,
3848 owner: BlockingOwnerToken,
3849 owns_lane: bool,
3850 ) -> CurrentThreadBlockingGuard<'_> {
3851 let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3852 CurrentThreadBlockingGuard {
3853 executor: self,
3854 owner,
3855 previous_owner,
3856 owns_lane,
3857 }
3858 }
3859
3860 fn schedule_blocking<F, T>(
3861 self: &Arc<Self>,
3862 function: F,
3863 registration: GenerationWorkGuard,
3864 ) -> JoinHandle<T>
3865 where
3866 F: FnOnce() -> T + Send + 'static,
3867 T: Send + 'static,
3868 {
3869 self.metrics.blocking_scheduled();
3871 let mut function = Some(function);
3872 let mut registration = Some(registration);
3873 let ambient_owner = BLOCKING_OWNER
3874 .with(std::cell::Cell::get)
3875 .filter(|owner| owner.executor_id == self.id);
3876 let mut run_owner = None;
3877 let mut owns_lane = false;
3878 let mut rejected = false;
3879
3880 #[cfg(napi_runtime_os_threads)]
3881 let (sender, receiver) = oneshot::channel();
3882 #[cfg(napi_runtime_os_threads)]
3883 let dependency = BlockingDependency {
3884 job: BlockingJobId(next_unique_id(
3885 &NEXT_BLOCKING_JOB_ID,
3886 "blocking job id space exhausted",
3887 )),
3888 owner: ambient_owner,
3889 };
3890 #[cfg(napi_runtime_os_threads)]
3891 let mut queued = false;
3892
3893 {
3894 let mut state = self
3895 .blocking_admission
3896 .state
3897 .lock()
3898 .unwrap_or_else(std::sync::PoisonError::into_inner);
3899 #[cfg(all(test, napi_runtime_os_threads))]
3900 run_current_thread_blocking_claim_test_hook();
3901 if self.stop.is_stopping()
3905 || state.closed
3906 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3907 {
3908 rejected = true;
3909 } else if ambient_owner.is_some() && state.owner == ambient_owner {
3910 run_owner = ambient_owner;
3911 } else {
3912 #[cfg(napi_runtime_os_threads)]
3913 let queue_empty = state.queue.is_empty();
3914 #[cfg(not(napi_runtime_os_threads))]
3915 let queue_empty = true;
3916
3917 if state.owner.is_none() && queue_empty {
3918 let owner = self.fresh_owner_token();
3919 state.owner = Some(owner);
3920 run_owner = Some(owner);
3921 owns_lane = true;
3922 } else {
3923 #[cfg(napi_runtime_os_threads)]
3924 {
3925 let function = function
3926 .take()
3927 .expect("blocking function must be available");
3928 let registration = registration
3929 .take()
3930 .expect("blocking registration must be available");
3931 let generation = self.generation;
3932 let function = RegisteredBlockingFunction::new(function, registration, generation);
3933 state.queue.push(QueuedBlocking {
3934 id: dependency.job,
3935 run: Box::new(move || {
3936 let _generation = RuntimeGenerationGuard::enter(generation);
3937 let result = function
3938 .run()
3939 .map(|output| ContainedTaskOutput::new(output, generation));
3940 let _ = sender.send(result);
3941 }),
3942 });
3943 queued = true;
3944 }
3945 #[cfg(not(napi_runtime_os_threads))]
3946 {
3947 rejected = true;
3954 }
3955 }
3956 }
3957 }
3958
3959 #[cfg(napi_runtime_os_threads)]
3960 if queued {
3961 self.stop.wake_all();
3962 if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3963 self.request_drain();
3964 }
3965 return JoinHandle(JoinHandleInner::Blocking {
3966 receiver,
3967 dependency,
3968 published: PublishedBlockingDependencies::default(),
3969 });
3970 }
3971
3972 if rejected {
3973 let _generation = RuntimeGenerationGuard::enter(self.generation);
3974 let _ = catch_unwind_contained(|| drop(function.take()));
3975 drop(registration.take());
3976 return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3977 message: "the async runtime stopped before the blocking task could start".to_string(),
3978 }))));
3979 }
3980
3981 let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3982 let blocking = self.enter_blocking_owner(owner, owns_lane);
3983 let _registration = registration
3984 .take()
3985 .expect("blocking registration must be available");
3986 let _generation = RuntimeGenerationGuard::enter(self.generation);
3987 let result = {
3988 let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3989 on_thread_handoff();
3990 catch_unwind_join_error(
3991 function
3992 .take()
3993 .expect("blocking function must be available"),
3994 )
3995 }
3996 .map(|output| ContainedTaskOutput::new(output, self.generation));
3997 drop(blocking);
3998 #[cfg(napi_runtime_os_threads)]
3999 if owns_lane && !self.draining.load(Ordering::Acquire) {
4000 self.service_queued_blocking_work();
4004 }
4005 JoinHandle(JoinHandleInner::Ready(Some(result)))
4006 }
4007
4008 #[cfg(napi_runtime_os_threads)]
4009 fn take_queued_blocking(
4010 &self,
4011 ) -> Option<(
4012 Box<dyn FnOnce() + Send + 'static>,
4013 CurrentThreadBlockingGuard<'_>,
4014 )> {
4015 let (job, owner) = {
4016 let mut state = self
4017 .blocking_admission
4018 .state
4019 .lock()
4020 .unwrap_or_else(std::sync::PoisonError::into_inner);
4021 #[cfg(all(test, napi_runtime_os_threads))]
4022 run_current_thread_blocking_claim_test_hook();
4023 if self.stop.is_stopping()
4024 || state.closed
4025 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4026 || state.owner.is_some()
4027 {
4028 return None;
4029 }
4030 let job = state.queue.pop_front()?;
4031 let owner = self.fresh_owner_token();
4032 state.owner = Some(owner);
4033 (job, owner)
4034 };
4035 Some((job, self.enter_blocking_owner(owner, true)))
4036 }
4037
4038 #[cfg(napi_runtime_os_threads)]
4039 fn run_one_blocking(&self) -> bool {
4040 let Some((job, blocking)) = self.take_queued_blocking() else {
4041 return false;
4042 };
4043 let _generation = RuntimeGenerationGuard::enter(self.generation);
4044 {
4045 let _task = self.metrics.blocking_started(true);
4046 let _ = catch_unwind_contained(job);
4047 }
4048 drop(blocking);
4049 true
4050 }
4051
4052 #[cfg(napi_runtime_os_threads)]
4053 fn service_queued_blocking_work(&self) {
4054 while self.run_one_blocking() {}
4055 }
4056
4057 #[cfg(napi_runtime_os_threads)]
4058 fn has_serviceable_blocking_work(&self) -> bool {
4059 let state = self
4060 .blocking_admission
4061 .state
4062 .lock()
4063 .unwrap_or_else(std::sync::PoisonError::into_inner);
4064 !state.closed && state.owner.is_none() && !state.queue.is_empty()
4065 }
4066
4067 #[cfg(napi_runtime_os_threads)]
4068 fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
4069 let Some(ambient_owner) = BLOCKING_OWNER
4070 .with(std::cell::Cell::get)
4071 .filter(|owner| owner.executor_id == self.id)
4072 else {
4073 return false;
4074 };
4075 let Some(current) = dependency.select_for_owner(ambient_owner) else {
4076 return false;
4077 };
4078 let job = {
4079 let mut state = self
4080 .blocking_admission
4081 .state
4082 .lock()
4083 .unwrap_or_else(std::sync::PoisonError::into_inner);
4084 #[cfg(all(test, napi_runtime_os_threads))]
4085 run_current_thread_blocking_claim_test_hook();
4086 if self.stop.is_stopping()
4087 || state.closed
4088 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4089 || state.owner != Some(ambient_owner)
4090 {
4091 return false;
4092 }
4093 if dependency.try_claim(¤t) {
4094 state.queue.remove(current.publication.dependency.job)
4095 } else {
4096 None
4097 }
4098 };
4099 let Some(job) = job else {
4100 return false;
4101 };
4102 let _generation = RuntimeGenerationGuard::enter(self.generation);
4103 let _task = self.metrics.blocking_started(false);
4104 let _ = catch_unwind_contained(job);
4105 true
4106 }
4107
4108 fn close_blocking_admission(&self) {
4109 #[cfg(napi_runtime_os_threads)]
4110 let queued = {
4111 let mut state = self
4112 .blocking_admission
4113 .state
4114 .lock()
4115 .unwrap_or_else(std::sync::PoisonError::into_inner);
4116 state.closed = true;
4117 state.queue.closed = true;
4118 state.queue.take_all()
4119 };
4120 #[cfg(not(napi_runtime_os_threads))]
4121 {
4122 let mut state = self
4123 .blocking_admission
4124 .state
4125 .lock()
4126 .unwrap_or_else(std::sync::PoisonError::into_inner);
4127 state.closed = true;
4128 }
4129 #[cfg(napi_runtime_os_threads)]
4130 for job in queued {
4131 let _ = catch_unwind_contained(|| drop(job));
4132 }
4133 }
4134
4135 #[cfg(test)]
4136 fn with_task_dispatch(
4137 metrics: Arc<RuntimeMetrics>,
4138 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4139 ) -> Self {
4140 Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4141 }
4142
4143 #[cfg(test)]
4144 fn with_task_dispatch_registry(
4145 metrics: Arc<RuntimeMetrics>,
4146 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4147 task_drivers: &'static CurrentThreadTaskDriverRegistry,
4148 ) -> Self {
4149 let mut executor = Self::new(metrics);
4150 executor.task_dispatch = task_dispatch;
4151 executor.task_drivers = task_drivers;
4152 executor
4153 }
4154
4155 fn schedule(self: &Arc<Self>, runnable: Runnable) {
4160 let _scope = CurrentThreadScheduleScope::enter();
4161 self.schedule_runnable(runnable);
4162 }
4163
4164 fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4171 self.schedule_runnable(runnable);
4172 }
4173
4174 fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4175 self.metrics.runnable_scheduled();
4176 {
4177 let mut queue = self
4178 .queue
4179 .lock()
4180 .unwrap_or_else(std::sync::PoisonError::into_inner);
4181 if queue.closed {
4186 self
4193 .metrics
4194 .queued_runnables
4195 .fetch_sub(1, Ordering::Relaxed);
4196 queue.rejected.push(runnable);
4197 drop(queue);
4198 if let Some(work) = self.generation_work.load_full() {
4199 work.notify_idle_waiters();
4200 }
4201 return;
4202 }
4203 if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4204 self
4208 .metrics
4209 .queued_runnables
4210 .fetch_sub(1, Ordering::Relaxed);
4211 queue.rejected.push(runnable);
4212 return;
4213 }
4214 queue.runnables.push_back(runnable);
4215 }
4216 self.stop.wake_all();
4220 if !self.draining.load(Ordering::Acquire) {
4221 self.request_drain();
4222 }
4223 }
4224
4225 fn request_drain(self: &Arc<Self>) {
4234 let _ = self.request_drain_with(next_current_thread_dispatch_id);
4235 }
4236
4237 fn lock_scheduler_for_publication(&self) -> MutexGuard<'_, CurrentThreadSchedulerState> {
4238 #[cfg(all(test, napi_runtime_os_threads))]
4239 match self.scheduler_idle_lock.try_lock() {
4240 Ok(scheduler) => scheduler,
4241 Err(std::sync::TryLockError::WouldBlock) => {
4242 run_current_thread_dispatch_publication_blocked_test_hook();
4243 self
4244 .scheduler_idle_lock
4245 .lock()
4246 .unwrap_or_else(std::sync::PoisonError::into_inner)
4247 }
4248 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4249 }
4250 #[cfg(any(not(test), not(napi_runtime_os_threads)))]
4251 self
4252 .scheduler_idle_lock
4253 .lock()
4254 .unwrap_or_else(std::sync::PoisonError::into_inner)
4255 }
4256
4257 fn request_drain_with(
4258 self: &Arc<Self>,
4259 next_dispatch: impl FnOnce() -> u64,
4260 ) -> CurrentThreadDispatchRequest {
4261 let mut scheduler = self.lock_scheduler_for_publication();
4262 if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4263 return CurrentThreadDispatchRequest::Unavailable;
4264 }
4265 if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4266 return CurrentThreadDispatchRequest::Coalesced;
4267 }
4268 let dispatch = next_dispatch();
4269 if self
4270 .dispatch_pending
4271 .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4272 .is_err()
4273 {
4274 return CurrentThreadDispatchRequest::Coalesced;
4275 }
4276 let dispatch_call = self
4277 .begin_host_dispatch_publication(&mut scheduler, dispatch)
4278 .expect("a fresh CurrentThread dispatch must own its publication");
4279 drop(scheduler);
4280 match self.publish_host_dispatch(dispatch, dispatch_call) {
4281 CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4282 CurrentThreadDispatchRequest::Accepted
4283 }
4284 CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4285 }
4286 }
4287
4288 fn claim_dispatch_call<'a>(
4289 &'a self,
4290 scheduler: &mut CurrentThreadSchedulerState,
4291 ) -> CurrentThreadDispatchCall<'a> {
4292 let generation = RuntimeGenerationGuard::enter(self.generation);
4293 let active_dispatch_calls = scheduler
4294 .active_dispatch_calls
4295 .checked_add(1)
4296 .expect("CurrentThread active dispatch-call count exhausted");
4297 scheduler.active_dispatch_calls = active_dispatch_calls;
4298 CurrentThreadDispatchCall {
4299 executor: self,
4300 publication: None,
4301 _generation: generation,
4302 }
4303 }
4304
4305 fn begin_host_dispatch_publication<'a>(
4306 &'a self,
4307 scheduler: &mut CurrentThreadSchedulerState,
4308 dispatch: u64,
4309 ) -> Option<CurrentThreadDispatchCall<'a>> {
4310 if let Some(publication) = scheduler
4312 .dispatch_publications
4313 .iter_mut()
4314 .find(|publication| publication.dispatch == dispatch)
4315 {
4316 publication.republish_requested = true;
4317 return None;
4318 }
4319 let mut dispatch_call = self.claim_dispatch_call(scheduler);
4320 scheduler
4321 .dispatch_publications
4322 .push(CurrentThreadDispatchPublication {
4323 dispatch,
4324 republish_requested: false,
4325 });
4326 dispatch_call.publication = Some(dispatch);
4327 Some(dispatch_call)
4328 }
4329
4330 fn publish_host_dispatch(
4331 self: &Arc<Self>,
4332 dispatch: u64,
4333 mut dispatch_call: CurrentThreadDispatchCall<'_>,
4334 ) -> CurrentThreadHostDispatchResult {
4335 let mut panic_payload = RetainedPanicPayload::default();
4336 loop {
4337 let result = match catch_unwind(AssertUnwindSafe(|| {
4338 #[cfg(all(test, napi_runtime_os_threads))]
4339 run_current_thread_dispatch_call_test_hook();
4340 let result = (self.task_dispatch)(dispatch);
4341 #[cfg(all(test, napi_runtime_os_threads))]
4342 run_current_thread_dispatch_returned_test_hook();
4343 result
4344 })) {
4345 Ok(result) => Some(result),
4346 Err(payload) => {
4347 panic_payload.capture(payload);
4348 None
4349 }
4350 };
4351
4352 let step = catch_unwind(AssertUnwindSafe(|| {
4353 let (republish, cancelling, failure, result) = {
4354 let mut scheduler = self
4355 .scheduler_idle_lock
4356 .lock()
4357 .unwrap_or_else(std::sync::PoisonError::into_inner);
4358 let index = scheduler
4359 .dispatch_publications
4360 .iter()
4361 .position(|publication| publication.dispatch == dispatch)
4362 .expect("the CurrentThread dispatch publication must remain registered");
4363 let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4364 let republish = dispatch_remains_pending
4365 && !self.stop.is_stopping()
4366 && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4367 && scheduler.dispatch_publications[index].republish_requested;
4368 if republish {
4369 if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4370 let _ = self.task_drivers.rearm_failed_publication(failure);
4371 }
4372 scheduler.dispatch_publications[index].republish_requested = false;
4373 (true, None, None, result)
4374 } else {
4375 dispatch_call.finish_publication(&mut scheduler);
4376 let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4377 #[cfg(all(test, napi_runtime_os_threads))]
4378 run_current_thread_failed_publication_retired_test_hook();
4379 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4380 } else {
4381 None
4382 };
4383 let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4384 && dispatch_remains_pending)
4385 .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4386 .flatten();
4387 let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4388 && !dispatch_remains_pending
4389 {
4390 Some(CurrentThreadHostDispatchResult::Accepted)
4393 } else {
4394 result
4395 };
4396 (false, cancelling, failure, result)
4397 }
4398 };
4399
4400 if republish {
4401 return CurrentThreadDispatchPublicationStep::Republish;
4402 }
4403 if let Some(cancelling) = cancelling {
4404 self.cancel_work_after_host_dispatch_failure(cancelling);
4405 }
4406 if let Some(failure) = failure {
4407 self.run_host_dispatch_failure(failure.action);
4408 }
4409 CurrentThreadDispatchPublicationStep::Complete(result)
4410 }));
4411
4412 match step {
4413 Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4414 Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4415 if panic_payload.is_some() {
4416 panic_payload.resume();
4417 }
4418 return result
4419 .expect("a CurrentThread host dispatch without a result must resume its panic");
4420 }
4421 Err(payload) => {
4422 panic_payload.capture(payload);
4423 panic_payload.resume();
4424 }
4425 }
4426 }
4427 }
4428
4429 fn claim_host_turn(
4430 self: &Arc<Self>,
4431 scheduler: &mut CurrentThreadSchedulerState,
4432 ) -> CurrentThreadHostTurn {
4433 scheduler.active_host_turns = scheduler
4434 .active_host_turns
4435 .checked_add(1)
4436 .expect("CurrentThread active host-turn count exhausted");
4437 self.draining.store(true, Ordering::Release);
4438 CurrentThreadHostTurn {
4439 executor: Arc::clone(self),
4440 draining: true,
4441 _generation: RuntimeGenerationGuard::enter(self.generation),
4442 }
4443 }
4444
4445 fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4446 let mut scheduler = self
4447 .scheduler_idle_lock
4448 .lock()
4449 .unwrap_or_else(std::sync::PoisonError::into_inner);
4450 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4451 return None;
4452 }
4453 if dispatch == 0
4454 || self
4455 .dispatch_pending
4456 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4457 .is_err()
4458 {
4459 return None;
4460 }
4461 #[cfg(all(test, napi_runtime_os_threads))]
4462 run_current_thread_dispatch_claim_test_hook();
4463 let _ =
4464 self
4465 .recovery_dispatch
4466 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4467 Some(self.claim_host_turn(&mut scheduler))
4468 }
4469
4470 #[cfg(test)]
4471 fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> 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 Some(self.claim_host_turn(&mut scheduler))
4480 }
4481
4482 #[cfg(test)]
4483 fn drive_host_turn(self: &Arc<Self>) {
4484 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4485 let host_turn = if dispatch == 0 {
4486 self.try_admit_host_turn_without_dispatch()
4487 } else {
4488 self.try_admit_host_turn(dispatch)
4489 };
4490 if let Some(mut host_turn) = host_turn {
4491 host_turn.drive();
4492 }
4493 }
4494
4495 #[cfg(test)]
4496 fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4497 if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4498 host_turn.drive();
4499 }
4500 }
4501
4502 fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4506 self.retire_rejected_work_off_waker_stack();
4509 #[cfg(napi_runtime_os_threads)]
4510 let mut runnable_streak = 0usize;
4511 for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4512 #[cfg(napi_runtime_os_threads)]
4513 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4514 runnable_streak = 0;
4515 if self.run_one_blocking() {
4516 continue;
4517 }
4518 }
4519 if self.drain_one() {
4520 #[cfg(napi_runtime_os_threads)]
4521 {
4522 runnable_streak += 1;
4523 }
4524 continue;
4525 }
4526 #[cfg(napi_runtime_os_threads)]
4527 if self.run_one_blocking() {
4528 runnable_streak = 0;
4529 continue;
4530 }
4531 break;
4532 }
4533 host_turn.release_draining();
4538 self.request_drain_if_work_remains();
4539 }
4540
4541 fn request_drain_if_queued(self: &Arc<Self>) {
4542 self.retire_rejected_work_off_waker_stack();
4543 if !self.has_serviceable_work() {
4544 return;
4545 }
4546 let mut scheduler = self.lock_scheduler_for_publication();
4547 if self.stop.is_stopping()
4548 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4549 || self.draining.load(Ordering::Acquire)
4550 {
4551 return;
4552 }
4553 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4554 if dispatch == 0 {
4555 drop(scheduler);
4556 self.request_drain_if_work_remains();
4557 return;
4558 }
4559
4560 let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4564 return;
4565 };
4566 drop(scheduler);
4567 let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4568 }
4569
4570 fn retire_unavailable_host_dispatch_locked(
4571 &self,
4572 dispatch: u64,
4573 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4574 if self
4575 .dispatch_pending
4576 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4577 .is_err()
4578 {
4579 return None;
4580 }
4581 if self
4582 .recovery_dispatch
4583 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4584 .is_ok()
4585 {
4586 self.begin_failed_dispatch_cancellation()
4587 } else {
4588 None
4589 }
4590 }
4591
4592 fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4593 let failure = {
4595 let mut scheduler = self
4596 .scheduler_idle_lock
4597 .lock()
4598 .unwrap_or_else(std::sync::PoisonError::into_inner);
4599 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4600 };
4601 if let Some(failure) = failure {
4602 self.run_host_dispatch_failure(failure.action);
4603 }
4604 self.retire_rejected_work_off_waker_stack();
4605 }
4606
4607 fn begin_host_dispatch_failure_locked<'a>(
4608 &'a self,
4609 scheduler: &mut CurrentThreadSchedulerState,
4610 failure: CurrentThreadTaskDispatchFailure,
4611 ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4612 if !self.task_drivers.is_current_failure(failure) {
4613 return None;
4614 }
4615 let registry_cancellation =
4616 CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4617 let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4618 Some(CurrentThreadDispatchFailureRecovery {
4619 action,
4620 _registry_cancellation: registry_cancellation,
4621 })
4622 }
4623
4624 fn begin_host_dispatch_failure_transition_locked<'a>(
4625 &'a self,
4626 scheduler: &mut CurrentThreadSchedulerState,
4627 dispatch: u64,
4628 ) -> CurrentThreadDispatchFailureAction<'a> {
4629 if dispatch == 0
4630 || self
4631 .dispatch_pending
4632 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4633 .is_err()
4634 {
4635 return CurrentThreadDispatchFailureAction::None;
4636 }
4637 #[cfg(all(test, napi_runtime_os_threads))]
4638 run_current_thread_dispatch_recovery_test_hook();
4639
4640 if self
4641 .recovery_dispatch
4642 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4643 .is_ok()
4644 {
4645 return self.begin_failed_dispatch_cancellation().map_or(
4649 CurrentThreadDispatchFailureAction::None,
4650 CurrentThreadDispatchFailureAction::Cancel,
4651 );
4652 }
4653
4654 if self.stop.is_stopping()
4655 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4656 || self.draining.load(Ordering::Acquire)
4657 || !self.has_serviceable_work()
4658 {
4659 return CurrentThreadDispatchFailureAction::None;
4660 }
4661
4662 let replacement = next_current_thread_dispatch_id();
4666 self.recovery_dispatch.store(replacement, Ordering::Release);
4667 self.dispatch_pending.store(replacement, Ordering::Release);
4668 let dispatch_call = self
4669 .begin_host_dispatch_publication(scheduler, replacement)
4670 .expect("a reserved CurrentThread replacement must own its publication");
4671 CurrentThreadDispatchFailureAction::Publish {
4672 dispatch: replacement,
4673 call: dispatch_call,
4674 }
4675 }
4676
4677 #[cfg(test)]
4678 fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4679 let failure = {
4680 let mut scheduler = self
4681 .scheduler_idle_lock
4682 .lock()
4683 .unwrap_or_else(std::sync::PoisonError::into_inner);
4684 self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4685 };
4686 self.run_host_dispatch_failure(failure);
4687 }
4688
4689 fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4690 match failure {
4691 CurrentThreadDispatchFailureAction::None => {}
4692 CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4693 self.cancel_work_after_host_dispatch_failure(cancelling);
4694 }
4695 CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4696 let _ = self.publish_host_dispatch(dispatch, call);
4697 }
4698 }
4699 }
4700
4701 fn has_serviceable_work(&self) -> bool {
4702 let has_queued_runnable = {
4703 let queue = self
4704 .queue
4705 .lock()
4706 .unwrap_or_else(std::sync::PoisonError::into_inner);
4707 !queue.closed && !queue.runnables.is_empty()
4708 };
4709 #[cfg(napi_runtime_os_threads)]
4710 let has_queued_blocking = self.has_serviceable_blocking_work();
4711 #[cfg(not(napi_runtime_os_threads))]
4712 let has_queued_blocking = false;
4713 has_queued_runnable || has_queued_blocking
4714 }
4715
4716 fn request_drain_if_work_remains(self: &Arc<Self>) {
4717 if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4718 self.request_drain();
4719 }
4720 }
4721
4722 fn begin_failed_dispatch_cancellation(
4723 &self,
4724 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4725 if self
4726 .cancelling_failed_dispatch
4727 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4728 .is_err()
4729 {
4730 return None;
4731 }
4732 self.dispatch_pending.store(0, Ordering::Release);
4733 self.recovery_dispatch.store(0, Ordering::Release);
4734 Some(CurrentThreadDispatchCancellationGuard(self))
4735 }
4736
4737 fn cancel_work_after_host_dispatch_failure(
4776 self: &Arc<Self>,
4777 cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4778 ) {
4779 let mut cancelling = Some(cancelling);
4780 #[cfg(napi_runtime_os_threads)]
4783 let queued_blocking = {
4784 let mut state = self
4785 .blocking_admission
4786 .state
4787 .lock()
4788 .unwrap_or_else(std::sync::PoisonError::into_inner);
4789 state.queue.take_all()
4790 };
4791 {
4792 let mut queue = self
4793 .queue
4794 .lock()
4795 .unwrap_or_else(std::sync::PoisonError::into_inner);
4796 let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4797 self
4799 .metrics
4800 .queued_runnables
4801 .fetch_sub(moved, Ordering::Relaxed);
4802 let runnables = std::mem::take(&mut queue.runnables);
4803 queue.rejected.extend(runnables);
4804 #[cfg(napi_runtime_os_threads)]
4805 queue.rejected_blocking.extend(queued_blocking);
4806 if CurrentThreadScheduleScope::is_active() {
4807 drop(cancelling.take());
4812 drop(queue);
4813 if let Some(work) = self.generation_work.load_full() {
4816 work.notify_idle_waiters();
4817 }
4818 self.request_rejected_work_retirement();
4822 return;
4823 }
4824 }
4825
4826 let _generation = RuntimeGenerationGuard::enter(self.generation);
4832 loop {
4833 #[cfg(napi_runtime_os_threads)]
4834 let (rejected, rejected_blocking) = {
4835 let mut queue = self
4836 .queue
4837 .lock()
4838 .unwrap_or_else(std::sync::PoisonError::into_inner);
4839 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4840 drop(cancelling.take());
4841 break;
4842 }
4843 (
4844 std::mem::take(&mut queue.rejected),
4845 std::mem::take(&mut queue.rejected_blocking),
4846 )
4847 };
4848 #[cfg(not(napi_runtime_os_threads))]
4849 let rejected = {
4850 let mut queue = self
4851 .queue
4852 .lock()
4853 .unwrap_or_else(std::sync::PoisonError::into_inner);
4854 if queue.rejected.is_empty() {
4855 drop(cancelling.take());
4856 break;
4857 }
4858 std::mem::take(&mut queue.rejected)
4859 };
4860 for runnable in rejected {
4861 let _ = catch_unwind_contained(|| drop(runnable));
4862 }
4863 #[cfg(napi_runtime_os_threads)]
4864 for job in rejected_blocking {
4865 let _ = catch_unwind_contained(|| drop(job));
4866 }
4867 }
4868 }
4869
4870 fn take_runnable(&self) -> Option<Runnable> {
4871 let scheduler = self
4876 .scheduler_idle_lock
4877 .lock()
4878 .unwrap_or_else(std::sync::PoisonError::into_inner);
4879 #[cfg(all(test, napi_runtime_os_threads))]
4880 run_current_thread_runnable_claim_test_hook();
4881 let stop_publication = self.stop.publication_guard();
4882 if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4883 return None;
4884 }
4885 let runnable = {
4886 let mut queue = self
4887 .queue
4888 .lock()
4889 .unwrap_or_else(std::sync::PoisonError::into_inner);
4890 if queue.closed {
4891 None
4892 } else {
4893 queue.runnables.pop_front()
4894 }
4895 };
4896 drop(stop_publication);
4897 drop(scheduler);
4898 runnable
4899 }
4900
4901 fn drain_one(&self) -> bool {
4902 let runnable = self.take_runnable();
4903 if let Some(runnable) = runnable {
4904 #[cfg(napi_runtime_os_threads)]
4905 let _non_owner = BlockingOwnerGuard::enter(None);
4906 run_runnable(&self.metrics, runnable);
4907 true
4908 } else {
4909 false
4910 }
4911 }
4912
4913 fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4935 let parker = Arc::new(DriverParker::default());
4936 let _stop_registration = self.stop.register(&parker);
4937 #[cfg(napi_runtime_os_threads)]
4938 let dependency = BlockOnDependencyGuard::enter(self.generation);
4939 let waker = std::task::Waker::from(Arc::clone(&parker));
4940 let mut cx = Context::from_waker(&waker);
4941 #[cfg(napi_runtime_os_threads)]
4942 let mut runnable_streak = 0usize;
4943 loop {
4944 if self.stop.is_stopping() {
4945 return BlockOnOutcome::Stopped;
4946 }
4947 #[cfg(napi_runtime_os_threads)]
4948 dependency.clear();
4949 on_thread_handoff();
4950 if future.as_mut().poll(&mut cx).is_ready() {
4951 return BlockOnOutcome::Completed;
4952 }
4953 if self.stop.is_stopping() {
4957 return BlockOnOutcome::Stopped;
4958 }
4959 #[cfg(napi_runtime_os_threads)]
4960 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4961 runnable_streak = 0;
4962 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4963 continue;
4964 }
4965 }
4966 if self.drain_one() {
4967 #[cfg(napi_runtime_os_threads)]
4968 {
4969 runnable_streak += 1;
4970 }
4971 continue;
4972 }
4973 #[cfg(napi_runtime_os_threads)]
4974 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4975 runnable_streak = 0;
4976 continue;
4977 }
4978 #[cfg(napi_runtime_os_threads)]
4979 {
4980 runnable_streak = 0;
4981 }
4982 if parker.consume_permit() {
4986 continue;
4987 }
4988 if self.threadless {
5000 if self.host_timers.has_pending() {
5001 std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
5002 }
5003 std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
5004 }
5005 match self.park_deadline {
5006 None => parker.park(),
5010 Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
5011 DeadlineParkOutcome::Woken => {}
5012 DeadlineParkOutcome::Expired(armed) => {
5013 if parker.consume_permit()
5028 || self.metrics.progress_fingerprint() != armed
5029 || self.host_timers.has_live_wait(deadline)
5030 {
5031 continue;
5032 }
5033 std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
5034 }
5035 },
5036 }
5037 }
5038 }
5039
5040 fn begin_shutdown(&self) {
5041 let _generation = RuntimeGenerationGuard::enter(self.generation);
5042 self.stop.begin_shutdown();
5043 self.close_blocking_admission();
5044 let (queued, rejected) = {
5045 let mut queue = self
5046 .queue
5047 .lock()
5048 .unwrap_or_else(std::sync::PoisonError::into_inner);
5049 queue.closed = true;
5050 #[cfg(napi_runtime_os_threads)]
5051 let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
5052 let rejected = std::mem::take(&mut queue.rejected);
5053 #[cfg(napi_runtime_os_threads)]
5054 let rejected = (rejected, rejected_blocking);
5055 (std::mem::take(&mut queue.runnables), rejected)
5056 };
5057 self.dispatch_pending.store(0, Ordering::Release);
5058 self.recovery_dispatch.store(0, Ordering::Release);
5059 self.task_drivers.retire_for_shutdown();
5060 for runnable in queued {
5061 self
5062 .metrics
5063 .queued_runnables
5064 .fetch_sub(1, Ordering::Relaxed);
5065 let _ = catch_unwind_contained(|| drop(runnable));
5068 }
5069 #[cfg(napi_runtime_os_threads)]
5072 let (rejected, rejected_blocking) = rejected;
5073 for runnable in rejected {
5074 let _ = catch_unwind_contained(|| drop(runnable));
5075 }
5076 #[cfg(napi_runtime_os_threads)]
5077 for job in rejected_blocking {
5078 let _ = catch_unwind_contained(|| drop(job));
5079 }
5080 self.host_timers.shutdown();
5081 }
5082
5083 fn wait_until_scheduler_idle(&self) {
5084 self.retire_rejected_work();
5087 let mut idle = self
5088 .scheduler_idle_lock
5089 .lock()
5090 .unwrap_or_else(std::sync::PoisonError::into_inner);
5091 while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
5092 idle = self
5093 .scheduler_idle
5094 .wait(idle)
5095 .unwrap_or_else(std::sync::PoisonError::into_inner);
5096 }
5097 debug_assert!(
5098 idle.dispatch_publications.is_empty(),
5099 "an idle CurrentThread scheduler must not retain dispatch publications"
5100 );
5101 }
5102}
5103
5104#[cfg(napi_runtime_os_threads)]
5117thread_local! {
5118 static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5119 static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5123}
5124
5125#[cfg(napi_runtime_os_threads)]
5131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5132struct SchedulerDriverRole {
5133 executor_id: u64,
5134 can_run_blocking: bool,
5135}
5136
5137#[cfg(napi_runtime_os_threads)]
5138thread_local! {
5139 static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5140 const { std::cell::Cell::new(None) };
5141}
5142
5143#[cfg(napi_runtime_os_threads)]
5160thread_local! {
5161 static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5162 const { std::cell::Cell::new(None) };
5163}
5164
5165#[cfg(napi_runtime_os_threads)]
5166struct OnPoolWorkerGuard {
5167 previous_worker: Option<u64>,
5168 previous_driver: Option<SchedulerDriverRole>,
5169}
5170
5171#[cfg(napi_runtime_os_threads)]
5172impl OnPoolWorkerGuard {
5173 fn enter(id: u64) -> Self {
5177 Self::enter_with_role(id, true)
5178 }
5179
5180 fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5181 Self {
5182 previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5183 previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5184 flag.replace(Some(SchedulerDriverRole {
5185 executor_id: id,
5186 can_run_blocking,
5187 }))
5188 }),
5189 }
5190 }
5191}
5192
5193#[cfg(napi_runtime_os_threads)]
5194impl Drop for OnPoolWorkerGuard {
5195 fn drop(&mut self) {
5196 IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5197 ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5198 }
5199}
5200
5201#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5214struct BlockingOwnerToken {
5215 executor_id: u64,
5216 frame: u64,
5217}
5218
5219#[cfg(napi_runtime_os_threads)]
5220enum OwnedBlockingOutcome {
5221 Ineligible,
5222 OwnerBusy(BlockingOwnerToken),
5223 ReservationReleased {
5224 owner: BlockingOwnerToken,
5225 ran: bool,
5226 },
5227}
5228
5229static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5232#[cfg(napi_runtime_os_threads)]
5233static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5234
5235thread_local! {
5238 static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5239 const { std::cell::Cell::new(None) };
5240}
5241
5242#[cfg(napi_runtime_os_threads)]
5249struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5250
5251#[cfg(napi_runtime_os_threads)]
5252impl BlockingOwnerGuard {
5253 fn enter(token: Option<BlockingOwnerToken>) -> Self {
5254 Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5255 }
5256}
5257
5258#[cfg(napi_runtime_os_threads)]
5259impl Drop for BlockingOwnerGuard {
5260 fn drop(&mut self) {
5261 BLOCKING_OWNER.with(|cell| cell.set(self.0));
5262 }
5263}
5264
5265#[cfg(napi_runtime_os_threads)]
5266#[derive(Default)]
5267struct ActiveBlockingOwners {
5268 owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5272}
5273
5274#[cfg(napi_runtime_os_threads)]
5275impl ActiveBlockingOwners {
5276 fn register(&self, owner: BlockingOwnerToken) {
5277 let previous = self
5278 .owners
5279 .lock()
5280 .unwrap_or_else(std::sync::PoisonError::into_inner)
5281 .insert(owner, None);
5282 debug_assert!(previous.is_none());
5283 }
5284
5285 fn unregister(&self, owner: BlockingOwnerToken) {
5286 let removed = self
5287 .owners
5288 .lock()
5289 .unwrap_or_else(std::sync::PoisonError::into_inner)
5290 .remove(&owner);
5291 debug_assert!(removed.is_some());
5292 }
5293
5294 fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5295 let mut owners = self
5296 .owners
5297 .lock()
5298 .unwrap_or_else(std::sync::PoisonError::into_inner);
5299 let reservation = owners.get_mut(&owner)?;
5300 if reservation.is_some() {
5301 return None;
5302 }
5303 let id = next_unique_id(
5304 &NEXT_OWNER_RESERVATION,
5305 "blocking owner reservation id space exhausted",
5306 );
5307 *reservation = Some(id);
5308 Some(id)
5309 }
5310
5311 fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5312 let mut owners = self
5313 .owners
5314 .lock()
5315 .unwrap_or_else(std::sync::PoisonError::into_inner);
5316 if let Some(current) = owners.get_mut(&owner)
5317 && *current == Some(reservation)
5318 {
5319 *current = None;
5320 }
5321 }
5322
5323 fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5324 self
5325 .owners
5326 .lock()
5327 .unwrap_or_else(std::sync::PoisonError::into_inner)
5328 .get(&owner)
5329 .is_some_and(Option::is_none)
5330 }
5331}
5332
5333#[cfg(napi_runtime_os_threads)]
5334struct ActiveBlockingOwnerGuard<'a> {
5335 owners: &'a ActiveBlockingOwners,
5336 owner: BlockingOwnerToken,
5337}
5338
5339#[cfg(napi_runtime_os_threads)]
5340impl<'a> ActiveBlockingOwnerGuard<'a> {
5341 fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5342 owners.register(owner);
5343 Self { owners, owner }
5344 }
5345}
5346
5347#[cfg(napi_runtime_os_threads)]
5348impl Drop for ActiveBlockingOwnerGuard<'_> {
5349 fn drop(&mut self) {
5350 self.owners.unregister(self.owner);
5351 }
5352}
5353
5354#[cfg(napi_runtime_os_threads)]
5355struct OwnerLaneReservation<'a> {
5356 owners: &'a ActiveBlockingOwners,
5357 owner: BlockingOwnerToken,
5358 reservation: u64,
5359}
5360
5361#[cfg(napi_runtime_os_threads)]
5362impl<'a> OwnerLaneReservation<'a> {
5363 fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5364 let reservation = owners.reserve(owner)?;
5365 Some(Self {
5366 owners,
5367 owner,
5368 reservation,
5369 })
5370 }
5371}
5372
5373#[cfg(napi_runtime_os_threads)]
5374impl Drop for OwnerLaneReservation<'_> {
5375 fn drop(&mut self) {
5376 self.owners.release(self.owner, self.reservation);
5377 }
5378}
5379
5380#[cfg(napi_runtime_os_threads)]
5384type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5385
5386#[cfg(napi_runtime_os_threads)]
5387struct QueuedBlocking {
5388 id: BlockingJobId,
5389 run: BlockingClosure,
5390}
5391
5392#[cfg(napi_runtime_os_threads)]
5393struct BlockingQueueNode {
5394 previous: Option<BlockingJobId>,
5395 next: Option<BlockingJobId>,
5396 run: Box<dyn FnOnce() + Send + 'static>,
5397}
5398
5399#[cfg(napi_runtime_os_threads)]
5400struct BlockingQueue {
5401 closed: bool,
5402 head: Option<BlockingJobId>,
5403 tail: Option<BlockingJobId>,
5404 jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5405}
5406
5407#[cfg(napi_runtime_os_threads)]
5408impl BlockingQueue {
5409 fn push(&mut self, job: QueuedBlocking) {
5410 let previous = self.tail;
5411 let replaced = self.jobs.insert(
5412 job.id,
5413 BlockingQueueNode {
5414 previous,
5415 next: None,
5416 run: job.run,
5417 },
5418 );
5419 debug_assert!(replaced.is_none());
5420 if let Some(previous) = previous {
5421 self
5422 .jobs
5423 .get_mut(&previous)
5424 .expect("blocking queue tail must be indexed")
5425 .next = Some(job.id);
5426 } else {
5427 self.head = Some(job.id);
5428 }
5429 self.tail = Some(job.id);
5430 }
5431
5432 fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5433 self.head.and_then(|id| self.remove(id))
5434 }
5435
5436 fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5437 let node = self.jobs.remove(&id)?;
5438 if let Some(previous) = node.previous {
5439 self
5440 .jobs
5441 .get_mut(&previous)
5442 .expect("blocking queue predecessor must be indexed")
5443 .next = node.next;
5444 } else {
5445 self.head = node.next;
5446 }
5447 if let Some(next) = node.next {
5448 self
5449 .jobs
5450 .get_mut(&next)
5451 .expect("blocking queue successor must be indexed")
5452 .previous = node.previous;
5453 } else {
5454 self.tail = node.previous;
5455 }
5456 Some(node.run)
5457 }
5458
5459 fn is_empty(&self) -> bool {
5460 debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5461 debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5462 self.jobs.is_empty()
5463 }
5464
5465 fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5466 self.head = None;
5467 self.tail = None;
5468 std::mem::take(&mut self.jobs)
5469 .into_values()
5470 .map(|node| node.run)
5471 .collect()
5472 }
5473}
5474
5475#[cfg(napi_runtime_os_threads)]
5476struct WorkerLifecycle {
5477 remaining: Mutex<usize>,
5478 handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5479}
5480
5481#[cfg(napi_runtime_os_threads)]
5482impl WorkerLifecycle {
5483 fn new(worker_threads: usize) -> Arc<Self> {
5484 Arc::new(Self {
5485 remaining: Mutex::new(worker_threads),
5486 handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5487 })
5488 }
5489
5490 fn retain(&self, handle: std::thread::JoinHandle<()>) {
5491 self
5492 .handles
5493 .lock()
5494 .unwrap_or_else(std::sync::PoisonError::into_inner)
5495 .as_mut()
5496 .expect("runtime worker handles already joined")
5497 .push(handle);
5498 }
5499
5500 fn worker_exited(&self) {
5501 let mut remaining = self
5502 .remaining
5503 .lock()
5504 .unwrap_or_else(std::sync::PoisonError::into_inner);
5505 *remaining = remaining
5506 .checked_sub(1)
5507 .expect("runtime worker exit count underflow");
5508 }
5509
5510 fn wait_for_all_workers(&self) {
5511 let handles = self
5512 .handles
5513 .lock()
5514 .unwrap_or_else(std::sync::PoisonError::into_inner)
5515 .take()
5516 .expect("runtime worker handles already joined");
5517 for handle in handles {
5518 if let Err(payload) = crate::sync::join(handle) {
5519 discard_panic_payload(payload);
5520 }
5521 }
5522 }
5523
5524 #[cfg(test)]
5525 fn remaining(&self) -> usize {
5526 *self
5527 .remaining
5528 .lock()
5529 .unwrap_or_else(std::sync::PoisonError::into_inner)
5530 }
5531}
5532
5533#[cfg(napi_runtime_os_threads)]
5534struct MultiThreadExecutor {
5535 generation: u64,
5536 stop: Arc<GenerationStop>,
5537 id: u64,
5540 pool: ThreadPool,
5541 injector: Injector<Runnable>,
5548 blocking_queue: Mutex<BlockingQueue>,
5549 active_drainers: AtomicUsize,
5550 active_blocking: AtomicUsize,
5551 scheduler_idle_lock: Mutex<usize>,
5559 scheduler_idle: Condvar,
5560 worker_lifecycle: Arc<WorkerLifecycle>,
5561 parked_drivers: ParkedDrivers,
5569 active_blocking_owners: ActiveBlockingOwners,
5570 #[cfg(test)]
5571 owner_lending_attempts: AtomicUsize,
5572 #[cfg(test)]
5582 drain_linger_registrations: AtomicUsize,
5583 #[cfg(test)]
5584 drain_linger_expiries: AtomicUsize,
5585 max_drainers: usize,
5586 max_blocking: usize,
5587 park_deadline: Option<Duration>,
5594 drain_linger: Option<Duration>,
5599 deadlock_state: Arc<MultiThreadDeadlockState>,
5603 timers: TimerHeap,
5606 metrics: Arc<RuntimeMetrics>,
5607}
5608
5609#[cfg(napi_runtime_os_threads)]
5610impl MultiThreadExecutor {
5611 const RUNNABLE_BUDGET: usize = 64;
5614 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5617
5618 #[cfg(test)]
5619 fn new(
5620 options: &RuntimeOptions,
5621 metrics: Arc<RuntimeMetrics>,
5622 ) -> Result<Self, RuntimeConfigError> {
5623 Self::new_for_generation(
5624 options,
5625 metrics,
5626 u64::MAX,
5627 Arc::new(GenerationStop::default()),
5628 )
5629 }
5630
5631 fn new_for_generation(
5632 options: &RuntimeOptions,
5633 metrics: Arc<RuntimeMetrics>,
5634 generation: u64,
5635 stop: Arc<GenerationStop>,
5636 ) -> Result<Self, RuntimeConfigError> {
5637 let id = next_unique_id(
5638 &NEXT_RUNTIME_EXECUTOR_ID,
5639 "async runtime executor id space exhausted",
5640 );
5641 let thread_name_prefix = options.thread_name_prefix.clone();
5642 let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5643 options.park_deadline.is_some(),
5644 ));
5645 let timers = TimerHeap::new(
5646 generation,
5647 id,
5648 format!("{thread_name_prefix}-timer"),
5649 Arc::clone(&deadlock_state),
5650 );
5651 let pool_threads = options.worker_threads;
5655 let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5656 let exit_lifecycle = Arc::clone(&worker_lifecycle);
5657 let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5658 let pool = ThreadPoolBuilder::new()
5659 .num_threads(pool_threads)
5660 .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5661 .start_handler(move |_| {
5662 ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5663 })
5664 .exit_handler(move |_| {
5665 IN_SCHEDULER_DRIVER.with(|driver| {
5666 if driver.get().is_some_and(|role| role.executor_id == id) {
5667 driver.set(None);
5668 }
5669 });
5670 exit_lifecycle.worker_exited();
5676 })
5677 .spawn_handler(move |thread| {
5678 let mut builder = std::thread::Builder::new();
5679 if let Some(name) = thread.name() {
5680 builder = builder.name(name.to_string());
5681 }
5682 if let Some(stack_size) = thread.stack_size() {
5683 builder = builder.stack_size(stack_size);
5684 }
5685 let handle = builder.spawn(move || thread.run())?;
5686 spawn_lifecycle.retain(handle);
5687 Ok(())
5688 })
5689 .build()
5690 .map_err(|error| {
5691 worker_lifecycle.wait_for_all_workers();
5692 RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5693 })?;
5694 Ok(Self {
5695 generation,
5696 stop,
5697 id,
5698 pool,
5699 injector: Injector::new(),
5700 blocking_queue: Mutex::new(BlockingQueue {
5701 closed: false,
5702 head: None,
5703 tail: None,
5704 jobs: FxHashMap::default(),
5705 }),
5706 active_drainers: AtomicUsize::new(0),
5707 active_blocking: AtomicUsize::new(0),
5708 scheduler_idle_lock: Mutex::new(0),
5709 scheduler_idle: Condvar::new(),
5710 worker_lifecycle,
5711 parked_drivers: ParkedDrivers::default(),
5712 active_blocking_owners: ActiveBlockingOwners::default(),
5713 #[cfg(test)]
5714 owner_lending_attempts: AtomicUsize::new(0),
5715 #[cfg(test)]
5716 drain_linger_registrations: AtomicUsize::new(0),
5717 #[cfg(test)]
5718 drain_linger_expiries: AtomicUsize::new(0),
5719 max_drainers: pool_threads,
5720 max_blocking: options.max_blocking_tasks,
5721 park_deadline: options.park_deadline,
5722 drain_linger: {
5723 let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5727 (!budget.is_zero()).then_some(budget)
5728 },
5729 deadlock_state,
5730 timers,
5731 metrics,
5732 })
5733 }
5734
5735 fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5736 self.deadlock_state.begin_admission()
5737 }
5738
5739 fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5740 self.deadlock_state.try_begin_verdict()
5741 }
5742
5743 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5744 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5746 self.parked_drivers.wake_one_owner_dependency(owner)
5747 }
5748
5749 fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5750 let owner = reservation.owner;
5751 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5755 drop(reservation);
5756 self.parked_drivers.wake_one_owner_dependency(owner)
5757 }
5758
5759 fn schedule(self: &Arc<Self>, runnable: Runnable) {
5760 let mut admission = self.begin_deadlock_admission();
5761 self.metrics.runnable_scheduled();
5764 if IN_SCHEDULER_DRIVER
5784 .with(std::cell::Cell::get)
5785 .is_some_and(|role| role.executor_id == self.id)
5786 && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5787 {
5788 match LIFO_SLOT.with(std::cell::Cell::take) {
5789 None => {
5790 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5791 if let Some(admission) = admission.as_mut() {
5792 admission.mark_published();
5793 }
5794 return;
5795 }
5796 Some((id, displaced)) if id == self.id => {
5800 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5801 self.push_to_queue_and_wake(displaced);
5802 if let Some(admission) = admission.as_mut() {
5803 admission.mark_published();
5804 }
5805 return;
5806 }
5807 Some(foreign) => {
5814 LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5815 }
5816 }
5817 }
5818 self.push_to_queue_and_wake(runnable);
5819 if let Some(admission) = admission.as_mut() {
5820 admission.mark_published();
5821 }
5822 }
5823
5824 fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5828 self.injector.push(runnable);
5829 std::sync::atomic::fence(Ordering::SeqCst);
5838 self.wake_for_new_work();
5839 }
5840
5841 fn wake_for_new_work(self: &Arc<Self>) {
5850 if !self.parked_drivers.wake_one() {
5851 self.ensure_drainer();
5852 }
5853 }
5854
5855 fn wake_for_blocking_work(self: &Arc<Self>) {
5856 if !self.parked_drivers.wake_one_blocking() {
5859 self.ensure_drainer();
5860 }
5861 }
5862
5863 #[cfg(test)]
5864 fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5865 where
5866 F: FnOnce() -> T + Send + 'static,
5867 T: Send + 'static,
5868 {
5869 self.schedule_blocking_result(move || catch_unwind_join_error(function))
5870 }
5871
5872 fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5873 where
5874 F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5875 T: Send + 'static,
5876 {
5877 let mut admission = self.begin_deadlock_admission();
5878 let (sender, receiver) = oneshot::channel();
5879 let generation = self.generation;
5880 let dependency = BlockingDependency {
5881 job: BlockingJobId(next_unique_id(
5882 &NEXT_BLOCKING_JOB_ID,
5883 "blocking job id space exhausted",
5884 )),
5885 owner: BLOCKING_OWNER
5886 .with(std::cell::Cell::get)
5887 .filter(|owner| owner.executor_id == self.id),
5888 };
5889 self.metrics.blocking_scheduled();
5894 let queued = QueuedBlocking {
5895 id: dependency.job,
5896 run: Box::new(move || {
5897 let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5898 let _ = sender.send(result);
5899 }),
5900 };
5901 let rejected = {
5902 let mut queue = self
5903 .blocking_queue
5904 .lock()
5905 .unwrap_or_else(std::sync::PoisonError::into_inner);
5906 if queue.closed {
5907 Some(queued)
5908 } else {
5909 queue.push(queued);
5910 None
5911 }
5912 };
5913 if let Some(rejected) = rejected {
5914 let _generation = RuntimeGenerationGuard::enter(self.generation);
5919 let _ = catch_unwind_contained(|| drop(rejected));
5920 } else {
5921 self.wake_for_blocking_work();
5923 if let Some(admission) = admission.as_mut() {
5924 admission.mark_published();
5925 }
5926 }
5927 JoinHandle(JoinHandleInner::Blocking {
5928 receiver,
5929 dependency,
5930 published: PublishedBlockingDependencies::default(),
5931 })
5932 }
5933
5934 fn ensure_drainer(self: &Arc<Self>) {
5935 loop {
5936 let active = self.active_drainers.load(Ordering::Acquire);
5937 if active >= self.max_drainers {
5938 return;
5939 }
5940 if self
5941 .active_drainers
5942 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5943 .is_ok()
5944 {
5945 let executor = Arc::clone(self);
5946 self.pool.spawn_fifo(move || executor.drain());
5947 return;
5948 }
5949 }
5950 }
5951
5952 fn drain(self: Arc<Self>) {
5953 let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5959 let _on_pool = OnPoolWorkerGuard::enter(self.id);
5962 let _slot_backstop = LifoSlotFlushGuard(&self);
5968
5969 let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5970 let mut linger: Option<DrainLinger> = None;
5973 let mut idle_deadline: Option<Instant> = None;
5979 let mut frame_deadline: Option<Instant> = None;
5985
5986 'frame: loop {
5987 let mut runnable_streak = 0usize;
5988 for _ in 0..Self::RUNNABLE_BUDGET {
5989 if self.run_one_fair(&mut runnable_streak) {
5990 idle_deadline = None;
5991 continue;
5992 }
5993 self.flush_lifo_slot();
6001 let Some(budget) = linger_budget else {
6023 break 'frame;
6024 };
6025 if self.stop.is_stopping() {
6026 break 'frame;
6027 }
6028 let now = Instant::now();
6029 let frame = *frame_deadline.get_or_insert_with(|| {
6035 now
6036 + budget
6037 .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
6038 .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
6039 });
6040 let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
6041 if now >= deadline {
6042 #[cfg(test)]
6049 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6050 break 'frame;
6051 }
6052 let state = match &linger {
6053 Some(state) => state,
6054 None => linger.insert(DrainLinger::new(&self)),
6055 };
6056 self
6057 .parked_drivers
6058 .register_with_role(&state.parker, true, None);
6059 #[cfg(test)]
6060 self
6061 .drain_linger_registrations
6062 .fetch_add(1, Ordering::SeqCst);
6063 if self.has_queued_work_for_role(true) {
6064 self.parked_drivers.deregister(&state.parker);
6065 continue 'frame;
6066 }
6067 if self.stop.is_stopping() {
6072 self.parked_drivers.deregister(&state.parker);
6073 break 'frame;
6074 }
6075 let woken = state.parker.park_timeout(deadline - now);
6076 self.parked_drivers.deregister(&state.parker);
6077 if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
6078 continue 'frame;
6079 }
6080 #[cfg(test)]
6085 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6086 break 'frame;
6087 }
6088 break 'frame;
6093 }
6094
6095 self.flush_lifo_slot();
6098 self.finish_draining();
6099 }
6100
6101 fn pop_lifo_slot(&self) -> Option<Runnable> {
6105 LIFO_SLOT.with(|slot| match slot.take() {
6106 Some((id, runnable)) if id == self.id => Some(runnable),
6107 other => {
6108 slot.set(other);
6109 None
6110 }
6111 })
6112 }
6113
6114 fn flush_lifo_slot(self: &Arc<Self>) {
6121 let mut admission = self.begin_deadlock_admission();
6122 if let Some(runnable) = self.pop_lifo_slot() {
6123 self.push_to_queue_and_wake(runnable);
6124 if let Some(admission) = admission.as_mut() {
6125 admission.mark_published();
6126 }
6127 }
6128 }
6129
6130 fn fresh_owner_token(&self) -> BlockingOwnerToken {
6134 BlockingOwnerToken {
6135 executor_id: self.id,
6136 frame: next_unique_id(
6137 &NEXT_BLOCKING_FRAME,
6138 "blocking owner frame id space exhausted",
6139 ),
6140 }
6141 }
6142
6143 fn finish_draining(self: &Arc<Self>) {
6144 self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6154 let has_runnable = self.has_queued_runnable();
6155 let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6156 && !self
6157 .blocking_queue
6158 .lock()
6159 .unwrap_or_else(std::sync::PoisonError::into_inner)
6160 .is_empty();
6161 if has_runnable || has_blocking {
6162 self.ensure_drainer();
6163 }
6164 let idle = self
6170 .scheduler_idle_lock
6171 .lock()
6172 .unwrap_or_else(std::sync::PoisonError::into_inner);
6173 if *idle > 0 {
6174 self.scheduler_idle.notify_all();
6175 }
6176 }
6177
6178 fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6179 if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6180 self.cooperative_block_on(future)
6188 } else {
6189 self.parking_block_on(future)
6193 }
6194 }
6195
6196 fn parking_block_on(
6197 self: &Arc<Self>,
6198 mut future: Pin<&mut dyn Future<Output = ()>>,
6199 ) -> BlockOnOutcome {
6200 let parker = Arc::new(DriverParker::default());
6201 let _stop_registration = self.stop.register(&parker);
6202 let waker = Waker::from(Arc::clone(&parker));
6203 let mut cx = Context::from_waker(&waker);
6204 loop {
6205 if self.stop.is_stopping() {
6206 return BlockOnOutcome::Stopped;
6207 }
6208 on_thread_handoff();
6209 if future.as_mut().poll(&mut cx).is_ready() {
6210 return BlockOnOutcome::Completed;
6211 }
6212 if self.stop.is_stopping() {
6213 return BlockOnOutcome::Stopped;
6214 }
6215 parker.park();
6216 }
6217 }
6218
6219 fn run_runnable(
6220 self: &Arc<Self>,
6221 runnable: Runnable,
6222 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6223 ) {
6224 on_thread_handoff();
6225 let _non_owner = BlockingOwnerGuard::enter(None);
6228 let _active = self.metrics.runnable_started();
6229 #[cfg(test)]
6230 run_multi_thread_work_started_test_hook();
6231 drop(admission);
6235 let _ = catch_unwind_contained(|| runnable.run());
6236 }
6237
6238 fn cancel_runnable(
6239 &self,
6240 runnable: Runnable,
6241 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6242 ) {
6243 self.metrics.runnable_cancelled();
6244 drop(admission);
6247 let _generation = RuntimeGenerationGuard::enter(self.generation);
6248 let _ = catch_unwind_contained(|| drop(runnable));
6249 }
6250
6251 fn finish_runnable_claim(
6252 self: &Arc<Self>,
6253 claim: RunnableClaim,
6254 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6255 ) -> bool {
6256 match claim {
6257 RunnableClaim::Empty => {
6258 drop(admission);
6259 false
6260 }
6261 RunnableClaim::Run(runnable) => {
6262 #[cfg(test)]
6263 run_multi_thread_work_claimed_test_hook();
6264 self.run_runnable(runnable, admission);
6265 true
6266 }
6267 RunnableClaim::Cancel(runnable) => {
6268 #[cfg(test)]
6269 run_multi_thread_work_claimed_test_hook();
6270 self.cancel_runnable(runnable, admission);
6271 true
6272 }
6273 }
6274 }
6275
6276 fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6277 match runnable {
6278 Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6279 Some(runnable) => RunnableClaim::Run(runnable),
6280 None => RunnableClaim::Empty,
6281 }
6282 }
6283
6284 fn steal_one(&self) -> Option<Runnable> {
6288 loop {
6289 match self.injector.steal() {
6290 Steal::Success(runnable) => return Some(runnable),
6291 Steal::Empty => return None,
6292 Steal::Retry => continue,
6293 }
6294 }
6295 }
6296
6297 #[cfg(test)]
6304 fn push_fifo_raw(&self, runnable: Runnable) {
6305 self.injector.push(runnable);
6306 std::sync::atomic::fence(Ordering::SeqCst);
6307 }
6308
6309 fn claim_fifo_runnable(&self) -> RunnableClaim {
6310 let stop_publication = self.stop.runnable_claim_guard();
6311 let runnable = self.steal_one();
6312 let stopping = stop_publication.is_stopping();
6313 drop(stop_publication);
6314 Self::runnable_claim(stopping, runnable)
6315 }
6316
6317 fn claim_runnable(&self) -> RunnableClaim {
6318 let stop_publication = self.stop.runnable_claim_guard();
6319 let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6320 let stopping = stop_publication.is_stopping();
6321 drop(stop_publication);
6322 Self::runnable_claim(stopping, runnable)
6323 }
6324
6325 fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6326 let admission = self.begin_deadlock_admission();
6327 let claim = self.claim_fifo_runnable();
6328 self.finish_runnable_claim(claim, admission)
6329 }
6330
6331 fn run_one_runnable(self: &Arc<Self>) -> bool {
6333 let admission = self.begin_deadlock_admission();
6334 let claim = self.claim_runnable();
6337 self.finish_runnable_claim(claim, admission)
6338 }
6339
6340 fn run_one_blocking(self: &Arc<Self>) -> bool {
6341 if let Some((blocking, admission)) = self.take_blocking() {
6342 let _generation = RuntimeGenerationGuard::enter(self.generation);
6343 {
6344 let _task = self.metrics.blocking_started(true);
6345 #[cfg(test)]
6346 run_multi_thread_work_started_test_hook();
6347 drop(admission);
6350 let owner = self.fresh_owner_token();
6351 let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6352 let _owner = BlockingOwnerGuard::enter(Some(owner));
6353 let _ = catch_unwind_contained(blocking);
6358 }
6359 self.active_blocking.fetch_sub(1, Ordering::Release);
6360 return true;
6361 }
6362 false
6363 }
6364
6365 fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6369 self.run_one_fair_with_exact(runnable_streak, None)
6370 }
6371
6372 fn run_one_fair_with_exact(
6376 self: &Arc<Self>,
6377 runnable_streak: &mut usize,
6378 exact_dependency: Option<&TaskDependency>,
6379 ) -> bool {
6380 let mut exact_attempted = false;
6381 if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6382 *runnable_streak = 0;
6383 if let Some(dependency) = exact_dependency {
6384 exact_attempted = true;
6385 if self.try_owned_blocking_over_cap(dependency) {
6386 return true;
6387 }
6388 }
6389 if self.run_one_blocking() {
6390 return true;
6391 }
6392 }
6393 if self.run_one_runnable() {
6394 *runnable_streak += 1;
6395 return true;
6396 }
6397 if !exact_attempted
6398 && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6399 {
6400 *runnable_streak = 0;
6401 return true;
6402 }
6403 if self.run_one_blocking() {
6404 *runnable_streak = 0;
6405 return true;
6406 }
6407 false
6408 }
6409
6410 #[cfg(test)]
6411 fn run_one(self: &Arc<Self>) -> bool {
6412 let mut runnable_streak = 0;
6413 self.run_one_fair(&mut runnable_streak)
6414 }
6415
6416 fn run_owned_blocking_over_cap(
6426 self: &Arc<Self>,
6427 dependency: &TaskDependency,
6428 ) -> OwnedBlockingOutcome {
6429 #[cfg(test)]
6430 self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6431
6432 let Some(ambient_owner) = BLOCKING_OWNER
6433 .with(std::cell::Cell::get)
6434 .filter(|owner| owner.executor_id == self.id)
6435 else {
6436 return OwnedBlockingOutcome::Ineligible;
6437 };
6438 let Some(current) = dependency.select_for_owner(ambient_owner) else {
6439 return OwnedBlockingOutcome::Ineligible;
6440 };
6441 let admission = self.begin_deadlock_admission();
6442 let owner = current
6443 .publication
6444 .dependency
6445 .owner
6446 .expect("selected dependency must carry its owner");
6447 let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6448 else {
6449 return OwnedBlockingOutcome::OwnerBusy(owner);
6450 };
6451 #[cfg(test)]
6452 run_before_owned_blocking_queue_claim_test_hook();
6453 let job = {
6454 let mut queue = self
6455 .blocking_queue
6456 .lock()
6457 .unwrap_or_else(std::sync::PoisonError::into_inner);
6458 if queue.closed || self.stop.is_stopping() {
6462 None
6463 } else if dependency.try_claim(¤t) {
6467 queue.remove(current.publication.dependency.job)
6468 } else {
6469 None
6470 }
6471 };
6472 let ran = if let Some(job) = job {
6473 #[cfg(test)]
6474 run_multi_thread_work_claimed_test_hook();
6475 let _generation = RuntimeGenerationGuard::enter(self.generation);
6476 {
6477 let _task = self.metrics.blocking_started(false);
6478 #[cfg(test)]
6479 run_multi_thread_work_started_test_hook();
6480 drop(admission);
6483 let nested_owner = self.fresh_owner_token();
6484 let _active_owner =
6485 ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6486 let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6487 let _ = catch_unwind_contained(job);
6488 }
6489 true
6490 } else {
6491 drop(admission);
6492 false
6493 };
6494 self.release_owner_lane_and_wake(reservation);
6497 OwnedBlockingOutcome::ReservationReleased { owner, ran }
6498 }
6499
6500 fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6501 matches!(
6502 self.run_owned_blocking_over_cap(dependency),
6503 OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6504 )
6505 }
6506
6507 fn service_owner_handoff(
6508 self: &Arc<Self>,
6509 parker: &DriverParker,
6510 dependency: &TaskDependency,
6511 can_run_blocking: bool,
6512 ) -> Option<bool> {
6513 let handoff_owner = parker.take_owner_handoff()?;
6514 if !can_run_blocking {
6515 self.wake_one_owner_dependency(handoff_owner);
6516 return Some(false);
6517 }
6518 match self.run_owned_blocking_over_cap(dependency) {
6519 OwnedBlockingOutcome::Ineligible => {
6520 self.wake_one_owner_dependency(handoff_owner);
6521 Some(false)
6522 }
6523 OwnedBlockingOutcome::OwnerBusy(owner) => {
6524 if owner != handoff_owner {
6525 self.wake_one_owner_dependency(handoff_owner);
6526 }
6527 Some(false)
6528 }
6529 OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6530 if owner != handoff_owner {
6531 self.wake_one_owner_dependency(handoff_owner);
6532 }
6533 Some(ran)
6534 }
6535 }
6536 }
6537
6538 fn forward_owner_handoff(&self, parker: &DriverParker) {
6539 if let Some(owner) = parker.take_owner_handoff() {
6540 self.wake_one_owner_dependency(owner);
6541 }
6542 }
6543
6544 fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6545 let Some(ambient_owner) = BLOCKING_OWNER
6546 .with(std::cell::Cell::get)
6547 .filter(|owner| owner.executor_id == self.id)
6548 else {
6549 return false;
6550 };
6551 self.active_blocking_owners.is_available(ambient_owner)
6552 && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6553 }
6554
6555 fn has_exact_owner_work_for_role(
6556 &self,
6557 parker: &DriverParker,
6558 dependency: &TaskDependency,
6559 can_run_blocking: bool,
6560 ) -> bool {
6561 parker.has_owner_handoff()
6562 || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6563 }
6564
6565 fn cooperative_block_on(
6574 self: &Arc<Self>,
6575 mut future: Pin<&mut dyn Future<Output = ()>>,
6576 ) -> BlockOnOutcome {
6577 let parker = Arc::new(DriverParker::default());
6582 let _stop_registration = self.stop.register(&parker);
6583 let dependency = BlockOnDependencyGuard::enter(self.generation);
6584 let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6585 let can_run_blocking =
6586 driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6587 let _exit = CooperativeDriverExitGuard {
6588 executor: self,
6589 parker: &parker,
6590 can_run_blocking,
6591 };
6592 let waker = std::task::Waker::from(Arc::clone(&parker));
6597 let mut cx = Context::from_waker(&waker);
6598 let mut runnable_streak = 0usize;
6606 let mut work_streak = 0usize;
6607 let mut force_fifo = false;
6608 loop {
6609 if self.stop.is_stopping() {
6610 return BlockOnOutcome::Stopped;
6611 }
6612 dependency.clear();
6613 on_thread_handoff();
6614 if future.as_mut().poll(&mut cx).is_ready() {
6615 return BlockOnOutcome::Completed;
6616 }
6617 if self.stop.is_stopping() {
6618 return BlockOnOutcome::Stopped;
6619 }
6620 if self
6621 .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6622 .is_some_and(|ran| ran)
6623 {
6624 continue;
6625 }
6626 if force_fifo {
6627 force_fifo = false;
6628 if self.run_one_fifo_runnable() {
6629 runnable_streak += 1;
6630 work_streak += 1;
6631 continue;
6632 }
6633 }
6634 let ran_work = if can_run_blocking {
6635 self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6636 } else if self.run_one_runnable() {
6637 runnable_streak += 1;
6638 true
6639 } else {
6640 false
6641 };
6642 if ran_work {
6643 work_streak += 1;
6644 if work_streak >= Self::RUNNABLE_BUDGET {
6645 self.flush_lifo_slot();
6650 work_streak = 0;
6651 force_fifo = true;
6652 }
6653 continue;
6654 }
6655 runnable_streak = 0;
6656 work_streak = 0;
6657 self.flush_lifo_slot();
6662 self.parked_drivers.register_with_role(
6666 &parker,
6667 can_run_blocking,
6668 Some(Arc::clone(&dependency.dependency)),
6669 );
6670 if self.has_queued_work_for_role(can_run_blocking)
6671 || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6672 {
6673 self.parked_drivers.deregister(&parker);
6674 continue;
6675 }
6676 if self.stop.is_stopping() {
6677 return BlockOnOutcome::Stopped;
6678 }
6679 if let Some(next) = self.next_timer_deadline() {
6694 let now = Instant::now();
6695 if next > now {
6696 parker.park_timeout(next - now);
6697 }
6698 self.parked_drivers.deregister(&parker);
6699 self.fire_due_timers();
6700 continue;
6701 }
6702 match self.park_deadline {
6703 None => parker.park(),
6704 Some(deadline) => {
6705 let deadlock_publications = self.deadlock_state.publications();
6711 match park_with_deadline(&parker, deadline, &self.metrics) {
6712 DeadlineParkOutcome::Woken => {}
6713 DeadlineParkOutcome::Expired(armed) => {
6714 #[cfg(test)]
6715 run_deadline_expiry_test_hook();
6716 self.parked_drivers.deregister(&parker);
6753 if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6754 continue;
6755 }
6756 #[cfg(test)]
6759 run_deadline_verdict_test_hook();
6760 if self.metrics.progress_fingerprint() != armed {
6761 continue;
6762 }
6763 if self.next_timer_deadline().is_some() {
6772 continue;
6773 }
6774 #[cfg(test)]
6777 run_deadline_final_verdict_test_hook();
6778 let Some(verdict) = self.try_begin_deadlock_verdict() else {
6779 continue;
6780 };
6781 #[cfg(test)]
6782 run_deadline_gated_verdict_test_hook();
6783 let stop_publication = self.stop.publication_guard();
6788 #[cfg(test)]
6789 run_deadline_synchronized_stop_test_hook();
6790 if stop_publication.is_stopping() {
6791 return BlockOnOutcome::Stopped;
6792 }
6793 let owner_handoff_publication = self
6794 .deadlock_state
6795 .owner_handoff_publication_guard()
6796 .expect("a deadline verdict requires exact-owner publication");
6797 #[cfg(test)]
6798 run_deadline_exact_owner_verdict_test_hook(&parker);
6799 if self.has_exact_owner_work_for_role(
6807 &parker,
6808 &dependency.dependency,
6809 can_run_blocking,
6810 ) || parker.consume_permit()
6811 || self.has_queued_work_for_role(can_run_blocking)
6812 || self.deadlock_state.publications() != deadlock_publications
6813 || self.metrics.progress_fingerprint() != armed
6814 || self.next_timer_deadline().is_some()
6815 {
6816 continue;
6817 }
6818 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6819 drop(owner_handoff_publication);
6824 drop(stop_publication);
6825 drop(verdict);
6826 std::panic::panic_any(diagnostic);
6827 }
6828 }
6829 }
6830 }
6831 self.parked_drivers.deregister(&parker);
6834 }
6835 }
6836
6837 fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6843 if self.has_queued_runnable() {
6844 return true;
6845 }
6846 can_run_blocking
6847 && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6848 && !self
6849 .blocking_queue
6850 .lock()
6851 .unwrap_or_else(std::sync::PoisonError::into_inner)
6852 .is_empty()
6853 }
6854
6855 fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6856 if self.has_queued_runnable() {
6857 self.wake_for_new_work();
6858 }
6859 if self.stop.is_stopping() {
6863 return;
6864 }
6865 if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6866 && !self
6867 .blocking_queue
6868 .lock()
6869 .unwrap_or_else(std::sync::PoisonError::into_inner)
6870 .is_empty()
6871 {
6872 let consumed = can_run_blocking && self.run_one_blocking();
6880 let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6881 && !self
6882 .blocking_queue
6883 .lock()
6884 .unwrap_or_else(std::sync::PoisonError::into_inner)
6885 .is_empty();
6886 if !self.stop.is_stopping() && (!consumed || has_residue) {
6887 self.wake_for_blocking_work();
6888 }
6889 }
6890 }
6891
6892 fn has_queued_runnable(&self) -> bool {
6893 std::sync::atomic::fence(Ordering::SeqCst);
6900 !self.injector.is_empty()
6901 }
6902
6903 fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6907 let admission = self.begin_deadlock_admission();
6908 loop {
6909 let active = self.active_blocking.load(Ordering::Acquire);
6910 if active >= self.max_blocking {
6911 return None;
6912 }
6913 let mut queue = self
6914 .blocking_queue
6915 .lock()
6916 .unwrap_or_else(std::sync::PoisonError::into_inner);
6917 if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6918 return None;
6919 }
6920 if self
6921 .active_blocking
6922 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6923 .is_ok()
6924 {
6925 let blocking = queue
6926 .pop_front()
6927 .expect("a non-empty blocking queue must have a FIFO head");
6928 drop(queue);
6929 #[cfg(test)]
6930 run_multi_thread_work_claimed_test_hook();
6931 return Some((blocking, admission));
6932 }
6933 }
6934 }
6935}
6936
6937#[cfg(napi_runtime_os_threads)]
6938struct MultiThreadDeadlockState {
6939 enabled: bool,
6940 verdict_closed: AtomicBool,
6941 admissions: AtomicUsize,
6942 publications: AtomicU64,
6943 owner_handoff_publication: Mutex<()>,
6944}
6945
6946#[cfg(napi_runtime_os_threads)]
6947impl MultiThreadDeadlockState {
6948 fn new(enabled: bool) -> Self {
6949 Self {
6950 enabled,
6951 verdict_closed: AtomicBool::new(false),
6952 admissions: AtomicUsize::new(0),
6953 publications: AtomicU64::new(0),
6954 owner_handoff_publication: Mutex::new(()),
6955 }
6956 }
6957
6958 fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6959 if !self.enabled {
6960 return None;
6961 }
6962 loop {
6963 while self.verdict_closed.load(Ordering::SeqCst) {
6964 #[cfg(test)]
6965 run_deadlock_closed_gate_admission_test_hook();
6966 std::hint::spin_loop();
6967 }
6968 self.admissions.fetch_add(1, Ordering::SeqCst);
6969 if !self.verdict_closed.load(Ordering::SeqCst) {
6970 return Some(MultiThreadDeadlockAdmissionGuard {
6971 state: Arc::clone(self),
6972 published: false,
6973 });
6974 }
6975 self.admissions.fetch_sub(1, Ordering::SeqCst);
6976 }
6977 }
6978
6979 fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6980 debug_assert!(
6981 self.enabled,
6982 "a deadline verdict requires an enabled admission state"
6983 );
6984 if self
6985 .verdict_closed
6986 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6987 .is_err()
6988 {
6989 return None;
6990 }
6991 if self.admissions.load(Ordering::SeqCst) != 0 {
6992 self.verdict_closed.store(false, Ordering::SeqCst);
6993 return None;
6994 }
6995 Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6996 }
6997
6998 fn publications(&self) -> u64 {
6999 self.publications.load(Ordering::SeqCst)
7000 }
7001
7002 fn owner_handoff_publication_guard(&self) -> Option<MutexGuard<'_, ()>> {
7003 self.enabled.then(|| {
7004 self
7005 .owner_handoff_publication
7006 .lock()
7007 .unwrap_or_else(std::sync::PoisonError::into_inner)
7008 })
7009 }
7010}
7011
7012#[cfg(napi_runtime_os_threads)]
7013struct MultiThreadDeadlockAdmissionGuard {
7014 state: Arc<MultiThreadDeadlockState>,
7015 published: bool,
7016}
7017
7018#[cfg(napi_runtime_os_threads)]
7019impl MultiThreadDeadlockAdmissionGuard {
7020 fn mark_published(&mut self) {
7021 self.published = true;
7022 }
7023}
7024
7025#[cfg(napi_runtime_os_threads)]
7026impl Drop for MultiThreadDeadlockAdmissionGuard {
7027 fn drop(&mut self) {
7028 if self.published {
7029 self.state.publications.fetch_add(1, Ordering::SeqCst);
7030 }
7031 self.state.admissions.fetch_sub(1, Ordering::SeqCst);
7034 }
7035}
7036
7037#[cfg(napi_runtime_os_threads)]
7038struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
7039
7040#[cfg(napi_runtime_os_threads)]
7041impl Drop for MultiThreadDeadlockVerdictGuard {
7042 fn drop(&mut self) {
7043 self.0.verdict_closed.store(false, Ordering::SeqCst);
7044 }
7045}
7046
7047#[cfg(napi_runtime_os_threads)]
7053struct CooperativeDriverExitGuard<'a> {
7054 executor: &'a Arc<MultiThreadExecutor>,
7055 parker: &'a Arc<DriverParker>,
7056 can_run_blocking: bool,
7057}
7058
7059#[cfg(napi_runtime_os_threads)]
7060impl Drop for CooperativeDriverExitGuard<'_> {
7061 fn drop(&mut self) {
7062 let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
7063 let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
7064 let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
7065 let _ = catch_unwind_contained(|| {
7066 self
7067 .executor
7068 .compensate_queued_work_wake(self.can_run_blocking);
7069 });
7070 }
7071}
7072
7073#[cfg(napi_runtime_os_threads)]
7079struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
7080
7081#[cfg(napi_runtime_os_threads)]
7082impl Drop for LifoSlotFlushGuard<'_> {
7083 fn drop(&mut self) {
7084 self.0.flush_lifo_slot();
7085 }
7086}
7087
7088#[cfg(napi_runtime_os_threads)]
7095struct DrainLinger {
7096 parker: Arc<DriverParker>,
7097 _stop_registration: GenerationStopGuard,
7098}
7099
7100#[cfg(napi_runtime_os_threads)]
7101impl DrainLinger {
7102 fn new(executor: &MultiThreadExecutor) -> Self {
7103 let parker = Arc::new(DriverParker::default());
7104 let stop_registration = executor.stop.register(&parker);
7105 Self {
7106 parker,
7107 _stop_registration: stop_registration,
7108 }
7109 }
7110}
7111
7112const PARKER_EMPTY: usize = 0;
7134const PARKER_NOTIFIED: usize = 1;
7136const PARKER_SLEEPING: usize = 2;
7138
7139#[derive(Default)]
7143struct DriverParker {
7144 state: AtomicUsize,
7145 lock: Mutex<()>,
7146 condvar: UnboundedCondvar,
7147 #[cfg(napi_runtime_os_threads)]
7148 owner_handoff_pending: AtomicBool,
7149 #[cfg(napi_runtime_os_threads)]
7150 owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7151}
7152
7153impl DriverParker {
7154 #[cfg(napi_runtime_os_threads)]
7155 fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7156 let mut pending = self
7157 .owner_handoff
7158 .lock()
7159 .unwrap_or_else(std::sync::PoisonError::into_inner);
7160 debug_assert!(pending.is_none());
7161 *pending = Some(owner);
7162 self.owner_handoff_pending.store(true, Ordering::Release);
7163 }
7164
7165 #[cfg(napi_runtime_os_threads)]
7166 fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7167 if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7168 return None;
7169 }
7170 self
7171 .owner_handoff
7172 .lock()
7173 .unwrap_or_else(std::sync::PoisonError::into_inner)
7174 .take()
7175 }
7176
7177 #[cfg(napi_runtime_os_threads)]
7178 fn has_owner_handoff(&self) -> bool {
7179 self.owner_handoff_pending.load(Ordering::Acquire)
7180 }
7181
7182 fn unpark(&self) {
7189 if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7190 drop(
7195 self
7196 .lock
7197 .lock()
7198 .unwrap_or_else(std::sync::PoisonError::into_inner),
7199 );
7200 self.condvar.notify_one();
7201 }
7202 }
7203
7204 fn consume_permit(&self) -> bool {
7209 self
7210 .state
7211 .compare_exchange(
7212 PARKER_NOTIFIED,
7213 PARKER_EMPTY,
7214 Ordering::SeqCst,
7215 Ordering::SeqCst,
7216 )
7217 .is_ok()
7218 }
7219
7220 fn park(&self) {
7224 if self.consume_permit() {
7226 return;
7227 }
7228 let mut guard = self
7229 .lock
7230 .lock()
7231 .unwrap_or_else(std::sync::PoisonError::into_inner);
7232 match self.state.compare_exchange(
7233 PARKER_EMPTY,
7234 PARKER_SLEEPING,
7235 Ordering::SeqCst,
7236 Ordering::SeqCst,
7237 ) {
7238 Ok(_) => {}
7239 Err(_) => {
7243 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7244 return;
7245 }
7246 }
7247 loop {
7248 guard = self
7249 .condvar
7250 .wait(guard)
7251 .unwrap_or_else(std::sync::PoisonError::into_inner);
7252 if self
7253 .state
7254 .compare_exchange(
7255 PARKER_NOTIFIED,
7256 PARKER_EMPTY,
7257 Ordering::SeqCst,
7258 Ordering::SeqCst,
7259 )
7260 .is_ok()
7261 {
7262 return;
7263 }
7264 }
7266 }
7267
7268 fn park_timeout(&self, timeout: Duration) -> bool {
7274 if self.consume_permit() {
7275 return true;
7276 }
7277 let mut guard = self
7278 .lock
7279 .lock()
7280 .unwrap_or_else(std::sync::PoisonError::into_inner);
7281 match self.state.compare_exchange(
7282 PARKER_EMPTY,
7283 PARKER_SLEEPING,
7284 Ordering::SeqCst,
7285 Ordering::SeqCst,
7286 ) {
7287 Ok(_) => {}
7288 Err(_) => {
7292 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7293 return true;
7294 }
7295 }
7296 let Some(deadline) = Instant::now().checked_add(timeout) else {
7297 loop {
7301 guard = self
7302 .condvar
7303 .wait(guard)
7304 .unwrap_or_else(std::sync::PoisonError::into_inner);
7305 if self
7306 .state
7307 .compare_exchange(
7308 PARKER_NOTIFIED,
7309 PARKER_EMPTY,
7310 Ordering::SeqCst,
7311 Ordering::SeqCst,
7312 )
7313 .is_ok()
7314 {
7315 return true;
7316 }
7317 }
7318 };
7319 loop {
7320 if self
7321 .state
7322 .compare_exchange(
7323 PARKER_NOTIFIED,
7324 PARKER_EMPTY,
7325 Ordering::SeqCst,
7326 Ordering::SeqCst,
7327 )
7328 .is_ok()
7329 {
7330 return true;
7331 }
7332 let now = Instant::now();
7333 if now >= deadline {
7334 return match self.state.compare_exchange(
7339 PARKER_SLEEPING,
7340 PARKER_EMPTY,
7341 Ordering::SeqCst,
7342 Ordering::SeqCst,
7343 ) {
7344 Ok(_) => false,
7345 Err(_) => {
7346 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7347 true
7348 }
7349 };
7350 }
7351 let (returned_guard, _timed_out) = self
7352 .condvar
7353 .wait_timeout(guard, deadline - now)
7354 .unwrap_or_else(std::sync::PoisonError::into_inner);
7355 guard = returned_guard;
7356 }
7359 }
7360}
7361
7362impl std::task::Wake for DriverParker {
7363 fn wake(self: Arc<Self>) {
7364 self.unpark();
7365 }
7366
7367 fn wake_by_ref(self: &Arc<Self>) {
7368 self.unpark();
7369 }
7370}
7371
7372enum DeadlineParkOutcome {
7374 Woken,
7376 Expired(ProgressFingerprint),
7385}
7386
7387fn park_with_deadline(
7396 parker: &DriverParker,
7397 deadline: Duration,
7398 metrics: &RuntimeMetrics,
7399) -> DeadlineParkOutcome {
7400 let mut armed = metrics.progress_fingerprint();
7401 loop {
7402 if parker.park_timeout(deadline) {
7403 return DeadlineParkOutcome::Woken;
7404 }
7405 let current = metrics.progress_fingerprint();
7406 if current != armed {
7407 armed = current;
7408 continue;
7409 }
7410 return DeadlineParkOutcome::Expired(armed);
7411 }
7412}
7413
7414#[cfg(all(test, napi_runtime_os_threads))]
7422thread_local! {
7423 static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7424 const { std::cell::RefCell::new(None) };
7425}
7426
7427#[cfg(all(test, napi_runtime_os_threads))]
7428fn run_deadline_expiry_test_hook() {
7429 if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7430 hook();
7431 }
7432}
7433
7434#[cfg(all(test, napi_runtime_os_threads))]
7441thread_local! {
7442 static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7443 const { std::cell::RefCell::new(None) };
7444}
7445
7446#[cfg(all(test, napi_runtime_os_threads))]
7447fn run_deadline_verdict_test_hook() {
7448 if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7449 hook();
7450 }
7451}
7452
7453#[cfg(all(test, napi_runtime_os_threads))]
7458thread_local! {
7459 static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7460 const { std::cell::RefCell::new(None) };
7461}
7462
7463#[cfg(all(test, napi_runtime_os_threads))]
7464fn run_deadline_final_verdict_test_hook() {
7465 if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7466 hook();
7467 }
7468}
7469
7470#[cfg(all(test, napi_runtime_os_threads))]
7475thread_local! {
7476 static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7477 const { std::cell::RefCell::new(None) };
7478}
7479
7480#[cfg(all(test, napi_runtime_os_threads))]
7481fn run_deadline_gated_verdict_test_hook() {
7482 if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7483 hook();
7484 }
7485}
7486
7487#[cfg(all(test, napi_runtime_os_threads))]
7491thread_local! {
7492 static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7493 const { std::cell::RefCell::new(None) };
7494}
7495
7496#[cfg(all(test, napi_runtime_os_threads))]
7497fn run_deadline_synchronized_stop_test_hook() {
7498 if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7499 hook();
7500 }
7501}
7502
7503#[cfg(all(test, napi_runtime_os_threads))]
7507type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7508
7509#[cfg(all(test, napi_runtime_os_threads))]
7510thread_local! {
7511 static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7512 std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7513 const { std::cell::RefCell::new(None) };
7514}
7515
7516#[cfg(all(test, napi_runtime_os_threads))]
7517fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7518 if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7519 hook(parker);
7520 }
7521}
7522
7523#[cfg(all(test, napi_runtime_os_threads))]
7527thread_local! {
7528 static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7529 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7530 const { std::cell::RefCell::new(None) };
7531}
7532
7533#[cfg(all(test, napi_runtime_os_threads))]
7534fn run_before_generation_stop_publication_lock_test_hook() {
7535 if let Some(hook) =
7536 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7537 {
7538 hook();
7539 }
7540}
7541
7542#[cfg(all(test, napi_runtime_os_threads))]
7546thread_local! {
7547 static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7548 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7549 const { std::cell::RefCell::new(None) };
7550}
7551
7552#[cfg(all(test, napi_runtime_os_threads))]
7553fn run_deadlock_closed_gate_admission_test_hook() {
7554 if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7555 {
7556 hook();
7557 }
7558}
7559
7560#[cfg(all(test, napi_runtime_os_threads))]
7564thread_local! {
7565 static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7566 const { std::cell::RefCell::new(None) };
7567}
7568
7569#[cfg(all(test, napi_runtime_os_threads))]
7570fn run_timer_registration_admission_test_hook() {
7571 if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7572 hook();
7573 }
7574}
7575
7576#[cfg(all(test, napi_runtime_os_threads))]
7577thread_local! {
7578 static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7579 const { std::cell::RefCell::new(None) };
7580}
7581
7582#[cfg(all(test, napi_runtime_os_threads))]
7583fn run_owner_handoff_selected_test_hook() {
7584 if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7585 hook();
7586 }
7587}
7588
7589#[cfg(napi_runtime_os_threads)]
7592#[derive(Default)]
7593struct ParkedDrivers {
7594 parked: Mutex<Vec<ParkedDriver>>,
7595 count: AtomicUsize,
7598}
7599
7600#[cfg(napi_runtime_os_threads)]
7601struct ParkedDriver {
7602 parker: Arc<DriverParker>,
7603 can_run_blocking: bool,
7604 dependency: Option<Arc<TaskDependency>>,
7605}
7606
7607#[cfg(napi_runtime_os_threads)]
7608impl ParkedDrivers {
7609 #[cfg(test)]
7612 fn register(&self, parker: &Arc<DriverParker>) {
7613 self.register_with_role(parker, true, None);
7614 }
7615
7616 fn register_with_role(
7617 &self,
7618 parker: &Arc<DriverParker>,
7619 can_run_blocking: bool,
7620 dependency: Option<Arc<TaskDependency>>,
7621 ) {
7622 let mut parked = self
7623 .parked
7624 .lock()
7625 .unwrap_or_else(std::sync::PoisonError::into_inner);
7626 parked.push(ParkedDriver {
7627 parker: Arc::clone(parker),
7628 can_run_blocking,
7629 dependency,
7630 });
7631 self.count.store(parked.len(), Ordering::SeqCst);
7632 }
7633
7634 fn deregister(&self, parker: &Arc<DriverParker>) {
7638 let removed = {
7639 let mut parked = self
7640 .parked
7641 .lock()
7642 .unwrap_or_else(std::sync::PoisonError::into_inner);
7643 let removed = parked
7644 .iter()
7645 .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7646 .map(|index| parked.swap_remove(index));
7647 self.count.store(parked.len(), Ordering::SeqCst);
7648 removed
7649 };
7650 drop(removed);
7651 }
7652
7653 fn wake_one(&self) -> bool {
7676 if self.count.load(Ordering::SeqCst) == 0 {
7677 return false;
7681 }
7682 let driver = {
7683 let mut parked = self
7684 .parked
7685 .lock()
7686 .unwrap_or_else(std::sync::PoisonError::into_inner);
7687 let driver = parked.pop();
7688 self.count.store(parked.len(), Ordering::SeqCst);
7689 driver
7690 };
7691 match driver {
7692 Some(driver) => {
7693 driver.parker.unpark();
7694 true
7695 }
7696 None => false,
7697 }
7698 }
7699
7700 fn wake_one_blocking(&self) -> bool {
7703 if self.count.load(Ordering::SeqCst) == 0 {
7704 return false;
7705 }
7706 let driver = {
7707 let mut parked = self
7708 .parked
7709 .lock()
7710 .unwrap_or_else(std::sync::PoisonError::into_inner);
7711 let driver = parked
7712 .iter()
7713 .rposition(|driver| driver.can_run_blocking)
7714 .map(|index| parked.swap_remove(index));
7715 self.count.store(parked.len(), Ordering::SeqCst);
7716 driver
7717 };
7718 match driver {
7719 Some(driver) => {
7720 driver.parker.unpark();
7721 true
7722 }
7723 None => false,
7724 }
7725 }
7726
7727 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7732 loop {
7733 if self.count.load(Ordering::SeqCst) == 0 {
7734 return false;
7735 }
7736 let candidates = {
7737 let parked = self
7738 .parked
7739 .lock()
7740 .unwrap_or_else(std::sync::PoisonError::into_inner);
7741 parked
7742 .iter()
7743 .rev()
7744 .filter_map(|driver| {
7745 if !driver.can_run_blocking {
7746 return None;
7747 }
7748 driver
7749 .dependency
7750 .as_ref()
7751 .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7752 })
7753 .collect::<Vec<_>>()
7754 };
7755 let selected = candidates.into_iter().find(|(_, dependency)| {
7756 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7757 });
7758 let Some((selected_parker, selected_dependency)) = selected else {
7759 return false;
7760 };
7761 let driver = {
7762 let mut parked = self
7763 .parked
7764 .lock()
7765 .unwrap_or_else(std::sync::PoisonError::into_inner);
7766 let driver = parked
7767 .iter()
7768 .rposition(|driver| {
7769 Arc::ptr_eq(&driver.parker, &selected_parker)
7770 && driver
7771 .dependency
7772 .as_ref()
7773 .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7774 })
7775 .map(|index| parked.swap_remove(index));
7776 if let Some(driver) = &driver {
7781 driver.parker.grant_owner_handoff(owner);
7782 }
7783 self.count.store(parked.len(), Ordering::SeqCst);
7784 driver
7785 };
7786 if let Some(driver) = driver {
7787 #[cfg(test)]
7788 run_owner_handoff_selected_test_hook();
7789 driver.parker.unpark();
7790 return true;
7791 }
7792 }
7793 }
7794
7795 fn wake_all(&self) {
7798 if self.count.load(Ordering::SeqCst) == 0 {
7799 return;
7800 }
7801 let drivers = {
7802 let mut parked = self
7803 .parked
7804 .lock()
7805 .unwrap_or_else(std::sync::PoisonError::into_inner);
7806 let drivers = parked.drain(..).collect::<Vec<_>>();
7807 self.count.store(0, Ordering::SeqCst);
7808 drivers
7809 };
7810 for driver in drivers {
7811 driver.parker.unpark();
7812 }
7813 }
7814}
7815
7816pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7861 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7895
7896 fn is_live(&self) -> bool {
7897 true
7898 }
7899
7900 fn on_swept(&self) {}
7903}
7904
7905#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7906pub struct CurrentThreadTaskDriverId(u64);
7907
7908type CurrentThreadTaskDriverSnapshot =
7909 ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7910
7911#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7912pub struct CurrentThreadTaskDelivery {
7913 driver: CurrentThreadTaskDriverId,
7914 capability: u64,
7915 dispatch: u64,
7916}
7917
7918impl CurrentThreadTaskDelivery {
7919 pub fn capability(self) -> u64 {
7921 self.capability
7922 }
7923}
7924
7925struct CurrentThreadTaskDeliveryState {
7926 delivery: CurrentThreadTaskDelivery,
7927 claimed: bool,
7928 accepted: bool,
7929}
7930
7931struct CurrentThreadTaskDriverEntry {
7932 driver: Arc<dyn CurrentThreadTaskDriver>,
7933 delivery: Option<CurrentThreadTaskDeliveryState>,
7934 pending: Option<u64>,
7935 previous: Option<CurrentThreadTaskDriverId>,
7936 next: Option<CurrentThreadTaskDriverId>,
7937}
7938
7939#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7940enum CurrentThreadDispatchPhase {
7941 #[default]
7942 Pending,
7943 Serviced,
7944 Cancelling,
7945}
7946
7947#[derive(Default)]
7948struct CurrentThreadDispatchState {
7949 phase: CurrentThreadDispatchPhase,
7950 epoch: u64,
7951 publications: usize,
7952 references: usize,
7953 accepted: bool,
7954 accepted_failure: bool,
7955}
7956
7957impl CurrentThreadDispatchState {
7958 fn is_serviced(&self) -> bool {
7959 self.phase == CurrentThreadDispatchPhase::Serviced
7960 }
7961
7962 fn is_cancelling(&self) -> bool {
7963 self.phase == CurrentThreadDispatchPhase::Cancelling
7964 }
7965
7966 fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7967 CurrentThreadTaskDispatchFailure {
7968 dispatch,
7969 epoch: self.epoch,
7970 }
7971 }
7972}
7973
7974#[derive(Default)]
7975struct CurrentThreadTaskDriverRegistryState {
7976 entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7977 oldest: Option<CurrentThreadTaskDriverId>,
7978 newest: Option<CurrentThreadTaskDriverId>,
7979 delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7980 dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7981}
7982
7983impl CurrentThreadTaskDriverRegistryState {
7984 fn insert_driver(
7985 &mut self,
7986 id: CurrentThreadTaskDriverId,
7987 driver: Arc<dyn CurrentThreadTaskDriver>,
7988 ) {
7989 let previous = self.newest;
7990 let replaced = self.entries.insert(
7991 id,
7992 CurrentThreadTaskDriverEntry {
7993 driver,
7994 delivery: None,
7995 pending: None,
7996 previous,
7997 next: None,
7998 },
7999 );
8000 assert!(
8001 replaced.is_none(),
8002 "CurrentThread task driver id must be unique"
8003 );
8004 if let Some(previous) = previous {
8005 self
8006 .entries
8007 .get_mut(&previous)
8008 .expect("the newest CurrentThread task driver must remain registered")
8009 .next = Some(id);
8010 } else {
8011 self.oldest = Some(id);
8012 }
8013 self.newest = Some(id);
8014 }
8015
8016 fn remove_driver(
8017 &mut self,
8018 id: CurrentThreadTaskDriverId,
8019 ) -> Option<CurrentThreadTaskDriverEntry> {
8020 let entry = self.entries.remove(&id)?;
8021 if let Some(previous) = entry.previous {
8022 self
8023 .entries
8024 .get_mut(&previous)
8025 .expect("the previous CurrentThread task driver must remain registered")
8026 .next = entry.next;
8027 } else {
8028 self.oldest = entry.next;
8029 }
8030 if let Some(next) = entry.next {
8031 self
8032 .entries
8033 .get_mut(&next)
8034 .expect("the next CurrentThread task driver must remain registered")
8035 .previous = entry.previous;
8036 } else {
8037 self.newest = entry.previous;
8038 }
8039 if let Some(delivery) = &entry.delivery {
8040 let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
8041 debug_assert_eq!(
8042 indexed,
8043 Some(id),
8044 "delivery capability index must match its driver"
8045 );
8046 }
8047 Some(entry)
8048 }
8049
8050 fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8051 let mut snapshot = Vec::with_capacity(self.entries.len());
8052 let mut current = self.oldest;
8053 while let Some(id) = current {
8054 let entry = self
8055 .entries
8056 .get(&id)
8057 .expect("CurrentThread task driver order must reference a live entry");
8058 snapshot.push((id, Arc::clone(&entry.driver)));
8059 current = entry.next;
8060 }
8061 debug_assert_eq!(
8062 snapshot.len(),
8063 self.entries.len(),
8064 "CurrentThread task driver order must contain every entry exactly once"
8065 );
8066 snapshot
8067 }
8068
8069 fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
8070 if self.entries.len() != expected.len() {
8071 return false;
8072 }
8073 let mut current = self.oldest;
8074 for expected in expected {
8075 if current != Some(*expected) {
8076 return false;
8077 }
8078 current = self
8079 .entries
8080 .get(expected)
8081 .expect("CurrentThread task driver order must reference a live entry")
8082 .next;
8083 }
8084 current.is_none()
8085 }
8086}
8087
8088struct CurrentThreadTaskDriverSelection {
8089 live: CurrentThreadTaskDriverSnapshot,
8090 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8091}
8092
8093#[derive(Default)]
8094struct CurrentThreadTaskDeliveryCompletion {
8095 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8096 redispatch: bool,
8097}
8098
8099struct CurrentThreadTaskDispatchCompletion {
8100 result: CurrentThreadHostDispatchResult,
8101 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8102}
8103
8104struct CurrentThreadTaskDispatchPublicationGuard<'a> {
8105 registry: &'a CurrentThreadTaskDriverRegistry,
8106 dispatch: Option<u64>,
8107}
8108
8109impl CurrentThreadTaskDispatchPublicationGuard<'_> {
8110 fn finish(mut self) -> CurrentThreadHostDispatchResult {
8111 let dispatch = self
8112 .dispatch
8113 .expect("the CurrentThread task dispatch publication must remain armed");
8114 let result = self.registry.finish_publication(dispatch);
8115 self.dispatch = None;
8116 result
8117 }
8118}
8119
8120impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
8121 fn drop(&mut self) {
8122 if let Some(dispatch) = self.dispatch.take() {
8123 self.registry.abort_publication(dispatch);
8124 }
8125 }
8126}
8127
8128#[derive(Default)]
8129struct CurrentThreadTaskDriverRegistry {
8130 state: Mutex<CurrentThreadTaskDriverRegistryState>,
8131 next_id: AtomicU64,
8132 next_delivery: AtomicU64,
8133}
8134
8135impl CurrentThreadTaskDriverRegistry {
8136 fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8137 let id = next_unique_id(
8138 &self.next_id,
8139 "CurrentThread task driver id space exhausted",
8140 );
8141 let id = CurrentThreadTaskDriverId(id);
8142 self
8143 .state
8144 .lock()
8145 .unwrap_or_else(std::sync::PoisonError::into_inner)
8146 .insert_driver(id, driver);
8147 id
8148 }
8149
8150 fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8151 let (removed, failed_dispatches) = {
8152 let mut state = self
8153 .state
8154 .lock()
8155 .unwrap_or_else(std::sync::PoisonError::into_inner);
8156 let removed = state.remove_driver(id);
8157 let mut failed_dispatches = Vec::new();
8158 if let Some(entry) = &removed {
8159 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8160 }
8161 (removed, failed_dispatches)
8162 };
8163 let _ = catch_unwind_contained(|| drop(removed));
8164 CurrentThreadTaskDeliveryCompletion {
8165 failed_dispatches,
8166 redispatch: false,
8167 }
8168 }
8169
8170 fn live(&self) -> CurrentThreadTaskDriverSelection {
8171 let mut failed_dispatches = Vec::new();
8172 loop {
8173 let snapshot = CurrentThreadTaskDriverSnapshot::new({
8174 let state = self
8175 .state
8176 .lock()
8177 .unwrap_or_else(std::sync::PoisonError::into_inner);
8178 state.ordered_snapshot()
8179 });
8180 let mut dead_ids = FxHashSet::default();
8181 let mut live_ids = Vec::new();
8182 for (id, driver) in snapshot.as_slice() {
8183 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8184 live_ids.push(*id);
8185 } else {
8186 dead_ids.insert(*id);
8187 }
8188 }
8189
8190 let (live, swept, retry) = {
8191 let mut state = self
8192 .state
8193 .lock()
8194 .unwrap_or_else(std::sync::PoisonError::into_inner);
8195 let mut swept = Vec::new();
8196 let mut current = state.oldest;
8197 while let Some(id) = current {
8198 let next = state
8199 .entries
8200 .get(&id)
8201 .expect("CurrentThread task driver order must reference a live entry")
8202 .next;
8203 if dead_ids.contains(&id)
8204 && let Some(entry) = state.remove_driver(id)
8205 {
8206 swept.push(entry);
8207 }
8208 current = next;
8209 }
8210 for entry in &swept {
8211 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8212 }
8213 let retry = !state.ordered_ids_match(&live_ids);
8214 let live = if retry {
8215 Vec::new()
8216 } else {
8217 state.ordered_snapshot()
8218 };
8219 (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8220 };
8221
8222 drop(snapshot);
8225 for entry in swept {
8226 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8227 let _ = catch_unwind_contained(|| drop(entry));
8228 }
8229 if !retry {
8230 return CurrentThreadTaskDriverSelection {
8231 live,
8232 failed_dispatches,
8233 };
8234 }
8235 }
8236 }
8237
8238 #[cfg(test)]
8239 fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8240 let mut live = self.live().live;
8241 live.pop().map(|(id, driver)| (id, driver.into_inner()))
8242 }
8243
8244 fn next_delivery(
8245 &self,
8246 driver: CurrentThreadTaskDriverId,
8247 dispatch: u64,
8248 ) -> CurrentThreadTaskDelivery {
8249 let capability = next_unique_id(
8250 &self.next_delivery,
8251 "CurrentThread task delivery id space exhausted",
8252 )
8253 .checked_add(1)
8254 .expect("CurrentThread task delivery id space exhausted");
8255 CurrentThreadTaskDelivery {
8256 driver,
8257 capability,
8258 dispatch,
8259 }
8260 }
8261
8262 fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8263 let dispatch_state = state
8264 .dispatches
8265 .get_mut(&dispatch)
8266 .expect("a referenced CurrentThread dispatch must remain registered");
8267 dispatch_state.references = dispatch_state
8268 .references
8269 .checked_add(1)
8270 .expect("CurrentThread dispatch reference count exhausted");
8271 }
8272
8273 fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8274 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8275 return;
8276 };
8277 dispatch_state.references = dispatch_state
8278 .references
8279 .checked_sub(1)
8280 .expect("CurrentThread dispatch reference count underflow");
8281 }
8282
8283 fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8284 let publication = {
8285 let mut state = self
8286 .state
8287 .lock()
8288 .unwrap_or_else(std::sync::PoisonError::into_inner);
8289 let dispatch_state = state.dispatches.entry(dispatch).or_default();
8290 if dispatch_state.is_serviced() {
8291 return CurrentThreadTaskDispatchCompletion {
8292 result: CurrentThreadHostDispatchResult::Accepted,
8293 failed_dispatches: Vec::new(),
8294 };
8295 }
8296 if dispatch_state.is_cancelling() {
8297 return CurrentThreadTaskDispatchCompletion {
8298 result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8299 failed_dispatches: Vec::new(),
8300 };
8301 }
8302 dispatch_state.publications = dispatch_state
8303 .publications
8304 .checked_add(1)
8305 .expect("CurrentThread dispatch publication count exhausted");
8306 CurrentThreadTaskDispatchPublicationGuard {
8307 registry: self,
8308 dispatch: Some(dispatch),
8309 }
8310 };
8311
8312 let CurrentThreadTaskDriverSelection {
8313 mut live,
8314 mut failed_dispatches,
8315 } = self.live();
8316 let planned_deliveries = {
8317 let mut state = self
8318 .state
8319 .lock()
8320 .unwrap_or_else(std::sync::PoisonError::into_inner);
8321 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8322 return CurrentThreadTaskDispatchCompletion {
8323 result: CurrentThreadHostDispatchResult::Unavailable,
8324 failed_dispatches,
8325 };
8326 };
8327 if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8328 Vec::new()
8329 } else {
8330 let mut reserved_deliveries = Vec::with_capacity(live.len());
8334 for (id, _) in live.as_slice().iter().rev() {
8335 let Some(entry) = state.entries.get(id) else {
8336 continue;
8337 };
8338 if entry.delivery.is_none() {
8339 reserved_deliveries.push(self.next_delivery(*id, dispatch));
8340 }
8341 }
8342 state.delivery_drivers.reserve(reserved_deliveries.len());
8343 let mut reserved_deliveries = reserved_deliveries.into_iter();
8344 let mut planned_deliveries = Vec::with_capacity(live.len());
8345 let mut retired = Vec::with_capacity(live.len());
8346 let mut accepted_reservation = false;
8347 for (id, _) in live.as_slice().iter().rev() {
8348 let Some(entry) = state.entries.get(id) else {
8349 continue;
8350 };
8351 if let Some(delivery) = &entry.delivery
8352 && delivery.delivery.dispatch == dispatch
8353 {
8354 accepted_reservation |= delivery.accepted;
8355 continue;
8356 }
8357 if entry.delivery.is_some() {
8358 let previous = state
8359 .entries
8360 .get_mut(id)
8361 .expect("the CurrentThread task driver must remain registered")
8362 .pending
8363 .replace(dispatch);
8364 if previous != Some(dispatch) {
8365 if let Some(previous) = previous {
8366 Self::remove_dispatch_reference(&mut state, previous);
8367 }
8368 Self::add_dispatch_reference(&mut state, dispatch);
8369 }
8370 retired.push(previous);
8371 accepted_reservation = true;
8372 continue;
8373 }
8374 let delivery = reserved_deliveries
8375 .next()
8376 .expect("every available CurrentThread host must have a reserved delivery");
8377 let pending = state
8378 .entries
8379 .get_mut(id)
8380 .expect("the CurrentThread task driver must remain registered")
8381 .pending
8382 .take();
8383 let accepted = pending == Some(dispatch);
8384 if !accepted && pending.is_some() {
8385 retired.push(pending);
8386 }
8387 match pending {
8388 Some(pending) if pending == dispatch => {}
8389 Some(pending) => {
8390 Self::remove_dispatch_reference(&mut state, pending);
8391 Self::add_dispatch_reference(&mut state, dispatch);
8392 }
8393 None => Self::add_dispatch_reference(&mut state, dispatch),
8394 }
8395 let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8396 assert!(
8397 indexed.is_none(),
8398 "CurrentThread delivery capability must be unique"
8399 );
8400 state
8401 .entries
8402 .get_mut(id)
8403 .expect("the CurrentThread task driver must remain registered")
8404 .delivery = Some(CurrentThreadTaskDeliveryState {
8405 delivery,
8406 claimed: false,
8407 accepted,
8408 });
8409 accepted_reservation |= accepted;
8410 planned_deliveries.push((*id, delivery));
8411 }
8412 let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8413 debug_assert!(
8414 all_reserved_deliveries_installed,
8415 "every reserved CurrentThread delivery must be installed"
8416 );
8417 if accepted_reservation {
8418 state
8419 .dispatches
8420 .get_mut(&dispatch)
8421 .expect("the publishing dispatch state must remain registered")
8422 .accepted = true;
8423 }
8424 for dispatch in retired.into_iter().flatten() {
8425 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8426 }
8427 planned_deliveries
8428 }
8429 };
8430
8431 let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8436 let mut planned_deliveries = planned_deliveries.into_iter();
8437 let mut next_delivery = planned_deliveries.next();
8438 while let Some((id, driver)) = live.pop() {
8439 let Some((planned_id, delivery)) = next_delivery else {
8440 break;
8441 };
8442 if planned_id == id {
8443 deliveries.push((id, driver, delivery));
8444 next_delivery = planned_deliveries.next();
8445 } else {
8446 next_delivery = Some((planned_id, delivery));
8447 }
8448 }
8449 debug_assert!(
8450 next_delivery.is_none() && planned_deliveries.len() == 0,
8451 "every planned CurrentThread delivery must retain its selected driver"
8452 );
8453
8454 for (id, driver, delivery) in deliveries {
8455 if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8456 self.accept_delivery(id, delivery);
8457 } else {
8458 let (entry, failures) = self.reject_delivery(id, delivery);
8459 failed_dispatches.extend(failures);
8460 if let Some(entry) = entry {
8461 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8462 let _ = catch_unwind_contained(|| drop(entry));
8463 }
8464 }
8465 }
8466
8467 let result = publication.finish();
8468 debug_assert!(
8469 !failed_dispatches
8470 .iter()
8471 .any(|failure| failure.dispatch == dispatch),
8472 "the active publication must report its own terminal outcome directly"
8473 );
8474 CurrentThreadTaskDispatchCompletion {
8475 result,
8476 failed_dispatches,
8477 }
8478 }
8479
8480 #[cfg(test)]
8481 fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8482 self.dispatch_with_completion(dispatch).result
8483 }
8484
8485 fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8486 let mut state = self
8487 .state
8488 .lock()
8489 .unwrap_or_else(std::sync::PoisonError::into_inner);
8490 let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8491 entry
8492 .delivery
8493 .as_ref()
8494 .is_some_and(|current| current.delivery == delivery)
8495 }) {
8496 state
8497 .entries
8498 .get_mut(&id)
8499 .expect("the matching CurrentThread task driver must remain registered")
8500 .delivery
8501 .as_mut()
8502 .expect("the matching delivery must remain installed")
8503 .accepted = true;
8504 true
8505 } else {
8506 false
8511 };
8512 if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8513 dispatch_state.accepted = true;
8514 dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8515 }
8516 }
8517
8518 fn reject_delivery(
8519 &self,
8520 id: CurrentThreadTaskDriverId,
8521 delivery: CurrentThreadTaskDelivery,
8522 ) -> (
8523 Option<CurrentThreadTaskDriverEntry>,
8524 Vec<CurrentThreadTaskDispatchFailure>,
8525 ) {
8526 let mut state = self
8527 .state
8528 .lock()
8529 .unwrap_or_else(std::sync::PoisonError::into_inner);
8530 if !state.entries.get(&id).is_some_and(|entry| {
8531 entry
8532 .delivery
8533 .as_ref()
8534 .is_some_and(|current| current.delivery == delivery)
8535 }) {
8536 return (None, Vec::new());
8537 }
8538 let entry = state
8539 .remove_driver(id)
8540 .expect("the matching CurrentThread task driver must remain registered");
8541 let mut failures = Vec::new();
8542 Self::finish_entry_references(&mut state, &entry, &mut failures);
8543 (Some(entry), failures)
8544 }
8545
8546 fn finish_delivery(
8547 &self,
8548 delivery: CurrentThreadTaskDelivery,
8549 failed: bool,
8550 ) -> CurrentThreadTaskDeliveryCompletion {
8551 let mut state = self
8552 .state
8553 .lock()
8554 .unwrap_or_else(std::sync::PoisonError::into_inner);
8555 if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8556 entry
8557 .delivery
8558 .as_ref()
8559 .is_some_and(|current| current.delivery == delivery)
8560 }) {
8561 return CurrentThreadTaskDeliveryCompletion::default();
8562 }
8563 let current = state
8564 .entries
8565 .get_mut(&delivery.driver)
8566 .expect("the matching CurrentThread task driver must remain registered")
8567 .delivery
8568 .take()
8569 .expect("the matching delivery must remain installed");
8570 let indexed = state.delivery_drivers.remove(&delivery.capability);
8571 debug_assert_eq!(
8572 indexed,
8573 Some(delivery.driver),
8574 "delivery capability index must match its driver"
8575 );
8576 let redispatch = state
8577 .entries
8578 .get(&delivery.driver)
8579 .expect("the matching CurrentThread task driver must remain registered")
8580 .pending
8581 .is_some_and(|dispatch| {
8582 state
8583 .dispatches
8584 .get(&dispatch)
8585 .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8586 });
8587 let mut failed_dispatches = Vec::new();
8588 Self::finish_dispatch_reference(
8593 &mut state,
8594 delivery.dispatch,
8595 true,
8596 failed || !current.claimed,
8597 &mut failed_dispatches,
8598 );
8599 CurrentThreadTaskDeliveryCompletion {
8600 failed_dispatches,
8601 redispatch,
8602 }
8603 }
8604
8605 fn claim_delivery(&self, capability: u64) -> Option<u64> {
8606 let mut state = self
8607 .state
8608 .lock()
8609 .unwrap_or_else(std::sync::PoisonError::into_inner);
8610 let driver = *state.delivery_drivers.get(&capability)?;
8611 let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8612 if delivery.capability != capability {
8613 return None;
8614 }
8615 let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8616 if dispatch_state.is_serviced()
8617 || dispatch_state.is_cancelling()
8618 || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8619 {
8620 return None;
8621 }
8622 let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8623 current.claimed = true;
8624 current.accepted = true;
8625 state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8626 Some(delivery.dispatch)
8627 }
8628
8629 fn mark_serviced(&self, dispatch: u64) {
8630 let mut state = self
8631 .state
8632 .lock()
8633 .unwrap_or_else(std::sync::PoisonError::into_inner);
8634 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8635 return;
8636 };
8637 dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8638 let mut removed_references = 0usize;
8639 for entry in state.entries.values_mut() {
8640 if entry.pending == Some(dispatch) {
8641 entry.pending = None;
8642 removed_references = removed_references
8643 .checked_add(1)
8644 .expect("CurrentThread dispatch reference count exhausted");
8645 }
8646 }
8647 let dispatch_state = state
8648 .dispatches
8649 .get_mut(&dispatch)
8650 .expect("the serviced CurrentThread dispatch must remain registered");
8651 dispatch_state.references = dispatch_state
8652 .references
8653 .checked_sub(removed_references)
8654 .expect("CurrentThread dispatch reference count underflow");
8655 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8656 }
8657
8658 fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8659 let delivery = {
8660 let state = self
8661 .state
8662 .lock()
8663 .unwrap_or_else(std::sync::PoisonError::into_inner);
8664 state
8665 .delivery_drivers
8666 .get(&capability)
8667 .and_then(|driver| state.entries.get(driver))
8668 .and_then(|entry| entry.delivery.as_ref())
8669 .map(|delivery| delivery.delivery)
8670 .filter(|delivery| delivery.capability == capability)
8671 };
8672 delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8673 self.finish_delivery(delivery, true)
8674 })
8675 }
8676
8677 fn finish_dispatch_reference(
8678 state: &mut CurrentThreadTaskDriverRegistryState,
8679 dispatch: u64,
8680 accepted: bool,
8681 failed: bool,
8682 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8683 ) {
8684 let (publications, references) = {
8685 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8686 return;
8687 };
8688 dispatch_state.references = dispatch_state
8689 .references
8690 .checked_sub(1)
8691 .expect("CurrentThread dispatch reference count underflow");
8692 dispatch_state.accepted |= accepted;
8693 dispatch_state.accepted_failure |= accepted && failed;
8694 (dispatch_state.publications, dispatch_state.references)
8695 };
8696 if references != 0 || publications != 0 {
8697 return;
8698 }
8699 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8700 return;
8701 };
8702 if dispatch_state.accepted_failure
8703 && !dispatch_state.is_serviced()
8704 && !dispatch_state.is_cancelling()
8705 {
8706 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8707 failures.push(dispatch_state.failure(dispatch));
8708 } else if !dispatch_state.is_cancelling() {
8709 state.dispatches.remove(&dispatch);
8710 }
8711 }
8712
8713 fn finish_entry_references(
8714 state: &mut CurrentThreadTaskDriverRegistryState,
8715 entry: &CurrentThreadTaskDriverEntry,
8716 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8717 ) {
8718 if let Some(delivery) = &entry.delivery {
8719 Self::finish_dispatch_reference(
8720 state,
8721 delivery.delivery.dispatch,
8722 delivery.accepted,
8723 delivery.accepted,
8724 failures,
8725 );
8726 }
8727 if let Some(pending) = entry.pending {
8728 Self::finish_dispatch_reference(state, pending, true, true, failures);
8729 }
8730 }
8731
8732 fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8733 let mut state = self
8734 .state
8735 .lock()
8736 .unwrap_or_else(std::sync::PoisonError::into_inner);
8737 let publications = {
8738 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8739 return CurrentThreadHostDispatchResult::Unavailable;
8740 };
8741 dispatch_state.publications = dispatch_state
8742 .publications
8743 .checked_sub(1)
8744 .expect("CurrentThread dispatch publication count underflow");
8745 dispatch_state.publications
8746 };
8747 if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8748 return CurrentThreadHostDispatchResult::Accepted;
8749 }
8750 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8751 return CurrentThreadHostDispatchResult::Unavailable;
8752 };
8753 if dispatch_state.is_cancelling() {
8754 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8755 }
8756 if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8757 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8758 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8759 }
8760 let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8761 state.dispatches.remove(&dispatch);
8762 if accepted {
8763 CurrentThreadHostDispatchResult::Accepted
8764 } else {
8765 CurrentThreadHostDispatchResult::Unavailable
8766 }
8767 }
8768
8769 fn abort_publication(&self, dispatch: u64) {
8770 let mut state = self
8771 .state
8772 .lock()
8773 .unwrap_or_else(std::sync::PoisonError::into_inner);
8774 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8775 return;
8776 };
8777 let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8778 return;
8779 };
8780 dispatch_state.publications = publications;
8781 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8782 }
8783
8784 fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8785 let mut state = self
8786 .state
8787 .lock()
8788 .unwrap_or_else(std::sync::PoisonError::into_inner);
8789 let can_rearm = state
8790 .dispatches
8791 .get(&failure.dispatch)
8792 .is_some_and(|dispatch_state| {
8793 dispatch_state.is_cancelling()
8794 && dispatch_state.epoch == failure.epoch
8795 && dispatch_state.publications == 0
8796 })
8797 && !Self::has_dispatch_reference(&state, failure.dispatch);
8798 if !can_rearm {
8799 return false;
8800 }
8801 let dispatch_state = state
8802 .dispatches
8803 .get_mut(&failure.dispatch)
8804 .expect("the exact failed CurrentThread dispatch must remain registered");
8805 let epoch = dispatch_state
8806 .epoch
8807 .checked_add(1)
8808 .expect("CurrentThread dispatch failure epoch space exhausted");
8809 *dispatch_state = CurrentThreadDispatchState {
8810 epoch,
8811 ..CurrentThreadDispatchState::default()
8812 };
8813 true
8814 }
8815
8816 fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8817 self
8818 .state
8819 .lock()
8820 .unwrap_or_else(std::sync::PoisonError::into_inner)
8821 .dispatches
8822 .get(&failure.dispatch)
8823 .is_some_and(|dispatch_state| {
8824 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8825 })
8826 }
8827
8828 fn retire_unreferenced_dispatch(
8829 state: &mut CurrentThreadTaskDriverRegistryState,
8830 dispatch: Option<u64>,
8831 ) {
8832 let Some(dispatch) = dispatch else {
8833 return;
8834 };
8835 if Self::has_dispatch_reference(state, dispatch) {
8836 return;
8837 }
8838 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8839 return;
8840 };
8841 if dispatch_state.publications == 0
8842 && !dispatch_state.is_cancelling()
8843 && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8844 {
8845 state.dispatches.remove(&dispatch);
8846 }
8847 }
8848
8849 fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8850 state
8851 .dispatches
8852 .get(&dispatch)
8853 .is_some_and(|dispatch_state| dispatch_state.references != 0)
8854 }
8855
8856 fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8857 let mut state = self
8858 .state
8859 .lock()
8860 .unwrap_or_else(std::sync::PoisonError::into_inner);
8861 if state
8862 .dispatches
8863 .get(&failure.dispatch)
8864 .is_some_and(|dispatch_state| {
8865 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8866 })
8867 {
8868 state.dispatches.remove(&failure.dispatch);
8869 }
8870 }
8871
8872 fn retire_for_shutdown(&self) {
8873 let mut state = self
8874 .state
8875 .lock()
8876 .unwrap_or_else(std::sync::PoisonError::into_inner);
8877 state.dispatches.clear();
8878 for entry in state.entries.values_mut() {
8879 entry.pending = None;
8880 }
8881 }
8882}
8883
8884static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8885 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8886
8887pub fn register_current_thread_task_driver(
8888 driver: Arc<dyn CurrentThreadTaskDriver>,
8889) -> CurrentThreadTaskDriverId {
8890 CURRENT_THREAD_TASK_DRIVERS.register(driver)
8891}
8892
8893pub fn request_current_thread_task_drain() {
8898 RUNTIME.request_current_thread_drain();
8899}
8900
8901pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8902 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8903}
8904
8905fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8906 let CurrentThreadTaskDispatchCompletion {
8907 result,
8908 failed_dispatches,
8909 } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8910 complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8911 failed_dispatches,
8912 redispatch: false,
8913 });
8914 result
8915}
8916
8917pub type TimerId = u64;
8918
8919static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8921
8922pub trait TimerDriver: Send + Sync + 'static {
8928 fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8932 fn cancel(&self, id: TimerId);
8935 fn is_live(&self) -> bool {
8940 true
8941 }
8942 fn on_swept(&self) {}
8952}
8953
8954#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8958pub struct TimerDriverId(u64);
8959
8960type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8961
8962struct TimerDriverEntry {
8963 driver: Arc<dyn TimerDriver>,
8964 previous: Option<TimerDriverId>,
8965 next: Option<TimerDriverId>,
8966}
8967
8968#[derive(Default)]
8969struct TimerDriverRegistryState {
8970 entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8971 oldest: Option<TimerDriverId>,
8972 newest: Option<TimerDriverId>,
8973}
8974
8975impl TimerDriverRegistryState {
8976 fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8977 let previous = self.newest;
8978 let replaced = self.entries.insert(
8979 id,
8980 TimerDriverEntry {
8981 driver,
8982 previous,
8983 next: None,
8984 },
8985 );
8986 assert!(replaced.is_none(), "timer driver id must be unique");
8987 if let Some(previous) = previous {
8988 self
8989 .entries
8990 .get_mut(&previous)
8991 .expect("the newest timer driver must remain registered")
8992 .next = Some(id);
8993 } else {
8994 self.oldest = Some(id);
8995 }
8996 self.newest = Some(id);
8997 }
8998
8999 fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
9000 let entry = self.entries.remove(&id)?;
9001 if let Some(previous) = entry.previous {
9002 self
9003 .entries
9004 .get_mut(&previous)
9005 .expect("the previous timer driver must remain registered")
9006 .next = entry.next;
9007 } else {
9008 self.oldest = entry.next;
9009 }
9010 if let Some(next) = entry.next {
9011 self
9012 .entries
9013 .get_mut(&next)
9014 .expect("the next timer driver must remain registered")
9015 .previous = entry.previous;
9016 } else {
9017 self.newest = entry.previous;
9018 }
9019 Some(entry)
9020 }
9021
9022 fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
9023 let mut snapshot = Vec::with_capacity(self.entries.len());
9024 let mut current = self.oldest;
9025 while let Some(id) = current {
9026 let entry = self
9027 .entries
9028 .get(&id)
9029 .expect("timer driver order must reference a live entry");
9030 snapshot.push((id, Arc::clone(&entry.driver)));
9031 current = entry.next;
9032 }
9033 debug_assert_eq!(
9034 snapshot.len(),
9035 self.entries.len(),
9036 "timer driver order must contain every entry exactly once"
9037 );
9038 snapshot
9039 }
9040
9041 fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
9042 if self.entries.len() != expected.len() {
9043 return false;
9044 }
9045 let mut current = self.oldest;
9046 for expected in expected {
9047 if current != Some(*expected) {
9048 return false;
9049 }
9050 current = self
9051 .entries
9052 .get(expected)
9053 .expect("timer driver order must reference a live entry")
9054 .next;
9055 }
9056 current.is_none()
9057 }
9058}
9059
9060#[derive(Default)]
9078pub struct TimerDriverRegistry {
9079 state: Mutex<TimerDriverRegistryState>,
9083 pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
9088 next_id: AtomicU64,
9089}
9090
9091impl Drop for TimerDriverRegistry {
9092 fn drop(&mut self) {
9093 let state = self
9094 .state
9095 .get_mut()
9096 .unwrap_or_else(std::sync::PoisonError::into_inner);
9097 state.oldest = None;
9098 state.newest = None;
9099 let entries = std::mem::take(&mut state.entries);
9100 for (_, entry) in entries {
9101 let _ = catch_unwind_contained(|| drop(entry));
9102 }
9103 }
9104}
9105
9106impl TimerDriverRegistry {
9107 pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9109 let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
9110 let id = TimerDriverId(id);
9111 self
9112 .state
9113 .lock()
9114 .unwrap_or_else(std::sync::PoisonError::into_inner)
9115 .insert_driver(id, driver);
9116 let pending = {
9121 let mut entries = self
9122 .pending
9123 .lock()
9124 .unwrap_or_else(std::sync::PoisonError::into_inner);
9125 let mut pending = Vec::with_capacity(entries.len());
9126 entries.retain(|_, repoll| {
9127 if let Some(repoll) = repoll.upgrade() {
9128 pending.push(repoll);
9129 true
9130 } else {
9131 false
9132 }
9133 });
9134 pending
9135 };
9136 for repoll in pending {
9137 repoll.wake();
9138 }
9139 id
9140 }
9141
9142 pub fn unregister(&self, id: TimerDriverId) {
9144 let removed = self
9145 .state
9146 .lock()
9147 .unwrap_or_else(std::sync::PoisonError::into_inner)
9148 .remove_driver(id);
9149 let _ = catch_unwind_contained(|| drop(removed));
9152 }
9153
9154 fn live(&self) -> HostTimerDriverSnapshot {
9165 loop {
9166 let snapshot = HostTimerDriverSnapshot::new({
9167 let state = self
9168 .state
9169 .lock()
9170 .unwrap_or_else(std::sync::PoisonError::into_inner);
9171 state.ordered_snapshot()
9172 });
9173 let mut dead_ids = FxHashSet::default();
9174 let mut live_ids = Vec::new();
9175 for (id, driver) in snapshot.as_slice() {
9176 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9177 live_ids.push(*id);
9178 } else {
9179 dead_ids.insert(*id);
9180 }
9181 }
9182
9183 let (live, swept, retry) = {
9184 let mut state = self
9185 .state
9186 .lock()
9187 .unwrap_or_else(std::sync::PoisonError::into_inner);
9188 let mut swept = Vec::new();
9189 let mut current = state.oldest;
9190 while let Some(id) = current {
9191 let next = state
9192 .entries
9193 .get(&id)
9194 .expect("timer driver order must reference a live entry")
9195 .next;
9196 if dead_ids.contains(&id)
9197 && let Some(entry) = state.remove_driver(id)
9198 {
9199 swept.push(entry);
9200 }
9201 current = next;
9202 }
9203 let retry = !state.ordered_ids_match(&live_ids);
9204 let live = if retry {
9205 Vec::new()
9206 } else {
9207 state.ordered_snapshot()
9208 };
9209 (HostTimerDriverSnapshot::new(live), swept, retry)
9210 };
9211
9212 drop(snapshot);
9215 for entry in swept {
9216 let _ = catch_unwind_contained(|| entry.driver.on_swept());
9217 let _ = catch_unwind_contained(|| drop(entry));
9218 }
9219 if !retry {
9220 return live;
9221 }
9222 }
9223 }
9224
9225 #[cfg(test)]
9226 fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9227 let mut live = self.live();
9228 live.pop().map(|(id, driver)| (id, driver.into_inner()))
9229 }
9230
9231 pub fn has_live_driver(&self) -> bool {
9233 !self.live().is_empty()
9234 }
9235
9236 fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9237 let replaced = self
9238 .pending
9239 .lock()
9240 .unwrap_or_else(std::sync::PoisonError::into_inner)
9241 .insert(id, Arc::downgrade(repoll));
9242 assert!(
9243 replaced.is_none(),
9244 "host timer {id} registered its repoll signal twice"
9245 );
9246 if repoll.is_closed() {
9249 self.remove_pending(id);
9250 }
9251 }
9252
9253 fn remove_pending(&self, id: TimerId) {
9254 let removed = self
9255 .pending
9256 .lock()
9257 .unwrap_or_else(std::sync::PoisonError::into_inner)
9258 .remove(&id);
9259 let _ = catch_unwind_contained(|| drop(removed));
9260 }
9261
9262 #[cfg(test)]
9263 fn pending_len(&self) -> usize {
9264 self
9265 .pending
9266 .lock()
9267 .unwrap_or_else(std::sync::PoisonError::into_inner)
9268 .len()
9269 }
9270}
9271
9272static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9275 LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9276
9277pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9283 TIMER_DRIVERS.register(driver)
9284}
9285
9286pub fn unregister_timer_driver(id: TimerDriverId) {
9290 TIMER_DRIVERS.unregister(id);
9291}
9292
9293pub fn has_live_timer_driver() -> bool {
9300 TIMER_DRIVERS.has_live_driver()
9301}
9302
9303#[derive(Default)]
9307struct HostTimerRepoll {
9308 state: Mutex<HostTimerRepollState>,
9309}
9310
9311#[derive(Default)]
9312struct HostTimerRepollState {
9313 closed: bool,
9314 notified: bool,
9315 waker: Option<Waker>,
9316}
9317
9318impl HostTimerRepoll {
9319 fn register(&self, waker: Waker) {
9320 let (replaced, wake) = {
9321 let mut state = self
9322 .state
9323 .lock()
9324 .unwrap_or_else(std::sync::PoisonError::into_inner);
9325 if state.closed {
9326 (Some(waker), None)
9327 } else if state.notified {
9328 state.notified = false;
9329 (state.waker.take(), Some(waker))
9330 } else {
9331 (state.waker.replace(waker), None)
9332 }
9333 };
9334 let _ = catch_unwind_contained(|| drop(replaced));
9335 if let Some(waker) = wake {
9336 wake_waker_contained(waker);
9337 }
9338 }
9339
9340 fn wake(&self) {
9341 let waker = {
9342 let mut state = self
9343 .state
9344 .lock()
9345 .unwrap_or_else(std::sync::PoisonError::into_inner);
9346 if state.closed {
9347 return;
9348 }
9349 if let Some(waker) = state.waker.take() {
9350 Some(waker)
9351 } else {
9352 state.notified = true;
9353 None
9354 }
9355 };
9356 if let Some(waker) = waker {
9357 wake_waker_contained(waker);
9358 }
9359 }
9360
9361 fn close(&self) {
9362 let waker = {
9363 let mut state = self
9364 .state
9365 .lock()
9366 .unwrap_or_else(std::sync::PoisonError::into_inner);
9367 state.closed = true;
9368 state.notified = false;
9369 state.waker.take()
9370 };
9371 let _ = catch_unwind_contained(|| drop(waker));
9372 }
9373
9374 fn is_closed(&self) -> bool {
9375 self
9376 .state
9377 .lock()
9378 .unwrap_or_else(std::sync::PoisonError::into_inner)
9379 .closed
9380 }
9381}
9382
9383#[derive(Default)]
9390struct HostTimerRegistry {
9391 state: Mutex<HostTimerRegistryState>,
9392}
9393
9394#[derive(Default)]
9395struct HostTimerRegistryState {
9396 closed: bool,
9397 timers: FxHashMap<TimerId, HostTimerEntry>,
9398}
9399
9400struct HostTimerEntry {
9401 deadline: Instant,
9402 waker: Waker,
9403 fired: Arc<AtomicBool>,
9404 drivers: Weak<TimerDriverRegistry>,
9405 repoll: Arc<HostTimerRepoll>,
9406}
9407
9408impl HostTimerRegistry {
9409 fn register(
9410 &self,
9411 id: TimerId,
9412 deadline: Instant,
9413 waker: Waker,
9414 fired: &Arc<AtomicBool>,
9415 drivers: &Arc<TimerDriverRegistry>,
9416 repoll: &Arc<HostTimerRepoll>,
9417 ) -> bool {
9418 let replaced = {
9419 let mut state = self
9420 .state
9421 .lock()
9422 .unwrap_or_else(std::sync::PoisonError::into_inner);
9423 if state.closed || fired.load(Ordering::SeqCst) {
9424 fired.store(true, Ordering::SeqCst);
9425 Err(waker)
9426 } else {
9427 Ok(state.timers.insert(
9428 id,
9429 HostTimerEntry {
9430 deadline,
9431 waker,
9432 fired: Arc::clone(fired),
9433 drivers: Arc::downgrade(drivers),
9434 repoll: Arc::clone(repoll),
9435 },
9436 ))
9437 }
9438 };
9439 match replaced {
9440 Ok(Some(entry)) => {
9441 let _ = catch_unwind_contained(|| drop(entry));
9442 true
9443 }
9444 Ok(None) => true,
9445 Err(waker) => {
9446 let _ = catch_unwind_contained(|| drop(waker));
9447 false
9448 }
9449 }
9450 }
9451
9452 fn remove(&self, id: TimerId) {
9453 let removed = self
9454 .state
9455 .lock()
9456 .unwrap_or_else(std::sync::PoisonError::into_inner)
9457 .timers
9458 .remove(&id);
9459 if let Some(entry) = removed {
9460 let _ = catch_unwind_contained(|| drop(entry));
9461 }
9462 }
9463
9464 fn shutdown(&self) {
9465 let timers = {
9466 let mut state = self
9467 .state
9468 .lock()
9469 .unwrap_or_else(std::sync::PoisonError::into_inner);
9470 state.closed = true;
9471 state
9472 .timers
9473 .drain()
9474 .map(|(id, entry)| {
9475 entry.fired.store(true, Ordering::SeqCst);
9476 (id, entry)
9477 })
9478 .collect::<Vec<_>>()
9479 };
9480 for (id, entry) in timers {
9481 entry.repoll.close();
9482 if let Some(drivers) = entry.drivers.upgrade() {
9483 drivers.remove_pending(id);
9484 }
9485 wake_waker_contained(entry.waker);
9486 }
9487 }
9488
9489 fn has_pending(&self) -> bool {
9494 !self
9495 .state
9496 .lock()
9497 .unwrap_or_else(std::sync::PoisonError::into_inner)
9498 .timers
9499 .is_empty()
9500 }
9501
9502 fn has_live_wait(&self, grace: Duration) -> bool {
9511 let now = Instant::now();
9512 self
9513 .state
9514 .lock()
9515 .unwrap_or_else(std::sync::PoisonError::into_inner)
9516 .timers
9517 .values()
9518 .any(|entry| {
9519 entry
9520 .deadline
9521 .checked_add(grace)
9522 .is_none_or(|deadline| deadline > now)
9523 })
9524 }
9525}
9526
9527struct HostTimerPendingGuard {
9531 registry: Arc<HostTimerRegistry>,
9532 drivers: Arc<TimerDriverRegistry>,
9533 repoll: Arc<HostTimerRepoll>,
9534 id: TimerId,
9535}
9536
9537impl Drop for HostTimerPendingGuard {
9538 fn drop(&mut self) {
9539 self.repoll.close();
9540 self.registry.remove(self.id);
9541 self.drivers.remove_pending(self.id);
9542 }
9543}
9544
9545#[cfg(napi_runtime_os_threads)]
9551struct TimerHeap {
9552 inner: Arc<Mutex<TimerHeapInner>>,
9553 parker: Arc<DriverParker>,
9554 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9555 deadlock_state: Arc<MultiThreadDeadlockState>,
9556 #[cfg(test)]
9557 fail_next_start: AtomicBool,
9558 generation: u64,
9559 executor_id: u64,
9560 thread_name: String,
9561}
9562
9563#[cfg(napi_runtime_os_threads)]
9564#[derive(Default)]
9565struct TimerHeapInner {
9566 queue: TimerDeadlineQueue,
9570 entries: FxHashMap<TimerId, HeapTimerEntry>,
9571 timekeeper_parker: Option<Arc<DriverParker>>,
9575 closed: bool,
9579}
9580
9581#[cfg(napi_runtime_os_threads)]
9582struct HeapTimerEntry {
9583 waker: Waker,
9584 fired: Arc<AtomicBool>,
9589}
9590
9591#[cfg(napi_runtime_os_threads)]
9592#[derive(Default)]
9593struct TimerDeadlineQueue {
9594 nodes: Vec<(Instant, TimerId)>,
9595 positions: FxHashMap<TimerId, usize>,
9596}
9597
9598#[cfg(napi_runtime_os_threads)]
9599impl TimerDeadlineQueue {
9600 #[cfg(test)]
9601 fn len(&self) -> usize {
9602 self.nodes.len()
9603 }
9604
9605 #[cfg(test)]
9606 fn is_empty(&self) -> bool {
9607 self.nodes.is_empty()
9608 }
9609
9610 fn clear(&mut self) {
9611 self.nodes.clear();
9612 self.positions.clear();
9613 }
9614
9615 fn peek(&self) -> Option<(Instant, TimerId)> {
9616 self.nodes.first().copied()
9617 }
9618
9619 fn push(&mut self, deadline: Instant, id: TimerId) {
9620 let index = self.nodes.len();
9621 assert!(
9622 self.positions.insert(id, index).is_none(),
9623 "timer deadline queue received duplicate id {id}"
9624 );
9625 self.nodes.push((deadline, id));
9626 self.sift_up(index);
9627 }
9628
9629 fn pop(&mut self) -> Option<(Instant, TimerId)> {
9630 let first = self.nodes.first().copied()?;
9631 self.positions.remove(&first.1);
9632 let last = self
9633 .nodes
9634 .pop()
9635 .expect("the non-empty timer heap must have a final node");
9636 if !self.nodes.is_empty() {
9637 self.nodes[0] = last;
9638 self.positions.insert(last.1, 0);
9639 self.sift_down(0);
9640 }
9641 Some(first)
9642 }
9643
9644 fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9645 let index = self.positions.remove(&id)?;
9646 let removed = self.nodes[index];
9647 let last = self.nodes.pop().expect("an indexed timer node must exist");
9648 if index < self.nodes.len() {
9649 self.nodes[index] = last;
9650 self.positions.insert(last.1, index);
9651 if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9652 self.sift_up(index);
9653 } else {
9654 self.sift_down(index);
9655 }
9656 }
9657 Some(removed)
9658 }
9659
9660 fn sift_up(&mut self, mut index: usize) {
9661 while index > 0 {
9662 let parent = (index - 1) / 2;
9663 if self.nodes[parent] <= self.nodes[index] {
9664 break;
9665 }
9666 self.swap(parent, index);
9667 index = parent;
9668 }
9669 }
9670
9671 fn sift_down(&mut self, mut index: usize) {
9672 loop {
9673 let left = index * 2 + 1;
9674 if left >= self.nodes.len() {
9675 return;
9676 }
9677 let right = left + 1;
9678 let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9679 right
9680 } else {
9681 left
9682 };
9683 if self.nodes[index] <= self.nodes[child] {
9684 return;
9685 }
9686 self.swap(index, child);
9687 index = child;
9688 }
9689 }
9690
9691 fn swap(&mut self, left: usize, right: usize) {
9692 self.nodes.swap(left, right);
9693 self.positions.insert(self.nodes[left].1, left);
9694 self.positions.insert(self.nodes[right].1, right);
9695 }
9696}
9697
9698#[cfg(napi_runtime_os_threads)]
9699impl TimerHeapInner {
9700 fn next_deadline(&self) -> Option<Instant> {
9701 self.queue.peek().map(|(deadline, _)| deadline)
9702 }
9703}
9704
9705#[cfg(napi_runtime_os_threads)]
9706impl TimerHeap {
9707 fn new(
9708 generation: u64,
9709 executor_id: u64,
9710 thread_name: String,
9711 deadlock_state: Arc<MultiThreadDeadlockState>,
9712 ) -> Self {
9713 Self {
9714 inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9715 parker: Arc::new(DriverParker::default()),
9716 thread: Mutex::new(None),
9717 deadlock_state,
9718 #[cfg(test)]
9719 fail_next_start: AtomicBool::new(false),
9720 generation,
9721 executor_id,
9722 thread_name,
9723 }
9724 }
9725
9726 fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9727 if self
9728 .inner
9729 .lock()
9730 .unwrap_or_else(std::sync::PoisonError::into_inner)
9731 .closed
9732 {
9733 return Ok(());
9734 }
9735 let current_thread = std::thread::current().id();
9736 loop {
9737 let finished = {
9738 let mut thread = self
9739 .thread
9740 .lock()
9741 .unwrap_or_else(std::sync::PoisonError::into_inner);
9742 match thread.as_ref() {
9743 Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9744 Some(handle) if !handle.is_finished() => return Ok(()),
9745 Some(_) => thread.take(),
9746 None => {
9747 if self
9750 .inner
9751 .lock()
9752 .unwrap_or_else(std::sync::PoisonError::into_inner)
9753 .closed
9754 {
9755 return Ok(());
9756 }
9757 #[cfg(test)]
9758 if self.fail_next_start.swap(false, Ordering::SeqCst) {
9759 return Err(RuntimeConfigError(
9760 "injected runtime timer thread creation failure".to_string(),
9761 ));
9762 }
9763
9764 let inner = Arc::clone(&self.inner);
9765 let parker = Arc::clone(&self.parker);
9766 let deadlock_state = Arc::clone(&self.deadlock_state);
9767 let generation = self.generation;
9768 let executor_id = self.executor_id;
9769 let handle = std::thread::Builder::new()
9770 .name(self.thread_name.clone())
9771 .spawn(move || {
9772 ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9775 let _generation = RuntimeGenerationGuard::enter(generation);
9776 timer_timekeeper_main(&inner, &parker, &deadlock_state);
9777 })
9778 .map_err(|error| {
9779 RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9780 })?;
9781 *thread = Some(handle);
9782 return Ok(());
9783 }
9784 }
9785 };
9786
9787 if let Some(finished) = finished
9791 && finished.thread().id() != current_thread
9792 && let Err(payload) = finished.join()
9793 {
9794 discard_panic_payload(payload);
9795 }
9796 if self
9797 .inner
9798 .lock()
9799 .unwrap_or_else(std::sync::PoisonError::into_inner)
9800 .closed
9801 {
9802 return Ok(());
9803 }
9804 }
9805 }
9806
9807 fn close(&self) -> Vec<Waker> {
9808 let mut inner = self
9809 .inner
9810 .lock()
9811 .unwrap_or_else(std::sync::PoisonError::into_inner);
9812 inner.closed = true;
9813 inner.queue.clear();
9814 inner.timekeeper_parker = None;
9815 inner
9816 .entries
9817 .drain()
9818 .map(|(_, entry)| {
9819 entry.fired.store(true, Ordering::SeqCst);
9820 entry.waker
9821 })
9822 .collect()
9823 }
9824
9825 fn join(&self) {
9826 let handle = self
9827 .thread
9828 .lock()
9829 .unwrap_or_else(std::sync::PoisonError::into_inner)
9830 .take();
9831 if let Some(handle) = handle {
9832 if handle.thread().id() == std::thread::current().id() {
9833 drop(handle);
9837 return;
9838 }
9839 if let Err(payload) = crate::sync::join(handle) {
9840 discard_panic_payload(payload);
9841 }
9842 }
9843 }
9844}
9845
9846#[cfg(napi_runtime_os_threads)]
9847impl Drop for TimerHeap {
9848 fn drop(&mut self) {
9849 let mut admission = self.deadlock_state.begin_admission();
9850 let wakers = self.close();
9851 if !wakers.is_empty()
9852 && let Some(admission) = admission.as_mut()
9853 {
9854 admission.mark_published();
9855 }
9856 self.parker.unpark();
9857 for waker in wakers {
9858 wake_waker_contained(waker);
9859 }
9860 drop(admission);
9861 self.join();
9862 }
9863}
9864
9865#[cfg(napi_runtime_os_threads)]
9866fn timer_timekeeper_main(
9867 inner: &Mutex<TimerHeapInner>,
9868 parker: &Arc<DriverParker>,
9869 deadlock_state: &Arc<MultiThreadDeadlockState>,
9870) {
9871 loop {
9872 let mut admission = deadlock_state.begin_admission();
9876 let (due, deadline) = {
9877 let mut inner = inner
9878 .lock()
9879 .unwrap_or_else(std::sync::PoisonError::into_inner);
9880 if inner.closed {
9881 inner.timekeeper_parker = None;
9882 return;
9883 }
9884 let now = Instant::now();
9885 let mut due = Vec::new();
9886 while let Some((deadline, id)) = inner.queue.peek() {
9887 if deadline > now {
9888 break;
9889 }
9890 inner.queue.pop();
9891 if let Some(entry) = inner.entries.remove(&id) {
9892 entry.fired.store(true, Ordering::SeqCst);
9893 due.push(entry.waker);
9894 }
9895 }
9896 let deadline = inner.next_deadline();
9897 inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9898 (due, deadline)
9899 };
9900
9901 if !due.is_empty() {
9902 if let Some(admission) = admission.as_mut() {
9903 admission.mark_published();
9904 }
9905 for waker in due {
9906 wake_waker_contained(waker);
9907 }
9908 continue;
9909 }
9910 drop(admission);
9913
9914 match deadline {
9915 Some(deadline) => {
9916 let now = Instant::now();
9917 if deadline > now {
9918 parker.park_timeout(deadline - now);
9919 }
9920 }
9921 None => parker.park(),
9922 }
9923 }
9924}
9925
9926#[cfg(napi_runtime_os_threads)]
9927impl MultiThreadExecutor {
9928 fn register_timer(
9934 self: &Arc<Self>,
9935 id: TimerId,
9936 deadline: Instant,
9937 waker: Waker,
9938 fired: &Arc<AtomicBool>,
9939 ) {
9940 let mut admission = self.begin_deadlock_admission();
9941 #[cfg(test)]
9942 run_timer_registration_admission_test_hook();
9943 if let Err(error) = self.timers.ensure_started() {
9947 let _ = catch_unwind_contained(|| drop(waker));
9948 panic!("{error}");
9949 }
9950 let (became_earliest, published, discarded_waker) = {
9951 let mut inner = self
9952 .timers
9953 .inner
9954 .lock()
9955 .unwrap_or_else(std::sync::PoisonError::into_inner);
9956 if inner.closed || fired.load(Ordering::SeqCst) {
9957 fired.store(true, Ordering::SeqCst);
9958 (false, false, Some(waker))
9959 } else if let Some(entry) = inner.entries.get_mut(&id) {
9960 (
9962 false,
9963 false,
9964 Some(std::mem::replace(&mut entry.waker, waker)),
9965 )
9966 } else {
9967 let previous = inner.next_deadline();
9968 inner.entries.insert(
9969 id,
9970 HeapTimerEntry {
9971 waker,
9972 fired: Arc::clone(fired),
9973 },
9974 );
9975 inner.queue.push(deadline, id);
9976 (
9977 previous.is_none_or(|previous| deadline < previous),
9978 true,
9979 None,
9980 )
9981 }
9982 };
9983 if published && let Some(admission) = admission.as_mut() {
9984 admission.mark_published();
9985 }
9986 if let Some(waker) = discarded_waker {
9987 let _ = catch_unwind_contained(|| drop(waker));
9990 }
9991 if became_earliest {
9992 self.note_earlier_timer();
9994 }
9995 }
9996
9997 fn cancel_timer(&self, id: TimerId) {
10001 let (removed, earliest_changed) = {
10002 let mut inner = self
10003 .timers
10004 .inner
10005 .lock()
10006 .unwrap_or_else(std::sync::PoisonError::into_inner);
10007 let previous = inner.next_deadline();
10008 let removed = inner.entries.remove(&id);
10009 if removed.is_some() {
10010 let queued = inner.queue.remove(id);
10011 debug_assert!(
10012 queued.is_some(),
10013 "a live timer entry must have one indexed deadline node"
10014 );
10015 }
10016 let current = inner.next_deadline();
10017 (removed, previous != current)
10018 };
10019 if let Some(entry) = removed {
10020 let _ = catch_unwind_contained(|| drop(entry));
10023 }
10024 if earliest_changed {
10025 self.timers.parker.unpark();
10026 self.parked_drivers.wake_all();
10027 }
10028 }
10029
10030 fn next_timer_deadline(&self) -> Option<Instant> {
10032 self
10033 .timers
10034 .inner
10035 .lock()
10036 .unwrap_or_else(std::sync::PoisonError::into_inner)
10037 .next_deadline()
10038 }
10039
10040 fn fire_due_timers(&self) {
10045 let mut admission = self.begin_deadlock_admission();
10046 let due: Vec<Waker> = {
10047 let mut inner = self
10048 .timers
10049 .inner
10050 .lock()
10051 .unwrap_or_else(std::sync::PoisonError::into_inner);
10052 let now = Instant::now();
10053 let mut due = Vec::new();
10054 while let Some((deadline, id)) = inner.queue.peek() {
10055 if deadline > now {
10056 break;
10057 }
10058 inner.queue.pop();
10059 if let Some(entry) = inner.entries.remove(&id) {
10060 entry.fired.store(true, Ordering::SeqCst);
10061 due.push(entry.waker);
10062 }
10063 }
10064 due
10065 };
10066 if !due.is_empty()
10067 && let Some(admission) = admission.as_mut()
10068 {
10069 admission.mark_published();
10070 }
10071 for waker in due {
10072 wake_waker_contained(waker);
10075 }
10076 }
10077
10078 fn note_earlier_timer(self: &Arc<Self>) {
10082 self.timers.parker.unpark();
10083 self.parked_drivers.wake_one();
10084 }
10085
10086 fn shutdown_timers(&self) {
10092 let mut admission = self.begin_deadlock_admission();
10093 let wakers = self.timers.close();
10094 if !wakers.is_empty()
10095 && let Some(admission) = admission.as_mut()
10096 {
10097 admission.mark_published();
10098 }
10099 for waker in wakers {
10100 wake_waker_contained(waker);
10104 }
10105 self.timers.parker.unpark();
10106 }
10107
10108 fn begin_shutdown(&self) {
10109 let _generation = RuntimeGenerationGuard::enter(self.generation);
10110 self.stop.begin_shutdown();
10115 let queued = {
10116 let mut queue = self
10117 .blocking_queue
10118 .lock()
10119 .unwrap_or_else(std::sync::PoisonError::into_inner);
10120 queue.closed = true;
10123 queue.take_all()
10124 };
10125 for queued in queued {
10130 let _ = catch_unwind_contained(|| drop(queued));
10131 }
10132 self.shutdown_timers();
10133 }
10134
10135 fn scheduler_work_pending(&self) -> bool {
10139 self.active_drainers.load(Ordering::Acquire) != 0
10140 }
10141
10142 fn wait_until_scheduler_idle(&self) {
10143 self.timers.join();
10144 let mut idle = self
10145 .scheduler_idle_lock
10146 .lock()
10147 .unwrap_or_else(std::sync::PoisonError::into_inner);
10148 *idle += 1;
10154 while self.active_drainers.load(Ordering::Acquire) != 0 {
10155 idle = self
10156 .scheduler_idle
10157 .wait(idle)
10158 .unwrap_or_else(std::sync::PoisonError::into_inner);
10159 }
10160 *idle -= 1;
10161 }
10162}
10163
10164pub struct Sleep {
10169 deadline: Instant,
10170 inner: SleepInner,
10171}
10172
10173enum SleepInner {
10174 #[cfg(napi_runtime_os_threads)]
10175 Heap(HeapSleep),
10176 Host(HostSleep),
10177}
10178
10179#[cfg(napi_runtime_os_threads)]
10180struct HeapSleep {
10181 id: TimerId,
10182 executor: Weak<MultiThreadExecutor>,
10183 fired: Arc<AtomicBool>,
10184 registered: bool,
10185}
10186
10187struct HostSleep {
10188 id: TimerId,
10189 drivers: Arc<TimerDriverRegistry>,
10193 armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10197 registry: Arc<HostTimerRegistry>,
10200 fired: Arc<AtomicBool>,
10201 repoll: Arc<HostTimerRepoll>,
10205 pending: Option<HostTimerPendingGuard>,
10210}
10211
10212fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10213 let _ = catch_unwind_contained(|| driver.cancel(id));
10214 let _ = catch_unwind_contained(|| drop(driver));
10215}
10216
10217fn cancel_host_timer_drivers(
10218 id: TimerId,
10219 armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10220) {
10221 for (_, driver) in armed.drain() {
10222 cancel_host_timer_driver(id, driver);
10223 }
10224}
10225
10226impl Future for Sleep {
10227 type Output = ();
10228
10229 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10230 let deadline = self.deadline;
10231 let this = self.get_mut();
10232 match &mut this.inner {
10233 #[cfg(napi_runtime_os_threads)]
10234 SleepInner::Heap(heap) => {
10235 if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10236 if heap.registered {
10237 if let Some(executor) = heap.executor.upgrade() {
10240 executor.cancel_timer(heap.id);
10241 }
10242 heap.registered = false;
10243 }
10244 return Poll::Ready(());
10245 }
10246 let Some(executor) = heap.executor.upgrade() else {
10247 heap.fired.store(true, Ordering::SeqCst);
10248 heap.registered = false;
10249 return Poll::Ready(());
10250 };
10251 executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10252 heap.registered = true;
10253 if heap.fired.load(Ordering::SeqCst) {
10257 heap.registered = false;
10258 return Poll::Ready(());
10259 }
10260 Poll::Pending
10261 }
10262 SleepInner::Host(host) => {
10263 if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10264 cancel_host_timer_drivers(host.id, &mut host.armed);
10265 host.pending.take();
10266 return Poll::Ready(());
10267 }
10268 let repoll_waker = cx.waker().clone();
10273 let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10274 Ok(waker) => waker,
10275 Err(payload) => {
10276 let _ = catch_unwind_contained(|| drop(repoll_waker));
10277 resume_unwind(payload);
10278 }
10279 };
10280 host.repoll.register(repoll_waker);
10281 if !host.registry.register(
10282 host.id,
10283 deadline,
10284 registry_waker,
10285 &host.fired,
10286 &host.drivers,
10287 &host.repoll,
10288 ) {
10289 cancel_host_timer_drivers(host.id, &mut host.armed);
10290 if host.pending.take().is_none() {
10291 host.repoll.close();
10292 }
10293 return Poll::Ready(());
10294 }
10295 if host.pending.is_none() {
10296 host.drivers.register_pending(host.id, &host.repoll);
10304 host.pending = Some(HostTimerPendingGuard {
10305 registry: Arc::clone(&host.registry),
10306 drivers: Arc::clone(&host.drivers),
10307 repoll: Arc::clone(&host.repoll),
10308 id: host.id,
10309 });
10310 }
10311 let current = host.drivers.live();
10315 assert!(
10316 !current.as_slice().is_empty(),
10317 "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10318 that registered timer drivers (via \
10319 `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10320 one per importing env through `registerTimerHost`) have all been torn down, so this \
10321 `sleep_until` can never be woken."
10322 );
10323 let missing = current
10329 .as_slice()
10330 .iter()
10331 .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10332 .count();
10333 host.armed.reserve(missing);
10334 for (current_id, current_driver) in current.as_slice() {
10335 host
10336 .armed
10337 .entry(*current_id)
10338 .or_insert_with(|| Arc::clone(current_driver));
10339 }
10340 for (current_id, _) in current.as_slice() {
10341 let armed_driver = host
10342 .armed
10343 .get(current_id)
10344 .expect("every live timer driver must have cancellation ownership before callbacks");
10345 armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10346 }
10347 let current_ids = current
10348 .as_slice()
10349 .iter()
10350 .map(|(current_id, _)| *current_id)
10351 .collect::<FxHashSet<_>>();
10352 let retired = host
10353 .armed
10354 .keys()
10355 .filter(|armed_id| !current_ids.contains(armed_id))
10356 .copied()
10357 .collect::<Vec<_>>();
10358 for retired in retired {
10359 let old_driver = host
10360 .armed
10361 .remove(&retired)
10362 .expect("the retired timer driver must remain armed");
10363 cancel_host_timer_driver(host.id, old_driver);
10364 }
10365 Poll::Pending
10366 }
10367 }
10368 }
10369}
10370
10371impl Drop for Sleep {
10372 fn drop(&mut self) {
10373 match &mut self.inner {
10374 #[cfg(napi_runtime_os_threads)]
10375 SleepInner::Heap(heap) => {
10376 if heap.registered
10377 && let Some(executor) = heap.executor.upgrade()
10378 {
10379 executor.cancel_timer(heap.id);
10380 }
10381 }
10382 SleepInner::Host(host) => {
10383 host.repoll.close();
10386 cancel_host_timer_drivers(host.id, &mut host.armed);
10387 }
10389 }
10390 }
10391}
10392
10393pub fn sleep_until(deadline: Instant) -> Sleep {
10398 make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10399}
10400
10401fn make_sleep(
10405 backend: &RuntimeBackend,
10406 host_drivers: &Arc<TimerDriverRegistry>,
10407 deadline: Instant,
10408) -> Sleep {
10409 let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10410 match &backend.executor {
10411 #[cfg(napi_runtime_os_threads)]
10412 RuntimeExecutor::MultiThread(executor) => Sleep {
10413 deadline,
10414 inner: SleepInner::Heap(HeapSleep {
10415 id,
10416 executor: Arc::downgrade(executor),
10417 fired: Arc::new(AtomicBool::new(false)),
10418 registered: false,
10419 }),
10420 },
10421 RuntimeExecutor::CurrentThread(executor) => {
10422 assert!(
10430 host_drivers.has_live_driver(),
10431 "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10432 single-thread flavor delegates timers to the host event loop. Install one via \
10433 `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10434 a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10435 );
10436 Sleep {
10443 deadline,
10444 inner: SleepInner::Host(HostSleep {
10445 id,
10446 drivers: Arc::clone(host_drivers),
10447 armed: FxHashMap::default(),
10448 registry: Arc::clone(&executor.host_timers),
10449 fired: Arc::new(AtomicBool::new(false)),
10450 repoll: Arc::new(HostTimerRepoll::default()),
10451 pending: None,
10452 }),
10453 }
10454 }
10455 }
10456}
10457
10458#[derive(Clone)]
10459enum RuntimeExecutor {
10460 CurrentThread(Arc<CurrentThreadExecutor>),
10461 #[cfg(napi_runtime_os_threads)]
10462 MultiThread(Arc<MultiThreadExecutor>),
10463}
10464
10465enum WeakRuntimeExecutor {
10466 CurrentThread(Weak<CurrentThreadExecutor>),
10467 #[cfg(napi_runtime_os_threads)]
10468 MultiThread(Weak<MultiThreadExecutor>),
10469}
10470
10471impl WeakRuntimeExecutor {
10472 fn schedule(&self, runnable: Runnable) {
10473 match self {
10474 Self::CurrentThread(executor) => {
10475 if let Some(executor) = executor.upgrade() {
10476 executor.schedule(runnable);
10477 }
10478 }
10479 #[cfg(napi_runtime_os_threads)]
10480 Self::MultiThread(executor) => {
10481 if let Some(executor) = executor.upgrade() {
10482 executor.schedule(runnable);
10483 }
10484 }
10485 }
10486 }
10487}
10488
10489#[derive(Clone)]
10490struct RuntimeBackend {
10491 work: Arc<GenerationWork>,
10492 executor: RuntimeExecutor,
10493}
10494
10495impl RuntimeBackend {
10496 fn new(
10497 options: &RuntimeOptions,
10498 metrics: Arc<RuntimeMetrics>,
10499 ) -> Result<Self, RuntimeConfigError> {
10500 #[cfg(test)]
10501 if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10502 return Err(RuntimeConfigError(
10503 "injected async runtime backend creation failure".to_string(),
10504 ));
10505 }
10506
10507 let work = GenerationWork::new();
10508 let stop = Arc::new(GenerationStop::default());
10509 let executor = match options.flavor {
10510 RuntimeFlavor::CurrentThread => {
10511 let executor = CurrentThreadExecutor::with_generation(
10512 metrics,
10513 THREADLESS_BUILD,
10514 options.park_deadline,
10515 work.id,
10516 Arc::clone(&stop),
10517 );
10518 executor.attach_generation_work(&work);
10519 RuntimeExecutor::CurrentThread(Arc::new(executor))
10520 }
10521 RuntimeFlavor::MultiThread => {
10522 #[cfg(napi_runtime_os_threads)]
10523 {
10524 RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10525 options, metrics, work.id, stop,
10526 )?))
10527 }
10528 #[cfg(not(napi_runtime_os_threads))]
10529 {
10530 let _ = metrics;
10531 return Err(RuntimeConfigError(
10532 "the multi-thread runtime is unavailable in this threadless WebAssembly build \
10533 (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
10534 .to_string(),
10535 ));
10536 }
10537 }
10538 };
10539 Ok(Self { work, executor })
10540 }
10541
10542 #[cfg(test)]
10543 fn from_executor(executor: RuntimeExecutor) -> Self {
10544 let work = GenerationWork::new();
10545 if let RuntimeExecutor::CurrentThread(executor) = &executor {
10546 executor.attach_generation_work(&work);
10547 }
10548 Self { work, executor }
10549 }
10550
10551 fn generation(&self) -> u64 {
10552 self.work.id
10553 }
10554
10555 fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10556 match &self.executor {
10557 RuntimeExecutor::CurrentThread(executor) => {
10558 WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10559 }
10560 #[cfg(napi_runtime_os_threads)]
10561 RuntimeExecutor::MultiThread(executor) => {
10562 WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10563 }
10564 }
10565 }
10566
10567 fn schedule_submission(&self, runnable: Runnable) {
10571 match &self.executor {
10572 RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10573 #[cfg(napi_runtime_os_threads)]
10574 RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10575 }
10576 }
10577
10578 fn spawn_registered_blocking<F, T>(
10579 &self,
10580 function: F,
10581 registration: GenerationWorkGuard,
10582 metrics: &Arc<RuntimeMetrics>,
10583 ) -> JoinHandle<T>
10584 where
10585 F: FnOnce() -> T + Send + 'static,
10586 T: Send + 'static,
10587 {
10588 match &self.executor {
10589 RuntimeExecutor::CurrentThread(executor) => {
10590 let _ = metrics;
10591 executor.schedule_blocking(function, registration)
10592 }
10593 #[cfg(napi_runtime_os_threads)]
10594 RuntimeExecutor::MultiThread(executor) => {
10595 let generation = self.generation();
10596 let _ = metrics;
10597 let function = RegisteredBlockingFunction::new(function, registration, generation);
10598 executor.schedule_blocking_result(move || function.run())
10599 }
10600 }
10601 }
10602
10603 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10604 match &self.executor {
10605 RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10606 #[cfg(napi_runtime_os_threads)]
10607 RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10608 }
10609 }
10610
10611 fn generation_stop(&self) -> Arc<GenerationStop> {
10612 match &self.executor {
10613 RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10614 #[cfg(napi_runtime_os_threads)]
10615 RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10616 }
10617 }
10618
10619 fn begin_shutdown(&self) {
10620 let _generation = RuntimeGenerationGuard::enter(self.generation());
10621 self.work.close_and_abort();
10622 match &self.executor {
10623 RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10624 #[cfg(napi_runtime_os_threads)]
10625 RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10626 }
10627 }
10628
10629 fn wait_until_idle(&self) {
10630 match &self.executor {
10631 RuntimeExecutor::CurrentThread(executor) => {
10632 self.work.wait_until_idle_retiring(
10638 || executor.has_rejected_work(),
10639 || executor.retire_rejected_work(),
10640 );
10641 executor.wait_until_scheduler_idle();
10642 }
10643 #[cfg(napi_runtime_os_threads)]
10644 RuntimeExecutor::MultiThread(executor) => {
10645 self.work.wait_until_idle();
10646 executor.wait_until_scheduler_idle();
10647 }
10648 }
10649 }
10650
10651 fn work_pending(&self, read: PendingRead) -> bool {
10666 if self.work.has_active_work(read) {
10667 return true;
10668 }
10669 match &self.executor {
10670 RuntimeExecutor::CurrentThread(executor) => {
10671 executor.rejected_work_pending(read) || executor.scheduler_work_pending(read)
10672 }
10673 #[cfg(napi_runtime_os_threads)]
10674 RuntimeExecutor::MultiThread(executor) => executor.scheduler_work_pending(),
10675 }
10676 }
10677
10678 #[cfg(napi_runtime_os_threads)]
10679 fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10680 match &self.executor {
10681 RuntimeExecutor::CurrentThread(_) => None,
10682 RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10683 }
10684 }
10685
10686 fn stop_identity(&self) -> RuntimeStopIdentity {
10687 RuntimeStopIdentity {
10688 generation: self.generation(),
10689 #[cfg(napi_runtime_os_threads)]
10690 executor_id: match &self.executor {
10691 RuntimeExecutor::CurrentThread(_) => None,
10692 RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10693 },
10694 }
10695 }
10696
10697 fn is_current(&self) -> bool {
10698 self.stop_identity().is_current()
10699 }
10700}
10701
10702#[derive(Clone, Copy)]
10703struct RuntimeStopIdentity {
10704 generation: u64,
10705 #[cfg(napi_runtime_os_threads)]
10706 executor_id: Option<u64>,
10707}
10708
10709impl RuntimeStopIdentity {
10710 fn is_current(self) -> bool {
10711 if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10712 return true;
10713 }
10714 #[cfg(napi_runtime_os_threads)]
10715 if let Some(executor_id) = self.executor_id
10716 && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10717 || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10718 {
10719 return true;
10720 }
10721 false
10722 }
10723}
10724
10725enum RuntimeLifecycle {
10726 Initial,
10727 StoppedBeforeFirstUse,
10728 Running(RuntimeBackend),
10729 Stopping(RuntimeStopIdentity),
10730 StoppingWithoutBackend(ZeroBackendShutdownTarget),
10731 Stopped,
10732}
10733
10734#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10735enum ZeroBackendShutdownTarget {
10736 StoppedBeforeFirstUse,
10737 Stopped,
10738}
10739
10740impl ZeroBackendShutdownTarget {
10741 fn into_lifecycle(self) -> RuntimeLifecycle {
10742 match self {
10743 Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10744 Self::Stopped => RuntimeLifecycle::Stopped,
10745 }
10746 }
10747}
10748
10749struct RuntimeState {
10750 options: RuntimeOptions,
10751 lifecycle: RuntimeLifecycle,
10752 rejected_drops: usize,
10753 draining: Option<ShutdownDrain>,
10778 phase_ever_opened: bool,
10798}
10799
10800impl RuntimeState {
10801 fn open_phase_one(&mut self, drain: ShutdownDrain) {
10805 self.phase_ever_opened = true;
10806 self.draining = Some(drain);
10807 }
10808
10809 fn forget_shutdown_handoff(&mut self) {
10826 debug_assert!(
10834 !matches!(
10835 self.draining,
10836 Some(ShutdownDrain::Backend(_) | ShutdownDrain::Joining(_) | ShutdownDrain::ZeroBackend(_))
10837 ),
10838 "a start must never publish over an outstanding backend drain"
10839 );
10840 self.draining = None;
10841 }
10842}
10843
10844enum ShutdownDrain {
10847 Backend(RuntimeBackend),
10850 Joining(Option<RuntimeBackend>),
10870 Settled,
10873 ZeroBackend(ZeroBackendShutdownTarget),
10890}
10891
10892#[cfg(test)]
10893thread_local! {
10894 static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10895 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10896 static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10897 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10898 static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10899 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10900 static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10901 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10902 static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10903 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10904 static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10905 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10906 static AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK:
10907 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10908 static BETWEEN_SHUTDOWN_PHASES_TEST_HOOK:
10909 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10910 static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10911 std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10912}
10913
10914#[cfg(test)]
10915fn run_before_runtime_submission_lock_test_hook() {
10916 if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10917 {
10918 hook();
10919 }
10920}
10921
10922#[cfg(test)]
10923fn run_after_async_work_registration_test_hook() {
10924 if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10925 {
10926 hook();
10927 }
10928}
10929
10930#[cfg(test)]
10931fn run_after_blocking_work_registration_test_hook() {
10932 if let Some(hook) =
10933 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10934 {
10935 hook();
10936 }
10937}
10938
10939#[cfg(test)]
10940fn run_after_current_thread_host_admission_test_hook() {
10941 if let Some(hook) =
10942 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10943 {
10944 hook();
10945 }
10946}
10947
10948#[cfg(test)]
10949fn run_before_initial_start_wait_test_hook() {
10950 if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10951 hook();
10952 }
10953}
10954
10955#[cfg(test)]
10956fn run_after_generation_stop_publication_test_hook() {
10957 if let Some(hook) =
10958 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10959 {
10960 hook();
10961 }
10962}
10963
10964#[cfg(test)]
10968fn run_after_shutdown_drain_taken_test_hook() {
10969 if let Some(hook) = AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10970 hook();
10971 }
10972}
10973
10974#[cfg(test)]
10978fn run_between_shutdown_phases_test_hook() {
10979 if let Some(hook) = BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10980 hook();
10981 }
10982}
10983
10984thread_local! {
10985 static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
10986 const { std::cell::Cell::new(0) };
10987}
10988
10989struct RejectedSubmissionDropContext;
10990
10991impl RejectedSubmissionDropContext {
10992 fn enter() -> Self {
10993 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
10994 Self
10995 }
10996
10997 fn is_current() -> bool {
10998 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
10999 }
11000}
11001
11002impl Drop for RejectedSubmissionDropContext {
11003 fn drop(&mut self) {
11004 IN_REJECTED_SUBMISSION_DROP.with(|depth| {
11005 depth.set(
11006 depth
11007 .get()
11008 .checked_sub(1)
11009 .expect("rejected submission drop depth underflow"),
11010 );
11011 });
11012 }
11013}
11014
11015struct RuntimeController {
11016 state: Mutex<RuntimeState>,
11017 lifecycle_changed: Condvar,
11018 metrics: Arc<RuntimeMetrics>,
11019 current_backend: ArcSwapOption<RuntimeBackend>,
11025}
11026
11027struct RejectedSubmissionGuard<'a> {
11028 controller: &'a RuntimeController,
11029}
11030
11031impl Drop for RejectedSubmissionGuard<'_> {
11032 fn drop(&mut self) {
11033 let mut state = self
11034 .controller
11035 .state
11036 .lock()
11037 .unwrap_or_else(std::sync::PoisonError::into_inner);
11038 state.rejected_drops = state
11039 .rejected_drops
11040 .checked_sub(1)
11041 .expect("rejected submission drop count underflow");
11042 self.controller.lifecycle_changed.notify_all();
11043 }
11044}
11045
11046impl RuntimeController {
11047 fn new() -> Self {
11048 let options = RuntimeOptions::default()
11049 .validate()
11050 .expect("default async runtime options must be valid");
11051 Self {
11052 state: Mutex::new(RuntimeState {
11053 options,
11054 lifecycle: RuntimeLifecycle::Initial,
11055 rejected_drops: 0,
11056 draining: None,
11057 phase_ever_opened: false,
11058 }),
11059 lifecycle_changed: Condvar::new(),
11060 metrics: Arc::new(RuntimeMetrics::default()),
11061 current_backend: ArcSwapOption::empty(),
11062 }
11063 }
11064
11065 fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
11066 let options = options.validate()?;
11067 let mut state = self
11068 .state
11069 .lock()
11070 .unwrap_or_else(std::sync::PoisonError::into_inner);
11071 Self::ensure_configuration_mutable(&state)?;
11072 state.options = options;
11073 Ok(())
11074 }
11075
11076 fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
11077 self.configure_partial_inner(patch, |_| {})
11078 }
11079
11080 fn configure_partial_inner(
11081 &self,
11082 patch: RuntimeOptionsPatch,
11083 after_merge: impl FnOnce(&RuntimeOptions),
11084 ) -> Result<(), RuntimeConfigError> {
11085 let mut state = self
11086 .state
11087 .lock()
11088 .unwrap_or_else(std::sync::PoisonError::into_inner);
11089 Self::ensure_configuration_mutable(&state)?;
11090
11091 let mut options = state.options.clone();
11092 patch.apply_to(&mut options);
11093 after_merge(&options);
11094 let options = options.validate()?;
11095 state.options = options;
11096 Ok(())
11097 }
11098
11099 fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11100 if matches!(
11101 &state.lifecycle,
11102 RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
11103 ) {
11104 return Ok(());
11105 }
11106 Err(RuntimeConfigError(
11107 "the async runtime configuration is frozen; configure it before the first async call"
11108 .to_string(),
11109 ))
11110 }
11111
11112 fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11113 let Some(active_generation) = active_runtime_generation() else {
11114 return Ok(());
11115 };
11116 let lifecycle_generation = match &state.lifecycle {
11117 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11118 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11119 RuntimeLifecycle::Initial
11120 | RuntimeLifecycle::StoppedBeforeFirstUse
11121 | RuntimeLifecycle::StoppingWithoutBackend(_)
11122 | RuntimeLifecycle::Stopped => None,
11123 };
11124 if lifecycle_generation == Some(active_generation) {
11125 return Ok(());
11126 }
11127 Err(RuntimeConfigError(
11128 "cannot access or control the async runtime from work owned by a retired generation"
11129 .to_string(),
11130 ))
11131 }
11132
11133 fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
11134 Self::ensure_active_generation_current(state)?;
11135 match &state.lifecycle {
11136 RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
11137 RuntimeLifecycle::StoppedBeforeFirstUse
11138 | RuntimeLifecycle::Stopping(_)
11139 | RuntimeLifecycle::StoppingWithoutBackend(_)
11140 | RuntimeLifecycle::Stopped => {
11141 return Err(RuntimeConfigError(
11142 "the async runtime is stopped; call start before submitting work".to_string(),
11143 ));
11144 }
11145 RuntimeLifecycle::Initial => {}
11146 }
11147 if state.rejected_drops != 0 {
11148 return Err(RuntimeConfigError(
11149 "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
11150 ));
11151 }
11152 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11153 state.lifecycle = RuntimeLifecycle::Running(backend.clone());
11154 state.forget_shutdown_handoff();
11155 self.current_backend.store(Some(Arc::new(backend.clone())));
11156 Ok(backend)
11157 }
11158
11159 fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
11160 let mut state = self
11161 .state
11162 .lock()
11163 .unwrap_or_else(std::sync::PoisonError::into_inner);
11164 self.backend_locked(&mut state)
11165 }
11166
11167 fn backend(&self) -> RuntimeBackend {
11168 self.try_backend().unwrap_or_else(|error| panic!("{error}"))
11169 }
11170
11171 fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
11172 let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
11173 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11174 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11175 RuntimeLifecycle::Initial
11176 | RuntimeLifecycle::StoppedBeforeFirstUse
11177 | RuntimeLifecycle::StoppingWithoutBackend(_)
11178 | RuntimeLifecycle::Stopped => None,
11179 });
11180 state.rejected_drops += 1;
11181 generation
11182 }
11183
11184 #[inline]
11192 fn fast_register<R>(
11193 &self,
11194 register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
11195 ) -> Option<(Arc<RuntimeBackend>, R)> {
11196 let guard = self.current_backend.load();
11197 let backend = guard.as_ref()?; if let Some(active) = active_runtime_generation()
11199 && active != backend.generation()
11200 {
11201 return None; }
11203 let registration = register(&backend.work)?; Some((Arc::clone(backend), registration))
11207 }
11208
11209 fn try_spawn_tracked<F, T>(
11210 &self,
11211 future: F,
11212 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11213 where
11214 F: Future<Output = T> + Send + 'static,
11215 T: Send + 'static,
11216 {
11217 #[cfg(test)]
11218 run_before_runtime_submission_lock_test_hook();
11219
11220 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
11221 #[cfg(test)]
11222 run_after_async_work_registration_test_hook();
11223 return Ok(spawn_registered(
11224 &backend,
11225 Arc::clone(&self.metrics),
11226 future,
11227 registration,
11228 ));
11229 }
11230
11231 let (backend, registration) = {
11232 let mut state = self
11233 .state
11234 .lock()
11235 .unwrap_or_else(std::sync::PoisonError::into_inner);
11236 let backend = match self.backend_locked(&mut state) {
11237 Ok(backend) => backend,
11238 Err(error) => {
11239 let generation = Self::begin_rejected_drop(&mut state);
11240 return Err((error, future, generation));
11241 }
11242 };
11243 let Some(registration) = backend.work.try_register_async() else {
11244 let generation = Self::begin_rejected_drop(&mut state);
11245 return Err((
11246 RuntimeConfigError(
11247 "the async runtime is stopped; call start before submitting work".to_string(),
11248 ),
11249 future,
11250 generation,
11251 ));
11252 };
11253 (backend, registration)
11254 };
11255 #[cfg(test)]
11256 run_after_async_work_registration_test_hook();
11257 Ok(spawn_registered(
11258 &backend,
11259 Arc::clone(&self.metrics),
11260 future,
11261 registration,
11262 ))
11263 }
11264
11265 fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11266 where
11267 F: Future<Output = T> + Send + 'static,
11268 T: Send + 'static,
11269 {
11270 match self.try_spawn_tracked(future) {
11271 Ok(handle) => Ok(handle),
11272 Err((error, future, _generation)) => {
11273 drop(RejectedSubmissionGuard { controller: self });
11274 Err((error, future))
11275 }
11276 }
11277 }
11278
11279 fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
11280 where
11281 F: Future<Output = T> + Send + 'static,
11282 T: Send + 'static,
11283 {
11284 match self.try_spawn_tracked(future) {
11285 Ok(handle) => handle,
11286 Err((error, future, generation)) => {
11287 let _rejection = RejectedSubmissionGuard { controller: self };
11288 let _drop_context = RejectedSubmissionDropContext::enter();
11289 let _generation = generation.map(RuntimeGenerationGuard::enter);
11290 let _ = catch_unwind_contained(|| drop(future));
11291 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11292 message: error.to_string(),
11293 }))))
11294 }
11295 }
11296 }
11297
11298 fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
11299 where
11300 F: Future<Output = ()> + Send + 'static,
11301 {
11302 match self.try_spawn(future) {
11303 Ok(handle) => {
11304 handle.detach();
11305 Ok(())
11306 }
11307 Err((_, future)) => Err(future),
11308 }
11309 }
11310
11311 fn try_spawn_blocking_tracked<F, T>(
11312 &self,
11313 function: F,
11314 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11315 where
11316 F: FnOnce() -> T + Send + 'static,
11317 T: Send + 'static,
11318 {
11319 #[cfg(test)]
11320 run_before_runtime_submission_lock_test_hook();
11321
11322 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
11323 #[cfg(test)]
11324 run_after_blocking_work_registration_test_hook();
11325 return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
11326 }
11327
11328 let (backend, registration) = {
11329 let mut state = self
11330 .state
11331 .lock()
11332 .unwrap_or_else(std::sync::PoisonError::into_inner);
11333 let backend = match self.backend_locked(&mut state) {
11334 Ok(backend) => backend,
11335 Err(error) => {
11336 let generation = Self::begin_rejected_drop(&mut state);
11337 return Err((error, function, generation));
11338 }
11339 };
11340 let Some(registration) = backend.work.try_register_work() else {
11341 let generation = Self::begin_rejected_drop(&mut state);
11342 return Err((
11343 RuntimeConfigError(
11344 "the async runtime is stopped; call start before submitting work".to_string(),
11345 ),
11346 function,
11347 generation,
11348 ));
11349 };
11350 (backend, registration)
11351 };
11352 #[cfg(test)]
11353 run_after_blocking_work_registration_test_hook();
11354 Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11355 }
11356
11357 fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11358 where
11359 F: FnOnce() -> T + Send + 'static,
11360 T: Send + 'static,
11361 {
11362 match self.try_spawn_blocking_tracked(function) {
11363 Ok(handle) => Ok(handle),
11364 Err((error, function, _generation)) => {
11365 drop(RejectedSubmissionGuard { controller: self });
11366 Err((error, function))
11367 }
11368 }
11369 }
11370
11371 fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11372 where
11373 F: FnOnce() -> T + Send + 'static,
11374 T: Send + 'static,
11375 {
11376 match self.try_spawn_blocking_tracked(function) {
11377 Ok(handle) => handle,
11378 Err((error, function, generation)) => {
11379 let _rejection = RejectedSubmissionGuard { controller: self };
11380 let _drop_context = RejectedSubmissionDropContext::enter();
11381 let _generation = generation.map(RuntimeGenerationGuard::enter);
11382 let _ = catch_unwind_contained(|| drop(function));
11383 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11384 message: error.to_string(),
11385 }))))
11386 }
11387 }
11388 }
11389
11390 #[cfg(test)]
11391 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11392 self
11393 .try_block_on(future)
11394 .unwrap_or_else(|error| panic!("{error}"))
11395 }
11396
11397 fn try_block_on_scope(
11398 &self,
11399 ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11400 let mut state = self
11401 .state
11402 .lock()
11403 .unwrap_or_else(std::sync::PoisonError::into_inner);
11404 let backend = match self.backend_locked(&mut state) {
11405 Ok(backend) => backend,
11406 Err(error) => {
11407 let generation = Self::begin_rejected_drop(&mut state);
11408 return Err((error, generation));
11409 }
11410 };
11411 let Some(registration) = backend.work.try_register_work() else {
11412 let generation = Self::begin_rejected_drop(&mut state);
11413 return Err((
11414 RuntimeConfigError(
11415 "the async runtime is stopped; call start before submitting work".to_string(),
11416 ),
11417 generation,
11418 ));
11419 };
11420 Ok((backend, registration))
11421 }
11422
11423 fn try_block_on(
11424 &self,
11425 future: Pin<&mut dyn Future<Output = ()>>,
11426 ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11427 let (backend, registration) = match self.try_block_on_scope() {
11428 Ok(scope) => scope,
11429 Err((error, _generation)) => {
11430 drop(RejectedSubmissionGuard { controller: self });
11431 return Err(error);
11432 }
11433 };
11434 let _registration = registration;
11435 let _generation = RuntimeGenerationGuard::enter(backend.generation());
11436 Ok(backend.block_on(future))
11437 }
11438
11439 fn start(&self) -> Result<(), RuntimeConfigError> {
11440 let mut state = self
11441 .state
11442 .lock()
11443 .unwrap_or_else(std::sync::PoisonError::into_inner);
11444 loop {
11445 Self::ensure_active_generation_current(&state)?;
11446 match &state.lifecycle {
11447 RuntimeLifecycle::Initial => {
11451 if state.rejected_drops == 0 {
11452 state.forget_shutdown_handoff();
11457 return Ok(());
11458 }
11459 if RejectedSubmissionDropContext::is_current() {
11460 return Err(RuntimeConfigError(
11461 "cannot start the async runtime while a rejected submission is being destroyed"
11462 .to_string(),
11463 ));
11464 }
11465 #[cfg(test)]
11466 run_before_initial_start_wait_test_hook();
11467 state = self
11468 .lifecycle_changed
11469 .wait(state)
11470 .unwrap_or_else(std::sync::PoisonError::into_inner);
11471 }
11472 RuntimeLifecycle::Running(_) => {
11473 state.forget_shutdown_handoff();
11474 return Ok(());
11475 }
11476 RuntimeLifecycle::Stopping(identity) => {
11477 if identity.is_current() {
11478 return Err(RuntimeConfigError(
11479 "cannot restart the async runtime from work in the generation being stopped"
11480 .to_string(),
11481 ));
11482 }
11483 state = self
11484 .lifecycle_changed
11485 .wait(state)
11486 .unwrap_or_else(std::sync::PoisonError::into_inner);
11487 }
11488 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11489 if RejectedSubmissionDropContext::is_current() {
11490 return Err(RuntimeConfigError(
11491 "cannot restart the async runtime while a rejected submission is being destroyed"
11492 .to_string(),
11493 ));
11494 }
11495 state = self
11496 .lifecycle_changed
11497 .wait(state)
11498 .unwrap_or_else(std::sync::PoisonError::into_inner);
11499 }
11500 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11501 if state.rejected_drops == 0 {
11502 if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11503 state.lifecycle = RuntimeLifecycle::Initial;
11504 state.forget_shutdown_handoff();
11505 return Ok(());
11506 }
11507 break;
11508 }
11509 if RejectedSubmissionDropContext::is_current() {
11510 return Err(RuntimeConfigError(
11511 "cannot restart the async runtime while a rejected submission is being destroyed"
11512 .to_string(),
11513 ));
11514 }
11515 state = self
11516 .lifecycle_changed
11517 .wait(state)
11518 .unwrap_or_else(std::sync::PoisonError::into_inner);
11519 }
11520 }
11521 }
11522 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11523 self.current_backend.store(Some(Arc::new(backend.clone())));
11524 state.lifecycle = RuntimeLifecycle::Running(backend);
11525 state.forget_shutdown_handoff();
11526 Ok(())
11527 }
11528
11529 fn options(&self) -> RuntimeOptions {
11530 self
11531 .state
11532 .lock()
11533 .unwrap_or_else(std::sync::PoisonError::into_inner)
11534 .options
11535 .clone()
11536 }
11537
11538 fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11539 let state = self
11540 .state
11541 .lock()
11542 .unwrap_or_else(std::sync::PoisonError::into_inner);
11543 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11544 return None;
11545 };
11546 match &backend.executor {
11547 RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11548 #[cfg(napi_runtime_os_threads)]
11549 RuntimeExecutor::MultiThread(_) => None,
11550 }
11551 }
11552
11553 fn request_current_thread_drain(&self) {
11554 if let Some(executor) = self.running_current_thread_executor() {
11555 executor.request_drain_if_queued();
11556 }
11557 }
11558
11559 fn retire_rejected_current_thread_work(&self) {
11560 if let Some(executor) = self.running_current_thread_executor() {
11561 executor.retire_rejected_work_off_waker_stack();
11562 }
11563 }
11564
11565 fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11566 let executor = {
11567 let state = self
11568 .state
11569 .lock()
11570 .unwrap_or_else(std::sync::PoisonError::into_inner);
11571 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11572 return;
11573 };
11574 match &backend.executor {
11575 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11576 #[cfg(napi_runtime_os_threads)]
11577 RuntimeExecutor::MultiThread(_) => return,
11578 }
11579 };
11580 executor.cancel_host_dispatch(failure);
11581 }
11582
11583 #[cfg(test)]
11584 fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11585 let executor = {
11586 let state = self
11587 .state
11588 .lock()
11589 .unwrap_or_else(std::sync::PoisonError::into_inner);
11590 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11591 return;
11592 };
11593 match &backend.executor {
11594 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11595 #[cfg(napi_runtime_os_threads)]
11596 RuntimeExecutor::MultiThread(_) => return,
11597 }
11598 };
11599 executor.cancel_host_dispatch_for_test(dispatch);
11600 }
11601
11602 fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11603 let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11604 self.drive_current_thread_dispatch_with_lease(dispatch, true)
11605 }
11606
11607 fn drive_current_thread_dispatch_with_lease(
11608 &self,
11609 dispatch: u64,
11610 mark_serviced: bool,
11611 ) -> Option<CurrentThreadTaskCallbackLease> {
11612 let mut host_turn = {
11613 let state = self
11614 .state
11615 .lock()
11616 .unwrap_or_else(std::sync::PoisonError::into_inner);
11617 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11618 return None;
11619 };
11620 let executor = match &backend.executor {
11621 RuntimeExecutor::CurrentThread(executor) => executor,
11622 #[cfg(napi_runtime_os_threads)]
11623 RuntimeExecutor::MultiThread(_) => return None,
11624 };
11625 let host_turn = executor.try_admit_host_turn(dispatch)?;
11626 if mark_serviced {
11627 CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11628 }
11629 host_turn
11630 };
11631 #[cfg(test)]
11632 run_after_current_thread_host_admission_test_hook();
11633 host_turn.drive();
11634 Some(CurrentThreadTaskCallbackLease::new(host_turn))
11635 }
11636
11637 #[cfg(test)]
11638 fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11639 self
11640 .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11641 .is_some()
11642 }
11643
11644 fn begin_shutdown(&self) -> Result<bool, RuntimeConfigError> {
11683 let _bounded_waits = BoundedWaits::enter();
11685 let backend = {
11686 let mut state = self
11687 .state
11688 .lock()
11689 .unwrap_or_else(std::sync::PoisonError::into_inner);
11690 Self::ensure_active_generation_current(&state)?;
11696 if let Some(drain) = &state.draining {
11697 let outstanding = match drain {
11698 ShutdownDrain::Backend(backend) => Some(backend.clone()),
11702 ShutdownDrain::Joining(backend) => backend.clone(),
11703 ShutdownDrain::ZeroBackend(_) => {
11711 if RejectedSubmissionDropContext::is_current() {
11712 return Err(RuntimeConfigError(
11713 "cannot shut down the async runtime while a rejected submission is being destroyed"
11714 .to_string(),
11715 ));
11716 }
11717 return Ok(state.rejected_drops != 0);
11718 }
11719 ShutdownDrain::Settled => None,
11720 };
11721 drop(state);
11731 return Ok(outstanding.is_some_and(|backend| backend.work_pending(PendingRead::Locked)));
11732 }
11733 loop {
11734 Self::ensure_active_generation_current(&state)?;
11735 match &state.lifecycle {
11736 RuntimeLifecycle::Initial => {
11737 if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11738 return Err(RuntimeConfigError(
11739 "cannot shut down the async runtime while a rejected submission is being destroyed"
11740 .to_string(),
11741 ));
11742 }
11743 let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11744 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11745 if state.rejected_drops != 0 {
11746 state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11753 self.lifecycle_changed.notify_all();
11754 return Ok(true);
11755 }
11756 state.lifecycle = target.into_lifecycle();
11757 state.open_phase_one(ShutdownDrain::Settled);
11758 self.lifecycle_changed.notify_all();
11759 return Ok(false);
11760 }
11761 RuntimeLifecycle::Running(backend) => {
11762 if backend.is_current() {
11763 return Err(RuntimeConfigError(
11764 "cannot shut down the async runtime from work running on that runtime".to_string(),
11765 ));
11766 }
11767 let identity = backend.stop_identity();
11768 let stop = backend.generation_stop();
11769 let stop_publication = stop.publication_guard();
11774 stop_publication.publish_stopping();
11775 let RuntimeLifecycle::Running(backend) =
11776 std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11777 else {
11778 unreachable!();
11779 };
11780 self.current_backend.store(None);
11781 state.open_phase_one(ShutdownDrain::Backend(backend.clone()));
11785 drop(stop_publication);
11789 break backend;
11790 }
11791 RuntimeLifecycle::Stopping(identity) => {
11792 if identity.is_current() {
11793 return Err(RuntimeConfigError(
11794 "cannot wait for async runtime shutdown from work in the generation being stopped"
11795 .to_string(),
11796 ));
11797 }
11798 state = self
11799 .lifecycle_changed
11800 .wait(state)
11801 .unwrap_or_else(std::sync::PoisonError::into_inner);
11802 }
11803 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11804 if RejectedSubmissionDropContext::is_current() {
11805 return Err(RuntimeConfigError(
11806 "cannot shut down the async runtime while a rejected submission is being destroyed"
11807 .to_string(),
11808 ));
11809 }
11810 state = self
11811 .lifecycle_changed
11812 .wait(state)
11813 .unwrap_or_else(std::sync::PoisonError::into_inner);
11814 }
11815 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11816 if state.rejected_drops == 0 {
11817 state.open_phase_one(ShutdownDrain::Settled);
11818 return Ok(false);
11819 }
11820 if RejectedSubmissionDropContext::is_current() {
11821 return Err(RuntimeConfigError(
11822 "cannot shut down the async runtime while a rejected submission is being destroyed"
11823 .to_string(),
11824 ));
11825 }
11826 let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11827 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11828 } else {
11829 ZeroBackendShutdownTarget::Stopped
11830 };
11831 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11835 state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11836 self.lifecycle_changed.notify_all();
11837 return Ok(true);
11838 }
11839 }
11840 }
11841 };
11842
11843 #[cfg(test)]
11844 run_after_generation_stop_publication_test_hook();
11845 backend.begin_shutdown();
11846 Ok(backend.work_pending(PendingRead::Locked))
11847 }
11848
11849 fn runtime_work_pending(&self) -> bool {
11866 let backend = {
11867 let state = match self.state.try_lock() {
11868 Ok(state) => state,
11869 Err(std::sync::TryLockError::Poisoned(poisoned)) => poisoned.into_inner(),
11870 Err(std::sync::TryLockError::WouldBlock) => return true,
11871 };
11872 match (&state.draining, &state.lifecycle) {
11873 (Some(ShutdownDrain::Backend(backend) | ShutdownDrain::Joining(Some(backend))), _) => {
11876 backend.clone()
11877 }
11878 (Some(ShutdownDrain::ZeroBackend(_)), _) => return state.rejected_drops != 0,
11884 (None, RuntimeLifecycle::Running(backend)) => backend.clone(),
11885 (Some(ShutdownDrain::Joining(None) | ShutdownDrain::Settled), _) | (None, _) => {
11890 return false;
11891 }
11892 }
11893 };
11894 backend.work_pending(PendingRead::NonBlocking)
11895 }
11896
11897 fn finish_shutdown(&self) -> Result<(), RuntimeConfigError> {
11937 let _bounded_waits = BoundedWaits::enter();
11938 const STOPPING_WAIT_ERROR: &str =
11939 "cannot wait for async runtime shutdown from work in the generation being stopped";
11940 const REJECTED_DROP_ERROR: &str =
11941 "cannot shut down the async runtime while a rejected submission is being destroyed";
11942 const NO_PHASE_ERROR: &str =
11943 "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown";
11944
11945 let backend = {
11946 let mut state = self
11947 .state
11948 .lock()
11949 .unwrap_or_else(std::sync::PoisonError::into_inner);
11950 match &state.draining {
11953 Some(ShutdownDrain::Settled) => {
11954 state.draining = None;
11956 return Ok(());
11957 }
11958 Some(ShutdownDrain::Backend(backend)) => {
11959 if backend.is_current() {
11960 return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
11961 }
11962 }
11963 Some(ShutdownDrain::ZeroBackend(_)) => loop {
11971 let target = match (&state.draining, &state.lifecycle) {
11972 (
11973 Some(ShutdownDrain::ZeroBackend(parked)),
11974 RuntimeLifecycle::StoppingWithoutBackend(published),
11975 ) if published == parked => *parked,
11976 _ => return Ok(()),
11980 };
11981 if state.rejected_drops == 0 {
11982 state.draining = None;
11987 state.lifecycle = target.into_lifecycle();
11988 self.lifecycle_changed.notify_all();
11989 return Ok(());
11990 }
11991 if RejectedSubmissionDropContext::is_current() {
11992 return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
11993 }
11994 state = self
11999 .lifecycle_changed
12000 .wait(state)
12001 .unwrap_or_else(std::sync::PoisonError::into_inner);
12002 },
12003 Some(ShutdownDrain::Joining(_)) | None => {
12008 if !state.phase_ever_opened {
12009 return Err(RuntimeConfigError(NO_PHASE_ERROR.to_string()));
12013 }
12014 loop {
12022 match &state.lifecycle {
12023 RuntimeLifecycle::Stopping(identity) => {
12024 if identity.is_current() {
12025 return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
12026 }
12027 }
12028 RuntimeLifecycle::StoppingWithoutBackend(_) => {
12029 if RejectedSubmissionDropContext::is_current() {
12030 return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
12031 }
12032 }
12033 RuntimeLifecycle::StoppedBeforeFirstUse
12038 | RuntimeLifecycle::Stopped
12039 | RuntimeLifecycle::Initial
12040 | RuntimeLifecycle::Running(_) => return Ok(()),
12041 }
12042 state = self
12043 .lifecycle_changed
12044 .wait(state)
12045 .unwrap_or_else(std::sync::PoisonError::into_inner);
12046 }
12047 }
12048 }
12049 let Some(ShutdownDrain::Backend(backend)) = state.draining.take() else {
12057 unreachable!();
12058 };
12059 state.draining = Some(ShutdownDrain::Joining(Some(backend.clone())));
12060 backend
12061 };
12062
12063 #[cfg(test)]
12064 run_after_shutdown_drain_taken_test_hook();
12065 backend.wait_until_idle();
12066
12067 #[cfg(napi_runtime_os_threads)]
12068 let worker_lifecycle = backend.worker_lifecycle();
12069 {
12077 let mut state = self
12078 .state
12079 .lock()
12080 .unwrap_or_else(std::sync::PoisonError::into_inner);
12081 debug_assert!(
12082 matches!(state.draining, Some(ShutdownDrain::Joining(Some(_)))),
12083 "nothing may disturb an in-flight drain marker before its publication"
12084 );
12085 state.draining = Some(ShutdownDrain::Joining(None));
12086 }
12087 drop(backend);
12088
12089 #[cfg(napi_runtime_os_threads)]
12090 if let Some(worker_lifecycle) = worker_lifecycle {
12091 worker_lifecycle.wait_for_all_workers();
12092 }
12093
12094 let mut state = self
12095 .state
12096 .lock()
12097 .unwrap_or_else(std::sync::PoisonError::into_inner);
12098 while state.rejected_drops != 0 {
12099 state = self
12100 .lifecycle_changed
12101 .wait(state)
12102 .unwrap_or_else(std::sync::PoisonError::into_inner);
12103 }
12104 debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
12105 debug_assert!(
12106 matches!(state.draining, Some(ShutdownDrain::Joining(None))),
12107 "nothing may retire an in-flight drain marker but its own publication"
12108 );
12109 state.draining = None;
12113 state.lifecycle = RuntimeLifecycle::Stopped;
12114 self.lifecycle_changed.notify_all();
12115 Ok(())
12116 }
12117
12118 fn shutdown(&self) -> Result<(), RuntimeConfigError> {
12131 self.begin_shutdown()?;
12132 #[cfg(test)]
12133 run_between_shutdown_phases_test_hook();
12134 self.finish_shutdown()
12135 }
12136}
12137
12138static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
12139
12140pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
12141 RUNTIME.configure(options)
12142}
12143
12144pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
12145 RUNTIME.configure_partial(patch)
12146}
12147
12148pub fn configured_options() -> RuntimeOptions {
12149 RUNTIME.options()
12150}
12151
12152pub fn is_multi_threaded() -> bool {
12153 RUNTIME.options().flavor == RuntimeFlavor::MultiThread
12154}
12155
12156fn spawn_registered<F, T>(
12157 backend: &RuntimeBackend,
12158 metrics: Arc<RuntimeMetrics>,
12159 future: F,
12160 registration: (AbortRegistration, GenerationWorkGuard),
12161) -> JoinHandle<T>
12162where
12163 F: Future<Output = T> + Send + 'static,
12164 T: Send + 'static,
12165{
12166 metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
12167 let generation = backend.generation();
12168 let (abort_registration, work_registration) = registration;
12169 #[cfg(napi_runtime_os_threads)]
12170 let dependency = Arc::new(TaskDependency::new_task(generation));
12171 #[cfg(napi_runtime_os_threads)]
12172 let task_dependency = Arc::clone(&dependency);
12173 let wrapped = async move {
12174 let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
12175 let mut abortable = std::pin::pin!(abortable);
12176 let polled = futures::future::poll_fn(|cx| {
12177 #[cfg(napi_runtime_os_threads)]
12178 task_dependency.begin_poll();
12179 #[cfg(napi_runtime_os_threads)]
12180 let _dependency = TaskDependencyGuard::enter(&task_dependency);
12181 abortable.as_mut().poll(cx)
12182 });
12183 match AssertUnwindSafe(polled).catch_unwind().await {
12184 Ok(Ok(output)) => {
12185 metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
12186 Ok(ContainedTaskOutput::new(output, generation))
12187 }
12188 Ok(Err(_)) => Err(JoinError {
12189 message: "async runtime stopped before the task completed".to_string(),
12190 }),
12191 Err(panic) => {
12192 metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
12193 Err(panic_payload_to_join_error(panic))
12194 }
12195 }
12196 };
12197 let scheduler = backend.downgrade_executor();
12198 let (runnable, task) = async_task::spawn(
12202 RegisteredTaskFuture::new(wrapped, work_registration, generation),
12203 move |runnable| {
12204 scheduler.schedule(runnable);
12205 },
12206 );
12207 backend.schedule_submission(runnable);
12208 JoinHandle(JoinHandleInner::Task {
12209 task: task.fallible(),
12210 awaiter: None,
12211 #[cfg(napi_runtime_os_threads)]
12212 dependency,
12213 })
12214}
12215
12216pub fn spawn<F, T>(future: F) -> JoinHandle<T>
12217where
12218 F: Future<Output = T> + Send + 'static,
12219 T: Send + 'static,
12220{
12221 RUNTIME.spawn(future)
12222}
12223
12224pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12236where
12237 F: Future<Output = T> + Send + 'static,
12238 T: Send + 'static,
12239{
12240 RUNTIME.try_spawn(future)
12241}
12242
12243pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
12244where
12245 F: Future<Output = ()> + Send + 'static,
12246{
12247 RUNTIME.try_spawn_detached(future)
12248}
12249
12250pub fn spawn_detached<F>(future: F)
12251where
12252 F: Future<Output = ()> + Send + 'static,
12253{
12254 spawn(future).detach();
12255}
12256
12257pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
12258where
12259 F: FnOnce() -> T + Send + 'static,
12260 T: Send + 'static,
12261{
12262 RUNTIME.spawn_blocking(function)
12263}
12264
12265pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12273where
12274 F: FnOnce() -> T + Send + 'static,
12275 T: Send + 'static,
12276{
12277 RUNTIME.try_spawn_blocking(function)
12278}
12279
12280fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
12281 let (backend, registration) = match controller.try_block_on_scope() {
12282 Ok(scope) => scope,
12283 Err((error, generation)) => {
12284 let _rejection = RejectedSubmissionGuard { controller };
12285 let _drop_context = RejectedSubmissionDropContext::enter();
12286 let _generation = generation.map(RuntimeGenerationGuard::enter);
12287 let _ = catch_unwind_contained(|| drop(future));
12288 panic!("{error}");
12289 }
12290 };
12291 let generation = backend.generation();
12292 let _registration = registration;
12293 let _generation = RuntimeGenerationGuard::enter(generation);
12294 let mut output = None;
12295 {
12296 let future = ContainedFuture::new(future, generation);
12297 let mut erased = std::pin::pin!(async {
12298 output = Some(future.await);
12299 });
12300 let _ = backend.block_on(erased.as_mut());
12301 }
12306 output.expect("async runtime returned before the future completed")
12307}
12308
12309pub fn block_on<F: Future>(future: F) -> F::Output {
12310 block_on_with_controller(&RUNTIME, future)
12311}
12312
12313pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
12314 try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
12315}
12316
12317pub fn try_block_on_dyn(
12327 future: Pin<&mut dyn Future<Output = ()>>,
12328) -> Result<(), RuntimeConfigError> {
12329 match RUNTIME.try_block_on(future)? {
12330 BlockOnOutcome::Completed => Ok(()),
12331 BlockOnOutcome::Stopped => Err(RuntimeConfigError(
12332 "the async runtime stopped before block_on completed its future".to_string(),
12333 )),
12334 }
12335}
12336
12337pub fn start() -> Result<(), RuntimeConfigError> {
12338 RUNTIME.start()
12339}
12340
12341pub fn shutdown() -> Result<(), RuntimeConfigError> {
12349 RUNTIME.shutdown()
12350}
12351
12352pub fn begin_shutdown() -> Result<bool, RuntimeConfigError> {
12375 RUNTIME.begin_shutdown()
12376}
12377
12378pub fn runtime_work_pending() -> bool {
12389 RUNTIME.runtime_work_pending()
12390}
12391
12392pub fn finish_shutdown() -> Result<(), RuntimeConfigError> {
12405 RUNTIME.finish_shutdown()
12406}
12407
12408fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
12409 for failure in completion.failed_dispatches {
12410 RUNTIME.cancel_current_thread_dispatch(failure);
12411 }
12412 if completion.redispatch {
12413 RUNTIME.request_current_thread_drain();
12414 }
12415 RUNTIME.retire_rejected_current_thread_work();
12421}
12422
12423pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12432 complete_current_thread_task_delivery(
12433 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
12434 );
12435}
12436
12437pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12443 complete_current_thread_task_delivery(
12444 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
12445 );
12446}
12447
12448pub fn cancel_current_thread_task_dispatch(delivery: u64) {
12455 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
12456}
12457
12458pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
12467 RUNTIME.drive_current_thread_tasks(delivery)
12468}
12469
12470pub fn reset_metrics() {
12471 RUNTIME.metrics.reset();
12472}
12473
12474pub fn metrics() -> RuntimeMetricsSnapshot {
12475 let options = RUNTIME.options();
12476 RUNTIME.metrics.snapshot(&options)
12477}
12478
12479#[cfg(test)]
12480#[cfg_attr(target_family = "wasm", allow(dead_code))]
12485mod tests {
12486 use super::*;
12487
12488 static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12512 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12513 LazyLock::new(Mutex::default);
12514 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12515 static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12516 static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12517 static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12518 LazyLock::new(Mutex::default);
12519 static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12520 static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12521 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12522 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12523 LazyLock::new(Mutex::default);
12524 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
12525 static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
12526 CurrentThreadTaskDriverRegistry,
12527 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12528 static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12529 static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12530 static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12531 LazyLock::new(Mutex::default);
12532 static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12533 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12534 static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12535 LazyLock::new(Mutex::default);
12536 static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12537 static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12538 LazyLock::new(Mutex::default);
12539 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12540 LazyLock::new(Mutex::default);
12541 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
12542 CurrentThreadTaskDriverRegistry,
12543 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12544 static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12545 LazyLock::new(Mutex::default);
12546 static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12547 static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12548 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12549 static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12550 CurrentThreadTaskDriverRegistry,
12551 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12552 static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12553 CurrentThreadTaskDriverRegistry,
12554 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12555 static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12556 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12557 static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12558 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12559 static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12560 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12561 static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12562 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12563 static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12564 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12565 static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
12566 CurrentThreadTaskDriverRegistry,
12567 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12568 static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12569 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12570 static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12571
12572 thread_local! {
12573 static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
12574 const { std::cell::Cell::new(0) };
12575 static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
12576 const { std::cell::Cell::new(0) };
12577 }
12578
12579 #[cfg(napi_runtime_os_threads)]
12580 struct StopPollProbe {
12581 polls: Arc<AtomicUsize>,
12582 first_poll: Option<std::sync::mpsc::Sender<()>>,
12583 }
12584
12585 #[cfg(napi_runtime_os_threads)]
12586 impl Future for StopPollProbe {
12587 type Output = ();
12588
12589 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12590 self.polls.fetch_add(1, Ordering::SeqCst);
12591 if let Some(first_poll) = self.first_poll.take() {
12592 first_poll.send(()).unwrap();
12593 }
12594 Poll::Pending
12595 }
12596 }
12597
12598 #[cfg(napi_runtime_os_threads)]
12599 struct PanickingJoinAwaiter {
12600 wakes: Arc<AtomicUsize>,
12601 drops: Arc<AtomicUsize>,
12602 }
12603
12604 #[cfg(napi_runtime_os_threads)]
12605 impl std::task::Wake for PanickingJoinAwaiter {
12606 fn wake(self: Arc<Self>) {
12607 self.wakes.fetch_add(1, Ordering::SeqCst);
12608 panic!("intentional JoinHandle awaiter wake panic");
12609 }
12610
12611 fn wake_by_ref(self: &Arc<Self>) {
12612 self.wakes.fetch_add(1, Ordering::SeqCst);
12613 panic!("intentional JoinHandle awaiter wake-by-ref panic");
12614 }
12615 }
12616
12617 #[cfg(napi_runtime_os_threads)]
12618 impl Drop for PanickingJoinAwaiter {
12619 fn drop(&mut self) {
12620 self.drops.fetch_add(1, Ordering::SeqCst);
12621 panic!("intentional JoinHandle awaiter destructor panic");
12622 }
12623 }
12624
12625 #[cfg(napi_runtime_os_threads)]
12626 fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
12627 Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
12628 }
12629
12630 #[cfg(napi_runtime_os_threads)]
12631 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12632 enum JoinAwaiterWakerEventKind {
12633 Clone,
12634 Wake,
12635 WakeByRef,
12636 Drop,
12637 }
12638
12639 #[cfg(napi_runtime_os_threads)]
12640 struct JoinAwaiterWakerState {
12641 events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
12642 }
12643
12644 #[cfg(napi_runtime_os_threads)]
12645 impl JoinAwaiterWakerState {
12646 fn record(&self, kind: JoinAwaiterWakerEventKind) {
12647 self
12648 .events
12649 .lock()
12650 .unwrap_or_else(std::sync::PoisonError::into_inner)
12651 .push((kind, active_runtime_generation()));
12652 }
12653
12654 fn clear(&self) {
12655 self
12656 .events
12657 .lock()
12658 .unwrap_or_else(std::sync::PoisonError::into_inner)
12659 .clear();
12660 }
12661
12662 fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
12663 self
12664 .events
12665 .lock()
12666 .unwrap_or_else(std::sync::PoisonError::into_inner)
12667 .clone()
12668 }
12669 }
12670
12671 #[cfg(napi_runtime_os_threads)]
12672 struct JoinAwaiterWakerToken {
12673 state: Arc<JoinAwaiterWakerState>,
12674 }
12675
12676 #[cfg(napi_runtime_os_threads)]
12677 unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
12678 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12679 token.state.record(JoinAwaiterWakerEventKind::Clone);
12680 let clone = Box::new(JoinAwaiterWakerToken {
12681 state: Arc::clone(&token.state),
12682 });
12683 std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
12684 }
12685
12686 #[cfg(napi_runtime_os_threads)]
12687 unsafe fn wake_join_awaiter_waker(data: *const ()) {
12688 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12689 token.state.record(JoinAwaiterWakerEventKind::Wake);
12690 }
12691
12692 #[cfg(napi_runtime_os_threads)]
12693 unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
12694 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12695 token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
12696 }
12697
12698 #[cfg(napi_runtime_os_threads)]
12699 unsafe fn drop_join_awaiter_waker(data: *const ()) {
12700 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12701 token.state.record(JoinAwaiterWakerEventKind::Drop);
12702 }
12703
12704 #[cfg(napi_runtime_os_threads)]
12705 static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
12706 clone_join_awaiter_waker,
12707 wake_join_awaiter_waker,
12708 wake_join_awaiter_waker_by_ref,
12709 drop_join_awaiter_waker,
12710 );
12711
12712 #[cfg(napi_runtime_os_threads)]
12713 fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
12714 let token = Box::new(JoinAwaiterWakerToken { state });
12715 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
12716 unsafe { Waker::from_raw(raw) }
12717 }
12718
12719 #[cfg(napi_runtime_os_threads)]
12720 fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
12721 wait_until("the block_on driver to sleep", || {
12722 stop
12723 .parkers
12724 .lock()
12725 .unwrap_or_else(std::sync::PoisonError::into_inner)
12726 .iter()
12727 .filter_map(Weak::upgrade)
12728 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12729 });
12730 }
12731
12732 #[cfg(panic = "unwind")]
12733 #[test]
12734 fn unique_ids_fail_before_wraparound() {
12735 let counter = AtomicU64::new(u64::MAX - 1);
12736 assert_eq!(
12737 next_unique_id(&counter, "test id space exhausted"),
12738 u64::MAX - 1
12739 );
12740
12741 let exhausted = catch_unwind(AssertUnwindSafe(|| {
12742 next_unique_id(&counter, "test id space exhausted");
12743 }));
12744 assert!(exhausted.is_err());
12745 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
12746 }
12747
12748 #[cfg(panic = "unwind")]
12749 #[test]
12750 fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
12751 let work = GenerationWork::new();
12752 {
12753 let mut state = work.state.lock().unwrap();
12754 state.next_task_id = u64::MAX;
12755 }
12756
12757 let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
12758 assert!(exhausted.is_err());
12759
12760 let state = work.state.lock().unwrap();
12761 assert_eq!(state.next_task_id, u64::MAX);
12762 assert_eq!(state.active, 0);
12763 assert!(state.abort_handles.is_empty());
12764 }
12765
12766 struct GenerationStateLockProbe {
12771 work: Weak<GenerationWork>,
12772 dropped_with_lock_free: Arc<Mutex<Option<bool>>>,
12773 }
12774
12775 #[expect(
12776 clippy::manual_noop_waker,
12777 reason = "the test observes the waker payload's Drop"
12778 )]
12779 impl std::task::Wake for GenerationStateLockProbe {
12780 fn wake(self: Arc<Self>) {}
12781 }
12782
12783 impl Drop for GenerationStateLockProbe {
12784 fn drop(&mut self) {
12785 let lock_free = self
12786 .work
12787 .upgrade()
12788 .is_some_and(|work| work.state.try_lock().is_ok());
12789 *self.dropped_with_lock_free.lock().unwrap() = Some(lock_free);
12790 }
12791 }
12792
12793 fn register_task_with_state_lock_probe(
12797 work: &Arc<GenerationWork>,
12798 ) -> (GenerationWorkGuard, Arc<Mutex<Option<bool>>>) {
12799 let (registration, guard) = work
12800 .try_register_async()
12801 .expect("an open generation must accept the task");
12802 let dropped_with_lock_free = Arc::new(Mutex::new(None));
12803 let waker = Waker::from(Arc::new(GenerationStateLockProbe {
12804 work: Arc::downgrade(work),
12805 dropped_with_lock_free: Arc::clone(&dropped_with_lock_free),
12806 }));
12807 let mut task = std::pin::pin!(Abortable::new(std::future::pending::<()>(), registration));
12808 assert!(
12809 task
12810 .as_mut()
12811 .poll(&mut Context::from_waker(&waker))
12812 .is_pending()
12813 );
12814 (guard, dropped_with_lock_free)
12815 }
12816
12817 #[test]
12818 fn generation_work_guard_drops_its_abort_handle_outside_the_state_lock() {
12819 let work = GenerationWork::new();
12824 let (guard, dropped_with_lock_free) = register_task_with_state_lock_probe(&work);
12825 assert_eq!(
12826 *dropped_with_lock_free.lock().unwrap(),
12827 None,
12828 "the handle the generation keeps must still own the abort state"
12829 );
12830
12831 drop(guard);
12832 assert_eq!(
12833 *dropped_with_lock_free.lock().unwrap(),
12834 Some(true),
12835 "the guard must drop the handle it removed after releasing the state lock"
12836 );
12837 }
12838
12839 #[test]
12840 fn close_and_abort_takes_the_abort_handles_out_of_the_state() {
12841 let work = GenerationWork::new();
12845 let (registration, guard) = work
12846 .try_register_async()
12847 .expect("an open generation must accept the task");
12848 let task = Abortable::new(std::future::pending::<()>(), registration);
12849
12850 work.close_and_abort();
12851 assert!(task.is_aborted(), "close must abort the registered task");
12852 assert!(
12853 work.state.lock().unwrap().abort_handles.is_empty(),
12854 "close must take the handles out of the state, not leave them for the guards"
12855 );
12856 drop(task);
12857 drop(guard);
12858 assert_eq!(work.state.lock().unwrap().active, 0);
12859 }
12860
12861 #[cfg(panic = "unwind")]
12862 #[test]
12863 fn caught_panic_payload_destructor_runs() {
12864 struct DropSignal(Arc<AtomicBool>);
12865
12866 impl Drop for DropSignal {
12867 fn drop(&mut self) {
12868 self.0.store(true, Ordering::SeqCst);
12869 }
12870 }
12871
12872 let dropped = Arc::new(AtomicBool::new(false));
12873 let payload = DropSignal(Arc::clone(&dropped));
12874
12875 assert!(
12876 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
12877 "the original panic must remain contained"
12878 );
12879 assert!(
12880 dropped.load(Ordering::SeqCst),
12881 "an ordinary caught panic payload must be destroyed"
12882 );
12883 }
12884
12885 #[cfg(panic = "unwind")]
12886 #[test]
12887 fn hostile_caught_panic_payload_destructor_remains_contained() {
12888 struct PanicOnDrop(Arc<AtomicBool>);
12889
12890 impl Drop for PanicOnDrop {
12891 fn drop(&mut self) {
12892 self.0.store(true, Ordering::SeqCst);
12893 panic!("caught panic payload destructor escaped");
12894 }
12895 }
12896
12897 let dropped = Arc::new(AtomicBool::new(false));
12898 let payload = PanicOnDrop(Arc::clone(&dropped));
12899 let result = catch_unwind(AssertUnwindSafe(|| {
12900 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12901 }));
12902
12903 assert!(
12904 result.is_ok(),
12905 "the payload destructor's nested panic must remain contained"
12906 );
12907 assert!(
12908 result.unwrap().is_none(),
12909 "the original panic must remain contained"
12910 );
12911 assert!(
12912 dropped.load(Ordering::SeqCst),
12913 "the hostile payload destructor must be attempted"
12914 );
12915 }
12916
12917 fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12918 CurrentThreadHostDispatchResult::Accepted
12919 }
12920
12921 fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12922 CurrentThreadHostDispatchResult::Unavailable
12923 }
12924
12925 fn count_current_thread_recovery_host_dispatch(
12926 _dispatch: u64,
12927 ) -> CurrentThreadHostDispatchResult {
12928 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
12929 CurrentThreadHostDispatchResult::Accepted
12930 }
12931
12932 fn reset_current_thread_recovery_host_dispatches() {
12933 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12934 }
12935
12936 fn current_thread_recovery_host_dispatches() -> usize {
12937 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
12938 }
12939
12940 fn accept_only_first_current_thread_host_dispatch(
12941 _dispatch: u64,
12942 ) -> CurrentThreadHostDispatchResult {
12943 if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
12944 let previous = dispatches.get();
12945 dispatches.set(previous + 1);
12946 previous == 0
12947 }) {
12948 CurrentThreadHostDispatchResult::Accepted
12949 } else {
12950 CurrentThreadHostDispatchResult::Unavailable
12951 }
12952 }
12953
12954 fn reset_current_thread_one_shot_host_dispatches() {
12955 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12956 }
12957
12958 fn current_thread_one_shot_host_dispatches() -> usize {
12959 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
12960 }
12961
12962 fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12963 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12964 CurrentThreadHostDispatchResult::Accepted
12965 }
12966
12967 fn count_current_thread_blocking_budget_host_dispatch(
12968 _dispatch: u64,
12969 ) -> CurrentThreadHostDispatchResult {
12970 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12971 CurrentThreadHostDispatchResult::Accepted
12972 }
12973
12974 fn count_current_thread_admission_host_dispatch(
12975 _dispatch: u64,
12976 ) -> CurrentThreadHostDispatchResult {
12977 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12978 CurrentThreadHostDispatchResult::Accepted
12979 }
12980
12981 fn count_current_thread_interleaved_recovery_host_dispatch(
12982 _dispatch: u64,
12983 ) -> CurrentThreadHostDispatchResult {
12984 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12985 CurrentThreadHostDispatchResult::Accepted
12986 }
12987
12988 fn dispatch_late_registration_current_thread_tasks(
12989 dispatch: u64,
12990 ) -> CurrentThreadHostDispatchResult {
12991 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
12992 }
12993
12994 fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12995 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
12996 .lock()
12997 .unwrap()
12998 .push(dispatch);
12999 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
13000 }
13001
13002 fn dispatch_authoritative_panic_current_thread_tasks(
13003 dispatch: u64,
13004 ) -> CurrentThreadHostDispatchResult {
13005 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13006 .lock()
13007 .unwrap()
13008 .push(dispatch);
13009 match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
13010 0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
13011 2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
13012 _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
13013 }
13014 }
13015
13016 fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
13017 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13018 }
13019
13020 fn dispatch_waker_stack_failure_current_thread_tasks(
13021 dispatch: u64,
13022 ) -> CurrentThreadHostDispatchResult {
13023 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13024 }
13025
13026 fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
13027 dispatch: u64,
13028 ) -> CurrentThreadHostDispatchResult {
13029 CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13030 }
13031
13032 fn dispatch_submission_failure_current_thread_tasks(
13033 dispatch: u64,
13034 ) -> CurrentThreadHostDispatchResult {
13035 CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13036 }
13037
13038 fn dispatch_retirement_turn_current_thread_tasks(
13039 dispatch: u64,
13040 ) -> CurrentThreadHostDispatchResult {
13041 CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
13042 }
13043
13044 fn dispatch_nested_retirement_current_thread_tasks(
13045 dispatch: u64,
13046 ) -> CurrentThreadHostDispatchResult {
13047 CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
13048 }
13049
13050 fn dispatch_sustained_producer_current_thread_tasks(
13051 dispatch: u64,
13052 ) -> CurrentThreadHostDispatchResult {
13053 CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
13054 }
13055
13056 fn dispatch_retirement_retry_current_thread_tasks(
13057 dispatch: u64,
13058 ) -> CurrentThreadHostDispatchResult {
13059 CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
13060 }
13061
13062 fn dispatch_final_retirement_offer_current_thread_tasks(
13063 dispatch: u64,
13064 ) -> CurrentThreadHostDispatchResult {
13065 CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
13066 }
13067
13068 fn dispatch_inline_final_offer_current_thread_tasks(
13069 dispatch: u64,
13070 ) -> CurrentThreadHostDispatchResult {
13071 CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
13072 }
13073
13074 fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
13075 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13076 .lock()
13077 .unwrap()
13078 .push(dispatch);
13079 assert!(
13080 !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13081 "injected CurrentThread host dispatch panic"
13082 );
13083 CurrentThreadHostDispatchResult::Accepted
13084 }
13085
13086 fn panic_once_current_thread_armed_republish_host_dispatch(
13087 dispatch: u64,
13088 ) -> CurrentThreadHostDispatchResult {
13089 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13090 .lock()
13091 .unwrap()
13092 .push(dispatch);
13093 assert!(
13094 !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13095 "injected armed CurrentThread host dispatch panic"
13096 );
13097 CurrentThreadHostDispatchResult::Accepted
13098 }
13099
13100 fn record_current_thread_republish_rearm_dispatch(
13101 dispatch: u64,
13102 ) -> CurrentThreadHostDispatchResult {
13103 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13104 .lock()
13105 .unwrap()
13106 .push(dispatch);
13107 CurrentThreadHostDispatchResult::Accepted
13108 }
13109
13110 fn reject_initial_current_thread_publication_once(
13111 dispatch: u64,
13112 ) -> CurrentThreadHostDispatchResult {
13113 let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13114 .lock()
13115 .unwrap();
13116 dispatches.push(dispatch);
13117 if dispatches.len() == 1 {
13118 CurrentThreadHostDispatchResult::Unavailable
13119 } else {
13120 CurrentThreadHostDispatchResult::Accepted
13121 }
13122 }
13123
13124 fn reject_current_thread_replacement_publication_once(
13125 dispatch: u64,
13126 ) -> CurrentThreadHostDispatchResult {
13127 let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13128 .lock()
13129 .unwrap();
13130 dispatches.push(dispatch);
13131 if dispatches.len() == 2 {
13132 CurrentThreadHostDispatchResult::Unavailable
13133 } else {
13134 CurrentThreadHostDispatchResult::Accepted
13135 }
13136 }
13137
13138 fn fail_current_thread_replacement_publication_once(
13139 dispatch: u64,
13140 ) -> CurrentThreadHostDispatchResult {
13141 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13142 .lock()
13143 .unwrap()
13144 .push(dispatch);
13145 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
13146 }
13147
13148 fn count_current_thread_shutdown_republish_host_dispatch(
13149 _dispatch: u64,
13150 ) -> CurrentThreadHostDispatchResult {
13151 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13152 CurrentThreadHostDispatchResult::Accepted
13153 }
13154
13155 fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13156 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13157 CurrentThreadHostDispatchResult::Accepted
13158 }
13159
13160 fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
13161 let pending = executor.dispatch_pending.load(Ordering::Acquire);
13162 if pending != 0 {
13163 return pending;
13164 }
13165 let dispatch = next_current_thread_dispatch_id();
13166 match executor.dispatch_pending.compare_exchange(
13167 0,
13168 dispatch,
13169 Ordering::AcqRel,
13170 Ordering::Acquire,
13171 ) {
13172 Ok(_) => dispatch,
13173 Err(pending) => pending,
13174 }
13175 }
13176
13177 fn current_thread_dispatch_failure(
13178 dispatch: u64,
13179 epoch: u64,
13180 ) -> CurrentThreadTaskDispatchFailure {
13181 CurrentThreadTaskDispatchFailure { dispatch, epoch }
13182 }
13183
13184 fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
13185 let executor = controller
13186 .running_current_thread_executor()
13187 .expect("the test controller must have a running CurrentThread executor");
13188 current_thread_dispatch(&executor)
13189 }
13190
13191 #[test]
13192 fn current_thread_executor_drives_spawned_tasks() {
13193 let metrics = Arc::new(RuntimeMetrics::default());
13194 let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
13195 let scheduler = Arc::clone(&executor);
13196 let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
13197 scheduler.schedule(runnable);
13198 });
13199
13200 executor.schedule(runnable);
13201
13202 let mut output = None;
13203 {
13204 let mut future = std::pin::pin!(async {
13205 output = Some(task.await);
13206 });
13207 executor.block_on(future.as_mut());
13208 }
13209 assert_eq!(output, Some(42));
13210 assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
13211 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
13212 }
13213
13214 #[cfg(napi_runtime_os_threads)]
13215 #[test]
13216 fn current_thread_shutdown_wake_does_not_repoll_user_future() {
13217 use std::sync::mpsc;
13218
13219 let executor = Arc::new(CurrentThreadExecutor::with_detection(
13220 Arc::new(RuntimeMetrics::default()),
13221 false,
13222 None,
13223 ));
13224 let polls = Arc::new(AtomicUsize::new(0));
13225 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13226 let (outcome_tx, outcome_rx) = mpsc::channel();
13227 let driver_executor = Arc::clone(&executor);
13228 let driver_polls = Arc::clone(&polls);
13229 let driver = std::thread::spawn(move || {
13230 let mut future = std::pin::pin!(StopPollProbe {
13231 polls: driver_polls,
13232 first_poll: Some(first_poll_tx)
13233 });
13234 outcome_tx
13235 .send(driver_executor.block_on(future.as_mut()))
13236 .unwrap();
13237 });
13238
13239 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13240 wait_for_generation_parker_to_sleep(&executor.stop);
13241 executor.stop.begin_shutdown();
13242
13243 assert_eq!(
13244 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13245 BlockOnOutcome::Stopped
13246 );
13247 driver.join().unwrap();
13248 assert_eq!(
13249 polls.load(Ordering::SeqCst),
13250 1,
13251 "a shutdown wake must be checked before the next CurrentThread user poll"
13252 );
13253 }
13254
13255 #[cfg(napi_runtime_os_threads)]
13256 #[test]
13257 fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
13258 use std::sync::mpsc;
13259
13260 let metrics = Arc::new(RuntimeMetrics::default());
13261 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13262 Arc::clone(&metrics),
13263 accept_current_thread_host_dispatch,
13264 ));
13265 let polls = Arc::new(AtomicUsize::new(0));
13266 let task_polls = Arc::clone(&polls);
13267 let (runnable, task) = async_task::spawn(
13268 async move {
13269 task_polls.fetch_add(1, Ordering::SeqCst);
13270 },
13271 |_| {},
13272 );
13273 executor.schedule(runnable);
13274 let dispatch = current_thread_dispatch(&executor);
13275
13276 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
13277 let (release_claim_tx, release_claim_rx) = mpsc::channel();
13278 let driver_executor = Arc::clone(&executor);
13279 let driver = std::thread::spawn(move || {
13280 CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
13281 *slot.borrow_mut() = Some(Box::new(move || {
13282 claim_locked_tx.send(()).unwrap();
13283 release_claim_rx.recv().unwrap();
13284 }));
13285 });
13286 driver_executor.drive_host_turn_for_dispatch(dispatch);
13287 });
13288 claim_locked_rx
13289 .recv_timeout(Duration::from_secs(2))
13290 .expect("the admitted host turn must pause before its runnable claim");
13291
13292 executor.stop.publish_stopping();
13293 release_claim_tx.send(()).unwrap();
13294 driver.join().unwrap();
13295
13296 assert_eq!(
13297 polls.load(Ordering::SeqCst),
13298 0,
13299 "stop publication must outrank a later runnable claim by an already-admitted host turn"
13300 );
13301 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13302
13303 executor.begin_shutdown();
13304 assert!(
13305 futures::executor::block_on(task.fallible()).is_none(),
13306 "shutdown must cancel the runnable rejected by the stopped claim"
13307 );
13308 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13309 }
13310
13311 #[cfg(napi_runtime_os_threads)]
13312 #[test]
13313 fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
13314 use std::sync::mpsc;
13315
13316 let metrics = Arc::new(RuntimeMetrics::default());
13317 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13318 Arc::clone(&metrics),
13319 accept_current_thread_host_dispatch,
13320 ));
13321 let polls = Arc::new(AtomicUsize::new(0));
13322 let task_polls = Arc::clone(&polls);
13323 let (runnable, task) = async_task::spawn(
13324 async move {
13325 task_polls.fetch_add(1, Ordering::SeqCst);
13326 },
13327 |_| {},
13328 );
13329 executor.schedule(runnable);
13330
13331 let queue = executor
13332 .queue
13333 .lock()
13334 .unwrap_or_else(std::sync::PoisonError::into_inner);
13335 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
13336 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
13337 let cancelling_executor = Arc::clone(&executor);
13338 let cancellation = std::thread::spawn(move || {
13339 let scheduler = cancelling_executor
13340 .scheduler_idle_lock
13341 .lock()
13342 .unwrap_or_else(std::sync::PoisonError::into_inner);
13343 let cancelling = cancelling_executor
13344 .begin_failed_dispatch_cancellation()
13345 .expect("the test must publish terminal dispatch cancellation");
13346 drop(scheduler);
13347 cancellation_published_tx.send(()).unwrap();
13348 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
13349 cancellation_done_tx.send(()).unwrap();
13350 });
13351 cancellation_published_rx
13352 .recv_timeout(Duration::from_secs(2))
13353 .expect("terminal dispatch cancellation must publish before queue draining");
13354
13355 let claimant_executor = Arc::clone(&executor);
13356 let claimant = std::thread::spawn(move || claimant_executor.drain_one());
13357 assert!(
13358 !claimant.join().unwrap(),
13359 "a runnable claim after terminal cancellation publication must be rejected"
13360 );
13361 assert_eq!(polls.load(Ordering::SeqCst), 0);
13362 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13363 assert!(
13364 matches!(
13365 cancellation_done_rx.try_recv(),
13366 Err(mpsc::TryRecvError::Empty)
13367 ),
13368 "terminal cancellation must still be waiting to drain the queued runnable"
13369 );
13370
13371 drop(queue);
13372 cancellation_done_rx
13373 .recv_timeout(Duration::from_secs(2))
13374 .expect("terminal cancellation must drain the rejected runnable");
13375 cancellation.join().unwrap();
13376 assert!(
13377 futures::executor::block_on(task.fallible()).is_none(),
13378 "terminal cancellation must cancel the runnable it prevented from starting"
13379 );
13380 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13381 }
13382
13383 #[cfg(napi_runtime_os_threads)]
13384 #[test]
13385 fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
13386 use std::sync::mpsc;
13387
13388 let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
13389 let polls = Arc::new(AtomicUsize::new(0));
13390 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13391 let (outcome_tx, outcome_rx) = mpsc::channel();
13392 let driver_executor = Arc::clone(&executor);
13393 let driver_polls = Arc::clone(&polls);
13394 let driver = std::thread::spawn(move || {
13395 let mut future = std::pin::pin!(StopPollProbe {
13396 polls: driver_polls,
13397 first_poll: Some(first_poll_tx)
13398 });
13399 outcome_tx
13400 .send(driver_executor.block_on(future.as_mut()))
13401 .unwrap();
13402 });
13403
13404 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13405 wait_for_generation_parker_to_sleep(&executor.stop);
13406 executor.stop.begin_shutdown();
13407
13408 assert_eq!(
13409 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13410 BlockOnOutcome::Stopped
13411 );
13412 driver.join().unwrap();
13413 assert_eq!(
13414 polls.load(Ordering::SeqCst),
13415 1,
13416 "a shutdown wake must be checked before the next parking block_on user poll"
13417 );
13418 }
13419
13420 #[cfg(napi_runtime_os_threads)]
13421 #[test]
13422 fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
13423 use std::sync::mpsc;
13424
13425 let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
13426 let polls = Arc::new(AtomicUsize::new(0));
13427 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13428 let (outcome_tx, outcome_rx) = mpsc::channel();
13429 let driver_executor = Arc::clone(&executor);
13430 let driver_polls = Arc::clone(&polls);
13431 let driver = std::thread::spawn(move || {
13432 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
13433 let mut future = std::pin::pin!(StopPollProbe {
13434 polls: driver_polls,
13435 first_poll: Some(first_poll_tx)
13436 });
13437 outcome_tx
13438 .send(driver_executor.block_on(future.as_mut()))
13439 .unwrap();
13440 });
13441
13442 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13443 wait_for_generation_parker_to_sleep(&executor.stop);
13444 executor.stop.begin_shutdown();
13445
13446 assert_eq!(
13447 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13448 BlockOnOutcome::Stopped
13449 );
13450 driver.join().unwrap();
13451 assert_eq!(
13452 polls.load(Ordering::SeqCst),
13453 1,
13454 "a shutdown wake must be checked before the next cooperative user poll"
13455 );
13456 }
13457
13458 #[test]
13459 fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
13460 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13461 Arc::new(RuntimeMetrics::default()),
13462 accept_current_thread_host_dispatch,
13463 ));
13464 let pending = u64::MAX - 1;
13465 executor.dispatch_pending.store(pending, Ordering::Release);
13466 let mut allocations = 0;
13467
13468 executor.request_drain_with(|| {
13469 allocations += 1;
13470 u64::MAX
13471 });
13472
13473 assert_eq!(allocations, 0);
13474 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
13475 }
13476
13477 #[cfg(napi_runtime_os_threads)]
13478 #[test]
13479 fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
13480 use std::sync::mpsc;
13481
13482 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13483 Arc::new(RuntimeMetrics::default()),
13484 accept_current_thread_host_dispatch,
13485 ));
13486 let dispatch = current_thread_dispatch(&executor);
13487 let scheduler = executor
13488 .scheduler_idle_lock
13489 .lock()
13490 .unwrap_or_else(std::sync::PoisonError::into_inner);
13491 let (claiming_tx, claiming_rx) = mpsc::channel();
13492 let (claimed_tx, claimed_rx) = mpsc::channel();
13493 let claiming_executor = Arc::clone(&executor);
13494 let claimant = std::thread::spawn(move || {
13495 claiming_tx.send(()).unwrap();
13496 claimed_tx
13497 .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
13498 .unwrap();
13499 });
13500 claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13501
13502 executor.stop.begin_shutdown();
13503 drop(scheduler);
13504
13505 assert!(
13506 !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13507 "an old-generation dispatch must not claim a host turn after stop becomes visible"
13508 );
13509 claimant.join().unwrap();
13510 assert_eq!(
13511 executor.dispatch_pending.load(Ordering::Acquire),
13512 dispatch,
13513 "rejecting the stopped claim must not consume its stale capability"
13514 );
13515 }
13516
13517 #[cfg(napi_runtime_os_threads)]
13518 #[test]
13519 fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
13520 use std::sync::mpsc;
13521
13522 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13523 let metrics = Arc::new(RuntimeMetrics::default());
13524 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13525 Arc::clone(&metrics),
13526 count_current_thread_stopped_host_dispatch,
13527 ));
13528 let scheduler = executor
13529 .scheduler_idle_lock
13530 .lock()
13531 .unwrap_or_else(std::sync::PoisonError::into_inner);
13532 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13533 let scheduling_executor = Arc::clone(&executor);
13534 let scheduling = std::thread::spawn(move || {
13535 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13536 *slot.borrow_mut() = Some(Box::new(move || {
13537 publication_blocked_tx.send(()).unwrap();
13538 }));
13539 });
13540 let scheduler = Arc::clone(&scheduling_executor);
13541 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13542 scheduler.schedule(runnable);
13543 });
13544 task.detach();
13545 scheduling_executor.schedule(runnable);
13546 });
13547
13548 publication_blocked_rx
13549 .recv_timeout(Duration::from_secs(2))
13550 .expect("dispatch publication must block behind the scheduler-idle mutex");
13551 executor.begin_shutdown();
13552 drop(scheduler);
13553 scheduling.join().unwrap();
13554 executor.wait_until_scheduler_idle();
13555
13556 assert_eq!(
13557 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
13558 0,
13559 "a publication that reaches the scheduler-idle barrier after stop must not call the host"
13560 );
13561 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13562 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13563 }
13564
13565 #[cfg(napi_runtime_os_threads)]
13566 #[test]
13567 fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
13568 use std::{sync::mpsc, time::Duration};
13569
13570 let metrics = Arc::new(RuntimeMetrics::default());
13571 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13572 Arc::clone(&metrics),
13573 accept_current_thread_host_dispatch,
13574 ));
13575 let scheduler = Arc::clone(&executor);
13576 let completed = Arc::new(AtomicBool::new(false));
13577 let completed_task = Arc::clone(&completed);
13578 let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
13579 let shared = async move {
13580 wake_rx.await.expect("wake sender dropped");
13581 }
13582 .boxed()
13583 .shared();
13584 let (runnable, task) = async_task::spawn(
13585 async move {
13586 shared.await;
13587 completed_task.store(true, Ordering::SeqCst);
13588 },
13589 move |runnable| scheduler.schedule(runnable),
13590 );
13591
13592 executor.schedule(runnable);
13593 assert!(
13594 !completed.load(Ordering::SeqCst),
13595 "accepted host dispatch must defer the first poll"
13596 );
13597 executor.drive_host_turn();
13598
13599 let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
13600 std::thread::spawn(move || {
13601 wake_tx.send(()).expect("shared wake receiver dropped");
13602 send_returned_tx.send(()).unwrap();
13603 });
13604 send_returned_rx
13605 .recv_timeout(Duration::from_secs(1))
13606 .expect("Shared wake re-entered its own waker mutex through inline polling");
13607 assert!(
13608 !completed.load(Ordering::SeqCst),
13609 "the wake must remain queued until a fresh host turn"
13610 );
13611
13612 executor.drive_host_turn();
13613 futures::executor::block_on(task);
13614 assert!(completed.load(Ordering::SeqCst));
13615 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13616 }
13617
13618 #[cfg(napi_runtime_os_threads)]
13619 #[test]
13620 fn current_thread_hostless_wake_is_enqueue_only() {
13621 use std::{sync::mpsc, time::Duration};
13622
13623 struct LockOnSecondPoll {
13624 gate: Arc<Mutex<()>>,
13625 first: bool,
13626 waker: Option<mpsc::Sender<Waker>>,
13627 completed: Arc<AtomicBool>,
13628 }
13629
13630 impl Future for LockOnSecondPoll {
13631 type Output = ();
13632
13633 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
13634 if self.first {
13635 self.first = false;
13636 self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
13637 return Poll::Pending;
13638 }
13639 drop(self.gate.lock().unwrap());
13640 self.completed.store(true, Ordering::SeqCst);
13641 Poll::Ready(())
13642 }
13643 }
13644
13645 let metrics = Arc::new(RuntimeMetrics::default());
13646 let executor = Arc::new(CurrentThreadExecutor::new(metrics));
13647 let gate = Arc::new(Mutex::new(()));
13648 let completed = Arc::new(AtomicBool::new(false));
13649 let (waker_tx, waker_rx) = mpsc::channel();
13650 let scheduler = Arc::clone(&executor);
13651 let (runnable, task) = async_task::spawn(
13652 LockOnSecondPoll {
13653 gate: Arc::clone(&gate),
13654 first: true,
13655 waker: Some(waker_tx),
13656 completed: Arc::clone(&completed),
13657 },
13658 move |runnable| scheduler.schedule(runnable),
13659 );
13660 executor.schedule(runnable);
13661 executor.drive_host_turn();
13662 let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
13663
13664 let (returned_tx, returned_rx) = mpsc::channel();
13665 let wake_gate = Arc::clone(&gate);
13666 let waking = std::thread::spawn(move || {
13667 let _held = wake_gate.lock().unwrap();
13668 waker.wake();
13669 returned_tx.send(()).unwrap();
13670 });
13671 returned_rx
13672 .recv_timeout(Duration::from_secs(1))
13673 .expect("hostless wake must enqueue without polling under the caller's lock");
13674 waking.join().unwrap();
13675 assert!(!completed.load(Ordering::SeqCst));
13676
13677 executor.drive_host_turn();
13678 futures::executor::block_on(task);
13679 assert!(completed.load(Ordering::SeqCst));
13680 }
13681
13682 #[cfg(napi_runtime_os_threads)]
13683 #[test]
13684 fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
13685 use std::sync::mpsc;
13686
13687 let controller = Arc::new(current_thread_controller(
13688 "current-thread-hostless-late-work",
13689 ));
13690 let backend = controller.backend();
13691 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
13692 panic!("configured CurrentThread backend must create a CurrentThread executor");
13693 };
13694 let executor = Arc::clone(executor);
13695 drop(backend);
13696
13697 let (value_tx, value_rx) = oneshot::channel();
13698 let (done_tx, done_rx) = mpsc::channel();
13699 let block_on_controller = Arc::clone(&controller);
13700 let runner = std::thread::spawn(move || {
13701 let mut output = None;
13702 let outcome = {
13703 let mut future = std::pin::pin!(async {
13704 output = Some(value_rx.await.expect("scheduled sender must deliver"));
13705 });
13706 block_on_controller.block_on(future.as_mut())
13707 };
13708 done_tx.send((outcome, output)).unwrap();
13709 });
13710
13711 wait_until(
13712 "the hostless CurrentThread block_on driver to sleep",
13713 || {
13714 executor
13715 .stop
13716 .parkers
13717 .lock()
13718 .unwrap_or_else(std::sync::PoisonError::into_inner)
13719 .iter()
13720 .filter_map(Weak::upgrade)
13721 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13722 },
13723 );
13724
13725 controller
13726 .try_spawn_detached(async move {
13727 value_tx
13728 .send(37usize)
13729 .expect("block_on receiver must remain alive");
13730 })
13731 .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
13732
13733 let (outcome, output) = done_rx
13734 .recv_timeout(Duration::from_secs(2))
13735 .expect("queue publication must wake the sleeping explicit driver");
13736 assert_eq!(outcome, BlockOnOutcome::Completed);
13737 assert_eq!(output, Some(37));
13738 runner.join().unwrap();
13739 controller.shutdown().unwrap();
13740 }
13741
13742 #[cfg(napi_runtime_os_threads)]
13743 #[test]
13744 fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
13745 use std::sync::mpsc;
13746
13747 struct BlockingPoll {
13748 entered: Option<mpsc::Sender<()>>,
13749 release: mpsc::Receiver<()>,
13750 }
13751
13752 impl Future for BlockingPoll {
13753 type Output = ();
13754
13755 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
13756 self.entered.take().unwrap().send(()).unwrap();
13757 self.release.recv().unwrap();
13758 Poll::Ready(())
13759 }
13760 }
13761
13762 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13763 Arc::new(RuntimeMetrics::default()),
13764 reject_current_thread_host_dispatch,
13765 ));
13766 let (value_tx, value_rx) = oneshot::channel();
13767 let (older_done_tx, older_done_rx) = mpsc::channel();
13768 let older_executor = Arc::clone(&executor);
13769 let older = std::thread::spawn(move || {
13770 let mut value = None;
13771 {
13772 let mut future = std::pin::pin!(async {
13773 value = Some(value_rx.await.unwrap());
13774 });
13775 assert_eq!(
13776 older_executor.block_on(future.as_mut()),
13777 BlockOnOutcome::Completed
13778 );
13779 }
13780 older_done_tx.send(value.unwrap()).unwrap();
13781 });
13782 wait_until("the older CurrentThread driver to sleep", || {
13783 executor
13784 .stop
13785 .parkers
13786 .lock()
13787 .unwrap_or_else(std::sync::PoisonError::into_inner)
13788 .iter()
13789 .filter_map(Weak::upgrade)
13790 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13791 });
13792
13793 let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
13794 let (release_newer_tx, release_newer_rx) = mpsc::channel();
13795 let newer_executor = Arc::clone(&executor);
13796 let newer = std::thread::spawn(move || {
13797 let mut future = std::pin::pin!(BlockingPoll {
13798 entered: Some(newer_entered_tx),
13799 release: release_newer_rx
13800 });
13801 assert_eq!(
13802 newer_executor.block_on(future.as_mut()),
13803 BlockOnOutcome::Completed
13804 );
13805 });
13806 newer_entered_rx
13807 .recv_timeout(Duration::from_secs(1))
13808 .expect("the newer driver must be blocked inside its poll");
13809
13810 let scheduler = Arc::clone(&executor);
13811 let (runnable, task) = async_task::spawn(
13812 async move {
13813 value_tx.send(41usize).unwrap();
13814 },
13815 move |runnable| scheduler.schedule(runnable),
13816 );
13817 task.detach();
13818 executor.schedule(runnable);
13819
13820 assert_eq!(
13821 older_done_rx
13822 .recv_timeout(Duration::from_secs(2))
13823 .expect("queue publication must also wake the older sleeping driver"),
13824 41
13825 );
13826 release_newer_tx.send(()).unwrap();
13827 older.join().unwrap();
13828 newer.join().unwrap();
13829 }
13830
13831 #[test]
13832 fn current_thread_shutdown_cancels_host_queued_task() {
13833 use std::sync::mpsc;
13834
13835 struct DropSignal(mpsc::Sender<()>);
13836
13837 impl Drop for DropSignal {
13838 fn drop(&mut self) {
13839 self.0.send(()).unwrap();
13840 }
13841 }
13842
13843 let metrics = Arc::new(RuntimeMetrics::default());
13844 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13845 Arc::clone(&metrics),
13846 accept_current_thread_host_dispatch,
13847 ));
13848 let scheduler = Arc::clone(&executor);
13849 let (dropped_tx, dropped_rx) = mpsc::channel();
13850 let signal = DropSignal(dropped_tx);
13851 let (runnable, task) = async_task::spawn(
13852 async move {
13853 let _signal = signal;
13854 futures::future::pending::<()>().await;
13855 },
13856 move |runnable| scheduler.schedule(runnable),
13857 );
13858
13859 executor.schedule(runnable);
13860 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13861 executor.begin_shutdown();
13862
13863 dropped_rx
13864 .recv_timeout(Duration::from_secs(1))
13865 .expect("shutdown must drop a task queued behind a host turn");
13866 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13867 assert!(
13868 futures::executor::block_on(task.fallible()).is_none(),
13869 "the queued task handle must resolve as cancelled"
13870 );
13871 }
13872
13873 #[test]
13874 fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
13875 reset_current_thread_recovery_host_dispatches();
13876 let metrics = Arc::new(RuntimeMetrics::default());
13877 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13878 Arc::clone(&metrics),
13879 count_current_thread_recovery_host_dispatch,
13880 ));
13881 let scheduler = Arc::clone(&executor);
13882 let completed = Arc::new(AtomicBool::new(false));
13883 let completed_task = Arc::clone(&completed);
13884 let (runnable, task) = async_task::spawn(
13885 async move {
13886 completed_task.store(true, Ordering::SeqCst);
13887 },
13888 move |runnable| scheduler.schedule(runnable),
13889 );
13890
13891 executor.schedule(runnable);
13892 assert_eq!(current_thread_recovery_host_dispatches(), 1);
13893 let lost_dispatch = current_thread_dispatch(&executor);
13894
13895 executor.request_drain_if_queued();
13899 assert_eq!(
13900 current_thread_recovery_host_dispatches(),
13901 2,
13902 "the newly registered host must receive the pending executor dispatch"
13903 );
13904 assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13905 executor.drive_host_turn_for_dispatch(lost_dispatch);
13906 futures::executor::block_on(task);
13907 assert!(completed.load(Ordering::SeqCst));
13908 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13909
13910 let second_completed = Arc::new(AtomicBool::new(false));
13911 let second_completed_task = Arc::clone(&second_completed);
13912 let second_scheduler = Arc::clone(&executor);
13913 let (second_runnable, second_task) = async_task::spawn(
13914 async move {
13915 second_completed_task.store(true, Ordering::SeqCst);
13916 },
13917 move |runnable| second_scheduler.schedule(runnable),
13918 );
13919 executor.schedule(second_runnable);
13920 assert_eq!(
13921 current_thread_recovery_host_dispatches(),
13922 3,
13923 "work queued after the stale callback must receive a new dispatch"
13924 );
13925 let third_dispatch = current_thread_dispatch(&executor);
13926
13927 executor.drive_host_turn_for_dispatch(lost_dispatch);
13930 assert!(!second_completed.load(Ordering::SeqCst));
13931 executor.drive_host_turn_for_dispatch(third_dispatch);
13932 futures::executor::block_on(second_task);
13933 assert!(second_completed.load(Ordering::SeqCst));
13934
13935 let third_scheduler = Arc::clone(&executor);
13936 let (third_runnable, third_task) =
13937 async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
13938 executor.schedule(third_runnable);
13939 assert_eq!(
13940 current_thread_recovery_host_dispatches(),
13941 4,
13942 "a stale callback must not leave the dispatch latch stuck"
13943 );
13944 executor.drive_host_turn();
13945 futures::executor::block_on(third_task);
13946 }
13947
13948 #[test]
13949 fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
13950 let metrics = Arc::new(RuntimeMetrics::default());
13951 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13952 Arc::clone(&metrics),
13953 accept_current_thread_host_dispatch,
13954 ));
13955 let scheduler = Arc::clone(&executor);
13956 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13957 scheduler.schedule(runnable)
13958 });
13959
13960 executor.schedule(runnable);
13961 let failed_dispatch = current_thread_dispatch(&executor);
13962 executor.cancel_host_dispatch_for_test(failed_dispatch);
13963 let recovery_dispatch = current_thread_dispatch(&executor);
13964 assert_eq!(
13965 executor.recovery_dispatch.load(Ordering::Acquire),
13966 recovery_dispatch
13967 );
13968
13969 executor.request_drain_if_queued();
13970 let new_host_dispatch = current_thread_dispatch(&executor);
13971 assert_eq!(new_host_dispatch, recovery_dispatch);
13972 assert_eq!(
13973 executor.recovery_dispatch.load(Ordering::Acquire),
13974 recovery_dispatch
13975 );
13976 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13977
13978 executor.drive_host_turn_for_dispatch(new_host_dispatch);
13979 assert_eq!(futures::executor::block_on(task), 17);
13980 }
13981
13982 #[cfg(napi_runtime_os_threads)]
13983 #[test]
13984 fn current_thread_host_admission_serializes_dispatch_publication() {
13985 use std::sync::mpsc;
13986
13987 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13988 let metrics = Arc::new(RuntimeMetrics::default());
13989 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13990 Arc::clone(&metrics),
13991 count_current_thread_admission_host_dispatch,
13992 ));
13993 let first_scheduler = Arc::clone(&executor);
13994 let (first_runnable, first_task) =
13995 async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
13996 first_task.detach();
13997 executor.schedule(first_runnable);
13998 let dispatch = current_thread_dispatch(&executor);
13999 assert_eq!(
14000 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14001 1
14002 );
14003
14004 let (claimed_tx, claimed_rx) = mpsc::channel();
14005 let (release_tx, release_rx) = mpsc::channel();
14006 let driver_executor = Arc::clone(&executor);
14007 let driver = std::thread::spawn(move || {
14008 CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
14009 *slot.borrow_mut() = Some(Box::new(move || {
14010 claimed_tx.send(()).unwrap();
14011 release_rx.recv().unwrap();
14012 }));
14013 });
14014 driver_executor.drive_host_turn_for_dispatch(dispatch);
14015 });
14016 claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14017
14018 let second_ran = Arc::new(AtomicBool::new(false));
14019 let second_ran_task = Arc::clone(&second_ran);
14020 let scheduling_executor = Arc::clone(&executor);
14021 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
14022 let (scheduled_tx, scheduled_rx) = mpsc::channel();
14023 let scheduling = std::thread::spawn(move || {
14024 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14025 *slot.borrow_mut() = Some(Box::new(move || {
14026 publication_blocked_tx.send(()).unwrap();
14027 }));
14028 });
14029 let scheduler = Arc::clone(&scheduling_executor);
14030 let (runnable, task) = async_task::spawn(
14031 async move {
14032 second_ran_task.store(true, Ordering::SeqCst);
14033 },
14034 move |runnable| scheduler.schedule(runnable),
14035 );
14036 task.detach();
14037 scheduling_executor.schedule(runnable);
14038 scheduled_tx.send(()).unwrap();
14039 });
14040
14041 publication_blocked_rx
14042 .recv_timeout(Duration::from_secs(2))
14043 .unwrap();
14044 assert!(
14045 matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
14046 "dispatch publication must wait until host admission publishes its draining role"
14047 );
14048 assert_eq!(
14049 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14050 1,
14051 "the admission gap must not publish a duplicate host dispatch"
14052 );
14053
14054 release_tx.send(()).unwrap();
14055 scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14056 scheduling.join().unwrap();
14057 driver.join().unwrap();
14058 if !second_ran.load(Ordering::SeqCst) {
14059 executor.drive_host_turn();
14060 }
14061 assert!(second_ran.load(Ordering::SeqCst));
14062 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14063 }
14064
14065 #[test]
14066 fn current_thread_host_turn_yields_after_runnable_budget() {
14067 struct SelfWakingFuture {
14068 remaining: usize,
14069 }
14070
14071 impl Future for SelfWakingFuture {
14072 type Output = ();
14073
14074 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14075 if self.remaining == 0 {
14076 return Poll::Ready(());
14077 }
14078 self.remaining -= 1;
14079 cx.waker().wake_by_ref();
14080 Poll::Pending
14081 }
14082 }
14083
14084 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14085 let metrics = Arc::new(RuntimeMetrics::default());
14086 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14087 Arc::clone(&metrics),
14088 count_current_thread_budget_host_dispatch,
14089 ));
14090 let scheduler = Arc::clone(&executor);
14091 let (runnable, task) = async_task::spawn(
14092 SelfWakingFuture {
14093 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14094 },
14095 move |runnable| scheduler.schedule(runnable),
14096 );
14097
14098 executor.schedule(runnable);
14099 executor.drive_host_turn();
14100
14101 assert_eq!(
14102 metrics.runnable_polls.load(Ordering::Relaxed),
14103 CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
14104 );
14105 assert_eq!(
14106 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14107 2,
14108 "remaining hot work must continue through a fresh host turn"
14109 );
14110
14111 executor.drive_host_turn();
14112 futures::executor::block_on(task);
14113 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14114 }
14115
14116 #[cfg(napi_runtime_os_threads)]
14117 #[test]
14118 fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
14119 const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
14120
14121 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14122 let metrics = Arc::new(RuntimeMetrics::default());
14123 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14124 Arc::clone(&metrics),
14125 count_current_thread_blocking_budget_host_dispatch,
14126 ));
14127 let work = GenerationWork::new();
14128 let observed = Arc::new(Mutex::new(Vec::new()));
14129
14130 let task_executor = Arc::clone(&executor);
14131 let wake_executor = Arc::clone(&executor);
14132 let producer_executor = Arc::clone(&executor);
14133 let producer_work = Arc::clone(&work);
14134 let producer_observed = Arc::clone(&observed);
14135 let outer_registration = work
14136 .try_register_work()
14137 .expect("the outer blocking job must be accepted");
14138 let (runnable, task) = async_task::spawn(
14139 async move {
14140 let queued = task_executor
14141 .schedule_blocking(
14142 move || {
14143 std::thread::spawn(move || {
14144 (0..QUEUED_BLOCKING_JOBS)
14145 .map(|index| {
14146 let registration = producer_work
14147 .try_register_work()
14148 .expect("queued blocking work must be accepted");
14149 let observed = Arc::clone(&producer_observed);
14150 producer_executor.schedule_blocking(
14151 move || {
14152 observed
14153 .lock()
14154 .unwrap_or_else(std::sync::PoisonError::into_inner)
14155 .push(index);
14156 },
14157 registration,
14158 )
14159 })
14160 .collect::<Vec<_>>()
14161 })
14162 .join()
14163 .expect("the blocking producer must finish")
14164 },
14165 outer_registration,
14166 )
14167 .await
14168 .expect("the inline blocking job must finish");
14169 for handle in queued {
14170 handle.await.expect("queued blocking work must finish");
14171 }
14172 },
14173 move |runnable| wake_executor.schedule(runnable),
14174 );
14175
14176 executor.schedule(runnable);
14177 executor.drive_host_turn();
14178
14179 let first_turn = observed
14180 .lock()
14181 .unwrap_or_else(std::sync::PoisonError::into_inner)
14182 .clone();
14183 assert!(
14184 !first_turn.is_empty(),
14185 "the first host turn must service queued blocking work"
14186 );
14187 assert!(
14188 first_turn.len() < QUEUED_BLOCKING_JOBS,
14189 "inline lane release must not drain the entire blocking FIFO in one host turn"
14190 );
14191 assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
14192 assert_eq!(
14193 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14194 2,
14195 "remaining blocking work must continue through a fresh host turn"
14196 );
14197
14198 let mut continuation_turns = 0;
14199 while observed
14200 .lock()
14201 .unwrap_or_else(std::sync::PoisonError::into_inner)
14202 .len()
14203 < QUEUED_BLOCKING_JOBS
14204 {
14205 continuation_turns += 1;
14206 assert!(
14207 continuation_turns <= QUEUED_BLOCKING_JOBS,
14208 "queued blocking work failed to make bounded host-turn progress"
14209 );
14210 executor.drive_host_turn();
14211 }
14212 executor.drive_host_turn();
14213 futures::executor::block_on(task);
14214
14215 assert_eq!(
14216 *observed
14217 .lock()
14218 .unwrap_or_else(std::sync::PoisonError::into_inner),
14219 (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
14220 "blocking jobs must preserve FIFO order across host turns"
14221 );
14222 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14223 }
14224
14225 #[cfg(napi_runtime_os_threads)]
14226 #[test]
14227 fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
14228 use std::sync::mpsc;
14229
14230 struct SelfWakingFuture {
14231 remaining: usize,
14232 }
14233
14234 impl Future for SelfWakingFuture {
14235 type Output = ();
14236
14237 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14238 if self.remaining == 0 {
14239 return Poll::Ready(());
14240 }
14241 self.remaining -= 1;
14242 cx.waker().wake_by_ref();
14243 Poll::Pending
14244 }
14245 }
14246
14247 let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
14248 let old_generation = controller.backend().generation();
14249 controller
14250 .try_spawn_detached(SelfWakingFuture {
14251 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14252 })
14253 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
14254 let dispatch = controller_current_thread_dispatch(&controller);
14255
14256 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14257 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14258 let driver_controller = Arc::clone(&controller);
14259 let driver = std::thread::spawn(move || {
14260 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14261 *slot.borrow_mut() = Some(Box::new(move || {
14262 dispatch_entered_tx.send(()).unwrap();
14263 release_dispatch_rx.recv().unwrap();
14264 }));
14265 });
14266 driver_controller.drive_current_thread_dispatch(dispatch, false);
14267 });
14268 dispatch_entered_rx
14269 .recv_timeout(Duration::from_secs(2))
14270 .expect("the bounded host turn must begin publishing its continuation");
14271
14272 let (shutdown_tx, shutdown_rx) = mpsc::channel();
14273 let shutdown_controller = Arc::clone(&controller);
14274 let shutdown = std::thread::spawn(move || {
14275 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14276 });
14277 wait_until("CurrentThread shutdown to leave Running", || {
14278 let state = controller
14279 .state
14280 .lock()
14281 .unwrap_or_else(std::sync::PoisonError::into_inner);
14282 !matches!(
14283 &state.lifecycle,
14284 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14285 )
14286 });
14287
14288 let (restart_tx, restart_rx) = mpsc::channel();
14289 let restart_controller = Arc::clone(&controller);
14290 let restart = std::thread::spawn(move || {
14291 let result = restart_controller
14292 .start()
14293 .map(|()| restart_controller.backend().generation());
14294 restart_tx.send(result).unwrap();
14295 });
14296
14297 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14298 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14299 let shutdown_overlapped = early_shutdown.is_some();
14300 let restart_overlapped = early_restart.is_some();
14301 release_dispatch_tx.send(()).unwrap();
14302 driver.join().unwrap();
14303
14304 let shutdown_result = early_shutdown.unwrap_or_else(|| {
14305 shutdown_rx
14306 .recv_timeout(Duration::from_secs(2))
14307 .expect("shutdown must finish after continuation publication retires")
14308 });
14309 shutdown_result.unwrap();
14310 let new_generation = early_restart.unwrap_or_else(|| {
14311 restart_rx
14312 .recv_timeout(Duration::from_secs(2))
14313 .expect("restart must follow continuation publication and shutdown")
14314 });
14315 let new_generation = new_generation.unwrap();
14316 shutdown.join().unwrap();
14317 restart.join().unwrap();
14318
14319 assert!(
14320 !shutdown_overlapped,
14321 "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
14322 );
14323 assert!(
14324 !restart_overlapped,
14325 "restart must not overlap an old-generation continuation dispatch"
14326 );
14327 assert_ne!(new_generation, old_generation);
14328 controller.shutdown().unwrap();
14329 }
14330
14331 #[cfg(napi_runtime_os_threads)]
14332 #[test]
14333 fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
14334 use std::sync::mpsc;
14335
14336 let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
14337 let old_generation = controller.backend().generation();
14338 let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
14339 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14340 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14341 let submit_controller = Arc::clone(&controller);
14342 let reentrant_controller = Arc::clone(&controller);
14343 let submitter = std::thread::spawn(move || {
14344 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14345 *slot.borrow_mut() = Some(Box::new(move || {
14346 reentrant_shutdown_tx
14347 .send(reentrant_controller.shutdown())
14348 .unwrap();
14349 dispatch_entered_tx.send(()).unwrap();
14350 release_dispatch_rx.recv().unwrap();
14351 }));
14352 });
14353 submit_controller
14354 .try_spawn_detached(std::future::pending::<()>())
14355 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
14356 });
14357
14358 let reentrant_shutdown = reentrant_shutdown_rx
14359 .recv_timeout(Duration::from_secs(2))
14360 .expect("dispatch publication must not self-deadlock on reentrant shutdown");
14361 dispatch_entered_rx
14362 .recv_timeout(Duration::from_secs(2))
14363 .expect("the initial host dispatch publication must reach the injected pause");
14364
14365 let (shutdown_tx, shutdown_rx) = mpsc::channel();
14366 let shutdown_controller = Arc::clone(&controller);
14367 let shutdown = std::thread::spawn(move || {
14368 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14369 });
14370 wait_until("CurrentThread shutdown to leave Running", || {
14371 let state = controller
14372 .state
14373 .lock()
14374 .unwrap_or_else(std::sync::PoisonError::into_inner);
14375 !matches!(
14376 &state.lifecycle,
14377 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14378 )
14379 });
14380
14381 let (restart_tx, restart_rx) = mpsc::channel();
14382 let restart_controller = Arc::clone(&controller);
14383 let restart = std::thread::spawn(move || {
14384 let result = restart_controller
14385 .start()
14386 .map(|()| restart_controller.backend().generation());
14387 restart_tx.send(result).unwrap();
14388 });
14389
14390 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14391 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14392 let shutdown_overlapped = early_shutdown.is_some();
14393 let restart_overlapped = early_restart.is_some();
14394 release_dispatch_tx.send(()).unwrap();
14395 submitter.join().unwrap();
14396
14397 let shutdown_result = early_shutdown.unwrap_or_else(|| {
14398 shutdown_rx
14399 .recv_timeout(Duration::from_secs(2))
14400 .expect("shutdown must finish after initial dispatch publication retires")
14401 });
14402 shutdown_result.unwrap();
14403 let new_generation = early_restart.unwrap_or_else(|| {
14404 restart_rx
14405 .recv_timeout(Duration::from_secs(2))
14406 .expect("restart must follow initial dispatch publication and shutdown")
14407 });
14408 let new_generation = new_generation.unwrap();
14409 shutdown.join().unwrap();
14410 restart.join().unwrap();
14411
14412 let reentrant_error = reentrant_shutdown
14413 .expect_err("dispatch publication must be generation-scoped scheduler work");
14414 assert!(
14415 reentrant_error
14416 .to_string()
14417 .contains("work running on that runtime")
14418 );
14419 assert!(
14420 !shutdown_overlapped,
14421 "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
14422 );
14423 assert!(
14424 !restart_overlapped,
14425 "restart must not overlap an old-generation host dispatch call"
14426 );
14427 assert_ne!(new_generation, old_generation);
14428 controller.shutdown().unwrap();
14429 }
14430
14431 #[cfg(panic = "unwind")]
14432 #[test]
14433 fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
14434 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14435 .lock()
14436 .unwrap()
14437 .clear();
14438 CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14439 let metrics = Arc::new(RuntimeMetrics::default());
14440 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14441 Arc::clone(&metrics),
14442 panic_once_current_thread_host_dispatch,
14443 ));
14444 let scheduler = Arc::clone(&executor);
14445 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14446 scheduler.schedule(runnable)
14447 });
14448
14449 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14450 assert!(
14451 publication.is_err(),
14452 "the injected host dispatch panic must unwind"
14453 );
14454 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14455 {
14456 let scheduler = executor
14457 .scheduler_idle_lock
14458 .lock()
14459 .unwrap_or_else(std::sync::PoisonError::into_inner);
14460 assert_eq!(scheduler.active_dispatch_calls, 0);
14461 assert!(scheduler.dispatch_publications.is_empty());
14462 }
14463
14464 executor.request_drain_if_queued();
14465
14466 assert_ne!(dispatch, 0);
14467 assert_eq!(
14468 *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14469 .lock()
14470 .unwrap(),
14471 vec![dispatch, dispatch],
14472 "a later host registration must retry the same pending capability"
14473 );
14474 {
14475 let scheduler = executor
14476 .scheduler_idle_lock
14477 .lock()
14478 .unwrap_or_else(std::sync::PoisonError::into_inner);
14479 assert_eq!(scheduler.active_dispatch_calls, 0);
14480 assert!(scheduler.dispatch_publications.is_empty());
14481 }
14482 executor.drive_host_turn_for_dispatch(dispatch);
14483 assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
14484 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14485 }
14486
14487 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14488 #[test]
14489 fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
14490 use std::sync::mpsc;
14491
14492 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14493 .lock()
14494 .unwrap()
14495 .clear();
14496 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14497 let metrics = Arc::new(RuntimeMetrics::default());
14498 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14499 Arc::clone(&metrics),
14500 panic_once_current_thread_armed_republish_host_dispatch,
14501 ));
14502 let scheduler = Arc::clone(&executor);
14503 let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
14504 scheduler.schedule(runnable)
14505 });
14506
14507 let (called_tx, called_rx) = mpsc::channel();
14508 let (release_tx, release_rx) = mpsc::channel();
14509 let publishing_executor = Arc::clone(&executor);
14510 let publication = std::thread::spawn(move || {
14511 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14512 *slot.borrow_mut() = Some(Box::new(move || {
14513 called_tx.send(()).unwrap();
14514 release_rx.recv().unwrap();
14515 }));
14516 });
14517 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14518 });
14519 called_rx
14520 .recv_timeout(Duration::from_secs(2))
14521 .expect("the first publication must pause before calling the host dispatcher");
14522
14523 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14524 executor.request_drain_if_queued();
14525 let publication_state = {
14526 let scheduler = executor
14527 .scheduler_idle_lock
14528 .lock()
14529 .unwrap_or_else(std::sync::PoisonError::into_inner);
14530 (
14531 scheduler.active_dispatch_calls,
14532 scheduler.dispatch_publications.len(),
14533 scheduler.dispatch_publications[0].dispatch,
14534 scheduler.dispatch_publications[0].republish_requested,
14535 )
14536 };
14537 release_tx.send(()).unwrap();
14538
14539 let publication = publication.join().unwrap();
14540 assert!(
14541 publication.is_err(),
14542 "the original host-dispatch panic must resume"
14543 );
14544 assert_ne!(dispatch, 0);
14545 assert_eq!(publication_state, (1, 1, dispatch, true));
14546 assert_eq!(
14547 *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14548 .lock()
14549 .unwrap(),
14550 vec![dispatch, dispatch],
14551 "the armed request must retry automatically before the original panic resumes"
14552 );
14553 {
14554 let scheduler = executor
14555 .scheduler_idle_lock
14556 .lock()
14557 .unwrap_or_else(std::sync::PoisonError::into_inner);
14558 assert_eq!(scheduler.active_dispatch_calls, 0);
14559 assert!(scheduler.dispatch_publications.is_empty());
14560 }
14561 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14562 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14563
14564 executor.drive_host_turn_for_dispatch(dispatch);
14565 assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
14566 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14567 }
14568
14569 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14570 #[test]
14571 fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
14572 use std::sync::mpsc;
14573
14574 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14575 .lock()
14576 .unwrap()
14577 .clear();
14578 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
14579 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14580 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14581 {
14582 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14583 .state
14584 .lock()
14585 .unwrap_or_else(std::sync::PoisonError::into_inner);
14586 assert!(state.entries.is_empty());
14587 assert!(state.dispatches.is_empty());
14588 }
14589 let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14590 .register(Arc::new(FailDuringAuthoritativePanicPublication));
14591 let metrics = Arc::new(RuntimeMetrics::default());
14592 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14593 Arc::clone(&metrics),
14594 dispatch_authoritative_panic_current_thread_tasks,
14595 &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
14596 ));
14597 let scheduler = Arc::clone(&executor);
14598 let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
14599 scheduler.schedule(runnable)
14600 });
14601
14602 let (called_tx, called_rx) = mpsc::channel();
14603 let (release_tx, release_rx) = mpsc::channel();
14604 let publishing_executor = Arc::clone(&executor);
14605 let publication = std::thread::spawn(move || {
14606 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14607 *slot.borrow_mut() = Some(Box::new(move || {
14608 called_tx.send(()).unwrap();
14609 release_rx.recv().unwrap();
14610 }));
14611 });
14612 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14613 });
14614 called_rx
14615 .recv_timeout(Duration::from_secs(2))
14616 .expect("the first publication must pause before raising its panic");
14617
14618 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14619 executor.request_drain_if_queued();
14620 release_tx.send(()).unwrap();
14621
14622 let payload = publication
14623 .join()
14624 .unwrap()
14625 .expect_err("the first host-dispatch panic must resume");
14626 assert!(
14627 payload.is::<FirstHostileCurrentThreadPanic>(),
14628 "the first panic must remain authoritative over nested recovery"
14629 );
14630 assert_eq!(
14631 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14632 1,
14633 "the later hostile payload must be discarded under containment"
14634 );
14635 assert_eq!(
14636 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14637 0,
14638 "the authoritative payload must remain owned by the resumed unwind"
14639 );
14640 discard_panic_payload(payload);
14641 assert_eq!(
14642 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14643 1
14644 );
14645
14646 let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14647 .lock()
14648 .unwrap()
14649 .clone();
14650 assert_eq!(dispatches.len(), 3);
14651 let replacement_dispatch = dispatches[2];
14652 assert_eq!(dispatches[0], failed_dispatch);
14653 assert_eq!(dispatches[1], failed_dispatch);
14654 assert_ne!(replacement_dispatch, failed_dispatch);
14655 assert_eq!(
14656 executor.dispatch_pending.load(Ordering::Acquire),
14657 replacement_dispatch
14658 );
14659 assert_eq!(
14660 executor.recovery_dispatch.load(Ordering::Acquire),
14661 replacement_dispatch
14662 );
14663 {
14664 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14665 .state
14666 .lock()
14667 .unwrap_or_else(std::sync::PoisonError::into_inner);
14668 assert!(
14669 !state.dispatches.contains_key(&failed_dispatch),
14670 "nested recovery unwind must retire the exact failed epoch"
14671 );
14672 assert!(!state.dispatches.contains_key(&replacement_dispatch));
14673 }
14674
14675 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
14676 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14677 let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14678 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14679 executor.request_drain_if_queued();
14680
14681 assert_eq!(
14682 *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14683 .lock()
14684 .unwrap(),
14685 vec![
14686 failed_dispatch,
14687 failed_dispatch,
14688 replacement_dispatch,
14689 replacement_dispatch
14690 ],
14691 "retry must retain the replacement reserved before the nested panic"
14692 );
14693 let delivery = healthy.deliveries()[0];
14694 assert_eq!(
14695 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
14696 Some(replacement_dispatch)
14697 );
14698 let mut host_turn = executor
14699 .try_admit_host_turn(replacement_dispatch)
14700 .expect("the retried replacement delivery must be admitted");
14701 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14702 host_turn.drive();
14703 assert!(
14704 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14705 .finish_delivery(delivery, false)
14706 .failed_dispatches
14707 .is_empty()
14708 );
14709 assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
14710 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14711
14712 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
14713 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14714 .state
14715 .lock()
14716 .unwrap_or_else(std::sync::PoisonError::into_inner);
14717 assert!(state.entries.is_empty());
14718 assert!(state.dispatches.is_empty());
14719 }
14720
14721 #[cfg(napi_runtime_os_threads)]
14722 #[test]
14723 fn current_thread_shutdown_suppresses_queued_republish() {
14724 use std::sync::mpsc;
14725
14726 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14727 let metrics = Arc::new(RuntimeMetrics::default());
14728 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14729 Arc::clone(&metrics),
14730 count_current_thread_shutdown_republish_host_dispatch,
14731 ));
14732 let scheduler = Arc::clone(&executor);
14733 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14734 scheduler.schedule(runnable)
14735 });
14736
14737 let (returned_tx, returned_rx) = mpsc::channel();
14738 let (release_tx, release_rx) = mpsc::channel();
14739 let publishing_executor = Arc::clone(&executor);
14740 let publication = std::thread::spawn(move || {
14741 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14742 *slot.borrow_mut() = Some(Box::new(move || {
14743 returned_tx.send(()).unwrap();
14744 release_rx.recv().unwrap();
14745 }));
14746 });
14747 publishing_executor.schedule(runnable);
14748 });
14749 returned_rx
14750 .recv_timeout(Duration::from_secs(2))
14751 .expect("the initial publication must reach its completion barrier");
14752
14753 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14754 executor.request_drain_if_queued();
14755 let publication_state = {
14756 let scheduler = executor
14757 .scheduler_idle_lock
14758 .lock()
14759 .unwrap_or_else(std::sync::PoisonError::into_inner);
14760 (
14761 scheduler.active_dispatch_calls,
14762 scheduler.dispatch_publications.len(),
14763 scheduler.dispatch_publications[0].dispatch,
14764 scheduler.dispatch_publications[0].republish_requested,
14765 )
14766 };
14767 executor.begin_shutdown();
14768 release_tx.send(()).unwrap();
14769 publication.join().unwrap();
14770 executor.wait_until_scheduler_idle();
14771
14772 assert_ne!(dispatch, 0);
14773 assert_eq!(publication_state, (1, 1, dispatch, true));
14774 assert_eq!(
14775 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
14776 1
14777 );
14778 {
14779 let scheduler = executor
14780 .scheduler_idle_lock
14781 .lock()
14782 .unwrap_or_else(std::sync::PoisonError::into_inner);
14783 assert_eq!(scheduler.active_dispatch_calls, 0);
14784 assert!(scheduler.dispatch_publications.is_empty());
14785 }
14786 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14787 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14788 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14789 assert_eq!(futures::executor::block_on(task.fallible()), None);
14790 }
14791
14792 #[cfg(napi_runtime_os_threads)]
14793 #[test]
14794 fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
14795 use std::sync::mpsc;
14796
14797 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
14798 .lock()
14799 .unwrap()
14800 .clear();
14801 let metrics = Arc::new(RuntimeMetrics::default());
14802 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14803 Arc::clone(&metrics),
14804 record_current_thread_republish_rearm_dispatch,
14805 ));
14806 let scheduler = Arc::clone(&executor);
14807 let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
14808 scheduler.schedule(runnable)
14809 });
14810
14811 let (first_returned_tx, first_returned_rx) = mpsc::channel();
14812 let (release_first_tx, release_first_rx) = mpsc::channel();
14813 let (second_returned_tx, second_returned_rx) = mpsc::channel();
14814 let (release_second_tx, release_second_rx) = mpsc::channel();
14815 let publishing_executor = Arc::clone(&executor);
14816 let publication = std::thread::spawn(move || {
14817 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14818 *slot.borrow_mut() = Some(Box::new(move || {
14819 first_returned_tx.send(()).unwrap();
14820 release_first_rx.recv().unwrap();
14821 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14822 *slot.borrow_mut() = Some(Box::new(move || {
14823 second_returned_tx.send(()).unwrap();
14824 release_second_rx.recv().unwrap();
14825 }));
14826 });
14827 }));
14828 });
14829 publishing_executor.schedule(runnable);
14830 });
14831 first_returned_rx
14832 .recv_timeout(Duration::from_secs(2))
14833 .expect("the first publication must reach its completion barrier");
14834
14835 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14836 executor.request_drain_if_queued();
14837 executor.request_drain_if_queued();
14838 let first_publication_state = {
14839 let scheduler = executor
14840 .scheduler_idle_lock
14841 .lock()
14842 .unwrap_or_else(std::sync::PoisonError::into_inner);
14843 (
14844 scheduler.active_dispatch_calls,
14845 scheduler.dispatch_publications.len(),
14846 scheduler.dispatch_publications[0].dispatch,
14847 scheduler.dispatch_publications[0].republish_requested,
14848 )
14849 };
14850 release_first_tx.send(()).unwrap();
14851 second_returned_rx
14852 .recv_timeout(Duration::from_secs(2))
14853 .expect("the second publication must reach its completion barrier");
14854
14855 executor.request_drain_if_queued();
14856 executor.request_drain_if_queued();
14857 let second_publication_state = {
14858 let scheduler = executor
14859 .scheduler_idle_lock
14860 .lock()
14861 .unwrap_or_else(std::sync::PoisonError::into_inner);
14862 (
14863 scheduler.active_dispatch_calls,
14864 scheduler.dispatch_publications.len(),
14865 scheduler.dispatch_publications[0].dispatch,
14866 scheduler.dispatch_publications[0].republish_requested,
14867 )
14868 };
14869 release_second_tx.send(()).unwrap();
14870 publication.join().unwrap();
14871
14872 assert_ne!(dispatch, 0);
14873 assert_eq!(first_publication_state, (1, 1, dispatch, true));
14874 assert_eq!(second_publication_state, (1, 1, dispatch, true));
14875 assert_eq!(
14876 *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
14877 vec![dispatch, dispatch, dispatch],
14878 "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
14879 );
14880 {
14881 let scheduler = executor
14882 .scheduler_idle_lock
14883 .lock()
14884 .unwrap_or_else(std::sync::PoisonError::into_inner);
14885 assert_eq!(scheduler.active_dispatch_calls, 0);
14886 assert!(scheduler.dispatch_publications.is_empty());
14887 }
14888 executor.drive_host_turn_for_dispatch(dispatch);
14889 assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
14890 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14891 }
14892
14893 #[cfg(napi_runtime_os_threads)]
14894 #[test]
14895 fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
14896 use std::sync::mpsc;
14897
14898 CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14899 .lock()
14900 .unwrap()
14901 .clear();
14902 let metrics = Arc::new(RuntimeMetrics::default());
14903 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14904 Arc::clone(&metrics),
14905 reject_initial_current_thread_publication_once,
14906 ));
14907 let scheduler = Arc::clone(&executor);
14908 let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14909 scheduler.schedule(runnable)
14910 });
14911
14912 let (returned_tx, returned_rx) = mpsc::channel();
14913 let (release_tx, release_rx) = mpsc::channel();
14914 let publishing_executor = Arc::clone(&executor);
14915 let publication = std::thread::spawn(move || {
14916 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14917 *slot.borrow_mut() = Some(Box::new(move || {
14918 returned_tx.send(()).unwrap();
14919 release_rx.recv().unwrap();
14920 }));
14921 });
14922 publishing_executor.schedule(runnable);
14923 });
14924 returned_rx
14925 .recv_timeout(Duration::from_secs(2))
14926 .expect("the unavailable initial publication must reach its completion barrier");
14927
14928 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14929 executor.request_drain_if_queued();
14930 let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14931 .lock()
14932 .unwrap()
14933 .len();
14934
14935 release_tx.send(()).unwrap();
14936 publication.join().unwrap();
14937
14938 let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14939 .lock()
14940 .unwrap()
14941 .clone();
14942 assert_ne!(dispatch, 0);
14943 assert_eq!(
14944 attempts_while_unavailable_is_paused, 1,
14945 "a racing host request must join the in-flight publication instead of retiring it independently"
14946 );
14947 assert_eq!(
14948 dispatches,
14949 vec![dispatch, dispatch],
14950 "the publication owner must retry the same internal capability after the new host joins"
14951 );
14952 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14953 executor.drive_host_turn_for_dispatch(dispatch);
14954 assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
14955 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14956 }
14957
14958 #[cfg(napi_runtime_os_threads)]
14959 #[test]
14960 fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
14961 use std::sync::mpsc;
14962
14963 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14964 .lock()
14965 .unwrap()
14966 .clear();
14967 let metrics = Arc::new(RuntimeMetrics::default());
14968 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14969 Arc::clone(&metrics),
14970 reject_current_thread_replacement_publication_once,
14971 ));
14972 let scheduler = Arc::clone(&executor);
14973 let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
14974 scheduler.schedule(runnable)
14975 });
14976 executor.schedule(runnable);
14977 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14978 assert_ne!(failed_dispatch, 0);
14979
14980 let (returned_tx, returned_rx) = mpsc::channel();
14981 let (release_tx, release_rx) = mpsc::channel();
14982 let cancelling_executor = Arc::clone(&executor);
14983 let cancellation = std::thread::spawn(move || {
14984 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14985 *slot.borrow_mut() = Some(Box::new(move || {
14986 returned_tx.send(()).unwrap();
14987 release_rx.recv().unwrap();
14988 }));
14989 });
14990 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14991 });
14992 returned_rx
14993 .recv_timeout(Duration::from_secs(2))
14994 .expect("the unavailable replacement publication must reach its completion barrier");
14995
14996 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14997 executor.request_drain_if_queued();
14998 let attempts_while_unavailable_is_paused =
14999 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15000 .lock()
15001 .unwrap()
15002 .len();
15003
15004 release_tx.send(()).unwrap();
15005 cancellation.join().unwrap();
15006
15007 let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15008 .lock()
15009 .unwrap()
15010 .clone();
15011 assert_ne!(replacement_dispatch, 0);
15012 assert_ne!(replacement_dispatch, failed_dispatch);
15013 assert_eq!(
15014 attempts_while_unavailable_is_paused, 2,
15015 "a racing host request must join the in-flight replacement publication"
15016 );
15017 assert_eq!(
15018 dispatches,
15019 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15020 "republication must reuse the sole replacement capability instead of allocating another"
15021 );
15022 assert_eq!(
15023 executor.dispatch_pending.load(Ordering::Acquire),
15024 replacement_dispatch
15025 );
15026 assert_eq!(
15027 executor.recovery_dispatch.load(Ordering::Acquire),
15028 replacement_dispatch
15029 );
15030 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15031
15032 executor.drive_host_turn_for_dispatch(replacement_dispatch);
15033 assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
15034 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15035 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15036 }
15037
15038 #[cfg(napi_runtime_os_threads)]
15039 #[test]
15040 fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
15041 use std::sync::mpsc;
15042
15043 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15044 .lock()
15045 .unwrap()
15046 .clear();
15047 {
15048 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15049 .state
15050 .lock()
15051 .unwrap_or_else(std::sync::PoisonError::into_inner);
15052 assert!(state.entries.is_empty());
15053 assert!(state.dispatches.is_empty());
15054 }
15055 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15056 let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15057 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15058 let metrics = Arc::new(RuntimeMetrics::default());
15059 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15060 Arc::clone(&metrics),
15061 fail_current_thread_replacement_publication_once,
15062 &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
15063 ));
15064 let scheduler = Arc::clone(&executor);
15065 let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
15066 scheduler.schedule(runnable)
15067 });
15068 executor.schedule(runnable);
15069 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15070 assert_ne!(failed_dispatch, 0);
15071 let initial_delivery = initial.deliveries()[0];
15072 let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15073 .finish_delivery(initial_delivery, true);
15074 let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
15075 panic!("the initial accepted delivery must report one exact failure");
15076 };
15077 assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
15078 let failed_dispatch_failure = *failed_dispatch_failure;
15079 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
15080 let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15081 .register(Arc::new(FailDuringReplacementRepublishPublication));
15082
15083 let (returned_tx, returned_rx) = mpsc::channel();
15084 let (release_tx, release_rx) = mpsc::channel();
15085 let cancelling_executor = Arc::clone(&executor);
15086 let cancellation = std::thread::spawn(move || {
15087 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15088 *slot.borrow_mut() = Some(Box::new(move || {
15089 returned_tx.send(()).unwrap();
15090 release_rx.recv().unwrap();
15091 }));
15092 });
15093 cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
15094 });
15095 returned_rx
15096 .recv_timeout(Duration::from_secs(2))
15097 .expect("the failed replacement publication must reach its completion barrier");
15098
15099 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15100 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15101 let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15102 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15103 executor.request_drain_if_queued();
15104 let publication_state = {
15105 let scheduler = executor
15106 .scheduler_idle_lock
15107 .lock()
15108 .unwrap_or_else(std::sync::PoisonError::into_inner);
15109 (
15110 scheduler.active_dispatch_calls,
15111 scheduler.dispatch_publications.len(),
15112 scheduler.dispatch_publications[0].dispatch,
15113 scheduler.dispatch_publications[0].republish_requested,
15114 )
15115 };
15116 release_tx.send(()).unwrap();
15117 cancellation.join().unwrap();
15118
15119 assert_ne!(replacement_dispatch, 0);
15120 assert_ne!(replacement_dispatch, failed_dispatch);
15121 assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
15122 assert_eq!(
15123 *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15124 .lock()
15125 .unwrap(),
15126 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15127 "the newly armed host request must receive the exact replacement before cancellation"
15128 );
15129 assert_eq!(
15130 executor.dispatch_pending.load(Ordering::Acquire),
15131 replacement_dispatch
15132 );
15133 assert_eq!(
15134 executor.recovery_dispatch.load(Ordering::Acquire),
15135 replacement_dispatch
15136 );
15137 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15138
15139 let delivery = healthy.deliveries()[0];
15140 assert_eq!(delivery.dispatch, replacement_dispatch);
15141 assert_eq!(
15142 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15143 .claim_delivery(delivery.capability()),
15144 Some(replacement_dispatch)
15145 );
15146 let mut host_turn = executor
15147 .try_admit_host_turn(replacement_dispatch)
15148 .expect("the rearmed replacement delivery must be admitted");
15149 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15150 host_turn.drive();
15151 assert!(
15152 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15153 .finish_delivery(delivery, false)
15154 .failed_dispatches
15155 .is_empty()
15156 );
15157 assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
15158 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15159 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15160
15161 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
15162 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
15163 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15164 .state
15165 .lock()
15166 .unwrap_or_else(std::sync::PoisonError::into_inner);
15167 assert!(state.entries.is_empty());
15168 assert!(state.dispatches.is_empty());
15169 }
15170
15171 #[cfg(napi_runtime_os_threads)]
15172 #[test]
15173 fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
15174 use std::sync::mpsc;
15175
15176 {
15177 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15178 .state
15179 .lock()
15180 .unwrap_or_else(std::sync::PoisonError::into_inner);
15181 assert!(state.entries.is_empty());
15182 assert!(state.dispatches.is_empty());
15183 }
15184 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15185 let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15186 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15187 let metrics = Arc::new(RuntimeMetrics::default());
15188 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15189 Arc::clone(&metrics),
15190 dispatch_stale_failure_current_thread_tasks,
15191 &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
15192 ));
15193 let scheduler = Arc::clone(&executor);
15194 let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
15195 scheduler.schedule(runnable)
15196 });
15197 executor.schedule(runnable);
15198
15199 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15200 let initial_delivery = initial.deliveries()[0];
15201 let delayed_completion =
15202 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
15203 let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
15204 panic!("the initial accepted delivery must report one exact failure");
15205 };
15206 assert_eq!(
15207 *delayed_failure,
15208 current_thread_dispatch_failure(dispatch, 0)
15209 );
15210 let delayed_failure = *delayed_failure;
15211
15212 let (returned_tx, returned_rx) = mpsc::channel();
15213 let (release_tx, release_rx) = mpsc::channel();
15214 let publishing_executor = Arc::clone(&executor);
15215 let publication = std::thread::spawn(move || {
15216 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15217 *slot.borrow_mut() = Some(Box::new(move || {
15218 returned_tx.send(()).unwrap();
15219 release_rx.recv().unwrap();
15220 }));
15221 });
15222 publishing_executor.request_drain_if_queued();
15223 });
15224 returned_rx
15225 .recv_timeout(Duration::from_secs(2))
15226 .expect("the old failed epoch must pause before executor recovery");
15227
15228 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15229 let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15230 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15231 executor.request_drain_if_queued();
15232 release_tx.send(()).unwrap();
15233 publication.join().unwrap();
15234
15235 let healthy_delivery = healthy.deliveries()[0];
15236 let delayed_initial_delivery = initial.deliveries()[1];
15237 {
15238 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15239 .state
15240 .lock()
15241 .unwrap_or_else(std::sync::PoisonError::into_inner);
15242 let dispatch_state = state
15243 .dispatches
15244 .get(&dispatch)
15245 .expect("the rearmed dispatch must remain registered");
15246 assert_eq!(dispatch_state.epoch, 1);
15247 assert!(!dispatch_state.is_cancelling());
15248 }
15249
15250 executor.cancel_host_dispatch(delayed_failure);
15251
15252 assert_eq!(
15253 executor.dispatch_pending.load(Ordering::Acquire),
15254 dispatch,
15255 "the delayed old-epoch failure must not consume the rearmed executor capability"
15256 );
15257 assert_eq!(
15258 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15259 .state
15260 .lock()
15261 .unwrap_or_else(std::sync::PoisonError::into_inner)
15262 .dispatches
15263 .get(&dispatch)
15264 .map(|state| (state.epoch, state.phase)),
15265 Some((1, CurrentThreadDispatchPhase::Pending)),
15266 "the delayed old-epoch cancellation must not remove the accepted rearm"
15267 );
15268
15269 assert_eq!(
15270 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
15271 Some(dispatch)
15272 );
15273 let mut host_turn = executor
15274 .try_admit_host_turn(dispatch)
15275 .expect("the current-epoch delivery must be admitted");
15276 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15277 host_turn.drive();
15278 assert!(
15279 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15280 .finish_delivery(healthy_delivery, false)
15281 .failed_dispatches
15282 .is_empty()
15283 );
15284 assert!(
15285 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15286 .finish_delivery(delayed_initial_delivery, false)
15287 .failed_dispatches
15288 .is_empty()
15289 );
15290 assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
15291 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15292
15293 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
15294 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
15295 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15296 .state
15297 .lock()
15298 .unwrap_or_else(std::sync::PoisonError::into_inner);
15299 assert!(state.entries.is_empty());
15300 assert!(state.dispatches.is_empty());
15301 }
15302
15303 #[cfg(napi_runtime_os_threads)]
15304 #[test]
15305 fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
15306 use std::sync::mpsc;
15307
15308 {
15309 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15310 .state
15311 .lock()
15312 .unwrap_or_else(std::sync::PoisonError::into_inner);
15313 assert!(state.entries.is_empty());
15314 assert!(state.dispatches.is_empty());
15315 }
15316 let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15317 .register(Arc::new(FailDuringLateRegistrationPublication));
15318
15319 let metrics = Arc::new(RuntimeMetrics::default());
15320 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15321 Arc::clone(&metrics),
15322 dispatch_late_registration_current_thread_tasks,
15323 &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
15324 ));
15325 let scheduler = Arc::clone(&executor);
15326 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15327 scheduler.schedule(runnable)
15328 });
15329 metrics.runnable_scheduled();
15330 executor
15331 .queue
15332 .lock()
15333 .unwrap_or_else(std::sync::PoisonError::into_inner)
15334 .runnables
15335 .push_back(runnable);
15336 let replacement_dispatch = next_current_thread_dispatch_id();
15337 executor
15338 .recovery_dispatch
15339 .store(replacement_dispatch, Ordering::Release);
15340 executor
15341 .dispatch_pending
15342 .store(replacement_dispatch, Ordering::Release);
15343
15344 let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
15345 let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
15346 let publishing_executor = Arc::clone(&executor);
15347 let publication = std::thread::spawn(move || {
15348 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15349 *slot.borrow_mut() = Some(Box::new(move || {
15350 owner_retired_tx.send(()).unwrap();
15351 request_blocked_rx
15352 .recv_timeout(Duration::from_secs(2))
15353 .expect("the binding-like drain request must wait for the failure transition");
15354 }));
15355 });
15356 publishing_executor.request_drain_if_queued();
15357 });
15358 owner_retired_rx
15359 .recv_timeout(Duration::from_secs(2))
15360 .expect("the failed replacement publication must retire its owner");
15361
15362 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15365 let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15366 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15367 let requesting_executor = Arc::clone(&executor);
15368 let request = std::thread::spawn(move || {
15369 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
15370 *slot.borrow_mut() = Some(Box::new(move || {
15371 request_blocked_tx.send(()).unwrap();
15372 }));
15373 });
15374 requesting_executor.request_drain_if_queued();
15375 });
15376
15377 publication.join().unwrap();
15378 request.join().unwrap();
15379
15380 assert!(
15381 healthy.deliveries().is_empty(),
15382 "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
15383 );
15384 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15385 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15386 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15387 assert_eq!(futures::executor::block_on(task.fallible()), None);
15388 {
15389 let scheduler = executor
15390 .scheduler_idle_lock
15391 .lock()
15392 .unwrap_or_else(std::sync::PoisonError::into_inner);
15393 assert_eq!(scheduler.active_dispatch_calls, 0);
15394 assert!(scheduler.dispatch_publications.is_empty());
15395 }
15396
15397 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
15398
15399 let scheduler = Arc::clone(&executor);
15400 let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
15401 scheduler.schedule(runnable)
15402 });
15403 executor.schedule(later_runnable);
15404 let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15405 let later_delivery = healthy.deliveries()[0];
15406 assert_eq!(
15407 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
15408 Some(later_dispatch)
15409 );
15410 let mut host_turn = executor
15411 .try_admit_host_turn(later_dispatch)
15412 .expect("the later exact delivery must be admitted");
15413 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
15414 host_turn.drive();
15415 assert!(
15416 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15417 .finish_delivery(later_delivery, false)
15418 .failed_dispatches
15419 .is_empty()
15420 );
15421 assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
15422 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15423
15424 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
15425 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15426 .state
15427 .lock()
15428 .unwrap_or_else(std::sync::PoisonError::into_inner);
15429 assert!(state.entries.is_empty());
15430 assert!(state.dispatches.is_empty());
15431 }
15432
15433 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
15434 #[test]
15435 fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
15436 {
15437 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15438 .lock()
15439 .unwrap()
15440 .clear();
15441 {
15442 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15443 .state
15444 .lock()
15445 .unwrap_or_else(std::sync::PoisonError::into_inner);
15446 assert!(state.entries.is_empty());
15447 assert!(state.dispatches.is_empty());
15448 }
15449 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15450 .next_delivery
15451 .store(0, Ordering::Relaxed);
15452 let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15453 .register(Arc::new(FailDuringNestedUnwindPublication));
15454
15455 let metrics = Arc::new(RuntimeMetrics::default());
15456 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15457 Arc::clone(&metrics),
15458 dispatch_nested_unwind_current_thread_tasks,
15459 &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
15460 ));
15461 let scheduler = Arc::clone(&executor);
15462 let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
15463 scheduler.schedule(runnable)
15464 });
15465
15466 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15467 *slot.borrow_mut() = Some(Box::new(|| {
15468 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15469 .next_delivery
15470 .store(u64::MAX, Ordering::Relaxed);
15471 }));
15472 });
15473 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
15474 assert!(
15475 publication.is_err(),
15476 "replacement delivery-id exhaustion must unwind through the outer failed publication"
15477 );
15478
15479 let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15480 .lock()
15481 .unwrap()
15482 .clone();
15483 assert_eq!(dispatches.len(), 2);
15484 let failed_dispatch = dispatches[0];
15485 let replacement_dispatch = dispatches[1];
15486 assert_ne!(failed_dispatch, replacement_dispatch);
15487 assert_eq!(
15488 executor.dispatch_pending.load(Ordering::Acquire),
15489 replacement_dispatch
15490 );
15491 assert_eq!(
15492 executor.recovery_dispatch.load(Ordering::Acquire),
15493 replacement_dispatch
15494 );
15495 {
15496 let scheduler = executor
15497 .scheduler_idle_lock
15498 .lock()
15499 .unwrap_or_else(std::sync::PoisonError::into_inner);
15500 assert_eq!(scheduler.active_dispatch_calls, 0);
15501 assert!(scheduler.dispatch_publications.is_empty());
15502 }
15503 {
15504 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15505 .state
15506 .lock()
15507 .unwrap_or_else(std::sync::PoisonError::into_inner);
15508 assert!(
15509 !state.dispatches.contains_key(&failed_dispatch),
15510 "outer unwind cleanup must retire the exact old cancelling dispatch"
15511 );
15512 assert!(
15513 !state.dispatches.contains_key(&replacement_dispatch),
15514 "the replacement publication guard must release its aborted registry state"
15515 );
15516 }
15517
15518 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15519 .next_delivery
15520 .store(1, Ordering::Relaxed);
15521 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
15522 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15523 let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15524 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15525
15526 executor.request_drain_if_queued();
15527
15528 assert_eq!(
15529 *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
15530 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15531 "retry must reuse the replacement reserved before the nested unwind"
15532 );
15533 let delivery = healthy.deliveries()[0];
15534 assert_eq!(delivery.dispatch, replacement_dispatch);
15535 assert_eq!(
15536 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
15537 Some(replacement_dispatch)
15538 );
15539 let mut host_turn = executor
15540 .try_admit_host_turn(replacement_dispatch)
15541 .expect("the retried replacement delivery must be admitted");
15542 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15543 host_turn.drive();
15544 assert!(
15545 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15546 .finish_delivery(delivery, false)
15547 .failed_dispatches
15548 .is_empty()
15549 );
15550 assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
15551 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15552
15553 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
15554 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15555 .state
15556 .lock()
15557 .unwrap_or_else(std::sync::PoisonError::into_inner);
15558 assert!(state.entries.is_empty());
15559 assert!(state.dispatches.is_empty());
15560 }
15561
15562 #[test]
15563 fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
15564 reset_current_thread_recovery_host_dispatches();
15565 let metrics = Arc::new(RuntimeMetrics::default());
15566 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15567 Arc::clone(&metrics),
15568 count_current_thread_recovery_host_dispatch,
15569 ));
15570
15571 let scheduler = Arc::clone(&executor);
15572 let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
15573 scheduler.schedule(runnable)
15574 });
15575 executor.schedule(runnable);
15576 assert_eq!(current_thread_recovery_host_dispatches(), 1);
15577 let failed_dispatch = current_thread_dispatch(&executor);
15578
15579 executor.cancel_host_dispatch_for_test(failed_dispatch);
15582 assert_eq!(
15583 current_thread_recovery_host_dispatches(),
15584 2,
15585 "cancellation must request one replacement without another submission"
15586 );
15587 let replacement_dispatch = current_thread_dispatch(&executor);
15588 assert_ne!(replacement_dispatch, failed_dispatch);
15589 assert_eq!(
15590 executor.recovery_dispatch.load(Ordering::Acquire),
15591 replacement_dispatch
15592 );
15593
15594 executor.drive_host_turn_for_dispatch(replacement_dispatch);
15595 assert_eq!(futures::executor::block_on(task), 1);
15596 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15597 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15598 }
15599
15600 #[test]
15601 fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
15602 reset_current_thread_recovery_host_dispatches();
15603 let metrics = Arc::new(RuntimeMetrics::default());
15604 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15605 Arc::clone(&metrics),
15606 count_current_thread_recovery_host_dispatch,
15607 ));
15608 let backend =
15609 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15610
15611 let registration = backend
15612 .work
15613 .try_register_async()
15614 .expect("the backend must accept work");
15615 let task = spawn_registered(
15616 &backend,
15617 Arc::clone(&metrics),
15618 std::future::pending::<()>(),
15619 registration,
15620 );
15621 let failed_dispatch = current_thread_dispatch(&executor);
15622 executor.cancel_host_dispatch_for_test(failed_dispatch);
15623 let replacement_dispatch = current_thread_dispatch(&executor);
15624
15625 executor.cancel_host_dispatch_for_test(replacement_dispatch);
15626
15627 assert_eq!(
15628 current_thread_recovery_host_dispatches(),
15629 2,
15630 "a second scheduler failure must not create an unbounded retry loop"
15631 );
15632 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15633 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15634 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15635 assert_eq!(
15636 futures::executor::block_on(task).unwrap_err().to_string(),
15637 "async runtime stopped before the task completed",
15638 "terminal host failure must settle the original operation as cancelled"
15639 );
15640 assert_eq!(backend.work.state.lock().unwrap().active, 0);
15641
15642 let later_registration = backend
15643 .work
15644 .try_register_async()
15645 .expect("terminal cancellation must reopen acceptance");
15646 let later_task = spawn_registered(
15647 &backend,
15648 Arc::clone(&metrics),
15649 async { 2usize },
15650 later_registration,
15651 );
15652 assert_eq!(
15653 current_thread_recovery_host_dispatches(),
15654 3,
15655 "terminal cancellation must not permanently close the executor"
15656 );
15657 executor.drive_host_turn();
15658 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
15659 }
15660
15661 #[cfg(napi_runtime_os_threads)]
15670 #[test]
15671 fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
15672 use std::{sync::mpsc, time::Duration};
15673
15674 let (done_tx, done_rx) = mpsc::channel();
15675 std::thread::spawn(move || {
15676 let metrics = Arc::new(RuntimeMetrics::default());
15677 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15678 Arc::clone(&metrics),
15679 accept_current_thread_host_dispatch,
15680 ));
15681
15682 let scheduler_a = Arc::clone(&executor);
15684 let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15685 scheduler_a.schedule(runnable)
15686 });
15687 let shared = async move { task_a.fallible().await }.boxed().shared();
15688 let shared_for_b = shared.clone();
15689
15690 let scheduler_b = Arc::clone(&executor);
15692 let (runnable_b, task_b) = async_task::spawn(
15693 async move {
15694 shared_for_b.await;
15695 },
15696 move |runnable| scheduler_b.schedule(runnable),
15697 );
15698 executor.schedule(runnable_b);
15699 executor.drive_host_turn();
15700 assert_eq!(
15701 metrics.queued_runnables.load(Ordering::Relaxed),
15702 0,
15703 "B must be parked and unscheduled after its first poll"
15704 );
15705
15706 executor.schedule(runnable_a);
15709 let failed_dispatch = current_thread_dispatch(&executor);
15710 executor.cancel_host_dispatch_for_test(failed_dispatch);
15711 let replacement_dispatch = current_thread_dispatch(&executor);
15712 executor.cancel_host_dispatch_for_test(replacement_dispatch);
15713
15714 assert!(
15715 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15716 "the cancellation sweep must reopen the executor"
15717 );
15718 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15719 assert!(
15720 futures::executor::block_on(task_b.fallible()).is_none(),
15721 "B's rejected wake must still cancel B"
15722 );
15723 drop(shared);
15724 done_tx.send(()).unwrap();
15725 });
15726 done_rx
15727 .recv_timeout(Duration::from_secs(5))
15728 .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
15729 }
15730
15731 #[cfg(napi_runtime_os_threads)]
15741 #[test]
15742 fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
15743 use std::{
15744 sync::{Barrier, mpsc},
15745 time::Duration,
15746 };
15747
15748 let (done_tx, done_rx) = mpsc::channel();
15749 std::thread::spawn(move || {
15750 let metrics = Arc::new(RuntimeMetrics::default());
15751 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15752 Arc::clone(&metrics),
15753 accept_current_thread_host_dispatch,
15754 ));
15755 let backend =
15756 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15757 let generation = backend.generation();
15758
15759 let (abort_a, guard_a) = backend
15761 .work
15762 .try_register_async()
15763 .expect("the backend must accept work");
15764 let scheduler_a = Arc::clone(&executor);
15765 let (runnable_a, task_a) = async_task::spawn(
15766 RegisteredTaskFuture::new(
15767 Abortable::new(std::future::pending::<()>(), abort_a),
15768 guard_a,
15769 generation,
15770 ),
15771 move |runnable| scheduler_a.schedule(runnable),
15772 );
15773 let shared = async move { task_a.fallible().await.is_some() }
15774 .boxed()
15775 .shared();
15776 let shared_for_b = shared.clone();
15777
15778 let (abort_b, guard_b) = backend
15782 .work
15783 .try_register_async()
15784 .expect("the backend must accept work");
15785 let wake_in_flight = Arc::new(Barrier::new(2));
15786 let wake_release = Arc::new(Barrier::new(2));
15787 let scheduler_b = Arc::clone(&executor);
15788 let (runnable_b, task_b) = async_task::spawn(
15789 RegisteredTaskFuture::new(
15790 Abortable::new(
15791 async move {
15792 shared_for_b.await;
15793 },
15794 abort_b,
15795 ),
15796 guard_b,
15797 generation,
15798 ),
15799 {
15800 let wake_in_flight = Arc::clone(&wake_in_flight);
15801 let wake_release = Arc::clone(&wake_release);
15802 move |runnable| {
15803 wake_in_flight.wait();
15804 wake_release.wait();
15805 scheduler_b.schedule(runnable);
15806 }
15807 },
15808 );
15809 executor.schedule(runnable_b);
15810 executor.drive_host_turn();
15811 assert_eq!(
15812 metrics.queued_runnables.load(Ordering::Relaxed),
15813 0,
15814 "B must be parked and unscheduled after its first poll"
15815 );
15816
15817 let waker_thread = std::thread::spawn(move || drop(runnable_a));
15821 wake_in_flight.wait();
15822
15823 backend.begin_shutdown();
15826 wake_release.wait();
15827 backend.wait_until_idle();
15828
15829 waker_thread
15830 .join()
15831 .expect("the waker thread must not panic");
15832 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15833 assert!(
15834 futures::executor::block_on(task_b.fallible()).is_none(),
15835 "B's rejected wake must still cancel B"
15836 );
15837 assert_eq!(backend.work.state.lock().unwrap().active, 0);
15838 drop(shared);
15839 done_tx.send(()).unwrap();
15840 });
15841 done_rx
15842 .recv_timeout(Duration::from_secs(5))
15843 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
15844 }
15845
15846 #[cfg(napi_runtime_os_threads)]
15856 #[test]
15857 fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
15858 use std::{sync::mpsc, time::Duration};
15859
15860 let (done_tx, done_rx) = mpsc::channel();
15861 std::thread::spawn(move || {
15862 {
15863 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15864 .state
15865 .lock()
15866 .unwrap_or_else(std::sync::PoisonError::into_inner);
15867 assert!(state.entries.is_empty());
15868 assert!(state.dispatches.is_empty());
15869 }
15870 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
15871 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15872 ));
15873 let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15874 .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15875 let metrics = Arc::new(RuntimeMetrics::default());
15876 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15877 Arc::clone(&metrics),
15878 dispatch_waker_stack_failure_current_thread_tasks,
15879 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15880 ));
15881
15882 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15884 let shared = rx.map(|_| ()).boxed().shared();
15885 let shared_for_b = shared.clone();
15886 let scheduler_b = Arc::clone(&executor);
15887 let (runnable_b, task_b) = async_task::spawn(
15888 async move {
15889 shared_for_b.await;
15890 },
15891 move |runnable| scheduler_b.schedule(runnable),
15892 );
15893 executor.schedule(runnable_b);
15894
15895 let delivery = driver.deliveries()[0];
15898 let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15899 .claim_delivery(delivery.capability())
15900 .expect("the accepted delivery must be claimable");
15901 let mut host_turn = executor
15902 .try_admit_host_turn(dispatch)
15903 .expect("the claimed delivery must be admitted");
15904 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15905 host_turn.drive();
15906 drop(host_turn);
15907 let completion =
15908 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15909 assert!(completion.failed_dispatches.is_empty());
15910 assert!(!completion.redispatch);
15911 assert_eq!(
15912 metrics.queued_runnables.load(Ordering::Relaxed),
15913 0,
15914 "B must be parked and unscheduled after its first poll"
15915 );
15916
15917 driver.fail.store(true, Ordering::SeqCst);
15919 tx.send(()).expect("B must still hold its Shared clone");
15923 assert_eq!(
15924 driver.deliveries().len(),
15925 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
15929 "the wake must publish the dispatch, its one replacement, and the \
15930 retirement turns the terminal transition owes itself"
15931 );
15932 assert!(
15933 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15934 "the terminal failure must reopen the executor before the waker returns"
15935 );
15936 assert!(
15937 executor.has_rejected_work(),
15938 "B must be moved aside, not dropped on the waker's stack"
15939 );
15940 assert_eq!(
15941 metrics.queued_runnables.load(Ordering::Relaxed),
15942 0,
15943 "the moved runnable carries its queued debit"
15944 );
15945 let mut task_b = task_b.fallible();
15946 assert!(
15947 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15948 "B must still be pending: nothing may be dropped on the waker's stack"
15949 );
15950
15951 executor.drive_host_turn();
15954 assert!(!executor.has_rejected_work());
15955 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15956 assert!(
15957 poll_ready_within(&mut task_b, Duration::from_secs(5))
15958 .expect("the host turn must cancel B")
15959 .is_none(),
15960 "the terminal failure must still cancel B"
15961 );
15962 drop(shared);
15963
15964 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
15965 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15966 .state
15967 .lock()
15968 .unwrap_or_else(std::sync::PoisonError::into_inner);
15969 assert!(state.entries.is_empty());
15970 assert!(state.dispatches.is_empty());
15971 done_tx.send(()).unwrap();
15972 });
15973 done_rx
15974 .recv_timeout(Duration::from_secs(5))
15975 .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
15976 }
15977
15978 #[cfg(napi_runtime_os_threads)]
15982 fn poll_ready_within<F: Future + Unpin>(
15983 future: &mut F,
15984 timeout: std::time::Duration,
15985 ) -> Option<F::Output> {
15986 let deadline = std::time::Instant::now() + timeout;
15987 let mut cx = Context::from_waker(futures::task::noop_waker_ref());
15988 loop {
15989 if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
15990 return Some(output);
15991 }
15992 if std::time::Instant::now() >= deadline {
15993 return None;
15994 }
15995 std::thread::sleep(std::time::Duration::from_millis(1));
15996 }
15997 }
15998
15999 #[cfg(napi_runtime_os_threads)]
16007 #[test]
16008 fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
16009 use std::{sync::mpsc, time::Duration};
16010
16011 const WAIT: Duration = Duration::from_secs(5);
16012 let (done_tx, done_rx) = mpsc::channel();
16013 std::thread::spawn(move || {
16014 let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
16015 {
16016 let state = registry
16017 .state
16018 .lock()
16019 .unwrap_or_else(std::sync::PoisonError::into_inner);
16020 assert!(state.entries.is_empty());
16021 assert!(state.dispatches.is_empty());
16022 }
16023 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
16024 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16025 let metrics = Arc::new(RuntimeMetrics::default());
16026 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16027 Arc::clone(&metrics),
16028 dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
16029 registry,
16030 ));
16031
16032 let service = |index: usize| {
16034 let delivery = driver.deliveries()[index];
16035 let dispatch = registry
16036 .claim_delivery(delivery.capability())
16037 .expect("the accepted delivery must be claimable");
16038 let mut host_turn = executor
16039 .try_admit_host_turn(dispatch)
16040 .expect("the claimed delivery must be admitted");
16041 registry.mark_serviced(dispatch);
16042 host_turn.drive();
16043 drop(host_turn);
16044 let completion = registry.finish_delivery(delivery, false);
16045 assert!(completion.failed_dispatches.is_empty());
16046 assert!(!completion.redispatch);
16047 };
16048
16049 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16051 let shared = rx.map(|_| ()).boxed().shared();
16052 let shared_for_b = shared.clone();
16053 let scheduler_b = Arc::clone(&executor);
16054 let (runnable_b, task_b) = async_task::spawn(
16055 async move {
16056 shared_for_b.await;
16057 },
16058 move |runnable| scheduler_b.schedule(runnable),
16059 );
16060 executor.schedule(runnable_b);
16061 service(0);
16062 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16063
16064 driver.fail.store(true, Ordering::SeqCst);
16067 tx.send(()).expect("B must still hold its Shared clone");
16068 const AFTER_WAKE: usize =
16072 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16073 assert_eq!(driver.deliveries().len(), AFTER_WAKE);
16074 let mut task_b = task_b.fallible();
16075 assert!(
16076 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16077 "B must still be pending: nothing may be dropped on the waker's stack"
16078 );
16079
16080 driver.fail.store(false, Ordering::SeqCst);
16083 let scheduler_later = Arc::clone(&executor);
16084 let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
16085 scheduler_later.schedule(runnable)
16086 });
16087 executor.schedule(runnable_later);
16088 assert_eq!(
16089 driver.deliveries().len(),
16090 AFTER_WAKE + 1,
16091 "a submission after the terminal failure must publish a fresh delivery"
16092 );
16093 assert!(
16094 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16095 "the executor must be open for the healthy host"
16096 );
16097
16098 service(AFTER_WAKE);
16101 let mut task_later = task_later.fallible();
16102 assert_eq!(
16103 poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
16104 Some(42),
16105 "the later task must complete on the delivered host turn"
16106 );
16107 assert!(
16108 poll_ready_within(&mut task_b, WAIT)
16109 .expect("B must have been cancelled by the host turn")
16110 .is_none(),
16111 "the terminal failure must still cancel the work queued when it happened"
16112 );
16113 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16114 drop(shared);
16115
16116 registry.unregister(registration);
16117 let state = registry
16118 .state
16119 .lock()
16120 .unwrap_or_else(std::sync::PoisonError::into_inner);
16121 assert!(state.entries.is_empty());
16122 assert!(state.dispatches.is_empty());
16123 done_tx.send(()).unwrap();
16124 });
16125 done_rx
16126 .recv_timeout(WAIT)
16127 .expect("the healthy host never got a delivery for the later submission");
16128 }
16129
16130 #[cfg(napi_runtime_os_threads)]
16150 #[test]
16151 fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
16152 use std::{sync::mpsc, time::Duration};
16153
16154 const WAIT: Duration = Duration::from_secs(5);
16155 let (done_tx, done_rx) = mpsc::channel();
16156 std::thread::spawn(move || {
16157 let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
16158 {
16159 let state = registry
16160 .state
16161 .lock()
16162 .unwrap_or_else(std::sync::PoisonError::into_inner);
16163 assert!(state.entries.is_empty());
16164 assert!(state.dispatches.is_empty());
16165 }
16166 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16167 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16168 let metrics = Arc::new(RuntimeMetrics::default());
16169 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16170 Arc::clone(&metrics),
16171 dispatch_retirement_turn_current_thread_tasks,
16172 registry,
16173 ));
16174
16175 let service = |index: usize| {
16177 let delivery = driver.deliveries()[index];
16178 let dispatch = registry
16179 .claim_delivery(delivery.capability())
16180 .expect("the accepted delivery must be claimable");
16181 let mut host_turn = executor
16182 .try_admit_host_turn(dispatch)
16183 .expect("the claimed delivery must be admitted");
16184 registry.mark_serviced(dispatch);
16185 host_turn.drive();
16186 drop(host_turn);
16187 let completion = registry.finish_delivery(delivery, false);
16188 assert!(completion.failed_dispatches.is_empty());
16189 assert!(!completion.redispatch);
16190 };
16191
16192 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16194 let shared = rx.map(|_| ()).boxed().shared();
16195 let shared_for_b = shared.clone();
16196 let scheduler_b = Arc::clone(&executor);
16197 let (runnable_b, task_b) = async_task::spawn(
16198 async move {
16199 shared_for_b.await;
16200 },
16201 move |runnable| scheduler_b.schedule(runnable),
16202 );
16203 executor.schedule(runnable_b);
16204 service(0);
16205 assert_eq!(
16206 metrics.queued_runnables.load(Ordering::Relaxed),
16207 0,
16208 "B must be parked and unscheduled after its first poll"
16209 );
16210
16211 driver.fail_budget.store(2, Ordering::SeqCst);
16214 tx.send(()).expect("B must still hold its Shared clone");
16215
16216 assert_eq!(
16217 driver.deliveries().len(),
16218 4,
16219 "the wake must publish the dispatch, its one replacement, and the \
16220 retirement turn the terminal transition owes itself; without that \
16221 third publication no host turn ever follows and B stays pending \
16222 forever"
16223 );
16224 assert!(
16225 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16226 "the terminal failure must reopen the executor before the waker returns"
16227 );
16228 assert!(
16229 executor.has_rejected_work(),
16230 "B must be moved aside, not dropped on the waker's stack"
16231 );
16232 assert_eq!(
16233 metrics.queued_runnables.load(Ordering::Relaxed),
16234 0,
16235 "the moved runnable carries its queued debit"
16236 );
16237 let mut task_b = task_b.fallible();
16238 assert!(
16239 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16240 "B must still be pending: nothing may be dropped on the waker's stack"
16241 );
16242
16243 service(3);
16246 assert!(
16247 !executor.has_rejected_work(),
16248 "the retirement turn must cancel the moved work on its own stack"
16249 );
16250 assert!(
16251 poll_ready_within(&mut task_b, WAIT)
16252 .expect("the retirement turn must cancel B")
16253 .is_none(),
16254 "the terminal failure must still cancel the work queued when it happened"
16255 );
16256 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16257 drop(shared);
16258
16259 registry.unregister(registration);
16260 let state = registry
16261 .state
16262 .lock()
16263 .unwrap_or_else(std::sync::PoisonError::into_inner);
16264 assert!(state.entries.is_empty());
16265 assert!(state.dispatches.is_empty());
16266 done_tx.send(()).unwrap();
16267 });
16268 done_rx.recv_timeout(WAIT).expect(
16269 "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
16270 `queue.rejected` and its JoinHandle never settled",
16271 );
16272 }
16273
16274 #[cfg(napi_runtime_os_threads)]
16304 #[test]
16305 fn current_thread_nested_retirement_failure_keeps_its_continuation() {
16306 use std::{sync::mpsc, time::Duration};
16307
16308 const WAIT: Duration = Duration::from_secs(5);
16309 let (done_tx, done_rx) = mpsc::channel();
16310 std::thread::spawn(move || {
16311 let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
16312 {
16313 let state = registry
16314 .state
16315 .lock()
16316 .unwrap_or_else(std::sync::PoisonError::into_inner);
16317 assert!(state.entries.is_empty());
16318 assert!(state.dispatches.is_empty());
16319 }
16320 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16321 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16322 let metrics = Arc::new(RuntimeMetrics::default());
16323 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16324 Arc::clone(&metrics),
16325 dispatch_nested_retirement_current_thread_tasks,
16326 registry,
16327 ));
16328
16329 let service = |index: usize| {
16331 let delivery = driver.deliveries()[index];
16332 let dispatch = registry
16333 .claim_delivery(delivery.capability())
16334 .expect("the accepted delivery must be claimable");
16335 let mut host_turn = executor
16336 .try_admit_host_turn(dispatch)
16337 .expect("the claimed delivery must be admitted");
16338 registry.mark_serviced(dispatch);
16339 host_turn.drive();
16340 drop(host_turn);
16341 let completion = registry.finish_delivery(delivery, false);
16342 assert!(completion.failed_dispatches.is_empty());
16343 assert!(!completion.redispatch);
16344 };
16345
16346 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16349 let shared = rx.map(|_| ()).boxed().shared();
16350 let shared_for_b = shared.clone();
16351 let scheduler_b = Arc::clone(&executor);
16352 let (runnable_b, task_b) = async_task::spawn(
16353 async move {
16354 shared_for_b.await;
16355 },
16356 move |runnable| scheduler_b.schedule(runnable),
16357 );
16358 executor.schedule(runnable_b);
16359 service(0);
16360 assert_eq!(
16361 metrics.queued_runnables.load(Ordering::Relaxed),
16362 0,
16363 "B must be parked and unscheduled after its first poll"
16364 );
16365
16366 let scheduler_c = Arc::clone(&executor);
16371 let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
16372 scheduler_c.schedule(runnable);
16373 });
16374 let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
16375 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16376 let producer_executor = Arc::clone(&executor);
16377 let producer = std::thread::spawn(move || {
16378 let runnable = gate_rx
16379 .recv()
16380 .expect("the retirement turn must open the window");
16381 producer_executor.schedule(runnable);
16382 queued_tx.send(()).expect("W must still be waiting");
16383 });
16384 driver.during_delivery(3, move || {
16385 gate_tx.send(runnable_c).expect("the producer must be live");
16386 queued_rx
16387 .recv_timeout(WAIT)
16388 .expect("the producer must queue C before the retirement turn fails");
16389 });
16390
16391 driver.fail_budget.store(4, Ordering::SeqCst);
16396 tx.send(()).expect("B must still hold its Shared clone");
16397 producer.join().expect("the producer must not panic");
16398
16399 assert_eq!(
16400 driver.deliveries().len(),
16401 6,
16402 "the nested terminal transition may not publish on this stack, but the \
16403 retirement request it hands back must be serviced after the outer \
16404 publication unwinds; without that sixth delivery no host turn ever \
16405 follows and both B and C stay pending forever"
16406 );
16407 assert!(
16408 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16409 "both terminal failures must reopen the executor before the waker returns"
16410 );
16411 assert!(
16412 executor.has_rejected_work(),
16413 "B and C must be moved aside, not dropped on the waker's stack"
16414 );
16415 assert_eq!(
16416 metrics.queued_runnables.load(Ordering::Relaxed),
16417 0,
16418 "the moved runnables carry their queued debit"
16419 );
16420 let mut task_b = task_b.fallible();
16421 let mut task_c = task_c.fallible();
16422 assert!(
16423 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16424 "B must still be pending: nothing may be dropped on the waker's stack"
16425 );
16426 assert!(
16427 poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
16428 "C must still be pending: nothing may be dropped on the waker's stack"
16429 );
16430
16431 service(5);
16435 assert!(
16436 !executor.has_rejected_work(),
16437 "the retirement turn must cancel both moved runnables on its own stack"
16438 );
16439 assert!(
16440 poll_ready_within(&mut task_b, WAIT)
16441 .expect("the retirement turn must cancel B")
16442 .is_none(),
16443 "the terminal failure must still cancel the work queued when it happened"
16444 );
16445 assert!(
16446 poll_ready_within(&mut task_c, WAIT)
16447 .expect("the retirement turn must cancel C")
16448 .is_none(),
16449 "the nested terminal failure must cancel the work it moved aside too"
16450 );
16451 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16452 drop(shared);
16453
16454 registry.unregister(registration);
16455 let state = registry
16456 .state
16457 .lock()
16458 .unwrap_or_else(std::sync::PoisonError::into_inner);
16459 assert!(state.entries.is_empty());
16460 assert!(state.dispatches.is_empty());
16461 done_tx.send(()).unwrap();
16462 });
16463 done_rx.recv_timeout(WAIT).expect(
16464 "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
16465 runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
16466 );
16467 }
16468
16469 #[cfg(napi_runtime_os_threads)]
16504 #[test]
16505 fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
16506 use std::{sync::mpsc, time::Duration};
16507
16508 const WAIT: Duration = Duration::from_secs(5);
16509 const PRODUCER_BUDGET: usize = 4096;
16512 const BOUNDED_DELIVERIES: usize =
16515 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16516
16517 let (done_tx, done_rx) = mpsc::channel();
16518 std::thread::spawn(move || {
16519 let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
16520 {
16521 let state = registry
16522 .state
16523 .lock()
16524 .unwrap_or_else(std::sync::PoisonError::into_inner);
16525 assert!(state.entries.is_empty());
16526 assert!(state.dispatches.is_empty());
16527 }
16528 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16529 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16530 let metrics = Arc::new(RuntimeMetrics::default());
16531 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16532 Arc::clone(&metrics),
16533 dispatch_sustained_producer_current_thread_tasks,
16534 registry,
16535 ));
16536 let backend =
16537 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16538
16539 let service = |index: usize| {
16541 let delivery = driver.deliveries()[index];
16542 let dispatch = registry
16543 .claim_delivery(delivery.capability())
16544 .expect("the accepted delivery must be claimable");
16545 let mut host_turn = executor
16546 .try_admit_host_turn(dispatch)
16547 .expect("the claimed delivery must be admitted");
16548 registry.mark_serviced(dispatch);
16549 host_turn.drive();
16550 drop(host_turn);
16551 let completion = registry.finish_delivery(delivery, false);
16552 assert!(completion.failed_dispatches.is_empty());
16553 assert!(!completion.redispatch);
16554 };
16555
16556 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16560 let shared = rx.map(|_| ()).boxed().shared();
16561 let shared_for_b = shared.clone();
16562 let work_registration = backend
16563 .work
16564 .try_register_async()
16565 .expect("the backend must accept work");
16566 let task_b = spawn_registered(
16567 &backend,
16568 Arc::clone(&metrics),
16569 async move {
16570 shared_for_b.await;
16571 },
16572 work_registration,
16573 );
16574 service(0);
16575 assert_eq!(
16576 metrics.queued_runnables.load(Ordering::Relaxed),
16577 0,
16578 "B must be parked and unscheduled after its first poll"
16579 );
16580 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16581
16582 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16587 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16588 let producer_executor = Arc::clone(&executor);
16589 let producer = std::thread::spawn(move || {
16590 let mut submissions = 0usize;
16591 while queue_one_rx.recv().is_ok() {
16592 let scheduler = Arc::clone(&producer_executor);
16593 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16594 scheduler.schedule(runnable);
16595 });
16596 task.detach();
16597 producer_executor.schedule(runnable);
16598 submissions += 1;
16599 if queued_tx.send(()).is_err() {
16600 break;
16601 }
16602 }
16603 submissions
16604 });
16605 driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
16606
16607 driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
16610 let before = driver.deliveries().len();
16611 tx.send(()).expect("B must still hold its Shared clone");
16612 let during_wake = driver.deliveries().len() - before;
16613 let unspent = driver.disarm_producer();
16614 let submissions = producer.join().expect("the producer must not panic");
16615
16616 assert_eq!(
16617 during_wake, BOUNDED_DELIVERIES,
16618 "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
16619 on the waker's stack plus its one final offer; without a budget it \
16620 services the producer's stream instead and this wake never returns"
16621 );
16622 assert_eq!(
16623 submissions, during_wake,
16624 "the producer must have queued a runnable during every publication"
16625 );
16626 assert!(
16627 unspent > 0,
16628 "the producer must still have been willing to submit when the turns stopped"
16629 );
16630 assert!(
16631 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16632 "every terminal failure, and the final offer, must reopen the executor \
16633 before the waker returns"
16634 );
16635 assert!(
16636 executor.has_rejected_work(),
16637 "the moved work must be parked, not dropped on the waker's stack"
16638 );
16639 assert_eq!(
16640 executor.dispatch_pending.load(Ordering::Acquire),
16641 0,
16642 "a host that refused every offer including the final one leaves none \
16643 outstanding: this is the documented end state for a host that never accepts"
16644 );
16645 assert_eq!(
16646 metrics.queued_runnables.load(Ordering::Relaxed),
16647 0,
16648 "the moved runnables carry their queued debit"
16649 );
16650 let mut task_b = Box::pin(task_b);
16651 assert!(
16652 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16653 "B must still be pending: nothing may be dropped on the waker's stack"
16654 );
16655 assert_eq!(
16656 backend.work.state.lock().unwrap().active,
16657 1,
16658 "B's generation guard rides with its parked runnable"
16659 );
16660
16661 backend.wait_until_idle();
16665 assert!(!executor.has_rejected_work());
16666 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16667 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16668 assert!(
16669 poll_ready_within(&mut task_b, WAIT)
16670 .expect("the idle wait must cancel B")
16671 .is_err(),
16672 "the terminal failure must still cancel the work queued when it happened"
16673 );
16674 drop(shared);
16675
16676 registry.unregister(registration);
16677 let state = registry
16678 .state
16679 .lock()
16680 .unwrap_or_else(std::sync::PoisonError::into_inner);
16681 assert!(state.entries.is_empty());
16682 assert!(state.dispatches.is_empty());
16683 done_tx.send(()).unwrap();
16684 });
16685 done_rx.recv_timeout(WAIT).expect(
16686 "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
16687 the wake never returned",
16688 );
16689 }
16690
16691 #[cfg(napi_runtime_os_threads)]
16725 #[test]
16726 fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
16727 use std::{sync::mpsc, time::Duration};
16728
16729 const WAIT: Duration = Duration::from_secs(5);
16730 const BUDGETED_DELIVERIES: usize =
16734 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16735
16736 let (done_tx, done_rx) = mpsc::channel();
16737 std::thread::spawn(move || {
16738 struct DropFlag(Arc<AtomicBool>);
16739 impl Drop for DropFlag {
16740 fn drop(&mut self) {
16741 self.0.store(true, Ordering::SeqCst);
16742 }
16743 }
16744
16745 let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
16746 {
16747 let state = registry
16748 .state
16749 .lock()
16750 .unwrap_or_else(std::sync::PoisonError::into_inner);
16751 assert!(state.entries.is_empty());
16752 assert!(state.dispatches.is_empty());
16753 }
16754 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16755 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16756 let metrics = Arc::new(RuntimeMetrics::default());
16757 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16758 Arc::clone(&metrics),
16759 dispatch_final_retirement_offer_current_thread_tasks,
16760 registry,
16761 ));
16762 let backend =
16763 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16764
16765 let service = |index: usize| {
16766 let delivery = driver.deliveries()[index];
16767 let dispatch = registry
16768 .claim_delivery(delivery.capability())
16769 .expect("the accepted delivery must be claimable");
16770 let mut host_turn = executor
16771 .try_admit_host_turn(dispatch)
16772 .expect("the claimed delivery must be admitted");
16773 registry.mark_serviced(dispatch);
16774 host_turn.drive();
16775 drop(host_turn);
16776 let completion = registry.finish_delivery(delivery, false);
16777 assert!(completion.failed_dispatches.is_empty());
16778 assert!(!completion.redispatch);
16779 };
16780
16781 let dropped = Arc::new(AtomicBool::new(false));
16782 let flag = DropFlag(Arc::clone(&dropped));
16783 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16784 let shared = rx.map(|_| ()).boxed().shared();
16785 let shared_for_b = shared.clone();
16786 let work_registration = backend
16787 .work
16788 .try_register_async()
16789 .expect("the backend must accept work");
16790 let task_b = spawn_registered(
16791 &backend,
16792 Arc::clone(&metrics),
16793 async move {
16794 let _flag = flag;
16795 shared_for_b.await;
16796 },
16797 work_registration,
16798 );
16799 service(0);
16800 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16801 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16802
16803 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16804 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16805 let producer_executor = Arc::clone(&executor);
16806 let producer = std::thread::spawn(move || {
16807 let mut submissions = 0usize;
16808 while queue_one_rx.recv().is_ok() {
16809 let scheduler = Arc::clone(&producer_executor);
16810 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16811 scheduler.schedule(runnable);
16812 });
16813 task.detach();
16814 producer_executor.schedule(runnable);
16815 submissions += 1;
16816 if queued_tx.send(()).is_err() {
16817 break;
16818 }
16819 }
16820 submissions
16821 });
16822 driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
16825 driver
16826 .fail_budget
16827 .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
16828
16829 let before = driver.deliveries().len();
16830 tx.send(()).expect("B must still hold its Shared clone");
16831 let during_wake = driver.deliveries().len() - before;
16832 let unspent = driver.disarm_producer();
16833 let submissions = producer.join().expect("the producer must not panic");
16834
16835 assert_eq!(
16836 during_wake,
16837 BUDGETED_DELIVERIES + 1,
16838 "the budget must be followed by the one offer a producer cannot \
16839 inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
16840 deliveries and never asks the host that would have accepted"
16841 );
16842 assert_eq!(
16843 submissions, BUDGETED_DELIVERIES,
16844 "the producer must have queued a runnable during every budgeted \
16845 publication and gone quiescent before the final offer"
16846 );
16847 assert_eq!(
16848 unspent, 0,
16849 "the producer must have run dry exactly as the budget ran out"
16850 );
16851 assert_eq!(
16852 driver.fail_budget.load(Ordering::SeqCst),
16853 0,
16854 "the host must have refused every budgeted delivery and no more"
16855 );
16856 assert!(
16857 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16858 "the final offer must reopen the executor before the waker returns"
16859 );
16860 assert!(
16861 executor.has_rejected_work(),
16862 "nothing may be dropped on the waker's stack"
16863 );
16864 assert_ne!(
16865 executor.dispatch_pending.load(Ordering::Acquire),
16866 0,
16867 "the wake must leave a dispatch outstanding: that accepted delivery is \
16868 the continuation the parked work has"
16869 );
16870 let mut task_b = Box::pin(task_b);
16871 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16872 assert!(!dropped.load(Ordering::SeqCst));
16873 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16874
16875 service(driver.deliveries().len() - 1);
16878 assert!(
16879 !executor.has_rejected_work(),
16880 "that host turn is a depth-0 stack and must retire everything the wake \
16881 moved aside"
16882 );
16883 assert!(
16884 poll_ready_within(&mut task_b, WAIT)
16885 .expect("the final offer's host turn must cancel B")
16886 .is_err()
16887 );
16888 assert!(
16889 dropped.load(Ordering::SeqCst),
16890 "B's future must be destroyed, on the host's stack and not the waker's"
16891 );
16892 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16893 assert_eq!(
16894 backend.work.state.lock().unwrap().active,
16895 0,
16896 "no GenerationWorkGuard may outlive the retirement"
16897 );
16898 drop(shared);
16899
16900 registry.unregister(registration);
16901 let state = registry
16902 .state
16903 .lock()
16904 .unwrap_or_else(std::sync::PoisonError::into_inner);
16905 assert!(state.entries.is_empty());
16906 assert!(state.dispatches.is_empty());
16907 done_tx.send(()).unwrap();
16908 });
16909 done_rx.recv_timeout(WAIT).expect(
16910 "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16911 );
16912 }
16913
16914 #[cfg(napi_runtime_os_threads)]
16932 #[test]
16933 fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
16934 use std::{sync::mpsc, time::Duration};
16935
16936 const WAIT: Duration = Duration::from_secs(5);
16937 let (done_tx, done_rx) = mpsc::channel();
16938 std::thread::spawn(move || {
16939 struct DropFlag(Arc<AtomicBool>);
16940 impl Drop for DropFlag {
16941 fn drop(&mut self) {
16942 self.0.store(true, Ordering::SeqCst);
16943 }
16944 }
16945
16946 let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
16947 {
16948 let state = registry
16949 .state
16950 .lock()
16951 .unwrap_or_else(std::sync::PoisonError::into_inner);
16952 assert!(state.entries.is_empty());
16953 assert!(state.dispatches.is_empty());
16954 }
16955 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16956 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16957 let metrics = Arc::new(RuntimeMetrics::default());
16958 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16959 Arc::clone(&metrics),
16960 dispatch_retirement_retry_current_thread_tasks,
16961 registry,
16962 ));
16963 let backend =
16964 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16965
16966 let service = |index: usize| {
16967 let delivery = driver.deliveries()[index];
16968 let dispatch = registry
16969 .claim_delivery(delivery.capability())
16970 .expect("the accepted delivery must be claimable");
16971 let mut host_turn = executor
16972 .try_admit_host_turn(dispatch)
16973 .expect("the claimed delivery must be admitted");
16974 registry.mark_serviced(dispatch);
16975 host_turn.drive();
16976 drop(host_turn);
16977 let completion = registry.finish_delivery(delivery, false);
16978 assert!(completion.failed_dispatches.is_empty());
16979 assert!(!completion.redispatch);
16980 };
16981
16982 let dropped = Arc::new(AtomicBool::new(false));
16983 let flag = DropFlag(Arc::clone(&dropped));
16984 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16985 let shared = rx.map(|_| ()).boxed().shared();
16986 let shared_for_b = shared.clone();
16987 let work_registration = backend
16988 .work
16989 .try_register_async()
16990 .expect("the backend must accept work");
16991 let task_b = spawn_registered(
16992 &backend,
16993 Arc::clone(&metrics),
16994 async move {
16995 let _flag = flag;
16996 shared_for_b.await;
16997 },
16998 work_registration,
16999 );
17000 service(0);
17001 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17002 assert_eq!(backend.work.state.lock().unwrap().active, 1);
17003
17004 let before = driver.deliveries().len();
17009 driver.fail_budget.store(3, Ordering::SeqCst);
17010 tx.send(()).expect("B must still hold its Shared clone");
17011 let during_wake = driver.deliveries().len() - before;
17012
17013 assert_eq!(
17014 during_wake, 4,
17015 "a refused offer owes another turn just as a nested transition does: \
17016 work is parked and no dispatch is outstanding"
17017 );
17018 assert_ne!(
17019 executor.dispatch_pending.load(Ordering::Acquire),
17020 0,
17021 "the accepted second offer is the continuation the parked work has"
17022 );
17023 assert!(executor.has_rejected_work());
17024 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17025 let mut task_b = Box::pin(task_b);
17026 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17027 assert!(!dropped.load(Ordering::SeqCst));
17028
17029 service(driver.deliveries().len() - 1);
17031 assert!(!executor.has_rejected_work());
17032 assert!(
17033 poll_ready_within(&mut task_b, WAIT)
17034 .expect("the accepted offer's host turn must cancel B")
17035 .is_err()
17036 );
17037 assert!(dropped.load(Ordering::SeqCst));
17038 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17039 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17040 drop(shared);
17041
17042 registry.unregister(registration);
17043 let state = registry
17044 .state
17045 .lock()
17046 .unwrap_or_else(std::sync::PoisonError::into_inner);
17047 assert!(state.entries.is_empty());
17048 assert!(state.dispatches.is_empty());
17049 done_tx.send(()).unwrap();
17050 });
17051 done_rx
17052 .recv_timeout(WAIT)
17053 .expect("a refused retirement offer left B stranded with a healthy host");
17054 }
17055
17056 #[cfg(napi_runtime_os_threads)]
17088 #[test]
17089 fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
17090 use std::{sync::mpsc, time::Duration};
17091
17092 const WAIT: Duration = Duration::from_secs(5);
17093 const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
17095
17096 let (done_tx, done_rx) = mpsc::channel();
17097 std::thread::spawn(move || {
17098 struct DropFlag(Arc<AtomicBool>);
17099 impl Drop for DropFlag {
17100 fn drop(&mut self) {
17101 self.0.store(true, Ordering::SeqCst);
17102 }
17103 }
17104
17105 let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
17106 {
17107 let state = registry
17108 .state
17109 .lock()
17110 .unwrap_or_else(std::sync::PoisonError::into_inner);
17111 assert!(state.entries.is_empty());
17112 assert!(state.dispatches.is_empty());
17113 }
17114 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
17115 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17116 let metrics = Arc::new(RuntimeMetrics::default());
17117 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17118 Arc::clone(&metrics),
17119 dispatch_inline_final_offer_current_thread_tasks,
17120 registry,
17121 ));
17122 let backend =
17123 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17124
17125 let service = |index: usize| {
17126 let delivery = driver.deliveries()[index];
17127 let dispatch = registry
17128 .claim_delivery(delivery.capability())
17129 .expect("the accepted delivery must be claimable");
17130 let mut host_turn = executor
17131 .try_admit_host_turn(dispatch)
17132 .expect("the claimed delivery must be admitted");
17133 registry.mark_serviced(dispatch);
17134 host_turn.drive();
17135 drop(host_turn);
17136 let completion = registry.finish_delivery(delivery, false);
17137 assert!(completion.failed_dispatches.is_empty());
17138 assert!(!completion.redispatch);
17139 };
17140
17141 let dropped = Arc::new(AtomicBool::new(false));
17142 let flag = DropFlag(Arc::clone(&dropped));
17143 let (tx, rx) = futures::channel::oneshot::channel::<()>();
17144 let shared = rx.map(|_| ()).boxed().shared();
17145 let shared_for_b = shared.clone();
17146 let work_registration = backend
17147 .work
17148 .try_register_async()
17149 .expect("the backend must accept work");
17150 let task_b = spawn_registered(
17151 &backend,
17152 Arc::clone(&metrics),
17153 async move {
17154 let _flag = flag;
17155 shared_for_b.await;
17156 },
17157 work_registration,
17158 );
17159 service(0);
17160 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17161 assert_eq!(backend.work.state.lock().unwrap().active, 1);
17162
17163 let before = driver.deliveries().len();
17168 let serviced_inline = Arc::new(AtomicBool::new(false));
17169 {
17170 let executor = Arc::clone(&executor);
17171 let hook_driver = Arc::clone(&driver);
17172 let serviced_inline = Arc::clone(&serviced_inline);
17173 driver.during_delivery(before + REFUSED_DELIVERIES, move || {
17174 let delivery = *hook_driver
17175 .deliveries()
17176 .last()
17177 .expect("the final offer must be recorded before the hook runs");
17178 let dispatch = registry
17179 .claim_delivery(delivery.capability())
17180 .expect("the final offer must be claimable");
17181 let mut host_turn = executor
17182 .try_admit_host_turn(dispatch)
17183 .expect("the claimed final offer must be admitted");
17184 registry.mark_serviced(dispatch);
17185 host_turn.drive();
17186 drop(host_turn);
17187 let completion = registry.finish_delivery(delivery, false);
17188 assert!(completion.failed_dispatches.is_empty());
17189 serviced_inline.store(true, Ordering::SeqCst);
17190 });
17191 }
17192 driver
17193 .fail_budget
17194 .store(REFUSED_DELIVERIES, Ordering::SeqCst);
17195 tx.send(()).expect("B must still hold its Shared clone");
17196 let during_wake = driver.deliveries().len() - before;
17197
17198 assert!(
17199 serviced_inline.load(Ordering::SeqCst),
17200 "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
17201 );
17202 assert_eq!(
17203 during_wake,
17204 REFUSED_DELIVERIES + 2,
17205 "an accepted offer whose host turn could not act owes the same continuation a refused \
17206 one owes: work parked and no dispatch outstanding"
17207 );
17208 assert_ne!(
17209 executor.dispatch_pending.load(Ordering::Acquire),
17210 0,
17211 "the publication that follows the consumed offer is the continuation the parked work has"
17212 );
17213 assert!(executor.has_rejected_work());
17214 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17215 let mut task_b = Box::pin(task_b);
17216 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17217 assert!(!dropped.load(Ordering::SeqCst));
17218
17219 service(driver.deliveries().len() - 1);
17221 assert!(!executor.has_rejected_work());
17222 assert!(
17223 poll_ready_within(&mut task_b, WAIT)
17224 .expect("the host turn that follows the consumed offer must cancel B")
17225 .is_err()
17226 );
17227 assert!(dropped.load(Ordering::SeqCst));
17228 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17229 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17230 drop(shared);
17231
17232 registry.unregister(registration);
17233 let state = registry
17234 .state
17235 .lock()
17236 .unwrap_or_else(std::sync::PoisonError::into_inner);
17237 assert!(state.entries.is_empty());
17238 assert!(state.dispatches.is_empty());
17239 done_tx.send(()).unwrap();
17240 });
17241 done_rx.recv_timeout(WAIT).expect(
17242 "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
17243 );
17244 }
17245
17246 #[cfg(napi_runtime_os_threads)]
17261 #[test]
17262 fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
17263 use std::time::Duration;
17264
17265 let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
17266 {
17267 let state = registry
17268 .state
17269 .lock()
17270 .unwrap_or_else(std::sync::PoisonError::into_inner);
17271 assert!(state.entries.is_empty());
17272 assert!(state.dispatches.is_empty());
17273 }
17274 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
17275 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17276 let metrics = Arc::new(RuntimeMetrics::default());
17277 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17278 Arc::clone(&metrics),
17279 dispatch_submission_failure_current_thread_tasks,
17280 registry,
17281 ));
17282 let backend =
17283 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17284
17285 driver.fail.store(true, Ordering::SeqCst);
17288
17289 let work_registration = backend
17290 .work
17291 .try_register_async()
17292 .expect("the backend must accept work");
17293 let task = spawn_registered(
17294 &backend,
17295 Arc::clone(&metrics),
17296 std::future::pending::<()>(),
17297 work_registration,
17298 );
17299
17300 assert_eq!(
17301 driver.deliveries().len(),
17302 2,
17303 "the submission must publish the dispatch and exactly one replacement"
17304 );
17305 assert!(
17306 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
17307 "the terminal failure must reopen the executor"
17308 );
17309 assert!(
17310 !executor.has_rejected_work(),
17311 "a submission's own stack is not a waker's: the rejected work must be cancelled here, because nothing else will"
17312 );
17313 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17314 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17315
17316 let mut task = Box::pin(task);
17317 assert_eq!(
17318 poll_ready_within(&mut task, Duration::ZERO)
17319 .expect("the submission must settle without any later entry")
17320 .unwrap_err()
17321 .to_string(),
17322 "async runtime stopped before the task completed",
17323 "terminal host failure must settle the submitted operation as cancelled"
17324 );
17325
17326 registry.unregister(registration);
17327 let state = registry
17328 .state
17329 .lock()
17330 .unwrap_or_else(std::sync::PoisonError::into_inner);
17331 assert!(state.entries.is_empty());
17332 assert!(state.dispatches.is_empty());
17333 }
17334
17335 #[cfg(napi_runtime_os_threads)]
17349 #[test]
17350 fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
17351 use std::{
17352 sync::{Barrier, mpsc},
17353 time::Duration,
17354 };
17355
17356 let (done_tx, done_rx) = mpsc::channel();
17357 std::thread::spawn(move || {
17358 let metrics = Arc::new(RuntimeMetrics::default());
17359 let options = RuntimeOptions {
17360 flavor: RuntimeFlavor::MultiThread,
17361 worker_threads: 2,
17362 max_blocking_tasks: 1,
17363 thread_name_prefix: "mt-inline-drop".to_string(),
17364 park_deadline: None,
17365 drain_linger: DEFAULT_DRAIN_LINGER,
17366 };
17367 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17368 let backend =
17369 RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
17370 let generation = backend.generation();
17371
17372 let (abort_a, guard_a) = backend
17374 .work
17375 .try_register_async()
17376 .expect("the backend must accept work");
17377 let scheduler_a = Arc::clone(&executor);
17378 let (runnable_a, task_a) = async_task::spawn(
17379 RegisteredTaskFuture::new(
17380 Abortable::new(std::future::pending::<()>(), abort_a),
17381 guard_a,
17382 generation,
17383 ),
17384 move |runnable| scheduler_a.schedule(runnable),
17385 );
17386 let shared = async move { task_a.fallible().await.is_some() }
17387 .boxed()
17388 .shared();
17389 let shared_for_b = shared.clone();
17390
17391 let (abort_b, guard_b) = backend
17395 .work
17396 .try_register_async()
17397 .expect("the backend must accept work");
17398 let wake_in_flight = Arc::new(Barrier::new(2));
17399 let wake_release = Arc::new(Barrier::new(2));
17400 let scheduler_b = Arc::clone(&executor);
17401 let (runnable_b, task_b) = async_task::spawn(
17402 RegisteredTaskFuture::new(
17403 Abortable::new(
17404 async move {
17405 shared_for_b.await;
17406 },
17407 abort_b,
17408 ),
17409 guard_b,
17410 generation,
17411 ),
17412 {
17413 let wake_in_flight = Arc::clone(&wake_in_flight);
17414 let wake_release = Arc::clone(&wake_release);
17415 move |runnable| {
17416 wake_in_flight.wait();
17417 wake_release.wait();
17418 scheduler_b.schedule(runnable);
17419 }
17420 },
17421 );
17422 executor.schedule(runnable_b);
17427 wait_until("B parked after its first poll", || {
17428 metrics.runnable_polls.load(Ordering::SeqCst) == 1
17429 && metrics.active_runnables.load(Ordering::SeqCst) == 0
17430 && metrics.queued_runnables.load(Ordering::SeqCst) == 0
17431 });
17432
17433 let waker_thread = std::thread::spawn(move || drop(runnable_a));
17437 wake_in_flight.wait();
17438
17439 backend.begin_shutdown();
17442 wake_release.wait();
17443 backend.wait_until_idle();
17444
17445 waker_thread
17446 .join()
17447 .expect("the waker thread must not panic");
17448 assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
17449 assert!(
17450 futures::executor::block_on(task_b.fallible()).is_none(),
17451 "B's late wake must still cancel B"
17452 );
17453 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17454 drop(shared);
17455 done_tx.send(()).unwrap();
17456 });
17457 done_rx
17458 .recv_timeout(Duration::from_secs(5))
17459 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
17460 }
17461
17462 #[cfg(napi_runtime_os_threads)]
17463 #[test]
17464 fn current_thread_recovery_reservation_blocks_new_host_publication() {
17465 use std::sync::mpsc;
17466
17467 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
17468 let metrics = Arc::new(RuntimeMetrics::default());
17469 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17470 Arc::clone(&metrics),
17471 count_current_thread_interleaved_recovery_host_dispatch,
17472 ));
17473 let backend =
17474 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17475
17476 let registration = backend
17477 .work
17478 .try_register_async()
17479 .expect("the backend must accept work");
17480 let task = spawn_registered(
17481 &backend,
17482 Arc::clone(&metrics),
17483 std::future::pending::<()>(),
17484 registration,
17485 );
17486 let failed_dispatch = current_thread_dispatch(&executor);
17487 assert_eq!(
17488 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17489 1
17490 );
17491
17492 let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
17493 let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
17494 let cancelling_executor = Arc::clone(&executor);
17495 let cancellation = std::thread::spawn(move || {
17496 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
17497 *slot.borrow_mut() = Some(Box::new(move || {
17498 recovery_cleared_tx.send(()).unwrap();
17499 release_recovery_rx.recv().unwrap();
17500 }));
17501 });
17502 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
17503 });
17504 recovery_cleared_rx
17505 .recv_timeout(Duration::from_secs(2))
17506 .expect("failed dispatch cancellation must pause after consuming the old capability");
17507
17508 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
17509 let (publication_done_tx, publication_done_rx) = mpsc::channel();
17510 let publishing_executor = Arc::clone(&executor);
17511 let publication = std::thread::spawn(move || {
17512 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
17513 *slot.borrow_mut() = Some(Box::new(move || {
17514 publication_blocked_tx.send(()).unwrap();
17515 }));
17516 });
17517 publishing_executor.request_drain_if_queued();
17518 publication_done_tx.send(()).unwrap();
17519 });
17520
17521 if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
17522 release_recovery_tx.send(()).unwrap();
17523 cancellation.join().unwrap();
17524 publication.join().unwrap();
17525 panic!("a newly registered host must wait for recovery reservation: {error}");
17526 }
17527 assert!(
17528 matches!(
17529 publication_done_rx.try_recv(),
17530 Err(mpsc::TryRecvError::Empty)
17531 ),
17532 "the new host must not publish in the consumed-to-recovery gap"
17533 );
17534 release_recovery_tx.send(()).unwrap();
17535
17536 cancellation.join().unwrap();
17537 publication_done_rx
17538 .recv_timeout(Duration::from_secs(2))
17539 .unwrap();
17540 publication.join().unwrap();
17541
17542 let replacement_dispatch = current_thread_dispatch(&executor);
17543 assert_ne!(replacement_dispatch, failed_dispatch);
17544 assert_eq!(
17545 executor.recovery_dispatch.load(Ordering::Acquire),
17546 replacement_dispatch
17547 );
17548 assert_eq!(
17549 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17550 3,
17551 "the original publication and two host attempts for its one replacement must run"
17552 );
17553
17554 executor.cancel_host_dispatch_for_test(replacement_dispatch);
17555
17556 assert_eq!(
17557 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17558 3,
17559 "failure of the tagged replacement must cancel work without a second replacement"
17560 );
17561 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17562 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17563 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17564 assert_eq!(
17565 futures::executor::block_on(task).unwrap_err().to_string(),
17566 "async runtime stopped before the task completed"
17567 );
17568
17569 let later_registration = backend
17570 .work
17571 .try_register_async()
17572 .expect("terminal cancellation must reopen acceptance");
17573 let later_task = spawn_registered(
17574 &backend,
17575 Arc::clone(&metrics),
17576 async { 2usize },
17577 later_registration,
17578 );
17579 assert_eq!(
17580 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17581 4
17582 );
17583 executor.drive_host_turn();
17584 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
17585 }
17586
17587 #[cfg(napi_runtime_os_threads)]
17588 #[test]
17589 fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
17590 use std::sync::mpsc;
17591
17592 struct BlockingDrop {
17593 entered: mpsc::Sender<()>,
17594 release: mpsc::Receiver<()>,
17595 }
17596
17597 impl Drop for BlockingDrop {
17598 fn drop(&mut self) {
17599 self.entered.send(()).unwrap();
17600 self.release.recv().unwrap();
17601 }
17602 }
17603
17604 reset_current_thread_recovery_host_dispatches();
17605 let metrics = Arc::new(RuntimeMetrics::default());
17606 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17607 Arc::clone(&metrics),
17608 count_current_thread_recovery_host_dispatch,
17609 ));
17610 let backend =
17611 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17612
17613 let holder_registration = backend
17614 .work
17615 .try_register_work()
17616 .expect("the backend must accept the blocking holder");
17617 let (holder_started_tx, holder_started_rx) = mpsc::channel();
17618 let (release_holder_tx, release_holder_rx) = mpsc::channel();
17619 let holder_executor = Arc::clone(&executor);
17620 let holder = std::thread::spawn(move || {
17621 let handle = holder_executor.schedule_blocking(
17622 move || {
17623 holder_started_tx.send(()).unwrap();
17624 release_holder_rx.recv().unwrap();
17625 },
17626 holder_registration,
17627 );
17628 futures::executor::block_on(handle).unwrap();
17629 });
17630 holder_started_rx
17631 .recv_timeout(Duration::from_secs(2))
17632 .expect("the first blocking call must hold the CurrentThread lane");
17633
17634 let cancelled_registration = backend
17635 .work
17636 .try_register_work()
17637 .expect("the backend must accept queued blocking work");
17638 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
17639 let (release_drop_tx, release_drop_rx) = mpsc::channel();
17640 let blocking_drop = BlockingDrop {
17641 entered: drop_entered_tx,
17642 release: release_drop_rx,
17643 };
17644 let cancelled = executor.schedule_blocking(
17645 move || {
17646 let _blocking_drop = blocking_drop;
17647 },
17648 cancelled_registration,
17649 );
17650
17651 let async_registration = backend
17652 .work
17653 .try_register_async()
17654 .expect("the backend must accept queued async work");
17655 let pending = spawn_registered(
17656 &backend,
17657 Arc::clone(&metrics),
17658 std::future::pending::<()>(),
17659 async_registration,
17660 );
17661 let failed_dispatch = current_thread_dispatch(&executor);
17662 executor.cancel_host_dispatch_for_test(failed_dispatch);
17663 let replacement_dispatch = current_thread_dispatch(&executor);
17664
17665 let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
17666 let cancelling_executor = Arc::clone(&executor);
17667 let cancellation = std::thread::spawn(move || {
17668 cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
17669 cancel_done_tx.send(()).unwrap();
17670 });
17671 drop_entered_rx
17672 .recv_timeout(Duration::from_secs(2))
17673 .expect("terminal dispatch cancellation must destroy the queued blocking capture");
17674 let active_during_drop = backend.work.state.lock().unwrap().active;
17675 let cancellation_blocked = cancel_done_rx.try_recv().is_err();
17676
17677 release_drop_tx.send(()).unwrap();
17678 cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17679 cancellation.join().unwrap();
17680
17681 assert_eq!(
17682 active_during_drop, 2,
17683 "the blocking capture's work registration must remain active until its destructor returns"
17684 );
17685 assert!(
17686 cancellation_blocked,
17687 "terminal cancellation must wait for capture destruction"
17688 );
17689 assert!(futures::executor::block_on(cancelled).is_err());
17690 assert!(futures::executor::block_on(pending).is_err());
17691 assert_eq!(backend.work.state.lock().unwrap().active, 1);
17692
17693 release_holder_tx.send(()).unwrap();
17694 holder.join().unwrap();
17695 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17696 }
17697
17698 #[test]
17699 fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
17700 reset_current_thread_one_shot_host_dispatches();
17701 let metrics = Arc::new(RuntimeMetrics::default());
17702 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17703 Arc::clone(&metrics),
17704 accept_only_first_current_thread_host_dispatch,
17705 ));
17706
17707 let scheduler = Arc::clone(&executor);
17708 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
17709 scheduler.schedule(runnable)
17710 });
17711 executor.schedule(runnable);
17712 let failed_dispatch = current_thread_dispatch(&executor);
17713
17714 executor.cancel_host_dispatch_for_test(failed_dispatch);
17715
17716 assert_eq!(
17717 current_thread_one_shot_host_dispatches(),
17718 2,
17719 "the original and its one bounded replacement must be attempted"
17720 );
17721 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17722 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17723 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17724 assert!(
17725 futures::executor::block_on(task.fallible()).is_none(),
17726 "an unavailable replacement host must settle the original task as cancelled"
17727 );
17728 }
17729
17730 enum TestCurrentThreadTaskDriverBehavior {
17731 Healthy,
17732 PanicInLivenessAndSweep,
17733 PanicInDispatchAndSweep,
17734 }
17735
17736 struct TestCurrentThreadTaskDriver {
17737 behavior: TestCurrentThreadTaskDriverBehavior,
17738 dispatched: AtomicBool,
17739 }
17740
17741 #[derive(Default)]
17742 struct ManualCurrentThreadTaskDriver {
17743 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17744 }
17745
17746 struct FailDuringAuthoritativePanicPublication;
17747 struct FailDuringLateRegistrationPublication;
17748 struct FailDuringNestedUnwindPublication;
17749 struct FailDuringReplacementRepublishPublication;
17750 struct FirstHostileCurrentThreadPanic;
17751 struct LaterHostileCurrentThreadPanic;
17752
17753 impl Drop for FirstHostileCurrentThreadPanic {
17754 fn drop(&mut self) {
17755 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17756 panic!("the first retained CurrentThread panic payload was dropped");
17757 }
17758 }
17759
17760 impl Drop for LaterHostileCurrentThreadPanic {
17761 fn drop(&mut self) {
17762 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17763 panic!("a later CurrentThread panic payload escaped containment");
17764 }
17765 }
17766
17767 impl ManualCurrentThreadTaskDriver {
17768 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17769 self.deliveries.lock().unwrap().clone()
17770 }
17771 }
17772
17773 impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
17774 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17775 self.deliveries.lock().unwrap().push(delivery);
17776 true
17777 }
17778 }
17779
17780 struct SynchronouslyFailingCurrentThreadTaskDriver {
17785 registry: &'static CurrentThreadTaskDriverRegistry,
17786 fail: AtomicBool,
17787 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17788 }
17789
17790 impl SynchronouslyFailingCurrentThreadTaskDriver {
17791 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17792 Self {
17793 registry,
17794 fail: AtomicBool::new(false),
17795 deliveries: Mutex::default(),
17796 }
17797 }
17798
17799 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17800 self.deliveries.lock().unwrap().clone()
17801 }
17802 }
17803
17804 impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
17805 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17806 self.deliveries.lock().unwrap().push(delivery);
17807 if self.fail.load(Ordering::SeqCst) {
17808 let registry = self.registry;
17809 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17810 .join()
17811 .expect("the failing helper must not panic");
17812 assert!(
17813 completion.failed_dispatches.is_empty(),
17814 "the failed delivery must remain provisional until its publication retires"
17815 );
17816 }
17817 true
17818 }
17819 }
17820
17821 type DeliveryHook = Box<dyn FnOnce() + Send>;
17829
17830 struct BoundedFailureCurrentThreadTaskDriver {
17831 registry: &'static CurrentThreadTaskDriverRegistry,
17832 fail_budget: AtomicUsize,
17833 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17834 during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
17835 }
17836
17837 impl BoundedFailureCurrentThreadTaskDriver {
17838 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17839 Self {
17840 registry,
17841 fail_budget: AtomicUsize::new(0),
17842 deliveries: Mutex::default(),
17843 during_delivery: Mutex::default(),
17844 }
17845 }
17846
17847 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17848 self.deliveries.lock().unwrap().clone()
17849 }
17850
17851 fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
17856 *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
17857 }
17858 }
17859
17860 impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
17861 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17862 let index = {
17863 let mut deliveries = self.deliveries.lock().unwrap();
17864 deliveries.push(delivery);
17865 deliveries.len() - 1
17866 };
17867 let hook = {
17868 let mut slot = self.during_delivery.lock().unwrap();
17869 if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
17870 slot.take().map(|(_, hook)| hook)
17871 } else {
17872 None
17873 }
17874 };
17875 if let Some(hook) = hook {
17876 hook();
17877 }
17878 let budget = self.fail_budget.load(Ordering::SeqCst);
17879 if budget != 0 {
17880 self.fail_budget.store(budget - 1, Ordering::SeqCst);
17881 let registry = self.registry;
17882 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17883 .join()
17884 .expect("the failing helper must not panic");
17885 assert!(
17886 completion.failed_dispatches.is_empty(),
17887 "the failed delivery must remain provisional until its publication retires"
17888 );
17889 }
17890 true
17891 }
17892 }
17893
17894 struct SustainedProducerGate {
17901 remaining: usize,
17902 queue_one: std::sync::mpsc::Sender<()>,
17903 queued: std::sync::mpsc::Receiver<()>,
17904 }
17905
17906 struct SustainedProducerCurrentThreadTaskDriver {
17907 registry: &'static CurrentThreadTaskDriverRegistry,
17908 fail_budget: AtomicUsize,
17911 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17912 producer: Mutex<Option<SustainedProducerGate>>,
17913 }
17914
17915 impl SustainedProducerCurrentThreadTaskDriver {
17916 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17917 Self {
17918 registry,
17919 fail_budget: AtomicUsize::new(0),
17920 deliveries: Mutex::default(),
17921 producer: Mutex::default(),
17922 }
17923 }
17924
17925 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17926 self.deliveries.lock().unwrap().clone()
17927 }
17928
17929 fn arm_producer(
17930 &self,
17931 remaining: usize,
17932 queue_one: std::sync::mpsc::Sender<()>,
17933 queued: std::sync::mpsc::Receiver<()>,
17934 ) {
17935 *self.producer.lock().unwrap() = Some(SustainedProducerGate {
17936 remaining,
17937 queue_one,
17938 queued,
17939 });
17940 }
17941
17942 fn disarm_producer(&self) -> usize {
17945 self
17946 .producer
17947 .lock()
17948 .unwrap()
17949 .take()
17950 .map_or(0, |gate| gate.remaining)
17951 }
17952 }
17953
17954 impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
17955 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17956 self.deliveries.lock().unwrap().push(delivery);
17957 {
17958 let mut slot = self.producer.lock().unwrap();
17959 if let Some(gate) = slot.as_mut()
17960 && gate.remaining != 0
17961 {
17962 gate.remaining -= 1;
17963 gate.queue_one.send(()).expect("the producer must be live");
17964 gate
17965 .queued
17966 .recv_timeout(std::time::Duration::from_secs(5))
17967 .expect("the producer must queue one runnable per publication");
17968 }
17969 }
17970 let budget = self.fail_budget.load(Ordering::SeqCst);
17971 if budget != 0 {
17972 if budget != usize::MAX {
17973 self.fail_budget.store(budget - 1, Ordering::SeqCst);
17974 }
17975 let completion = self.registry.finish_delivery(delivery, true);
17976 assert!(
17977 completion.failed_dispatches.is_empty(),
17978 "the failed delivery must remain provisional until its publication retires"
17979 );
17980 }
17981 true
17982 }
17983 }
17984
17985 impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
17986 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17987 let completion =
17988 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
17989 assert!(
17990 completion.failed_dispatches.is_empty(),
17991 "the failed delivery must remain provisional until its publication retires"
17992 );
17993 true
17994 }
17995 }
17996
17997 impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
17998 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17999 let completion =
18000 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
18001 assert!(
18002 completion.failed_dispatches.is_empty(),
18003 "the failed delivery must remain provisional until its publication retires"
18004 );
18005 true
18006 }
18007 }
18008
18009 impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
18010 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18011 let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
18012 assert!(
18013 completion.failed_dispatches.is_empty(),
18014 "the failed delivery must remain provisional until its publication retires"
18015 );
18016 true
18017 }
18018 }
18019
18020 impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
18021 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18022 let completion =
18023 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
18024 assert!(
18025 completion.failed_dispatches.is_empty(),
18026 "the failed delivery must remain provisional until its publication retires"
18027 );
18028 true
18029 }
18030 }
18031
18032 struct ReentrantCurrentThreadTaskDriver {
18033 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18034 registration: Mutex<Option<CurrentThreadTaskDriverId>>,
18035 }
18036
18037 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
18038 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18039 true
18040 }
18041
18042 fn is_live(&self) -> bool {
18043 let registration = *self.registration.lock().unwrap();
18044 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
18045 registry.unregister(id);
18046 }
18047 false
18048 }
18049 }
18050
18051 struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
18052 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18053 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18054 }
18055
18056 impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
18057 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18058 true
18059 }
18060
18061 fn is_live(&self) -> bool {
18062 if let Some(registry) = self.registry.upgrade() {
18063 for registration in self.registrations.lock().unwrap().iter().copied() {
18064 let completion = registry.unregister(registration);
18065 assert!(completion.failed_dispatches.is_empty());
18066 assert!(!completion.redispatch);
18067 }
18068 }
18069 false
18070 }
18071 }
18072
18073 struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18074 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18075 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18076 drops: Arc<AtomicUsize>,
18077 }
18078
18079 impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18080 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18081 if let Some(registry) = self.registry.upgrade() {
18082 for registration in self.registrations.lock().unwrap().iter().copied() {
18083 let completion = registry.unregister(registration);
18084 assert!(completion.failed_dispatches.is_empty());
18085 assert!(!completion.redispatch);
18086 }
18087 }
18088 true
18089 }
18090 }
18091
18092 impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18093 fn drop(&mut self) {
18094 self.drops.fetch_add(1, Ordering::SeqCst);
18095 panic!("intentional reentrant CurrentThread task-driver destructor panic");
18096 }
18097 }
18098
18099 struct PanickingDropCurrentThreadTaskDriver {
18100 drops: Arc<AtomicUsize>,
18101 }
18102
18103 impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
18104 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18105 true
18106 }
18107 }
18108
18109 impl Drop for PanickingDropCurrentThreadTaskDriver {
18110 fn drop(&mut self) {
18111 self.drops.fetch_add(1, Ordering::SeqCst);
18112 panic!("intentional CurrentThread task-driver destructor panic");
18113 }
18114 }
18115
18116 struct ReentrantCurrentThreadTaskDriverDrop {
18117 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18118 fallback: CurrentThreadTaskDriverId,
18119 }
18120
18121 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
18122 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18123 true
18124 }
18125 }
18126
18127 impl Drop for ReentrantCurrentThreadTaskDriverDrop {
18128 fn drop(&mut self) {
18129 if let Some(registry) = self.registry.upgrade() {
18130 registry.unregister(self.fallback);
18131 }
18132 }
18133 }
18134
18135 impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
18136 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18137 assert!(
18138 !matches!(
18139 self.behavior,
18140 TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
18141 ),
18142 "intentional task-driver dispatch panic"
18143 );
18144 self.dispatched.store(true, Ordering::SeqCst);
18145 true
18146 }
18147
18148 fn is_live(&self) -> bool {
18149 assert!(
18150 !matches!(
18151 self.behavior,
18152 TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
18153 ),
18154 "intentional task-driver liveness panic"
18155 );
18156 true
18157 }
18158
18159 fn on_swept(&self) {
18160 assert!(
18161 matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
18162 "intentional task-driver sweep panic"
18163 );
18164 }
18165 }
18166
18167 #[cfg(panic = "unwind")]
18168 #[test]
18169 fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
18170 let registry = CurrentThreadTaskDriverRegistry::default();
18171 let fallback = Arc::new(TestCurrentThreadTaskDriver {
18172 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18173 dispatched: AtomicBool::new(false),
18174 });
18175 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18176 registry.register(Arc::new(TestCurrentThreadTaskDriver {
18177 behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
18178 dispatched: AtomicBool::new(false),
18179 }));
18180 registry.register(Arc::new(TestCurrentThreadTaskDriver {
18181 behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
18182 dispatched: AtomicBool::new(false),
18183 }));
18184
18185 assert_eq!(
18186 registry.dispatch(37),
18187 CurrentThreadHostDispatchResult::Accepted,
18188 "a panicking newest host must fall back to a live host"
18189 );
18190 assert!(fallback.dispatched.load(Ordering::SeqCst));
18191 }
18192
18193 #[cfg(panic = "unwind")]
18194 #[test]
18195 fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
18196 let registry = CurrentThreadTaskDriverRegistry::default();
18197 let first = Arc::new(ManualCurrentThreadTaskDriver::default());
18198 let second = Arc::new(ManualCurrentThreadTaskDriver::default());
18199 registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18200 registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18201 registry
18202 .next_delivery
18203 .store(u64::MAX - 1, Ordering::Relaxed);
18204
18205 let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
18206 assert!(
18207 publication.is_err(),
18208 "delivery-id exhaustion on the second host must unwind the publication"
18209 );
18210 {
18211 let state = registry
18212 .state
18213 .lock()
18214 .unwrap_or_else(std::sync::PoisonError::into_inner);
18215 assert!(
18216 !state.dispatches.contains_key(&38),
18217 "unwind cleanup must release the exact registry publication barrier"
18218 );
18219 assert!(
18220 state
18221 .entries
18222 .values()
18223 .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
18224 "failed transactional reservation must leave every physical host slot clean"
18225 );
18226 }
18227 assert!(first.deliveries().is_empty());
18228 assert!(second.deliveries().is_empty());
18229
18230 registry.next_delivery.store(0, Ordering::Relaxed);
18231 assert_eq!(
18232 registry.dispatch(38),
18233 CurrentThreadHostDispatchResult::Accepted
18234 );
18235 let first_delivery = first.deliveries()[0];
18236 let second_delivery = second.deliveries()[0];
18237 assert_eq!(
18238 registry.claim_delivery(first_delivery.capability()),
18239 Some(38)
18240 );
18241 assert_eq!(
18242 registry.claim_delivery(second_delivery.capability()),
18243 Some(38)
18244 );
18245 registry.mark_serviced(38);
18246 assert!(
18247 registry
18248 .finish_delivery(first_delivery, false)
18249 .failed_dispatches
18250 .is_empty()
18251 );
18252 assert!(
18253 registry
18254 .finish_delivery(second_delivery, false)
18255 .failed_dispatches
18256 .is_empty()
18257 );
18258
18259 assert_eq!(
18260 registry.dispatch(39),
18261 CurrentThreadHostDispatchResult::Accepted
18262 );
18263 let first_later = first.deliveries()[1];
18264 let second_later = second.deliveries()[1];
18265 assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
18266 assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
18267 registry.mark_serviced(39);
18268 assert!(
18269 registry
18270 .finish_delivery(first_later, false)
18271 .failed_dispatches
18272 .is_empty()
18273 );
18274 assert!(
18275 registry
18276 .finish_delivery(second_later, false)
18277 .failed_dispatches
18278 .is_empty()
18279 );
18280 assert!(
18281 registry
18282 .state
18283 .lock()
18284 .unwrap_or_else(std::sync::PoisonError::into_inner)
18285 .dispatches
18286 .is_empty()
18287 );
18288 let state = registry
18289 .state
18290 .lock()
18291 .unwrap_or_else(std::sync::PoisonError::into_inner);
18292 assert!(
18293 state
18294 .entries
18295 .values()
18296 .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
18297 );
18298 }
18299
18300 #[test]
18301 fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
18302 let registry = CurrentThreadTaskDriverRegistry::default();
18303 let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
18304 let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
18305 let dispatch = 40;
18306
18307 assert_eq!(
18308 registry.dispatch(dispatch),
18309 CurrentThreadHostDispatchResult::Accepted
18310 );
18311 let failed_delivery = failed.deliveries()[0];
18312 let failure = current_thread_dispatch_failure(dispatch, 0);
18313 assert_eq!(
18314 registry
18315 .finish_delivery(failed_delivery, true)
18316 .failed_dispatches,
18317 vec![failure]
18318 );
18319 registry.unregister(failed_id);
18320
18321 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18322 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18323 assert!(
18324 registry.rearm_failed_publication(failure),
18325 "a closed unreferenced dispatch must reopen for its armed publication owner"
18326 );
18327 assert_eq!(
18328 registry.dispatch(dispatch),
18329 CurrentThreadHostDispatchResult::Accepted
18330 );
18331 let replacement_delivery = replacement.deliveries()[0];
18332 assert_eq!(replacement_delivery.dispatch, dispatch);
18333 assert_eq!(
18334 registry.claim_delivery(replacement_delivery.capability()),
18335 Some(dispatch)
18336 );
18337 registry.mark_serviced(dispatch);
18338 assert!(
18339 registry
18340 .finish_delivery(replacement_delivery, false)
18341 .failed_dispatches
18342 .is_empty()
18343 );
18344 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18345 }
18346
18347 #[test]
18348 fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
18349 struct RacingTaskDriver {
18350 executor: Arc<CurrentThreadExecutor>,
18351 drives: bool,
18352 dispatches: Mutex<Vec<u64>>,
18353 }
18354
18355 impl CurrentThreadTaskDriver for RacingTaskDriver {
18356 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18357 self.dispatches.lock().unwrap().push(delivery.dispatch);
18358 if self.drives {
18359 self
18360 .executor
18361 .drive_host_turn_for_dispatch(delivery.dispatch);
18362 }
18363 true
18364 }
18365 }
18366
18367 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
18368 Arc::new(RuntimeMetrics::default()),
18369 accept_current_thread_host_dispatch,
18370 ));
18371 let polls = Arc::new(AtomicUsize::new(0));
18372 let task_polls = Arc::clone(&polls);
18373 let scheduler = Arc::clone(&executor);
18374 let (runnable, task) = async_task::spawn(
18375 async move {
18376 task_polls.fetch_add(1, Ordering::SeqCst);
18377 },
18378 move |runnable| scheduler.schedule(runnable),
18379 );
18380 executor.schedule(runnable);
18381 let dispatch = current_thread_dispatch(&executor);
18382
18383 let registry = CurrentThreadTaskDriverRegistry::default();
18384 let responsive = Arc::new(RacingTaskDriver {
18385 executor: Arc::clone(&executor),
18386 drives: true,
18387 dispatches: Mutex::new(Vec::new()),
18388 });
18389 let starved = Arc::new(RacingTaskDriver {
18390 executor: Arc::clone(&executor),
18391 drives: false,
18392 dispatches: Mutex::new(Vec::new()),
18393 });
18394 registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
18395 registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
18397
18398 assert_eq!(
18399 registry.dispatch(dispatch),
18400 CurrentThreadHostDispatchResult::Accepted
18401 );
18402 assert_eq!(
18403 responsive.dispatches.lock().unwrap().as_slice(),
18404 &[dispatch]
18405 );
18406 assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
18407 assert_eq!(
18408 polls.load(Ordering::SeqCst),
18409 1,
18410 "the responsive older host must win even when the newest host is starved"
18411 );
18412
18413 executor.drive_host_turn_for_dispatch(dispatch);
18416 assert_eq!(polls.load(Ordering::SeqCst), 1);
18417 futures::executor::block_on(task);
18418 }
18419
18420 #[test]
18421 fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
18422 const HOSTS: usize = 1_024;
18423
18424 let registry = CurrentThreadTaskDriverRegistry::default();
18425 let mut ids = Vec::with_capacity(HOSTS);
18426 let mut drivers = Vec::with_capacity(HOSTS);
18427 for _ in 0..HOSTS {
18428 let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
18429 ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
18430 drivers.push(driver);
18431 }
18432
18433 let dispatch = u64::MAX - 300;
18434 assert_eq!(
18435 registry.dispatch(dispatch),
18436 CurrentThreadHostDispatchResult::Accepted
18437 );
18438 let deliveries = drivers
18439 .iter()
18440 .map(|driver| driver.deliveries()[0])
18441 .collect::<Vec<_>>();
18442 {
18443 let state = registry
18444 .state
18445 .lock()
18446 .unwrap_or_else(std::sync::PoisonError::into_inner);
18447 assert_eq!(state.delivery_drivers.len(), HOSTS);
18448 assert_eq!(
18449 state
18450 .dispatches
18451 .get(&dispatch)
18452 .map(|state| state.references),
18453 Some(HOSTS)
18454 );
18455 assert_eq!(
18456 state
18457 .ordered_snapshot()
18458 .iter()
18459 .map(|(id, _)| *id)
18460 .collect::<Vec<_>>(),
18461 ids,
18462 "keyed driver lookup must retain registration order"
18463 );
18464 }
18465
18466 for delivery in &deliveries {
18467 assert_eq!(
18468 registry.claim_delivery(delivery.capability()),
18469 Some(dispatch)
18470 );
18471 }
18472 registry.mark_serviced(dispatch);
18473 for delivery in deliveries {
18474 assert!(
18475 registry
18476 .finish_delivery(delivery, false)
18477 .failed_dispatches
18478 .is_empty()
18479 );
18480 }
18481 {
18482 let state = registry
18483 .state
18484 .lock()
18485 .unwrap_or_else(std::sync::PoisonError::into_inner);
18486 assert!(state.delivery_drivers.is_empty());
18487 assert!(state.dispatches.is_empty());
18488 }
18489
18490 let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
18491 for id in &removed {
18492 let completion = registry.unregister(*id);
18493 assert!(completion.failed_dispatches.is_empty());
18494 assert!(!completion.redispatch);
18495 }
18496 let expected = ids
18497 .iter()
18498 .copied()
18499 .filter(|id| !removed.contains(id))
18500 .collect::<Vec<_>>();
18501 let live = registry.live().live;
18502 assert_eq!(
18503 live
18504 .as_slice()
18505 .iter()
18506 .map(|(id, _)| *id)
18507 .collect::<Vec<_>>(),
18508 expected,
18509 "O(1) removals must preserve the relative order of surviving host registrations"
18510 );
18511 drop(live);
18512
18513 for id in expected {
18514 registry.unregister(id);
18515 }
18516 let state = registry
18517 .state
18518 .lock()
18519 .unwrap_or_else(std::sync::PoisonError::into_inner);
18520 assert!(state.entries.is_empty());
18521 assert!(state.oldest.is_none());
18522 assert!(state.newest.is_none());
18523 }
18524
18525 #[test]
18526 fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
18527 let registry = CurrentThreadTaskDriverRegistry::default();
18528 let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
18529 let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
18530 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
18531 registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
18532 registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
18533 registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
18534
18535 let dispatch = 41;
18536 assert_eq!(
18537 registry.dispatch(dispatch),
18538 CurrentThreadHostDispatchResult::Accepted
18539 );
18540 let failing_delivery = failing.deliveries()[0];
18541 let delayed_delivery = delayed.deliveries()[0];
18542 let healthy_delivery = healthy.deliveries()[0];
18543 assert_eq!(failing_delivery.dispatch, dispatch);
18544 assert_eq!(delayed_delivery.dispatch, dispatch);
18545 assert_eq!(healthy_delivery.dispatch, dispatch);
18546
18547 assert!(
18548 registry
18549 .finish_delivery(failing_delivery, true)
18550 .failed_dispatches
18551 .is_empty(),
18552 "one host failure must not recover while other accepted attempts remain"
18553 );
18554 assert_eq!(
18555 registry.claim_delivery(healthy_delivery.capability()),
18556 Some(dispatch)
18557 );
18558 registry.mark_serviced(dispatch);
18559 assert!(
18560 registry
18561 .finish_delivery(healthy_delivery, false)
18562 .failed_dispatches
18563 .is_empty()
18564 );
18565 assert_eq!(
18566 registry.claim_delivery(delayed_delivery.capability()),
18567 None,
18568 "a delayed losing delivery must become stale after another host services the dispatch"
18569 );
18570 assert!(
18571 registry
18572 .finish_delivery(delayed_delivery, true)
18573 .failed_dispatches
18574 .is_empty(),
18575 "a stale losing failure must not recover or cancel a serviced dispatch"
18576 );
18577 assert!(
18578 registry.state.lock().unwrap().dispatches.is_empty(),
18579 "all per-dispatch acknowledgement state must retire"
18580 );
18581 }
18582
18583 #[test]
18584 fn current_thread_unclaimed_callback_success_is_delivery_failure() {
18585 let registry = CurrentThreadTaskDriverRegistry::default();
18586 let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
18587 registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
18588
18589 let dispatch = 56;
18590 assert_eq!(
18591 registry.dispatch(dispatch),
18592 CurrentThreadHostDispatchResult::Accepted
18593 );
18594 let delivery = no_op.deliveries()[0];
18595 let completion = registry.finish_delivery(delivery, false);
18596 let failure = current_thread_dispatch_failure(dispatch, 0);
18597 assert_eq!(
18598 completion.failed_dispatches,
18599 vec![failure],
18600 "a callback return without an exact capability claim must enter recovery"
18601 );
18602 assert!(
18603 registry
18604 .state
18605 .lock()
18606 .unwrap()
18607 .dispatches
18608 .get(&dispatch)
18609 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18610 );
18611 registry.finish_cancellation(failure);
18612 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18613 }
18614
18615 #[test]
18616 fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
18617 struct FailDuringPublication {
18618 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18619 }
18620
18621 impl CurrentThreadTaskDriver for FailDuringPublication {
18622 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18623 let completion = self
18624 .registry
18625 .upgrade()
18626 .expect("the registry must outlive publication")
18627 .finish_delivery(delivery, true);
18628 assert!(
18629 completion.failed_dispatches.is_empty(),
18630 "failure must remain provisional until the broadcast closes"
18631 );
18632 true
18633 }
18634 }
18635
18636 struct RejectProvisional;
18637
18638 impl CurrentThreadTaskDriver for RejectProvisional {
18639 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18640 false
18641 }
18642 }
18643
18644 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18645 registry.register(Arc::new(RejectProvisional));
18646 registry.register(Arc::new(FailDuringPublication {
18649 registry: Arc::downgrade(®istry),
18650 }));
18651
18652 let failure = current_thread_dispatch_failure(57, 0);
18653 assert_eq!(
18654 registry.dispatch(57),
18655 CurrentThreadHostDispatchResult::Failed(failure),
18656 "the final provisional rejection must expose the earlier accepted failure"
18657 );
18658 assert!(
18659 registry
18660 .state
18661 .lock()
18662 .unwrap()
18663 .dispatches
18664 .get(&57)
18665 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18666 );
18667 assert_eq!(
18668 registry.dispatch(57),
18669 CurrentThreadHostDispatchResult::Failed(failure),
18670 "a racing replacement publication must preserve terminal failure until cancellation runs"
18671 );
18672 registry.finish_cancellation(failure);
18673 }
18674
18675 #[test]
18676 fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
18677 struct RejectProvisional;
18678
18679 impl CurrentThreadTaskDriver for RejectProvisional {
18680 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18681 false
18682 }
18683 }
18684
18685 let registry = CurrentThreadTaskDriverRegistry::default();
18686 registry.register(Arc::new(RejectProvisional));
18687 registry.register(Arc::new(RejectProvisional));
18688
18689 assert_eq!(
18690 registry.dispatch(58),
18691 CurrentThreadHostDispatchResult::Unavailable
18692 );
18693 assert!(
18694 registry.state.lock().unwrap().dispatches.is_empty(),
18695 "a broadcast with no accepted attempt must not enter failure recovery"
18696 );
18697 }
18698
18699 #[test]
18700 fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
18701 let registry = CurrentThreadTaskDriverRegistry::default();
18702 let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
18703 let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
18704
18705 assert_eq!(
18706 registry.dispatch(1),
18707 CurrentThreadHostDispatchResult::Accepted
18708 );
18709 let first = blocked.deliveries()[0];
18710 for dispatch in 2..=256 {
18711 assert_eq!(
18712 registry.dispatch(dispatch),
18713 CurrentThreadHostDispatchResult::Accepted
18714 );
18715 assert_eq!(
18716 blocked.deliveries().len(),
18717 1,
18718 "a blocked host must never receive a second queued or in-flight callback"
18719 );
18720 }
18721 {
18722 let state = registry.state.lock().unwrap();
18723 let entry = state
18724 .entries
18725 .get(&blocked_id)
18726 .expect("the blocked host must remain registered");
18727 assert_eq!(entry.pending, Some(256));
18728 assert_eq!(
18729 state.dispatches.len(),
18730 2,
18731 "only the in-flight and latest pending dispatches may retain registry state"
18732 );
18733 }
18734
18735 assert_eq!(registry.claim_delivery(first.capability()), Some(1));
18736 registry.mark_serviced(1);
18737 let completion = registry.finish_delivery(first, false);
18738 assert!(completion.failed_dispatches.is_empty());
18739 assert!(completion.redispatch);
18740 assert_eq!(
18741 registry.dispatch(256),
18742 CurrentThreadHostDispatchResult::Accepted
18743 );
18744 let deliveries = blocked.deliveries();
18745 assert_eq!(deliveries.len(), 2);
18746 assert_eq!(
18747 deliveries[1].dispatch, 256,
18748 "completion must schedule exactly the latest coalesced capability"
18749 );
18750 assert_eq!(registry.claim_delivery(first.capability()), None);
18751 assert_eq!(
18752 registry.claim_delivery(deliveries[1].capability()),
18753 Some(256)
18754 );
18755 registry.mark_serviced(256);
18756 assert!(
18757 registry
18758 .finish_delivery(deliveries[1], false)
18759 .failed_dispatches
18760 .is_empty()
18761 );
18762 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18763 }
18764
18765 #[test]
18766 fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
18767 struct RejectAfterFirst {
18768 calls: AtomicUsize,
18769 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
18770 }
18771
18772 impl CurrentThreadTaskDriver for RejectAfterFirst {
18773 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18774 self.deliveries.lock().unwrap().push(delivery);
18775 self.calls.fetch_add(1, Ordering::SeqCst) == 0
18776 }
18777 }
18778
18779 let registry = CurrentThreadTaskDriverRegistry::default();
18780 let host = Arc::new(RejectAfterFirst {
18781 calls: AtomicUsize::new(0),
18782 deliveries: Mutex::new(Vec::new()),
18783 });
18784 registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18785 assert_eq!(
18786 registry.dispatch(11),
18787 CurrentThreadHostDispatchResult::Accepted
18788 );
18789 let first = host.deliveries.lock().unwrap()[0];
18790 assert_eq!(
18791 registry.dispatch(12),
18792 CurrentThreadHostDispatchResult::Accepted
18793 );
18794
18795 assert_eq!(registry.claim_delivery(first.capability()), Some(11));
18796 registry.mark_serviced(11);
18797 let completion = registry.finish_delivery(first, false);
18798 assert!(completion.redispatch);
18799 let failure = current_thread_dispatch_failure(12, 0);
18800 assert_eq!(
18801 registry.dispatch(12),
18802 CurrentThreadHostDispatchResult::Failed(failure),
18803 "an accepted coalesced capability must report failure if its eventual host queue rejects it"
18804 );
18805 assert!(
18806 registry
18807 .state
18808 .lock()
18809 .unwrap()
18810 .dispatches
18811 .get(&12)
18812 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18813 );
18814 registry.finish_cancellation(failure);
18815 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18816 }
18817
18818 #[test]
18819 fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
18820 let registry = CurrentThreadTaskDriverRegistry::default();
18821 let old = Arc::new(ManualCurrentThreadTaskDriver::default());
18822 let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
18823 assert_eq!(
18824 registry.dispatch(73),
18825 CurrentThreadHostDispatchResult::Accepted
18826 );
18827 let old_delivery = old.deliveries()[0];
18828
18829 let completion = registry.unregister(old_id);
18830 let failure = current_thread_dispatch_failure(73, 0);
18831 assert_eq!(
18832 completion.failed_dispatches,
18833 vec![failure],
18834 "unregistering the final accepting host must retire its exact dispatch"
18835 );
18836 registry.finish_cancellation(failure);
18837 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18838 let replacement_id =
18839 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18840 assert_ne!(replacement_id, old_id);
18841 assert_eq!(
18842 registry.dispatch(74),
18843 CurrentThreadHostDispatchResult::Accepted
18844 );
18845 let replacement_delivery = replacement.deliveries()[0];
18846 assert_ne!(replacement_delivery.capability(), old_delivery.capability());
18847
18848 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18849 assert!(
18850 registry
18851 .finish_delivery(old_delivery, true)
18852 .failed_dispatches
18853 .is_empty()
18854 );
18855 assert!(
18856 registry
18857 .fail_delivery(old_delivery.capability())
18858 .failed_dispatches
18859 .is_empty()
18860 );
18861 assert_eq!(
18862 registry.claim_delivery(replacement_delivery.capability()),
18863 Some(74)
18864 );
18865 registry.mark_serviced(74);
18866 assert!(
18867 registry
18868 .finish_delivery(replacement_delivery, false)
18869 .failed_dispatches
18870 .is_empty()
18871 );
18872 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18873 }
18874
18875 #[test]
18876 fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
18877 struct QueuePendingThenReject {
18878 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18879 }
18880
18881 impl CurrentThreadTaskDriver for QueuePendingThenReject {
18882 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18883 assert_eq!(delivery.dispatch, 81);
18884 let completion = self
18885 .registry
18886 .upgrade()
18887 .expect("the registry must outlive publication")
18888 .dispatch_with_completion(82);
18889 assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
18890 assert!(completion.failed_dispatches.is_empty());
18891 false
18892 }
18893 }
18894
18895 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18896 registry.register(Arc::new(QueuePendingThenReject {
18897 registry: Arc::downgrade(®istry),
18898 }));
18899
18900 let completion = registry.dispatch_with_completion(81);
18901 assert_eq!(
18902 completion.result,
18903 CurrentThreadHostDispatchResult::Unavailable
18904 );
18905 let failure = current_thread_dispatch_failure(82, 0);
18906 assert_eq!(
18907 completion.failed_dispatches,
18908 vec![failure],
18909 "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18910 );
18911 assert!(
18912 registry
18913 .state
18914 .lock()
18915 .unwrap()
18916 .dispatches
18917 .get(&82)
18918 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18919 );
18920 registry.finish_cancellation(failure);
18921 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18922 }
18923
18924 #[test]
18925 fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
18926 let registry = CurrentThreadTaskDriverRegistry::default();
18927 let host = Arc::new(ManualCurrentThreadTaskDriver::default());
18928 let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18929 assert_eq!(
18930 registry.dispatch(91),
18931 CurrentThreadHostDispatchResult::Accepted
18932 );
18933 let old_delivery = host.deliveries()[0];
18934 assert_eq!(
18935 registry.dispatch(92),
18936 CurrentThreadHostDispatchResult::Accepted
18937 );
18938
18939 registry.retire_for_shutdown();
18940 {
18941 let state = registry.state.lock().unwrap();
18942 assert!(state.dispatches.is_empty());
18943 let entry = state
18944 .entries
18945 .get(&host_id)
18946 .expect("the host must remain registered");
18947 assert_eq!(entry.pending, None);
18948 assert!(
18949 entry.delivery.is_some(),
18950 "the physical callback remains single-flight until it actually retires"
18951 );
18952 }
18953 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18954
18955 assert_eq!(
18956 registry.dispatch(93),
18957 CurrentThreadHostDispatchResult::Accepted
18958 );
18959 assert_eq!(host.deliveries().len(), 1);
18960 let completion = registry.finish_delivery(old_delivery, true);
18961 assert!(
18962 completion.failed_dispatches.is_empty(),
18963 "a retired-generation failure must not cancel restarted work"
18964 );
18965 assert!(completion.redispatch);
18966 assert_eq!(
18967 registry.dispatch(93),
18968 CurrentThreadHostDispatchResult::Accepted
18969 );
18970 let restarted_delivery = host.deliveries()[1];
18971 assert_eq!(restarted_delivery.dispatch, 93);
18972 assert_eq!(
18973 registry.claim_delivery(restarted_delivery.capability()),
18974 Some(93)
18975 );
18976 registry.mark_serviced(93);
18977 assert!(
18978 registry
18979 .finish_delivery(restarted_delivery, false)
18980 .failed_dispatches
18981 .is_empty()
18982 );
18983
18984 registry.unregister(host_id);
18985 let state = registry.state.lock().unwrap();
18986 assert!(state.entries.is_empty());
18987 assert!(state.dispatches.is_empty());
18988 }
18989
18990 #[cfg(napi_runtime_os_threads)]
18991 #[test]
18992 fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
18993 use std::{sync::mpsc, time::Duration};
18994
18995 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18996 let fallback = Arc::new(TestCurrentThreadTaskDriver {
18997 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18998 dispatched: AtomicBool::new(false),
18999 });
19000 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19001 let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
19002 registry: Arc::downgrade(®istry),
19003 registration: Mutex::new(None),
19004 });
19005 let reentrant_id =
19006 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19007 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
19008
19009 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
19010 let selection_registry = Arc::clone(®istry);
19011 let selection_thread = std::thread::spawn(move || {
19012 selection_tx
19013 .send(selection_registry.current().map(|(id, _)| id))
19014 .unwrap();
19015 });
19016 assert_eq!(
19017 selection_rx
19018 .recv_timeout(Duration::from_secs(10))
19019 .expect("is_live re-entry deadlocked"),
19020 Some(fallback_id)
19021 );
19022 selection_thread.join().unwrap();
19023
19024 let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
19025 Arc::new(ReentrantCurrentThreadTaskDriverDrop {
19026 registry: Arc::downgrade(®istry),
19027 fallback: fallback_id,
19028 });
19029 let drop_id = registry.register(Arc::clone(&drop_driver));
19030 drop(drop_driver);
19031
19032 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
19033 let drop_registry = Arc::clone(®istry);
19034 let drop_thread = std::thread::spawn(move || {
19035 drop_registry.unregister(drop_id);
19036 drop_tx.send(()).unwrap();
19037 });
19038 drop_rx
19039 .recv_timeout(Duration::from_secs(10))
19040 .expect("driver destructor re-entry deadlocked");
19041 drop_thread.join().unwrap();
19042 assert!(
19043 registry.current().is_none(),
19044 "the destructor must unregister the fallback"
19045 );
19046 }
19047
19048 #[cfg(not(target_family = "wasm"))]
19049 #[test]
19050 fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
19051 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
19052
19053 if std::env::var_os(CHILD_ENV).is_some() {
19054 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19055 let fallback = Arc::new(TestCurrentThreadTaskDriver {
19056 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
19057 dispatched: AtomicBool::new(false),
19058 });
19059 let fallback_id =
19060 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19061 let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
19062 registry: Arc::downgrade(®istry),
19063 registrations: Mutex::new(Vec::new()),
19064 });
19065 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19066
19067 let first_drops = Arc::new(AtomicUsize::new(0));
19068 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19069 drops: Arc::clone(&first_drops),
19070 });
19071 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19072 drop(first);
19073
19074 let second_drops = Arc::new(AtomicUsize::new(0));
19075 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19076 drops: Arc::clone(&second_drops),
19077 });
19078 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19079 drop(second);
19080 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
19081
19082 assert_eq!(
19083 registry.current().map(|(id, _)| id),
19084 Some(fallback_id),
19085 "selection must retry after reentrant removals and preserve the healthy fallback"
19086 );
19087 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19088 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19089 let state = registry
19090 .state
19091 .lock()
19092 .unwrap_or_else(std::sync::PoisonError::into_inner);
19093 assert_eq!(state.entries.len(), 1);
19094 assert!(state.entries.contains_key(&fallback_id));
19095 assert!(state.dispatches.is_empty());
19096 return;
19097 }
19098
19099 let output = std::process::Command::new(std::env::current_exe().unwrap())
19100 .arg("--exact")
19101 .arg(
19102 "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
19103 )
19104 .arg("--nocapture")
19105 .env(CHILD_ENV, "1")
19106 .output()
19107 .expect("the hostile task-driver snapshot subprocess test must start");
19108 assert!(
19109 output.status.success(),
19110 "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19111 output.status.code(),
19112 String::from_utf8_lossy(&output.stdout),
19113 String::from_utf8_lossy(&output.stderr)
19114 );
19115 }
19116
19117 #[cfg(not(target_family = "wasm"))]
19118 #[test]
19119 fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
19120 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
19121
19122 if std::env::var_os(CHILD_ENV).is_some() {
19123 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19124
19125 let first_drops = Arc::new(AtomicUsize::new(0));
19126 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19127 drops: Arc::clone(&first_drops),
19128 });
19129 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19130 drop(first);
19131
19132 let second_drops = Arc::new(AtomicUsize::new(0));
19133 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19134 drops: Arc::clone(&second_drops),
19135 });
19136 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19137 drop(second);
19138
19139 let reentrant_drops = Arc::new(AtomicUsize::new(0));
19140 let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
19141 registry: Arc::downgrade(®istry),
19142 registrations: Mutex::new(Vec::new()),
19143 drops: Arc::clone(&reentrant_drops),
19144 });
19145 let reentrant_id =
19146 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19147 *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
19148 drop(reentrant);
19149
19150 let dispatch = 59;
19151 let failure = current_thread_dispatch_failure(dispatch, 0);
19152 assert_eq!(
19153 registry.dispatch(dispatch),
19154 CurrentThreadHostDispatchResult::Failed(failure),
19155 "reentrant removal of every provisional host must report accepted delivery failure"
19156 );
19157 assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
19158 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19159 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19160 registry.finish_cancellation(failure);
19161 let state = registry
19162 .state
19163 .lock()
19164 .unwrap_or_else(std::sync::PoisonError::into_inner);
19165 assert!(state.entries.is_empty());
19166 assert!(state.dispatches.is_empty());
19167 return;
19168 }
19169
19170 let output = std::process::Command::new(std::env::current_exe().unwrap())
19171 .arg("--exact")
19172 .arg(
19173 "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
19174 )
19175 .arg("--nocapture")
19176 .env(CHILD_ENV, "1")
19177 .output()
19178 .expect("the hostile task-driver delivery subprocess test must start");
19179 assert!(
19180 output.status.success(),
19181 "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19182 output.status.code(),
19183 String::from_utf8_lossy(&output.stdout),
19184 String::from_utf8_lossy(&output.stderr)
19185 );
19186 }
19187
19188 #[cfg(napi_runtime_os_threads)]
19191 #[test]
19192 fn dropping_join_handle_detaches_task_like_tokio() {
19193 let completed = Arc::new(AtomicBool::new(false));
19194 let completed_task = Arc::clone(&completed);
19195 let (runnable, task) = async_task::spawn(
19196 async move {
19197 completed_task.store(true, Ordering::SeqCst);
19198 Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
19199 },
19200 |_| {},
19201 );
19202 let handle = JoinHandle(JoinHandleInner::Task {
19203 task: task.fallible(),
19204 awaiter: None,
19205 dependency: Arc::new(TaskDependency::default()),
19206 });
19207
19208 drop(handle);
19209 runnable.run();
19210 assert!(completed.load(Ordering::SeqCst));
19211 }
19212
19213 #[cfg(not(target_family = "wasm"))]
19214 #[test]
19215 fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
19216 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
19217
19218 if std::env::var_os(CHILD_ENV).is_some() {
19219 let wakes = Arc::new(AtomicUsize::new(0));
19220 let drops = Arc::new(AtomicUsize::new(0));
19221 let (runnable, task) = async_task::spawn(
19222 async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
19223 |_| {},
19224 );
19225 let mut handle = JoinHandle(JoinHandleInner::Task {
19226 task: task.fallible(),
19227 awaiter: None,
19228 dependency: Arc::new(TaskDependency::default()),
19229 });
19230 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19231 {
19232 let mut cx = Context::from_waker(&waker);
19233 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19234 }
19235 drop(waker);
19236
19237 runnable.run();
19238
19239 let waker = futures::task::noop_waker();
19240 let mut cx = Context::from_waker(&waker);
19241 assert!(matches!(
19242 Pin::new(&mut handle).poll(&mut cx),
19243 Poll::Ready(Ok(()))
19244 ));
19245 assert_eq!(
19246 drops.load(Ordering::SeqCst),
19247 1,
19248 "a completed handle must not retain the caller's awaiter"
19249 );
19250 drop(handle);
19251 assert!(wakes.load(Ordering::SeqCst) >= 1);
19252 assert_eq!(drops.load(Ordering::SeqCst), 1);
19253 return;
19254 }
19255
19256 let output = std::process::Command::new(std::env::current_exe().unwrap())
19257 .arg("--exact")
19258 .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
19259 .arg("--nocapture")
19260 .env(CHILD_ENV, "1")
19261 .output()
19262 .expect("the JoinHandle completion awaiter subprocess must start");
19263 assert!(
19264 output.status.success(),
19265 "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19266 output.status.code(),
19267 String::from_utf8_lossy(&output.stdout),
19268 String::from_utf8_lossy(&output.stderr)
19269 );
19270 }
19271
19272 #[cfg(not(target_family = "wasm"))]
19273 #[test]
19274 fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
19275 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
19276
19277 if std::env::var_os(CHILD_ENV).is_some() {
19278 let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
19279 let wakes = Arc::new(AtomicUsize::new(0));
19280 let drops = Arc::new(AtomicUsize::new(0));
19281 let mut handle = controller.spawn(std::future::pending::<()>());
19282 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19283 {
19284 let mut cx = Context::from_waker(&waker);
19285 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19286 }
19287 drop(waker);
19288
19289 controller
19290 .shutdown()
19291 .expect("shutdown must survive the hostile awaiter");
19292 drop(handle);
19293 assert!(wakes.load(Ordering::SeqCst) >= 1);
19294 assert_eq!(drops.load(Ordering::SeqCst), 1);
19295 return;
19296 }
19297
19298 let output = std::process::Command::new(std::env::current_exe().unwrap())
19299 .arg("--exact")
19300 .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
19301 .arg("--nocapture")
19302 .env(CHILD_ENV, "1")
19303 .output()
19304 .expect("the JoinHandle shutdown awaiter subprocess must start");
19305 assert!(
19306 output.status.success(),
19307 "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19308 output.status.code(),
19309 String::from_utf8_lossy(&output.stdout),
19310 String::from_utf8_lossy(&output.stderr)
19311 );
19312 }
19313
19314 #[cfg(napi_runtime_os_threads)]
19315 #[test]
19316 fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
19317 let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
19318 let mut handle = controller.spawn(std::future::pending::<()>());
19319 let old_generation = controller.backend().generation();
19320 let state = Arc::new(JoinAwaiterWakerState {
19321 events: Mutex::new(Vec::new()),
19322 });
19323 let waker = {
19324 let _generation = RuntimeGenerationGuard::enter(old_generation);
19325 join_awaiter_waker(Arc::clone(&state))
19326 };
19327 {
19328 let _generation = RuntimeGenerationGuard::enter(old_generation);
19329 let mut cx = Context::from_waker(&waker);
19330 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19331 }
19332 {
19333 let _generation = RuntimeGenerationGuard::enter(old_generation);
19334 drop(waker);
19335 }
19336
19337 controller
19338 .shutdown()
19339 .expect("the first generation must stop");
19340 controller.start().expect("the runtime must restart");
19341 let new_generation = controller.backend().generation();
19342 assert_ne!(new_generation, old_generation);
19343 state.clear();
19344
19345 let replacement = futures::task::noop_waker();
19346 {
19347 let _generation = RuntimeGenerationGuard::enter(new_generation);
19348 let mut cx = Context::from_waker(&replacement);
19349 assert!(matches!(
19350 Pin::new(&mut handle).poll(&mut cx),
19351 Poll::Ready(Err(_))
19352 ));
19353 }
19354
19355 let events = state.snapshot();
19356 assert!(
19357 !events.is_empty(),
19358 "completion must retire the registered awaiter waker"
19359 );
19360 assert!(
19361 events.iter().all(|(kind, generation)| {
19362 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19363 }),
19364 "old-generation awaiter retirement escaped its generation: {events:?}"
19365 );
19366 controller
19367 .shutdown()
19368 .expect("the replacement generation must stop");
19369 }
19370
19371 #[cfg(napi_runtime_os_threads)]
19372 #[test]
19373 fn dropping_join_handle_retains_awaiter_generation_after_restart() {
19374 let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
19375 let mut handle = controller.spawn(std::future::pending::<()>());
19376 let old_generation = controller.backend().generation();
19377 let state = Arc::new(JoinAwaiterWakerState {
19378 events: Mutex::new(Vec::new()),
19379 });
19380 let waker = {
19381 let _generation = RuntimeGenerationGuard::enter(old_generation);
19382 join_awaiter_waker(Arc::clone(&state))
19383 };
19384 {
19385 let _generation = RuntimeGenerationGuard::enter(old_generation);
19386 let mut cx = Context::from_waker(&waker);
19387 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19388 }
19389 {
19390 let _generation = RuntimeGenerationGuard::enter(old_generation);
19391 drop(waker);
19392 }
19393
19394 controller
19395 .shutdown()
19396 .expect("the first generation must stop");
19397 controller.start().expect("the runtime must restart");
19398 let new_generation = controller.backend().generation();
19399 assert_ne!(new_generation, old_generation);
19400 state.clear();
19401
19402 {
19403 let _generation = RuntimeGenerationGuard::enter(new_generation);
19404 drop(handle);
19405 }
19406
19407 let events = state.snapshot();
19408 assert!(
19409 !events.is_empty(),
19410 "handle drop must retire the registered awaiter waker"
19411 );
19412 assert!(
19413 events.iter().all(|(kind, generation)| {
19414 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19415 }),
19416 "old-generation awaiter destruction escaped its generation: {events:?}"
19417 );
19418 controller
19419 .shutdown()
19420 .expect("the replacement generation must stop");
19421 }
19422
19423 #[cfg(napi_runtime_os_threads)]
19424 #[test]
19425 fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
19426 use std::{sync::mpsc, time::Duration};
19427
19428 let options = RuntimeOptions {
19429 flavor: RuntimeFlavor::MultiThread,
19430 worker_threads: 2,
19431 max_blocking_tasks: 1,
19432 thread_name_prefix: "worker-classification".to_string(),
19433 park_deadline: None,
19434 drain_linger: DEFAULT_DRAIN_LINGER,
19435 };
19436 let executor =
19437 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19438 let id = executor.id;
19439 let (tx, rx) = mpsc::sync_channel(1);
19440
19441 executor.pool.spawn(move || {
19442 tx.send((
19443 ON_POOL_WORKER.with(std::cell::Cell::get),
19444 IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
19445 ))
19446 .unwrap();
19447 });
19448
19449 let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
19450 assert_eq!(
19451 worker,
19452 Some(id),
19453 "nested Rayon work must use cooperative block_on"
19454 );
19455 assert_eq!(
19456 driver, None,
19457 "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
19458 );
19459 }
19460
19461 #[cfg(napi_runtime_os_threads)]
19462 #[test]
19463 fn multi_thread_block_on_does_not_park_pool_worker() {
19464 use std::sync::{Barrier, mpsc};
19469 use std::time::Duration;
19470
19471 let (done_tx, done_rx) = mpsc::channel();
19472 let runner = std::thread::spawn(move || {
19473 let metrics = Arc::new(RuntimeMetrics::default());
19474 let options = RuntimeOptions {
19475 flavor: RuntimeFlavor::MultiThread,
19476 worker_threads: 2,
19477 max_blocking_tasks: 2,
19478 thread_name_prefix: "rd1-test".to_string(),
19479 park_deadline: None,
19480 drain_linger: DEFAULT_DRAIN_LINGER,
19481 };
19482 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19483
19484 let task_count = 2usize;
19485 let barrier = Arc::new(Barrier::new(task_count));
19488
19489 let mut tasks = Vec::new();
19490 for _ in 0..task_count {
19491 let exec = Arc::clone(&executor);
19492 let barrier = Arc::clone(&barrier);
19493 let body = async move {
19494 barrier.wait();
19495 let inner_exec = Arc::clone(&exec);
19496 let mut inner = std::pin::pin!(async move {
19497 let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
19498 assert_eq!(value, 7);
19499 });
19500 exec.block_on(inner.as_mut());
19502 };
19503 let scheduler = Arc::clone(&executor);
19504 let (runnable, task) =
19505 async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
19506 executor.schedule(runnable);
19507 tasks.push(task);
19508 }
19509
19510 for task in tasks {
19511 futures::executor::block_on(task);
19512 }
19513 let _ = done_tx.send(());
19514 });
19515
19516 match done_rx.recv_timeout(Duration::from_secs(10)) {
19517 Ok(()) => runner.join().unwrap(),
19518 Err(error) => panic!(
19519 "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
19520 ),
19521 }
19522 }
19523
19524 #[cfg(napi_runtime_os_threads)]
19525 #[test]
19526 fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
19527 use std::sync::mpsc;
19546
19547 for round in 0..3 {
19548 let metrics = Arc::new(RuntimeMetrics::default());
19549 let options = RuntimeOptions {
19550 flavor: RuntimeFlavor::MultiThread,
19551 worker_threads: 2,
19552 max_blocking_tasks: 1,
19553 thread_name_prefix: format!("linger-starve-{round}"),
19554 park_deadline: None,
19555 drain_linger: DEFAULT_DRAIN_LINGER,
19556 };
19557 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19558
19559 let r_done = Arc::new(AtomicBool::new(false));
19560 let give_up = Arc::new(AtomicBool::new(false));
19561 let (done_tx, done_rx) = mpsc::channel::<Duration>();
19562
19563 let t1_executor = Arc::clone(&executor);
19569 let t1_started = Arc::new(AtomicBool::new(false));
19570 let t1_started_setter = Arc::clone(&t1_started);
19571 let t1_body = async move {
19572 t1_started_setter.store(true, Ordering::SeqCst);
19573 let deadline = Instant::now() + Duration::from_secs(10);
19574 while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
19575 if Instant::now() >= deadline {
19576 return;
19578 }
19579 std::hint::spin_loop();
19580 }
19581 };
19582 let t1_scheduler = Arc::clone(&executor);
19583 let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
19584 executor.schedule(t1_runnable);
19585 wait_until("the worker-pinning task to start", || {
19586 t1_started.load(Ordering::SeqCst)
19587 });
19588
19589 let blocking_executor = Arc::clone(&executor);
19591 let r_done_setter = Arc::clone(&r_done);
19592 let started = Instant::now();
19593 let handle = executor.schedule_blocking(move || {
19594 let (r_tx, r_rx) = oneshot::channel::<()>();
19595 blocking_executor.pool.spawn(move || {
19596 r_done_setter.store(true, Ordering::SeqCst);
19597 let _ = r_tx.send(());
19598 });
19599 let mut wait_for_r = std::pin::pin!(async move {
19600 let _ = r_rx.await;
19601 });
19602 blocking_executor.block_on(wait_for_r.as_mut());
19603 let _ = done_tx.send(started.elapsed());
19604 });
19605
19606 let arm_deadline = Instant::now() + Duration::from_secs(15);
19610 while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
19611 && !r_done.load(Ordering::SeqCst)
19612 {
19613 assert!(
19614 Instant::now() < arm_deadline,
19615 "timed out arming the linger-starvation interleaving"
19616 );
19617 std::thread::yield_now();
19618 }
19619
19620 let producer_executor = Arc::clone(&executor);
19623 let producer_r_done = Arc::clone(&r_done);
19624 let producer_give_up = Arc::clone(&give_up);
19625 let producer = std::thread::spawn(move || {
19626 let cadence = Duration::from_micros(200);
19627 let mut next_tick = Instant::now();
19628 while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
19629 let scheduler = Arc::clone(&producer_executor);
19630 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
19631 producer_executor.schedule(runnable);
19632 task.detach();
19633 next_tick += cadence;
19634 while Instant::now() < next_tick {
19635 std::hint::spin_loop();
19636 }
19637 }
19638 });
19639
19640 let outcome = done_rx.recv_timeout(Duration::from_secs(5));
19641 give_up.store(true, Ordering::SeqCst);
19642 producer.join().unwrap();
19643 match outcome {
19644 Ok(elapsed) => {
19645 futures::executor::block_on(handle).expect("blocking closure join");
19646 drop(t1_task);
19647 eprintln!("round {round}: completed in {elapsed:?}");
19648 }
19649 Err(error) => panic!(
19650 "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
19651 sub-budget periodic work must not keep a drain frame resident forever"
19652 ),
19653 }
19654 }
19655 }
19656
19657 #[cfg(napi_runtime_os_threads)]
19658 #[test]
19659 #[ignore = "microbenchmark: run explicitly in release mode"]
19660 fn parked_drivers_registry_cycle_microbench() {
19661 for &n in &[8usize, 64, 256] {
19668 let registry = ParkedDrivers::default();
19669 let parkers: Vec<Arc<DriverParker>> =
19670 (0..n).map(|_| Arc::new(DriverParker::default())).collect();
19671 for parker in &parkers {
19672 registry.register_with_role(parker, true, None);
19673 }
19674 let hot = &parkers[n - 1];
19675
19676 let iters = 2_000_000u32;
19677 let start = Instant::now();
19678 for _ in 0..iters {
19679 assert!(registry.wake_one());
19680 registry.deregister(hot);
19681 assert!(hot.consume_permit());
19682 registry.register_with_role(hot, true, None);
19683 }
19684 let wake_cycle = start.elapsed() / iters;
19685
19686 let start = Instant::now();
19687 for _ in 0..iters {
19688 registry.deregister(hot);
19689 registry.register_with_role(hot, true, None);
19690 }
19691 let timeout_cycle = start.elapsed() / iters;
19692
19693 let owner = BlockingOwnerToken {
19694 executor_id: u64::MAX,
19695 frame: u64::MAX,
19696 };
19697 let start = Instant::now();
19698 for _ in 0..iters {
19699 assert!(!registry.wake_one_owner_dependency(owner));
19700 }
19701 let owner_sweep = start.elapsed() / iters;
19702
19703 eprintln!(
19704 "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
19705 timeout-deregister+re-register {timeout_cycle:?}, \
19706 owner-dependency sweep {owner_sweep:?}"
19707 );
19708 }
19709 }
19710
19711 #[cfg(napi_runtime_os_threads)]
19712 #[test]
19713 fn multi_thread_block_on_from_caller_thread_still_parks() {
19714 let metrics = Arc::new(RuntimeMetrics::default());
19717 let options = RuntimeOptions {
19718 flavor: RuntimeFlavor::MultiThread,
19719 worker_threads: 3,
19720 max_blocking_tasks: 2,
19721 thread_name_prefix: "rd1-caller".to_string(),
19722 park_deadline: None,
19723 drain_linger: DEFAULT_DRAIN_LINGER,
19724 };
19725 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19726
19727 let mut output = None;
19728 let mut future = std::pin::pin!(async {
19729 output = Some(41usize + 1);
19730 });
19731 executor.block_on(future.as_mut());
19732 assert_eq!(output, Some(42));
19733 }
19734
19735 #[cfg(napi_runtime_os_threads)]
19736 #[test]
19737 fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
19738 use std::sync::mpsc;
19761 use std::time::Duration;
19762
19763 struct CallerProbe {
19768 task: Task<usize>,
19769 parked_tx: Option<mpsc::Sender<()>>,
19770 }
19771 impl Future for CallerProbe {
19772 type Output = usize;
19773 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
19774 let poll = Pin::new(&mut self.task).poll(cx);
19776 if poll.is_pending()
19777 && let Some(tx) = self.parked_tx.take()
19778 {
19779 let _ = tx.send(());
19781 }
19782 poll
19783 }
19784 }
19785
19786 let (done_tx, done_rx) = mpsc::channel();
19787 let runner = std::thread::spawn(move || {
19788 let metrics = Arc::new(RuntimeMetrics::default());
19789 let options = RuntimeOptions {
19790 flavor: RuntimeFlavor::MultiThread,
19791 worker_threads: 2,
19792 max_blocking_tasks: 2,
19793 thread_name_prefix: "rd10-caller".to_string(),
19794 park_deadline: None,
19795 drain_linger: DEFAULT_DRAIN_LINGER,
19796 };
19797 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19798
19799 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
19804 let scheduler = Arc::clone(&executor);
19805 let (runnable, task) = async_task::spawn(
19806 async move { gate_rx.await.expect("gate sender dropped") },
19807 move |r| {
19808 scheduler.schedule(r);
19809 },
19810 );
19811 executor.schedule(runnable);
19812
19813 let (parked_tx, parked_rx) = mpsc::channel::<()>();
19817 let releaser = std::thread::spawn(move || {
19818 if parked_rx.recv().is_ok() {
19819 let _ = gate_tx.send(42usize);
19820 }
19821 });
19822
19823 let mut output = None;
19826 {
19827 let mut future = std::pin::pin!(async {
19828 output = Some(
19829 CallerProbe {
19830 task,
19831 parked_tx: Some(parked_tx),
19832 }
19833 .await,
19834 );
19835 });
19836 executor.block_on(future.as_mut());
19837 }
19838
19839 releaser.join().unwrap();
19840 assert_eq!(
19841 output,
19842 Some(42usize),
19843 "block_on must return the pool-computed value"
19844 );
19845 let _ = done_tx.send(());
19846 });
19847
19848 match done_rx.recv_timeout(Duration::from_secs(10)) {
19849 Ok(()) => runner.join().unwrap(),
19850 Err(error) => panic!(
19851 "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
19852 ),
19853 }
19854 }
19855
19856 #[cfg(napi_runtime_os_threads)]
19857 #[test]
19858 fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
19859 use std::sync::{Barrier, mpsc};
19865 use std::time::Duration;
19866
19867 let (done_tx, done_rx) = mpsc::channel();
19868 let runner = std::thread::spawn(move || {
19869 let metrics = Arc::new(RuntimeMetrics::default());
19870 let options = RuntimeOptions {
19871 flavor: RuntimeFlavor::MultiThread,
19872 worker_threads: 2,
19873 max_blocking_tasks: 2,
19874 thread_name_prefix: "rd1-park".to_string(),
19875 park_deadline: None,
19876 drain_linger: DEFAULT_DRAIN_LINGER,
19877 };
19878 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19879
19880 let task_count = 2usize;
19881 let barrier = Arc::new(Barrier::new(task_count));
19882
19883 let mut senders = Vec::new();
19884 let mut tasks = Vec::new();
19885 for _ in 0..task_count {
19886 let (tx, rx) = oneshot::channel::<usize>();
19887 senders.push(tx);
19888 let exec = Arc::clone(&executor);
19889 let barrier = Arc::clone(&barrier);
19890 let body = async move {
19891 barrier.wait();
19893 let child_exec = Arc::clone(&exec);
19896 let (child_runnable, child_task) =
19897 async_task::spawn(async move { rx.await.unwrap() }, move |r| {
19898 child_exec.schedule(r)
19899 });
19900 exec.schedule(child_runnable);
19901 let mut inner = std::pin::pin!(async move {
19902 assert_eq!(child_task.await, 9usize);
19903 });
19904 exec.block_on(inner.as_mut());
19905 };
19906 let scheduler = Arc::clone(&executor);
19907 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19908 executor.schedule(runnable);
19909 tasks.push(task);
19910 }
19911
19912 std::thread::sleep(Duration::from_millis(200));
19914 for tx in senders {
19915 let _ = tx.send(9usize);
19916 }
19917
19918 for task in tasks {
19919 futures::executor::block_on(task);
19920 }
19921 let _ = done_tx.send(());
19922 });
19923
19924 match done_rx.recv_timeout(Duration::from_secs(10)) {
19925 Ok(()) => runner.join().unwrap(),
19926 Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
19927 }
19928 }
19929
19930 #[cfg(napi_runtime_os_threads)]
19936 fn wait_until(what: &str, condition: impl Fn() -> bool) {
19937 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
19938 while !condition() {
19939 assert!(
19940 std::time::Instant::now() < deadline,
19941 "timed out waiting for: {what}"
19942 );
19943 std::thread::yield_now();
19944 }
19945 }
19946
19947 #[cfg(napi_runtime_os_threads)]
19956 fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
19957 let deadline = std::time::Instant::now() + timeout;
19958 while !handle.is_finished() {
19959 assert!(
19960 std::time::Instant::now() < deadline,
19961 "thread wedged past its {timeout:?} bound while: {what}"
19962 );
19963 std::thread::sleep(Duration::from_millis(10));
19964 }
19965 handle.join().unwrap();
19966 }
19967
19968 #[cfg(napi_runtime_os_threads)]
19969 fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
19970 executor: &Arc<MultiThreadExecutor>,
19971 start_counter: &AtomicU64,
19972 claim: impl FnOnce() -> T + Send + 'static,
19973 ) -> T {
19974 use std::sync::mpsc;
19975
19976 let (claimed_tx, claimed_rx) = mpsc::channel();
19977 let (release_claim_tx, release_claim_rx) = mpsc::channel();
19978 let (started_tx, started_rx) = mpsc::channel();
19979 let (release_start_tx, release_start_rx) = mpsc::channel();
19980 let starts_before = start_counter.load(Ordering::SeqCst);
19981 let worker = std::thread::spawn(move || {
19982 MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
19983 *slot.borrow_mut() = Some(Box::new(move || {
19984 claimed_tx.send(()).unwrap();
19985 release_claim_rx.recv().unwrap();
19986 }));
19987 });
19988 MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
19989 *slot.borrow_mut() = Some(Box::new(move || {
19990 started_tx.send(()).unwrap();
19991 release_start_rx.recv().unwrap();
19992 }));
19993 });
19994 claim()
19995 });
19996 claimed_rx
19997 .recv_timeout(Duration::from_secs(2))
19998 .expect("the scheduler claim must pause after removing work");
19999
20000 let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20001 let verdict = executor.try_begin_deadlock_verdict();
20002 let claim_verdict_rejected = verdict.is_none();
20003 drop(verdict);
20004 release_claim_tx.send(()).unwrap();
20005
20006 started_rx
20007 .recv_timeout(Duration::from_secs(2))
20008 .expect("the scheduler claim must pause after publishing its start");
20009 let admission_active_after_start =
20010 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20011 let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
20012 let verdict = executor.try_begin_deadlock_verdict();
20013 let start_verdict_rejected = verdict.is_none();
20014 drop(verdict);
20015 release_start_tx.send(()).unwrap();
20016 let output = worker.join().unwrap();
20017
20018 assert!(
20019 admission_active,
20020 "removing scheduler work must retain a detector admission until start accounting"
20021 );
20022 assert!(
20023 claim_verdict_rejected,
20024 "a deadline verdict must not close while removed work has not published its start"
20025 );
20026 assert!(
20027 start_published,
20028 "work start accounting must publish before admission retires"
20029 );
20030 assert!(
20031 admission_active_after_start,
20032 "the detector admission must remain active until after start accounting"
20033 );
20034 assert!(
20035 start_verdict_rejected,
20036 "a deadline verdict must not close between start accounting and admission retirement"
20037 );
20038 output
20039 }
20040
20041 #[cfg(napi_runtime_os_threads)]
20042 #[test]
20043 fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
20044 let executor =
20045 multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
20046
20047 let fifo_ran = Arc::new(AtomicBool::new(false));
20048 let fifo_task_ran = Arc::clone(&fifo_ran);
20049 let (fifo, fifo_task) = async_task::spawn(
20050 async move {
20051 fifo_task_ran.store(true, Ordering::SeqCst);
20052 },
20053 |_| {},
20054 );
20055 executor.metrics.runnable_scheduled();
20056 executor.push_fifo_raw(fifo);
20057 let fifo_executor = Arc::clone(&executor);
20058 assert!(run_claim_with_deadlock_gate_assertion(
20059 &executor,
20060 &executor.metrics.runnable_polls,
20061 move || {
20062 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20063 fifo_executor.run_one_fifo_runnable()
20064 }
20065 ));
20066 futures::executor::block_on(fifo_task);
20067 assert!(fifo_ran.load(Ordering::SeqCst));
20068
20069 let fifo_order = Arc::new(Mutex::new(Vec::new()));
20070 let mut fifo_tasks = Vec::new();
20071 for name in ["first", "second"] {
20072 let task_order = Arc::clone(&fifo_order);
20073 let (runnable, task) = async_task::spawn(
20074 async move {
20075 task_order.lock().unwrap().push(name);
20076 },
20077 |_| {},
20078 );
20079 executor.metrics.runnable_scheduled();
20080 executor.push_fifo_raw(runnable);
20081 fifo_tasks.push(task);
20082 }
20083 for _ in 0..2 {
20084 let fifo_executor = Arc::clone(&executor);
20085 assert!(run_claim_with_deadlock_gate_assertion(
20086 &executor,
20087 &executor.metrics.runnable_polls,
20088 move || {
20089 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20090 fifo_executor.run_one_runnable()
20091 }
20092 ));
20093 }
20094 for task in fifo_tasks {
20095 futures::executor::block_on(task);
20096 }
20097 assert_eq!(
20098 *fifo_order.lock().unwrap(),
20099 ["first", "second"],
20100 "the detector-enabled normal fallback must preserve FIFO order"
20101 );
20102
20103 let lifo_ran = Arc::new(AtomicBool::new(false));
20104 let lifo_task_ran = Arc::clone(&lifo_ran);
20105 let (lifo, lifo_task) = async_task::spawn(
20106 async move {
20107 lifo_task_ran.store(true, Ordering::SeqCst);
20108 },
20109 |_| {},
20110 );
20111 executor.metrics.runnable_scheduled();
20112 let lifo_executor = Arc::clone(&executor);
20113 assert!(run_claim_with_deadlock_gate_assertion(
20114 &executor,
20115 &executor.metrics.runnable_polls,
20116 move || {
20117 let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
20118 LIFO_SLOT.with(|slot| {
20119 assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
20120 });
20121 lifo_executor.run_one_runnable()
20122 }
20123 ));
20124 futures::executor::block_on(lifo_task);
20125 assert!(lifo_ran.load(Ordering::SeqCst));
20126
20127 executor.begin_shutdown();
20128 executor.wait_until_scheduler_idle();
20129 }
20130
20131 #[cfg(napi_runtime_os_threads)]
20132 #[test]
20133 fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
20134 let executor =
20135 multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
20136
20137 let fifo_ran = Arc::new(AtomicBool::new(false));
20138 let fifo_job_ran = Arc::clone(&fifo_ran);
20139 executor.metrics.blocking_scheduled();
20140 executor
20141 .blocking_queue
20142 .lock()
20143 .unwrap()
20144 .push(QueuedBlocking {
20145 id: BlockingJobId(u64::MAX - 90),
20146 run: Box::new(move || {
20147 fifo_job_ran.store(true, Ordering::SeqCst);
20148 }),
20149 });
20150 let fifo_executor = Arc::clone(&executor);
20151 assert!(run_claim_with_deadlock_gate_assertion(
20152 &executor,
20153 &executor.metrics.blocking_tasks_started,
20154 move || {
20155 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20156 fifo_executor.run_one_blocking()
20157 }
20158 ));
20159 assert!(fifo_ran.load(Ordering::SeqCst));
20160
20161 let owner = executor.fresh_owner_token();
20162 let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20163 let exact_job = BlockingJobId(u64::MAX - 91);
20164 let exact_ran = Arc::new(AtomicBool::new(false));
20165 let exact_job_ran = Arc::clone(&exact_ran);
20166 executor.metrics.blocking_scheduled();
20167 executor
20168 .blocking_queue
20169 .lock()
20170 .unwrap()
20171 .push(QueuedBlocking {
20172 id: exact_job,
20173 run: Box::new(move || {
20174 exact_job_ran.store(true, Ordering::SeqCst);
20175 }),
20176 });
20177 let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
20178 dependency.set_owned(BlockingDependency {
20179 job: exact_job,
20180 owner: Some(owner),
20181 });
20182 let exact_executor = Arc::clone(&executor);
20183 assert!(run_claim_with_deadlock_gate_assertion(
20184 &executor,
20185 &executor.metrics.blocking_tasks_started,
20186 move || {
20187 let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
20188 let _owner = BlockingOwnerGuard::enter(Some(owner));
20189 exact_executor.try_owned_blocking_over_cap(&dependency)
20190 }
20191 ));
20192 assert!(exact_ran.load(Ordering::SeqCst));
20193
20194 drop(active_owner);
20195 executor.begin_shutdown();
20196 executor.wait_until_scheduler_idle();
20197 }
20198
20199 #[cfg(napi_runtime_os_threads)]
20200 #[test]
20201 fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
20202 use std::sync::mpsc;
20210 use std::time::Duration;
20211
20212 let registry = Arc::new(ParkedDrivers::default());
20213
20214 let guard = registry.parked.lock().unwrap();
20216 let (done_tx, done_rx) = mpsc::channel();
20217 let waker_thread = {
20218 let registry = Arc::clone(®istry);
20219 std::thread::spawn(move || {
20220 done_tx.send(registry.wake_one()).unwrap();
20221 })
20222 };
20223 let woke = done_rx
20224 .recv_timeout(Duration::from_secs(5))
20225 .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
20226 assert!(!woke, "an empty registry has nobody to wake");
20227 waker_thread.join().unwrap();
20228 drop(guard);
20229
20230 let parker = Arc::new(DriverParker::default());
20234 registry.register(&parker);
20235 let guard = registry.parked.lock().unwrap();
20236 let (done_tx, done_rx) = mpsc::channel();
20237 let waker_thread = {
20238 let registry = Arc::clone(®istry);
20239 std::thread::spawn(move || {
20240 done_tx.send(registry.wake_one()).unwrap();
20241 })
20242 };
20243 assert!(
20244 done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20245 "wake_one with a registered parker must take the slow path (block on the held lock)"
20246 );
20247 drop(guard);
20248 let woke = done_rx
20249 .recv_timeout(Duration::from_secs(5))
20250 .expect("slow-path wake_one must complete once the lock is released");
20251 assert!(
20252 woke,
20253 "the slow path must pop and wake the registered parker"
20254 );
20255 waker_thread.join().unwrap();
20256 parker.park();
20259 assert_eq!(
20260 registry.count.load(Ordering::SeqCst),
20261 0,
20262 "wake_one deregisters what it pops"
20263 );
20264 }
20265
20266 #[cfg(napi_runtime_os_threads)]
20267 #[test]
20268 fn driver_parker_permit_granted_while_running_is_not_lost() {
20269 use std::sync::mpsc;
20273 use std::time::Duration;
20274
20275 let parker = Arc::new(DriverParker::default());
20276 parker.unpark();
20278 parker.unpark();
20279
20280 let (tx, rx) = mpsc::channel();
20281 let driver = {
20282 let parker = Arc::clone(&parker);
20283 std::thread::spawn(move || {
20284 parker.park(); tx.send(1).unwrap();
20286 parker.park(); tx.send(2).unwrap();
20288 })
20289 };
20290
20291 assert_eq!(
20292 rx.recv_timeout(Duration::from_secs(5))
20293 .expect("a stored permit must not be lost"),
20294 1
20295 );
20296 assert!(
20297 rx.recv_timeout(Duration::from_millis(300)).is_err(),
20298 "two pre-park wakes must coalesce into one permit (second park must sleep)"
20299 );
20300 parker.unpark();
20301 assert_eq!(
20302 rx.recv_timeout(Duration::from_secs(5))
20303 .expect("unpark must wake a sleeping driver"),
20304 2
20305 );
20306 driver.join().unwrap();
20307 }
20308
20309 #[cfg(napi_runtime_os_threads)]
20310 #[test]
20311 fn driver_parker_unrepresentable_timeout_remains_wakeable() {
20312 use std::sync::mpsc;
20313
20314 let parker = Arc::new(DriverParker::default());
20315 assert!(
20316 Instant::now().checked_add(Duration::MAX).is_none(),
20317 "Duration::MAX must exercise the unrepresentable-deadline path"
20318 );
20319 let (done_tx, done_rx) = mpsc::channel();
20320 let driver = {
20321 let parker = Arc::clone(&parker);
20322 std::thread::spawn(move || {
20323 done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
20324 })
20325 };
20326 wait_until("the unbounded timeout driver to sleep", || {
20327 parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
20328 });
20329 assert!(
20330 done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
20331 "an unrepresentable timeout must not expire immediately"
20332 );
20333
20334 let unparker = {
20335 let parker = Arc::clone(&parker);
20336 std::thread::spawn(move || parker.unpark())
20337 };
20338 assert!(
20339 done_rx
20340 .recv_timeout(Duration::from_secs(1))
20341 .expect("an unrepresentable timeout must remain wakeable"),
20342 "a permit must be reported as a wake"
20343 );
20344 unparker.join().unwrap();
20345 driver.join().unwrap();
20346 }
20347
20348 #[cfg(napi_runtime_os_threads)]
20349 #[test]
20350 fn parked_drivers_wake_one_wakes_exactly_one() {
20351 use std::sync::mpsc;
20355 use std::time::Duration;
20356
20357 let registry = Arc::new(ParkedDrivers::default());
20358 let (woke_tx, woke_rx) = mpsc::channel();
20359 let mut drivers = Vec::new();
20360 for index in 0..2 {
20361 let registry = Arc::clone(®istry);
20362 let woke_tx = woke_tx.clone();
20363 drivers.push(std::thread::spawn(move || {
20364 let parker = Arc::new(DriverParker::default());
20365 registry.register(&parker);
20366 parker.park();
20367 registry.deregister(&parker);
20368 woke_tx.send(index).unwrap();
20369 }));
20370 }
20371 wait_until("both drivers registered as parked", || {
20372 registry.count.load(Ordering::SeqCst) == 2
20373 });
20374
20375 assert!(registry.wake_one(), "wake_one must wake a parked driver");
20376 woke_rx
20377 .recv_timeout(Duration::from_secs(5))
20378 .expect("exactly one driver must wake");
20379 assert!(
20380 woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20381 "the second driver must stay parked after a single wake_one"
20382 );
20383 assert_eq!(
20384 registry.count.load(Ordering::SeqCst),
20385 1,
20386 "one driver must remain registered"
20387 );
20388
20389 assert!(
20390 registry.wake_one(),
20391 "a second wake_one must wake the remaining driver"
20392 );
20393 woke_rx
20394 .recv_timeout(Duration::from_secs(5))
20395 .expect("the second driver must wake");
20396 for driver in drivers {
20397 driver.join().unwrap();
20398 }
20399 }
20400
20401 #[cfg(napi_runtime_os_threads)]
20402 #[test]
20403 fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
20404 use std::sync::mpsc;
20405
20406 struct ReenterRegistryOnDrop {
20407 registry: Arc<ParkedDrivers>,
20408 dropped: mpsc::Sender<()>,
20409 }
20410
20411 #[expect(
20412 clippy::manual_noop_waker,
20413 reason = "the test observes the waker payload's Drop"
20414 )]
20415 impl std::task::Wake for ReenterRegistryOnDrop {
20416 fn wake(self: Arc<Self>) {}
20417 }
20418
20419 impl Drop for ReenterRegistryOnDrop {
20420 fn drop(&mut self) {
20421 let parker = Arc::new(DriverParker::default());
20422 self.registry.register(&parker);
20423 self.registry.deregister(&parker);
20424 self.dropped.send(()).unwrap();
20425 }
20426 }
20427
20428 let registry = Arc::new(ParkedDrivers::default());
20429 let dependency = Arc::new(TaskDependency::default());
20430 let (dropped_tx, dropped_rx) = mpsc::channel();
20431 let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
20432 registry: Arc::clone(®istry),
20433 dropped: dropped_tx,
20434 }));
20435 dependency.register_waiter(&waiter);
20436 drop(waiter);
20437
20438 let parker = Arc::new(DriverParker::default());
20439 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20440 drop(dependency);
20441
20442 let retiring_registry = Arc::clone(®istry);
20443 let retiring_parker = Arc::clone(&parker);
20444 let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
20445 dropped_rx
20446 .recv_timeout(Duration::from_secs(1))
20447 .expect("retained dependency waiters must be destroyed after releasing the registry lock");
20448 retiring.join().unwrap();
20449 }
20450
20451 #[cfg(napi_runtime_os_threads)]
20452 #[test]
20453 fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
20454 use std::sync::mpsc;
20455
20456 let registry = Arc::new(ParkedDrivers::default());
20457 let owner = BlockingOwnerToken {
20458 executor_id: u64::MAX - 70,
20459 frame: u64::MAX - 71,
20460 };
20461 let dependency = Arc::new(TaskDependency::default());
20462 dependency.set_owned(BlockingDependency {
20463 job: BlockingJobId(u64::MAX - 72),
20464 owner: Some(owner),
20465 });
20466 let parker = Arc::new(DriverParker::default());
20467 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20468
20469 let (predicate_tx, predicate_rx) = mpsc::channel();
20470 let (release_tx, release_rx) = mpsc::channel();
20471 let selecting_registry = Arc::clone(®istry);
20472 let selector = std::thread::spawn(move || {
20473 DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
20474 *slot.borrow_mut() = Some(Box::new(move || {
20475 predicate_tx.send(()).unwrap();
20476 release_rx.recv().unwrap();
20477 }));
20478 });
20479 selecting_registry.wake_one_owner_dependency(owner)
20480 });
20481
20482 predicate_rx
20483 .recv_timeout(Duration::from_secs(2))
20484 .expect("owner selection must reach the dependency predicate");
20485 let registry_was_unlocked = registry.parked.try_lock().is_ok();
20486 release_tx.send(()).unwrap();
20487 assert!(
20488 selector.join().unwrap(),
20489 "the exact-owner parker must still be selected"
20490 );
20491
20492 assert!(
20493 registry_was_unlocked,
20494 "owner dependency predicates must not run while the parked-driver registry is locked"
20495 );
20496 assert!(parker.consume_permit());
20497 assert_eq!(parker.take_owner_handoff(), Some(owner));
20498 }
20499
20500 #[cfg(napi_runtime_os_threads)]
20501 #[test]
20502 fn owner_handoff_skips_newer_untagged_dependency() {
20503 let registry = ParkedDrivers::default();
20504 let owner = BlockingOwnerToken {
20505 executor_id: u64::MAX - 1,
20506 frame: u64::MAX - 2,
20507 };
20508 let owned_dependency = Arc::new(TaskDependency::default());
20509 owned_dependency.set_owned(BlockingDependency {
20510 job: BlockingJobId(u64::MAX - 3),
20511 owner: Some(owner),
20512 });
20513 let untagged_dependency = Arc::new(TaskDependency::default());
20514 untagged_dependency.set_owned(BlockingDependency {
20515 job: BlockingJobId(u64::MAX - 4),
20516 owner: None,
20517 });
20518 let owned = Arc::new(DriverParker::default());
20519 let untagged = Arc::new(DriverParker::default());
20520
20521 registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
20522 registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
20525
20526 assert!(registry.wake_one_owner_dependency(owner));
20527 assert!(
20528 owned.consume_permit(),
20529 "the exact owner dependency must receive the retry"
20530 );
20531 assert!(
20532 !untagged.consume_permit(),
20533 "an untagged dependency must not consume an exact owner handoff"
20534 );
20535 registry.deregister(&untagged);
20536 }
20537
20538 #[cfg(napi_runtime_os_threads)]
20539 #[test]
20540 fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
20541 let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
20542 let owner = executor.fresh_owner_token();
20543 let valid_dependency = Arc::new(TaskDependency::default());
20544 valid_dependency.set_owned(BlockingDependency {
20545 job: BlockingJobId(u64::MAX - 45),
20546 owner: Some(owner),
20547 });
20548 let withdrawn_dependency = Arc::new(TaskDependency::default());
20549 withdrawn_dependency.set_owned(BlockingDependency {
20550 job: BlockingJobId(u64::MAX - 46),
20551 owner: Some(owner),
20552 });
20553 let valid = Arc::new(DriverParker::default());
20554 let withdrawn = Arc::new(DriverParker::default());
20555
20556 executor
20557 .parked_drivers
20558 .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
20559 executor.parked_drivers.register_with_role(
20560 &withdrawn,
20561 true,
20562 Some(Arc::clone(&withdrawn_dependency)),
20563 );
20564 assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
20565 withdrawn_dependency.clear();
20566
20567 assert_eq!(
20568 executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
20569 Some(false),
20570 "a selected dependency withdrawn before retry must forward the owner handoff"
20571 );
20572 assert!(
20573 valid.consume_permit(),
20574 "the next live same-owner dependency must receive the forwarded wake"
20575 );
20576 assert_eq!(
20577 valid.take_owner_handoff(),
20578 Some(owner),
20579 "the forwarded wake must retain the exact owner identity"
20580 );
20581 }
20582
20583 #[cfg(napi_runtime_os_threads)]
20584 #[test]
20585 fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
20586 use std::sync::mpsc;
20587
20588 let executor = multi_thread_executor(2, None, "owner-handoff-publication");
20589 let owner = executor.fresh_owner_token();
20590 let fallback_dependency = Arc::new(TaskDependency::default());
20591 fallback_dependency.set_owned(BlockingDependency {
20592 job: BlockingJobId(u64::MAX - 47),
20593 owner: Some(owner),
20594 });
20595 let exiting_dependency = Arc::new(TaskDependency::default());
20596 exiting_dependency.set_owned(BlockingDependency {
20597 job: BlockingJobId(u64::MAX - 48),
20598 owner: Some(owner),
20599 });
20600 let fallback = Arc::new(DriverParker::default());
20601 let exiting = Arc::new(DriverParker::default());
20602
20603 executor.parked_drivers.register_with_role(
20604 &fallback,
20605 true,
20606 Some(Arc::clone(&fallback_dependency)),
20607 );
20608 executor.parked_drivers.register_with_role(
20609 &exiting,
20610 true,
20611 Some(Arc::clone(&exiting_dependency)),
20612 );
20613
20614 let (selected_tx, selected_rx) = mpsc::channel();
20615 let (release_selection_tx, release_selection_rx) = mpsc::channel();
20616 let selector_executor = Arc::clone(&executor);
20617 let selector = std::thread::spawn(move || {
20618 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
20619 *slot.borrow_mut() = Some(Box::new(move || {
20620 selected_tx.send(()).unwrap();
20621 release_selection_rx.recv().unwrap();
20622 }));
20623 });
20624 assert!(
20625 selector_executor
20626 .parked_drivers
20627 .wake_one_owner_dependency(owner)
20628 );
20629 });
20630 selected_rx
20631 .recv_timeout(Duration::from_secs(2))
20632 .expect("selection must pause after removing the exact-owner parker");
20633
20634 let (exited_tx, exited_rx) = mpsc::channel();
20635 let exiting_executor = Arc::clone(&executor);
20636 let exiting_driver = Arc::clone(&exiting);
20637 let driver = std::thread::spawn(move || {
20638 exiting_driver.park();
20639 exiting_executor.parked_drivers.deregister(&exiting_driver);
20640 exiting_executor.forward_owner_handoff(&exiting_driver);
20641 exited_tx.send(()).unwrap();
20642 });
20643 exiting.unpark();
20644 exited_rx
20645 .recv_timeout(Duration::from_secs(2))
20646 .expect("the direct future wake must let the selected driver exit");
20647
20648 release_selection_tx.send(()).unwrap();
20649 selector.join().unwrap();
20650 driver.join().unwrap();
20651
20652 assert!(
20653 fallback.consume_permit(),
20654 "an exiting selected driver must forward the owner handoff to the next dependency"
20655 );
20656 assert_eq!(
20657 fallback.take_owner_handoff(),
20658 Some(owner),
20659 "the forwarded handoff must retain the exact owner identity"
20660 );
20661 }
20662
20663 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20664 #[test]
20665 fn cooperative_panic_forwards_pending_owner_handoff() {
20666 use std::sync::mpsc;
20667
20668 struct PanicAfterWake {
20669 dependency: BlockingDependency,
20670 first_poll: Option<mpsc::Sender<()>>,
20671 }
20672
20673 impl Future for PanicAfterWake {
20674 type Output = ();
20675
20676 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20677 if let Some(first_poll) = self.first_poll.take() {
20678 let mut published = PublishedBlockingDependencies::default();
20681 record_blocking_dependency(&mut published, self.dependency);
20682 first_poll.send(()).unwrap();
20683 return Poll::Pending;
20684 }
20685 panic!("intentional cooperative driver panic after owner handoff");
20686 }
20687 }
20688
20689 let executor = multi_thread_executor(2, None, "panic-owner-handoff");
20690 let owner = executor.fresh_owner_token();
20691 let fallback_dependency = Arc::new(TaskDependency::default());
20692 fallback_dependency.set_owned(BlockingDependency {
20693 job: BlockingJobId(u64::MAX - 49),
20694 owner: Some(owner),
20695 });
20696 let fallback = Arc::new(DriverParker::default());
20697 executor.parked_drivers.register_with_role(
20698 &fallback,
20699 true,
20700 Some(Arc::clone(&fallback_dependency)),
20701 );
20702
20703 let (first_poll_tx, first_poll_rx) = mpsc::channel();
20704 let (panicked_tx, panicked_rx) = mpsc::channel();
20705 let driver_executor = Arc::clone(&executor);
20706 let driver = std::thread::spawn(move || {
20707 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20708 let mut future = std::pin::pin!(PanicAfterWake {
20709 dependency: BlockingDependency {
20710 job: BlockingJobId(u64::MAX - 50),
20711 owner: Some(owner)
20712 },
20713 first_poll: Some(first_poll_tx),
20714 });
20715 let panicked = catch_unwind(AssertUnwindSafe(|| {
20716 driver_executor.block_on(future.as_mut());
20717 }))
20718 .is_err();
20719 panicked_tx.send(panicked).unwrap();
20720 });
20721
20722 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20723 wait_until("the panicking cooperative driver to park", || {
20724 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20725 });
20726 assert!(
20727 executor.parked_drivers.wake_one_owner_dependency(owner),
20728 "the exact owner handoff must select the cooperative driver"
20729 );
20730 assert!(
20731 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20732 "the test future must unwind from its second poll"
20733 );
20734 driver.join().unwrap();
20735
20736 assert!(
20737 fallback.consume_permit(),
20738 "panic cleanup must forward the unconsumed owner handoff"
20739 );
20740 assert_eq!(
20741 fallback.take_owner_handoff(),
20742 Some(owner),
20743 "panic cleanup must preserve the exact owner identity"
20744 );
20745 }
20746
20747 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20748 #[test]
20749 fn cooperative_panic_flushes_lifo_slot() {
20750 struct ScheduleThenPanic {
20751 executor: Arc<MultiThreadExecutor>,
20752 ran: Arc<AtomicBool>,
20753 }
20754
20755 impl Future for ScheduleThenPanic {
20756 type Output = ();
20757
20758 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20759 let ran = Arc::clone(&self.ran);
20760 let scheduler = Arc::clone(&self.executor);
20761 let (runnable, task) = async_task::spawn(
20762 async move {
20763 ran.store(true, Ordering::SeqCst);
20764 },
20765 move |runnable| scheduler.schedule(runnable),
20766 );
20767 self.executor.schedule(runnable);
20768 task.detach();
20769 panic!("intentional cooperative driver panic with a populated LIFO slot");
20770 }
20771 }
20772
20773 let executor = multi_thread_executor(2, None, "panic-lifo-flush");
20774 executor
20775 .active_drainers
20776 .store(executor.max_drainers, Ordering::SeqCst);
20777 let ran = Arc::new(AtomicBool::new(false));
20778 let panicked = {
20779 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20780 let mut future = std::pin::pin!(ScheduleThenPanic {
20781 executor: Arc::clone(&executor),
20782 ran: Arc::clone(&ran),
20783 });
20784 catch_unwind(AssertUnwindSafe(|| {
20785 executor.block_on(future.as_mut());
20786 }))
20787 .is_err()
20788 };
20789
20790 assert!(panicked, "the test future must unwind from its first poll");
20791 assert!(
20792 executor.pop_lifo_slot().is_none(),
20793 "panic cleanup must empty the LIFO slot"
20794 );
20795 assert_eq!(
20796 executor.injector.len(),
20797 1,
20798 "panic cleanup must move the slot runnable to the shared FIFO"
20799 );
20800 assert!(
20801 executor.run_one(),
20802 "the flushed runnable must remain executable"
20803 );
20804 assert!(ran.load(Ordering::SeqCst));
20805 executor.active_drainers.store(0, Ordering::SeqCst);
20806 }
20807
20808 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20809 #[test]
20810 fn cooperative_panic_compensates_absorbed_queue_wake() {
20811 use std::sync::mpsc;
20812
20813 struct PanicAfterWake {
20814 first_poll: Option<mpsc::Sender<()>>,
20815 }
20816
20817 impl Future for PanicAfterWake {
20818 type Output = ();
20819
20820 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20821 if let Some(first_poll) = self.first_poll.take() {
20822 first_poll.send(()).unwrap();
20823 return Poll::Pending;
20824 }
20825 panic!("intentional cooperative driver panic after a queue wake");
20826 }
20827 }
20828
20829 let executor = multi_thread_executor(2, None, "panic-wake-compensation");
20830 executor
20831 .active_drainers
20832 .store(executor.max_drainers, Ordering::SeqCst);
20833 let fallback = Arc::new(DriverParker::default());
20834 executor.parked_drivers.register(&fallback);
20835 let (first_poll_tx, first_poll_rx) = mpsc::channel();
20836 let (panicked_tx, panicked_rx) = mpsc::channel();
20837 let driver_executor = Arc::clone(&executor);
20838 let driver = std::thread::spawn(move || {
20839 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20840 let mut future = std::pin::pin!(PanicAfterWake {
20841 first_poll: Some(first_poll_tx)
20842 });
20843 let panicked = catch_unwind(AssertUnwindSafe(|| {
20844 driver_executor.block_on(future.as_mut());
20845 }))
20846 .is_err();
20847 panicked_tx.send(panicked).unwrap();
20848 });
20849
20850 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20851 wait_until(
20852 "the panicking cooperative driver to park after the fallback",
20853 || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
20854 );
20855
20856 let ran = Arc::new(AtomicBool::new(false));
20857 let ran_task = Arc::clone(&ran);
20858 let scheduler = Arc::clone(&executor);
20859 let (runnable, task) = async_task::spawn(
20860 async move {
20861 ran_task.store(true, Ordering::SeqCst);
20862 },
20863 move |runnable| scheduler.schedule(runnable),
20864 );
20865 task.detach();
20866 executor.metrics.runnable_scheduled();
20867 executor.push_fifo_raw(runnable);
20868 assert!(
20869 executor.parked_drivers.wake_one(),
20870 "the queued runnable's wake must be absorbed by the cooperative driver"
20871 );
20872
20873 assert!(
20874 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20875 "the test future must unwind after absorbing the queue wake"
20876 );
20877 driver.join().unwrap();
20878 assert!(
20879 fallback.consume_permit(),
20880 "panic cleanup must compensate the absorbed queue wake to the fallback driver"
20881 );
20882 assert!(
20883 executor.run_one(),
20884 "the queued runnable must remain executable"
20885 );
20886 assert!(ran.load(Ordering::SeqCst));
20887 executor.active_drainers.store(0, Ordering::SeqCst);
20888 }
20889
20890 #[cfg(napi_runtime_os_threads)]
20891 #[test]
20892 fn multi_thread_future_wake_targets_its_own_parked_driver() {
20893 use std::sync::mpsc;
20901 use std::time::Duration;
20902
20903 let (done_tx, done_rx) = mpsc::channel();
20904 let runner = std::thread::spawn(move || {
20905 let metrics = Arc::new(RuntimeMetrics::default());
20906 let options = RuntimeOptions {
20907 flavor: RuntimeFlavor::MultiThread,
20908 worker_threads: 2,
20909 max_blocking_tasks: 2,
20910 thread_name_prefix: "wake-target".to_string(),
20911 park_deadline: None,
20912 drain_linger: DEFAULT_DRAIN_LINGER,
20913 };
20914 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20915
20916 let (tx1, rx1) = oneshot::channel::<usize>();
20917 let (tx2, rx2) = oneshot::channel::<usize>();
20918 let t1_done = Arc::new(AtomicBool::new(false));
20919 let t2_done = Arc::new(AtomicBool::new(false));
20920
20921 let t1_exec = Arc::clone(&executor);
20923 let t1_flag = Arc::clone(&t1_done);
20924 let t1_body = async move {
20925 let mut inner = std::pin::pin!(async move {
20926 assert_eq!(rx1.await.unwrap(), 7usize);
20927 });
20928 t1_exec.block_on(inner.as_mut());
20929 t1_flag.store(true, Ordering::SeqCst);
20930 };
20931 let sched1 = Arc::clone(&executor);
20932 let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
20933 executor.schedule(runnable1);
20934 wait_until("driver 1 parked", || {
20935 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20936 });
20937
20938 let t2_exec = Arc::clone(&executor);
20942 let t2_flag = Arc::clone(&t2_done);
20943 let t2_body = async move {
20944 let mut inner = std::pin::pin!(async move {
20945 assert_eq!(rx2.await.unwrap(), 9usize);
20946 });
20947 t2_exec.block_on(inner.as_mut());
20948 t2_flag.store(true, Ordering::SeqCst);
20949 };
20950 let sched2 = Arc::clone(&executor);
20951 let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
20952 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(runnable2);
20954 executor.ensure_drainer();
20955 wait_until("driver 2 parked", || {
20956 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20957 });
20958
20959 tx1.send(7usize).unwrap();
20962 wait_until("T1 completed via its own driver's wake", || {
20963 t1_done.load(Ordering::SeqCst)
20964 });
20965 futures::executor::block_on(task1);
20966 assert!(
20967 !t2_done.load(Ordering::SeqCst),
20968 "D2 must still be parked: its future was never completed"
20969 );
20970 wait_until(
20987 "the retiring drain frame registered its linger parker",
20988 || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
20989 );
20990 wait_until("the lingering drainer's idle budget expired", || {
20991 executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
20992 });
20993 assert_eq!(
20994 executor.parked_drivers.count.load(Ordering::SeqCst),
20995 1,
20996 "exactly the untargeted driver must remain parked after the linger expired"
20997 );
20998 assert!(
20999 !t2_done.load(Ordering::SeqCst),
21000 "D2 must still be parked after the lingering drainer expired"
21001 );
21002
21003 tx2.send(9usize).unwrap();
21005 futures::executor::block_on(task2);
21006 assert!(t2_done.load(Ordering::SeqCst));
21007 let _ = done_tx.send(());
21008 });
21009
21010 match done_rx.recv_timeout(Duration::from_secs(10)) {
21011 Ok(()) => runner.join().unwrap(),
21012 Err(error) => panic!(
21013 "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
21014 ),
21015 }
21016 }
21017
21018 #[cfg(napi_runtime_os_threads)]
21019 #[test]
21020 fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
21021 use std::sync::mpsc;
21029 use std::time::Duration;
21030
21031 let (done_tx, done_rx) = mpsc::channel();
21032 let runner = std::thread::spawn(move || {
21033 let metrics = Arc::new(RuntimeMetrics::default());
21034 let options = RuntimeOptions {
21035 flavor: RuntimeFlavor::MultiThread,
21036 worker_threads: 1,
21037 max_blocking_tasks: 1,
21038 thread_name_prefix: "wake-compensate".to_string(),
21039 park_deadline: None,
21040 drain_linger: DEFAULT_DRAIN_LINGER,
21041 };
21042 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21043
21044 let (gate_tx, gate_rx) = oneshot::channel::<()>();
21046 let td_exec = Arc::clone(&executor);
21047 let td_body = async move {
21048 let mut inner = std::pin::pin!(async move {
21049 gate_rx.await.unwrap();
21050 });
21051 td_exec.block_on(inner.as_mut());
21052 };
21053 let sched = Arc::clone(&executor);
21054 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
21055 executor.schedule(td_runnable);
21056 wait_until("the sole driver parked", || {
21057 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21058 });
21059
21060 let (ran_tx, ran_rx) = mpsc::channel::<()>();
21063 let sched_stranded = Arc::clone(&executor);
21064 let (stranded_runnable, stranded_task) = async_task::spawn(
21065 async move {
21066 ran_tx.send(()).unwrap();
21067 },
21068 move |r| sched_stranded.schedule(r),
21069 );
21070 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(stranded_runnable);
21072 stranded_task.detach();
21073
21074 {
21079 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21080 let mut ready = std::pin::pin!(async {});
21081 executor.block_on(ready.as_mut());
21082 }
21083
21084 ran_rx.recv_timeout(Duration::from_secs(5)).expect(
21085 "exit compensation must wake the parked driver to run the stranded queued runnable",
21086 );
21087
21088 gate_tx.send(()).unwrap();
21090 futures::executor::block_on(td_task);
21091 let _ = done_tx.send(());
21092 });
21093
21094 match done_rx.recv_timeout(Duration::from_secs(10)) {
21095 Ok(()) => runner.join().unwrap(),
21096 Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
21097 }
21098 }
21099
21100 #[cfg(napi_runtime_os_threads)]
21101 #[test]
21102 fn cooperative_exit_compensates_blocking_only_residue() {
21103 use std::sync::mpsc;
21104
21105 struct Controlled {
21106 ready: Arc<AtomicBool>,
21107 entered: Option<mpsc::Sender<()>>,
21108 }
21109
21110 impl Future for Controlled {
21111 type Output = ();
21112
21113 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21114 if let Some(entered) = self.entered.take() {
21115 entered.send(()).unwrap();
21116 }
21117 if self.ready.load(Ordering::SeqCst) {
21118 Poll::Ready(())
21119 } else {
21120 Poll::Pending
21121 }
21122 }
21123 }
21124
21125 let options = RuntimeOptions {
21126 flavor: RuntimeFlavor::MultiThread,
21127 worker_threads: 2,
21128 max_blocking_tasks: 1,
21129 thread_name_prefix: "blocking-exit-compensation".to_string(),
21130 park_deadline: None,
21131 drain_linger: DEFAULT_DRAIN_LINGER,
21132 };
21133 let executor =
21134 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21135 let ready = Arc::new(AtomicBool::new(false));
21136 let (entered_tx, entered_rx) = mpsc::channel();
21137 let (returned_tx, returned_rx) = mpsc::channel();
21138 let (release_tx, release_rx) = mpsc::channel();
21139 let task_exec = Arc::clone(&executor);
21140 let task_ready = Arc::clone(&ready);
21141 let scheduler = Arc::clone(&executor);
21142 let (runnable, task) = async_task::spawn(
21143 async move {
21144 let mut future = std::pin::pin!(Controlled {
21145 ready: task_ready,
21146 entered: Some(entered_tx)
21147 });
21148 task_exec.block_on(future.as_mut());
21149 returned_tx.send(()).unwrap();
21150 release_rx.recv().unwrap();
21151 },
21152 move |runnable| scheduler.schedule(runnable),
21153 );
21154 executor.schedule(runnable);
21155 entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
21156
21157 wait_until("the cooperative frame parked", || {
21167 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21168 });
21169
21170 let timer_exec = Arc::clone(&executor);
21171 let timer_scheduler = Arc::clone(&executor);
21172 let (timer_runnable, timer_task) = async_task::spawn(
21173 async move {
21174 heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
21175 },
21176 move |runnable| timer_scheduler.schedule(runnable),
21177 );
21178 executor.schedule(timer_runnable);
21179 wait_until("the dedicated timekeeper parked", || {
21180 executor
21181 .timers
21182 .inner
21183 .lock()
21184 .unwrap()
21185 .timekeeper_parker
21186 .is_some()
21187 });
21188 wait_until(
21193 "the cooperative frame re-parked in its timer-bounded park",
21194 || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
21195 );
21196
21197 ready.store(true, Ordering::SeqCst);
21198 let (blocking_tx, blocking_rx) = mpsc::channel();
21199 let blocking = executor.schedule_blocking(move || {
21200 blocking_tx.send(()).unwrap();
21201 });
21202 blocking_rx
21205 .recv_timeout(Duration::from_secs(10))
21206 .expect("blocking-only residue must be serviced by a Rayon worker");
21207 returned_rx
21208 .recv_timeout(Duration::from_secs(10))
21209 .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
21210 release_tx.send(()).unwrap();
21211 futures::executor::block_on(task);
21212 futures::executor::block_on(blocking).unwrap();
21213 executor.shutdown_timers();
21214 futures::executor::block_on(timer_task);
21215 }
21216
21217 #[cfg(napi_runtime_os_threads)]
21218 #[test]
21219 fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
21220 let options = RuntimeOptions {
21221 flavor: RuntimeFlavor::MultiThread,
21222 worker_threads: 2,
21223 max_blocking_tasks: 1,
21224 thread_name_prefix: "stopped-exit-compensation".to_string(),
21225 park_deadline: None,
21226 drain_linger: DEFAULT_DRAIN_LINGER,
21227 };
21228 let executor =
21229 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21230 let ran = Arc::new(AtomicBool::new(false));
21231 let blocking_ran = Arc::clone(&ran);
21232
21233 executor
21237 .active_drainers
21238 .store(executor.max_drainers, Ordering::SeqCst);
21239 let blocking = executor.schedule_blocking(move || {
21240 blocking_ran.store(true, Ordering::SeqCst);
21241 });
21242 assert!(!executor.blocking_queue.lock().unwrap().is_empty());
21243
21244 executor.stop.begin_shutdown();
21245 executor.compensate_queued_work_wake(true);
21246 let ran_after_stop = ran.load(Ordering::SeqCst);
21247
21248 executor.active_drainers.store(0, Ordering::SeqCst);
21249 executor.begin_shutdown();
21250 let blocking_result = futures::executor::block_on(blocking);
21251
21252 assert!(
21253 !ran_after_stop,
21254 "exit compensation started queued blocking work after stop"
21255 );
21256 assert!(
21257 blocking_result.is_err(),
21258 "shutdown must cancel the queued blocking job"
21259 );
21260 }
21261
21262 #[cfg(napi_runtime_os_threads)]
21263 #[test]
21264 fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
21265 use std::sync::mpsc;
21274 use std::time::Duration;
21275
21276 const ROUNDS: usize = 20_000;
21277
21278 let (done_tx, done_rx) = mpsc::channel();
21279 let registry = Arc::new(ParkedDrivers::default());
21280 let work = Arc::new(AtomicBool::new(false));
21281
21282 let consumer = {
21283 let registry = Arc::clone(®istry);
21284 let work = Arc::clone(&work);
21285 std::thread::spawn(move || {
21286 let parker = Arc::new(DriverParker::default());
21287 for _ in 0..ROUNDS {
21288 loop {
21289 if work.swap(false, Ordering::SeqCst) {
21290 break;
21291 }
21292 registry.register(&parker);
21295 if work.load(Ordering::SeqCst) {
21296 registry.deregister(&parker);
21297 continue;
21298 }
21299 parker.park();
21300 registry.deregister(&parker);
21301 }
21302 }
21303 done_tx.send(()).unwrap();
21304 })
21305 };
21306
21307 for _ in 0..ROUNDS {
21308 work.store(true, Ordering::SeqCst);
21309 let _ = registry.wake_one();
21313 while work.load(Ordering::SeqCst) {
21314 std::hint::spin_loop();
21315 }
21316 }
21317
21318 done_rx
21319 .recv_timeout(Duration::from_secs(30))
21320 .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
21321 consumer.join().unwrap();
21322 }
21323
21324 #[cfg(napi_runtime_os_threads)]
21325 #[test]
21326 fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
21327 let options = RuntimeOptions {
21331 flavor: RuntimeFlavor::MultiThread,
21332 worker_threads: 2,
21333 max_blocking_tasks: 2,
21334 thread_name_prefix: "lifo-order".to_string(),
21335 park_deadline: None,
21336 drain_linger: DEFAULT_DRAIN_LINGER,
21337 };
21338 let executor =
21339 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21340 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21341
21342 let old_order = Arc::clone(&order);
21345 let sched = Arc::clone(&executor);
21346 let (old_runnable, old_task) = async_task::spawn(
21347 async move {
21348 old_order.lock().unwrap().push("old");
21349 },
21350 move |r| sched.schedule(r),
21351 );
21352 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(old_runnable);
21354 old_task.detach();
21355
21356 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21359 let new_order = Arc::clone(&order);
21360 let sched = Arc::clone(&executor);
21361 let (new_runnable, new_task) = async_task::spawn(
21362 async move {
21363 new_order.lock().unwrap().push("new");
21364 },
21365 move |r| sched.schedule(r),
21366 );
21367 executor.schedule(new_runnable);
21368 new_task.detach();
21369 assert_eq!(
21370 executor.injector.len(),
21371 1,
21372 "the slot-path schedule must not push to the shared FIFO"
21373 );
21374
21375 assert!(executor.run_one(), "run_one must pop the slot first");
21376 assert!(executor.run_one(), "run_one must then pop the FIFO");
21377 assert!(!executor.run_one(), "no work must remain");
21378 assert_eq!(
21379 *order.lock().unwrap(),
21380 vec!["new", "old"],
21381 "the slot runnable (newest) must run before older FIFO work"
21382 );
21383 }
21384
21385 #[cfg(napi_runtime_os_threads)]
21386 #[test]
21387 fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
21388 let options = RuntimeOptions {
21389 flavor: RuntimeFlavor::MultiThread,
21390 worker_threads: 2,
21391 max_blocking_tasks: 1,
21392 thread_name_prefix: "fifo-owner".to_string(),
21393 park_deadline: None,
21394 drain_linger: DEFAULT_DRAIN_LINGER,
21395 };
21396 let executor =
21397 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21398 let observed_owner = Arc::new(Mutex::new(None));
21399 let observed = Arc::clone(&observed_owner);
21400 let (runnable, task) = async_task::spawn(
21401 async move {
21402 *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21403 },
21404 |_| {},
21405 );
21406 task.detach();
21407 executor.metrics.runnable_scheduled();
21408 executor.push_fifo_raw(runnable);
21409 executor.active_drainers.store(1, Ordering::Release);
21410
21411 let owner = executor.fresh_owner_token();
21412 let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
21413 Arc::clone(&executor).drain();
21414
21415 assert_eq!(
21416 *observed_owner.lock().unwrap(),
21417 Some(None),
21418 "FIFO runnables must not borrow a blocking closure's over-cap privilege"
21419 );
21420 assert_eq!(
21421 BLOCKING_OWNER.with(std::cell::Cell::get),
21422 Some(owner),
21423 "the ambient blocking owner must be restored after the runnable"
21424 );
21425 }
21426
21427 #[cfg(napi_runtime_os_threads)]
21428 #[test]
21429 fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
21430 use std::sync::mpsc;
21434 use std::time::Duration;
21435
21436 let (done_tx, done_rx) = mpsc::channel();
21437 let runner = std::thread::spawn(move || {
21438 let options = RuntimeOptions {
21439 flavor: RuntimeFlavor::MultiThread,
21440 worker_threads: 1,
21441 max_blocking_tasks: 1,
21442 thread_name_prefix: "lifo-drain".to_string(),
21443 park_deadline: None,
21444 drain_linger: DEFAULT_DRAIN_LINGER,
21445 };
21446 let executor =
21447 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21448 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21449
21450 let p_exec = Arc::clone(&executor);
21451 let p_order = Arc::clone(&order);
21452 let p_body = async move {
21453 p_order.lock().unwrap().push("P");
21454 let c_order = Arc::clone(&p_order);
21456 let sched = Arc::clone(&p_exec);
21457 let (c_runnable, c_task) = async_task::spawn(
21458 async move {
21459 c_order.lock().unwrap().push("C");
21460 },
21461 move |r| sched.schedule(r),
21462 );
21463 p_exec.schedule(c_runnable);
21464 c_task.detach();
21465 };
21466
21467 let mut tasks = Vec::new();
21471 let sched = Arc::clone(&executor);
21472 let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
21473 executor.metrics.runnable_scheduled();
21474 executor.push_fifo_raw(p_runnable);
21475 tasks.push(p_task);
21476 for name in ["T1", "T2"] {
21477 let t_order = Arc::clone(&order);
21478 let sched = Arc::clone(&executor);
21479 let (t_runnable, t_task) = async_task::spawn(
21480 async move {
21481 t_order.lock().unwrap().push(name);
21482 },
21483 move |r| sched.schedule(r),
21484 );
21485 executor.metrics.runnable_scheduled();
21486 executor.push_fifo_raw(t_runnable);
21487 tasks.push(t_task);
21488 }
21489 executor.ensure_drainer();
21490
21491 for task in tasks {
21492 futures::executor::block_on(task);
21493 }
21494 wait_until("the detached child C ran", || {
21495 order.lock().unwrap().len() == 4
21496 });
21497 assert_eq!(
21498 *order.lock().unwrap(),
21499 vec!["P", "C", "T1", "T2"],
21500 "the slot child C must run right after its scheduler P, ahead of older FIFO work"
21501 );
21502 let _ = done_tx.send(());
21503 });
21504
21505 match done_rx.recv_timeout(Duration::from_secs(10)) {
21506 Ok(()) => runner.join().unwrap(),
21507 Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
21508 }
21509 }
21510
21511 #[cfg(napi_runtime_os_threads)]
21512 #[test]
21513 fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
21514 let options = RuntimeOptions {
21520 flavor: RuntimeFlavor::MultiThread,
21521 worker_threads: 2,
21522 max_blocking_tasks: 2,
21523 thread_name_prefix: "lifo-displace".to_string(),
21524 park_deadline: None,
21525 drain_linger: DEFAULT_DRAIN_LINGER,
21526 };
21527 let executor =
21528 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21529 executor
21530 .active_drainers
21531 .store(executor.max_drainers, Ordering::SeqCst);
21532 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21533
21534 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21535 for name in ["a", "b"] {
21536 let n_order = Arc::clone(&order);
21537 let sched = Arc::clone(&executor);
21538 let (runnable, task) = async_task::spawn(
21539 async move {
21540 n_order.lock().unwrap().push(name);
21541 },
21542 move |r| sched.schedule(r),
21543 );
21544 executor.schedule(runnable);
21545 task.detach();
21546 }
21547 assert_eq!(
21548 executor.injector.len(),
21549 1,
21550 "the displaced occupant (a) must have been pushed to the shared FIFO"
21551 );
21552
21553 assert!(executor.run_one(), "run_one must pop the slot (b) first");
21554 assert!(
21555 executor.run_one(),
21556 "run_one must then pop the displaced (a) from the FIFO"
21557 );
21558 assert!(!executor.run_one(), "no work must remain");
21559 assert_eq!(
21560 *order.lock().unwrap(),
21561 vec!["b", "a"],
21562 "the newest schedule must win the slot; the displaced one must survive via the FIFO"
21563 );
21564 executor.active_drainers.store(0, Ordering::SeqCst);
21565 }
21566
21567 #[cfg(napi_runtime_os_threads)]
21568 #[test]
21569 fn lifo_slot_is_executor_scoped() {
21570 let options = RuntimeOptions {
21575 flavor: RuntimeFlavor::MultiThread,
21576 worker_threads: 2,
21577 max_blocking_tasks: 2,
21578 thread_name_prefix: "lifo-scope".to_string(),
21579 park_deadline: None,
21580 drain_linger: DEFAULT_DRAIN_LINGER,
21581 };
21582 let exec1 =
21583 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21584 let exec2 =
21585 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21586 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21587 exec1
21588 .active_drainers
21589 .store(exec1.max_drainers, Ordering::SeqCst);
21590 exec2
21591 .active_drainers
21592 .store(exec2.max_drainers, Ordering::SeqCst);
21593 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21594
21595 let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
21597 let r1_order = Arc::clone(&order);
21598 let sched = Arc::clone(&exec1);
21599 let (r1, t1) = async_task::spawn(
21600 async move {
21601 r1_order.lock().unwrap().push("one");
21602 },
21603 move |r| sched.schedule(r),
21604 );
21605 exec1.schedule(r1);
21606 t1.detach();
21607 assert!(
21608 exec1.injector.is_empty(),
21609 "r1 must sit in the slot, not exec1's FIFO"
21610 );
21611
21612 {
21613 let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
21616 let r2_order = Arc::clone(&order);
21617 let sched = Arc::clone(&exec2);
21618 let (r2, t2) = async_task::spawn(
21619 async move {
21620 r2_order.lock().unwrap().push("two");
21621 },
21622 move |r| sched.schedule(r),
21623 );
21624 exec2.schedule(r2);
21625 t2.detach();
21626 assert_eq!(
21627 exec2.injector.len(),
21628 1,
21629 "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
21630 );
21631 assert!(
21632 exec2.pop_lifo_slot().is_none(),
21633 "exec2 must not pop exec1's slot entry (executor-scoped pop)"
21634 );
21635 assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
21636 }
21637
21638 assert!(
21640 exec1.run_one(),
21641 "exec1's slot entry must have survived the foreign activity"
21642 );
21643 assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
21644 exec1.active_drainers.store(0, Ordering::SeqCst);
21645 exec2.active_drainers.store(0, Ordering::SeqCst);
21646 }
21647
21648 #[cfg(napi_runtime_os_threads)]
21649 #[test]
21650 fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
21651 use std::sync::mpsc;
21658 use std::time::Duration;
21659
21660 const CHAIN: u64 = 200; fn spawn_link(
21663 executor: &Arc<MultiThreadExecutor>,
21664 remaining: u64,
21665 counter: Arc<AtomicU64>,
21666 done: mpsc::Sender<()>,
21667 ) {
21668 let body_exec = Arc::clone(executor);
21669 let body = async move {
21670 counter.fetch_add(1, Ordering::SeqCst);
21671 if remaining > 0 {
21672 spawn_link(&body_exec, remaining - 1, counter, done);
21673 } else {
21674 done.send(()).unwrap();
21675 }
21676 };
21677 let sched = Arc::clone(executor);
21678 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21679 executor.schedule(runnable);
21680 task.detach();
21681 }
21682
21683 let (done_tx, done_rx) = mpsc::channel();
21684 let runner = std::thread::spawn(move || {
21685 let metrics = Arc::new(RuntimeMetrics::default());
21686 let options = RuntimeOptions {
21687 flavor: RuntimeFlavor::MultiThread,
21688 worker_threads: 1,
21689 max_blocking_tasks: 1,
21690 thread_name_prefix: "lifo-budget".to_string(),
21691 park_deadline: None,
21692 drain_linger: DEFAULT_DRAIN_LINGER,
21693 };
21694 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21695
21696 let counter = Arc::new(AtomicU64::new(0));
21697 let (chain_tx, chain_rx) = mpsc::channel();
21698 spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
21699
21700 chain_rx
21701 .recv_timeout(Duration::from_secs(10))
21702 .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
21703 assert_eq!(
21704 counter.load(Ordering::SeqCst),
21705 CHAIN,
21706 "every chain link must have run"
21707 );
21708 wait_until("runnable counters settle to zero (no slot leak)", || {
21709 metrics.queued_runnables.load(Ordering::Relaxed) == 0
21710 && metrics.active_runnables.load(Ordering::Relaxed) == 0
21711 });
21712 let _ = done_tx.send(());
21713 });
21714
21715 match done_rx.recv_timeout(Duration::from_secs(15)) {
21716 Ok(()) => runner.join().unwrap(),
21717 Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
21718 }
21719 }
21720
21721 #[cfg(napi_runtime_os_threads)]
21722 #[test]
21723 fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
21724 let options = RuntimeOptions {
21731 flavor: RuntimeFlavor::MultiThread,
21732 worker_threads: 2,
21733 max_blocking_tasks: 2,
21734 thread_name_prefix: "lifo-flush".to_string(),
21735 park_deadline: None,
21736 drain_linger: DEFAULT_DRAIN_LINGER,
21737 };
21738 let executor =
21739 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21740 executor
21741 .active_drainers
21742 .store(executor.max_drainers, Ordering::SeqCst);
21743 let ran = Arc::new(AtomicBool::new(false));
21744
21745 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21746 let ran_flag = Arc::clone(&ran);
21747 let sched = Arc::clone(&executor);
21748 let (runnable, task) = async_task::spawn(
21749 async move {
21750 ran_flag.store(true, Ordering::SeqCst);
21751 },
21752 move |r| sched.schedule(r),
21753 );
21754 executor.schedule(runnable);
21755 task.detach();
21756 assert!(
21757 executor.injector.is_empty(),
21758 "the runnable must start in the slot"
21759 );
21760
21761 executor.flush_lifo_slot();
21762 assert!(
21763 executor.pop_lifo_slot().is_none(),
21764 "the slot must be empty after a flush"
21765 );
21766 assert_eq!(
21767 executor.injector.len(),
21768 1,
21769 "the flushed runnable must be on the shared FIFO"
21770 );
21771 assert!(executor.run_one(), "the flushed runnable must still run");
21772 assert!(
21773 ran.load(Ordering::SeqCst),
21774 "the flushed task must complete, not be dropped"
21775 );
21776 executor.active_drainers.store(0, Ordering::SeqCst);
21777 }
21778
21779 #[cfg(napi_runtime_os_threads)]
21780 #[test]
21781 fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
21782 let options = RuntimeOptions {
21787 flavor: RuntimeFlavor::MultiThread,
21788 worker_threads: 2,
21789 max_blocking_tasks: 2,
21790 thread_name_prefix: "lifo-owner".to_string(),
21791 park_deadline: None,
21792 drain_linger: DEFAULT_DRAIN_LINGER,
21793 };
21794 let executor =
21795 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21796 let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
21797
21798 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21799
21800 let observed_slot = Arc::clone(&observed);
21804 let sched = Arc::clone(&executor);
21805 let (runnable, task) = async_task::spawn(
21806 async move {
21807 *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21808 },
21809 move |r| sched.schedule(r),
21810 );
21811 executor.schedule(runnable);
21812 task.detach();
21813 assert!(
21814 executor.injector.is_empty(),
21815 "the runnable must sit in the slot"
21816 );
21817
21818 let token = executor.fresh_owner_token();
21822 let _owner = BlockingOwnerGuard::enter(Some(token));
21823 assert!(executor.run_one(), "the slot runnable must run");
21824 assert_eq!(
21825 *observed.lock().unwrap(),
21826 Some(None),
21827 "a slot runnable must observe a CLEARED blocking owner"
21828 );
21829 assert_eq!(
21830 BLOCKING_OWNER.with(std::cell::Cell::get),
21831 Some(token),
21832 "the ambient owner frame must be restored after the slot runnable"
21833 );
21834 }
21835
21836 #[cfg(napi_runtime_os_threads)]
21842 struct PingPongShared {
21843 count: AtomicU64,
21844 stop: AtomicBool,
21845 stop_after: u64,
21846 wakers: Mutex<[Option<std::task::Waker>; 2]>,
21847 }
21848
21849 #[cfg(napi_runtime_os_threads)]
21850 struct PingPong {
21851 shared: Arc<PingPongShared>,
21852 me: usize,
21853 }
21854
21855 #[cfg(napi_runtime_os_threads)]
21856 impl Future for PingPong {
21857 type Output = ();
21858 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21859 let shared = &self.shared;
21860 let polls = shared.count.fetch_add(1, Ordering::SeqCst);
21861 if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
21862 let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
21863 if let Some(partner) = partner {
21864 partner.wake();
21865 }
21866 return Poll::Ready(());
21867 }
21868 let mut wakers = shared.wakers.lock().unwrap();
21869 wakers[self.me] = Some(cx.waker().clone());
21870 if let Some(partner) = wakers[1 - self.me].take() {
21871 drop(wakers);
21872 partner.wake();
21873 }
21874 Poll::Pending
21875 }
21876 }
21877
21878 #[cfg(napi_runtime_os_threads)]
21879 fn spawn_ping_pong_pair(
21880 executor: &Arc<MultiThreadExecutor>,
21881 shared: &Arc<PingPongShared>,
21882 ) -> Vec<Task<()>> {
21883 let mut tasks = Vec::new();
21884 for me in 0..2 {
21885 let sched = Arc::clone(executor);
21886 let (runnable, task) = async_task::spawn(
21887 PingPong {
21888 shared: Arc::clone(shared),
21889 me,
21890 },
21891 move |r| sched.schedule(r),
21892 );
21893 executor.schedule(runnable);
21894 tasks.push(task);
21895 }
21896 tasks
21897 }
21898
21899 #[cfg(napi_runtime_os_threads)]
21900 #[test]
21901 fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
21902 use std::sync::mpsc;
21909 use std::time::Duration;
21910
21911 let (done_tx, done_rx) = mpsc::channel();
21912 let runner = std::thread::spawn(move || {
21913 let options = RuntimeOptions {
21914 flavor: RuntimeFlavor::MultiThread,
21915 worker_threads: 1,
21916 max_blocking_tasks: 0,
21917 thread_name_prefix: "lifo-streak".to_string(),
21918 park_deadline: None,
21919 drain_linger: DEFAULT_DRAIN_LINGER,
21920 };
21921 let executor =
21922 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21923
21924 let shared = Arc::new(PingPongShared {
21925 count: AtomicU64::new(0),
21926 stop: AtomicBool::new(false),
21927 stop_after: u64::MAX,
21928 wakers: Mutex::new([None, None]),
21929 });
21930 let pair = spawn_ping_pong_pair(&executor, &shared);
21931
21932 let (t3_tx, t3_rx) = mpsc::channel::<()>();
21934 let sched = Arc::clone(&executor);
21935 let (t3_runnable, t3_task) = async_task::spawn(
21936 async move {
21937 t3_tx.send(()).unwrap();
21938 },
21939 move |r| sched.schedule(r),
21940 );
21941 executor.schedule(t3_runnable);
21942
21943 t3_rx
21944 .recv_timeout(Duration::from_secs(10))
21945 .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
21946 assert!(
21947 shared.count.load(Ordering::SeqCst) >= 32,
21948 "the pair must have been bouncing through the slot before T3 got its turn"
21949 );
21950
21951 shared.stop.store(true, Ordering::SeqCst);
21953 let stored: Vec<_> = shared
21954 .wakers
21955 .lock()
21956 .unwrap()
21957 .iter_mut()
21958 .filter_map(Option::take)
21959 .collect();
21960 for waker in stored {
21961 waker.wake();
21962 }
21963 for task in pair {
21964 futures::executor::block_on(task);
21965 }
21966 futures::executor::block_on(t3_task);
21967 let _ = done_tx.send(());
21968 });
21969
21970 match done_rx.recv_timeout(Duration::from_secs(15)) {
21971 Ok(()) => runner.join().unwrap(),
21972 Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
21973 }
21974 }
21975
21976 #[cfg(napi_runtime_os_threads)]
21977 #[test]
21978 fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
21979 use std::sync::mpsc;
21986 use std::time::Duration;
21987
21988 const BOUNCES: u64 = 2_000; let (done_tx, done_rx) = mpsc::channel();
21991 let runner = std::thread::spawn(move || {
21992 let options = RuntimeOptions {
21993 flavor: RuntimeFlavor::MultiThread,
21994 worker_threads: 1,
21995 max_blocking_tasks: 1,
21996 thread_name_prefix: "lifo-blocking".to_string(),
21997 park_deadline: None,
21998 drain_linger: DEFAULT_DRAIN_LINGER,
21999 };
22000 let executor =
22001 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22002
22003 let shared = Arc::new(PingPongShared {
22004 count: AtomicU64::new(0),
22005 stop: AtomicBool::new(false),
22006 stop_after: BOUNCES,
22007 wakers: Mutex::new([None, None]),
22008 });
22009 let pair = spawn_ping_pong_pair(&executor, &shared);
22010
22011 let blocking = executor.schedule_blocking(|| 11usize);
22013
22014 assert_eq!(
22015 futures::executor::block_on(blocking).unwrap(),
22016 11,
22017 "the queued blocking job must complete despite the hot LIFO pair"
22018 );
22019 for task in pair {
22020 futures::executor::block_on(task);
22021 }
22022 assert!(
22023 shared.count.load(Ordering::SeqCst) >= BOUNCES,
22024 "the pair must have actually run its full bounce quota"
22025 );
22026 let _ = done_tx.send(());
22027 });
22028
22029 match done_rx.recv_timeout(Duration::from_secs(15)) {
22030 Ok(()) => runner.join().unwrap(),
22031 Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
22032 }
22033 }
22034
22035 #[cfg(napi_runtime_os_threads)]
22036 #[test]
22037 fn infinite_hot_runnable_yields_to_blocking_fifo() {
22038 use std::sync::mpsc;
22039
22040 struct HotFuture {
22041 stop: Arc<AtomicBool>,
22042 }
22043
22044 impl Future for HotFuture {
22045 type Output = ();
22046
22047 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22048 if self.stop.load(Ordering::SeqCst) {
22049 Poll::Ready(())
22050 } else {
22051 cx.waker().wake_by_ref();
22052 Poll::Pending
22053 }
22054 }
22055 }
22056
22057 let options = RuntimeOptions {
22058 flavor: RuntimeFlavor::MultiThread,
22059 worker_threads: 2,
22060 max_blocking_tasks: 1,
22061 thread_name_prefix: "blocking-fairness".to_string(),
22062 park_deadline: None,
22063 drain_linger: DEFAULT_DRAIN_LINGER,
22064 };
22065 let executor =
22066 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22067 let stop = Arc::new(AtomicBool::new(false));
22068 let scheduler = Arc::clone(&executor);
22069 let (runnable, task) = async_task::spawn(
22070 HotFuture {
22071 stop: Arc::clone(&stop),
22072 },
22073 move |r| scheduler.schedule(r),
22074 );
22075 task.detach();
22076 let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
22077 executor
22078 .blocking_queue
22079 .lock()
22080 .unwrap()
22081 .push(QueuedBlocking {
22082 id: BlockingJobId(u64::MAX),
22083 run: Box::new(move || blocking_tx.send(()).unwrap()),
22084 });
22085
22086 let _driver = OnPoolWorkerGuard::enter(executor.id);
22087 executor.schedule(runnable);
22088 let mut runnable_streak = 0;
22089 for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
22090 assert!(executor.run_one_fair(&mut runnable_streak));
22091 }
22092 blocking_rx
22093 .try_recv()
22094 .expect("an infinite self-waker must yield a bounded quantum to blocking work");
22095
22096 stop.store(true, Ordering::SeqCst);
22097 assert!(executor.run_one_fair(&mut runnable_streak));
22098 }
22099
22100 #[cfg(napi_runtime_os_threads)]
22101 #[test]
22102 fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22103 use std::sync::mpsc;
22104
22105 struct HotFuture {
22106 stop: Arc<AtomicBool>,
22107 }
22108
22109 impl Future for HotFuture {
22110 type Output = ();
22111
22112 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22113 if self.stop.load(Ordering::SeqCst) {
22114 Poll::Ready(())
22115 } else {
22116 cx.waker().wake_by_ref();
22117 Poll::Pending
22118 }
22119 }
22120 }
22121
22122 let options = RuntimeOptions {
22123 flavor: RuntimeFlavor::MultiThread,
22124 worker_threads: 1,
22125 max_blocking_tasks: 1,
22126 thread_name_prefix: "exact-dependency-fairness".to_string(),
22127 park_deadline: None,
22128 drain_linger: DEFAULT_DRAIN_LINGER,
22129 };
22130 let executor =
22131 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22132 let stop = Arc::new(AtomicBool::new(false));
22133 let (queued_tx, queued_rx) = mpsc::channel();
22134 let (exact_tx, exact_rx) = mpsc::channel();
22135 let owner_executor = Arc::clone(&executor);
22136 let owner_stop = Arc::clone(&stop);
22137 let owner = executor.schedule_blocking(move || {
22138 let scheduler = Arc::clone(&owner_executor);
22139 let hot_stop = Arc::clone(&owner_stop);
22140 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22141 scheduler.schedule(runnable);
22142 });
22143 owner_executor.schedule(runnable);
22144
22145 let exact_stop = Arc::clone(&owner_stop);
22146 let exact = owner_executor.schedule_blocking(move || {
22147 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22148 41usize
22149 });
22150 queued_tx.send(()).unwrap();
22151
22152 let mut value = None;
22153 {
22154 let mut future = std::pin::pin!(async {
22155 value = Some(exact.await.unwrap());
22156 hot_task.await;
22157 });
22158 assert_eq!(
22159 owner_executor.block_on(future.as_mut()),
22160 BlockOnOutcome::Completed
22161 );
22162 }
22163 value.unwrap()
22164 });
22165
22166 queued_rx
22167 .recv_timeout(Duration::from_secs(2))
22168 .expect("the exact dependency must be queued behind the saturated owner");
22169 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22170 stop.store(true, Ordering::SeqCst);
22171 assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
22172 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22173 assert_eq!(
22174 exact_before_stop,
22175 Some(true),
22176 "a hot local runnable must yield to the exact dependency before it is stopped"
22177 );
22178 }
22179
22180 #[cfg(napi_runtime_os_threads)]
22181 #[test]
22182 fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
22183 use std::sync::mpsc;
22184
22185 struct BlockingPoll {
22186 started: Option<mpsc::Sender<()>>,
22187 released: Arc<(Mutex<bool>, Condvar)>,
22188 }
22189
22190 impl Future for BlockingPoll {
22191 type Output = ();
22192
22193 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22194 if let Some(started) = self.started.take() {
22195 started.send(()).unwrap();
22196 }
22197 let (released, changed) = &*self.released;
22198 let mut released = released
22199 .lock()
22200 .unwrap_or_else(std::sync::PoisonError::into_inner);
22201 while !*released {
22202 released = changed
22203 .wait(released)
22204 .unwrap_or_else(std::sync::PoisonError::into_inner);
22205 }
22206 Poll::Ready(())
22207 }
22208 }
22209
22210 let options = RuntimeOptions {
22211 flavor: RuntimeFlavor::MultiThread,
22212 worker_threads: 3,
22213 max_blocking_tasks: 2,
22214 thread_name_prefix: "exact-before-last-slot".to_string(),
22215 park_deadline: None,
22216 drain_linger: DEFAULT_DRAIN_LINGER,
22217 };
22218 let executor =
22219 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22220
22221 let released = Arc::new((Mutex::new(false), Condvar::new()));
22222 let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
22223 let mut blockers = Vec::new();
22224 for _ in 0..2 {
22225 let scheduler = Arc::clone(&executor);
22226 let blocker_released = Arc::clone(&released);
22227 let started = blocker_started_tx.clone();
22228 let (runnable, task) = async_task::spawn(
22229 BlockingPoll {
22230 started: Some(started),
22231 released: blocker_released,
22232 },
22233 move |runnable| scheduler.schedule(runnable),
22234 );
22235 executor.schedule(runnable);
22236 blockers.push(task);
22237 }
22238 drop(blocker_started_tx);
22239 for _ in 0..2 {
22240 blocker_started_rx
22241 .recv_timeout(Duration::from_secs(2))
22242 .expect("two workers must be gated before the owner starts");
22243 }
22244
22245 let dependency_started = Arc::new(AtomicBool::new(false));
22246 let owner_executor = Arc::clone(&executor);
22247 let owner_dependency_started = Arc::clone(&dependency_started);
22248 let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
22249 let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
22250 let owner_signal = Arc::clone(&dependency_signal);
22251 let owner = executor.schedule_blocking(move || {
22252 let unrelated_signal = Arc::clone(&owner_signal);
22253 let unrelated_started = Arc::clone(&owner_dependency_started);
22254 let unrelated = owner_executor.schedule_blocking(move || {
22255 let observed = unrelated_started.load(Ordering::SeqCst);
22256 unrelated_observed_tx.send(observed).unwrap();
22257 let (ready, changed) = &*unrelated_signal;
22258 let mut ready = ready
22259 .lock()
22260 .unwrap_or_else(std::sync::PoisonError::into_inner);
22261 while !*ready {
22262 ready = changed
22263 .wait(ready)
22264 .unwrap_or_else(std::sync::PoisonError::into_inner);
22265 }
22266 });
22267
22268 let exact_signal = Arc::clone(&owner_signal);
22269 let exact_started = Arc::clone(&owner_dependency_started);
22270 let exact = owner_executor.schedule_blocking(move || {
22271 exact_started.store(true, Ordering::SeqCst);
22272 let (ready, changed) = &*exact_signal;
22273 *ready
22274 .lock()
22275 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22276 changed.notify_all();
22277 41usize
22278 });
22279
22280 let mut output = None;
22281 {
22282 let mut future = std::pin::pin!(async {
22283 output = Some(exact.await.unwrap());
22284 unrelated.await.unwrap();
22285 });
22286 assert_eq!(
22287 owner_executor.block_on(future.as_mut()),
22288 BlockOnOutcome::Completed
22289 );
22290 }
22291 output.unwrap()
22292 });
22293
22294 let (owner_done_tx, owner_done_rx) = mpsc::channel();
22295 let owner_waiter = std::thread::spawn(move || {
22296 owner_done_tx
22297 .send(futures::executor::block_on(owner))
22298 .unwrap();
22299 });
22300 let (completed_while_gated, owner_result) =
22301 match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
22302 Ok(result) => (true, result),
22303 Err(_) => {
22304 let (released, changed) = &*released;
22307 *released
22308 .lock()
22309 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22310 changed.notify_all();
22311 (
22312 false,
22313 owner_done_rx
22314 .recv_timeout(Duration::from_secs(2))
22315 .expect("releasing the other workers must let the broken ordering recover"),
22316 )
22317 }
22318 };
22319 let unrelated_observed = unrelated_observed_rx
22320 .recv_timeout(Duration::from_secs(2))
22321 .expect("the unrelated FIFO job must eventually run");
22322
22323 {
22324 let (released, changed) = &*released;
22325 *released
22326 .lock()
22327 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22328 changed.notify_all();
22329 }
22330 owner_waiter.join().unwrap();
22331 for blocker in blockers {
22332 futures::executor::block_on(blocker);
22333 }
22334 executor.begin_shutdown();
22335 executor.wait_until_scheduler_idle();
22336
22337 assert!(
22338 completed_while_gated,
22339 "the exact dependency must complete while unrelated workers remain gated"
22340 );
22341 assert_eq!(owner_result.unwrap(), 41);
22342 assert!(
22343 unrelated_observed,
22344 "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
22345 );
22346 }
22347
22348 #[cfg(napi_runtime_os_threads)]
22349 #[test]
22350 fn blocking_saturation_preserves_a_runnable_execution_lane() {
22351 use std::{sync::mpsc, time::Duration};
22352
22353 let options = RuntimeOptions {
22354 flavor: RuntimeFlavor::MultiThread,
22355 worker_threads: 2,
22356 max_blocking_tasks: 2,
22357 thread_name_prefix: "blocking-reserve".to_string(),
22358 park_deadline: None,
22359 drain_linger: DEFAULT_DRAIN_LINGER,
22360 }
22361 .validate()
22362 .expect("production options must reserve a runnable lane");
22363 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
22364 let executor =
22365 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22366 let (release_tx, release_rx) = mpsc::channel();
22367 let (started_tx, started_rx) = mpsc::channel();
22368 let first = executor.schedule_blocking(move || {
22369 started_tx.send(()).unwrap();
22370 release_rx.recv().unwrap();
22371 });
22372 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
22373
22374 let (queued_tx, queued_rx) = mpsc::channel();
22375 let second = executor.schedule_blocking(move || {
22376 queued_tx.send(()).unwrap();
22377 });
22378 assert!(
22379 queued_rx.try_recv().is_err(),
22380 "the second blocking job must remain queued while the sole blocking slot is occupied"
22381 );
22382
22383 let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
22384 let scheduler = Arc::clone(&executor);
22385 let (runnable, task) = async_task::spawn(
22386 async move {
22387 runnable_tx.send(()).unwrap();
22388 },
22389 move |r| scheduler.schedule(r),
22390 );
22391 task.detach();
22392 executor.schedule(runnable);
22393 runnable_rx
22394 .recv_timeout(Duration::from_secs(1))
22395 .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
22396
22397 release_tx.send(()).unwrap();
22398 futures::executor::block_on(first).unwrap();
22399 futures::executor::block_on(second).unwrap();
22400 queued_rx
22401 .recv_timeout(Duration::from_secs(1))
22402 .expect("queued blocking work must run after the occupied slot is released");
22403 }
22404
22405 #[cfg(napi_runtime_os_threads)]
22406 #[test]
22407 fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
22408 use std::sync::mpsc;
22416 use std::time::Duration;
22417
22418 struct SpawnOnReady {
22419 executor: Arc<MultiThreadExecutor>,
22420 child_tx: Option<mpsc::Sender<u64>>,
22421 }
22422 impl Future for SpawnOnReady {
22423 type Output = ();
22424 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22425 let child_tx = self.child_tx.take().expect("polled once");
22426 let sched = Arc::clone(&self.executor);
22427 let (runnable, task) = async_task::spawn(
22428 async move {
22429 child_tx.send(21).unwrap();
22430 },
22431 move |r| sched.schedule(r),
22432 );
22433 self.executor.schedule(runnable);
22435 task.detach();
22436 Poll::Ready(())
22437 }
22438 }
22439
22440 let (done_tx, done_rx) = mpsc::channel();
22441 let runner = std::thread::spawn(move || {
22442 let options = RuntimeOptions {
22443 flavor: RuntimeFlavor::MultiThread,
22444 worker_threads: 2,
22445 max_blocking_tasks: 2,
22446 thread_name_prefix: "exit-flush".to_string(),
22447 park_deadline: None,
22448 drain_linger: DEFAULT_DRAIN_LINGER,
22449 };
22450 let executor =
22451 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22452
22453 let (child_tx, child_rx) = mpsc::channel::<u64>();
22454 {
22455 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
22458 let mut future = std::pin::pin!(SpawnOnReady {
22459 executor: Arc::clone(&executor),
22460 child_tx: Some(child_tx)
22461 });
22462 executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
22463 }
22464
22465 let value = child_rx
22469 .recv_timeout(Duration::from_secs(5))
22470 .expect("the child scheduled by the final poll was stranded in the LIFO slot");
22471 assert_eq!(value, 21);
22472 let _ = done_tx.send(());
22473 });
22474
22475 match done_rx.recv_timeout(Duration::from_secs(10)) {
22476 Ok(()) => runner.join().unwrap(),
22477 Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
22478 }
22479 }
22480
22481 #[cfg(napi_runtime_os_threads)]
22485 fn spawn_hot_chain(
22486 executor: &Arc<MultiThreadExecutor>,
22487 stop: Arc<AtomicBool>,
22488 hops: Arc<AtomicU64>,
22489 ) {
22490 if stop.load(Ordering::SeqCst) {
22491 return;
22492 }
22493 let link_exec = Arc::clone(executor);
22494 let body = async move {
22495 hops.fetch_add(1, Ordering::SeqCst);
22496 spawn_hot_chain(&link_exec, stop, hops);
22497 };
22498 let sched = Arc::clone(executor);
22499 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
22500 executor.schedule(runnable);
22501 task.detach();
22502 }
22503
22504 #[cfg(napi_runtime_os_threads)]
22505 #[test]
22506 fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
22507 use std::sync::mpsc;
22516 use std::time::Duration;
22517
22518 let (done_tx, done_rx) = mpsc::channel();
22519 let runner = std::thread::spawn(move || {
22520 let options = RuntimeOptions {
22521 flavor: RuntimeFlavor::MultiThread,
22522 worker_threads: 1,
22523 max_blocking_tasks: 0,
22526 thread_name_prefix: "coop-budget".to_string(),
22527 park_deadline: None,
22528 drain_linger: DEFAULT_DRAIN_LINGER,
22529 };
22530 let executor =
22531 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22532
22533 let stop = Arc::new(AtomicBool::new(false));
22534 let hops = Arc::new(AtomicU64::new(0));
22535
22536 let m_exec = Arc::clone(&executor);
22540 let m_stop = Arc::clone(&stop);
22541 let m_hops = Arc::clone(&hops);
22542 let m_body = async move {
22543 let (tx, rx) = oneshot::channel::<u64>();
22544 let sched_f = Arc::clone(&m_exec);
22546 let (f_runnable, f_task) = async_task::spawn(
22547 async move {
22548 let _ = tx.send(7);
22549 },
22550 move |r| sched_f.schedule(r),
22551 );
22552 m_exec.schedule(f_runnable);
22553 f_task.detach();
22554 spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
22557
22558 let mut inner = std::pin::pin!(async move {
22559 assert_eq!(
22560 rx.await.unwrap(),
22561 7,
22562 "F must run and complete the awaited future"
22563 );
22564 });
22565 m_exec.block_on(inner.as_mut());
22566 m_stop.store(true, Ordering::SeqCst);
22567 };
22568 let sched_m = Arc::clone(&executor);
22569 let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
22570 executor.schedule(m_runnable);
22571
22572 futures::executor::block_on(m_task);
22573 assert!(
22574 hops.load(Ordering::SeqCst) >= 64,
22575 "the chain must have been genuinely hot (>= one budget window) before F ran"
22576 );
22577 let _ = done_tx.send(());
22578 });
22579
22580 match done_rx.recv_timeout(Duration::from_secs(10)) {
22581 Ok(()) => runner.join().unwrap(),
22582 Err(error) => panic!(
22583 "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
22584 ),
22585 }
22586 }
22587
22588 #[cfg(napi_runtime_os_threads)]
22589 #[test]
22590 fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
22591 use std::sync::mpsc;
22592
22593 struct RefillLifo {
22594 executor: Arc<MultiThreadExecutor>,
22595 fifo_ran: Arc<AtomicBool>,
22596 }
22597
22598 impl Future for RefillLifo {
22599 type Output = ();
22600
22601 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22602 if self.fifo_ran.load(Ordering::SeqCst) {
22603 return Poll::Ready(());
22604 }
22605 let scheduler = Arc::clone(&self.executor);
22606 let (runnable, task) =
22607 async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
22608 self.executor.schedule(runnable);
22609 task.detach();
22610 Poll::Pending
22611 }
22612 }
22613
22614 let (done_tx, done_rx) = mpsc::channel();
22615 let runner = std::thread::spawn(move || {
22616 let options = RuntimeOptions {
22617 flavor: RuntimeFlavor::MultiThread,
22618 worker_threads: 1,
22619 max_blocking_tasks: 0,
22620 thread_name_prefix: "forced-fifo".to_string(),
22621 park_deadline: None,
22622 drain_linger: DEFAULT_DRAIN_LINGER,
22623 };
22624 let executor =
22625 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22626 executor
22627 .active_drainers
22628 .store(executor.max_drainers, Ordering::SeqCst);
22629
22630 let fifo_ran = Arc::new(AtomicBool::new(false));
22631 let fifo_flag = Arc::clone(&fifo_ran);
22632 let scheduler = Arc::clone(&executor);
22633 let (fifo, task) = async_task::spawn(
22634 async move {
22635 fifo_flag.store(true, Ordering::SeqCst);
22636 },
22637 move |runnable| scheduler.schedule(runnable),
22638 );
22639 task.detach();
22640 executor.metrics.runnable_scheduled();
22641 executor.push_fifo_raw(fifo);
22642
22643 {
22644 let _driver = OnPoolWorkerGuard::enter(executor.id);
22645 let mut future = std::pin::pin!(RefillLifo {
22646 executor: Arc::clone(&executor),
22647 fifo_ran: Arc::clone(&fifo_ran)
22648 });
22649 executor.block_on(future.as_mut());
22650 while executor.run_one() {}
22651 }
22652 executor.active_drainers.store(0, Ordering::SeqCst);
22653 assert!(fifo_ran.load(Ordering::SeqCst));
22654 done_tx.send(()).unwrap();
22655 });
22656 done_rx
22657 .recv_timeout(Duration::from_secs(3))
22658 .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
22659 runner.join().unwrap();
22660 }
22661
22662 #[cfg(napi_runtime_os_threads)]
22663 #[test]
22664 fn blocking_closure_spawn_is_not_stranded_in_slot() {
22665 use std::sync::mpsc;
22672 use std::time::Duration;
22673
22674 let (done_tx, done_rx) = mpsc::channel();
22675 let runner = std::thread::spawn(move || {
22676 let options = RuntimeOptions {
22677 flavor: RuntimeFlavor::MultiThread,
22678 worker_threads: 2,
22679 max_blocking_tasks: 2,
22680 thread_name_prefix: "blk-spawn-wait".to_string(),
22681 park_deadline: None,
22682 drain_linger: DEFAULT_DRAIN_LINGER,
22683 };
22684 let executor =
22685 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22686
22687 let blk_exec = Arc::clone(&executor);
22690 let handle = executor.schedule_blocking(move || {
22691 let (tx, rx) = mpsc::channel::<u64>();
22692 let sched = Arc::clone(&blk_exec);
22693 let (runnable, task) = async_task::spawn(
22694 async move {
22695 tx.send(21).unwrap();
22696 },
22697 move |r| sched.schedule(r),
22698 );
22699 blk_exec.schedule(runnable);
22700 task.detach();
22701 rx.recv_timeout(Duration::from_secs(5))
22702 .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
22703 });
22704 assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
22705
22706 let (v_tx, v_rx) = mpsc::channel::<u64>();
22709 let v_exec = Arc::clone(&executor);
22710 let v_handle = executor.schedule_blocking(move || {
22711 let sched = Arc::clone(&v_exec);
22712 let (runnable, task) = async_task::spawn(
22713 async move {
22714 v_tx.send(42).unwrap();
22715 },
22716 move |r| sched.schedule(r),
22717 );
22718 v_exec.schedule(runnable);
22719 task.detach();
22720 1u64
22721 });
22722 assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
22723 assert_eq!(
22724 v_rx
22725 .recv_timeout(Duration::from_secs(5))
22726 .expect("fire-and-forget child must complete"),
22727 42
22728 );
22729 let _ = done_tx.send(());
22730 });
22731
22732 match done_rx.recv_timeout(Duration::from_secs(15)) {
22733 Ok(()) => runner.join().unwrap(),
22734 Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
22735 }
22736 }
22737
22738 #[cfg(napi_runtime_os_threads)]
22739 #[test]
22740 fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
22741 use std::sync::{Barrier, mpsc};
22747 use std::time::Duration;
22748
22749 let (done_tx, done_rx) = mpsc::channel();
22750 let runner = std::thread::spawn(move || {
22751 let metrics = Arc::new(RuntimeMetrics::default());
22752 let options = RuntimeOptions {
22753 flavor: RuntimeFlavor::MultiThread,
22754 worker_threads: 2,
22755 max_blocking_tasks: 2,
22756 thread_name_prefix: "rd1-nested-blk".to_string(),
22757 park_deadline: None,
22758 drain_linger: DEFAULT_DRAIN_LINGER,
22759 };
22760 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
22761
22762 let outer_count = 2usize; let barrier = Arc::new(Barrier::new(outer_count));
22764
22765 let mut handles = Vec::new();
22766 for _ in 0..outer_count {
22767 let exec = Arc::clone(&executor);
22768 let barrier = Arc::clone(&barrier);
22769 let handle = executor.schedule_blocking(move || {
22770 barrier.wait();
22772 let inner_exec = Arc::clone(&exec);
22773 let mut value = 0usize;
22774 {
22775 let mut inner = std::pin::pin!(async {
22776 value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
22777 });
22778 exec.block_on(inner.as_mut());
22779 }
22780 value
22781 });
22782 handles.push(handle);
22783 }
22784
22785 for handle in handles {
22786 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
22787 }
22788 let _ = done_tx.send(());
22789 });
22790
22791 match done_rx.recv_timeout(Duration::from_secs(10)) {
22792 Ok(()) => runner.join().unwrap(),
22793 Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
22794 }
22795 }
22796
22797 #[cfg(napi_runtime_os_threads)]
22798 #[test]
22799 fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
22800 let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
22801 let owner = executor.fresh_owner_token();
22802 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22803 let job = BlockingJobId(u64::MAX - 5);
22804 let dependency = TaskDependency::default();
22805 dependency.set_owned(BlockingDependency { job, owner: None });
22806 let ran = Arc::new(AtomicBool::new(false));
22807 let job_ran = Arc::clone(&ran);
22808 executor
22809 .blocking_queue
22810 .lock()
22811 .unwrap()
22812 .push(QueuedBlocking {
22813 id: job,
22814 run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
22815 });
22816
22817 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22818 assert!(
22819 !executor.try_owned_blocking_over_cap(&dependency),
22820 "an untagged worker must not infer ancestry from a sole active owner"
22821 );
22822 assert!(!ran.load(Ordering::SeqCst));
22823 assert!(
22824 executor
22825 .blocking_queue
22826 .lock()
22827 .unwrap()
22828 .jobs
22829 .contains_key(&job),
22830 "rejected owner inference must leave the exact job queued"
22831 );
22832 }
22833
22834 #[cfg(napi_runtime_os_threads)]
22835 #[test]
22836 fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
22837 use std::sync::{Barrier, mpsc};
22838
22839 let options = RuntimeOptions {
22840 flavor: RuntimeFlavor::MultiThread,
22841 worker_threads: 2,
22842 max_blocking_tasks: 2,
22843 thread_name_prefix: "exact-owner-frame".to_string(),
22844 park_deadline: None,
22845 drain_linger: DEFAULT_DRAIN_LINGER,
22846 };
22847 let executor =
22848 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22849 let owners_saturated = Arc::new(Barrier::new(3));
22850 let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
22851 let begin_lending = Arc::new(Barrier::new(3));
22852 let release_dependencies = Arc::new(Barrier::new(3));
22853 let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
22854 let mut owners = Vec::new();
22855
22856 for owner_index in 0..2 {
22857 let owner_executor = Arc::clone(&executor);
22858 let owners_saturated = Arc::clone(&owners_saturated);
22859 let dependency_queued_tx = dependency_queued_tx.clone();
22860 let begin_lending = Arc::clone(&begin_lending);
22861 let release_dependencies = Arc::clone(&release_dependencies);
22862 let dependency_started_tx = dependency_started_tx.clone();
22863 owners.push(executor.schedule_blocking(move || {
22864 let owner_thread = std::thread::current().id();
22865 owners_saturated.wait();
22866 let child_started = dependency_started_tx.clone();
22867 let child_release = Arc::clone(&release_dependencies);
22868 let child = owner_executor.schedule_blocking(move || {
22869 assert_eq!(
22870 std::thread::current().id(),
22871 owner_thread,
22872 "an owner frame must execute only its own exact dependency on its lent lane"
22873 );
22874 child_started.send(owner_index).unwrap();
22875 child_release.wait();
22876 owner_index
22877 });
22878 dependency_queued_tx.send(()).unwrap();
22879 begin_lending.wait();
22880
22881 let mut result = None;
22882 {
22883 let mut future = std::pin::pin!(async {
22884 result = Some(child.await.unwrap());
22885 });
22886 assert_eq!(
22887 owner_executor.block_on(future.as_mut()),
22888 BlockOnOutcome::Completed
22889 );
22890 }
22891 result.unwrap()
22892 }));
22893 }
22894 drop(dependency_queued_tx);
22895 drop(dependency_started_tx);
22896 owners_saturated.wait();
22897
22898 for _ in 0..2 {
22899 dependency_queued_rx
22900 .recv_timeout(Duration::from_secs(2))
22901 .expect("both owner-scoped dependencies must be queued while the cap is saturated");
22902 }
22903 let (unrelated_tx, unrelated_rx) = mpsc::channel();
22904 let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
22905 begin_lending.wait();
22906
22907 let mut started = Vec::new();
22908 for _ in 0..2 {
22909 started.push(
22910 dependency_started_rx
22911 .recv_timeout(Duration::from_secs(2))
22912 .expect("each owner must service its own exact dependency"),
22913 );
22914 }
22915 started.sort_unstable();
22916 assert_eq!(started, vec![0, 1]);
22917 assert!(
22918 unrelated_rx.try_recv().is_err(),
22919 "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
22920 );
22921
22922 release_dependencies.wait();
22923 for (owner_index, owner) in owners.into_iter().enumerate() {
22924 assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
22925 }
22926 unrelated_rx
22927 .recv_timeout(Duration::from_secs(2))
22928 .expect("the unrelated job must run after an owner releases a counted lane");
22929 futures::executor::block_on(unrelated).unwrap();
22930 }
22931
22932 #[cfg(napi_runtime_os_threads)]
22933 #[test]
22934 fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
22935 use std::sync::{Mutex, mpsc};
22936
22937 const DEPENDENCY_COUNT: usize = 4;
22938
22939 let options = RuntimeOptions {
22940 flavor: RuntimeFlavor::MultiThread,
22941 worker_threads: 3,
22942 max_blocking_tasks: 1,
22943 thread_name_prefix: "parked-unrelated-worker".to_string(),
22944 park_deadline: None,
22945 drain_linger: DEFAULT_DRAIN_LINGER,
22946 };
22947 let executor =
22948 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22949 let (parked_tx, parked_rx) = mpsc::channel();
22950 let (release_parked_tx, release_parked_rx) = mpsc::channel();
22951 executor.pool.spawn(move || {
22952 parked_tx.send(()).unwrap();
22953 release_parked_rx.recv().unwrap();
22954 });
22955 parked_rx
22956 .recv_timeout(Duration::from_secs(2))
22957 .expect("the unrelated Rayon worker must remain occupied");
22958
22959 let owner_executor = Arc::clone(&executor);
22960 let owner = executor.schedule_blocking(move || {
22961 let owner = BLOCKING_OWNER
22962 .with(std::cell::Cell::get)
22963 .expect("the blocking closure must carry its exact owner frame");
22964 let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
22965 let results = Arc::new(Mutex::new(Vec::new()));
22966 let descendant_results = Arc::clone(&results);
22967 let descendant_executor = Arc::clone(&owner_executor);
22968 rayon::scope(move |scope| {
22969 scope.spawn(move |_| {
22970 assert_eq!(
22971 BLOCKING_OWNER.with(std::cell::Cell::get),
22972 None,
22973 "the parked owner must force its descendant onto the remaining worker"
22974 );
22975 let _owner = BlockingOwnerGuard::enter(Some(owner));
22976 descendant_started_tx.send(()).unwrap();
22977 for value in 0..DEPENDENCY_COUNT {
22978 let handle = descendant_executor.schedule_blocking(move || value);
22979 let mut result = None;
22980 {
22981 let mut future = std::pin::pin!(async {
22982 result = Some(handle.await.unwrap());
22983 });
22984 assert_eq!(
22985 descendant_executor.block_on(future.as_mut()),
22986 BlockOnOutcome::Completed
22987 );
22988 }
22989 descendant_results.lock().unwrap().push(result.unwrap());
22990 }
22991 });
22992 descendant_started_rx
22993 .recv_timeout(Duration::from_secs(2))
22994 .expect("the nested descendant must start on the only unoccupied worker");
22995 });
22996 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22997 });
22998
22999 assert_eq!(
23000 futures::executor::block_on(owner).unwrap(),
23001 (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
23002 );
23003
23004 let (idle_tx, idle_rx) = mpsc::channel();
23005 let idle_executor = Arc::clone(&executor);
23006 let idle_waiter = std::thread::spawn(move || {
23007 idle_executor.begin_shutdown();
23008 idle_executor.wait_until_scheduler_idle();
23009 idle_tx.send(()).unwrap();
23010 });
23011 idle_rx
23012 .recv_timeout(Duration::from_secs(2))
23013 .expect("an unrelated parked worker must not retain owner-probe idle accounting");
23014
23015 release_parked_tx.send(()).unwrap();
23016 idle_waiter.join().unwrap();
23017 }
23018
23019 #[cfg(napi_runtime_os_threads)]
23020 #[test]
23021 fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
23022 use std::sync::{Condvar, Mutex, mpsc};
23023
23024 let options = RuntimeOptions {
23025 flavor: RuntimeFlavor::MultiThread,
23026 worker_threads: 3,
23027 max_blocking_tasks: 1,
23028 thread_name_prefix: "same-owner-rearm".to_string(),
23029 park_deadline: None,
23030 drain_linger: DEFAULT_DRAIN_LINGER,
23031 };
23032 let executor =
23033 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23034 let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
23035 let (job_started_tx, job_started_rx) = mpsc::channel();
23036 let owner_executor = Arc::clone(&executor);
23037 let owner_releases = Arc::clone(&releases);
23038 let owner = executor.schedule_blocking(move || {
23039 let owner = BLOCKING_OWNER
23040 .with(std::cell::Cell::get)
23041 .expect("the blocking closure must carry its exact owner frame");
23042 let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
23043 let results = Arc::new(Mutex::new(Vec::new()));
23044 let scope_results = Arc::clone(&results);
23045 rayon::scope(move |scope| {
23046 for index in 0..2 {
23047 let descendant_ready_tx = descendant_ready_tx.clone();
23048 let descendant_executor = Arc::clone(&owner_executor);
23049 let descendant_results = Arc::clone(&scope_results);
23050 let job_started_tx = job_started_tx.clone();
23051 let releases = Arc::clone(&owner_releases);
23052 scope.spawn(move |_| {
23053 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
23054 let _owner = BlockingOwnerGuard::enter(Some(owner));
23055 descendant_ready_tx.send(()).unwrap();
23056 let handle = descendant_executor.schedule_blocking(move || {
23057 job_started_tx.send(index).unwrap();
23058 let (released, changed) = &*releases;
23059 let mut state = released.lock().unwrap();
23060 while !state[index] {
23061 state = changed.wait(state).unwrap();
23062 }
23063 index
23064 });
23065 let mut result = None;
23066 {
23067 let mut future = std::pin::pin!(async {
23068 result = Some(handle.await.unwrap());
23069 });
23070 assert_eq!(
23071 descendant_executor.block_on(future.as_mut()),
23072 BlockOnOutcome::Completed
23073 );
23074 }
23075 descendant_results.lock().unwrap().push(result.unwrap());
23076 });
23077 }
23078 drop(descendant_ready_tx);
23079 for _ in 0..2 {
23080 descendant_ready_rx
23081 .recv_timeout(Duration::from_secs(2))
23082 .expect("both descendants must be stolen before the owner leaves its Rayon scope");
23083 }
23084 });
23085 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
23086 });
23087
23088 let first = job_started_rx
23089 .recv_timeout(Duration::from_secs(2))
23090 .expect("one exact dependency must borrow the owner lane");
23091 assert!(
23092 job_started_rx
23093 .recv_timeout(Duration::from_millis(100))
23094 .is_err(),
23095 "one owner frame must not service two dependencies concurrently"
23096 );
23097 let unrelated_parker = Arc::new(DriverParker::default());
23098 executor
23099 .parked_drivers
23100 .register_with_role(&unrelated_parker, true, None);
23101 {
23102 let (released, changed) = &*releases;
23103 released.lock().unwrap()[first] = true;
23104 changed.notify_all();
23105 }
23106
23107 let second = job_started_rx
23108 .recv_timeout(Duration::from_secs(2))
23109 .expect("completion must rearm one parked same-owner dependency");
23110 assert_ne!(second, first);
23111 assert!(
23112 !unrelated_parker.consume_permit(),
23113 "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
23114 );
23115 executor.parked_drivers.deregister(&unrelated_parker);
23116 {
23117 let (released, changed) = &*releases;
23118 released.lock().unwrap()[second] = true;
23119 changed.notify_all();
23120 }
23121
23122 let mut results = futures::executor::block_on(owner).unwrap();
23123 results.sort_unstable();
23124 assert_eq!(results, vec![0, 1]);
23125 }
23126
23127 #[cfg(napi_runtime_os_threads)]
23128 #[test]
23129 fn failed_exact_claim_rearms_parked_same_owner_dependency() {
23130 let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
23131 let owner = executor.fresh_owner_token();
23132 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23133 let failed_dependency = Arc::new(TaskDependency::default());
23134 failed_dependency.set_owned(BlockingDependency {
23135 job: BlockingJobId(u64::MAX - 21),
23136 owner: Some(owner),
23137 });
23138 let parked_dependency = Arc::new(TaskDependency::default());
23139 parked_dependency.set_owned(BlockingDependency {
23140 job: BlockingJobId(u64::MAX - 22),
23141 owner: Some(owner),
23142 });
23143 let parker = Arc::new(DriverParker::default());
23144
23145 let queue = executor.blocking_queue.lock().unwrap();
23146 let claimant_executor = Arc::clone(&executor);
23147 let claimant_dependency = Arc::clone(&failed_dependency);
23148 let claimant = std::thread::spawn(move || {
23149 let _owner = BlockingOwnerGuard::enter(Some(owner));
23150 claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
23151 });
23152 wait_until("the failed claimant to reserve the owner lane", || {
23153 !executor.active_blocking_owners.is_available(owner)
23154 });
23155 executor
23156 .parked_drivers
23157 .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
23158 failed_dependency.clear();
23159 drop(queue);
23160
23161 assert!(
23162 !claimant.join().unwrap(),
23163 "the cancelled dependency must not execute"
23164 );
23165 assert!(
23166 parker.consume_permit(),
23167 "releasing a failed exact claim must hand the owner lane to another parked dependency"
23168 );
23169 }
23170
23171 #[cfg(napi_runtime_os_threads)]
23172 #[test]
23173 fn exact_owner_lending_does_not_start_blocking_after_stop() {
23174 use std::sync::mpsc;
23175
23176 let options = RuntimeOptions {
23177 flavor: RuntimeFlavor::MultiThread,
23178 worker_threads: 2,
23179 max_blocking_tasks: 1,
23180 thread_name_prefix: "stopped-exact-owner".to_string(),
23181 park_deadline: None,
23182 drain_linger: DEFAULT_DRAIN_LINGER,
23183 };
23184 let executor =
23185 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23186 let ran = Arc::new(AtomicBool::new(false));
23187 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
23188 let (release_claim_tx, release_claim_rx) = mpsc::channel();
23189 let owner_executor = Arc::clone(&executor);
23190 let child_ran = Arc::clone(&ran);
23191 let owner = executor.schedule_blocking(move || {
23192 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
23193 *slot.borrow_mut() = Some(Box::new(move || {
23194 claim_entered_tx.send(()).unwrap();
23195 release_claim_rx.recv().unwrap();
23196 }));
23197 });
23198 let child = owner_executor.schedule_blocking(move || {
23199 child_ran.store(true, Ordering::SeqCst);
23200 });
23201 let mut future = std::pin::pin!(async move {
23202 let _ = child.await;
23203 });
23204 owner_executor.block_on(future.as_mut())
23205 });
23206
23207 claim_entered_rx
23208 .recv_timeout(Duration::from_secs(2))
23209 .expect("the cooperative driver must pause before the exact-owner queue claim");
23210 assert_eq!(
23211 executor.blocking_queue.lock().unwrap().jobs.len(),
23212 1,
23213 "the exact dependency must remain queued before stop publication"
23214 );
23215
23216 executor.stop.begin_shutdown();
23219 release_claim_tx.send(()).unwrap();
23220
23221 assert_eq!(
23222 futures::executor::block_on(owner).unwrap(),
23223 BlockOnOutcome::Stopped,
23224 "the cooperative driver must stop instead of executing its queued dependency"
23225 );
23226 assert!(
23227 !ran.load(Ordering::SeqCst),
23228 "exact-owner lending started blocking work after stop"
23229 );
23230 assert_eq!(
23231 executor.blocking_queue.lock().unwrap().jobs.len(),
23232 1,
23233 "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
23234 );
23235
23236 executor.begin_shutdown();
23237 executor.wait_until_scheduler_idle();
23238 assert!(executor.blocking_queue.lock().unwrap().is_empty());
23239 assert!(!ran.load(Ordering::SeqCst));
23240 }
23241
23242 #[cfg(napi_runtime_os_threads)]
23243 #[test]
23244 fn exact_owner_lending_attempts_scale_linearly_with_topology() {
23245 const DEPENDENCY_COUNT: usize = 1024;
23246
23247 let options = RuntimeOptions {
23248 flavor: RuntimeFlavor::MultiThread,
23249 worker_threads: 2,
23250 max_blocking_tasks: 1,
23251 thread_name_prefix: "linear-owner-lending".to_string(),
23252 park_deadline: None,
23253 drain_linger: DEFAULT_DRAIN_LINGER,
23254 };
23255 let executor =
23256 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23257 let completed = Arc::new(AtomicUsize::new(0));
23258
23259 for index in 0..DEPENDENCY_COUNT {
23260 let owner = executor.fresh_owner_token();
23261 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23262 let dependency = TaskDependency::new(u64::MAX, Some(owner));
23263 let blocking = BlockingDependency {
23264 job: BlockingJobId(u64::MAX - index as u64),
23265 owner: Some(owner),
23266 };
23267 dependency.set_owned(blocking);
23268 let completed = Arc::clone(&completed);
23269 executor
23270 .blocking_queue
23271 .lock()
23272 .unwrap()
23273 .push(QueuedBlocking {
23274 id: blocking.job,
23275 run: Box::new(move || {
23276 completed.fetch_add(1, Ordering::SeqCst);
23277 }),
23278 });
23279
23280 let _owner = BlockingOwnerGuard::enter(Some(owner));
23281 assert!(executor.try_owned_blocking_over_cap(&dependency));
23282 }
23283
23284 assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
23285 assert_eq!(
23286 executor.owner_lending_attempts.load(Ordering::Relaxed),
23287 DEPENDENCY_COUNT,
23288 "targeted lending must perform one O(1) claim attempt per exact dependency"
23289 );
23290 let queue = executor.blocking_queue.lock().unwrap();
23291 assert!(queue.is_empty());
23292 assert_eq!(queue.head, None);
23293 assert_eq!(queue.tail, None);
23294 }
23295
23296 #[cfg(napi_runtime_os_threads)]
23297 #[test]
23298 fn one_dependency_interspersed_owner_publications_stays_linear() {
23299 const PUBLICATION_COUNT: usize = 1024;
23300
23301 let executor = multi_thread_executor(2, None, "linear-publication-lending");
23302 let owner = executor.fresh_owner_token();
23303 let wrong_owner = executor.fresh_owner_token();
23304 let dependency = TaskDependency::new(u64::MAX, Some(owner));
23305 let completed = Arc::new(AtomicUsize::new(0));
23306 for index in 0..PUBLICATION_COUNT {
23307 let cancelled_source = TaskDependency::default();
23308 cancelled_source.set_owned(BlockingDependency {
23309 job: BlockingJobId(u64::MAX - 100_000 - index as u64),
23310 owner: Some(owner),
23311 });
23312 dependency.set_borrowed(
23313 &cancelled_source
23314 .get()
23315 .expect("the source publication must initially be live"),
23316 );
23317 cancelled_source.clear();
23318
23319 dependency.set_owned(BlockingDependency {
23320 job: BlockingJobId(u64::MAX - 200_000 - index as u64),
23321 owner: Some(wrong_owner),
23322 });
23323 let blocking = BlockingDependency {
23324 job: BlockingJobId(u64::MAX - 1_000 - index as u64),
23325 owner: Some(owner),
23326 };
23327 dependency.set_owned(blocking);
23328 let completed = Arc::clone(&completed);
23329 executor
23330 .blocking_queue
23331 .lock()
23332 .unwrap()
23333 .push(QueuedBlocking {
23334 id: blocking.job,
23335 run: Box::new(move || {
23336 completed.fetch_add(1, Ordering::SeqCst);
23337 }),
23338 });
23339 }
23340 dependency.reset_entry_visits();
23341
23342 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23343 let _owner = BlockingOwnerGuard::enter(Some(owner));
23344 for _ in 0..PUBLICATION_COUNT {
23345 assert!(
23346 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23347 "the owner-handoff predicate must find the next exact publication"
23348 );
23349 assert!(
23350 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23351 "the park-availability predicate must find the next serviceable publication"
23352 );
23353 assert!(executor.try_owned_blocking_over_cap(&dependency));
23354 }
23355
23356 assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
23357 assert!(
23358 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23359 "wrong-owner publications must not satisfy an exact handoff"
23360 );
23361 assert!(
23362 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23363 "wrong-owner publications must not make the owner's lane appear serviceable"
23364 );
23365 assert!(
23366 dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
23367 "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
23368 dependency.entry_visits()
23369 );
23370 assert!(executor.blocking_queue.lock().unwrap().is_empty());
23371 }
23372
23373 #[cfg(napi_runtime_os_threads)]
23374 #[test]
23375 fn binding_unowned_dependency_updates_exact_owner_predicate() {
23376 let owner = BlockingOwnerToken {
23377 executor_id: u64::MAX - 200,
23378 frame: u64::MAX - 201,
23379 };
23380 let dependency = TaskDependency::default();
23381 let blocking = BlockingDependency {
23382 job: BlockingJobId(u64::MAX - 202),
23383 owner: None,
23384 };
23385 dependency.set_owned(blocking);
23386
23387 assert!(
23388 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23389 "an unowned publication must not satisfy an exact owner handoff"
23390 );
23391 assert!(
23392 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23393 "an unowned publication is available to the ambient owner"
23394 );
23395
23396 let selected = dependency
23397 .select_for_owner(owner)
23398 .expect("the owner must bind the unowned publication");
23399 assert_eq!(selected.publication.dependency.owner, Some(owner));
23400 assert!(
23401 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23402 "binding must immediately publish the entry to exact-owner predicates"
23403 );
23404 assert!(dependency.try_claim(&selected));
23405 assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23406 }
23407
23408 #[cfg(napi_runtime_os_threads)]
23409 #[test]
23410 fn owner_selection_preserves_unowned_and_exact_publication_order() {
23411 let owner = BlockingOwnerToken {
23412 executor_id: u64::MAX - 210,
23413 frame: u64::MAX - 211,
23414 };
23415 let dependency = TaskDependency::default();
23416 let first = BlockingDependency {
23417 job: BlockingJobId(u64::MAX - 212),
23418 owner: None,
23419 };
23420 let second = BlockingDependency {
23421 job: BlockingJobId(u64::MAX - 213),
23422 owner: Some(owner),
23423 };
23424 dependency.set_owned(first);
23425 dependency.set_owned(second);
23426
23427 let selected = dependency
23428 .select_for_owner(owner)
23429 .expect("the first publication must be found");
23430 assert_eq!(
23431 selected.publication.dependency,
23432 BlockingDependency {
23433 owner: Some(owner),
23434 ..first
23435 }
23436 );
23437 assert!(dependency.try_claim(&selected));
23438
23439 let selected = dependency
23440 .select_for_owner(owner)
23441 .expect("the second publication must remain eligible");
23442 assert_eq!(selected.publication.dependency, second);
23443 }
23444
23445 #[cfg(napi_runtime_os_threads)]
23446 #[test]
23447 fn blocking_queue_exact_removal_reclaims_fifo_links() {
23448 const JOB_COUNT: u64 = 4096;
23449
23450 let mut queue = BlockingQueue {
23451 closed: false,
23452 head: None,
23453 tail: None,
23454 jobs: FxHashMap::default(),
23455 };
23456 for id in 1..=JOB_COUNT {
23457 queue.push(QueuedBlocking {
23458 id: BlockingJobId(id),
23459 run: Box::new(|| {}),
23460 });
23461 }
23462 for id in (2..=JOB_COUNT).step_by(2) {
23463 drop(
23464 queue
23465 .remove(BlockingJobId(id))
23466 .expect("even job must remain indexed"),
23467 );
23468 }
23469 for id in (1..=JOB_COUNT).step_by(2) {
23470 drop(
23471 queue
23472 .remove(BlockingJobId(id))
23473 .expect("odd job must remain indexed"),
23474 );
23475 }
23476 assert!(queue.jobs.is_empty());
23477 assert_eq!(queue.head, None);
23478 assert_eq!(queue.tail, None);
23479
23480 let order = Arc::new(Mutex::new(Vec::new()));
23481 for id in 1..=3 {
23482 let order = Arc::clone(&order);
23483 queue.push(QueuedBlocking {
23484 id: BlockingJobId(id),
23485 run: Box::new(move || order.lock().unwrap().push(id)),
23486 });
23487 }
23488 drop(
23489 queue
23490 .remove(BlockingJobId(2))
23491 .expect("middle job must be removable"),
23492 );
23493 queue.pop_front().expect("FIFO head must remain linked")();
23494 queue.pop_front().expect("FIFO tail must remain linked")();
23495 assert!(queue.pop_front().is_none());
23496 assert_eq!(*order.lock().unwrap(), vec![1, 3]);
23497 assert_eq!(queue.head, None);
23498 assert_eq!(queue.tail, None);
23499 }
23500
23501 #[cfg(not(target_family = "wasm"))]
23502 #[test]
23503 fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
23504 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
23505
23506 if std::env::var_os(CHILD_ENV).is_some() {
23507 configure(RuntimeOptions {
23508 flavor: RuntimeFlavor::MultiThread,
23509 worker_threads: 2,
23510 max_blocking_tasks: 1,
23511 thread_name_prefix: "dependency-lending-metrics".to_string(),
23512 park_deadline: None,
23513 drain_linger: DEFAULT_DRAIN_LINGER,
23514 })
23515 .unwrap();
23516
23517 let outer = spawn_blocking(|| {
23518 let inner = spawn_blocking(|| 42usize);
23519 block_on(inner).unwrap()
23520 });
23521 assert_eq!(block_on(outer).unwrap(), 42);
23522
23523 shutdown().unwrap();
23528 let snapshot = metrics();
23529 assert_eq!(snapshot.blocking_tasks_started, 2);
23530 assert_eq!(snapshot.blocking_tasks_completed, 2);
23531 assert_eq!(snapshot.active_blocking_tasks, 0);
23532 assert_eq!(
23533 snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
23534 "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
23535 );
23536 return;
23537 }
23538
23539 let output = std::process::Command::new(std::env::current_exe().unwrap())
23540 .arg("--exact")
23541 .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
23542 .arg("--nocapture")
23543 .env(CHILD_ENV, "1")
23544 .output()
23545 .expect("the dependency-lending metrics subprocess must start");
23546 assert!(
23547 output.status.success(),
23548 "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
23549 output.status.code(),
23550 String::from_utf8_lossy(&output.stdout),
23551 String::from_utf8_lossy(&output.stderr)
23552 );
23553 }
23554
23555 #[cfg(napi_runtime_os_threads)]
23556 #[test]
23557 fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
23558 use std::sync::mpsc;
23559
23560 let (done_tx, done_rx) = mpsc::channel();
23561 let runner = std::thread::spawn(move || {
23562 let options = RuntimeOptions {
23563 flavor: RuntimeFlavor::MultiThread,
23564 worker_threads: 2,
23565 max_blocking_tasks: 1,
23566 thread_name_prefix: "exact-blocking-job".to_string(),
23567 park_deadline: None,
23568 drain_linger: DEFAULT_DRAIN_LINGER,
23569 };
23570 let executor =
23571 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23572 let outer_exec = Arc::clone(&executor);
23573 let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
23574 let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
23575 let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
23576 let outer = executor.schedule_blocking(move || {
23577 let sibling = outer_exec.schedule_blocking(move || {
23578 sibling_started_tx.send(()).unwrap();
23579 release_sibling_rx.recv().unwrap();
23580 });
23581 let awaited = outer_exec.schedule_blocking(move || {
23582 awaited_ran_tx.send(()).unwrap();
23583 17usize
23584 });
23585 let mut value = None;
23586 {
23587 let mut future = std::pin::pin!(async {
23588 value = Some(awaited.await.unwrap());
23589 });
23590 outer_exec.block_on(future.as_mut());
23591 }
23592 (sibling, value.unwrap())
23593 });
23594
23595 awaited_ran_rx
23596 .recv_timeout(Duration::from_secs(2))
23597 .expect("the exact awaited job must bypass the earlier detached sibling");
23598 let (sibling, value) = futures::executor::block_on(outer).unwrap();
23599 assert_eq!(value, 17);
23600 sibling_started_rx
23601 .recv_timeout(Duration::from_secs(2))
23602 .expect("the sibling must run after the owner releases its counted slot");
23603 release_sibling_tx.send(()).unwrap();
23604 futures::executor::block_on(sibling).unwrap();
23605 done_tx.send(()).unwrap();
23606 });
23607 done_rx
23608 .recv_timeout(Duration::from_secs(5))
23609 .expect("same-owner sibling selection deadlocked the exact awaited job");
23610 runner.join().unwrap();
23611 }
23612
23613 #[cfg(napi_runtime_os_threads)]
23614 #[test]
23615 fn blocking_dependency_propagates_through_async_join_lineage() {
23616 use std::sync::mpsc;
23617
23618 let (done_tx, done_rx) = mpsc::channel();
23619 let runner = std::thread::spawn(move || {
23620 let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
23621 let owner_controller = Arc::clone(&controller);
23622 let (child_tx, child_rx) = mpsc::channel();
23623 let outer = controller
23624 .try_spawn_blocking(move || {
23625 let async_controller = Arc::clone(&owner_controller);
23626 let async_child = owner_controller
23627 .try_spawn(async move {
23628 async_controller
23629 .try_spawn_blocking(move || {
23630 child_tx.send(()).unwrap();
23631 23usize
23632 })
23633 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
23634 .await
23635 .unwrap()
23636 })
23637 .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
23638 let mut value = None;
23639 {
23640 let mut future = std::pin::pin!(async {
23641 value = Some(async_child.await.unwrap());
23642 });
23643 owner_controller.block_on(future.as_mut());
23644 }
23645 value.unwrap()
23646 })
23647 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23648
23649 child_rx
23650 .recv_timeout(Duration::from_secs(2))
23651 .expect("the async descendant's exact blocking dependency must inherit the owner lane");
23652 assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
23653 controller.shutdown().unwrap();
23654 done_tx.send(()).unwrap();
23655 });
23656 done_rx
23657 .recv_timeout(Duration::from_secs(5))
23658 .expect("blocking dependency lineage deadlocked");
23659 runner.join().unwrap();
23660 }
23661
23662 #[cfg(napi_runtime_os_threads)]
23663 #[test]
23664 fn blocking_dependency_does_not_escape_a_serviced_runnable() {
23665 use std::sync::mpsc;
23666
23667 let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
23668 let outer_controller = Arc::clone(&controller);
23669 let (child_queued_tx, child_queued_rx) = mpsc::channel();
23670 let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
23671 let (release_outer_tx, release_outer_rx) = oneshot::channel();
23672 let outer = controller
23673 .try_spawn_blocking(move || {
23674 let child_controller = Arc::clone(&outer_controller);
23675 let child = outer_controller
23676 .try_spawn(async move {
23677 let blocking = child_controller
23678 .try_spawn_blocking(move || {
23679 unrelated_ran_tx.send(()).unwrap();
23680 31usize
23681 })
23682 .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
23683 child_queued_tx.send(()).unwrap();
23684 blocking.await.unwrap()
23685 })
23686 .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
23687 let mut released = false;
23688 {
23689 let mut future = std::pin::pin!(async {
23690 release_outer_rx.await.unwrap();
23691 released = true;
23692 });
23693 outer_controller.block_on(future.as_mut());
23694 }
23695 assert!(released);
23696 child
23697 })
23698 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23699
23700 child_queued_rx
23701 .recv_timeout(Duration::from_secs(2))
23702 .expect("the cooperative driver must service the unrelated async runnable");
23703 assert!(
23704 unrelated_ran_rx
23705 .recv_timeout(Duration::from_millis(200))
23706 .is_err(),
23707 "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
23708 );
23709
23710 release_outer_tx.send(()).unwrap();
23711 let child = futures::executor::block_on(outer).unwrap();
23712 unrelated_ran_rx
23713 .recv_timeout(Duration::from_secs(2))
23714 .expect("the unrelated blocking job must run after the owner releases its counted slot");
23715 assert_eq!(futures::executor::block_on(child).unwrap(), 31);
23716 controller.shutdown().unwrap();
23717 }
23718
23719 #[cfg(napi_runtime_os_threads)]
23720 #[test]
23721 fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
23722 let parent = Arc::new(TaskDependency::default());
23723 let _context = TaskDependencyGuard::enter(&parent);
23724 let dependency = BlockingDependency {
23725 job: BlockingJobId(u64::MAX),
23726 owner: None,
23727 };
23728 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23729 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23730 receiver,
23731 dependency,
23732 published: PublishedBlockingDependencies::default(),
23733 });
23734 let waker = futures::task::noop_waker();
23735 let mut cx = Context::from_waker(&waker);
23736
23737 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23738 assert_eq!(parent.get_dependency(), Some(dependency));
23739 drop(handle);
23740 assert_eq!(
23741 parent.get_dependency(),
23742 None,
23743 "an abandoned JoinHandle must clear stale dependency lineage"
23744 );
23745 drop(sender);
23746 }
23747
23748 #[cfg(napi_runtime_os_threads)]
23749 #[test]
23750 fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
23751 let owner = BlockingOwnerToken {
23752 executor_id: u64::MAX - 40,
23753 frame: u64::MAX - 41,
23754 };
23755 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23756 let _context = TaskDependencyGuard::enter(&parent);
23757 let dependency = BlockingDependency {
23758 job: BlockingJobId(u64::MAX - 42),
23759 owner: None,
23760 };
23761 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23762 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23763 receiver,
23764 dependency,
23765 published: PublishedBlockingDependencies::default(),
23766 });
23767 let waker = futures::task::noop_waker();
23768 let mut cx = Context::from_waker(&waker);
23769
23770 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23771 assert_eq!(
23772 parent.get_dependency(),
23773 Some(BlockingDependency {
23774 owner: Some(owner),
23775 ..dependency
23776 }),
23777 "the ambient task owner must enrich an untagged direct blocking dependency"
23778 );
23779 drop(handle);
23780 assert_eq!(
23781 parent.get_dependency(),
23782 None,
23783 "detach cleanup must match the stable job id after owner enrichment"
23784 );
23785 drop(sender);
23786 }
23787
23788 #[cfg(napi_runtime_os_threads)]
23789 #[test]
23790 fn one_poll_preserves_every_observed_blocking_dependency() {
23791 let parent = Arc::new(TaskDependency::default());
23792 let _context = TaskDependencyGuard::enter(&parent);
23793 let first = BlockingDependency {
23794 job: BlockingJobId(u64::MAX - 60),
23795 owner: None,
23796 };
23797 let second = BlockingDependency {
23798 job: BlockingJobId(u64::MAX - 61),
23799 owner: None,
23800 };
23801 let (first_sender, first_receiver) =
23802 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23803 let (second_sender, second_receiver) =
23804 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23805 let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
23806 receiver: first_receiver,
23807 dependency: first,
23808 published: PublishedBlockingDependencies::default(),
23809 });
23810 let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
23811 receiver: second_receiver,
23812 dependency: second,
23813 published: PublishedBlockingDependencies::default(),
23814 });
23815 let waker = futures::task::noop_waker();
23816 let mut cx = Context::from_waker(&waker);
23817
23818 assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
23819 assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
23820 assert_eq!(
23821 parent.get_dependencies(),
23822 vec![first, second],
23823 "publishing a later dependency in one user poll must not replace an earlier dependency"
23824 );
23825
23826 drop(first_handle);
23827 drop(second_handle);
23828 drop(first_sender);
23829 drop(second_sender);
23830 }
23831
23832 #[cfg(napi_runtime_os_threads)]
23833 #[test]
23834 fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
23835 let owner = BlockingOwnerToken {
23836 executor_id: u64::MAX - 900,
23837 frame: u64::MAX - 901,
23838 };
23839 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23840 let _context = TaskDependencyGuard::enter(&parent);
23841 let waker = futures::task::noop_waker();
23842 let mut cx = Context::from_waker(&waker);
23843
23844 let j1 = BlockingDependency {
23845 job: BlockingJobId(u64::MAX - 910),
23846 owner: Some(owner),
23847 };
23848 let j2 = BlockingDependency {
23849 job: BlockingJobId(u64::MAX - 911),
23850 owner: Some(owner),
23851 };
23852
23853 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23854 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23855
23856 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23857 receiver: r1,
23858 dependency: j1,
23859 published: PublishedBlockingDependencies::default(),
23860 });
23861 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23864 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23865 let entries_after_double_poll = parent
23866 .state
23867 .lock()
23868 .unwrap_or_else(std::sync::PoisonError::into_inner)
23869 .current
23870 .len();
23871 drop(h1);
23872
23873 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23874 receiver: r2,
23875 dependency: j2,
23876 published: PublishedBlockingDependencies::default(),
23877 });
23878 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23879
23880 assert_eq!(
23881 entries_after_double_poll, 1,
23882 "a repeat Pending poll must supersede the handle's own previous publication"
23883 );
23884 let selected = parent
23885 .select_for_owner(owner)
23886 .expect("a live dependency must be selectable");
23887 assert_eq!(
23888 selected.publication.dependency.job, j2.job,
23889 "a DETACHED job must not be selected for owner lending ahead of the live one"
23890 );
23891 drop((s1, s2));
23892 }
23893
23894 #[cfg(napi_runtime_os_threads)]
23895 #[test]
23896 fn handle_polled_under_a_nested_frame_retires_both_publications() {
23897 let owner = BlockingOwnerToken {
23898 executor_id: u64::MAX - 920,
23899 frame: u64::MAX - 921,
23900 };
23901 let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23902 let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23903 let waker = futures::task::noop_waker();
23904 let mut cx = Context::from_waker(&waker);
23905
23906 let j1 = BlockingDependency {
23907 job: BlockingJobId(u64::MAX - 930),
23908 owner: Some(owner),
23909 };
23910 let j2 = BlockingDependency {
23911 job: BlockingJobId(u64::MAX - 931),
23912 owner: Some(owner),
23913 };
23914 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23915 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23916 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23917 receiver: r1,
23918 dependency: j1,
23919 published: Default::default(),
23920 });
23921
23922 {
23923 let _outer_ctx = TaskDependencyGuard::enter(&outer);
23924 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23928 {
23929 let _inner_ctx = TaskDependencyGuard::enter(&inner);
23930 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23931 }
23932 drop(h1);
23933 assert!(
23934 outer.get_dependencies().is_empty(),
23935 "detaching must retire the publication made in the outer frame too"
23936 );
23937 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23938 receiver: r2,
23939 dependency: j2,
23940 published: Default::default(),
23941 });
23942 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23943 let selected = outer.select_for_owner(owner).expect("selectable");
23944 assert_eq!(
23945 selected.publication.dependency.job, j2.job,
23946 "a detached job published in an outer frame must not outrank a live one"
23947 );
23948 drop(h2);
23949 }
23950 drop((s1, s2));
23951 }
23952
23953 #[cfg(napi_runtime_os_threads)]
23954 #[test]
23955 fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
23956 const FRAME_COUNT: usize = 64;
23957 let waker = futures::task::noop_waker();
23958 let mut cx = Context::from_waker(&waker);
23959 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23960 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23961 receiver,
23962 dependency: BlockingDependency {
23963 job: BlockingJobId(u64::MAX - 940),
23964 owner: None,
23965 },
23966 published: PublishedBlockingDependencies::default(),
23967 });
23968 let mut frames = Vec::new();
23969 for _ in 0..FRAME_COUNT {
23970 let frame = Arc::new(TaskDependency::default());
23971 frames.push(Arc::downgrade(&frame));
23972 let _context = TaskDependencyGuard::enter(&frame);
23973 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23974 }
23975 let retained = frames
23980 .iter()
23981 .filter(|frame| frame.strong_count() > 0)
23982 .count();
23983 assert_eq!(
23984 retained, 0,
23985 "a completed frame must not be kept alive by a pending handle's publication history"
23986 );
23987 drop(handle);
23988 drop(sender);
23989 }
23990
23991 #[cfg(napi_runtime_os_threads)]
23992 #[test]
23993 fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
23994 let owner = BlockingOwnerToken {
23999 executor_id: u64::MAX - 950,
24000 frame: u64::MAX - 951,
24001 };
24002 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24003 let _context = TaskDependencyGuard::enter(&parent);
24004 let waker = futures::task::noop_waker();
24005 let mut cx = Context::from_waker(&waker);
24006
24007 let j1 = BlockingDependency {
24008 job: BlockingJobId(u64::MAX - 960),
24009 owner: Some(owner),
24010 };
24011 let j2 = BlockingDependency {
24012 job: BlockingJobId(u64::MAX - 961),
24013 owner: Some(owner),
24014 };
24015 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24016 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24017 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
24018 receiver: r1,
24019 dependency: j1,
24020 published: PublishedBlockingDependencies::default(),
24021 });
24022 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
24023 receiver: r2,
24024 dependency: j2,
24025 published: PublishedBlockingDependencies::default(),
24026 });
24027
24028 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24029 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
24030 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24031
24032 let selected = parent
24033 .select_for_owner(owner)
24034 .expect("a live dependency must be selectable");
24035 assert_eq!(
24036 selected.publication.dependency.job, j1.job,
24037 "re-polling a handle must not push its dependency behind one published later"
24038 );
24039 drop((h1, h2, s1, s2));
24040 }
24041
24042 #[cfg(napi_runtime_os_threads)]
24043 #[test]
24044 fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
24045 const FRAME_COUNT: usize = 256;
24052 let waker = futures::task::noop_waker();
24053 let mut cx = Context::from_waker(&waker);
24054 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24055 let mut handle = JoinHandle(JoinHandleInner::Blocking {
24056 receiver,
24057 dependency: BlockingDependency {
24058 job: BlockingJobId(u64::MAX - 970),
24059 owner: None,
24060 },
24061 published: PublishedBlockingDependencies::default(),
24062 });
24063 let mut retained = Vec::new();
24064 for _ in 0..FRAME_COUNT {
24065 let frame = Arc::new(TaskDependency::default());
24066 retained.push(Arc::clone(&frame));
24067 let _context = TaskDependencyGuard::enter(&frame);
24068 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24069 frame.begin_poll();
24071 }
24072 let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
24073 panic!("the handle must still be a blocking handle");
24074 };
24075 let remembered = usize::from(published.first.is_some()) + published.rest.len();
24076 assert!(
24077 remembered <= 2,
24078 "a drained frame must not stay in the publication history, remembered={remembered}"
24079 );
24080 drop(handle);
24081 drop(sender);
24082 }
24083
24084 #[cfg(napi_runtime_os_threads)]
24085 #[test]
24086 fn mass_detach_of_pending_blocking_handles_stays_linear() {
24087 const HANDLE_COUNT: usize = 512;
24098
24099 let parent = Arc::new(TaskDependency::default());
24100 let _context = TaskDependencyGuard::enter(&parent);
24101 let waker = futures::task::noop_waker();
24102 let mut cx = Context::from_waker(&waker);
24103
24104 let mut senders = Vec::with_capacity(HANDLE_COUNT);
24105 let mut handles = Vec::with_capacity(HANDLE_COUNT);
24106 for index in 0..HANDLE_COUNT {
24107 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24108 senders.push(sender);
24109 handles.push(JoinHandle(JoinHandleInner::Blocking {
24110 receiver,
24111 dependency: BlockingDependency {
24112 job: BlockingJobId(u64::MAX - 300_000 - index as u64),
24113 owner: None,
24114 },
24115 published: PublishedBlockingDependencies::default(),
24116 }));
24117 }
24118 for handle in &mut handles {
24119 assert!(Pin::new(handle).poll(&mut cx).is_pending());
24120 }
24121 assert_eq!(
24122 parent
24123 .state
24124 .lock()
24125 .unwrap_or_else(std::sync::PoisonError::into_inner)
24126 .current
24127 .len(),
24128 HANDLE_COUNT,
24129 "every handle must be registered and still pending before the detach"
24130 );
24131
24132 parent.reset_entry_visits();
24133 drop(handles);
24134
24135 assert!(
24136 parent.entry_visits() <= HANDLE_COUNT * 4,
24137 "mass-detach must visit O(n) entries across n handles, visits={}",
24138 parent.entry_visits()
24139 );
24140 assert_eq!(
24141 parent.get_dependency(),
24142 None,
24143 "every detached handle must have retired its own publication"
24144 );
24145 drop(senders);
24146 }
24147
24148 #[cfg(napi_runtime_os_threads)]
24149 #[test]
24150 fn dependency_tls_borrow_is_released_before_reentrant_wake() {
24151 use std::sync::mpsc;
24152
24153 struct ReenterDependencyContext {
24154 dependency: Arc<TaskDependency>,
24155 woke: mpsc::Sender<()>,
24156 }
24157
24158 impl std::task::Wake for ReenterDependencyContext {
24159 fn wake(self: Arc<Self>) {
24160 let _context = TaskDependencyGuard::enter(&self.dependency);
24161 self.woke.send(()).unwrap();
24162 }
24163 }
24164
24165 let parent = Arc::new(TaskDependency::default());
24166 let nested = Arc::new(TaskDependency::default());
24167 let (woke_tx, woke_rx) = mpsc::channel();
24168 let waiter = Waker::from(Arc::new(ReenterDependencyContext {
24169 dependency: Arc::clone(&nested),
24170 woke: woke_tx,
24171 }));
24172 parent.register_waiter(&waiter);
24173 let blocking = BlockingDependency {
24174 job: BlockingJobId(u64::MAX - 62),
24175 owner: None,
24176 };
24177
24178 let _context = TaskDependencyGuard::enter(&parent);
24179 let mut published = PublishedBlockingDependencies::default();
24180 record_blocking_dependency(&mut published, blocking);
24181 woke_rx
24182 .recv_timeout(Duration::from_secs(1))
24183 .expect("dependency notification must permit reentrant TLS context access");
24184
24185 parent.register_waiter(&waiter);
24186 clear_blocking_dependency(published);
24187 woke_rx
24188 .recv_timeout(Duration::from_secs(1))
24189 .expect("dependency clearing must permit reentrant TLS context access");
24190 }
24191
24192 #[cfg(napi_runtime_os_threads)]
24193 #[test]
24194 fn dependency_free_begin_poll_does_not_lock_state() {
24195 use std::sync::mpsc;
24196
24197 let dependency = Arc::new(TaskDependency::default());
24198 let state = dependency.state.lock().unwrap();
24199 let (completed_tx, completed_rx) = mpsc::channel();
24200 let polling_dependency = Arc::clone(&dependency);
24201 let polling = std::thread::spawn(move || {
24202 polling_dependency.begin_poll();
24203 completed_tx.send(()).unwrap();
24204 });
24205
24206 let completed_without_lock = completed_rx
24207 .recv_timeout(Duration::from_millis(200))
24208 .is_ok();
24209 drop(state);
24210 polling.join().unwrap();
24211 assert!(
24212 completed_without_lock,
24213 "a task with no published dependency must skip the dependency-state mutex"
24214 );
24215 }
24216
24217 #[cfg(not(target_family = "wasm"))]
24218 #[test]
24219 fn deep_dependency_liveness_chain_is_iterative() {
24220 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
24221
24222 if std::env::var_os(CHILD_ENV).is_some() {
24223 const DEPTH: usize = 100_000;
24224
24225 let mut links = Vec::with_capacity(DEPTH + 1);
24226 links.push(DependencyLink::root());
24227 for _ in 0..DEPTH {
24228 links.push(DependencyLink::derived(links.last().unwrap()));
24229 }
24230 assert!(links.last().unwrap().is_live());
24231 links[0].cancel();
24232 assert!(!links.last().unwrap().is_live());
24233 return;
24234 }
24235
24236 let output = std::process::Command::new(std::env::current_exe().unwrap())
24237 .arg("--exact")
24238 .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
24239 .arg("--nocapture")
24240 .env(CHILD_ENV, "1")
24241 .output()
24242 .expect("the subprocess test must start");
24243 assert!(
24244 output.status.success(),
24245 "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
24246 output.status.code(),
24247 String::from_utf8_lossy(&output.stdout),
24248 String::from_utf8_lossy(&output.stderr)
24249 );
24250 }
24251
24252 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24253 #[test]
24254 fn task_dependency_set_contains_panicking_waiter() {
24255 struct PanicWake;
24256 impl std::task::Wake for PanicWake {
24257 fn wake(self: Arc<Self>) {
24258 panic!("intentional TaskDependency::set waiter panic");
24259 }
24260 }
24261
24262 let dependency = TaskDependency::default();
24263 let blocking = BlockingDependency {
24264 job: BlockingJobId(u64::MAX),
24265 owner: None,
24266 };
24267 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24268
24269 let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
24270 assert!(
24271 result.is_ok(),
24272 "set notification must not unwind its caller"
24273 );
24274 assert_eq!(
24275 dependency.get_dependency(),
24276 Some(blocking),
24277 "set must commit before notifying its waiter"
24278 );
24279 }
24280
24281 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24282 #[test]
24283 fn task_dependency_clear_contains_panicking_waiter() {
24284 struct PanicWake;
24285 impl std::task::Wake for PanicWake {
24286 fn wake(self: Arc<Self>) {
24287 panic!("intentional TaskDependency::clear waiter panic");
24288 }
24289 }
24290
24291 let dependency = TaskDependency::default();
24292 dependency.set_owned(BlockingDependency {
24293 job: BlockingJobId(u64::MAX),
24294 owner: None,
24295 });
24296 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24297
24298 let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
24299 assert!(
24300 result.is_ok(),
24301 "clear notification must not unwind its caller"
24302 );
24303 assert_eq!(
24304 dependency.get_dependency(),
24305 None,
24306 "clear must commit before notifying its waiter"
24307 );
24308 }
24309
24310 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24311 #[test]
24312 fn task_dependency_clear_if_contains_panicking_waiter() {
24313 struct PanicWake;
24314 impl std::task::Wake for PanicWake {
24315 fn wake(self: Arc<Self>) {
24316 panic!("intentional TaskDependency::clear_if waiter panic");
24317 }
24318 }
24319
24320 let dependency = TaskDependency::default();
24321 let blocking = BlockingDependency {
24322 job: BlockingJobId(u64::MAX),
24323 owner: None,
24324 };
24325 let sequence = dependency.set_owned(blocking);
24326 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24327
24328 let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
24329 assert!(
24330 result.is_ok(),
24331 "retire notification must not unwind its caller"
24332 );
24333 assert_eq!(
24334 dependency.get_dependency(),
24335 None,
24336 "retire must commit before notifying its waiter"
24337 );
24338 }
24339
24340 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24341 #[test]
24342 fn task_dependency_waiter_replacement_contains_panicking_drop() {
24343 use std::sync::mpsc;
24344
24345 const GENERATION: u64 = u64::MAX - 20;
24346
24347 struct PanicOnDropWake {
24348 dropped: mpsc::Sender<Option<u64>>,
24349 }
24350
24351 #[expect(
24352 clippy::manual_noop_waker,
24353 reason = "the test observes the waker payload's Drop"
24354 )]
24355 impl std::task::Wake for PanicOnDropWake {
24356 fn wake(self: Arc<Self>) {}
24357 }
24358
24359 impl Drop for PanicOnDropWake {
24360 fn drop(&mut self) {
24361 self
24362 .dropped
24363 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24364 .unwrap();
24365 panic!("intentional replaced dependency waiter destructor panic");
24366 }
24367 }
24368
24369 let dependency = TaskDependency::new(GENERATION, None);
24370 let (dropped_tx, dropped_rx) = mpsc::channel();
24371 let waiter = Waker::from(Arc::new(PanicOnDropWake {
24372 dropped: dropped_tx,
24373 }));
24374 dependency.register_waiter(&waiter);
24375 drop(waiter);
24376
24377 let replacement = futures::task::noop_waker();
24378 let result = catch_unwind(AssertUnwindSafe(|| {
24379 dependency.register_waiter(&replacement)
24380 }));
24381 assert!(
24382 result.is_ok(),
24383 "replacing a hostile waiter must not unwind registration"
24384 );
24385 assert_eq!(
24386 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24387 Some(GENERATION),
24388 "the replaced waiter must be destroyed under its runtime generation"
24389 );
24390 }
24391
24392 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24393 #[test]
24394 fn task_dependency_retirement_contains_stored_waiter_drop() {
24395 use std::sync::mpsc;
24396
24397 const GENERATION: u64 = u64::MAX - 21;
24398
24399 struct PanicOnDropWake {
24400 dropped: mpsc::Sender<Option<u64>>,
24401 }
24402
24403 #[expect(
24404 clippy::manual_noop_waker,
24405 reason = "the test observes the waker payload's Drop"
24406 )]
24407 impl std::task::Wake for PanicOnDropWake {
24408 fn wake(self: Arc<Self>) {}
24409 }
24410
24411 impl Drop for PanicOnDropWake {
24412 fn drop(&mut self) {
24413 self
24414 .dropped
24415 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24416 .unwrap();
24417 panic!("intentional stored dependency waiter destructor panic");
24418 }
24419 }
24420
24421 let dependency = TaskDependency::new(GENERATION, None);
24422 let (dropped_tx, dropped_rx) = mpsc::channel();
24423 let waiter = Waker::from(Arc::new(PanicOnDropWake {
24424 dropped: dropped_tx,
24425 }));
24426 dependency.register_waiter(&waiter);
24427 drop(waiter);
24428
24429 let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
24430 assert!(
24431 result.is_ok(),
24432 "retiring a hostile stored waiter must not unwind"
24433 );
24434 assert_eq!(
24435 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24436 Some(GENERATION),
24437 "the retained waiter must be destroyed under its runtime generation"
24438 );
24439 }
24440
24441 #[cfg(napi_runtime_os_threads)]
24442 #[test]
24443 fn detaching_task_clears_dependency_waiter_parent_retention() {
24444 struct RetainingWake {
24445 retained_parent: Arc<()>,
24446 }
24447
24448 impl std::task::Wake for RetainingWake {
24449 fn wake(self: Arc<Self>) {
24450 let _ = &self.retained_parent;
24451 }
24452 }
24453
24454 let dependency = Arc::new(TaskDependency::default());
24455 let retained_parent = Arc::new(());
24456 let retained_parent_weak = Arc::downgrade(&retained_parent);
24457 let waiter = Waker::from(Arc::new(RetainingWake {
24458 retained_parent: Arc::clone(&retained_parent),
24459 }));
24460 dependency.register_waiter(&waiter);
24461 drop(waiter);
24462 drop(retained_parent);
24463 assert!(
24464 retained_parent_weak.upgrade().is_some(),
24465 "the stored dependency waiter must retain its parent before detachment"
24466 );
24467
24468 let (runnable, task) = async_task::spawn(
24469 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24470 |_| {},
24471 );
24472 drop(JoinHandle(JoinHandleInner::Task {
24473 task: task.fallible(),
24474 awaiter: None,
24475 dependency: Arc::clone(&dependency),
24476 }));
24477 assert!(
24478 retained_parent_weak.upgrade().is_none(),
24479 "detachment must clear the dependency waiter instead of retaining its parent task"
24480 );
24481 drop(runnable);
24482 }
24483
24484 #[cfg(napi_runtime_os_threads)]
24485 #[test]
24486 fn detaching_task_clears_many_parent_publications_linearly() {
24487 const PUBLICATION_COUNT: usize = 1024;
24488
24489 struct CountWake(Arc<AtomicUsize>);
24490
24491 impl std::task::Wake for CountWake {
24492 fn wake(self: Arc<Self>) {
24493 self.0.fetch_add(1, Ordering::SeqCst);
24494 }
24495 }
24496
24497 fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
24498 dependency
24499 .state
24500 .lock()
24501 .unwrap_or_else(std::sync::PoisonError::into_inner)
24502 .current
24503 .back()
24504 .expect("the sentinel publication must be appended")
24505 .publication
24506 .clone()
24507 }
24508
24509 let owner = BlockingOwnerToken {
24510 executor_id: u64::MAX - 300,
24511 frame: u64::MAX - 301,
24512 };
24513 let other_owner = BlockingOwnerToken {
24514 executor_id: u64::MAX - 302,
24515 frame: u64::MAX - 303,
24516 };
24517 let child = Arc::new(TaskDependency::default());
24518 for index in 0..PUBLICATION_COUNT {
24519 child.set_owned(BlockingDependency {
24520 job: BlockingJobId(1_000_000 + index as u64),
24521 owner: (index % 2 == 0).then_some(owner),
24522 });
24523 }
24524 let child_publications = child.snapshot_all_live();
24525 let source = child_publications[0].clone();
24526
24527 let parent = Arc::new(TaskDependency::default());
24528 let _context = TaskDependencyGuard::enter(&parent);
24529 let (runnable, task) = async_task::spawn(
24530 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24531 |_| {},
24532 );
24533 let mut handle = JoinHandle(JoinHandleInner::Task {
24534 task: task.fallible(),
24535 awaiter: None,
24536 dependency: Arc::clone(&child),
24537 });
24538 let waker = futures::task::noop_waker();
24539 let mut cx = Context::from_waker(&waker);
24540 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24541 assert_eq!(
24542 parent.get_dependencies().len(),
24543 PUBLICATION_COUNT,
24544 "polling the child handle must propagate every current publication"
24545 );
24546
24547 let mut wrong_claim_source = source.clone();
24548 wrong_claim_source.claim = Arc::new(DependencyClaim::new());
24549 parent.set_borrowed(&wrong_claim_source);
24550 let wrong_claim_publication = latest_publication(&parent);
24551
24552 let mut wrong_job_source = source.clone();
24553 wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
24554 parent.set_borrowed(&wrong_job_source);
24555 let wrong_job_publication = latest_publication(&parent);
24556
24557 let wrong_immediate_source = source.derived(None);
24558 parent.set_borrowed(&wrong_immediate_source);
24559 let wrong_immediate_publication = latest_publication(&parent);
24560
24561 let wakes = Arc::new(AtomicUsize::new(0));
24562 parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
24563 parent.reset_entry_visits();
24564 drop(handle);
24565
24566 assert_eq!(
24567 wakes.load(Ordering::SeqCst),
24568 1,
24569 "one batched parent cleanup must emit one waiter notification"
24570 );
24571 assert!(
24572 parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
24573 "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
24574 parent.entry_visits()
24575 );
24576 assert!(
24577 wrong_claim_publication.is_live(),
24578 "claim identity must remain part of the match"
24579 );
24580 assert!(
24581 wrong_job_publication.is_live(),
24582 "job identity must remain part of the match"
24583 );
24584 assert!(
24585 wrong_immediate_publication.is_live(),
24586 "only an exact immediate-parent link may be retired"
24587 );
24588
24589 {
24590 let state = parent
24591 .state
24592 .lock()
24593 .unwrap_or_else(std::sync::PoisonError::into_inner);
24594 assert_eq!(state.current.len(), 3);
24595 assert!(
24602 state
24603 .current
24604 .iter()
24605 .all(|entry| entry.publication.is_live()),
24606 "only the three non-matching sentinels may remain, all still live"
24607 );
24608 assert!(state.unowned_sequences.is_empty());
24609 assert_eq!(state.owner_sequences.len(), 1);
24610 assert_eq!(
24611 state.owner_sequences.get(&owner).map(VecDeque::len),
24612 Some(3)
24613 );
24614 }
24615 assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
24616 assert!(
24617 !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
24618 "cancelled unowned publications must not survive in availability predicates"
24619 );
24620 drop(runnable);
24621 }
24622
24623 #[cfg(not(target_family = "wasm"))]
24624 #[test]
24625 fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
24626 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
24627
24628 if std::env::var_os(CHILD_ENV).is_some() {
24629 struct PanicWake;
24630 impl std::task::Wake for PanicWake {
24631 fn wake(self: Arc<Self>) {
24632 panic!("intentional dependency waker panic");
24633 }
24634 }
24635
24636 struct PanicOnDrop;
24637 impl Drop for PanicOnDrop {
24638 fn drop(&mut self) {
24639 panic!("intentional buffered blocking result destructor panic");
24640 }
24641 }
24642
24643 let parent = Arc::new(TaskDependency::default());
24644 let dependency = BlockingDependency {
24645 job: BlockingJobId(u64::MAX - 1),
24646 owner: None,
24647 };
24648 let sequence = parent.set_owned(dependency);
24652 parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
24653 let _context = TaskDependencyGuard::enter(&parent);
24654 let (sender, receiver) = oneshot::channel();
24655 let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
24656 ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
24657 ));
24658 drop(JoinHandle(JoinHandleInner::Blocking {
24659 receiver,
24660 dependency,
24661 published: PublishedBlockingDependencies {
24662 first: Some(PublishedBlockingDependency {
24663 state: Arc::downgrade(&parent),
24664 sequence,
24665 }),
24666 rest: Vec::new(),
24667 },
24668 }));
24669 assert_eq!(parent.get_dependency(), None);
24670 return;
24671 }
24672
24673 let output = std::process::Command::new(std::env::current_exe().unwrap())
24674 .arg("--exact")
24675 .arg(
24676 "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
24677 )
24678 .arg("--nocapture")
24679 .env(CHILD_ENV, "1")
24680 .output()
24681 .expect("the subprocess test must start");
24682 assert!(
24683 output.status.success(),
24684 "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
24685 output.status.code(),
24686 String::from_utf8_lossy(&output.stdout),
24687 String::from_utf8_lossy(&output.stderr)
24688 );
24689 }
24690
24691 #[cfg(napi_runtime_os_threads)]
24692 #[test]
24693 fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
24694 use std::sync::{Barrier, mpsc};
24703 use std::time::Duration;
24704
24705 let (done_tx, done_rx) = mpsc::channel();
24706 let peak_out = Arc::new(Mutex::new(None));
24707 let peak_slot = Arc::clone(&peak_out);
24708 let runner = std::thread::spawn(move || {
24709 let metrics = Arc::new(RuntimeMetrics::default());
24710 let options = RuntimeOptions {
24711 flavor: RuntimeFlavor::MultiThread,
24712 worker_threads: 4,
24713 max_blocking_tasks: 2,
24714 thread_name_prefix: "rd1-cap".to_string(),
24715 park_deadline: None,
24716 drain_linger: DEFAULT_DRAIN_LINGER,
24717 };
24718 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24719
24720 let holder_count = 2usize; let driver_count = 2usize;
24722
24723 let (held_tx, held_rx) = mpsc::channel();
24725 let release = Arc::new(Barrier::new(holder_count + 1));
24726
24727 let mut holder_handles = Vec::new();
24728 for _ in 0..holder_count {
24729 let held_tx = held_tx.clone();
24730 let release = Arc::clone(&release);
24731 let handle = executor.schedule_blocking(move || {
24732 held_tx.send(()).unwrap();
24733 release.wait();
24734 0usize
24735 });
24736 holder_handles.push(handle);
24737 }
24738 for _ in 0..holder_count {
24740 held_rx.recv().unwrap();
24741 }
24742
24743 let mut driver_tasks = Vec::new();
24746 for _ in 0..driver_count {
24747 let exec = Arc::clone(&executor);
24748 let body = async move {
24749 let inner_exec = Arc::clone(&exec);
24750 let mut inner = std::pin::pin!(async move {
24751 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24752 assert_eq!(v, 5);
24753 });
24754 exec.block_on(inner.as_mut());
24755 };
24756 let scheduler = Arc::clone(&executor);
24757 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
24758 executor.schedule(runnable);
24759 driver_tasks.push(task);
24760 }
24761
24762 std::thread::sleep(Duration::from_millis(300));
24765
24766 release.wait();
24768
24769 for task in driver_tasks {
24770 futures::executor::block_on(task);
24771 }
24772 for handle in holder_handles {
24773 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24774 }
24775
24776 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24777 let _ = done_tx.send(());
24778 });
24779
24780 match done_rx.recv_timeout(Duration::from_secs(10)) {
24781 Ok(()) => runner.join().unwrap(),
24782 Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
24783 }
24784 let peak = peak_out.lock().unwrap().expect("peak captured");
24785 assert!(
24786 peak <= 2,
24787 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24788 );
24789 }
24790
24791 #[cfg(napi_runtime_os_threads)]
24792 #[test]
24793 fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
24794 use std::sync::{Barrier, mpsc};
24810 use std::time::Duration;
24811
24812 let (done_tx, done_rx) = mpsc::channel();
24813 let peak_out = Arc::new(Mutex::new(None));
24814 let peak_slot = Arc::clone(&peak_out);
24815 let runner = std::thread::spawn(move || {
24816 let metrics = Arc::new(RuntimeMetrics::default());
24817 let options = RuntimeOptions {
24818 flavor: RuntimeFlavor::MultiThread,
24819 worker_threads: 2,
24820 max_blocking_tasks: 2,
24821 thread_name_prefix: "rd1-owner-runnable".to_string(),
24822 park_deadline: None,
24823 drain_linger: DEFAULT_DRAIN_LINGER,
24824 };
24825 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24826
24827 let saturated = Arc::new(Barrier::new(2));
24828 let release = Arc::new(Barrier::new(2));
24829
24830 let h2_saturated = Arc::clone(&saturated);
24832 let h2_release = Arc::clone(&release);
24833 let h2 = executor.schedule_blocking(move || {
24834 h2_saturated.wait();
24835 h2_release.wait();
24836 0usize
24837 });
24838
24839 let o_exec = Arc::clone(&executor);
24842 let o_saturated = Arc::clone(&saturated);
24843 let o = executor.schedule_blocking(move || {
24844 o_saturated.wait(); let r_exec = Arc::clone(&o_exec);
24847 let body = async move {
24848 let inner_exec = Arc::clone(&r_exec);
24849 let mut inner = std::pin::pin!(async move {
24850 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24851 assert_eq!(v, 5);
24852 });
24853 r_exec.block_on(inner.as_mut());
24854 };
24855 let sched = Arc::clone(&o_exec);
24856 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
24857 o_exec.schedule(runnable);
24858
24859 let mut wait_r = std::pin::pin!(async move {
24860 task.await;
24861 });
24862 o_exec.block_on(wait_r.as_mut());
24863 1usize
24864 });
24865
24866 std::thread::sleep(Duration::from_millis(300));
24869 release.wait();
24871
24872 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24873 assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
24874
24875 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24876 let _ = done_tx.send(());
24877 });
24878
24879 match done_rx.recv_timeout(Duration::from_secs(10)) {
24880 Ok(()) => runner.join().unwrap(),
24881 Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
24882 }
24883 let peak = peak_out.lock().unwrap().expect("peak captured");
24884 assert!(
24885 peak <= 2,
24886 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24887 );
24888 }
24889
24890 #[cfg(napi_runtime_os_threads)]
24891 #[test]
24892 fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
24893 use std::sync::{Barrier, mpsc};
24902
24903 let (done_tx, done_rx) = mpsc::channel();
24904 let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
24905 let order_read = Arc::clone(&order_out);
24906 let runner = std::thread::spawn(move || {
24907 let metrics = Arc::new(RuntimeMetrics::default());
24908 let options = RuntimeOptions {
24909 flavor: RuntimeFlavor::MultiThread,
24910 worker_threads: 2,
24911 max_blocking_tasks: 2,
24912 thread_name_prefix: "rd1-unrelated".to_string(),
24913 park_deadline: None,
24914 drain_linger: DEFAULT_DRAIN_LINGER,
24915 };
24916 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24917
24918 let (held_tx, held_rx) = mpsc::channel();
24921 let u_ready = Arc::new(Barrier::new(2)); let h_release = Arc::new(Barrier::new(2)); let h_held = held_tx.clone();
24926 let h_rel = Arc::clone(&h_release);
24927 let h = executor.schedule_blocking(move || {
24928 h_held.send(()).unwrap();
24929 h_rel.wait();
24930 0usize
24931 });
24932
24933 let o_exec = Arc::clone(&executor);
24936 let o_ready = Arc::clone(&u_ready);
24937 let o_order = Arc::clone(&order_out);
24938 let o = executor.schedule_blocking(move || {
24939 held_tx.send(()).unwrap();
24940 o_ready.wait(); let inner_exec = Arc::clone(&o_exec);
24942 let d_order = Arc::clone(&o_order);
24943 let mut inner = std::pin::pin!(async move {
24944 let v = inner_exec
24945 .schedule_blocking(move || {
24946 d_order.lock().unwrap().push("D");
24947 5usize
24948 })
24949 .await
24950 .unwrap();
24951 assert_eq!(v, 5);
24952 });
24953 o_exec.block_on(inner.as_mut());
24954 1usize
24955 });
24956
24957 held_rx.recv().unwrap();
24959 held_rx.recv().unwrap();
24960
24961 let u_order = Arc::clone(&order_out);
24963 let u = executor.schedule_blocking(move || {
24964 u_order.lock().unwrap().push("U");
24965 2usize
24966 });
24967
24968 u_ready.wait();
24970
24971 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24973 assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
24974
24975 h_release.wait();
24977 assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
24978
24979 let _ = done_tx.send(());
24980 });
24981
24982 match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
24983 Ok(()) => runner.join().unwrap(),
24984 Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
24985 }
24986 let order = order_read.lock().unwrap().clone();
24987 assert_eq!(
24988 order,
24989 vec!["D", "U"],
24990 "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
24991 );
24992 }
24993
24994 #[cfg(napi_runtime_os_threads)]
24995 #[test]
24996 fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
24997 use std::sync::{Barrier, mpsc};
24998
24999 let opts = RuntimeOptions {
25000 flavor: RuntimeFlavor::MultiThread,
25001 worker_threads: 2,
25002 max_blocking_tasks: 2,
25003 thread_name_prefix: "rd1-scope".to_string(),
25004 park_deadline: None,
25005 drain_linger: DEFAULT_DRAIN_LINGER,
25006 };
25007 let exec1 =
25008 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25009 let exec2 =
25010 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25011 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25012
25013 let holder_count = opts.max_blocking_tasks;
25014 let (held_tx, held_rx) = mpsc::channel();
25015 let release = Arc::new(Barrier::new(holder_count + 1));
25016 let mut holder_handles = Vec::new();
25017 for _ in 0..holder_count {
25018 let held_tx = held_tx.clone();
25019 let release = Arc::clone(&release);
25020 holder_handles.push(exec2.schedule_blocking(move || {
25021 held_tx.send(()).unwrap();
25022 release.wait();
25023 0usize
25024 }));
25025 }
25026 for _ in 0..holder_count {
25027 held_rx.recv().unwrap();
25028 }
25029 assert_eq!(
25030 exec2.active_blocking.load(Ordering::Acquire),
25031 opts.max_blocking_tasks
25032 );
25033
25034 let exact = BlockingJobId(u64::MAX);
25035 let ran = Arc::new(AtomicBool::new(false));
25036 let ran_job = Arc::clone(&ran);
25037 exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
25038 id: exact,
25039 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25040 });
25041 let dependency = TaskDependency::default();
25042
25043 let token1 = exec1.fresh_owner_token();
25044 {
25045 let _owner = BlockingOwnerGuard::enter(Some(token1));
25046 dependency.set_owned(BlockingDependency {
25047 job: exact,
25048 owner: Some(token1),
25049 });
25050 assert!(
25051 !exec2.try_owned_blocking_over_cap(&dependency),
25052 "a foreign-executor ambient token must fail the executor gate"
25053 );
25054 }
25055
25056 let token2 = exec2.fresh_owner_token();
25057 {
25058 let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
25059 let _owner = BlockingOwnerGuard::enter(Some(token2));
25060 dependency.set_owned(BlockingDependency {
25061 job: BlockingJobId(u64::MAX - 1),
25062 owner: Some(token2),
25063 });
25064 assert!(
25065 !exec2.try_owned_blocking_over_cap(&dependency),
25066 "a foreign or unrelated dependency id must fail the exact-job gate"
25067 );
25068 dependency.set_owned(BlockingDependency {
25069 job: BlockingJobId(exact.0 - 2),
25070 owner: Some(token2),
25071 });
25072 assert!(
25073 !exec2.try_owned_blocking_over_cap(&dependency),
25074 "a different job id must not consume the queued dependency"
25075 );
25076 dependency.set_owned(BlockingDependency {
25077 job: exact,
25078 owner: Some(token2),
25079 });
25080 assert!(
25081 exec2.try_owned_blocking_over_cap(&dependency),
25082 "the exact awaited job must run over cap"
25083 );
25084 }
25085 assert!(
25086 ran.load(Ordering::SeqCst),
25087 "the exact dependency must have run"
25088 );
25089
25090 release.wait();
25091 for handle in holder_handles {
25092 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
25093 }
25094 }
25095
25096 #[cfg(napi_runtime_os_threads)]
25097 #[test]
25098 fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
25099 let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
25100 let first_owner = executor.fresh_owner_token();
25101 let second_owner = executor.fresh_owner_token();
25102 let _first_owner =
25103 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
25104 let _second_owner =
25105 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
25106 let exact = BlockingJobId(u64::MAX - 30);
25107 let dependency = TaskDependency::default();
25108 dependency.set_owned(BlockingDependency {
25109 job: exact,
25110 owner: None,
25111 });
25112 let ran = Arc::new(AtomicBool::new(false));
25113 let ran_job = Arc::clone(&ran);
25114 executor
25115 .blocking_queue
25116 .lock()
25117 .unwrap()
25118 .push(QueuedBlocking {
25119 id: exact,
25120 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25121 });
25122
25123 assert!(
25124 !executor.try_owned_blocking_over_cap(&dependency),
25125 "an untagged driver must not choose between multiple active owner frames"
25126 );
25127 assert!(!ran.load(Ordering::SeqCst));
25128 let queued = executor.blocking_queue.lock().unwrap().remove(exact);
25129 assert!(
25130 queued.is_some(),
25131 "ambiguous ownership must leave the exact job queued"
25132 );
25133 }
25134
25135 #[cfg(napi_runtime_os_threads)]
25136 #[test]
25137 fn owner_lending_claims_live_dependency_not_stale_snapshot() {
25138 let executor = multi_thread_executor(2, None, "live-parked-dependency");
25139 let owner = executor.fresh_owner_token();
25140 let dependency = TaskDependency::new(u64::MAX, Some(owner));
25141 let stale = BlockingJobId(u64::MAX - 10);
25142 let live = BlockingJobId(u64::MAX - 9);
25143 dependency.set_owned(BlockingDependency {
25144 job: stale,
25145 owner: Some(owner),
25146 });
25147 dependency.begin_poll();
25148 dependency.set_owned(BlockingDependency {
25149 job: live,
25150 owner: Some(owner),
25151 });
25152
25153 let stale_ran = Arc::new(AtomicBool::new(false));
25154 let stale_job_ran = Arc::clone(&stale_ran);
25155 let live_ran = Arc::new(AtomicBool::new(false));
25156 let live_job_ran = Arc::clone(&live_ran);
25157 {
25158 let mut queue = executor
25159 .blocking_queue
25160 .lock()
25161 .unwrap_or_else(std::sync::PoisonError::into_inner);
25162 queue.push(QueuedBlocking {
25163 id: stale,
25164 run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
25165 });
25166 queue.push(QueuedBlocking {
25167 id: live,
25168 run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
25169 });
25170 }
25171
25172 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25173 let _owner = BlockingOwnerGuard::enter(Some(owner));
25174 assert!(
25175 executor.try_owned_blocking_over_cap(&dependency),
25176 "the owner lane must claim the currently published dependency"
25177 );
25178 assert!(
25179 live_ran.load(Ordering::SeqCst),
25180 "the live dependency must run"
25181 );
25182 assert!(
25183 !stale_ran.load(Ordering::SeqCst),
25184 "the stale registered id must remain queued"
25185 );
25186 let remaining = executor
25187 .blocking_queue
25188 .lock()
25189 .unwrap_or_else(std::sync::PoisonError::into_inner)
25190 .jobs
25191 .keys()
25192 .copied()
25193 .collect::<Vec<_>>();
25194 assert_eq!(remaining, vec![stale]);
25195 }
25196
25197 #[cfg(napi_runtime_os_threads)]
25198 #[test]
25199 fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
25200 let executor = multi_thread_executor(2, None, "cancelled-child-publication");
25201 let owner = executor.fresh_owner_token();
25202 let child = TaskDependency::new_task(u64::MAX);
25203 let parent = TaskDependency::new(u64::MAX, Some(owner));
25204 let exact = BlockingDependency {
25205 job: BlockingJobId(u64::MAX - 31),
25206 owner: Some(owner),
25207 };
25208
25209 child.set_owned(exact);
25210 let stale = child
25211 .get()
25212 .expect("the child must publish its direct dependency");
25213 child.begin_poll();
25214 parent.set_borrowed(&stale);
25215
25216 let ran = Arc::new(AtomicBool::new(false));
25217 let ran_job = Arc::clone(&ran);
25218 executor
25219 .blocking_queue
25220 .lock()
25221 .unwrap()
25222 .push(QueuedBlocking {
25223 id: exact.job,
25224 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25225 });
25226 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25227 let _owner = BlockingOwnerGuard::enter(Some(owner));
25228 assert!(
25229 !executor.try_owned_blocking_over_cap(&parent),
25230 "a publication copied before child cancellation must remain cancelled after parent install"
25231 );
25232 assert!(!ran.load(Ordering::SeqCst));
25233
25234 child.set_owned(exact);
25235 parent.set_borrowed(
25236 &child
25237 .get()
25238 .expect("the replacement publication must be live"),
25239 );
25240 assert!(
25241 executor.try_owned_blocking_over_cap(&parent),
25242 "a fresh child publication must still authorize its exact queued job"
25243 );
25244 assert!(ran.load(Ordering::SeqCst));
25245 }
25246
25247 #[cfg(napi_runtime_os_threads)]
25248 #[test]
25249 fn intermediate_repoll_does_not_cancel_child_source_claim() {
25250 let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
25251 let owner = executor.fresh_owner_token();
25252 let child = TaskDependency::new_task(u64::MAX);
25253 let intermediate = TaskDependency::new_task(u64::MAX);
25254 let parent = TaskDependency::new(u64::MAX, Some(owner));
25255 let exact = BlockingDependency {
25256 job: BlockingJobId(u64::MAX - 43),
25257 owner: Some(owner),
25258 };
25259
25260 child.set_owned(exact);
25261 intermediate.set_borrowed(&child.get().expect("the child source must be live"));
25262 parent.set_borrowed(
25263 &intermediate
25264 .get()
25265 .expect("the intermediate publication must be live"),
25266 );
25267
25268 intermediate.begin_poll();
25269 assert!(
25270 child.get().is_some(),
25271 "withdrawing an intermediate hop must not cancel the child's source claim"
25272 );
25273 assert!(
25274 parent.get().is_none(),
25275 "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
25276 );
25277
25278 intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
25279 parent.set_borrowed(
25280 &intermediate
25281 .get()
25282 .expect("the intermediate must republish the still-live source"),
25283 );
25284 let ran = Arc::new(AtomicBool::new(false));
25285 let ran_job = Arc::clone(&ran);
25286 executor
25287 .blocking_queue
25288 .lock()
25289 .unwrap()
25290 .push(QueuedBlocking {
25291 id: exact.job,
25292 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25293 });
25294
25295 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25296 let _owner = BlockingOwnerGuard::enter(Some(owner));
25297 assert!(
25298 executor.try_owned_blocking_over_cap(&parent),
25299 "a fresh chain through the repolled intermediate must retain the exact source claim"
25300 );
25301 assert!(ran.load(Ordering::SeqCst));
25302 }
25303
25304 #[cfg(napi_runtime_os_threads)]
25305 #[test]
25306 fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
25307 use std::sync::mpsc;
25308
25309 let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
25310 let owner = executor.fresh_owner_token();
25311 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25312 let child = Arc::new(TaskDependency::new_task(u64::MAX));
25313 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25314 let exact = BlockingDependency {
25315 job: BlockingJobId(u64::MAX - 44),
25316 owner: Some(owner),
25317 };
25318 child.set_owned(exact);
25319 parent.set_borrowed(&child.get().expect("the child source must be live"));
25320
25321 let ran = Arc::new(AtomicBool::new(false));
25322 let ran_job = Arc::clone(&ran);
25323 executor
25324 .blocking_queue
25325 .lock()
25326 .unwrap()
25327 .push(QueuedBlocking {
25328 id: exact.job,
25329 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25330 });
25331
25332 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
25333 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25334 let claimant_executor = Arc::clone(&executor);
25335 let claimant_parent = Arc::clone(&parent);
25336 let claimant = std::thread::spawn(move || {
25337 DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
25338 *slot.borrow_mut() = Some(Box::new(move || {
25339 claim_entered_tx.send(()).unwrap();
25340 release_claim_rx.recv().unwrap();
25341 }));
25342 });
25343 let _owner = BlockingOwnerGuard::enter(Some(owner));
25344 claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
25345 });
25346 claim_entered_rx
25347 .recv_timeout(Duration::from_secs(2))
25348 .expect("the exact claimant must hold the shared transition lock");
25349
25350 let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
25351 let withdrawing_child = Arc::clone(&child);
25352 let withdrawal = std::thread::spawn(move || {
25353 withdrawing_child.begin_poll();
25354 withdrawn_tx.send(()).unwrap();
25355 });
25356 let withdrew_before_claim = withdrawn_rx
25357 .recv_timeout(Duration::from_millis(200))
25358 .is_ok();
25359 release_claim_tx.send(()).unwrap();
25360
25361 assert!(
25362 claimant.join().unwrap(),
25363 "the claim that linearized first must execute the exact job"
25364 );
25365 withdrawal.join().unwrap();
25366 assert!(
25367 !withdrew_before_claim,
25368 "source withdrawal must not interleave after claim validation but before claim commit"
25369 );
25370 assert!(ran.load(Ordering::SeqCst));
25371 }
25372
25373 #[cfg(napi_runtime_os_threads)]
25374 #[test]
25375 fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
25376 struct PendThenReady {
25397 polls: usize,
25398 }
25399 impl Future for PendThenReady {
25400 type Output = ();
25401 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25402 self.polls += 1;
25403 if self.polls >= 2 {
25404 Poll::Ready(())
25405 } else {
25406 cx.waker().wake_by_ref();
25410 Poll::Pending
25411 }
25412 }
25413 }
25414
25415 fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
25418 let scheduler = Arc::clone(target);
25419 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
25420 target.push_fifo_raw(runnable);
25421 task.detach();
25422 }
25423
25424 let opts = RuntimeOptions {
25425 flavor: RuntimeFlavor::MultiThread,
25426 worker_threads: 2,
25427 max_blocking_tasks: 2,
25428 thread_name_prefix: "rd-onpool-scope".to_string(),
25429 park_deadline: None,
25430 drain_linger: DEFAULT_DRAIN_LINGER,
25431 };
25432 let exec1 =
25433 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25434 let exec2 =
25435 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25436 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25437
25438 let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
25440
25441 enqueue_runnable(&exec2);
25445 {
25446 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25447 exec2.block_on(probe.as_mut());
25448 }
25449 assert_eq!(
25450 exec2.metrics.runnable_polls.load(Ordering::Relaxed),
25451 0,
25452 "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
25453 );
25454 assert_eq!(
25455 exec2.injector.len(),
25456 1,
25457 "the foreign call must leave exec2's runnable untouched on its queue"
25458 );
25459
25460 enqueue_runnable(&exec1);
25464 {
25465 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25466 exec1.block_on(probe.as_mut());
25467 }
25468 assert_eq!(
25469 exec1.metrics.runnable_polls.load(Ordering::Relaxed),
25470 1,
25471 "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
25472 );
25473 assert!(
25474 exec1.injector.is_empty(),
25475 "the cooperative branch must have drained exec1's queued runnable"
25476 );
25477
25478 while exec2.steal_one().is_some() {}
25481 }
25482
25483 #[cfg(napi_runtime_os_threads)]
25484 #[test]
25485 fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
25486 use std::sync::mpsc;
25490 use std::time::Duration;
25491
25492 const PRODUCER_COUNT: usize = 8;
25493
25494 fn schedule_pool_workload(
25495 executor: &Arc<MultiThreadExecutor>,
25496 tx: &mpsc::Sender<Option<usize>>,
25497 count: usize,
25498 ) {
25499 for i in 0..count {
25500 let tx = tx.clone();
25501 let scheduler = Arc::clone(executor);
25502 let (runnable, task) =
25503 async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
25504 scheduler.schedule(r);
25505 });
25506 executor.schedule(runnable);
25507 task.detach();
25508 }
25509 let tx = tx.clone();
25510 let scheduler = Arc::clone(executor);
25511 let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
25512 scheduler.schedule(r)
25513 });
25514 executor.schedule(runnable);
25515 task.detach();
25516 }
25517
25518 let expected_sum: usize = (0..PRODUCER_COUNT).sum();
25519 let (done_tx, done_rx) = mpsc::sync_channel(1);
25520 let runner = std::thread::spawn(move || {
25521 let options = RuntimeOptions {
25522 flavor: RuntimeFlavor::MultiThread,
25523 worker_threads: 1,
25524 max_blocking_tasks: 1,
25525 thread_name_prefix: "rd2-pre-fix".to_string(),
25526 park_deadline: None,
25527 drain_linger: DEFAULT_DRAIN_LINGER,
25528 }
25529 .validate()
25530 .expect("production MultiThread options must provide a reserve lane");
25531 let executor =
25532 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25533
25534 let (tx, rx) = mpsc::channel::<Option<usize>>();
25535 let (started_tx, started_rx) = mpsc::sync_channel(1);
25536 let handle = executor.schedule_blocking(move || {
25537 started_tx.send(()).unwrap();
25538 let mut sum = 0usize;
25539 while let Ok(msg) = rx.recv() {
25540 match msg {
25541 Some(value) => sum += value,
25542 None => break,
25543 }
25544 }
25545 sum
25546 });
25547 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
25548 schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
25549 let sum = futures::executor::block_on(handle).unwrap();
25550 done_tx.send(sum).unwrap();
25551 });
25552
25553 match done_rx.recv_timeout(Duration::from_secs(10)) {
25554 Ok(sum) => {
25555 runner.join().unwrap();
25556 assert_eq!(sum, expected_sum);
25557 }
25558 Err(error) => {
25559 panic!("a saturated blocking lane starved the async producers that feed it ({error})")
25560 }
25561 }
25562 }
25563
25564 #[test]
25565 fn current_thread_blocking_work_completes_inline() {
25566 let metrics = Arc::new(RuntimeMetrics::default());
25567 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
25568 CurrentThreadExecutor::new(Arc::clone(&metrics)),
25569 )));
25570 let registration = backend.work.try_register_work().unwrap();
25571
25572 let value =
25573 futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
25574 .expect("blocking task should complete");
25575
25576 assert_eq!(value, 7);
25577 assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
25578 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25579 }
25580
25581 #[cfg(napi_runtime_os_threads)]
25582 #[test]
25583 fn current_thread_blocking_work_obeys_single_lane_across_callers() {
25584 use std::sync::mpsc;
25585
25586 let controller = Arc::new(current_thread_controller(
25587 "current-thread-blocking-admission",
25588 ));
25589 let backend = controller.backend();
25590 let (first_started_tx, first_started_rx) = mpsc::channel();
25591 let (release_first_tx, release_first_rx) = mpsc::channel();
25592 let first_controller = Arc::clone(&controller);
25593 let first = std::thread::spawn(move || {
25594 let handle = first_controller
25595 .try_spawn_blocking(move || {
25596 first_started_tx.send(()).unwrap();
25597 release_first_rx.recv().unwrap();
25598 1usize
25599 })
25600 .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
25601 futures::executor::block_on(handle).unwrap()
25602 });
25603 first_started_rx
25604 .recv_timeout(Duration::from_secs(1))
25605 .unwrap();
25606
25607 let (second_started_tx, second_started_rx) = mpsc::channel();
25608 let second_controller = Arc::clone(&controller);
25609 let second = std::thread::spawn(move || {
25610 let handle = second_controller
25611 .try_spawn_blocking(move || {
25612 second_started_tx.send(()).unwrap();
25613 2usize
25614 })
25615 .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
25616 futures::executor::block_on(handle).unwrap()
25617 });
25618
25619 wait_until("both CurrentThread blocking calls to be accepted", || {
25620 backend
25621 .work
25622 .state
25623 .lock()
25624 .unwrap_or_else(std::sync::PoisonError::into_inner)
25625 .active
25626 == 2
25627 });
25628 assert!(
25629 second_started_rx
25630 .recv_timeout(Duration::from_millis(100))
25631 .is_err(),
25632 "CurrentThread must not execute two blocking closures concurrently"
25633 );
25634
25635 release_first_tx.send(()).unwrap();
25636 second_started_rx
25637 .recv_timeout(Duration::from_secs(1))
25638 .expect("the waiting blocking call must acquire the released lane");
25639 assert_eq!(first.join().unwrap(), 1);
25640 assert_eq!(second.join().unwrap(), 2);
25641 assert_eq!(
25642 controller
25643 .metrics
25644 .blocking_tasks_started
25645 .load(Ordering::Relaxed),
25646 2
25647 );
25648 assert_eq!(
25649 controller
25650 .metrics
25651 .blocking_tasks_completed
25652 .load(Ordering::Relaxed),
25653 2
25654 );
25655 assert_eq!(
25656 controller
25657 .metrics
25658 .active_blocking_tasks
25659 .load(Ordering::Relaxed),
25660 0
25661 );
25662 assert_eq!(
25663 controller
25664 .metrics
25665 .max_active_blocking_tasks
25666 .load(Ordering::Relaxed),
25667 1,
25668 "the public active-lane metric must remain within CurrentThread's reported capacity"
25669 );
25670 controller.shutdown().unwrap();
25671 }
25672
25673 #[cfg(napi_runtime_os_threads)]
25674 #[test]
25675 fn current_thread_nested_blocking_reuses_the_active_lane() {
25676 use std::sync::mpsc;
25677
25678 let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
25679 let (done_tx, done_rx) = mpsc::channel();
25680 let runner_controller = Arc::clone(&controller);
25681 let runner = std::thread::spawn(move || {
25682 let nested_controller = Arc::clone(&runner_controller);
25683 let outer = runner_controller
25684 .try_spawn_blocking(move || {
25685 let inner = nested_controller
25686 .try_spawn_blocking(|| 41usize)
25687 .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
25688 futures::executor::block_on(inner).unwrap() + 1
25689 })
25690 .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
25691 done_tx
25692 .send(futures::executor::block_on(outer).unwrap())
25693 .unwrap();
25694 });
25695
25696 assert_eq!(
25697 done_rx
25698 .recv_timeout(Duration::from_secs(2))
25699 .expect("a nested CurrentThread blocking call must not wait on its own lane"),
25700 42
25701 );
25702 runner.join().unwrap();
25703 assert_eq!(
25704 controller
25705 .metrics
25706 .blocking_tasks_started
25707 .load(Ordering::Relaxed),
25708 2
25709 );
25710 assert_eq!(
25711 controller
25712 .metrics
25713 .blocking_tasks_completed
25714 .load(Ordering::Relaxed),
25715 2
25716 );
25717 assert_eq!(
25718 controller
25719 .metrics
25720 .active_blocking_tasks
25721 .load(Ordering::Relaxed),
25722 0
25723 );
25724 assert_eq!(
25725 controller
25726 .metrics
25727 .max_active_blocking_tasks
25728 .load(Ordering::Relaxed),
25729 1,
25730 "nested work borrows the outer lane instead of reporting a second physical lane"
25731 );
25732 controller.shutdown().unwrap();
25733 }
25734
25735 #[cfg(napi_runtime_os_threads)]
25736 #[test]
25737 fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
25738 use std::sync::mpsc;
25739
25740 struct HotFuture {
25741 stop: Arc<AtomicBool>,
25742 }
25743
25744 impl Future for HotFuture {
25745 type Output = ();
25746
25747 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25748 if self.stop.load(Ordering::SeqCst) {
25749 Poll::Ready(())
25750 } else {
25751 cx.waker().wake_by_ref();
25752 Poll::Pending
25753 }
25754 }
25755 }
25756
25757 let stop = Arc::new(AtomicBool::new(false));
25758 let (ready_tx, ready_rx) = mpsc::channel();
25759 let (exact_tx, exact_rx) = mpsc::channel();
25760 let (done_tx, done_rx) = mpsc::channel();
25761 let runner_stop = Arc::clone(&stop);
25762 let runner = std::thread::spawn(move || {
25763 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25764 RuntimeMetrics::default(),
25765 )));
25766 let owner = executor.fresh_owner_token();
25767 let job = BlockingJobId(u64::MAX - 70);
25768 let (result_tx, result_rx) = oneshot::channel();
25769 {
25770 let mut state = executor
25771 .blocking_admission
25772 .state
25773 .lock()
25774 .unwrap_or_else(std::sync::PoisonError::into_inner);
25775 state.owner = Some(owner);
25776 let exact_stop = Arc::clone(&runner_stop);
25777 state.queue.push(QueuedBlocking {
25778 id: job,
25779 run: Box::new(move || {
25780 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
25781 let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
25782 }),
25783 });
25784 }
25785 let exact = JoinHandle(JoinHandleInner::Blocking {
25786 receiver: result_rx,
25787 dependency: BlockingDependency {
25788 job,
25789 owner: Some(owner),
25790 },
25791 published: PublishedBlockingDependencies::default(),
25792 });
25793 let _owner = executor.enter_blocking_owner(owner, false);
25794
25795 let scheduler = Arc::clone(&executor);
25796 let hot_stop = Arc::clone(&runner_stop);
25797 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
25798 scheduler.schedule(runnable);
25799 });
25800 executor.schedule(runnable);
25801 ready_tx.send(()).unwrap();
25802
25803 let mut value = None;
25804 {
25805 let mut future = std::pin::pin!(async {
25806 value = Some(exact.await.unwrap());
25807 hot_task.await;
25808 });
25809 assert_eq!(
25810 executor.block_on(future.as_mut()),
25811 BlockOnOutcome::Completed
25812 );
25813 }
25814 executor
25815 .blocking_admission
25816 .state
25817 .lock()
25818 .unwrap_or_else(std::sync::PoisonError::into_inner)
25819 .owner = None;
25820 done_tx.send(value.unwrap()).unwrap();
25821 });
25822
25823 ready_rx
25824 .recv_timeout(Duration::from_secs(2))
25825 .expect("the fairness driver must start");
25826 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
25827 stop.store(true, Ordering::SeqCst);
25828 assert_eq!(
25829 done_rx
25830 .recv_timeout(Duration::from_secs(2))
25831 .expect("the CurrentThread driver must retire"),
25832 41
25833 );
25834 runner.join().unwrap();
25835 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
25836 assert_eq!(
25837 exact_before_stop,
25838 Some(true),
25839 "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
25840 );
25841 }
25842
25843 #[cfg(napi_runtime_os_threads)]
25844 #[test]
25845 fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
25846 use std::sync::mpsc;
25847
25848 let controller = Arc::new(current_thread_controller(
25849 "current-thread-cross-driver-blocking",
25850 ));
25851 let backend = controller.backend();
25852 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25853 panic!("configured CurrentThread backend must create a CurrentThread executor");
25854 };
25855 let executor = Arc::clone(executor);
25856 drop(backend);
25857
25858 let (release_driver_tx, release_driver_rx) = oneshot::channel();
25859 let (driver_started_tx, driver_started_rx) = mpsc::channel();
25860 let driver_controller = Arc::clone(&controller);
25861 let driver = std::thread::spawn(move || {
25862 driver_started_tx.send(std::thread::current().id()).unwrap();
25863 block_on_with_controller(&driver_controller, async {
25864 release_driver_rx
25865 .await
25866 .expect("driver release sender must remain alive");
25867 });
25868 });
25869 let driver_thread = driver_started_rx
25870 .recv_timeout(Duration::from_secs(1))
25871 .unwrap();
25872 wait_until("the secondary CurrentThread driver to park", || {
25873 executor
25874 .stop
25875 .parkers
25876 .lock()
25877 .unwrap_or_else(std::sync::PoisonError::into_inner)
25878 .iter()
25879 .filter_map(Weak::upgrade)
25880 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25881 });
25882
25883 let (child_polled_tx, child_polled_rx) = mpsc::channel();
25884 let (release_child_tx, release_child_rx) = mpsc::channel();
25885 let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
25886 let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
25887 let (owner_done_tx, owner_done_rx) = mpsc::channel();
25888 let owner_controller = Arc::clone(&controller);
25889 let owner = std::thread::spawn(move || {
25890 let owner_thread = std::thread::current().id();
25891 let await_controller = Arc::clone(&owner_controller);
25892 let child_controller = Arc::clone(&owner_controller);
25893 let outer = owner_controller
25894 .try_spawn_blocking(move || {
25895 let spawn_controller = Arc::clone(&child_controller);
25896 let child = spawn_controller
25897 .try_spawn(async move {
25898 child_polled_tx.send(std::thread::current().id()).unwrap();
25899 release_child_rx.recv().unwrap();
25900 child_controller
25901 .try_spawn_blocking(move || {
25902 blocking_ran_tx.send(std::thread::current().id()).unwrap();
25903 41usize
25904 })
25905 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
25906 .await
25907 .unwrap()
25908 + 1
25909 })
25910 .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
25911
25912 assert_eq!(
25913 child_polled_rx
25914 .recv_timeout(Duration::from_secs(1))
25915 .unwrap(),
25916 driver_thread,
25917 "the secondary driver must own the child poll that reaches blocking admission"
25918 );
25919 owner_awaiting_tx.send(owner_thread).unwrap();
25920 block_on_with_controller(&await_controller, child).unwrap()
25921 })
25922 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
25923 owner_done_tx
25924 .send(futures::executor::block_on(outer))
25925 .unwrap();
25926 });
25927
25928 let owner_thread = owner_awaiting_rx
25929 .recv_timeout(Duration::from_secs(1))
25930 .unwrap();
25931 wait_until(
25932 "the blocking owner to park while awaiting the cross-driver child",
25933 || {
25934 executor
25935 .stop
25936 .parkers
25937 .lock()
25938 .unwrap_or_else(std::sync::PoisonError::into_inner)
25939 .iter()
25940 .filter_map(Weak::upgrade)
25941 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25942 },
25943 );
25944 release_child_tx.send(()).unwrap();
25945
25946 assert_eq!(
25947 blocking_ran_rx
25948 .recv_timeout(Duration::from_secs(2))
25949 .unwrap(),
25950 owner_thread,
25951 "the exact descendant must be serviced by the lane-owning driver"
25952 );
25953 assert_eq!(
25954 owner_done_rx
25955 .recv_timeout(Duration::from_secs(2))
25956 .expect("cross-driver blocking dependency must not deadlock")
25957 .unwrap(),
25958 42
25959 );
25960 release_driver_tx.send(()).unwrap();
25961 owner.join().unwrap();
25962 driver.join().unwrap();
25963 controller.shutdown().unwrap();
25964 assert_eq!(
25965 controller
25966 .metrics
25967 .blocking_tasks_started
25968 .load(Ordering::Relaxed),
25969 2
25970 );
25971 assert_eq!(
25972 controller
25973 .metrics
25974 .blocking_tasks_completed
25975 .load(Ordering::Relaxed),
25976 2
25977 );
25978 assert_eq!(
25979 controller
25980 .metrics
25981 .active_blocking_tasks
25982 .load(Ordering::Relaxed),
25983 0
25984 );
25985 assert_eq!(
25986 controller
25987 .metrics
25988 .max_active_blocking_tasks
25989 .load(Ordering::Relaxed),
25990 1,
25991 "cross-driver dependency service must reuse the owner's physical lane"
25992 );
25993 }
25994
25995 #[cfg(napi_runtime_os_threads)]
25996 #[test]
25997 fn current_thread_blocking_admission_rechecks_stop_under_lock() {
25998 use std::sync::mpsc;
25999
26000 let controller = Arc::new(current_thread_controller(
26001 "current-thread-blocking-stop-race",
26002 ));
26003 let backend = controller.backend();
26004 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26005 panic!("configured CurrentThread backend must create a CurrentThread executor");
26006 };
26007 let executor = Arc::clone(executor);
26008 drop(backend);
26009
26010 let ran = Arc::new(AtomicBool::new(false));
26011 let blocking_ran = Arc::clone(&ran);
26012 let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
26013 let (release_admission_tx, release_admission_rx) = mpsc::channel();
26014 let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
26015 let submit_controller = Arc::clone(&controller);
26016 let submitter = std::thread::spawn(move || {
26017 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26018 *slot.borrow_mut() = Some(Box::new(move || {
26019 admission_locked_tx.send(()).unwrap();
26020 release_admission_rx.recv().unwrap();
26021 }));
26022 });
26023 let handle = submit_controller
26024 .try_spawn_blocking(move || {
26025 blocking_ran.store(true, Ordering::SeqCst);
26026 })
26027 .unwrap_or_else(|_| {
26028 panic!("the submission linearized before shutdown and must be accepted")
26029 });
26030 blocking_result_tx
26031 .send(futures::executor::block_on(handle))
26032 .unwrap();
26033 });
26034 admission_locked_rx
26035 .recv_timeout(Duration::from_secs(2))
26036 .expect("the blocking submitter must pause while holding the admission lock");
26037
26038 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
26039 let shutdown_controller = Arc::clone(&controller);
26040 let shutdown = std::thread::spawn(move || {
26041 shutdown_result_tx
26042 .send(shutdown_controller.shutdown())
26043 .unwrap();
26044 });
26045 wait_until("CurrentThread shutdown to publish generation stop", || {
26046 executor.stop.is_stopping()
26047 });
26048 release_admission_tx.send(()).unwrap();
26049
26050 let blocking_error = blocking_result_rx
26051 .recv_timeout(Duration::from_secs(2))
26052 .expect("the admitted blocking handle must resolve")
26053 .expect_err("stop publication must reject the blocking lane claim");
26054 assert_eq!(
26055 blocking_error.to_string(),
26056 "the async runtime stopped before the blocking task could start"
26057 );
26058 assert!(
26059 !ran.load(Ordering::SeqCst),
26060 "blocking work must not start after stop publication"
26061 );
26062 shutdown_result_rx
26063 .recv_timeout(Duration::from_secs(2))
26064 .expect("shutdown must finish after the rejected admission retires")
26065 .unwrap();
26066 submitter.join().unwrap();
26067 shutdown.join().unwrap();
26068 }
26069
26070 #[cfg(napi_runtime_os_threads)]
26071 #[test]
26072 fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
26073 use std::sync::mpsc;
26074
26075 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26076 RuntimeMetrics::default(),
26077 )));
26078 let ran = Arc::new(AtomicBool::new(false));
26079 let job_ran = Arc::clone(&ran);
26080 {
26081 let mut state = executor
26082 .blocking_admission
26083 .state
26084 .lock()
26085 .unwrap_or_else(std::sync::PoisonError::into_inner);
26086 state.queue.push(QueuedBlocking {
26087 id: BlockingJobId(u64::MAX - 71),
26088 run: Box::new(move || {
26089 job_ran.store(true, Ordering::SeqCst);
26090 }),
26091 });
26092 }
26093
26094 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26095 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26096 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26097 let claim_executor = Arc::clone(&executor);
26098 let claimant = std::thread::spawn(move || {
26099 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26100 *slot.borrow_mut() = Some(Box::new(move || {
26101 claim_locked_tx.send(()).unwrap();
26102 release_claim_rx.recv().unwrap();
26103 }));
26104 });
26105 claim_result_tx
26106 .send(claim_executor.run_one_blocking())
26107 .unwrap();
26108 });
26109 claim_locked_rx
26110 .recv_timeout(Duration::from_secs(2))
26111 .expect("the FIFO claimant must pause while holding the admission lock");
26112
26113 executor.stop.begin_shutdown();
26114 release_claim_tx.send(()).unwrap();
26115 assert!(
26116 !claim_result_rx
26117 .recv_timeout(Duration::from_secs(2))
26118 .expect("the stopped FIFO claim must finish"),
26119 "stop publication must reject the queued FIFO claim"
26120 );
26121 claimant.join().unwrap();
26122 assert!(
26123 !ran.load(Ordering::SeqCst),
26124 "queued FIFO work must not start after stop publication"
26125 );
26126 assert!(
26127 !executor
26128 .blocking_admission
26129 .state
26130 .lock()
26131 .unwrap_or_else(std::sync::PoisonError::into_inner)
26132 .queue
26133 .is_empty(),
26134 "the rejected FIFO job must remain queued for shutdown cancellation"
26135 );
26136
26137 executor.close_blocking_admission();
26138 assert!(
26139 executor
26140 .blocking_admission
26141 .state
26142 .lock()
26143 .unwrap_or_else(std::sync::PoisonError::into_inner)
26144 .queue
26145 .is_empty()
26146 );
26147 }
26148
26149 #[cfg(napi_runtime_os_threads)]
26150 #[test]
26151 fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26152 use std::sync::mpsc;
26153
26154 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26155 RuntimeMetrics::default(),
26156 )));
26157 let ran = Arc::new(AtomicBool::new(false));
26158 let job_ran = Arc::clone(&ran);
26159 {
26160 let mut state = executor
26161 .blocking_admission
26162 .state
26163 .lock()
26164 .unwrap_or_else(std::sync::PoisonError::into_inner);
26165 state.queue.push(QueuedBlocking {
26166 id: BlockingJobId(u64::MAX - 73),
26167 run: Box::new(move || {
26168 job_ran.store(true, Ordering::SeqCst);
26169 }),
26170 });
26171 }
26172
26173 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26174 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26175 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26176 let claim_executor = Arc::clone(&executor);
26177 let claimant = std::thread::spawn(move || {
26178 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26179 *slot.borrow_mut() = Some(Box::new(move || {
26180 claim_locked_tx.send(()).unwrap();
26181 release_claim_rx.recv().unwrap();
26182 }));
26183 });
26184 claim_result_tx
26185 .send(claim_executor.run_one_blocking())
26186 .unwrap();
26187 });
26188 claim_locked_rx
26189 .recv_timeout(Duration::from_secs(2))
26190 .expect("the FIFO claimant must pause while holding the admission lock");
26191
26192 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26193 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26194 let cancelling_executor = Arc::clone(&executor);
26195 let cancellation = std::thread::spawn(move || {
26196 let cancelling = cancelling_executor
26197 .begin_failed_dispatch_cancellation()
26198 .expect("the test must publish terminal dispatch cancellation");
26199 cancellation_published_tx.send(()).unwrap();
26200 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26201 cancellation_done_tx.send(()).unwrap();
26202 });
26203 cancellation_published_rx
26204 .recv_timeout(Duration::from_secs(2))
26205 .expect("terminal dispatch cancellation must publish before queue cancellation");
26206 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26207 assert!(
26208 matches!(
26209 cancellation_done_rx.try_recv(),
26210 Err(mpsc::TryRecvError::Empty)
26211 ),
26212 "terminal cancellation must wait for the blocking-admission lock"
26213 );
26214
26215 release_claim_tx.send(()).unwrap();
26216 assert!(
26217 !claim_result_rx
26218 .recv_timeout(Duration::from_secs(2))
26219 .expect("the cancelled FIFO claim must finish"),
26220 "terminal cancellation publication must reject the queued FIFO claim"
26221 );
26222 cancellation_done_rx
26223 .recv_timeout(Duration::from_secs(2))
26224 .expect("terminal cancellation must drain the rejected FIFO job");
26225 claimant.join().unwrap();
26226 cancellation.join().unwrap();
26227
26228 assert!(
26229 !ran.load(Ordering::SeqCst),
26230 "terminally cancelled FIFO work must not start"
26231 );
26232 assert!(
26233 executor
26234 .blocking_admission
26235 .state
26236 .lock()
26237 .unwrap_or_else(std::sync::PoisonError::into_inner)
26238 .queue
26239 .is_empty(),
26240 "terminal cancellation must drain the rejected FIFO job"
26241 );
26242 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26243 }
26244
26245 #[cfg(napi_runtime_os_threads)]
26246 #[test]
26247 fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
26248 use std::sync::mpsc;
26249
26250 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26251 RuntimeMetrics::default(),
26252 )));
26253 let owner = executor.fresh_owner_token();
26254 let job = BlockingJobId(u64::MAX - 72);
26255 let ran = Arc::new(AtomicBool::new(false));
26256 let job_ran = Arc::clone(&ran);
26257 {
26258 let mut state = executor
26259 .blocking_admission
26260 .state
26261 .lock()
26262 .unwrap_or_else(std::sync::PoisonError::into_inner);
26263 state.owner = Some(owner);
26264 state.queue.push(QueuedBlocking {
26265 id: job,
26266 run: Box::new(move || {
26267 job_ran.store(true, Ordering::SeqCst);
26268 }),
26269 });
26270 }
26271 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26272 dependency.set_owned(BlockingDependency {
26273 job,
26274 owner: Some(owner),
26275 });
26276
26277 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26278 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26279 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26280 let claim_executor = Arc::clone(&executor);
26281 let claim_dependency = Arc::clone(&dependency);
26282 let claimant = std::thread::spawn(move || {
26283 let _owner = claim_executor.enter_blocking_owner(owner, false);
26284 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26285 *slot.borrow_mut() = Some(Box::new(move || {
26286 claim_locked_tx.send(()).unwrap();
26287 release_claim_rx.recv().unwrap();
26288 }));
26289 });
26290 claim_result_tx
26291 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26292 .unwrap();
26293 });
26294 claim_locked_rx
26295 .recv_timeout(Duration::from_secs(2))
26296 .expect("the owner claimant must pause while holding the admission lock");
26297
26298 executor.stop.begin_shutdown();
26299 release_claim_tx.send(()).unwrap();
26300 assert!(
26301 !claim_result_rx
26302 .recv_timeout(Duration::from_secs(2))
26303 .expect("the stopped owner claim must finish"),
26304 "stop publication must reject the exact-owner claim"
26305 );
26306 claimant.join().unwrap();
26307 assert!(
26308 !ran.load(Ordering::SeqCst),
26309 "queued exact-owner work must not start after stop publication"
26310 );
26311 assert!(
26312 !executor
26313 .blocking_admission
26314 .state
26315 .lock()
26316 .unwrap_or_else(std::sync::PoisonError::into_inner)
26317 .queue
26318 .is_empty(),
26319 "the rejected exact-owner job must remain queued for shutdown cancellation"
26320 );
26321
26322 executor
26323 .blocking_admission
26324 .state
26325 .lock()
26326 .unwrap_or_else(std::sync::PoisonError::into_inner)
26327 .owner = None;
26328 executor.close_blocking_admission();
26329 assert!(
26330 executor
26331 .blocking_admission
26332 .state
26333 .lock()
26334 .unwrap_or_else(std::sync::PoisonError::into_inner)
26335 .queue
26336 .is_empty()
26337 );
26338 }
26339
26340 #[cfg(napi_runtime_os_threads)]
26341 #[test]
26342 fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26343 use std::sync::mpsc;
26344
26345 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26346 RuntimeMetrics::default(),
26347 )));
26348 let owner = executor.fresh_owner_token();
26349 let job = BlockingJobId(u64::MAX - 74);
26350 let ran = Arc::new(AtomicBool::new(false));
26351 let job_ran = Arc::clone(&ran);
26352 {
26353 let mut state = executor
26354 .blocking_admission
26355 .state
26356 .lock()
26357 .unwrap_or_else(std::sync::PoisonError::into_inner);
26358 state.owner = Some(owner);
26359 state.queue.push(QueuedBlocking {
26360 id: job,
26361 run: Box::new(move || {
26362 job_ran.store(true, Ordering::SeqCst);
26363 }),
26364 });
26365 }
26366 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26367 dependency.set_owned(BlockingDependency {
26368 job,
26369 owner: Some(owner),
26370 });
26371
26372 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26373 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26374 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26375 let claim_executor = Arc::clone(&executor);
26376 let claim_dependency = Arc::clone(&dependency);
26377 let claimant = std::thread::spawn(move || {
26378 let _owner = claim_executor.enter_blocking_owner(owner, false);
26379 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26380 *slot.borrow_mut() = Some(Box::new(move || {
26381 claim_locked_tx.send(()).unwrap();
26382 release_claim_rx.recv().unwrap();
26383 }));
26384 });
26385 claim_result_tx
26386 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26387 .unwrap();
26388 });
26389 claim_locked_rx
26390 .recv_timeout(Duration::from_secs(2))
26391 .expect("the owner claimant must pause while holding the admission lock");
26392
26393 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26394 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26395 let cancelling_executor = Arc::clone(&executor);
26396 let cancellation = std::thread::spawn(move || {
26397 let cancelling = cancelling_executor
26398 .begin_failed_dispatch_cancellation()
26399 .expect("the test must publish terminal dispatch cancellation");
26400 cancellation_published_tx.send(()).unwrap();
26401 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26402 cancellation_done_tx.send(()).unwrap();
26403 });
26404 cancellation_published_rx
26405 .recv_timeout(Duration::from_secs(2))
26406 .expect("terminal dispatch cancellation must publish before queue cancellation");
26407 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26408 assert!(
26409 matches!(
26410 cancellation_done_rx.try_recv(),
26411 Err(mpsc::TryRecvError::Empty)
26412 ),
26413 "terminal cancellation must wait for the blocking-admission lock"
26414 );
26415
26416 release_claim_tx.send(()).unwrap();
26417 assert!(
26418 !claim_result_rx
26419 .recv_timeout(Duration::from_secs(2))
26420 .expect("the cancelled owner claim must finish"),
26421 "terminal cancellation publication must reject the exact-owner claim"
26422 );
26423 cancellation_done_rx
26424 .recv_timeout(Duration::from_secs(2))
26425 .expect("terminal cancellation must drain the rejected exact-owner job");
26426 claimant.join().unwrap();
26427 cancellation.join().unwrap();
26428
26429 assert!(
26430 !ran.load(Ordering::SeqCst),
26431 "terminally cancelled exact-owner work must not start"
26432 );
26433 assert!(
26434 executor
26435 .blocking_admission
26436 .state
26437 .lock()
26438 .unwrap_or_else(std::sync::PoisonError::into_inner)
26439 .queue
26440 .is_empty(),
26441 "terminal cancellation must drain the rejected exact-owner job"
26442 );
26443 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26444 }
26445
26446 #[cfg(napi_runtime_os_threads)]
26447 #[test]
26448 fn current_thread_shutdown_cancels_waiting_blocking_work() {
26449 use std::sync::mpsc;
26450
26451 struct DropSignal(mpsc::Sender<()>);
26452
26453 impl Drop for DropSignal {
26454 fn drop(&mut self) {
26455 let _ = self.0.send(());
26456 }
26457 }
26458
26459 let controller = Arc::new(current_thread_controller(
26460 "current-thread-blocking-shutdown",
26461 ));
26462 let backend = controller.backend();
26463 let (first_started_tx, first_started_rx) = mpsc::channel();
26464 let (release_first_tx, release_first_rx) = mpsc::channel();
26465 let first_controller = Arc::clone(&controller);
26466 let first = std::thread::spawn(move || {
26467 let handle = first_controller
26468 .try_spawn_blocking(move || {
26469 first_started_tx.send(()).unwrap();
26470 release_first_rx.recv().unwrap();
26471 })
26472 .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
26473 futures::executor::block_on(handle)
26474 });
26475 first_started_rx
26476 .recv_timeout(Duration::from_secs(1))
26477 .unwrap();
26478
26479 let waiting_ran = Arc::new(AtomicBool::new(false));
26480 let waiting_ran_task = Arc::clone(&waiting_ran);
26481 let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
26482 let waiting_drop = DropSignal(waiting_dropped_tx);
26483 let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
26484 let waiting_controller = Arc::clone(&controller);
26485 let waiting = std::thread::spawn(move || {
26486 let handle = waiting_controller
26487 .try_spawn_blocking(move || {
26488 let _waiting_drop = waiting_drop;
26489 waiting_ran_task.store(true, Ordering::SeqCst);
26490 })
26491 .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
26492 waiting_result_tx
26493 .send(futures::executor::block_on(handle))
26494 .unwrap();
26495 });
26496 wait_until("both CurrentThread blocking calls to be accepted", || {
26497 backend
26498 .work
26499 .state
26500 .lock()
26501 .unwrap_or_else(std::sync::PoisonError::into_inner)
26502 .active
26503 == 2
26504 });
26505
26506 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26507 let shutdown_controller = Arc::clone(&controller);
26508 let shutdown = std::thread::spawn(move || {
26509 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26510 });
26511 waiting_dropped_rx
26512 .recv_timeout(Duration::from_secs(1))
26513 .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
26514 assert!(
26515 !waiting_ran.load(Ordering::SeqCst),
26516 "waiting blocking work must not start"
26517 );
26518 assert!(
26519 waiting_result_rx
26520 .recv_timeout(Duration::from_secs(1))
26521 .unwrap()
26522 .is_err(),
26523 "the waiting blocking handle must resolve as cancelled"
26524 );
26525 assert!(
26526 shutdown_rx
26527 .recv_timeout(Duration::from_millis(100))
26528 .is_err(),
26529 "shutdown must still wait for the blocking closure that already owns the lane"
26530 );
26531
26532 release_first_tx.send(()).unwrap();
26533 first.join().unwrap().unwrap();
26534 waiting.join().unwrap();
26535 shutdown_rx
26536 .recv_timeout(Duration::from_secs(1))
26537 .unwrap()
26538 .unwrap();
26539 shutdown.join().unwrap();
26540 assert_eq!(
26541 controller
26542 .metrics
26543 .blocking_tasks_started
26544 .load(Ordering::Relaxed),
26545 1
26546 );
26547 assert_eq!(
26548 controller
26549 .metrics
26550 .blocking_tasks_completed
26551 .load(Ordering::Relaxed),
26552 1
26553 );
26554 assert_eq!(
26555 controller
26556 .metrics
26557 .active_blocking_tasks
26558 .load(Ordering::Relaxed),
26559 0
26560 );
26561 }
26562
26563 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
26564 #[test]
26565 fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
26566 use std::{panic::panic_any, sync::mpsc};
26567
26568 struct ReentrantPayload {
26569 controller: Arc<RuntimeController>,
26570 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26571 }
26572
26573 impl Drop for ReentrantPayload {
26574 fn drop(&mut self) {
26575 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26576 let shutdown = self.controller.shutdown();
26577 self.result.send((generation, shutdown)).unwrap();
26578 }
26579 }
26580
26581 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26582
26583 impl Drop for PanicPayloadOnDrop {
26584 fn drop(&mut self) {
26585 panic_any(self.0.take().expect("the result must panic only once"));
26586 }
26587 }
26588
26589 let controller = Arc::new(current_thread_controller(
26590 "current-thread-panic-payload-generation",
26591 ));
26592 let backend = controller.backend();
26593 let generation = backend.generation();
26594 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26595 panic!("configured CurrentThread backend must create a CurrentThread executor");
26596 };
26597 let executor = Arc::clone(executor);
26598 drop(backend);
26599
26600 let owner = executor.fresh_owner_token();
26603 executor
26604 .blocking_admission
26605 .state
26606 .lock()
26607 .unwrap_or_else(std::sync::PoisonError::into_inner)
26608 .owner = Some(owner);
26609 let (result_tx, result_rx) = mpsc::channel();
26610 let payload_controller = Arc::clone(&controller);
26611 let handle = controller
26612 .try_spawn_blocking(move || {
26613 PanicPayloadOnDrop(Some(ReentrantPayload {
26614 controller: payload_controller,
26615 result: result_tx,
26616 }))
26617 })
26618 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26619 drop(handle);
26620 executor
26621 .blocking_admission
26622 .state
26623 .lock()
26624 .unwrap_or_else(std::sync::PoisonError::into_inner)
26625 .owner = None;
26626 let dispatch = current_thread_dispatch(&executor);
26627 controller.drive_current_thread_dispatch(dispatch, false);
26628
26629 let (observed_generation, shutdown) = result_rx
26630 .recv_timeout(Duration::from_secs(1))
26631 .expect("the caught result panic payload must be destroyed in the scheduler turn");
26632 assert_eq!(observed_generation, Some(generation));
26633 let error = shutdown.expect_err("payload destruction is still work in the active generation");
26634 assert!(error.to_string().contains("work running on that runtime"));
26635 controller.shutdown().unwrap();
26636 }
26637
26638 #[cfg(panic = "unwind")]
26639 #[test]
26640 fn current_thread_detached_blocking_result_drop_is_contained() {
26641 use std::sync::mpsc;
26642
26643 struct PanicOnDrop(mpsc::Sender<()>);
26644
26645 impl Drop for PanicOnDrop {
26646 fn drop(&mut self) {
26647 self.0.send(()).unwrap();
26648 panic!("intentional CurrentThread blocking result destructor panic");
26649 }
26650 }
26651
26652 let controller = current_thread_controller("current-thread-blocking-result-drop");
26653 let (dropped_tx, dropped_rx) = mpsc::channel();
26654 let handle = controller
26655 .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
26656 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26657
26658 let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26659 dropped_rx
26660 .recv_timeout(Duration::from_secs(1))
26661 .expect("dropping the detached handle must reclaim its blocking result");
26662 assert!(
26663 result.is_ok(),
26664 "the blocking result destructor panic must be contained"
26665 );
26666 controller.shutdown().unwrap();
26667 }
26668
26669 #[test]
26674 fn validate_rejects_zero_worker_threads() {
26675 let error = RuntimeOptions {
26676 flavor: RuntimeFlavor::MultiThread,
26677 worker_threads: 0,
26678 max_blocking_tasks: 1,
26679 thread_name_prefix: "rd8".to_string(),
26680 park_deadline: None,
26681 drain_linger: DEFAULT_DRAIN_LINGER,
26682 }
26683 .validate()
26684 .expect_err("worker_threads == 0 must be rejected");
26685 assert_eq!(
26686 error.to_string(),
26687 "worker_threads must be greater than zero"
26688 );
26689 }
26690
26691 #[test]
26692 fn validate_rejects_zero_max_blocking_tasks() {
26693 let error = RuntimeOptions {
26694 flavor: RuntimeFlavor::MultiThread,
26695 worker_threads: 2,
26696 max_blocking_tasks: 0,
26697 thread_name_prefix: "rd8".to_string(),
26698 park_deadline: None,
26699 drain_linger: DEFAULT_DRAIN_LINGER,
26700 }
26701 .validate()
26702 .expect_err("max_blocking_tasks == 0 must be rejected");
26703 assert_eq!(
26704 error.to_string(),
26705 "max_blocking_tasks must be greater than zero"
26706 );
26707 }
26708
26709 #[test]
26710 fn validate_current_thread_coerces_worker_threads_to_one() {
26711 let validated = RuntimeOptions {
26714 flavor: RuntimeFlavor::CurrentThread,
26715 worker_threads: 8,
26716 max_blocking_tasks: 8,
26717 thread_name_prefix: "rd8".to_string(),
26718 park_deadline: None,
26719 drain_linger: DEFAULT_DRAIN_LINGER,
26720 }
26721 .validate()
26722 .expect("CurrentThread options must validate");
26723 assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
26724 assert_eq!(validated.worker_threads, 1);
26725 assert_eq!(validated.max_blocking_tasks, 1);
26726 }
26727
26728 #[cfg(napi_runtime_os_threads)]
26732 #[test]
26733 fn validate_reserves_one_worker_from_blocking_admission() {
26734 let validated = RuntimeOptions {
26735 flavor: RuntimeFlavor::MultiThread,
26736 worker_threads: 2,
26737 max_blocking_tasks: 8,
26738 thread_name_prefix: "rd8".to_string(),
26739 park_deadline: None,
26740 drain_linger: DEFAULT_DRAIN_LINGER,
26741 }
26742 .validate()
26743 .expect("MultiThread options must validate");
26744 assert_eq!(validated.worker_threads, 2);
26745 assert_eq!(validated.max_blocking_tasks, 1);
26746 }
26747
26748 #[cfg(napi_runtime_os_threads)]
26749 #[test]
26750 fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
26751 let validated = RuntimeOptions {
26752 flavor: RuntimeFlavor::MultiThread,
26753 worker_threads: 1,
26754 max_blocking_tasks: 8,
26755 thread_name_prefix: "rd8-minimum".to_string(),
26756 park_deadline: None,
26757 drain_linger: DEFAULT_DRAIN_LINGER,
26758 }
26759 .validate()
26760 .expect("MultiThread options must validate");
26761 assert_eq!(validated.worker_threads, 2);
26762 assert_eq!(validated.max_blocking_tasks, 1);
26763 }
26764
26765 #[cfg(napi_runtime_os_threads)]
26766 #[test]
26767 fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
26768 let simulated_32_bit = RuntimeOptions {
26769 flavor: RuntimeFlavor::MultiThread,
26770 worker_threads: 256,
26771 max_blocking_tasks: 256,
26772 thread_name_prefix: "rd8-rayon-cap-32".to_string(),
26773 park_deadline: None,
26774 drain_linger: DEFAULT_DRAIN_LINGER,
26775 }
26776 .validate_with_rayon_max_threads(Some(255))
26777 .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
26778 assert_eq!(simulated_32_bit.worker_threads, 255);
26779 assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
26780
26781 let rayon_max_threads = max_num_threads();
26782 let requested_threads = rayon_max_threads
26783 .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
26784 .checked_add(1)
26785 .expect("supported worker counts must leave room for an oversized request");
26786 let controller = RuntimeController::new();
26787 controller
26788 .configure(RuntimeOptions {
26789 flavor: RuntimeFlavor::MultiThread,
26790 worker_threads: requested_threads,
26791 max_blocking_tasks: requested_threads,
26792 thread_name_prefix: "rd8-rayon-cap".to_string(),
26793 park_deadline: None,
26794 drain_linger: DEFAULT_DRAIN_LINGER,
26795 })
26796 .expect("an oversized request must clamp to the production worker ceiling");
26797
26798 let reported = controller.options();
26799 let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
26800 assert_eq!(reported.worker_threads, expected_workers);
26801 assert_eq!(
26802 reported.max_blocking_tasks,
26803 expected_workers - 1,
26804 "the blocking cap must reserve a lane from the physically realizable worker count"
26805 );
26806 }
26807
26808 #[cfg(napi_runtime_os_threads)]
26809 #[test]
26810 fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
26811 let extreme = Duration::from_micros(u64::MAX);
26818 let validated = RuntimeOptions {
26819 flavor: RuntimeFlavor::MultiThread,
26820 worker_threads: 2,
26821 max_blocking_tasks: 1,
26822 thread_name_prefix: "linger-clamp".to_string(),
26823 park_deadline: None,
26824 drain_linger: extreme,
26825 }
26826 .validate_with_rayon_max_threads(Some(2))
26827 .expect("an extreme linger budget must clamp, not fail validation");
26828 assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
26829
26830 let controller = RuntimeController::new();
26831 controller
26832 .configure_partial(RuntimeOptionsPatch {
26833 drain_linger: Some(extreme),
26834 ..RuntimeOptionsPatch::default()
26835 })
26836 .expect("an extreme linger patch must clamp, not fail validation");
26837 assert_eq!(
26838 controller.options().drain_linger,
26839 MAX_DRAIN_LINGER,
26840 "introspection must report the clamped budget, not the requested one"
26841 );
26842 controller
26843 .configure_partial(RuntimeOptionsPatch {
26844 drain_linger: Some(Duration::ZERO),
26845 ..RuntimeOptionsPatch::default()
26846 })
26847 .expect("ZERO (lingering disabled) must remain a valid budget");
26848 assert_eq!(controller.options().drain_linger, Duration::ZERO);
26849
26850 let metrics = Arc::new(RuntimeMetrics::default());
26851 let unvalidated = RuntimeOptions {
26852 flavor: RuntimeFlavor::MultiThread,
26853 worker_threads: 2,
26854 max_blocking_tasks: 2,
26855 thread_name_prefix: "linger-clamp-exec".to_string(),
26856 park_deadline: None,
26857 drain_linger: extreme,
26858 };
26859 let executor = MultiThreadExecutor::new(&unvalidated, metrics)
26860 .expect("the executor must construct with an extreme unvalidated budget");
26861 assert_eq!(
26862 executor.drain_linger,
26863 Some(MAX_DRAIN_LINGER),
26864 "the constructor's defensive clamp must bound unvalidated budgets"
26865 );
26866
26867 let metrics = Arc::new(RuntimeMetrics::default());
26868 let disabled = RuntimeOptions {
26869 flavor: RuntimeFlavor::MultiThread,
26870 worker_threads: 2,
26871 max_blocking_tasks: 2,
26872 thread_name_prefix: "linger-clamp-off".to_string(),
26873 park_deadline: None,
26874 drain_linger: Duration::ZERO,
26875 };
26876 let executor = MultiThreadExecutor::new(&disabled, metrics)
26877 .expect("the executor must construct with lingering disabled");
26878 assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
26879 }
26880
26881 #[cfg(not(target_family = "wasm"))]
26882 #[test]
26883 fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
26884 let controller = RuntimeController::new();
26885 let options = controller.options();
26886 assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
26887 assert_eq!(
26888 options.max_blocking_tasks,
26889 options.worker_threads.saturating_sub(1).max(1),
26890 "the unconfigured Rust API must use the same validated reserve as the binding"
26891 );
26892 }
26893
26894 #[cfg(napi_runtime_os_threads)]
26895 #[test]
26896 fn concurrent_partial_configuration_merges_from_latest_committed_options() {
26897 use std::{sync::mpsc, time::Duration};
26898
26899 let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
26900 let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
26901 let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
26902 let first_controller = Arc::clone(&controller);
26903 let first = std::thread::spawn(move || {
26904 first_controller
26905 .configure_partial_inner(
26906 RuntimeOptionsPatch {
26907 max_blocking_tasks: Some(3),
26908 ..RuntimeOptionsPatch::default()
26909 },
26910 |candidate| {
26911 assert_eq!(
26912 (candidate.worker_threads, candidate.max_blocking_tasks),
26913 (8, 3)
26914 );
26915 first_merged_tx
26916 .send(())
26917 .expect("the race coordinator must still be listening");
26918 release_first_rx
26919 .recv_timeout(Duration::from_secs(5))
26920 .expect("the first partial configuration must be released");
26921 },
26922 )
26923 .expect("the first partial configuration must commit");
26924 });
26925 first_merged_rx
26926 .recv_timeout(Duration::from_secs(5))
26927 .expect("the first partial configuration must hold the controller lock after merging");
26928
26929 let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
26930 let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
26931 let second_controller = Arc::clone(&controller);
26932 let second = std::thread::spawn(move || {
26933 second_started_tx
26934 .send(())
26935 .expect("the race coordinator must still be listening");
26936 second_controller
26937 .configure_partial_inner(
26938 RuntimeOptionsPatch {
26939 worker_threads: Some(6),
26940 ..RuntimeOptionsPatch::default()
26941 },
26942 |candidate| {
26943 second_candidate_tx
26944 .send((candidate.worker_threads, candidate.max_blocking_tasks))
26945 .expect("the race coordinator must still be listening");
26946 },
26947 )
26948 .expect("the second partial configuration must commit");
26949 });
26950 second_started_rx
26951 .recv_timeout(Duration::from_secs(5))
26952 .expect("the second partial configuration thread must start");
26953 release_first_tx
26954 .send(())
26955 .expect("the first partial configuration must still be waiting");
26956
26957 first
26958 .join()
26959 .expect("the first configuration thread must not panic");
26960 second
26961 .join()
26962 .expect("the second configuration thread must not panic");
26963 assert_eq!(
26964 second_candidate_rx
26965 .recv_timeout(Duration::from_secs(5))
26966 .expect("the second partial configuration must expose its merged candidate"),
26967 (6, 3),
26968 "the second caller must merge against the first caller's committed options"
26969 );
26970
26971 let options = controller.options();
26972 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
26973 assert_eq!(options.thread_name_prefix, "partial-config-race");
26974 }
26975
26976 #[cfg(napi_runtime_os_threads)]
26977 #[test]
26978 fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
26979 use std::{sync::mpsc, time::Duration};
26980
26981 let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
26982 let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
26983 let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
26984 let invalid_controller = Arc::clone(&controller);
26985 let invalid = std::thread::spawn(move || {
26986 invalid_controller
26987 .configure_partial_inner(
26988 RuntimeOptionsPatch {
26989 worker_threads: Some(0),
26990 ..RuntimeOptionsPatch::default()
26991 },
26992 |candidate| {
26993 assert_eq!(
26994 (candidate.worker_threads, candidate.max_blocking_tasks),
26995 (0, 2)
26996 );
26997 invalid_merged_tx
26998 .send(())
26999 .expect("the race coordinator must still be listening");
27000 release_invalid_rx
27001 .recv_timeout(Duration::from_secs(5))
27002 .expect("the invalid partial configuration must be released");
27003 },
27004 )
27005 .expect_err("the invalid partial configuration must be rejected")
27006 .to_string()
27007 });
27008 invalid_merged_rx
27009 .recv_timeout(Duration::from_secs(5))
27010 .expect("the invalid partial configuration must hold the lock after merging");
27011
27012 let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
27013 let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
27014 let valid_controller = Arc::clone(&controller);
27015 let valid = std::thread::spawn(move || {
27016 valid_started_tx
27017 .send(())
27018 .expect("the race coordinator must still be listening");
27019 valid_controller
27020 .configure_partial_inner(
27021 RuntimeOptionsPatch {
27022 max_blocking_tasks: Some(4),
27023 ..RuntimeOptionsPatch::default()
27024 },
27025 |candidate| {
27026 valid_candidate_tx
27027 .send((candidate.worker_threads, candidate.max_blocking_tasks))
27028 .expect("the race coordinator must still be listening");
27029 },
27030 )
27031 .expect("the valid partial configuration must commit");
27032 });
27033 valid_started_rx
27034 .recv_timeout(Duration::from_secs(5))
27035 .expect("the valid partial configuration thread must start");
27036 release_invalid_tx
27037 .send(())
27038 .expect("the invalid partial configuration must still be waiting");
27039
27040 assert_eq!(
27041 invalid
27042 .join()
27043 .expect("the invalid configuration thread must not panic"),
27044 "worker_threads must be greater than zero"
27045 );
27046 valid
27047 .join()
27048 .expect("the valid configuration thread must not panic");
27049 assert_eq!(
27050 valid_candidate_rx
27051 .recv_timeout(Duration::from_secs(5))
27052 .expect("the valid partial configuration must expose its merged candidate"),
27053 (6, 4),
27054 "the valid caller must merge against the last committed options, not the rejected candidate"
27055 );
27056
27057 let options = controller.options();
27058 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
27059 assert_eq!(options.thread_name_prefix, "partial-config-rollback");
27060 }
27061
27062 #[test]
27063 fn configure_after_backend_started_is_rejected() {
27064 let controller = RuntimeController::new();
27067 controller
27068 .configure(RuntimeOptions {
27069 flavor: RuntimeFlavor::CurrentThread,
27070 worker_threads: 1,
27071 max_blocking_tasks: 1,
27072 thread_name_prefix: "rd8".to_string(),
27073 park_deadline: None,
27074 drain_linger: DEFAULT_DRAIN_LINGER,
27075 })
27076 .expect("first configure before the backend exists must succeed");
27077
27078 let _backend = controller.backend();
27080
27081 let error = controller
27082 .configure(RuntimeOptions::default())
27083 .expect_err("configure after the backend started must be rejected");
27084 assert_eq!(
27085 error.to_string(),
27086 "the async runtime configuration is frozen; configure it before the first async call"
27087 );
27088 }
27089
27090 #[test]
27091 fn partial_configuration_after_backend_started_is_rejected() {
27092 let controller = current_thread_controller("partial-config-frozen");
27093 let _backend = controller.backend();
27094
27095 let error = controller
27096 .configure_partial(RuntimeOptionsPatch {
27097 worker_threads: Some(2),
27098 ..RuntimeOptionsPatch::default()
27099 })
27100 .expect_err("partial configuration after the backend started must be rejected");
27101 assert_eq!(
27102 error.to_string(),
27103 "the async runtime configuration is frozen; configure it before the first async call"
27104 );
27105 }
27106
27107 fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
27108 let controller = RuntimeController::new();
27109 controller
27110 .configure(RuntimeOptions {
27111 flavor: RuntimeFlavor::CurrentThread,
27112 worker_threads: 1,
27113 max_blocking_tasks: 1,
27114 thread_name_prefix: thread_name_prefix.to_string(),
27115 park_deadline: None,
27116 drain_linger: DEFAULT_DRAIN_LINGER,
27117 })
27118 .expect("CurrentThread test configuration must be accepted");
27119 controller
27120 }
27121
27122 #[cfg(napi_runtime_os_threads)]
27123 fn multi_thread_controller(
27124 thread_name_prefix: &str,
27125 worker_threads: usize,
27126 max_blocking_tasks: usize,
27127 ) -> RuntimeController {
27128 let controller = RuntimeController::new();
27129 controller
27130 .configure(RuntimeOptions {
27131 flavor: RuntimeFlavor::MultiThread,
27132 worker_threads,
27133 max_blocking_tasks,
27134 thread_name_prefix: thread_name_prefix.to_string(),
27135 park_deadline: None,
27136 drain_linger: DEFAULT_DRAIN_LINGER,
27137 })
27138 .expect("MultiThread test configuration must be accepted");
27139 controller
27140 }
27141
27142 #[test]
27143 fn blocking_backend_creation_failure_preserves_diagnostic() {
27144 let controller = current_thread_controller("blocking-backend-creation-failure");
27145 let ran = Arc::new(AtomicBool::new(false));
27146 let work_ran = Arc::clone(&ran);
27147
27148 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27149 let Err((error, work)) = controller.try_spawn_blocking(move || {
27150 work_ran.store(true, Ordering::SeqCst);
27151 17usize
27152 }) else {
27153 panic!("the injected backend creation failure must reject blocking work");
27154 };
27155 assert_eq!(
27156 error.to_string(),
27157 "injected async runtime backend creation failure"
27158 );
27159 assert!(
27160 !ran.load(Ordering::SeqCst),
27161 "rejected blocking work must not run"
27162 );
27163 assert_eq!(
27164 work(),
27165 17,
27166 "the rejected blocking closure must remain usable"
27167 );
27168 assert!(ran.load(Ordering::SeqCst));
27169
27170 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27171 let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
27172 .expect_err("infallible blocking submission must return a failed join handle");
27173 assert_eq!(
27174 error.to_string(),
27175 "injected async runtime backend creation failure"
27176 );
27177
27178 let handle = controller
27179 .try_spawn_blocking(|| 31usize)
27180 .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
27181 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27182 controller.shutdown().unwrap();
27183 }
27184
27185 #[cfg(not(target_family = "wasm"))]
27186 #[test]
27187 fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
27188 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
27189
27190 if std::env::var_os(CHILD_ENV).is_some() {
27191 struct PollProbe {
27192 polls: Arc<AtomicUsize>,
27193 }
27194
27195 impl Future for PollProbe {
27196 type Output = ();
27197
27198 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27199 self.polls.fetch_add(1, Ordering::SeqCst);
27200 Poll::Ready(())
27201 }
27202 }
27203
27204 shutdown().expect("the isolated runtime must stop before block_on admission");
27205 let polls = Arc::new(AtomicUsize::new(0));
27206 let mut future = std::pin::pin!(PollProbe {
27207 polls: Arc::clone(&polls)
27208 });
27209 let error = try_block_on_dyn(future.as_mut())
27210 .expect_err("the stopped runtime must reject the borrowed future");
27211 assert_eq!(
27212 error.to_string(),
27213 "the async runtime is stopped; call start before submitting work"
27214 );
27215 assert_eq!(
27216 polls.load(Ordering::SeqCst),
27217 0,
27218 "rejected block_on must not poll its future"
27219 );
27220
27221 start().expect("the isolated runtime must restart");
27222 try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
27223 assert_eq!(polls.load(Ordering::SeqCst), 1);
27224 shutdown().expect("the restarted isolated runtime must stop");
27225 return;
27226 }
27227
27228 let output = std::process::Command::new(std::env::current_exe().unwrap())
27229 .arg("--exact")
27230 .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
27231 .arg("--nocapture")
27232 .env(CHILD_ENV, "1")
27233 .output()
27234 .expect("the stopped block_on subprocess must start");
27235 assert!(
27236 output.status.success(),
27237 "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
27238 output.status.code(),
27239 String::from_utf8_lossy(&output.stdout),
27240 String::from_utf8_lossy(&output.stderr)
27241 );
27242 }
27243
27244 #[cfg(not(target_family = "wasm"))]
27245 #[test]
27246 fn try_block_on_dyn_reports_concurrent_shutdown() {
27247 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
27248
27249 if std::env::var_os(CHILD_ENV).is_some() {
27250 use std::sync::mpsc;
27251
27252 struct PendingProbe {
27253 entered: Option<mpsc::Sender<()>>,
27254 complete: Arc<AtomicBool>,
27255 }
27256
27257 impl Future for PendingProbe {
27258 type Output = ();
27259
27260 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27261 if let Some(entered) = self.entered.take() {
27262 entered.send(()).unwrap();
27263 }
27264 if self.complete.load(Ordering::SeqCst) {
27265 Poll::Ready(())
27266 } else {
27267 Poll::Pending
27268 }
27269 }
27270 }
27271
27272 configure(RuntimeOptions {
27273 flavor: RuntimeFlavor::CurrentThread,
27274 worker_threads: 1,
27275 max_blocking_tasks: 1,
27276 thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
27277 park_deadline: None,
27278 drain_linger: DEFAULT_DRAIN_LINGER,
27279 })
27280 .expect("the isolated runtime must accept CurrentThread configuration");
27281 start().expect("the isolated runtime must start");
27282
27283 let (entered_tx, entered_rx) = mpsc::channel();
27284 let (error_tx, error_rx) = mpsc::channel();
27285 let (retry_tx, retry_rx) = mpsc::channel();
27286 let complete = Arc::new(AtomicBool::new(false));
27287 let future_complete = Arc::clone(&complete);
27288 let runner = std::thread::spawn(move || {
27289 let mut future = std::pin::pin!(PendingProbe {
27290 entered: Some(entered_tx),
27291 complete: future_complete
27292 });
27293 let error = try_block_on_dyn(future.as_mut())
27294 .expect_err("concurrent shutdown must interrupt the borrowed future")
27295 .to_string();
27296 error_tx.send(error).unwrap();
27297 retry_rx.recv().unwrap();
27298 try_block_on_dyn(future.as_mut())
27299 .expect("restart must accept the same interrupted borrowed future");
27300 });
27301 entered_rx
27302 .recv_timeout(Duration::from_secs(2))
27303 .expect("the borrowed future must be polled before shutdown");
27304 shutdown().expect("shutdown must wait for the active block_on scope to retire");
27305 assert_eq!(
27306 error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27307 "the async runtime stopped before block_on completed its future"
27308 );
27309 start().expect("the isolated runtime must restart after the interrupted drive");
27310 complete.store(true, Ordering::SeqCst);
27311 retry_tx.send(()).unwrap();
27312 runner.join().unwrap();
27313 shutdown().expect("the retried isolated runtime must stop");
27314 return;
27315 }
27316
27317 let output = std::process::Command::new(std::env::current_exe().unwrap())
27318 .arg("--exact")
27319 .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
27320 .arg("--nocapture")
27321 .env(CHILD_ENV, "1")
27322 .output()
27323 .expect("the concurrent block_on shutdown subprocess must start");
27324 assert!(
27325 output.status.success(),
27326 "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
27327 output.status.code(),
27328 String::from_utf8_lossy(&output.stdout),
27329 String::from_utf8_lossy(&output.stderr)
27330 );
27331 }
27332
27333 #[test]
27334 fn initial_lifecycle_start_preserves_lazy_configuration() {
27335 let controller = RuntimeController::new();
27336 controller
27337 .start()
27338 .expect("initial lifecycle start must succeed");
27339 controller
27340 .configure(RuntimeOptions {
27341 flavor: RuntimeFlavor::CurrentThread,
27342 worker_threads: 1,
27343 max_blocking_tasks: 1,
27344 thread_name_prefix: "lifecycle-initial-start".to_string(),
27345 park_deadline: None,
27346 drain_linger: DEFAULT_DRAIN_LINGER,
27347 })
27348 .expect("module-registration start must not consume the configuration window");
27349
27350 let state = controller
27351 .state
27352 .lock()
27353 .unwrap_or_else(std::sync::PoisonError::into_inner);
27354 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27355 }
27356
27357 #[test]
27358 fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
27359 let controller = RuntimeController::new();
27360
27361 for _ in 0..3 {
27362 controller
27363 .start()
27364 .expect("zero-work lifecycle start must succeed");
27365 assert!(matches!(
27366 &controller
27367 .state
27368 .lock()
27369 .unwrap_or_else(std::sync::PoisonError::into_inner)
27370 .lifecycle,
27371 RuntimeLifecycle::Initial
27372 ));
27373
27374 controller
27375 .shutdown()
27376 .expect("zero-work lifecycle shutdown must succeed");
27377 assert!(matches!(
27378 &controller
27379 .state
27380 .lock()
27381 .unwrap_or_else(std::sync::PoisonError::into_inner)
27382 .lifecycle,
27383 RuntimeLifecycle::StoppedBeforeFirstUse
27384 ));
27385 }
27386
27387 controller
27388 .start()
27389 .expect("a later zero-work restart must remain lazy");
27390 controller
27391 .configure(RuntimeOptions {
27392 flavor: RuntimeFlavor::CurrentThread,
27393 worker_threads: 1,
27394 max_blocking_tasks: 1,
27395 thread_name_prefix: "lifecycle-zero-work-config".to_string(),
27396 park_deadline: None,
27397 drain_linger: DEFAULT_DRAIN_LINGER,
27398 })
27399 .expect("zero-work lifecycle cycles must not consume the configuration window");
27400
27401 let state = controller
27402 .state
27403 .lock()
27404 .unwrap_or_else(std::sync::PoisonError::into_inner);
27405 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27406 }
27407
27408 #[cfg(napi_runtime_os_threads)]
27409 #[test]
27410 fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
27411 use std::sync::mpsc;
27412
27413 struct BlockingDropFuture {
27414 entered: mpsc::Sender<()>,
27415 release: mpsc::Receiver<()>,
27416 }
27417
27418 impl Future for BlockingDropFuture {
27419 type Output = ();
27420
27421 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27422 Poll::Pending
27423 }
27424 }
27425
27426 impl Drop for BlockingDropFuture {
27427 fn drop(&mut self) {
27428 self.entered.send(()).unwrap();
27429 self.release.recv().unwrap();
27430 }
27431 }
27432
27433 let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
27434 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27435 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27436 let submit_controller = Arc::clone(&controller);
27437 let submitter = std::thread::spawn(move || {
27438 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27439 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27440 entered: drop_entered_tx,
27441 release: release_drop_rx,
27442 }));
27443 assert!(
27444 result.is_err(),
27445 "the injected backend creation failure must reject the submission"
27446 );
27447 });
27448 drop_entered_rx
27449 .recv_timeout(Duration::from_secs(2))
27450 .expect("the rejected future destructor must start");
27451
27452 let retry_ran = Arc::new(AtomicBool::new(false));
27453 let retry_ran_task = Arc::clone(&retry_ran);
27454 let Err(retry) = controller.try_spawn_detached(async move {
27455 retry_ran_task.store(true, Ordering::SeqCst);
27456 }) else {
27457 panic!("backend creation must remain closed during rejected destruction");
27458 };
27459 drop(retry);
27460 assert!(!retry_ran.load(Ordering::SeqCst));
27461
27462 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27463 let shutdown_controller = Arc::clone(&controller);
27464 let shutdown = std::thread::spawn(move || {
27465 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27466 });
27467 wait_until("initial shutdown to close runtime admission", || {
27468 matches!(
27469 controller
27470 .state
27471 .lock()
27472 .unwrap_or_else(std::sync::PoisonError::into_inner)
27473 .lifecycle,
27474 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
27475 )
27476 });
27477 assert!(
27478 shutdown_rx
27479 .recv_timeout(Duration::from_millis(200))
27480 .is_err(),
27481 "initial shutdown must wait for rejected user state destruction"
27482 );
27483 assert!(
27484 controller.try_spawn_detached(async {}).is_err(),
27485 "shutdown must keep first-generation admission closed while it waits"
27486 );
27487
27488 let (start_tx, start_rx) = mpsc::channel();
27489 let start_controller = Arc::clone(&controller);
27490 let starter = std::thread::spawn(move || {
27491 start_tx.send(start_controller.start()).unwrap();
27492 });
27493 assert!(
27494 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27495 "start must wait for the zero-backend shutdown transition to finish"
27496 );
27497
27498 release_drop_tx.send(()).unwrap();
27499 submitter.join().unwrap();
27500 shutdown_rx
27501 .recv_timeout(Duration::from_secs(2))
27502 .unwrap()
27503 .unwrap();
27504 shutdown.join().unwrap();
27505 start_rx
27506 .recv_timeout(Duration::from_secs(2))
27507 .unwrap()
27508 .unwrap();
27509 starter.join().unwrap();
27510
27511 let ran = Arc::new(AtomicBool::new(false));
27512 let ran_task = Arc::clone(&ran);
27513 controller
27514 .try_spawn_detached(async move {
27515 ran_task.store(true, Ordering::SeqCst);
27516 })
27517 .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
27518 let dispatch = controller_current_thread_dispatch(&controller);
27519 controller.drive_current_thread_dispatch(dispatch, false);
27520 assert!(ran.load(Ordering::SeqCst));
27521 controller.shutdown().unwrap();
27522 }
27523
27524 #[cfg(napi_runtime_os_threads)]
27525 #[test]
27526 fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
27527 use std::sync::mpsc;
27528
27529 struct BlockingDropFuture {
27530 entered: mpsc::Sender<()>,
27531 release: mpsc::Receiver<()>,
27532 }
27533
27534 impl Future for BlockingDropFuture {
27535 type Output = ();
27536
27537 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27538 Poll::Pending
27539 }
27540 }
27541
27542 impl Drop for BlockingDropFuture {
27543 fn drop(&mut self) {
27544 self.entered.send(()).unwrap();
27545 self.release.recv().unwrap();
27546 }
27547 }
27548
27549 let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
27550 let old_generation = controller.backend().generation();
27551 controller.shutdown().unwrap();
27552
27553 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27554 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27555 let submit_controller = Arc::clone(&controller);
27556 let submitter = std::thread::spawn(move || {
27557 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27558 entered: drop_entered_tx,
27559 release: release_drop_rx,
27560 }));
27561 assert!(
27562 result.is_err(),
27563 "the stopped runtime must reject the submission"
27564 );
27565 });
27566 drop_entered_rx
27567 .recv_timeout(Duration::from_secs(2))
27568 .unwrap();
27569
27570 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27571 let shutdown_controller = Arc::clone(&controller);
27572 let shutdown = std::thread::spawn(move || {
27573 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27574 });
27575 wait_until(
27576 "stopped shutdown to enter its zero-backend transition",
27577 || {
27578 matches!(
27579 controller
27580 .state
27581 .lock()
27582 .unwrap_or_else(std::sync::PoisonError::into_inner)
27583 .lifecycle,
27584 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
27585 )
27586 },
27587 );
27588
27589 let (start_tx, start_rx) = mpsc::channel();
27590 let start_controller = Arc::clone(&controller);
27591 let starter = std::thread::spawn(move || {
27592 start_tx.send(start_controller.start()).unwrap();
27593 });
27594 assert!(
27595 shutdown_rx
27596 .recv_timeout(Duration::from_millis(200))
27597 .is_err(),
27598 "stopped shutdown must wait for rejected destruction"
27599 );
27600 assert!(
27601 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27602 "restart must wait for the stopped zero-backend transition"
27603 );
27604
27605 release_drop_tx.send(()).unwrap();
27606 submitter.join().unwrap();
27607 shutdown_rx
27608 .recv_timeout(Duration::from_secs(2))
27609 .unwrap()
27610 .unwrap();
27611 shutdown.join().unwrap();
27612 start_rx
27613 .recv_timeout(Duration::from_secs(2))
27614 .unwrap()
27615 .unwrap();
27616 starter.join().unwrap();
27617
27618 let new_generation = controller.backend().generation();
27619 assert_ne!(new_generation, old_generation);
27620 controller.shutdown().unwrap();
27621 }
27622
27623 #[test]
27624 fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
27625 use std::sync::mpsc;
27626
27627 struct ShutdownOnDrop {
27628 controller: Arc<RuntimeController>,
27629 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27630 }
27631
27632 impl Future for ShutdownOnDrop {
27633 type Output = ();
27634
27635 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27636 Poll::Pending
27637 }
27638 }
27639
27640 impl Drop for ShutdownOnDrop {
27641 fn drop(&mut self) {
27642 self.result.send(self.controller.shutdown()).unwrap();
27643 }
27644 }
27645
27646 let controller = Arc::new(current_thread_controller(
27647 "failed-initial-reentrant-shutdown",
27648 ));
27649 let (result_tx, result_rx) = mpsc::channel();
27650 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27651 let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
27652 controller: Arc::clone(&controller),
27653 result: result_tx,
27654 }));
27655 assert!(rejected.is_err());
27656
27657 let error = result_rx
27658 .recv_timeout(Duration::from_secs(1))
27659 .expect("the rejected destructor must not self-deadlock")
27660 .expect_err("rejected destruction cannot wait for its own shutdown");
27661 assert!(error.to_string().contains("rejected submission"));
27662 {
27663 let state = controller
27664 .state
27665 .lock()
27666 .unwrap_or_else(std::sync::PoisonError::into_inner);
27667 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27668 assert_eq!(state.rejected_drops, 0);
27669 }
27670
27671 let handle = controller
27672 .try_spawn(async { 23usize })
27673 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27674 let dispatch = controller_current_thread_dispatch(&controller);
27675 controller.drive_current_thread_dispatch(dispatch, false);
27676 assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
27677 controller.shutdown().unwrap();
27678 }
27679
27680 #[cfg(napi_runtime_os_threads)]
27681 #[test]
27682 fn failed_initial_submission_destructor_cannot_reenter_start() {
27683 use std::sync::mpsc;
27684
27685 struct StartOnDrop {
27686 controller: Arc<RuntimeController>,
27687 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27688 release: mpsc::Receiver<()>,
27689 }
27690
27691 impl Future for StartOnDrop {
27692 type Output = ();
27693
27694 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27695 Poll::Pending
27696 }
27697 }
27698
27699 impl Drop for StartOnDrop {
27700 fn drop(&mut self) {
27701 self.result.send(self.controller.start()).unwrap();
27702 self.release.recv().unwrap();
27703 }
27704 }
27705
27706 let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
27707 let (result_tx, result_rx) = mpsc::channel();
27708 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27709 let (submission_tx, submission_rx) = mpsc::channel();
27710 let submit_controller = Arc::clone(&controller);
27711 let submitter = std::thread::spawn(move || {
27712 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27713 let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
27714 controller: Arc::clone(&submit_controller),
27715 result: result_tx,
27716 release: release_drop_rx,
27717 }));
27718 submission_tx.send(rejected).unwrap();
27719 });
27720
27721 let error = result_rx
27722 .recv_timeout(Duration::from_secs(1))
27723 .expect("the rejected destructor must not self-deadlock")
27724 .expect_err("rejected destruction must not report a successful runtime start");
27725 assert!(error.to_string().contains("rejected submission"));
27726 {
27727 let state = controller
27728 .state
27729 .lock()
27730 .unwrap_or_else(std::sync::PoisonError::into_inner);
27731 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27732 assert_eq!(state.rejected_drops, 1);
27733 }
27734
27735 let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
27736 let (external_start_tx, external_start_rx) = mpsc::channel();
27737 let start_controller = Arc::clone(&controller);
27738 let starter = std::thread::spawn(move || {
27739 BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
27740 *slot.borrow_mut() = Some(Box::new(move || {
27741 wait_entered_tx.send(()).unwrap();
27742 }));
27743 });
27744 external_start_tx.send(start_controller.start()).unwrap();
27745 });
27746 wait_entered_rx
27747 .recv_timeout(Duration::from_secs(1))
27748 .expect("external start must reach the Initial rejected-destruction wait");
27749 assert!(
27750 external_start_rx.try_recv().is_err(),
27751 "external start must not return while rejected destruction remains active"
27752 );
27753
27754 release_drop_tx.send(()).unwrap();
27755 assert!(
27756 submission_rx
27757 .recv_timeout(Duration::from_secs(2))
27758 .unwrap()
27759 .is_err()
27760 );
27761 submitter.join().unwrap();
27762 external_start_rx
27763 .recv_timeout(Duration::from_secs(2))
27764 .unwrap()
27765 .unwrap();
27766 starter.join().unwrap();
27767
27768 {
27769 let state = controller
27770 .state
27771 .lock()
27772 .unwrap_or_else(std::sync::PoisonError::into_inner);
27773 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27774 assert_eq!(state.rejected_drops, 0);
27775 }
27776
27777 let handle = controller
27778 .try_spawn(async { 31usize })
27779 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27780 let dispatch = controller_current_thread_dispatch(&controller);
27781 controller.drive_current_thread_dispatch(dispatch, false);
27782 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27783 controller.shutdown().unwrap();
27784 }
27785
27786 #[cfg(panic = "unwind")]
27787 #[test]
27788 fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
27789 use std::sync::mpsc;
27790
27791 struct BlockingDropFuture {
27792 controller: Arc<RuntimeController>,
27793 entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
27794 release: mpsc::Receiver<()>,
27795 }
27796
27797 impl Future for BlockingDropFuture {
27798 type Output = ();
27799
27800 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27801 Poll::Pending
27802 }
27803 }
27804
27805 impl Drop for BlockingDropFuture {
27806 fn drop(&mut self) {
27807 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
27808 self
27809 .entered
27810 .send((generation, self.controller.shutdown()))
27811 .unwrap();
27812 self.release.recv().unwrap();
27813 }
27814 }
27815
27816 let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
27817 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27818 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27819 let (runner_done_tx, runner_done_rx) = mpsc::channel();
27820 let runner_controller = Arc::clone(&controller);
27821 let runner = std::thread::spawn(move || {
27822 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27823 let result = catch_unwind(AssertUnwindSafe(|| {
27824 block_on_with_controller(
27825 &runner_controller,
27826 BlockingDropFuture {
27827 controller: Arc::clone(&runner_controller),
27828 entered: drop_entered_tx,
27829 release: release_drop_rx,
27830 },
27831 );
27832 }));
27833 runner_done_tx.send(result.is_err()).unwrap();
27834 });
27835
27836 let (generation, reentrant_shutdown) = drop_entered_rx
27837 .recv_timeout(Duration::from_secs(2))
27838 .expect("failed block_on acquisition must destroy its owned future");
27839 assert_eq!(generation, None, "no runtime generation was created");
27840 let error = reentrant_shutdown
27841 .expect_err("rejected block_on destruction cannot wait for its own shutdown");
27842 assert!(error.to_string().contains("rejected submission"));
27843
27844 let Err(retry_error) = controller.try_backend() else {
27845 panic!("backend retry must remain closed during rejected block_on destruction");
27846 };
27847 assert!(retry_error.to_string().contains("rejected submission"));
27848
27849 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27850 let shutdown_controller = Arc::clone(&controller);
27851 let shutdown = std::thread::spawn(move || {
27852 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27853 });
27854 assert!(
27855 shutdown_rx
27856 .recv_timeout(Duration::from_millis(200))
27857 .is_err(),
27858 "initial shutdown must wait for rejected block_on destruction"
27859 );
27860
27861 release_drop_tx.send(()).unwrap();
27862 assert!(
27863 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27864 "block_on must preserve its infallible panic contract after contained cleanup"
27865 );
27866 runner.join().unwrap();
27867 shutdown_rx
27868 .recv_timeout(Duration::from_secs(2))
27869 .unwrap()
27870 .unwrap();
27871 shutdown.join().unwrap();
27872
27873 controller
27874 .start()
27875 .expect("zero-generation shutdown must remain restartable");
27876 let ran = Arc::new(AtomicBool::new(false));
27877 let ran_task = Arc::clone(&ran);
27878 controller
27879 .try_spawn_detached(async move {
27880 ran_task.store(true, Ordering::SeqCst);
27881 })
27882 .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
27883 let dispatch = controller_current_thread_dispatch(&controller);
27884 controller.drive_current_thread_dispatch(dispatch, false);
27885 assert!(ran.load(Ordering::SeqCst));
27886 controller.shutdown().unwrap();
27887 }
27888
27889 #[cfg(not(target_family = "wasm"))]
27890 #[test]
27891 fn public_block_on_contains_poll_and_drop_double_panic() {
27892 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
27893
27894 if std::env::var_os(CHILD_ENV).is_some() {
27895 struct PanicInPollAndDrop;
27896
27897 impl Future for PanicInPollAndDrop {
27898 type Output = ();
27899
27900 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27901 panic!("intentional block_on poll panic");
27902 }
27903 }
27904
27905 impl Drop for PanicInPollAndDrop {
27906 fn drop(&mut self) {
27907 panic!("intentional block_on future destructor panic");
27908 }
27909 }
27910
27911 let controller = current_thread_controller("block-on-double-panic");
27912 assert!(
27913 catch_unwind(AssertUnwindSafe(|| {
27914 block_on_with_controller(&controller, PanicInPollAndDrop);
27915 }))
27916 .is_err(),
27917 "the original poll panic must still reach the caller"
27918 );
27919 controller
27920 .shutdown()
27921 .expect("contained future destruction must not strand the runtime");
27922 return;
27923 }
27924
27925 let output = std::process::Command::new(std::env::current_exe().unwrap())
27926 .arg("--exact")
27927 .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
27928 .arg("--nocapture")
27929 .env(CHILD_ENV, "1")
27930 .output()
27931 .expect("the subprocess test must start");
27932 assert!(
27933 output.status.success(),
27934 "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
27935 output.status.code(),
27936 String::from_utf8_lossy(&output.stdout),
27937 String::from_utf8_lossy(&output.stderr)
27938 );
27939 }
27940
27941 #[test]
27942 fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
27943 let controller = RuntimeController::new();
27944 controller
27945 .start()
27946 .expect("initial lifecycle start must succeed");
27947 controller
27948 .shutdown()
27949 .expect("zero-work lifecycle shutdown must succeed");
27950 controller
27951 .start()
27952 .expect("zero-work lifecycle restart must remain lazy");
27953 controller
27954 .configure(RuntimeOptions {
27955 flavor: RuntimeFlavor::CurrentThread,
27956 worker_threads: 1,
27957 max_blocking_tasks: 1,
27958 thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
27959 park_deadline: None,
27960 drain_linger: DEFAULT_DRAIN_LINGER,
27961 })
27962 .expect("configuration before first real async use must succeed");
27963
27964 let first_generation = controller.backend().generation();
27965 controller
27966 .shutdown()
27967 .expect("the first real generation must stop");
27968 assert!(matches!(
27969 &controller
27970 .state
27971 .lock()
27972 .unwrap_or_else(std::sync::PoisonError::into_inner)
27973 .lifecycle,
27974 RuntimeLifecycle::Stopped
27975 ));
27976 let error = controller
27977 .configure(RuntimeOptions::default())
27978 .expect_err("a real generation must permanently freeze configuration");
27979 assert_eq!(
27980 error.to_string(),
27981 "the async runtime configuration is frozen; configure it before the first async call"
27982 );
27983
27984 controller
27985 .start()
27986 .expect("the stopped real generation must restart");
27987 let restarted_generation = {
27988 let state = controller
27989 .state
27990 .lock()
27991 .unwrap_or_else(std::sync::PoisonError::into_inner);
27992 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
27993 panic!("a real generation restart must eagerly create its replacement backend");
27994 };
27995 backend.generation()
27996 };
27997 assert_ne!(
27998 restarted_generation, first_generation,
27999 "a real generation restart must not reuse the retired backend"
28000 );
28001 controller
28002 .shutdown()
28003 .expect("the replacement generation must stop");
28004 }
28005
28006 #[test]
28007 fn runtime_controller_rejects_submissions_after_shutdown() {
28008 let controller = current_thread_controller("lifecycle-reject");
28009 controller.start().expect("runtime must start");
28010 drop(controller.backend());
28011 controller.shutdown().expect("runtime must stop");
28012
28013 let async_ran = Arc::new(AtomicBool::new(false));
28014 let async_ran_task = Arc::clone(&async_ran);
28015 let Err(rejected_future) = controller.try_spawn_detached(async move {
28016 async_ran_task.store(true, Ordering::SeqCst);
28017 }) else {
28018 panic!("shutdown must reject async work");
28019 };
28020 assert!(
28021 !async_ran.load(Ordering::SeqCst),
28022 "a rejected future must not be polled"
28023 );
28024 futures::executor::block_on(rejected_future);
28025 assert!(
28026 async_ran.load(Ordering::SeqCst),
28027 "the original future must remain usable"
28028 );
28029
28030 let blocking_ran = Arc::new(AtomicBool::new(false));
28031 let blocking_ran_task = Arc::clone(&blocking_ran);
28032 let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
28033 blocking_ran_task.store(true, Ordering::SeqCst);
28034 7
28035 }) else {
28036 panic!("shutdown must reject blocking work");
28037 };
28038 assert_eq!(
28039 blocking_error.to_string(),
28040 "the async runtime is stopped; call start before submitting work"
28041 );
28042 assert!(
28043 !blocking_ran.load(Ordering::SeqCst),
28044 "rejected blocking work must not run"
28045 );
28046 assert_eq!(
28047 rejected_work(),
28048 7,
28049 "the original closure must remain usable"
28050 );
28051
28052 let config_error = controller
28053 .configure(RuntimeOptions::default())
28054 .expect_err("shutdown must not make runtime configuration mutable again");
28055 assert_eq!(
28056 config_error.to_string(),
28057 "the async runtime configuration is frozen; configure it before the first async call"
28058 );
28059
28060 let state = controller
28061 .state
28062 .lock()
28063 .unwrap_or_else(std::sync::PoisonError::into_inner);
28064 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
28065 }
28066
28067 #[test]
28068 fn rejected_future_can_be_resubmitted_after_restart() {
28069 let controller = current_thread_controller("lifecycle-retry-future");
28070 controller.start().expect("runtime must start");
28071 drop(controller.backend());
28072 controller.shutdown().expect("runtime must stop");
28073
28074 let runs = Arc::new(AtomicUsize::new(0));
28075 let runs_task = Arc::clone(&runs);
28076 let future = async move {
28077 runs_task.fetch_add(1, Ordering::SeqCst);
28078 23
28079 };
28080 let Err((error, future)) = controller.try_spawn(future) else {
28081 panic!("stopped runtime must reject the future");
28082 };
28083 assert!(error.to_string().contains("runtime is stopped"));
28084 assert_eq!(runs.load(Ordering::SeqCst), 0);
28085
28086 controller.start().expect("runtime must restart");
28087 let handle = controller
28088 .try_spawn(future)
28089 .unwrap_or_else(|_| panic!("restart must accept retained future"));
28090 let dispatch = controller_current_thread_dispatch(&controller);
28091 controller.drive_current_thread_dispatch(dispatch, false);
28092 assert_eq!(
28093 futures::executor::block_on(handle).expect("retained future must complete"),
28094 23
28095 );
28096 assert_eq!(runs.load(Ordering::SeqCst), 1);
28097 controller
28098 .shutdown()
28099 .expect("replacement runtime must stop");
28100 }
28101
28102 #[test]
28103 fn runtime_controller_start_after_shutdown_accepts_work() {
28104 let controller = current_thread_controller("lifecycle-restart");
28105 controller.start().expect("runtime must start");
28106 drop(controller.backend());
28107 controller.shutdown().expect("runtime must stop");
28108 controller.start().expect("runtime must restart");
28109
28110 let async_ran = Arc::new(AtomicBool::new(false));
28111 let async_ran_task = Arc::clone(&async_ran);
28112 controller
28113 .try_spawn_detached(async move {
28114 async_ran_task.store(true, Ordering::SeqCst);
28115 })
28116 .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
28117 assert!(
28118 !async_ran.load(Ordering::SeqCst),
28119 "hostless CurrentThread submission must remain enqueue-only"
28120 );
28121 let dispatch = controller_current_thread_dispatch(&controller);
28122 controller.drive_current_thread_dispatch(dispatch, false);
28123 assert!(
28124 async_ran.load(Ordering::SeqCst),
28125 "restarted runtime must poll submitted work"
28126 );
28127
28128 let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
28129 panic!("restarted runtime must accept blocking work");
28130 };
28131 assert_eq!(
28132 futures::executor::block_on(handle).expect("blocking work must complete after restart"),
28133 11
28134 );
28135 }
28136
28137 #[test]
28138 fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
28139 let controller = current_thread_controller("stale-host-generation");
28140 let old_generation = controller.backend().generation();
28141 let old_dispatch = controller_current_thread_dispatch(&controller);
28142 controller.shutdown().unwrap();
28143 controller.start().unwrap();
28144 let new_generation = controller.backend().generation();
28145 assert_ne!(new_generation, old_generation);
28146
28147 let ran = Arc::new(AtomicBool::new(false));
28148 let ran_task = Arc::clone(&ran);
28149 controller
28150 .try_spawn_detached(async move {
28151 ran_task.store(true, Ordering::SeqCst);
28152 })
28153 .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
28154 let new_dispatch = controller_current_thread_dispatch(&controller);
28155 assert_ne!(new_dispatch, old_dispatch);
28156
28157 controller.drive_current_thread_dispatch(old_dispatch, false);
28158 assert!(
28159 !ran.load(Ordering::SeqCst),
28160 "a callback from the retired generation must not touch the restarted executor"
28161 );
28162 controller.drive_current_thread_dispatch(new_dispatch, false);
28163 assert!(ran.load(Ordering::SeqCst));
28164 controller.shutdown().unwrap();
28165 }
28166
28167 #[test]
28168 fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
28169 let controller = current_thread_controller("stale-host-cancellation");
28170 let old_generation = controller.backend().generation();
28171 let old_dispatch = controller_current_thread_dispatch(&controller);
28172 controller.shutdown().unwrap();
28173 controller.start().unwrap();
28174 let backend = controller.backend();
28175 let new_generation = backend.generation();
28176 assert_ne!(new_generation, old_generation);
28177 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28178 panic!("configured CurrentThread backend must create a CurrentThread executor");
28179 };
28180 let new_dispatch = current_thread_dispatch(executor);
28181 assert_ne!(new_dispatch, old_dispatch);
28182
28183 controller.cancel_current_thread_dispatch_for_test(old_dispatch);
28184 assert_eq!(
28185 executor.dispatch_pending.load(Ordering::Acquire),
28186 new_dispatch,
28187 "a delayed failure from the retired generation must not release the replacement latch"
28188 );
28189
28190 controller.cancel_current_thread_dispatch_for_test(new_dispatch);
28191 assert_eq!(
28192 executor.dispatch_pending.load(Ordering::Acquire),
28193 0,
28194 "the live generation must be able to cancel its accepted dispatch"
28195 );
28196 drop(backend);
28197 controller.shutdown().unwrap();
28198 }
28199
28200 #[cfg(napi_runtime_os_threads)]
28201 #[test]
28202 fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
28203 use std::sync::mpsc;
28204
28205 type ProbeResult = (
28206 &'static str,
28207 Option<u64>,
28208 Result<(), String>,
28209 Result<(), String>,
28210 Result<(), String>,
28211 );
28212
28213 struct StaleGenerationProbe {
28214 label: &'static str,
28215 controller: Arc<RuntimeController>,
28216 result: mpsc::Sender<ProbeResult>,
28217 }
28218
28219 impl Drop for StaleGenerationProbe {
28220 fn drop(&mut self) {
28221 let generation = active_runtime_generation();
28222 let submission = match self.controller.try_spawn(async {}) {
28223 Ok(handle) => {
28224 drop(handle);
28225 Ok(())
28226 }
28227 Err((error, future)) => {
28228 drop(future);
28229 Err(error.to_string())
28230 }
28231 };
28232 let start = self.controller.start().map_err(|error| error.to_string());
28233 let shutdown = self
28234 .controller
28235 .shutdown()
28236 .map_err(|error| error.to_string());
28237 self
28238 .result
28239 .send((self.label, generation, submission, start, shutdown))
28240 .unwrap();
28241 }
28242 }
28243
28244 let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
28245 let old_generation = controller.backend().generation();
28246 let (result_tx, result_rx) = mpsc::channel();
28247
28248 let async_controller = Arc::clone(&controller);
28249 let async_result = result_tx.clone();
28250 let async_handle = controller
28251 .try_spawn(async move {
28252 StaleGenerationProbe {
28253 label: "async",
28254 controller: async_controller,
28255 result: async_result,
28256 }
28257 })
28258 .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
28259 let old_dispatch = controller_current_thread_dispatch(&controller);
28260 controller.drive_current_thread_dispatch(old_dispatch, false);
28261
28262 let ready_controller = Arc::clone(&controller);
28263 let ready_handle = controller
28264 .try_spawn_blocking(move || StaleGenerationProbe {
28265 label: "ready",
28266 controller: ready_controller,
28267 result: result_tx,
28268 })
28269 .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
28270
28271 controller.shutdown().unwrap();
28272 controller.start().unwrap();
28273 let new_generation = controller.backend().generation();
28274 assert_ne!(new_generation, old_generation);
28275
28276 drop(async_handle);
28277 let async_result = result_rx
28278 .recv_timeout(Duration::from_secs(2))
28279 .expect("the retained async output must be destroyed without waiting on the replacement");
28280 drop(ready_handle);
28281 let ready_result = result_rx
28282 .recv_timeout(Duration::from_secs(2))
28283 .expect("the retained immediate output must be destroyed without touching the replacement");
28284
28285 for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
28286 let (label, generation, submission, start, shutdown) = result;
28287 assert_eq!(label, expected_label);
28288 assert_eq!(generation, Some(old_generation));
28289 for error in [submission, start, shutdown] {
28290 assert!(
28291 error
28292 .expect_err("retired-generation output destruction must be rejected")
28293 .contains("retired generation")
28294 );
28295 }
28296 }
28297
28298 let survivor = controller
28299 .try_spawn(async { 29usize })
28300 .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
28301 let new_dispatch = controller_current_thread_dispatch(&controller);
28302 controller.drive_current_thread_dispatch(new_dispatch, false);
28303 assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
28304 controller.shutdown().unwrap();
28305 }
28306
28307 #[cfg(napi_runtime_os_threads)]
28308 #[test]
28309 fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
28310 use std::sync::mpsc;
28311
28312 struct ShutdownOnDrop {
28313 controller: Arc<RuntimeController>,
28314 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28315 }
28316
28317 impl Drop for ShutdownOnDrop {
28318 fn drop(&mut self) {
28319 self
28320 .result
28321 .send((active_runtime_generation(), self.controller.shutdown()))
28322 .unwrap();
28323 }
28324 }
28325
28326 let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
28327 let old_generation = controller.backend().generation();
28328 let (result_tx, result_rx) = mpsc::channel();
28329 let old_handle = controller
28330 .try_spawn_blocking({
28331 let controller = Arc::clone(&controller);
28332 move || ShutdownOnDrop {
28333 controller,
28334 result: result_tx,
28335 }
28336 })
28337 .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
28338 wait_until(
28339 "the first-generation blocking result to be buffered",
28340 || {
28341 controller
28342 .metrics
28343 .blocking_tasks_completed
28344 .load(Ordering::Acquire)
28345 == 1
28346 },
28347 );
28348 controller.shutdown().unwrap();
28349 controller.start().unwrap();
28350 let new_generation = controller.backend().generation();
28351 assert_ne!(new_generation, old_generation);
28352
28353 let (holder_started_tx, holder_started_rx) = mpsc::channel();
28354 let (release_holder_tx, release_holder_rx) = mpsc::channel();
28355 let holder = controller
28356 .try_spawn_blocking(move || {
28357 holder_started_tx.send(()).unwrap();
28358 release_holder_rx.recv().unwrap();
28359 })
28360 .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
28361 holder_started_rx
28362 .recv_timeout(Duration::from_secs(2))
28363 .unwrap();
28364
28365 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28366 let shutdown_controller = Arc::clone(&controller);
28367 let shutdown = std::thread::spawn(move || {
28368 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28369 });
28370 wait_until("the replacement generation to enter Stopping", || {
28371 matches!(
28372 controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
28373 RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
28374 )
28375 });
28376
28377 let dropper = std::thread::spawn(move || drop(old_handle));
28378 let (observed_generation, reentrant_shutdown) =
28379 match result_rx.recv_timeout(Duration::from_secs(2)) {
28380 Ok(result) => result,
28381 Err(error) => {
28382 release_holder_tx.send(()).unwrap();
28383 let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
28384 let _ = dropper.join();
28385 let _ = shutdown.join();
28386 panic!("retained first-generation output waited on replacement shutdown: {error}");
28387 }
28388 };
28389 assert_eq!(observed_generation, Some(old_generation));
28390 let error = reentrant_shutdown
28391 .expect_err("a retained first-generation output must not control replacement shutdown");
28392 assert!(error.to_string().contains("retired generation"));
28393
28394 release_holder_tx.send(()).unwrap();
28395 shutdown_rx
28396 .recv_timeout(Duration::from_secs(2))
28397 .unwrap()
28398 .unwrap();
28399 dropper.join().unwrap();
28400 shutdown.join().unwrap();
28401 futures::executor::block_on(holder).unwrap();
28402 }
28403
28404 #[cfg(napi_runtime_os_threads)]
28405 #[test]
28406 fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
28407 use std::sync::mpsc;
28408
28409 let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
28410 let generation = controller.backend().generation();
28411 let dispatch = controller_current_thread_dispatch(&controller);
28412 let (admitted_tx, admitted_rx) = mpsc::channel();
28413 let (release_tx, release_rx) = mpsc::channel();
28414 let driver_controller = Arc::clone(&controller);
28415 let driver = std::thread::spawn(move || {
28416 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
28417 *slot.borrow_mut() = Some(Box::new(move || {
28418 admitted_tx.send(()).unwrap();
28419 release_rx.recv().unwrap();
28420 }));
28421 });
28422 driver_controller.drive_current_thread_dispatch(dispatch, false);
28423 });
28424 admitted_rx
28425 .recv_timeout(Duration::from_secs(2))
28426 .expect("the host turn must claim its scheduler role before execution");
28427
28428 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28429 let shutdown_controller = Arc::clone(&controller);
28430 let shutdown = std::thread::spawn(move || {
28431 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28432 });
28433 wait_until(
28434 "shutdown to close the admitted CurrentThread generation",
28435 || {
28436 let state = controller
28437 .state
28438 .lock()
28439 .unwrap_or_else(std::sync::PoisonError::into_inner);
28440 matches!(
28441 &state.lifecycle,
28442 RuntimeLifecycle::Stopping(identity) if identity.generation == generation
28443 )
28444 },
28445 );
28446 assert!(
28447 shutdown_rx
28448 .recv_timeout(Duration::from_millis(200))
28449 .is_err(),
28450 "shutdown must wait for an admitted host callback even before it starts draining"
28451 );
28452
28453 release_tx.send(()).unwrap();
28454 driver.join().unwrap();
28455 shutdown_rx
28456 .recv_timeout(Duration::from_secs(2))
28457 .unwrap()
28458 .unwrap();
28459 shutdown.join().unwrap();
28460 }
28461
28462 #[cfg(napi_runtime_os_threads)]
28463 #[test]
28464 fn spawn_racing_shutdown_cannot_recreate_backend() {
28465 use std::sync::mpsc;
28466
28467 let controller = Arc::new(current_thread_controller("lifecycle-race"));
28468 controller.start().expect("runtime must start");
28469 drop(controller.backend());
28470
28471 let (at_hook_tx, at_hook_rx) = mpsc::channel();
28472 let (release_tx, release_rx) = mpsc::channel();
28473 let ran = Arc::new(AtomicBool::new(false));
28474 let ran_task = Arc::clone(&ran);
28475 let worker_controller = Arc::clone(&controller);
28476 let worker = std::thread::spawn(move || {
28477 BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
28478 *slot.borrow_mut() = Some(Box::new(move || {
28479 at_hook_tx
28480 .send(())
28481 .expect("test coordinator must still be listening");
28482 release_rx
28483 .recv()
28484 .expect("test coordinator must release the submission");
28485 }));
28486 });
28487
28488 let future = async move {
28489 ran_task.store(true, Ordering::SeqCst);
28490 };
28491 match worker_controller.try_spawn_detached(future) {
28492 Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
28493 Err(future) => drop(future),
28494 }
28495 });
28496
28497 at_hook_rx
28498 .recv_timeout(Duration::from_secs(5))
28499 .expect("submitting thread must stop before taking the controller lock");
28500 controller.shutdown().expect("runtime must stop");
28501 release_tx
28502 .send(())
28503 .expect("submitting thread must still be waiting");
28504 worker.join().expect("submitting thread must not panic");
28505
28506 assert!(
28507 !ran.load(Ordering::SeqCst),
28508 "the rejected task must never run"
28509 );
28510 let state = controller
28511 .state
28512 .lock()
28513 .unwrap_or_else(std::sync::PoisonError::into_inner);
28514 assert!(
28515 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
28516 "the rejected submission must not lazily recreate a backend"
28517 );
28518 }
28519
28520 #[cfg(napi_runtime_os_threads)]
28521 #[test]
28522 fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
28523 use std::sync::mpsc;
28524
28525 struct BlockingDropFuture {
28526 work: Arc<GenerationWork>,
28527 entered: mpsc::Sender<(Option<u64>, usize)>,
28528 release: mpsc::Receiver<()>,
28529 }
28530
28531 impl Future for BlockingDropFuture {
28532 type Output = ();
28533
28534 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28535 Poll::Pending
28536 }
28537 }
28538
28539 impl Drop for BlockingDropFuture {
28540 fn drop(&mut self) {
28541 let active = self
28542 .work
28543 .state
28544 .lock()
28545 .unwrap_or_else(std::sync::PoisonError::into_inner)
28546 .active;
28547 self
28548 .entered
28549 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28550 .unwrap();
28551 self.release.recv().unwrap();
28552 }
28553 }
28554
28555 let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
28556 let backend = controller.backend();
28557 let old_generation = backend.generation();
28558 let work = Arc::clone(&backend.work);
28559 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28560 panic!("configured CurrentThread backend must create a CurrentThread executor");
28561 };
28562 let executor = Arc::clone(executor);
28563 drop(backend);
28564
28565 let (registered_tx, registered_rx) = mpsc::channel();
28566 let (release_submission_tx, release_submission_rx) = mpsc::channel();
28567 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28568 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28569 let submit_controller = Arc::clone(&controller);
28570 let submit_work = Arc::clone(&work);
28571 let submitter = std::thread::spawn(move || {
28572 AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28573 *slot.borrow_mut() = Some(Box::new(move || {
28574 registered_tx.send(()).unwrap();
28575 release_submission_rx.recv().unwrap();
28576 }));
28577 });
28578 submit_controller
28579 .try_spawn_detached(BlockingDropFuture {
28580 work: submit_work,
28581 entered: drop_entered_tx,
28582 release: release_drop_rx,
28583 })
28584 .unwrap_or_else(|_| {
28585 panic!("the submission linearized before shutdown and must be accepted")
28586 });
28587 });
28588 registered_rx
28589 .recv_timeout(Duration::from_secs(2))
28590 .expect("the submitting thread must register generation work before shutdown");
28591
28592 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28593 let shutdown_controller = Arc::clone(&controller);
28594 let shutdown = std::thread::spawn(move || {
28595 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28596 });
28597 wait_until("CurrentThread shutdown to close the runnable queue", || {
28598 executor
28599 .queue
28600 .lock()
28601 .unwrap_or_else(std::sync::PoisonError::into_inner)
28602 .closed
28603 });
28604 release_submission_tx.send(()).unwrap();
28605
28606 let (drop_generation, active_during_drop) = drop_entered_rx
28607 .recv_timeout(Duration::from_secs(2))
28608 .expect("the closed queue must destroy the accepted unpolled future");
28609 assert_eq!(drop_generation, Some(old_generation));
28610 assert_eq!(
28611 active_during_drop, 1,
28612 "accepted generation work must remain registered through user future destruction"
28613 );
28614
28615 let (start_tx, start_rx) = mpsc::channel();
28616 let start_controller = Arc::clone(&controller);
28617 let starter = std::thread::spawn(move || {
28618 start_tx.send(start_controller.start()).unwrap();
28619 });
28620 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28621 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28622 let shutdown_overlapped = early_shutdown.is_some();
28623 let restart_overlapped = early_start.is_some();
28624
28625 release_drop_tx.send(()).unwrap();
28626 submitter.join().unwrap();
28627 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28628 shutdown_rx
28629 .recv_timeout(Duration::from_secs(2))
28630 .expect("shutdown must finish after drop")
28631 });
28632 shutdown_result.unwrap();
28633 shutdown.join().unwrap();
28634 let start_result = early_start.unwrap_or_else(|| {
28635 start_rx
28636 .recv_timeout(Duration::from_secs(2))
28637 .expect("restart must follow shutdown")
28638 });
28639 start_result.unwrap();
28640 starter.join().unwrap();
28641
28642 assert!(
28643 !shutdown_overlapped,
28644 "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
28645 );
28646 assert!(
28647 !restart_overlapped,
28648 "restart must not create a new generation while an accepted old-generation future is being destroyed"
28649 );
28650 assert_ne!(controller.backend().generation(), old_generation);
28651 controller.shutdown().unwrap();
28652 }
28653
28654 #[cfg(napi_runtime_os_threads)]
28655 #[test]
28656 fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
28657 use std::sync::mpsc;
28658
28659 struct BlockingDrop {
28660 work: Arc<GenerationWork>,
28661 entered: mpsc::Sender<(Option<u64>, usize)>,
28662 release: mpsc::Receiver<()>,
28663 }
28664
28665 impl Drop for BlockingDrop {
28666 fn drop(&mut self) {
28667 let active = self
28668 .work
28669 .state
28670 .lock()
28671 .unwrap_or_else(std::sync::PoisonError::into_inner)
28672 .active;
28673 self
28674 .entered
28675 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28676 .unwrap();
28677 self.release.recv().unwrap();
28678 }
28679 }
28680
28681 let controller = Arc::new(multi_thread_controller(
28682 "accepted-blocking-submission-drop",
28683 2,
28684 1,
28685 ));
28686 let backend = controller.backend();
28687 let old_generation = backend.generation();
28688 let work = Arc::clone(&backend.work);
28689 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28690 panic!("configured MultiThread backend must create a MultiThread executor");
28691 };
28692 let executor = Arc::downgrade(executor);
28693 drop(backend);
28694
28695 let (registered_tx, registered_rx) = mpsc::channel();
28696 let (release_submission_tx, release_submission_rx) = mpsc::channel();
28697 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28698 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28699 let submit_controller = Arc::clone(&controller);
28700 let submitter = std::thread::spawn(move || {
28701 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28702 *slot.borrow_mut() = Some(Box::new(move || {
28703 registered_tx.send(()).unwrap();
28704 release_submission_rx.recv().unwrap();
28705 }));
28706 });
28707 let drop_signal = BlockingDrop {
28708 work,
28709 entered: drop_entered_tx,
28710 release: release_drop_rx,
28711 };
28712 let handle = submit_controller
28713 .try_spawn_blocking(move || {
28714 let _drop_signal = drop_signal;
28715 })
28716 .unwrap_or_else(|_| {
28717 panic!("the submission linearized before shutdown and must be accepted")
28718 });
28719 assert!(
28720 futures::executor::block_on(handle).is_err(),
28721 "the executor must cancel work that reaches its queue after shutdown closed it"
28722 );
28723 });
28724 registered_rx
28725 .recv_timeout(Duration::from_secs(2))
28726 .expect("the submitting thread must register generation work before shutdown");
28727
28728 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28729 let shutdown_controller = Arc::clone(&controller);
28730 let shutdown = std::thread::spawn(move || {
28731 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28732 });
28733 wait_until("MultiThread shutdown to close the blocking queue", || {
28734 executor
28735 .upgrade()
28736 .expect("the retiring executor must remain alive until shutdown completes")
28737 .blocking_queue
28738 .lock()
28739 .unwrap_or_else(std::sync::PoisonError::into_inner)
28740 .closed
28741 });
28742 release_submission_tx.send(()).unwrap();
28743
28744 let (drop_generation, active_during_drop) = drop_entered_rx
28745 .recv_timeout(Duration::from_secs(2))
28746 .expect("the closed queue must destroy the accepted blocking closure");
28747 assert_eq!(drop_generation, Some(old_generation));
28748 assert_eq!(
28749 active_during_drop, 1,
28750 "accepted generation work must remain registered through blocking closure destruction"
28751 );
28752
28753 let (start_tx, start_rx) = mpsc::channel();
28754 let start_controller = Arc::clone(&controller);
28755 let starter = std::thread::spawn(move || {
28756 start_tx.send(start_controller.start()).unwrap();
28757 });
28758 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28759 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28760 let shutdown_overlapped = early_shutdown.is_some();
28761 let restart_overlapped = early_start.is_some();
28762
28763 release_drop_tx.send(()).unwrap();
28764 submitter.join().unwrap();
28765 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28766 shutdown_rx
28767 .recv_timeout(Duration::from_secs(2))
28768 .expect("shutdown must finish after drop")
28769 });
28770 shutdown_result.unwrap();
28771 shutdown.join().unwrap();
28772 let start_result = early_start.unwrap_or_else(|| {
28773 start_rx
28774 .recv_timeout(Duration::from_secs(2))
28775 .expect("restart must follow shutdown")
28776 });
28777 start_result.unwrap();
28778 starter.join().unwrap();
28779
28780 assert!(
28781 !shutdown_overlapped,
28782 "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
28783 );
28784 assert!(
28785 !restart_overlapped,
28786 "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
28787 );
28788 assert_ne!(controller.backend().generation(), old_generation);
28789 controller.shutdown().unwrap();
28790 }
28791
28792 #[cfg(napi_runtime_os_threads)]
28793 #[test]
28794 fn multi_thread_controller_reports_its_exact_physical_worker_count() {
28795 let controller = multi_thread_controller("lifecycle-physical", 1, 8);
28796 let options = controller.options();
28797 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
28798
28799 let backend = controller.backend();
28800 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28801 panic!("configured MultiThread backend must create a Rayon executor");
28802 };
28803 assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
28804 let lifecycle = Arc::clone(&executor.worker_lifecycle);
28805 drop(backend);
28806
28807 controller.shutdown().expect("runtime must stop");
28808 assert_eq!(
28809 lifecycle.remaining(),
28810 0,
28811 "shutdown must observe every physical worker exit"
28812 );
28813 }
28814
28815 #[cfg(napi_runtime_os_threads)]
28816 #[test]
28817 fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
28818 use std::sync::mpsc;
28835
28836 let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
28837 let backend = controller.backend();
28838 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28839 panic!("configured MultiThread backend must create a Rayon executor");
28840 };
28841 let worker_count = executor.pool.current_num_threads();
28842 let completed = Arc::new(AtomicUsize::new(0));
28843 let probe_completed = Arc::clone(&completed);
28844 executor.pool.broadcast(move |_| {
28845 WORKER_TLS_DROP_PROBE.with(|slot| {
28846 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28847 action: Some(WorkerTlsDropAction::Record {
28848 completed: Arc::clone(&probe_completed),
28849 }),
28850 });
28851 assert!(
28852 previous.is_none(),
28853 "each worker must install exactly one TLS drop probe"
28854 );
28855 });
28856 });
28857 drop(backend);
28858
28859 assert_eq!(
28860 completed.load(Ordering::SeqCst),
28861 0,
28862 "no worker TLS destructor may run before shutdown retires the workers"
28863 );
28864
28865 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28866 let shutdown_controller = Arc::clone(&controller);
28867 let sample_completed = Arc::clone(&completed);
28868 let shutdown = std::thread::spawn(move || {
28869 let result = shutdown_controller.shutdown();
28870 let seen_at_return = sample_completed.load(Ordering::SeqCst);
28874 shutdown_tx.send((result, seen_at_return)).unwrap();
28875 });
28876 let (shutdown_result, seen_at_return) = shutdown_rx
28877 .recv_timeout(Duration::from_secs(10))
28878 .expect("shutdown must finish once worker TLS destructors have run");
28879 shutdown_result.unwrap();
28880 assert_eq!(
28881 seen_at_return, worker_count,
28882 "every worker TLS destructor must have completed before shutdown() returned: \
28883 the worker-join barrier must include TLS-destructor completion"
28884 );
28885 join_within(
28886 "the shutdown thread exits after teardown",
28887 shutdown,
28888 Duration::from_secs(30),
28889 );
28890 }
28891
28892 #[cfg(not(target_family = "wasm"))]
28893 #[test]
28894 fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
28895 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
28896
28897 if std::env::var_os(CHILD_ENV).is_some() {
28898 use std::sync::mpsc;
28899
28900 let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
28901 let backend = controller.backend();
28902 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28903 panic!("configured MultiThread backend must create a Rayon executor");
28904 };
28905 let installed = Arc::new(AtomicBool::new(false));
28906 let (completed_tx, completed_rx) = mpsc::channel();
28907 let probe_controller = Arc::clone(&controller);
28908 let probe_installed = Arc::clone(&installed);
28909 executor.pool.broadcast(move |_| {
28910 if probe_installed.swap(true, Ordering::SeqCst) {
28911 return;
28912 }
28913 WORKER_TLS_DROP_PROBE.with(|slot| {
28914 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28915 action: Some(WorkerTlsDropAction::ReenterLifecycle {
28916 controller: Arc::clone(&probe_controller),
28917 completed: completed_tx.clone(),
28918 }),
28919 });
28920 assert!(
28921 previous.is_none(),
28922 "the selected worker must have no prior TLS probe"
28923 );
28924 });
28925 });
28926 assert!(
28927 installed.load(Ordering::SeqCst),
28928 "one worker must install the lifecycle probe"
28929 );
28930 drop(backend);
28931
28932 controller
28933 .shutdown()
28934 .expect("external shutdown must join the retiring workers");
28935 let (start, shutdown) = completed_rx
28936 .recv_timeout(Duration::from_secs(2))
28937 .expect("the TLS destructor must complete both lifecycle reentry attempts");
28938 assert_eq!(
28939 start.unwrap_err(),
28940 "cannot restart the async runtime from work in the generation being stopped"
28941 );
28942 assert_eq!(
28943 shutdown.unwrap_err(),
28944 "cannot wait for async runtime shutdown from work in the generation being stopped"
28945 );
28946 return;
28947 }
28948
28949 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28950 .arg("--exact")
28951 .arg(
28952 "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
28953 )
28954 .arg("--nocapture")
28955 .env(CHILD_ENV, "1")
28956 .stdout(std::process::Stdio::piped())
28957 .stderr(std::process::Stdio::piped())
28958 .spawn()
28959 .expect("the TLS lifecycle-reentry subprocess must start");
28960 let deadline = Instant::now() + Duration::from_secs(5);
28961 let status = loop {
28962 if let Some(status) = child
28963 .try_wait()
28964 .expect("the subprocess status must be readable")
28965 {
28966 break status;
28967 }
28968 if Instant::now() >= deadline {
28969 let _ = child.kill();
28970 let output = child
28971 .wait_with_output()
28972 .expect("the timed-out subprocess must be reaped");
28973 panic!(
28974 "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
28975 String::from_utf8_lossy(&output.stdout),
28976 String::from_utf8_lossy(&output.stderr)
28977 );
28978 }
28979 std::thread::sleep(Duration::from_millis(10));
28980 };
28981 let output = child
28982 .wait_with_output()
28983 .expect("the completed subprocess output must be read");
28984 assert!(
28985 status.success(),
28986 "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
28987 status.code(),
28988 String::from_utf8_lossy(&output.stdout),
28989 String::from_utf8_lossy(&output.stderr)
28990 );
28991 }
28992
28993 #[cfg(napi_runtime_os_threads)]
28994 #[test]
28995 fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
28996 use std::sync::mpsc;
28997
28998 struct DropSignal(mpsc::Sender<()>);
28999 impl Drop for DropSignal {
29000 fn drop(&mut self) {
29001 let _ = self.0.send(());
29002 }
29003 }
29004
29005 let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
29006 let (polled_tx, polled_rx) = mpsc::channel();
29007 let (dropped_tx, dropped_rx) = mpsc::channel();
29008 controller
29009 .try_spawn_detached(async move {
29010 let _drop_signal = DropSignal(dropped_tx);
29011 polled_tx.send(()).unwrap();
29012 std::future::pending::<()>().await;
29013 })
29014 .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
29015 polled_rx
29016 .recv_timeout(Duration::from_secs(5))
29017 .expect("the pending task must be polled before shutdown");
29018
29019 controller
29020 .shutdown()
29021 .expect("shutdown must cancel accepted async work");
29022 dropped_rx
29023 .recv_timeout(Duration::from_secs(5))
29024 .expect("shutdown must drop the accepted task future before returning");
29025 }
29026
29027 #[cfg(napi_runtime_os_threads)]
29028 #[test]
29029 fn current_thread_shutdown_outranks_perpetual_self_wake() {
29030 use std::sync::mpsc;
29031
29032 struct SelfWakingPending {
29033 entered: Option<mpsc::Sender<()>>,
29034 }
29035
29036 impl Future for SelfWakingPending {
29037 type Output = ();
29038
29039 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29040 if let Some(entered) = self.entered.take() {
29041 entered.send(()).unwrap();
29042 }
29043 cx.waker().wake_by_ref();
29044 Poll::Pending
29045 }
29046 }
29047
29048 let controller = Arc::new(current_thread_controller(
29049 "current-thread-self-wake-shutdown",
29050 ));
29051 let (entered_tx, entered_rx) = mpsc::channel();
29052 let (outcome_tx, outcome_rx) = mpsc::channel();
29053 let block_on_controller = Arc::clone(&controller);
29054 let runner = std::thread::spawn(move || {
29055 let mut future = std::pin::pin!(SelfWakingPending {
29056 entered: Some(entered_tx)
29057 });
29058 outcome_tx
29059 .send(block_on_controller.block_on(future.as_mut()))
29060 .unwrap();
29061 });
29062 entered_rx
29063 .recv_timeout(Duration::from_secs(2))
29064 .expect("the self-waking future must be polled before shutdown");
29065
29066 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29067 let shutdown_controller = Arc::clone(&controller);
29068 let shutdown = std::thread::spawn(move || {
29069 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29070 });
29071 shutdown_rx
29072 .recv_timeout(Duration::from_secs(2))
29073 .expect("shutdown must outrank a stored self-wake permit")
29074 .unwrap();
29075 assert_eq!(
29076 outcome_rx
29077 .recv_timeout(Duration::from_secs(2))
29078 .expect("the raw block_on driver must report shutdown"),
29079 BlockOnOutcome::Stopped
29080 );
29081 runner.join().unwrap();
29082 shutdown.join().unwrap();
29083 }
29084
29085 #[cfg(napi_runtime_os_threads)]
29086 #[test]
29087 fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
29088 use std::sync::mpsc;
29089
29090 let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
29091 let backend = controller.backend();
29092 let executor = match &backend.executor {
29093 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29094 RuntimeExecutor::CurrentThread(_) => unreachable!(),
29095 };
29096 let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
29097 let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
29098 for _ in 0..2 {
29099 let worker_ready_tx = worker_ready_tx.clone();
29100 let release_workers = Arc::clone(&release_workers);
29101 executor.pool.spawn(move || {
29102 worker_ready_tx.send(()).unwrap();
29103 let (released, changed) = &*release_workers;
29104 let mut released = released
29105 .lock()
29106 .unwrap_or_else(std::sync::PoisonError::into_inner);
29107 while !*released {
29108 released = changed
29109 .wait(released)
29110 .unwrap_or_else(std::sync::PoisonError::into_inner);
29111 }
29112 });
29113 }
29114 drop(worker_ready_tx);
29115 for _ in 0..2 {
29116 worker_ready_rx
29117 .recv_timeout(Duration::from_secs(5))
29118 .expect("both workers must be occupied before the FIFO runnable is submitted");
29119 }
29120 drop(executor);
29121 drop(backend);
29122
29123 let polls = Arc::new(AtomicUsize::new(0));
29124 let handle = controller.spawn(StopPollProbe {
29125 polls: Arc::clone(&polls),
29126 first_poll: None,
29127 });
29128 wait_until("the FIFO runnable to be queued", || {
29129 controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
29130 });
29131
29132 let observed_queue = Arc::new(Mutex::new(None));
29133 let hook_observed_queue = Arc::clone(&observed_queue);
29134 let hook_metrics = Arc::clone(&controller.metrics);
29135 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29136 *slot.borrow_mut() = Some(Box::new(move || {
29137 let (released, changed) = &*release_workers;
29138 *released
29139 .lock()
29140 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29141 changed.notify_all();
29142 wait_until("the stopped FIFO runnable claim", || {
29143 hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
29144 });
29145 *hook_observed_queue
29146 .lock()
29147 .unwrap_or_else(std::sync::PoisonError::into_inner) =
29148 Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
29149 }));
29150 });
29151
29152 controller
29153 .shutdown()
29154 .expect("shutdown must cancel the stopped FIFO claim");
29155 assert_eq!(polls.load(Ordering::SeqCst), 0);
29156 assert_eq!(
29157 *observed_queue
29158 .lock()
29159 .unwrap_or_else(std::sync::PoisonError::into_inner),
29160 Some(0)
29161 );
29162 assert_eq!(
29163 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29164 0
29165 );
29166 drop(handle);
29167 }
29168
29169 #[cfg(napi_runtime_os_threads)]
29170 #[test]
29171 fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
29172 use std::sync::mpsc;
29173
29174 let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
29175 let backend = controller.backend();
29176 let executor = match &backend.executor {
29177 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29178 RuntimeExecutor::CurrentThread(_) => unreachable!(),
29179 };
29180 let polls = Arc::new(AtomicUsize::new(0));
29181 let scheduler = Arc::downgrade(&executor);
29182 let (runnable, task) = async_task::spawn(
29183 StopPollProbe {
29184 polls: Arc::clone(&polls),
29185 first_poll: None,
29186 },
29187 move |runnable| {
29188 if let Some(executor) = scheduler.upgrade() {
29189 executor.schedule(runnable);
29190 }
29191 },
29192 );
29193 let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
29194 let (release_slot_tx, release_slot_rx) = mpsc::channel();
29195 let (claim_done_tx, claim_done_rx) = mpsc::channel();
29196 let worker_executor = Arc::clone(&executor);
29197 let worker_metrics = Arc::clone(&controller.metrics);
29198 executor.pool.spawn(move || {
29199 let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
29200 worker_executor.schedule(runnable);
29201 slot_ready_tx.send(()).unwrap();
29202 release_slot_rx.recv().unwrap();
29203 let claimed = worker_executor.run_one_runnable();
29204 claim_done_tx
29205 .send((
29206 claimed,
29207 worker_metrics.queued_runnables.load(Ordering::SeqCst),
29208 ))
29209 .unwrap();
29210 });
29211 slot_ready_rx
29212 .recv_timeout(Duration::from_secs(5))
29213 .expect("the worker must publish the LIFO runnable before shutdown");
29214 assert_eq!(
29215 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29216 1
29217 );
29218 drop(executor);
29219 drop(backend);
29220
29221 let observed_claim = Arc::new(Mutex::new(None));
29222 let hook_observed_claim = Arc::clone(&observed_claim);
29223 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29224 *slot.borrow_mut() = Some(Box::new(move || {
29225 release_slot_tx.send(()).unwrap();
29226 let result = claim_done_rx
29227 .recv_timeout(Duration::from_secs(5))
29228 .expect("the stopped LIFO runnable claim must complete before abort");
29229 *hook_observed_claim
29230 .lock()
29231 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
29232 }));
29233 });
29234
29235 controller
29236 .shutdown()
29237 .expect("shutdown must cancel the stopped LIFO claim");
29238 assert_eq!(polls.load(Ordering::SeqCst), 0);
29239 assert_eq!(
29240 *observed_claim
29241 .lock()
29242 .unwrap_or_else(std::sync::PoisonError::into_inner),
29243 Some((true, 0))
29244 );
29245 assert_eq!(
29246 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29247 0
29248 );
29249 drop(task);
29250 }
29251
29252 #[cfg(napi_runtime_os_threads)]
29253 #[test]
29254 fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
29255 use std::sync::mpsc;
29256
29257 struct BlockingDrop {
29258 entered: mpsc::Sender<()>,
29259 release: mpsc::Receiver<()>,
29260 }
29261
29262 impl Drop for BlockingDrop {
29263 fn drop(&mut self) {
29264 self.entered.send(()).unwrap();
29265 self.release.recv().unwrap();
29266 }
29267 }
29268
29269 struct TwoPollProbe {
29270 first_poll: Option<mpsc::Sender<()>>,
29271 release_first_poll: mpsc::Receiver<()>,
29272 second_poll: mpsc::Sender<()>,
29273 }
29274
29275 impl Future for TwoPollProbe {
29276 type Output = ();
29277
29278 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29279 if let Some(first_poll) = self.first_poll.take() {
29280 first_poll.send(()).unwrap();
29281 self.release_first_poll.recv().unwrap();
29282 } else {
29283 self.second_poll.send(()).unwrap();
29284 }
29285 cx.waker().wake_by_ref();
29286 Poll::Pending
29287 }
29288 }
29289
29290 let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
29291 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29292 let (release_drop_tx, release_drop_rx) = mpsc::channel();
29293 let blocking_drop = BlockingDrop {
29294 entered: drop_entered_tx,
29295 release: release_drop_rx,
29296 };
29297 controller
29298 .try_spawn_detached(async move {
29299 let _drop = blocking_drop;
29300 std::future::pending::<()>().await;
29301 })
29302 .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
29303
29304 let (first_poll_tx, first_poll_rx) = mpsc::channel();
29305 let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
29306 let (second_poll_tx, second_poll_rx) = mpsc::channel();
29307 let (outcome_tx, outcome_rx) = mpsc::channel();
29308 let runner_controller = Arc::clone(&controller);
29309 let runner = std::thread::spawn(move || {
29310 let mut future = std::pin::pin!(TwoPollProbe {
29311 first_poll: Some(first_poll_tx),
29312 release_first_poll: release_first_poll_rx,
29313 second_poll: second_poll_tx,
29314 });
29315 outcome_tx
29316 .send(runner_controller.block_on(future.as_mut()))
29317 .unwrap();
29318 });
29319 first_poll_rx
29320 .recv_timeout(Duration::from_secs(2))
29321 .expect("the active block_on poll must enter before shutdown");
29322
29323 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29324 let shutdown_controller = Arc::clone(&controller);
29325 let shutdown = std::thread::spawn(move || {
29326 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29327 });
29328 drop_entered_rx
29329 .recv_timeout(Duration::from_secs(2))
29330 .expect("shutdown must begin destroying queued work");
29331
29332 release_first_poll_tx.send(()).unwrap();
29333 let polled_again = second_poll_rx
29334 .recv_timeout(Duration::from_millis(200))
29335 .is_ok();
29336 release_drop_tx.send(()).unwrap();
29337
29338 assert!(
29339 !polled_again,
29340 "an active block_on driver must observe generation stop before queued user cleanup blocks"
29341 );
29342 assert_eq!(
29343 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29344 BlockOnOutcome::Stopped
29345 );
29346 shutdown_rx
29347 .recv_timeout(Duration::from_secs(2))
29348 .unwrap()
29349 .unwrap();
29350 runner.join().unwrap();
29351 shutdown.join().unwrap();
29352 }
29353
29354 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29355 #[test]
29356 fn shutdown_cancels_nested_block_on_within_deadline() {
29357 use std::sync::mpsc;
29358
29359 let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
29360 let task_controller = Arc::clone(&controller);
29361 let (entered_tx, entered_rx) = mpsc::channel();
29362 let task = controller
29363 .try_spawn(async move {
29364 entered_tx.send(()).unwrap();
29365 block_on_with_controller(&task_controller, std::future::pending::<()>());
29366 })
29367 .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
29368 entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29369
29370 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29371 let shutdown_controller = Arc::clone(&controller);
29372 let shutdown = std::thread::spawn(move || {
29373 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29374 });
29375 shutdown_rx
29376 .recv_timeout(Duration::from_secs(2))
29377 .expect("shutdown must wake and cancel an active nested block_on")
29378 .unwrap();
29379 shutdown.join().unwrap();
29380 assert!(
29381 futures::executor::block_on(task).is_err(),
29382 "the cancelled nested block_on task must resolve as stopped"
29383 );
29384 }
29385
29386 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29387 #[test]
29388 fn stopped_public_block_on_drops_future_before_generation_retirement() {
29389 use std::sync::mpsc;
29390
29391 struct PendingDrop {
29392 entered: Option<mpsc::Sender<()>>,
29393 dropped: mpsc::Sender<Option<u64>>,
29394 release: mpsc::Receiver<()>,
29395 }
29396
29397 impl Future for PendingDrop {
29398 type Output = ();
29399
29400 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29401 if let Some(entered) = self.entered.take() {
29402 entered.send(()).unwrap();
29403 }
29404 Poll::Pending
29405 }
29406 }
29407
29408 impl Drop for PendingDrop {
29409 fn drop(&mut self) {
29410 self
29411 .dropped
29412 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29413 .unwrap();
29414 self.release.recv().unwrap();
29415 }
29416 }
29417
29418 let controller = Arc::new(current_thread_controller(
29419 "public-block-on-generation-destruction",
29420 ));
29421 let generation = controller.backend().generation();
29422 let (entered_tx, entered_rx) = mpsc::channel();
29423 let (dropped_tx, dropped_rx) = mpsc::channel();
29424 let (release_tx, release_rx) = mpsc::channel();
29425 let (runner_done_tx, runner_done_rx) = mpsc::channel();
29426 let block_controller = Arc::clone(&controller);
29427 let runner = std::thread::spawn(move || {
29428 let result = catch_unwind(AssertUnwindSafe(|| {
29429 block_on_with_controller(
29430 &block_controller,
29431 PendingDrop {
29432 entered: Some(entered_tx),
29433 dropped: dropped_tx,
29434 release: release_rx,
29435 },
29436 );
29437 }));
29438 runner_done_tx.send(result.is_err()).unwrap();
29439 });
29440 entered_rx
29441 .recv_timeout(Duration::from_secs(2))
29442 .expect("the public block_on future must be polled before shutdown");
29443
29444 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29445 let shutdown_controller = Arc::clone(&controller);
29446 let shutdown = std::thread::spawn(move || {
29447 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29448 });
29449
29450 assert_eq!(
29451 dropped_rx
29452 .recv_timeout(Duration::from_secs(2))
29453 .expect("shutdown must wake block_on and begin destroying its future"),
29454 Some(generation),
29455 "the stopped future must be destroyed under its retiring generation identity"
29456 );
29457 assert!(
29458 shutdown_rx
29459 .recv_timeout(Duration::from_millis(200))
29460 .is_err(),
29461 "shutdown must not publish Stopped while the public block_on future destructor is running"
29462 );
29463
29464 release_tx.send(()).unwrap();
29465 assert!(
29466 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29467 "the public helper must retain its existing early-stop panic behavior"
29468 );
29469 shutdown_rx
29470 .recv_timeout(Duration::from_secs(2))
29471 .unwrap()
29472 .unwrap();
29473 runner.join().unwrap();
29474 shutdown.join().unwrap();
29475 }
29476
29477 #[cfg(not(target_family = "wasm"))]
29478 #[test]
29479 fn shutdown_contains_panicking_nested_block_on_future_drop() {
29480 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
29481
29482 if std::env::var_os(CHILD_ENV).is_some() {
29483 use std::sync::mpsc;
29484
29485 struct PendingPanicOnDrop {
29486 entered: Option<mpsc::Sender<()>>,
29487 }
29488
29489 impl Future for PendingPanicOnDrop {
29490 type Output = ();
29491
29492 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29493 if let Some(entered) = self.entered.take() {
29494 entered.send(()).unwrap();
29495 }
29496 Poll::Pending
29497 }
29498 }
29499
29500 impl Drop for PendingPanicOnDrop {
29501 fn drop(&mut self) {
29502 panic!("intentional nested block_on future destructor panic");
29503 }
29504 }
29505
29506 let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
29507 let task_controller = Arc::clone(&controller);
29508 let (entered_tx, entered_rx) = mpsc::channel();
29509 let task = controller
29510 .try_spawn(async move {
29511 block_on_with_controller(
29512 &task_controller,
29513 PendingPanicOnDrop {
29514 entered: Some(entered_tx),
29515 },
29516 );
29517 })
29518 .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
29519 entered_rx
29520 .recv_timeout(Duration::from_secs(2))
29521 .expect("the nested block_on future must be polled before shutdown");
29522
29523 controller
29524 .shutdown()
29525 .expect("shutdown must contain nested future destruction");
29526 assert!(
29527 futures::executor::block_on(task).is_err(),
29528 "shutdown cancellation must still surface as a task error"
29529 );
29530 return;
29531 }
29532
29533 let output = std::process::Command::new(std::env::current_exe().unwrap())
29534 .arg("--exact")
29535 .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
29536 .arg("--nocapture")
29537 .env(CHILD_ENV, "1")
29538 .output()
29539 .expect("the subprocess test must start");
29540 assert!(
29541 output.status.success(),
29542 "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
29543 output.status.code(),
29544 String::from_utf8_lossy(&output.stdout),
29545 String::from_utf8_lossy(&output.stderr)
29546 );
29547 }
29548
29549 #[cfg(not(target_family = "wasm"))]
29550 #[test]
29551 fn panicking_future_drop_is_contained_before_async_task_drop() {
29552 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
29553
29554 if std::env::var_os(CHILD_ENV).is_some() {
29555 struct PanicOnDrop;
29556 impl Drop for PanicOnDrop {
29557 fn drop(&mut self) {
29558 panic!("intentional future destructor panic");
29559 }
29560 }
29561
29562 let controller = current_thread_controller("future-drop-subprocess");
29563 let bomb = PanicOnDrop;
29564 controller
29565 .try_spawn_detached(async move {
29566 let _bomb = bomb;
29567 std::future::pending::<()>().await;
29568 })
29569 .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
29570 controller
29571 .shutdown()
29572 .expect("shutdown must contain the future destructor panic");
29573 return;
29574 }
29575
29576 let output = std::process::Command::new(std::env::current_exe().unwrap())
29577 .arg("--exact")
29578 .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
29579 .arg("--nocapture")
29580 .env(CHILD_ENV, "1")
29581 .output()
29582 .expect("the subprocess test must start");
29583 assert!(
29584 output.status.success(),
29585 "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29586 output.status.code(),
29587 String::from_utf8_lossy(&output.stdout),
29588 String::from_utf8_lossy(&output.stderr)
29589 );
29590 }
29591
29592 #[cfg(not(target_family = "wasm"))]
29593 #[test]
29594 fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
29595 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
29596
29597 if std::env::var_os(CHILD_ENV).is_some() {
29598 struct PanicOnDrop;
29599 impl Drop for PanicOnDrop {
29600 fn drop(&mut self) {
29601 panic!("intentional detached output destructor panic");
29602 }
29603 }
29604
29605 let controller = current_thread_controller("output-drop-subprocess");
29606 let handle = controller
29607 .try_spawn(async { PanicOnDrop })
29608 .unwrap_or_else(|_| panic!("runtime must accept the task"));
29609 drop(handle);
29610 let dispatch = controller_current_thread_dispatch(&controller);
29611 controller.drive_current_thread_dispatch(dispatch, false);
29612 controller.shutdown().unwrap();
29613 return;
29614 }
29615
29616 let output = std::process::Command::new(std::env::current_exe().unwrap())
29617 .arg("--exact")
29618 .arg(
29619 "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
29620 )
29621 .arg("--nocapture")
29622 .env(CHILD_ENV, "1")
29623 .output()
29624 .expect("the subprocess test must start");
29625 assert!(
29626 output.status.success(),
29627 "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29628 output.status.code(),
29629 String::from_utf8_lossy(&output.stdout),
29630 String::from_utf8_lossy(&output.stderr)
29631 );
29632 }
29633
29634 #[cfg(napi_runtime_os_threads)]
29635 #[test]
29636 fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
29637 use std::sync::mpsc;
29638
29639 struct BlockingDrop {
29640 controller: Arc<RuntimeController>,
29641 entered: mpsc::Sender<Option<u64>>,
29642 release: mpsc::Receiver<()>,
29643 reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
29644 }
29645
29646 impl Drop for BlockingDrop {
29647 fn drop(&mut self) {
29648 self
29649 .entered
29650 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29651 .unwrap();
29652 self.release.recv().unwrap();
29653 self
29654 .reentrant_shutdown
29655 .send(self.controller.shutdown())
29656 .unwrap();
29657 }
29658 }
29659
29660 let controller = Arc::new(current_thread_controller(
29661 "current-thread-host-drain-output-retirement",
29662 ));
29663 let old_generation = controller.backend().generation();
29664 let (entered_tx, entered_rx) = mpsc::channel();
29665 let (release_tx, release_rx) = mpsc::channel();
29666 let (reentrant_tx, reentrant_rx) = mpsc::channel();
29667 let output_controller = Arc::clone(&controller);
29668 let handle = controller
29669 .try_spawn(async move {
29670 BlockingDrop {
29671 controller: output_controller,
29672 entered: entered_tx,
29673 release: release_rx,
29674 reentrant_shutdown: reentrant_tx,
29675 }
29676 })
29677 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
29678 drop(handle);
29679 let dispatch = controller_current_thread_dispatch(&controller);
29680
29681 let driver_controller = Arc::clone(&controller);
29682 let driver =
29683 std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
29684 assert_eq!(
29685 entered_rx
29686 .recv_timeout(Duration::from_secs(2))
29687 .expect("the host turn must begin destroying the detached output"),
29688 Some(old_generation),
29689 "detached output destruction must retain the generation that produced it"
29690 );
29691
29692 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29693 let shutdown_controller = Arc::clone(&controller);
29694 let shutdown = std::thread::spawn(move || {
29695 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29696 });
29697 wait_until(
29698 "CurrentThread shutdown to enter the old generation's stopping state",
29699 || {
29700 let state = controller
29701 .state
29702 .lock()
29703 .unwrap_or_else(std::sync::PoisonError::into_inner);
29704 matches!(
29705 &state.lifecycle,
29706 RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
29707 )
29708 },
29709 );
29710
29711 let (restart_tx, restart_rx) = mpsc::channel();
29712 let restart_controller = Arc::clone(&controller);
29713 let restart = std::thread::spawn(move || {
29714 restart_controller.start().unwrap();
29715 restart_tx
29716 .send(restart_controller.backend().generation())
29717 .unwrap();
29718 });
29719
29720 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29721 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
29722 let shutdown_overlapped = early_shutdown.is_some();
29723 let restart_overlapped = early_restart.is_some();
29724 release_tx.send(()).unwrap();
29725
29726 let reentrant_error = reentrant_rx
29727 .recv_timeout(Duration::from_secs(2))
29728 .expect("the retiring output destructor must finish its lifecycle check")
29729 .expect_err("old-generation output destruction must not shut down a replacement runtime");
29730 assert!(
29731 reentrant_error
29732 .to_string()
29733 .contains("generation being stopped"),
29734 "the destructor must still observe its old generation as Stopping: {reentrant_error}"
29735 );
29736
29737 let shutdown_result = early_shutdown.unwrap_or_else(|| {
29738 shutdown_rx
29739 .recv_timeout(Duration::from_secs(2))
29740 .expect("shutdown must finish after detached output destruction retires")
29741 });
29742 shutdown_result.unwrap();
29743 let new_generation = early_restart.unwrap_or_else(|| {
29744 restart_rx
29745 .recv_timeout(Duration::from_secs(2))
29746 .expect("restart must finish after the old host turn retires")
29747 });
29748
29749 driver.join().unwrap();
29750 shutdown.join().unwrap();
29751 restart.join().unwrap();
29752 assert!(
29753 !shutdown_overlapped,
29754 "shutdown must not publish Stopped while a host turn is destroying detached output"
29755 );
29756 assert!(
29757 !restart_overlapped,
29758 "restart must not overlap a host turn from the retiring generation"
29759 );
29760 assert_ne!(new_generation, old_generation);
29761 controller.shutdown().unwrap();
29762 }
29763
29764 #[cfg(napi_runtime_os_threads)]
29765 #[test]
29766 fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
29767 use std::sync::mpsc;
29768
29769 struct ShutdownOnDrop {
29770 controller: Arc<RuntimeController>,
29771 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29772 }
29773
29774 impl Drop for ShutdownOnDrop {
29775 fn drop(&mut self) {
29776 let _ = self.result.send(self.controller.shutdown());
29777 }
29778 }
29779
29780 let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
29781 let (running_tx, running_rx) = mpsc::channel();
29782 let (release_tx, release_rx) = mpsc::channel();
29783 let running = controller
29784 .try_spawn_blocking(move || {
29785 running_tx.send(()).unwrap();
29786 release_rx.recv().unwrap();
29787 })
29788 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29789 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29790
29791 let (reentrant_tx, reentrant_rx) = mpsc::channel();
29792 let bomb = ShutdownOnDrop {
29793 controller: Arc::clone(&controller),
29794 result: reentrant_tx,
29795 };
29796 let queued = controller
29797 .try_spawn_blocking(move || {
29798 let _bomb = &bomb;
29799 })
29800 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29801
29802 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29803 let shutdown_controller = Arc::clone(&controller);
29804 let shutdown = std::thread::spawn(move || {
29805 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29806 });
29807 let error = reentrant_rx
29808 .recv_timeout(Duration::from_secs(2))
29809 .expect("queued destruction must not self-deadlock")
29810 .expect_err("the retiring generation must reject reentrant shutdown");
29811 assert!(error.to_string().contains("generation being stopped"));
29812 release_tx.send(()).unwrap();
29813 shutdown_rx
29814 .recv_timeout(Duration::from_secs(2))
29815 .unwrap()
29816 .unwrap();
29817 shutdown.join().unwrap();
29818 futures::executor::block_on(running).unwrap();
29819 assert!(futures::executor::block_on(queued).is_err());
29820 }
29821
29822 #[cfg(napi_runtime_os_threads)]
29823 #[test]
29824 fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
29825 use std::sync::mpsc;
29826
29827 struct ShutdownAfterRelease {
29828 controller: Arc<RuntimeController>,
29829 entered: mpsc::Sender<()>,
29830 release: mpsc::Receiver<()>,
29831 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29832 }
29833
29834 impl Drop for ShutdownAfterRelease {
29835 fn drop(&mut self) {
29836 self.entered.send(()).unwrap();
29837 self.release.recv().unwrap();
29838 self.result.send(self.controller.shutdown()).unwrap();
29839 }
29840 }
29841
29842 let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
29843 let (holder_started_tx, holder_started_rx) = mpsc::channel();
29844 let (release_holder_tx, release_holder_rx) = mpsc::channel();
29845 let holder = controller
29846 .try_spawn_blocking(move || {
29847 holder_started_tx.send(()).unwrap();
29848 release_holder_rx.recv().unwrap();
29849 })
29850 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29851 holder_started_rx
29852 .recv_timeout(Duration::from_secs(2))
29853 .unwrap();
29854
29855 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29856 let shutdown_controller = Arc::clone(&controller);
29857 let shutdown = std::thread::spawn(move || {
29858 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29859 });
29860 wait_until("the runtime entered Stopping", || {
29861 matches!(
29862 controller
29863 .state
29864 .lock()
29865 .unwrap_or_else(std::sync::PoisonError::into_inner)
29866 .lifecycle,
29867 RuntimeLifecycle::Stopping(_)
29868 )
29869 });
29870
29871 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29872 let (release_drop_tx, release_drop_rx) = mpsc::channel();
29873 let (reentrant_tx, reentrant_rx) = mpsc::channel();
29874 let rejected_controller = Arc::clone(&controller);
29875 let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
29876 let rejected = std::thread::spawn(move || {
29877 let bomb = ShutdownAfterRelease {
29878 controller: Arc::clone(&rejected_controller),
29879 entered: drop_entered_tx,
29880 release: release_drop_rx,
29881 result: reentrant_tx,
29882 };
29883 let handle = rejected_controller.spawn(async move {
29884 let _bomb = bomb;
29885 std::future::pending::<()>().await;
29886 });
29887 rejected_done_tx
29888 .send(futures::executor::block_on(handle))
29889 .unwrap();
29890 });
29891 drop_entered_rx
29892 .recv_timeout(Duration::from_secs(2))
29893 .unwrap();
29894
29895 release_holder_tx.send(()).unwrap();
29896 assert!(
29897 shutdown_rx
29898 .recv_timeout(Duration::from_millis(200))
29899 .is_err(),
29900 "shutdown crossed into Stopped before rejected destruction finished"
29901 );
29902
29903 let (start_tx, start_rx) = mpsc::channel();
29904 let start_controller = Arc::clone(&controller);
29905 let starter = std::thread::spawn(move || {
29906 start_tx.send(start_controller.start()).unwrap();
29907 });
29908 assert!(
29909 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
29910 "restart crossed into a new generation before rejected destruction finished"
29911 );
29912
29913 release_drop_tx.send(()).unwrap();
29914 let error = reentrant_rx
29915 .recv_timeout(Duration::from_secs(2))
29916 .expect("the rejected destructor must complete")
29917 .expect_err("the retiring generation must reject the destructor's shutdown");
29918 assert!(error.to_string().contains("generation being stopped"));
29919 assert!(
29920 rejected_done_rx
29921 .recv_timeout(Duration::from_secs(2))
29922 .unwrap()
29923 .is_err()
29924 );
29925 rejected.join().unwrap();
29926
29927 shutdown_rx
29928 .recv_timeout(Duration::from_secs(2))
29929 .unwrap()
29930 .unwrap();
29931 shutdown.join().unwrap();
29932 start_rx
29933 .recv_timeout(Duration::from_secs(2))
29934 .unwrap()
29935 .unwrap();
29936 starter.join().unwrap();
29937 futures::executor::block_on(holder).unwrap();
29938 controller.shutdown().unwrap();
29939 }
29940
29941 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29942 #[test]
29943 fn shutdown_contains_hostile_caught_panic_payload() {
29944 use std::{panic::panic_any, sync::mpsc};
29945
29946 struct PanicAgain;
29947 impl Drop for PanicAgain {
29948 fn drop(&mut self) {
29949 panic!("caught panic payload destructor escaped");
29950 }
29951 }
29952
29953 struct PanicPayloadOnDrop(mpsc::Sender<()>);
29954 impl Drop for PanicPayloadOnDrop {
29955 fn drop(&mut self) {
29956 let _ = self.0.send(());
29957 panic_any(PanicAgain);
29958 }
29959 }
29960
29961 let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
29962 let (running_tx, running_rx) = mpsc::channel();
29963 let (release_tx, release_rx) = mpsc::channel();
29964 let running = controller
29965 .try_spawn_blocking(move || {
29966 running_tx.send(()).unwrap();
29967 release_rx.recv().unwrap();
29968 })
29969 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29970 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29971
29972 let (drop_tx, drop_rx) = mpsc::channel();
29973 let payload = PanicPayloadOnDrop(drop_tx);
29974 let queued = controller
29975 .try_spawn_blocking(move || {
29976 let _payload = &payload;
29977 })
29978 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29979
29980 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29981 let shutdown_controller = Arc::clone(&controller);
29982 let shutdown = std::thread::spawn(move || {
29983 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29984 });
29985 drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29986 release_tx.send(()).unwrap();
29987 shutdown_rx
29988 .recv_timeout(Duration::from_secs(2))
29989 .expect("hostile panic payload destruction must remain contained")
29990 .unwrap();
29991 shutdown
29992 .join()
29993 .expect("the caught payload must not panic during destruction");
29994 futures::executor::block_on(running).unwrap();
29995 assert!(futures::executor::block_on(queued).is_err());
29996 }
29997
29998 #[cfg(napi_runtime_os_threads)]
29999 #[test]
30000 fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
30001 use std::sync::mpsc;
30002
30003 struct DropSignal(mpsc::Sender<()>);
30004 impl Drop for DropSignal {
30005 fn drop(&mut self) {
30006 let _ = self.0.send(());
30007 }
30008 }
30009
30010 let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
30011 let (running_tx, running_rx) = mpsc::channel();
30012 let (release_tx, release_rx) = mpsc::channel();
30013 let running = controller
30014 .try_spawn_blocking(move || {
30015 running_tx.send(()).unwrap();
30016 release_rx.recv().unwrap();
30017 7usize
30018 })
30019 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30020 running_rx
30021 .recv_timeout(Duration::from_secs(5))
30022 .expect("the first blocking job must hold the only blocking slot");
30023
30024 let queued_ran = Arc::new(AtomicBool::new(false));
30025 let queued_ran_work = Arc::clone(&queued_ran);
30026 let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
30027 let queued_drop_signal = DropSignal(queued_dropped_tx);
30028 let queued = controller
30029 .try_spawn_blocking(move || {
30030 let _drop_signal = queued_drop_signal;
30031 queued_ran_work.store(true, Ordering::SeqCst);
30032 11usize
30033 })
30034 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30035
30036 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30037 let shutdown_controller = Arc::clone(&controller);
30038 let shutdown = std::thread::spawn(move || {
30039 shutdown_controller.shutdown().unwrap();
30040 shutdown_done_tx.send(()).unwrap();
30041 });
30042
30043 queued_dropped_rx
30044 .recv_timeout(Duration::from_secs(5))
30045 .expect("shutdown must drop queued blocking work");
30046 assert!(
30047 !queued_ran.load(Ordering::SeqCst),
30048 "queued blocking work must not execute"
30049 );
30050 assert!(
30051 shutdown_done_rx.try_recv().is_err(),
30052 "shutdown must wait while an accepted blocking closure is still running"
30053 );
30054
30055 release_tx.send(()).unwrap();
30056 shutdown_done_rx
30057 .recv_timeout(Duration::from_secs(5))
30058 .expect("shutdown must finish after running blocking work retires");
30059 shutdown.join().unwrap();
30060 assert_eq!(futures::executor::block_on(running).unwrap(), 7);
30061 assert!(
30062 futures::executor::block_on(queued).is_err(),
30063 "the queued blocking handle must resolve as cancelled"
30064 );
30065 }
30066
30067 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30068 #[test]
30069 fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
30070 use std::sync::mpsc;
30071
30072 struct PanicOnDrop(mpsc::Sender<()>);
30073 impl Drop for PanicOnDrop {
30074 fn drop(&mut self) {
30075 let _ = self.0.send(());
30076 panic!("intentional queued blocking destructor panic");
30077 }
30078 }
30079
30080 let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
30081 let (running_tx, running_rx) = mpsc::channel();
30082 let (release_tx, release_rx) = mpsc::channel();
30083 let running = controller
30084 .try_spawn_blocking(move || {
30085 running_tx.send(()).unwrap();
30086 release_rx.recv().unwrap();
30087 })
30088 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30089 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30090
30091 let (drop_tx, drop_rx) = mpsc::channel();
30092 let panic_on_drop = PanicOnDrop(drop_tx);
30093 let queued = controller
30094 .try_spawn_blocking(move || {
30095 let _keep_captured_until_run = &panic_on_drop;
30096 })
30097 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30098
30099 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30100 let shutdown_controller = Arc::clone(&controller);
30101 let shutdown = std::thread::spawn(move || {
30102 shutdown_controller.shutdown().unwrap();
30103 shutdown_done_tx.send(()).unwrap();
30104 });
30105
30106 drop_rx
30107 .recv_timeout(Duration::from_secs(5))
30108 .expect("shutdown must attempt to drop the queued closure");
30109 release_tx.send(()).unwrap();
30110 shutdown_done_rx
30111 .recv_timeout(Duration::from_secs(5))
30112 .expect("a panicking queued destructor must not strand shutdown");
30113 shutdown
30114 .join()
30115 .expect("shutdown must contain the destructor panic");
30116 futures::executor::block_on(running).unwrap();
30117 assert!(futures::executor::block_on(queued).is_err());
30118
30119 controller
30120 .start()
30121 .expect("the stopped controller must restart");
30122 let restarted = controller
30123 .try_spawn_blocking(|| 17usize)
30124 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30125 assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
30126 controller.shutdown().unwrap();
30127 }
30128
30129 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30130 #[test]
30131 fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
30132 use std::{panic::panic_any, sync::mpsc};
30133
30134 struct ReentrantPayload {
30135 controller: Arc<RuntimeController>,
30136 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
30137 }
30138
30139 impl Drop for ReentrantPayload {
30140 fn drop(&mut self) {
30141 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
30142 let shutdown = self.controller.shutdown();
30143 self.result.send((generation, shutdown)).unwrap();
30144 }
30145 }
30146
30147 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
30148
30149 impl Drop for PanicPayloadOnDrop {
30150 fn drop(&mut self) {
30151 panic_any(self.0.take().expect("the result must panic only once"));
30152 }
30153 }
30154
30155 let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
30156 let generation = controller.backend().generation();
30157 let (started_tx, started_rx) = mpsc::channel();
30158 let (release_tx, release_rx) = mpsc::channel();
30159 let (result_tx, result_rx) = mpsc::channel();
30160 let payload_controller = Arc::clone(&controller);
30161 let handle = controller
30162 .try_spawn_blocking(move || {
30163 started_tx.send(()).unwrap();
30164 release_rx.recv().unwrap();
30165 PanicPayloadOnDrop(Some(ReentrantPayload {
30166 controller: payload_controller,
30167 result: result_tx,
30168 }))
30169 })
30170 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30171 started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30172
30173 drop(handle);
30176 release_tx.send(()).unwrap();
30177 let (observed_generation, reentrant_shutdown) = result_rx
30178 .recv_timeout(Duration::from_secs(5))
30179 .expect("the dropped result panic payload must not self-deadlock");
30180 assert_eq!(observed_generation, Some(generation));
30181 let error =
30182 reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
30183 assert!(error.to_string().contains("work running on that runtime"));
30184
30185 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30186 let shutdown_controller = Arc::clone(&controller);
30187 let shutdown = std::thread::spawn(move || {
30188 shutdown_controller.shutdown().unwrap();
30189 shutdown_done_tx.send(()).unwrap();
30190 });
30191 shutdown_done_rx
30192 .recv_timeout(Duration::from_secs(5))
30193 .expect("a dropped-result destructor panic must not strand shutdown");
30194 shutdown
30195 .join()
30196 .expect("shutdown must contain the destructor panic");
30197
30198 controller
30199 .start()
30200 .expect("the stopped controller must restart");
30201 let restarted = controller
30202 .try_spawn_blocking(|| 19usize)
30203 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30204 assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
30205 controller.shutdown().unwrap();
30206 }
30207
30208 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30209 #[test]
30210 fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
30211 use std::sync::mpsc;
30212
30213 struct PanicOnDrop(mpsc::Sender<()>);
30214
30215 impl Drop for PanicOnDrop {
30216 fn drop(&mut self) {
30217 self.0.send(()).unwrap();
30218 panic!("intentional buffered blocking result destructor panic");
30219 }
30220 }
30221
30222 let options = RuntimeOptions {
30223 flavor: RuntimeFlavor::MultiThread,
30224 worker_threads: 2,
30225 max_blocking_tasks: 1,
30226 thread_name_prefix: "buffered-blocking-result-drop".to_string(),
30227 park_deadline: None,
30228 drain_linger: DEFAULT_DRAIN_LINGER,
30229 };
30230 let executor =
30231 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30232 let (started_tx, started_rx) = mpsc::channel();
30233 let (dropped_tx, dropped_rx) = mpsc::channel();
30234 let handle = executor.schedule_blocking(move || {
30235 started_tx.send(()).unwrap();
30236 PanicOnDrop(dropped_tx)
30237 });
30238 started_rx
30239 .recv_timeout(Duration::from_secs(2))
30240 .expect("the blocking closure must run");
30241 wait_until("the completed blocking result to be buffered", || {
30242 executor.active_blocking.load(Ordering::Acquire) == 0
30243 });
30244 assert!(
30245 dropped_rx.try_recv().is_err(),
30246 "the buffered result must remain owned by the live receiver"
30247 );
30248
30249 let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
30250 assert!(
30251 dropped.is_ok(),
30252 "dropping the buffered result must contain its destructor panic"
30253 );
30254 dropped_rx
30255 .recv_timeout(Duration::from_secs(2))
30256 .expect("dropping the receiver must reclaim the buffered result");
30257 }
30258
30259 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30260 #[test]
30261 fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
30262 use std::sync::mpsc;
30263
30264 struct PanicOnDrop(mpsc::Sender<()>);
30265 impl Drop for PanicOnDrop {
30266 fn drop(&mut self) {
30267 let _ = self.0.send(());
30268 panic!("intentional late blocking destructor panic");
30269 }
30270 }
30271
30272 let executor = multi_thread_executor(2, None, "closed-queue-drop");
30273 executor.begin_shutdown();
30274
30275 let (drop_tx, drop_rx) = mpsc::channel();
30276 let panic_on_drop = PanicOnDrop(drop_tx);
30277 let handle = executor.schedule_blocking(move || {
30278 let _keep_captured_until_run = &panic_on_drop;
30279 });
30280
30281 drop_rx
30282 .recv_timeout(Duration::from_secs(5))
30283 .expect("the closed queue must retire the rejected closure");
30284 assert!(
30285 futures::executor::block_on(handle).is_err(),
30286 "the rejected blocking handle must resolve as cancelled"
30287 );
30288 }
30289
30290 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30291 #[test]
30292 fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
30293 struct PanicWake;
30294 impl std::task::Wake for PanicWake {
30295 fn wake(self: Arc<Self>) {
30296 panic!("intentional timer waker panic");
30297 }
30298 }
30299
30300 let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
30301 let backend = controller.backend();
30302 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30303 panic!("configured MultiThread backend must create a Rayon executor");
30304 };
30305 let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
30306 let waker = Waker::from(Arc::new(PanicWake));
30307 let mut cx = Context::from_waker(&waker);
30308 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30309 drop(backend);
30310
30311 controller
30312 .shutdown()
30313 .expect("shutdown must contain the timer waker panic");
30314 controller
30315 .start()
30316 .expect("the stopped controller must restart");
30317 let restarted = controller
30318 .try_spawn_blocking(|| 23usize)
30319 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30320 assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
30321 controller.shutdown().unwrap();
30322 }
30323
30324 #[cfg(napi_runtime_os_threads)]
30325 #[test]
30326 fn multi_thread_restart_waits_for_old_generation_worker_exit() {
30327 use std::sync::mpsc;
30328
30329 let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
30330 let backend = controller.backend();
30331 let old_generation = backend.generation();
30332 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30333 panic!("configured MultiThread backend must create a Rayon executor");
30334 };
30335 let old_workers = Arc::clone(&executor.worker_lifecycle);
30336 drop(backend);
30337
30338 let (running_tx, running_rx) = mpsc::channel();
30339 let (release_tx, release_rx) = mpsc::channel();
30340 let running = controller
30341 .try_spawn_blocking(move || {
30342 running_tx.send(()).unwrap();
30343 release_rx.recv().unwrap();
30344 })
30345 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30346 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30347
30348 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30349 let shutdown_controller = Arc::clone(&controller);
30350 let shutdown = std::thread::spawn(move || {
30351 shutdown_controller.shutdown().unwrap();
30352 shutdown_done_tx.send(()).unwrap();
30353 });
30354 wait_until("controller entered Stopping", || {
30355 let state = controller
30356 .state
30357 .lock()
30358 .unwrap_or_else(std::sync::PoisonError::into_inner);
30359 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
30360 });
30361
30362 let (start_done_tx, start_done_rx) = mpsc::channel();
30363 let start_controller = Arc::clone(&controller);
30364 let starter = std::thread::spawn(move || {
30365 start_controller.start().unwrap();
30366 start_done_tx.send(()).unwrap();
30367 });
30368 assert!(
30369 start_done_rx.try_recv().is_err(),
30370 "start must wait while shutdown is quiescing"
30371 );
30372 assert!(
30373 old_workers.remaining() > 0,
30374 "the held old generation must still own workers"
30375 );
30376
30377 release_tx.send(()).unwrap();
30378 futures::executor::block_on(running).unwrap();
30379 shutdown_done_rx
30380 .recv_timeout(Duration::from_secs(5))
30381 .unwrap();
30382 start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30383 shutdown.join().unwrap();
30384 starter.join().unwrap();
30385 assert_eq!(
30386 old_workers.remaining(),
30387 0,
30388 "restart may begin only after every old worker exits"
30389 );
30390
30391 let backend = controller.backend();
30392 assert_ne!(backend.generation(), old_generation);
30393 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30394 panic!("restarted backend must remain MultiThread");
30395 };
30396 assert_eq!(
30397 executor.pool.current_num_threads(),
30398 controller.options().worker_threads
30399 );
30400 drop(backend);
30401 controller.shutdown().unwrap();
30402 }
30403
30404 #[cfg(napi_runtime_os_threads)]
30405 #[test]
30406 fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
30407 let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
30408 let task_controller = Arc::clone(&controller);
30409 let handle = controller
30410 .try_spawn(async move {
30411 task_controller
30412 .shutdown()
30413 .expect_err("runtime work must not wait for its own generation")
30414 .to_string()
30415 })
30416 .unwrap_or_else(|_| panic!("running runtime must accept the task"));
30417 let message = futures::executor::block_on(handle).unwrap();
30418 assert!(message.contains("cannot shut down"));
30419 controller.shutdown().unwrap();
30420 }
30421
30422 #[cfg(napi_runtime_os_threads)]
30433 #[test]
30434 fn two_phase_shutdown_matches_single_call() {
30435 let controller = multi_thread_controller("two-phase-shutdown", 2, 1);
30439 let entered = Arc::new(AtomicBool::new(false));
30440 let release = Arc::new(AtomicBool::new(false));
30441 let job_entered = Arc::clone(&entered);
30442 let job_release = Arc::clone(&release);
30443 let handle = controller
30444 .try_spawn_blocking(move || {
30445 job_entered.store(true, Ordering::SeqCst);
30446 while !job_release.load(Ordering::SeqCst) {
30447 std::hint::spin_loop();
30448 }
30449 7usize
30450 })
30451 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30452 while !entered.load(Ordering::SeqCst) {
30453 std::thread::yield_now();
30454 }
30455
30456 assert!(
30457 controller
30458 .begin_shutdown()
30459 .expect("phase 1 on a running generation must be accepted"),
30460 "phase 1 must report the still-running blocking closure instead of waiting for it"
30461 );
30462 assert!(
30463 controller.runtime_work_pending(),
30464 "the parked closure holds its generation guard"
30465 );
30466 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30469 panic!("a submission between the phases must be rejected");
30470 };
30471 assert_eq!(
30472 error.to_string(),
30473 "the async runtime is stopped; call start before submitting work"
30474 );
30475
30476 release.store(true, Ordering::SeqCst);
30477 let deadline = Instant::now() + Duration::from_secs(5);
30478 while controller.runtime_work_pending() {
30479 assert!(
30480 Instant::now() < deadline,
30481 "the released closure must retire and the poll must answer false"
30482 );
30483 std::thread::yield_now();
30484 }
30485 assert_eq!(
30486 futures::executor::block_on(handle).unwrap(),
30487 7usize,
30488 "the closure that phase 1 refused to wait for still delivers its result"
30489 );
30490
30491 controller
30492 .finish_shutdown()
30493 .expect("phase 2 must complete the outstanding phase 1");
30494 {
30495 let state = controller
30496 .state
30497 .lock()
30498 .unwrap_or_else(std::sync::PoisonError::into_inner);
30499 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30500 assert!(
30501 state.draining.is_none(),
30502 "phase 2 must consume the phase-1 handoff"
30503 );
30504 }
30505
30506 controller.start().expect("a stopped runtime must restart");
30508 drop(controller.backend());
30509 controller
30510 .shutdown()
30511 .expect("the single call must still shut the restarted runtime down");
30512 }
30513
30514 #[cfg(napi_runtime_os_threads)]
30515 #[test]
30516 fn concurrent_shutdown_during_two_phase_still_reports_success() {
30517 let controller = Arc::new(multi_thread_controller("two-phase-concurrent", 2, 1));
30528 let entered = Arc::new(AtomicBool::new(false));
30529 let committed = Arc::new(AtomicBool::new(false));
30530 let job_entered = Arc::clone(&entered);
30531 let job_committed = Arc::clone(&committed);
30532 let handle = controller
30536 .try_spawn_blocking(move || {
30537 job_entered.store(true, Ordering::SeqCst);
30538 while !job_committed.load(Ordering::SeqCst) {
30539 std::hint::spin_loop();
30540 }
30541 3usize
30542 })
30543 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30544 wait_until("the blocking closure to park", || {
30545 entered.load(Ordering::SeqCst)
30546 });
30547 assert!(
30548 controller
30549 .begin_shutdown()
30550 .expect("phase 1 must be accepted")
30551 );
30552
30553 let (concurrent_tx, concurrent_rx) = std::sync::mpsc::channel();
30554 let hook_controller = Arc::clone(&controller);
30555 let hook_committed = Arc::clone(&committed);
30556 let owner_took_the_drain = Arc::new(AtomicBool::new(false));
30557 let hook_took_the_drain = Arc::clone(&owner_took_the_drain);
30558 AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30559 *slot.borrow_mut() = Some(Box::new(move || {
30560 hook_took_the_drain.store(true, Ordering::SeqCst);
30561 let concurrent = std::thread::spawn(move || {
30562 hook_committed.store(true, Ordering::SeqCst);
30563 hook_controller.shutdown()
30564 });
30565 concurrent_tx
30566 .send(concurrent)
30567 .expect("the test thread must still be waiting for the concurrent handle");
30568 }));
30569 });
30570
30571 controller
30572 .finish_shutdown()
30573 .expect("phase 2 must complete the handshake");
30574 assert!(
30575 owner_took_the_drain.load(Ordering::SeqCst),
30576 "the phase-1 owner must be the thread that took the handoff"
30577 );
30578 concurrent_rx
30579 .recv()
30580 .expect("the hook must have started the concurrent shutdown")
30581 .join()
30582 .expect("the concurrent shutdown thread must not panic")
30583 .expect("a concurrent single-call shutdown must still succeed");
30584
30585 assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30586 let state = controller
30587 .state
30588 .lock()
30589 .unwrap_or_else(std::sync::PoisonError::into_inner);
30590 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30591 assert!(state.draining.is_none());
30592 }
30593
30594 #[cfg(napi_runtime_os_threads)]
30595 #[test]
30596 fn a_phase_one_during_the_in_flight_drain_answers_without_waiting() {
30597 let controller = Arc::new(multi_thread_controller("two-phase-inflight-drain", 2, 1));
30615 let entered = Arc::new(AtomicBool::new(false));
30616 let release = Arc::new(AtomicBool::new(false));
30617 let job_entered = Arc::clone(&entered);
30618 let job_release = Arc::clone(&release);
30619 let handle = controller
30620 .try_spawn_blocking(move || {
30621 job_entered.store(true, Ordering::SeqCst);
30622 while !job_release.load(Ordering::SeqCst) {
30623 std::hint::spin_loop();
30624 }
30625 11usize
30626 })
30627 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30628 wait_until("the blocking closure to park", || {
30629 entered.load(Ordering::SeqCst)
30630 });
30631 assert!(
30632 controller
30633 .begin_shutdown()
30634 .expect("phase 1 must be accepted"),
30635 "the parked closure must be reported as live"
30636 );
30637
30638 let (probe_tx, probe_rx) = std::sync::mpsc::channel();
30639 let hook_controller = Arc::clone(&controller);
30640 let hook_release = Arc::clone(&release);
30641 AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30642 *slot.borrow_mut() = Some(Box::new(move || {
30643 let probe_controller = Arc::clone(&hook_controller);
30644 let probe = std::thread::spawn(move || {
30645 let pending = probe_controller.runtime_work_pending();
30646 (pending, probe_controller.begin_shutdown())
30647 });
30648 let watchdog = std::thread::spawn(move || {
30649 let deadline = Instant::now() + Duration::from_secs(5);
30650 while !probe.is_finished() && Instant::now() < deadline {
30651 std::thread::yield_now();
30652 }
30653 let answered_during_the_drain = probe.is_finished();
30657 hook_release.store(true, Ordering::SeqCst);
30658 (answered_during_the_drain, probe.join())
30659 });
30660 probe_tx
30661 .send(watchdog)
30662 .expect("the test thread must still be waiting for the probe handle");
30663 }));
30664 });
30665
30666 controller
30667 .finish_shutdown()
30668 .expect("phase 2 must complete the outstanding phase 1");
30669 let (answered_during_the_drain, probe) = probe_rx
30670 .recv()
30671 .expect("the hook must have started the probe")
30672 .join()
30673 .expect("the watchdog thread must not panic");
30674 let (pending, verdict) = probe.expect("the probe thread must not panic");
30675 assert!(
30676 answered_during_the_drain,
30677 "a phase 1 re-entered during the in-flight drain must answer without waiting for the join"
30678 );
30679 assert!(
30680 pending,
30681 "the poll must still see the parked closure while the drain that is waiting for it runs"
30682 );
30683 assert!(
30684 verdict.expect("a phase 1 during the in-flight drain must be accepted"),
30685 "the re-entering phase 1 must report the parked closure as live"
30686 );
30687
30688 assert_eq!(futures::executor::block_on(handle).unwrap(), 11usize);
30689 {
30690 let state = controller
30691 .state
30692 .lock()
30693 .unwrap_or_else(std::sync::PoisonError::into_inner);
30694 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30695 assert!(
30696 state.draining.is_none(),
30697 "publishing `Stopped` must retire the in-flight drain marker too"
30698 );
30699 }
30700 assert!(
30701 !controller.runtime_work_pending(),
30702 "a stopped runtime has no pending work"
30703 );
30704
30705 controller.start().expect("a stopped runtime must restart");
30706 drop(controller.backend());
30707 controller
30708 .shutdown()
30709 .expect("the single call must still shut the restarted runtime down");
30710 }
30711
30712 #[cfg(napi_runtime_os_threads)]
30713 #[test]
30714 fn phase_one_owner_finishes_after_a_concurrent_shutdown_drains() {
30715 let controller = Arc::new(multi_thread_controller("two-phase-owner-finish", 2, 1));
30722 let entered = Arc::new(AtomicBool::new(false));
30723 let release = Arc::new(AtomicBool::new(false));
30724 let job_entered = Arc::clone(&entered);
30725 let job_release = Arc::clone(&release);
30726 let handle = controller
30727 .try_spawn_blocking(move || {
30728 job_entered.store(true, Ordering::SeqCst);
30729 while !job_release.load(Ordering::SeqCst) {
30730 std::hint::spin_loop();
30731 }
30732 3usize
30733 })
30734 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30735 wait_until("the blocking closure to park", || {
30736 entered.load(Ordering::SeqCst)
30737 });
30738 assert!(
30739 controller
30740 .begin_shutdown()
30741 .expect("phase 1 must be accepted"),
30742 "the parked closure must be reported as live"
30743 );
30744
30745 let single_call = Arc::clone(&controller);
30746 let concurrent = std::thread::spawn(move || single_call.shutdown());
30747 release.store(true, Ordering::SeqCst);
30748 concurrent
30749 .join()
30750 .expect("the concurrent shutdown thread must not panic")
30751 .expect("a concurrent single-call shutdown must still succeed");
30752 {
30753 let state = controller
30754 .state
30755 .lock()
30756 .unwrap_or_else(std::sync::PoisonError::into_inner);
30757 assert!(
30758 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30759 "the concurrent call must have published the stop"
30760 );
30761 assert!(
30762 state.draining.is_none(),
30763 "the concurrent call must have consumed the phase-1 handoff"
30764 );
30765 }
30766
30767 controller
30768 .finish_shutdown()
30769 .expect("the thread that began phase 1 must still be told its shutdown completed");
30770 assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30771
30772 controller.start().expect("a stopped runtime must restart");
30777 drop(controller.backend());
30778 controller
30779 .finish_shutdown()
30780 .expect("phase 2 after a restart is a no-op, not an error");
30781 {
30782 let state = controller
30783 .state
30784 .lock()
30785 .unwrap_or_else(std::sync::PoisonError::into_inner);
30786 assert!(
30787 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30788 "the no-op phase 2 must not stop the restarted generation"
30789 );
30790 }
30791 controller
30792 .shutdown()
30793 .expect("the single call must still shut the restarted runtime down");
30794 }
30795
30796 #[test]
30797 fn begin_shutdown_twice_does_not_block() {
30798 let controller = current_thread_controller("two-phase-reentrant-begin");
30806 drop(controller.backend());
30807
30808 assert!(
30809 !controller
30810 .begin_shutdown()
30811 .expect("phase 1 on an idle generation must be accepted"),
30812 "an idle generation has nothing for the host to wait for"
30813 );
30814 {
30815 let state = controller
30816 .state
30817 .lock()
30818 .unwrap_or_else(std::sync::PoisonError::into_inner);
30819 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_)));
30820 }
30821 assert!(
30822 !controller
30823 .begin_shutdown()
30824 .expect("a second phase 1 must answer immediately"),
30825 "the second phase 1 must report the same verdict"
30826 );
30827
30828 controller
30829 .finish_shutdown()
30830 .expect("one phase 2 completes the single outstanding phase 1");
30831 {
30832 let state = controller
30833 .state
30834 .lock()
30835 .unwrap_or_else(std::sync::PoisonError::into_inner);
30836 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30837 assert!(state.draining.is_none());
30838 }
30839
30840 controller
30841 .finish_shutdown()
30842 .expect("a second phase 2 repeats the completed stop instead of erroring");
30843 {
30844 let state = controller
30845 .state
30846 .lock()
30847 .unwrap_or_else(std::sync::PoisonError::into_inner);
30848 assert!(
30849 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30850 "the repeated phase 2 must change nothing"
30851 );
30852 }
30853 }
30854
30855 #[test]
30856 fn current_thread_two_phase_is_idle_immediately() {
30857 let controller = current_thread_controller("two-phase-current-thread");
30860 drop(controller.backend());
30861 assert!(
30862 !controller.runtime_work_pending(),
30863 "an idle running generation has no pending work"
30864 );
30865
30866 assert!(
30867 !controller
30868 .begin_shutdown()
30869 .expect("phase 1 must be accepted"),
30870 "phase 1 on an idle generation must report nothing to wait for"
30871 );
30872 assert!(
30873 !controller.runtime_work_pending(),
30874 "the poll must stay false across the window"
30875 );
30876 controller.finish_shutdown().expect("phase 2 must complete");
30877 {
30878 let state = controller
30879 .state
30880 .lock()
30881 .unwrap_or_else(std::sync::PoisonError::into_inner);
30882 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30883 }
30884
30885 controller.start().expect("a stopped runtime must restart");
30886 drop(controller.backend());
30887 controller
30888 .shutdown()
30889 .expect("the single call must still work");
30890 }
30891
30892 #[test]
30893 fn finish_shutdown_without_begin_is_an_error() {
30894 let controller = current_thread_controller("two-phase-orphan-finish");
30895 drop(controller.backend());
30896 let error = controller
30897 .finish_shutdown()
30898 .expect_err("phase 2 without an outstanding phase 1 must be rejected");
30899 assert_eq!(
30900 error.to_string(),
30901 "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown"
30902 );
30903 controller
30904 .shutdown()
30905 .expect("the rejected phase 2 must leave the runtime shuttable");
30906 }
30907
30908 #[test]
30909 fn a_restart_does_not_inherit_a_settled_shutdown_handoff() {
30910 let controller = current_thread_controller("two-phase-stale-settled");
30918 drop(controller.backend());
30919 controller
30920 .shutdown()
30921 .expect("the first shutdown must complete");
30922
30923 assert!(
30924 !controller
30925 .begin_shutdown()
30926 .expect("phase 1 on a stopped runtime must be accepted"),
30927 "a stopped runtime has no work for the host to wait for"
30928 );
30929 controller.start().expect("a stopped runtime must restart");
30930 {
30931 let state = controller
30932 .state
30933 .lock()
30934 .unwrap_or_else(std::sync::PoisonError::into_inner);
30935 assert!(
30936 state.draining.is_none(),
30937 "a fresh generation must not inherit the settled handoff"
30938 );
30939 }
30940 drop(controller.backend());
30941
30942 controller
30943 .shutdown()
30944 .expect("the second shutdown must stop the restarted generation");
30945 {
30946 let state = controller
30947 .state
30948 .lock()
30949 .unwrap_or_else(std::sync::PoisonError::into_inner);
30950 assert!(
30951 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30952 "the restarted generation must really be stopped, not reported stopped"
30953 );
30954 assert!(state.draining.is_none());
30955 }
30956 assert!(
30957 controller.current_backend.load().is_none(),
30958 "the stopped generation's backend must be gone"
30959 );
30960 assert!(
30961 !controller.runtime_work_pending(),
30962 "a stopped runtime has no pending work"
30963 );
30964 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30965 panic!("a submission after the second shutdown must be rejected");
30966 };
30967 assert_eq!(
30968 error.to_string(),
30969 "the async runtime is stopped; call start before submitting work"
30970 );
30971 }
30972
30973 #[test]
30974 fn a_settled_phase_one_is_reported_by_every_finish_across_a_restart() {
30975 let controller = current_thread_controller("two-phase-settled-idempotent");
30983 drop(controller.backend());
30984 controller
30985 .shutdown()
30986 .expect("the first shutdown must complete");
30987 assert!(
30988 !controller
30989 .begin_shutdown()
30990 .expect("phase 1 on a stopped runtime must be accepted")
30991 );
30992 controller.start().expect("a stopped runtime must restart");
30993 {
30994 let state = controller
30995 .state
30996 .lock()
30997 .unwrap_or_else(std::sync::PoisonError::into_inner);
30998 assert!(
30999 state.draining.is_none(),
31000 "the restart must drop the settled handoff"
31001 );
31002 assert!(
31003 state.phase_ever_opened,
31004 "the restart must not erase the fact that a stop was announced"
31005 );
31006 }
31007
31008 controller
31009 .finish_shutdown()
31010 .expect("the announced stop must be reported to the caller that opened it");
31011 controller
31012 .finish_shutdown()
31013 .expect("and to every phase 2 after it -- the call is idempotent");
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::Running(_)),
31021 "neither phase 2 may stop the generation the restart published"
31022 );
31023 }
31024 drop(controller.backend());
31025 controller
31026 .shutdown()
31027 .expect("the no-op phase 2s must leave the runtime shuttable");
31028 }
31029
31030 #[test]
31031 fn shutdown_racing_a_start_still_reports_ok() {
31032 let controller = Arc::new(current_thread_controller("shutdown-racing-start"));
31044 drop(controller.backend());
31045 controller
31046 .shutdown()
31047 .expect("the first shutdown must complete");
31048
31049 let start_controller = Arc::clone(&controller);
31050 let started = Arc::new(AtomicBool::new(false));
31051 let hook_started = Arc::clone(&started);
31052 BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31053 *slot.borrow_mut() = Some(Box::new(move || {
31054 start_controller
31055 .start()
31056 .expect("a stopped runtime must restart");
31057 hook_started.store(true, Ordering::SeqCst);
31058 }));
31059 });
31060 controller
31061 .shutdown()
31062 .expect("a shutdown must report the phase it opened, even across a racing start");
31063 assert!(
31064 started.load(Ordering::SeqCst),
31065 "the hook must have run the start inside the window"
31066 );
31067
31068 {
31069 let state = controller
31070 .state
31071 .lock()
31072 .unwrap_or_else(std::sync::PoisonError::into_inner);
31073 assert!(
31074 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31075 "the racing start wins the window: its generation is live, not stopped"
31076 );
31077 assert!(
31078 state.draining.is_none(),
31079 "the start must have dropped the settled handoff"
31080 );
31081 }
31082
31083 let ran = Arc::new(AtomicBool::new(false));
31086 let ran_task = Arc::clone(&ran);
31087 controller
31088 .try_spawn_detached(async move {
31089 ran_task.store(true, Ordering::SeqCst);
31090 })
31091 .unwrap_or_else(|_| panic!("the generation the start published must accept work"));
31092 let dispatch = controller_current_thread_dispatch(&controller);
31093 controller.drive_current_thread_dispatch(dispatch, false);
31094 assert!(ran.load(Ordering::SeqCst));
31095 controller
31096 .shutdown()
31097 .expect("the restarted generation must still shut down");
31098 let state = controller
31099 .state
31100 .lock()
31101 .unwrap_or_else(std::sync::PoisonError::into_inner);
31102 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31103 assert!(state.draining.is_none());
31104 }
31105
31106 #[cfg(napi_runtime_os_threads)]
31110 #[test]
31111 fn concurrent_split_finish_racing_a_single_call_shutdown() {
31112 let controller = Arc::new(current_thread_controller("split-finish-vs-single"));
31122 drop(controller.backend());
31123
31124 let split_controller = Arc::clone(&controller);
31125 let (tx, rx) = std::sync::mpsc::channel();
31126 BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31127 *slot.borrow_mut() = Some(Box::new(move || {
31128 let split = std::thread::spawn(move || {
31129 let pending = split_controller
31130 .begin_shutdown()
31131 .expect("a second phase 1 must re-report the outstanding handoff");
31132 split_controller
31133 .finish_shutdown()
31134 .expect("an in-protocol phase 2 must complete the outstanding stop");
31135 pending
31136 });
31137 tx.send(
31138 split
31139 .join()
31140 .expect("the split shutdown thread must not panic"),
31141 )
31142 .expect("the test thread must still be waiting for the split result");
31143 }));
31144 });
31145
31146 controller
31147 .shutdown()
31148 .expect("the single call must report the stop it announced, whoever joined it");
31149 assert!(
31150 !rx
31151 .try_recv()
31152 .expect("the hook must have run the split pair inside the window"),
31153 "an idle generation has nothing for the host to wait for"
31154 );
31155
31156 let state = controller
31157 .state
31158 .lock()
31159 .unwrap_or_else(std::sync::PoisonError::into_inner);
31160 assert!(
31161 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31162 "the generation must really be stopped, not reported stopped"
31163 );
31164 assert!(state.draining.is_none());
31165 }
31166
31167 #[test]
31168 fn repeated_begin_start_pairs_leave_no_residue() {
31169 const PAIRS: usize = 50;
31174 let controller = current_thread_controller("begin-start-residue");
31175 drop(controller.backend());
31176 for _ in 0..PAIRS {
31177 controller
31178 .shutdown()
31179 .expect("each shutdown must stop the live generation");
31180 assert!(
31181 !controller
31182 .begin_shutdown()
31183 .expect("phase 1 on a stopped runtime must be accepted"),
31184 "a stopped runtime has no work for the host to wait for"
31185 );
31186 controller
31187 .start()
31188 .expect("a zero-backend window must let a restart through");
31189 let state = controller
31190 .state
31191 .lock()
31192 .unwrap_or_else(std::sync::PoisonError::into_inner);
31193 assert!(
31194 state.draining.is_none(),
31195 "the restart must drop the settled handoff"
31196 );
31197 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Running(_)));
31198 }
31199
31200 controller
31201 .finish_shutdown()
31202 .expect("a phase 2 with nothing outstanding is a no-op Ok");
31203 {
31204 let state = controller
31205 .state
31206 .lock()
31207 .unwrap_or_else(std::sync::PoisonError::into_inner);
31208 assert!(
31209 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31210 "the no-op phase 2 must not stop the generation the last restart published"
31211 );
31212 }
31213
31214 controller
31215 .shutdown()
31216 .expect("a full shutdown after the begin/start pairs must still work");
31217 let state = controller
31218 .state
31219 .lock()
31220 .unwrap_or_else(std::sync::PoisonError::into_inner);
31221 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31222 assert!(state.draining.is_none());
31223 }
31224
31225 #[cfg(napi_runtime_os_threads)]
31226 #[test]
31227 fn a_restart_after_a_settled_phase_one_still_joins_its_workers() {
31228 let controller = multi_thread_controller("two-phase-stale-settled-mt", 2, 1);
31232 drop(controller.backend());
31233 controller
31234 .shutdown()
31235 .expect("the first shutdown must complete");
31236 assert!(
31237 !controller
31238 .begin_shutdown()
31239 .expect("phase 1 on a stopped runtime must be accepted")
31240 );
31241 controller.start().expect("a stopped runtime must restart");
31242
31243 let backend = controller.backend();
31244 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31245 panic!("configured MultiThread backend must create a Rayon executor");
31246 };
31247 let workers = Arc::clone(&executor.worker_lifecycle);
31248 drop(backend);
31249 let handle = controller
31250 .try_spawn_blocking(|| 5usize)
31251 .unwrap_or_else(|_| panic!("the restarted generation must accept work"));
31252 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
31253
31254 controller
31255 .shutdown()
31256 .expect("the second shutdown must stop the restarted generation");
31257 assert_eq!(
31258 workers.remaining(),
31259 0,
31260 "phase 2 must join the restarted generation's workers"
31261 );
31262 {
31263 let state = controller
31264 .state
31265 .lock()
31266 .unwrap_or_else(std::sync::PoisonError::into_inner);
31267 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31268 assert!(state.draining.is_none());
31269 }
31270 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31271 panic!("a submission after the second shutdown must be rejected");
31272 };
31273 assert_eq!(
31274 error.to_string(),
31275 "the async runtime is stopped; call start before submitting work"
31276 );
31277 }
31278
31279 #[cfg(napi_runtime_os_threads)]
31283 struct ParkedRejectedDropFuture {
31284 entered: std::sync::mpsc::Sender<()>,
31285 release: std::sync::mpsc::Receiver<()>,
31286 }
31287
31288 #[cfg(napi_runtime_os_threads)]
31289 impl Future for ParkedRejectedDropFuture {
31290 type Output = ();
31291
31292 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31293 Poll::Pending
31294 }
31295 }
31296
31297 #[cfg(napi_runtime_os_threads)]
31298 impl Drop for ParkedRejectedDropFuture {
31299 fn drop(&mut self) {
31300 self.entered.send(()).unwrap();
31301 self.release.recv().unwrap();
31302 }
31303 }
31304
31305 #[cfg(napi_runtime_os_threads)]
31309 fn park_a_rejected_destructor(
31310 controller: &Arc<RuntimeController>,
31311 fail_backend_creation: bool,
31312 ) -> (std::thread::JoinHandle<()>, std::sync::mpsc::Sender<()>) {
31313 use std::sync::mpsc;
31314
31315 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
31316 let (release_drop_tx, release_drop_rx) = mpsc::channel();
31317 let submit_controller = Arc::clone(controller);
31318 let submitter = std::thread::spawn(move || {
31319 if fail_backend_creation {
31320 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
31321 }
31322 let result = futures::executor::block_on(submit_controller.spawn(ParkedRejectedDropFuture {
31323 entered: drop_entered_tx,
31324 release: release_drop_rx,
31325 }));
31326 assert!(
31327 result.is_err(),
31328 "the submission must be rejected, not accepted"
31329 );
31330 });
31331 drop_entered_rx
31332 .recv_timeout(Duration::from_secs(2))
31333 .expect("the rejected future destructor must start");
31334 (submitter, release_drop_tx)
31335 }
31336
31337 #[cfg(napi_runtime_os_threads)]
31343 fn begin_shutdown_within(
31344 controller: &Arc<RuntimeController>,
31345 what: &str,
31346 timeout: Duration,
31347 ) -> Result<bool, RuntimeConfigError> {
31348 use std::sync::mpsc;
31349
31350 let (tx, rx) = mpsc::channel();
31351 let begin_controller = Arc::clone(controller);
31352 let beginner = std::thread::spawn(move || {
31353 tx.send(begin_controller.begin_shutdown()).unwrap();
31354 });
31355 let answer = rx
31356 .recv_timeout(timeout)
31357 .unwrap_or_else(|_| panic!("phase 1 must not wait: {what}"));
31358 join_within(what, beginner, Duration::from_secs(2));
31359 answer
31360 }
31361
31362 #[cfg(napi_runtime_os_threads)]
31363 #[test]
31364 fn begin_shutdown_does_not_wait_for_rejected_destruction_on_a_stopped_runtime() {
31365 use std::sync::mpsc;
31373
31374 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-stopped"));
31375 drop(controller.backend());
31376 controller
31377 .shutdown()
31378 .expect("the first shutdown must complete");
31379
31380 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31381
31382 assert!(
31383 begin_shutdown_within(
31384 &controller,
31385 "phase 1 on a stopped runtime with a rejected destructor outstanding",
31386 Duration::from_secs(2),
31387 )
31388 .expect("phase 1 on a stopped runtime must be accepted"),
31389 "the outstanding rejected destruction is a wait phase 2 still owes"
31390 );
31391 assert!(
31392 controller.runtime_work_pending(),
31393 "the poll must report the destruction, or the host stops turning the loop that frees it"
31394 );
31395 {
31396 let state = controller
31397 .state
31398 .lock()
31399 .unwrap_or_else(std::sync::PoisonError::into_inner);
31400 assert!(matches!(
31401 &state.lifecycle,
31402 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
31403 ));
31404 assert!(matches!(
31405 &state.draining,
31406 Some(ShutdownDrain::ZeroBackend(
31407 ZeroBackendShutdownTarget::Stopped
31408 ))
31409 ));
31410 }
31411
31412 assert!(
31413 begin_shutdown_within(
31414 &controller,
31415 "a second phase 1 inside the zero-backend window",
31416 Duration::from_secs(2),
31417 )
31418 .expect("a second phase 1 must answer from the outstanding handoff"),
31419 "the second phase 1 must repeat the same verdict"
31420 );
31421
31422 let (finish_tx, finish_rx) = mpsc::channel();
31423 let finish_controller = Arc::clone(&controller);
31424 let finisher = std::thread::spawn(move || {
31425 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31426 });
31427 assert!(
31428 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31429 "phase 2 owns the wait phase 1 refused"
31430 );
31431
31432 release_drop_tx.send(()).unwrap();
31433 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31434 finish_rx
31435 .recv_timeout(Duration::from_secs(2))
31436 .expect("phase 2 must return once the destructor retires")
31437 .expect("phase 2 must complete the announced stop");
31438 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31439
31440 {
31441 let state = controller
31442 .state
31443 .lock()
31444 .unwrap_or_else(std::sync::PoisonError::into_inner);
31445 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31446 assert!(state.draining.is_none());
31447 }
31448 }
31449
31450 #[cfg(napi_runtime_os_threads)]
31451 #[test]
31452 fn begin_shutdown_does_not_wait_for_rejected_destruction_on_an_initial_runtime() {
31453 use std::sync::mpsc;
31459
31460 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-initial"));
31461 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, true);
31462
31463 assert!(
31464 begin_shutdown_within(
31465 &controller,
31466 "phase 1 on an initial runtime with a rejected destructor outstanding",
31467 Duration::from_secs(2),
31468 )
31469 .expect("phase 1 on an initial runtime must be accepted"),
31470 "the outstanding rejected destruction is a wait phase 2 still owes"
31471 );
31472 assert!(controller.runtime_work_pending());
31473 {
31474 let state = controller
31475 .state
31476 .lock()
31477 .unwrap_or_else(std::sync::PoisonError::into_inner);
31478 assert!(matches!(
31479 &state.lifecycle,
31480 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
31481 ));
31482 assert!(matches!(
31483 &state.draining,
31484 Some(ShutdownDrain::ZeroBackend(
31485 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
31486 ))
31487 ));
31488 }
31489 assert!(
31490 controller.try_spawn_detached(async {}).is_err(),
31491 "the announced stop must keep first-generation admission closed"
31492 );
31493
31494 let (finish_tx, finish_rx) = mpsc::channel();
31495 let finish_controller = Arc::clone(&controller);
31496 let finisher = std::thread::spawn(move || {
31497 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31498 });
31499 assert!(
31500 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31501 "phase 2 owns the wait phase 1 refused"
31502 );
31503
31504 release_drop_tx.send(()).unwrap();
31505 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31506 finish_rx
31507 .recv_timeout(Duration::from_secs(2))
31508 .expect("phase 2 must return once the destructor retires")
31509 .expect("phase 2 must complete the announced stop");
31510 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31511
31512 {
31513 let state = controller
31514 .state
31515 .lock()
31516 .unwrap_or_else(std::sync::PoisonError::into_inner);
31517 assert!(
31518 matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse),
31519 "a generation that never had a backend must stop before first use"
31520 );
31521 assert!(state.draining.is_none());
31522 }
31523 }
31524
31525 #[cfg(napi_runtime_os_threads)]
31526 #[test]
31527 fn start_waits_out_a_zero_backend_phase_one() {
31528 use std::sync::mpsc;
31536
31537 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-start"));
31538 let old_generation = controller.backend().generation();
31539 controller
31540 .shutdown()
31541 .expect("the first shutdown must complete");
31542
31543 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31544 assert!(
31545 begin_shutdown_within(
31546 &controller,
31547 "phase 1 before the restart",
31548 Duration::from_secs(2),
31549 )
31550 .expect("phase 1 on a stopped runtime must be accepted")
31551 );
31552
31553 let (start_tx, start_rx) = mpsc::channel();
31554 let start_controller = Arc::clone(&controller);
31555 let starter = std::thread::spawn(move || {
31556 start_tx.send(start_controller.start()).unwrap();
31557 });
31558 assert!(
31559 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31560 "a restart must wait for the announced zero-backend stop to be published"
31561 );
31562
31563 let (finish_tx, finish_rx) = mpsc::channel();
31564 let finish_controller = Arc::clone(&controller);
31565 let finisher = std::thread::spawn(move || {
31566 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31567 });
31568 assert!(
31569 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31570 "phase 2 must still be waiting on the destructor"
31571 );
31572
31573 release_drop_tx.send(()).unwrap();
31574 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31575 finish_rx
31576 .recv_timeout(Duration::from_secs(2))
31577 .expect("phase 2 must return once the destructor retires")
31578 .expect("phase 2 must complete the announced stop");
31579 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31580 start_rx
31581 .recv_timeout(Duration::from_secs(2))
31582 .expect("the restart must be admitted once phase 2 has published")
31583 .expect("a stopped runtime must restart after phase 2");
31584 join_within("the restart thread", starter, Duration::from_secs(2));
31585
31586 let new_generation = controller.backend().generation();
31587 assert_ne!(
31588 new_generation, old_generation,
31589 "the restart must publish a fresh generation"
31590 );
31591 controller
31592 .shutdown()
31593 .expect("the restarted generation must still shut down");
31594 }
31595
31596 #[cfg(napi_runtime_os_threads)]
31597 #[test]
31598 fn zero_backend_phase_one_settles_through_the_poll_before_finish() {
31599 use std::sync::mpsc;
31609
31610 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-poll"));
31611 drop(controller.backend());
31612 controller
31613 .shutdown()
31614 .expect("the first shutdown must complete");
31615
31616 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31617 assert!(
31618 begin_shutdown_within(
31619 &controller,
31620 "phase 1 before the loader's poll loop",
31621 Duration::from_secs(2),
31622 )
31623 .expect("phase 1 on a stopped runtime must be accepted"),
31624 "the outstanding rejected destruction is a wait phase 2 still owes"
31625 );
31626 assert!(
31627 controller.runtime_work_pending(),
31628 "the loader gates its poll loop on this, so it must report the destruction first"
31629 );
31630
31631 release_drop_tx.send(()).unwrap();
31632 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31633
31634 let deadline = std::time::Instant::now() + Duration::from_secs(5);
31637 while controller.runtime_work_pending() {
31638 assert!(
31639 std::time::Instant::now() < deadline,
31640 "the poll must stop reporting work once the rejected destructor retires"
31641 );
31642 std::thread::sleep(Duration::from_millis(1));
31643 }
31644
31645 let (finish_tx, finish_rx) = mpsc::channel();
31646 let finish_controller = Arc::clone(&controller);
31647 let finisher = std::thread::spawn(move || {
31648 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31649 });
31650 finish_rx
31651 .recv_timeout(Duration::from_millis(200))
31652 .expect("phase 2 entered after the poll cleared must not block")
31653 .expect("phase 2 must complete the announced stop");
31654 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31655
31656 {
31657 let state = controller
31658 .state
31659 .lock()
31660 .unwrap_or_else(std::sync::PoisonError::into_inner);
31661 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31662 assert!(state.draining.is_none());
31663 }
31664 }
31665
31666 #[cfg(napi_runtime_os_threads)]
31677 fn assert_poll_reports_pending_while_held(
31678 controller: RuntimeController,
31679 what: &str,
31680 hold: impl FnOnce(&RuntimeController, &RuntimeBackend),
31681 ) {
31682 use std::sync::mpsc;
31683
31684 let controller = Arc::new(controller);
31685 let backend = controller.backend();
31686 assert!(
31687 !controller
31688 .begin_shutdown()
31689 .expect("phase 1 on an idle generation must be accepted"),
31690 "an idle generation has nothing for the host to wait for"
31691 );
31692 assert!(
31693 !controller.runtime_work_pending(),
31694 "with every lock free the poll must read the idle generation"
31695 );
31696
31697 hold(&controller, &backend);
31698 let (pending_tx, pending_rx) = mpsc::channel();
31699 let poll_controller = Arc::clone(&controller);
31700 let poller = std::thread::spawn(move || {
31701 pending_tx
31702 .send(poll_controller.runtime_work_pending())
31703 .unwrap();
31704 });
31705 let pending = pending_rx
31706 .recv_timeout(Duration::from_secs(5))
31707 .unwrap_or_else(|_| panic!("the work-pending poll must not wait for {what}"));
31708 join_within(what, poller, Duration::from_secs(2));
31709 assert!(
31710 pending,
31711 "a poll that cannot read {what} must answer pending, not idle"
31712 );
31713
31714 std::mem::forget(backend);
31715 std::mem::forget(controller);
31716 }
31717
31718 #[cfg(napi_runtime_os_threads)]
31719 fn current_thread_executor(backend: &RuntimeBackend) -> &CurrentThreadExecutor {
31720 match &backend.executor {
31721 RuntimeExecutor::CurrentThread(executor) => executor,
31722 RuntimeExecutor::MultiThread(_) => panic!("expected a CurrentThread backend"),
31723 }
31724 }
31725
31726 #[cfg(napi_runtime_os_threads)]
31727 #[test]
31728 fn work_pending_poll_does_not_wait_for_a_held_controller_lock() {
31729 for controller in [
31730 current_thread_controller("poll-held-controller-lock-ct"),
31731 multi_thread_controller("poll-held-controller-lock-mt", 2, 1),
31732 ] {
31733 assert_poll_reports_pending_while_held(
31734 controller,
31735 "the controller state lock",
31736 |controller, _| {
31737 std::mem::forget(controller.state.lock().unwrap());
31738 },
31739 );
31740 }
31741 }
31742
31743 #[cfg(napi_runtime_os_threads)]
31744 #[test]
31745 fn work_pending_poll_does_not_wait_for_a_held_generation_lock() {
31746 for controller in [
31747 current_thread_controller("poll-held-generation-lock-ct"),
31748 multi_thread_controller("poll-held-generation-lock-mt", 2, 1),
31749 ] {
31750 assert_poll_reports_pending_while_held(
31751 controller,
31752 "the generation state lock",
31753 |_, backend| {
31754 std::mem::forget(backend.work.state.lock().unwrap());
31755 },
31756 );
31757 }
31758 }
31759
31760 #[cfg(napi_runtime_os_threads)]
31761 #[test]
31762 fn current_thread_work_pending_poll_does_not_wait_for_a_held_queue_lock() {
31763 assert_poll_reports_pending_while_held(
31764 current_thread_controller("poll-held-queue-lock"),
31765 "the CurrentThread queue lock",
31766 |_, backend| {
31767 std::mem::forget(current_thread_executor(backend).queue.lock().unwrap());
31768 },
31769 );
31770 }
31771
31772 #[cfg(napi_runtime_os_threads)]
31773 #[test]
31774 fn current_thread_work_pending_poll_does_not_wait_for_a_held_scheduler_lock() {
31775 assert_poll_reports_pending_while_held(
31776 current_thread_controller("poll-held-scheduler-lock"),
31777 "the CurrentThread scheduler lock",
31778 |_, backend| {
31779 std::mem::forget(
31780 current_thread_executor(backend)
31781 .scheduler_idle_lock
31782 .lock()
31783 .unwrap(),
31784 );
31785 },
31786 );
31787 }
31788
31789 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31799 fn assert_shutdown_phase_gives_up_after_a_thread_crash(
31800 what: &str,
31801 controller: Arc<RuntimeController>,
31802 phase: impl FnOnce(&RuntimeController) + Send + 'static,
31803 ) {
31804 use std::sync::mpsc;
31805
31806 let crashed = Arc::new(AtomicBool::new(false));
31807 let (outcome_tx, outcome_rx) = mpsc::channel();
31808 let js_thread = {
31809 let controller = Arc::clone(&controller);
31810 let crashed = Arc::clone(&crashed);
31811 std::thread::spawn(move || {
31812 crate::sync::watch_crash_flag_for_test(crashed);
31813 let outcome = match catch_unwind(AssertUnwindSafe(|| phase(&controller))) {
31814 Ok(()) => "returned",
31815 Err(payload) if payload.is::<crate::sync::GaveUpAfterThreadCrash>() => "gave up",
31816 Err(_) => "panicked",
31817 };
31818 outcome_tx.send(outcome).unwrap();
31819 })
31820 };
31821 assert!(
31822 outcome_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31823 "{what}: with no thread crashed, the phase must keep waiting"
31824 );
31825 crashed.store(true, Ordering::SeqCst);
31826 let outcome = outcome_rx
31827 .recv_timeout(Duration::from_secs(5))
31828 .unwrap_or_else(|_| panic!("{what}: the phase must stop waiting once a thread crashed"));
31829 join_within(what, js_thread, Duration::from_secs(2));
31830 assert_eq!(outcome, "gave up", "{what}");
31831 std::mem::forget(controller);
31832 }
31833
31834 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31835 #[test]
31836 fn begin_shutdown_gives_up_on_a_held_lock_after_a_thread_crash() {
31837 type Hold = fn(&RuntimeController, &RuntimeBackend);
31838 let holds: [(&str, Hold); 2] = [
31839 ("the controller state lock", |controller, _| {
31840 std::mem::forget(controller.state.lock().unwrap());
31841 }),
31842 ("the generation state lock", |_, backend| {
31844 std::mem::forget(backend.work.state.lock().unwrap());
31845 }),
31846 ];
31847 for (what, hold) in holds {
31848 for controller in [
31849 current_thread_controller("crash-begin-ct"),
31850 multi_thread_controller("crash-begin-mt", 2, 1),
31851 ] {
31852 let controller = Arc::new(controller);
31853 let backend = controller.backend();
31854 hold(&controller, &backend);
31855 assert_shutdown_phase_gives_up_after_a_thread_crash(
31856 &format!("begin_shutdown on {what}"),
31857 controller,
31858 |controller| {
31859 let _ = controller.begin_shutdown();
31860 },
31861 );
31862 std::mem::forget(backend);
31863 }
31864 }
31865 }
31866
31867 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31868 #[test]
31869 fn finish_shutdown_gives_up_after_a_thread_crash() {
31870 for controller in [
31871 current_thread_controller("crash-finish-work-ct"),
31872 multi_thread_controller("crash-finish-work-mt", 2, 1),
31873 ] {
31874 let controller = Arc::new(controller);
31877 let backend = controller.backend();
31878 let work = backend
31879 .work
31880 .try_register_work()
31881 .expect("an open generation must accept work");
31882 assert!(
31883 controller
31884 .begin_shutdown()
31885 .expect("phase 1 must be accepted"),
31886 "phase 1 must report the registered work"
31887 );
31888 std::mem::forget(work);
31889 assert_shutdown_phase_gives_up_after_a_thread_crash(
31890 "finish_shutdown on work that never retires",
31891 controller,
31892 |controller| {
31893 let _ = controller.finish_shutdown();
31894 },
31895 );
31896 std::mem::forget(backend);
31897 }
31898 for controller in [
31899 current_thread_controller("crash-finish-lock-ct"),
31900 multi_thread_controller("crash-finish-lock-mt", 2, 1),
31901 ] {
31902 let controller = Arc::new(controller);
31903 let backend = controller.backend();
31904 assert!(
31905 !controller
31906 .begin_shutdown()
31907 .expect("phase 1 on an idle generation must be accepted"),
31908 "an idle generation has nothing for the host to wait for"
31909 );
31910 std::mem::forget(backend.work.state.lock().unwrap());
31911 assert_shutdown_phase_gives_up_after_a_thread_crash(
31912 "finish_shutdown on the generation state lock",
31913 controller,
31914 |controller| {
31915 let _ = controller.finish_shutdown();
31916 },
31917 );
31918 std::mem::forget(backend);
31919 }
31920 }
31921
31922 #[cfg(napi_runtime_os_threads)]
31929 #[test]
31930 fn multi_thread_blocking_active_never_exceeds_max_blocking() {
31931 use std::sync::{Barrier, mpsc};
31953 use std::time::Duration;
31954
31955 let (done_tx, done_rx) = mpsc::channel();
31956 let peak_out = Arc::new(Mutex::new(None));
31957 let peak_slot = Arc::clone(&peak_out);
31958 let runner = std::thread::spawn(move || {
31959 let metrics = Arc::new(RuntimeMetrics::default());
31960 let options = RuntimeOptions {
31961 flavor: RuntimeFlavor::MultiThread,
31962 worker_threads: 4,
31963 max_blocking_tasks: 2,
31964 thread_name_prefix: "rd9-blk-cap".to_string(),
31965 park_deadline: None,
31966 drain_linger: DEFAULT_DRAIN_LINGER,
31967 };
31968 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
31969
31970 let holder_count = options.max_blocking_tasks; let extra_count = 4usize; let (held_tx, held_rx) = mpsc::channel();
31975 let release = Arc::new(Barrier::new(holder_count + 1));
31976 let mut holder_handles = Vec::new();
31977 for _ in 0..holder_count {
31978 let held_tx = held_tx.clone();
31979 let release = Arc::clone(&release);
31980 holder_handles.push(executor.schedule_blocking(move || {
31981 held_tx.send(()).unwrap();
31982 release.wait();
31983 0usize
31984 }));
31985 }
31986 for _ in 0..holder_count {
31988 held_rx.recv().unwrap();
31989 }
31990
31991 let (extra_started_tx, extra_started_rx) = mpsc::channel();
31993 let mut extra_handles = Vec::new();
31994 for i in 0..extra_count {
31995 let extra_started_tx = extra_started_tx.clone();
31996 extra_handles.push(executor.schedule_blocking(move || {
31997 extra_started_tx.send(i).unwrap();
31998 i * 2
31999 }));
32000 }
32001
32002 assert!(
32005 matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
32006 "a beyond-cap blocking job ran before the cap was released (cap check broken)"
32007 );
32008
32009 release.wait();
32011
32012 for handle in holder_handles {
32013 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
32014 }
32015 for (i, handle) in extra_handles.into_iter().enumerate() {
32018 assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
32019 }
32020
32021 assert_eq!(
32024 metrics.blocking_tasks_started.load(Ordering::Relaxed),
32025 (holder_count + extra_count) as u64
32026 );
32027
32028 *peak_slot
32029 .lock()
32030 .unwrap_or_else(std::sync::PoisonError::into_inner) =
32031 Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
32032 let _ = done_tx.send(());
32033 });
32034
32035 match done_rx.recv_timeout(Duration::from_secs(10)) {
32036 Ok(()) => runner.join().unwrap(),
32037 Err(error) => panic!("blocking-cap test did not complete ({error})"),
32038 }
32039 let peak = peak_out
32040 .lock()
32041 .unwrap_or_else(std::sync::PoisonError::into_inner)
32042 .expect("peak captured");
32043 assert!(
32044 peak >= 1,
32045 "at least one blocking job must have run (peak {peak})"
32046 );
32047 assert!(
32048 peak <= 2,
32049 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
32050 );
32051 }
32052
32053 #[cfg(napi_runtime_os_threads)]
32054 #[test]
32055 fn multi_thread_drain_budget_completes_large_runnable_batch() {
32056 use std::sync::mpsc;
32061 use std::time::{Duration, Instant};
32062
32063 let (done_tx, done_rx) = mpsc::channel();
32064 let runner = std::thread::spawn(move || {
32065 let metrics = Arc::new(RuntimeMetrics::default());
32066 let options = RuntimeOptions {
32067 flavor: RuntimeFlavor::MultiThread,
32068 worker_threads: 2,
32069 max_blocking_tasks: 2,
32070 thread_name_prefix: "rd9-budget".to_string(),
32071 park_deadline: None,
32072 drain_linger: DEFAULT_DRAIN_LINGER,
32073 };
32074 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32075
32076 let task_count = 200usize; let completed = Arc::new(AtomicU64::new(0));
32078 let mut tasks = Vec::new();
32079 for _ in 0..task_count {
32080 let completed = Arc::clone(&completed);
32081 let scheduler = Arc::clone(&executor);
32082 let (runnable, task) = async_task::spawn(
32083 async move {
32084 completed.fetch_add(1, Ordering::Relaxed);
32085 },
32086 move |r| scheduler.schedule(r),
32087 );
32088 executor.schedule(runnable);
32089 tasks.push(task);
32090 }
32091 for task in tasks {
32092 futures::executor::block_on(task);
32093 }
32094
32095 assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
32098 assert_eq!(
32099 metrics.runnable_polls.load(Ordering::Relaxed),
32100 task_count as u64
32101 );
32102
32103 let deadline = Instant::now() + Duration::from_secs(5);
32107 loop {
32108 let active = metrics.active_runnables.load(Ordering::Relaxed);
32109 let queued = metrics.queued_runnables.load(Ordering::Relaxed);
32110 if active == 0 && queued == 0 {
32111 break;
32112 }
32113 assert!(
32114 Instant::now() < deadline,
32115 "runnable counters did not settle to 0: active={active} queued={queued}"
32116 );
32117 std::thread::yield_now();
32118 }
32119 let _ = done_tx.send(());
32120 });
32121
32122 match done_rx.recv_timeout(Duration::from_secs(15)) {
32123 Ok(()) => runner.join().unwrap(),
32124 Err(error) => panic!("drain-budget test did not complete ({error})"),
32125 }
32126 }
32127
32128 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32129 #[test]
32130 fn multi_thread_spawned_panic_surfaces_as_join_error() {
32131 use std::sync::mpsc;
32138 use std::time::Duration;
32139
32140 let (done_tx, done_rx) = mpsc::channel();
32141 let runner = std::thread::spawn(move || {
32142 let metrics = Arc::new(RuntimeMetrics::default());
32143 let options = RuntimeOptions {
32144 flavor: RuntimeFlavor::MultiThread,
32145 worker_threads: 2,
32146 max_blocking_tasks: 2,
32147 thread_name_prefix: "rd9-panic".to_string(),
32148 park_deadline: None,
32149 drain_linger: DEFAULT_DRAIN_LINGER,
32150 };
32151 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
32152
32153 let scheduler = Arc::clone(&executor);
32154 let wrapped = async {
32155 AssertUnwindSafe(async { panic!("x") })
32156 .catch_unwind()
32157 .await
32158 .map(|()| ContainedTaskOutput::new((), u64::MAX))
32159 .map_err(panic_payload_to_join_error)
32160 };
32161 let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
32162 executor.schedule(runnable);
32163
32164 let result: Result<(), JoinError> =
32165 futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
32166 task: task.fallible(),
32167 awaiter: None,
32168 dependency: Arc::new(TaskDependency::default()),
32169 }));
32170 let _ = done_tx.send(result);
32171 });
32172
32173 let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
32174 Ok(result) => {
32175 runner.join().unwrap();
32176 result
32177 }
32178 Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
32179 };
32180 let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
32181 assert_eq!(error.to_string(), "x");
32182 }
32183
32184 #[cfg(napi_runtime_os_threads)]
32185 #[test]
32186 fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
32187 use std::sync::mpsc;
32188
32189 let metrics = Arc::new(RuntimeMetrics::default());
32190 metrics.tasks_spawned.store(1, Ordering::Relaxed);
32191 metrics.tasks_completed.store(2, Ordering::Relaxed);
32192 metrics.tasks_panicked.store(3, Ordering::Relaxed);
32193 metrics.runnable_schedules.store(4, Ordering::Relaxed);
32194 metrics.runnable_polls.store(5, Ordering::Relaxed);
32195 metrics
32196 .blocking_tasks_scheduled
32197 .store(14, Ordering::Relaxed);
32198 metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
32199 metrics
32200 .blocking_tasks_completed
32201 .store(11, Ordering::Relaxed);
32202 metrics.runnable_scheduled();
32203 let (guards_live_tx, guards_live_rx) = mpsc::channel();
32204 let (release_tx, release_rx) = mpsc::channel();
32205 let guard_metrics = Arc::clone(&metrics);
32206 let guard_thread = std::thread::spawn(move || {
32207 let runnable_guard = guard_metrics.runnable_started();
32208 let blocking_guard = guard_metrics.blocking_started(true);
32209 guards_live_tx.send(()).unwrap();
32210 release_rx.recv().unwrap();
32211 drop(runnable_guard);
32212 drop(blocking_guard);
32213 });
32214 guards_live_rx.recv().unwrap();
32215 let fingerprint_before_reset = metrics.progress_fingerprint();
32216
32217 metrics.reset();
32218
32219 assert!(
32220 metrics.progress_fingerprint() != fingerprint_before_reset,
32221 "a reset must advance the deadlock detector generation even if counters later repeat"
32222 );
32223 assert_eq!(
32224 metrics.tasks_spawned.load(Ordering::Relaxed),
32225 0,
32226 "tasks_spawned"
32227 );
32228 assert_eq!(
32229 metrics.tasks_completed.load(Ordering::Relaxed),
32230 0,
32231 "tasks_completed"
32232 );
32233 assert_eq!(
32234 metrics.tasks_panicked.load(Ordering::Relaxed),
32235 0,
32236 "tasks_panicked"
32237 );
32238 assert_eq!(
32239 metrics.runnable_schedules.load(Ordering::Relaxed),
32240 0,
32241 "runnable_schedules"
32242 );
32243 assert_eq!(
32244 metrics.runnable_polls.load(Ordering::Relaxed),
32245 0,
32246 "runnable_polls"
32247 );
32248 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
32249 assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
32250 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
32251 assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
32252 assert_eq!(
32253 metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
32254 0,
32255 "blocking_tasks_scheduled"
32256 );
32257 assert_eq!(
32258 metrics.blocking_tasks_started.load(Ordering::Relaxed),
32259 0,
32260 "blocking_tasks_started"
32261 );
32262 assert_eq!(
32263 metrics.blocking_tasks_completed.load(Ordering::Relaxed),
32264 0,
32265 "blocking_tasks_completed"
32266 );
32267 assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
32268 assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
32269
32270 release_tx.send(()).unwrap();
32271 guard_thread.join().unwrap();
32272 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
32273 assert_eq!(
32274 metrics.active_blocking_tasks.load(Ordering::Relaxed),
32275 0,
32276 "a live blocking guard completed after reset without unsigned underflow"
32277 );
32278 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
32279 }
32280
32281 #[cfg(napi_runtime_os_threads)]
32282 #[test]
32283 fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
32284 use std::sync::mpsc;
32285
32286 fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
32287 RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
32288 *slot.borrow_mut() = Some(Box::new(move || {
32289 entered.send(()).unwrap();
32290 release.recv().unwrap();
32291 }));
32292 });
32293 }
32294
32295 let options = RuntimeOptions {
32296 flavor: RuntimeFlavor::CurrentThread,
32297 worker_threads: 1,
32298 max_blocking_tasks: 1,
32299 thread_name_prefix: "metrics-snapshot".to_string(),
32300 park_deadline: None,
32301 drain_linger: DEFAULT_DRAIN_LINGER,
32302 };
32303
32304 let queued_metrics = Arc::new(RuntimeMetrics::default());
32305 let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
32306 let (queued_release_tx, queued_release_rx) = mpsc::channel();
32307 let queued_writer_metrics = Arc::clone(&queued_metrics);
32308 let queued_writer = std::thread::spawn(move || {
32309 pause_next_high_water_update(queued_entered_tx, queued_release_rx);
32310 queued_writer_metrics.runnable_scheduled();
32311 });
32312 queued_entered_rx.recv().unwrap();
32313 assert_eq!(
32314 queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
32315 0
32316 );
32317 let snapshot = queued_metrics.snapshot(&options);
32318 assert_eq!(snapshot.queued_runnables, 1);
32319 assert_eq!(snapshot.max_queued_runnables, 1);
32320 queued_release_tx.send(()).unwrap();
32321 queued_writer.join().unwrap();
32322 queued_metrics.runnable_cancelled();
32323
32324 let active_metrics = Arc::new(RuntimeMetrics::default());
32325 active_metrics.queued_runnables.store(1, Ordering::Relaxed);
32326 let (active_entered_tx, active_entered_rx) = mpsc::channel();
32327 let (active_release_tx, active_release_rx) = mpsc::channel();
32328 let active_writer_metrics = Arc::clone(&active_metrics);
32329 let active_writer = std::thread::spawn(move || {
32330 pause_next_high_water_update(active_entered_tx, active_release_rx);
32331 let _active = active_writer_metrics.runnable_started();
32332 });
32333 active_entered_rx.recv().unwrap();
32334 assert_eq!(
32335 active_metrics.max_active_runnables.load(Ordering::Relaxed),
32336 0
32337 );
32338 let snapshot = active_metrics.snapshot(&options);
32339 assert_eq!(snapshot.active_runnables, 1);
32340 assert_eq!(snapshot.max_active_runnables, 1);
32341 active_release_tx.send(()).unwrap();
32342 active_writer.join().unwrap();
32343
32344 let blocking_metrics = Arc::new(RuntimeMetrics::default());
32345 let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
32346 let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
32347 let blocking_writer_metrics = Arc::clone(&blocking_metrics);
32348 let blocking_writer = std::thread::spawn(move || {
32349 pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
32350 let _active = blocking_writer_metrics.blocking_started(true);
32351 });
32352 blocking_entered_rx.recv().unwrap();
32353 assert_eq!(
32354 blocking_metrics
32355 .max_active_blocking_tasks
32356 .load(Ordering::Relaxed),
32357 0
32358 );
32359 let snapshot = blocking_metrics.snapshot(&options);
32360 assert_eq!(snapshot.active_blocking_tasks, 1);
32361 assert_eq!(snapshot.max_active_blocking_tasks, 1);
32362 blocking_release_tx.send(()).unwrap();
32363 blocking_writer.join().unwrap();
32364 }
32365
32366 #[cfg(panic = "unwind")]
32367 #[test]
32368 fn runtime_metrics_reset_fails_before_generation_wraparound() {
32369 let metrics = RuntimeMetrics::default();
32370 metrics
32371 .reset_generation
32372 .store(u64::MAX - 1, Ordering::SeqCst);
32373 metrics.tasks_spawned.store(7, Ordering::Relaxed);
32374
32375 let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
32376 assert!(exhausted.is_err());
32377 assert!(!metrics.reset_lock.is_poisoned());
32378 assert_eq!(
32379 metrics.reset_generation.load(Ordering::SeqCst),
32380 u64::MAX - 1
32381 );
32382 assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
32383 }
32384
32385 struct NeverReady;
32397 impl Future for NeverReady {
32398 type Output = ();
32399 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32400 Poll::Pending
32401 }
32402 }
32403
32404 #[cfg(napi_runtime_os_threads)]
32405 struct ReadyAfterOwnerLanePublication {
32406 dependency: BlockingDependency,
32407 ready: Arc<AtomicBool>,
32408 }
32409
32410 #[cfg(napi_runtime_os_threads)]
32411 impl Future for ReadyAfterOwnerLanePublication {
32412 type Output = ();
32413
32414 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32415 if self.ready.load(Ordering::SeqCst) {
32416 Poll::Ready(())
32417 } else {
32418 let mut published = PublishedBlockingDependencies::default();
32421 record_blocking_dependency(&mut published, self.dependency);
32422 Poll::Pending
32423 }
32424 }
32425 }
32426
32427 #[cfg(panic = "unwind")]
32428 #[test]
32429 fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
32430 use std::sync::mpsc;
32437
32438 let (done_tx, done_rx) = mpsc::channel();
32439 let runner = std::thread::spawn(move || {
32440 let metrics = Arc::new(RuntimeMetrics::default());
32441 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32444
32445 let payload = catch_unwind(AssertUnwindSafe(|| {
32446 let mut future = std::pin::pin!(NeverReady);
32447 executor.block_on(future.as_mut());
32448 }))
32449 .expect_err("a threadless park with an empty queue and no pending wake must panic");
32450 let diagnostic = payload
32451 .downcast_ref::<BlockOnDeadlock>()
32452 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
32453 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
32454 assert_eq!(
32455 diagnostic.park_deadline, None,
32456 "the certain case carries no deadline"
32457 );
32458 let message = diagnostic.to_string();
32459 assert!(
32460 message.contains("block_on") && message.contains("JS"),
32461 "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
32462 );
32463 let _ = done_tx.send(());
32464 });
32465
32466 match done_rx.recv_timeout(Duration::from_secs(10)) {
32467 Ok(()) => runner.join().unwrap(),
32468 Err(error) => panic!(
32469 "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
32470 ),
32471 }
32472 }
32473
32474 #[cfg(napi_runtime_os_threads)]
32475 #[test]
32476 fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
32477 use std::sync::mpsc;
32483
32484 struct PendThenReady {
32485 polls: usize,
32486 }
32487 impl Future for PendThenReady {
32488 type Output = ();
32489 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
32490 self.polls += 1;
32491 if self.polls >= 2 {
32492 Poll::Ready(())
32493 } else {
32494 cx.waker().wake_by_ref();
32495 Poll::Pending
32496 }
32497 }
32498 }
32499
32500 let (done_tx, done_rx) = mpsc::channel();
32501 let runner = std::thread::spawn(move || {
32502 let metrics = Arc::new(RuntimeMetrics::default());
32503 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32504 let mut future = std::pin::pin!(PendThenReady { polls: 0 });
32505 executor.block_on(future.as_mut());
32506 assert_eq!(
32507 future.polls, 2,
32508 "the self-woken future must have been re-polled to completion"
32509 );
32510 let _ = done_tx.send(());
32511 });
32512
32513 match done_rx.recv_timeout(Duration::from_secs(10)) {
32514 Ok(()) => runner.join().unwrap(),
32515 Err(error) => panic!(
32516 "a self-waking Pending future must complete without panicking on a threadless build ({error})"
32517 ),
32518 }
32519 }
32520
32521 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32522 #[test]
32523 fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
32524 use std::sync::mpsc;
32529
32530 let (done_tx, done_rx) = mpsc::channel();
32531 let runner = std::thread::spawn(move || {
32532 let metrics = Arc::new(RuntimeMetrics::default());
32533 let executor = Arc::new(CurrentThreadExecutor::with_detection(
32534 metrics,
32535 false,
32536 Some(Duration::from_millis(50)),
32537 ));
32538
32539 let payload = catch_unwind(AssertUnwindSafe(|| {
32540 let mut future = std::pin::pin!(NeverReady);
32541 executor.block_on(future.as_mut());
32542 }))
32543 .expect_err("a dead native CT park must fire the armed deadline");
32544 let diagnostic = payload
32545 .downcast_ref::<BlockOnDeadlock>()
32546 .expect("typed payload");
32547 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
32548 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32549 drop(executor);
32552 let _ = done_tx.send(());
32553 });
32554
32555 match done_rx.recv_timeout(Duration::from_secs(10)) {
32556 Ok(()) => join_within(
32557 "the CT deadline-fire runner exits after teardown",
32558 runner,
32559 Duration::from_secs(30),
32560 ),
32561 Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
32562 }
32563 }
32564
32565 #[cfg(napi_runtime_os_threads)]
32566 #[test]
32567 fn current_thread_native_park_deadline_resets_on_runtime_progress() {
32568 use std::sync::mpsc;
32585
32586 let deadline = Duration::from_millis(1000);
32587 let metrics = Arc::new(RuntimeMetrics::default());
32588 let executor = Arc::new(CurrentThreadExecutor::with_detection(
32589 Arc::clone(&metrics),
32590 false,
32591 Some(deadline),
32592 ));
32593
32594 let (value_tx, value_rx) = oneshot::channel::<usize>();
32595 let (done_tx, done_rx) = mpsc::channel();
32596 let runner = std::thread::spawn(move || {
32597 let mut output = None;
32598 {
32599 let mut future = std::pin::pin!(async {
32600 output = Some(value_rx.await.unwrap());
32601 });
32602 executor.block_on(future.as_mut());
32603 }
32604 assert_eq!(output, Some(9));
32605 drop(executor);
32607 let _ = done_tx.send(());
32608 });
32609
32610 let hold_until = Instant::now() + deadline * 5 / 2;
32616 while Instant::now() < hold_until {
32617 std::thread::sleep(Duration::from_millis(25));
32618 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32619 }
32620 value_tx.send(9).unwrap();
32621
32622 match done_rx.recv_timeout(Duration::from_secs(30)) {
32623 Ok(()) => join_within(
32624 "the CT deadline-reset runner exits after teardown",
32625 runner,
32626 Duration::from_secs(30),
32627 ),
32628 Err(error) => panic!(
32629 "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
32630 ),
32631 }
32632 }
32633
32634 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32635 #[test]
32636 fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
32637 use std::sync::mpsc;
32642
32643 let (done_tx, done_rx) = mpsc::channel();
32644 let runner = std::thread::spawn(move || {
32645 let options = RuntimeOptions {
32646 flavor: RuntimeFlavor::MultiThread,
32647 worker_threads: 1,
32648 max_blocking_tasks: 1,
32649 thread_name_prefix: "deadline-fire".to_string(),
32650 park_deadline: Some(Duration::from_millis(50)),
32651 drain_linger: DEFAULT_DRAIN_LINGER,
32652 };
32653 let executor =
32654 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32655
32656 let payload = {
32657 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32658 catch_unwind(AssertUnwindSafe(|| {
32659 let mut future = std::pin::pin!(NeverReady);
32660 executor.block_on(future.as_mut());
32661 }))
32662 .expect_err("a dead cooperative park must fire the armed deadline")
32663 };
32664 let diagnostic = payload
32665 .downcast_ref::<BlockOnDeadlock>()
32666 .expect("typed payload");
32667 assert_eq!(
32668 diagnostic.kind,
32669 BlockOnDeadlockKind::MultiThreadCooperativeDeadline
32670 );
32671 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32672 let message = diagnostic.to_string();
32673 assert!(
32674 message.contains("block_on"),
32675 "diagnostic must name the hazard, got: {message}"
32676 );
32677 assert_eq!(
32678 executor.parked_drivers.count.load(Ordering::SeqCst),
32679 0,
32680 "the firing driver must deregister itself before panicking"
32681 );
32682 drop(executor);
32684 let _ = done_tx.send(());
32685 });
32686
32687 match done_rx.recv_timeout(Duration::from_secs(10)) {
32688 Ok(()) => join_within(
32689 "the MT deadline-fire runner exits after executor teardown",
32690 runner,
32691 Duration::from_secs(30),
32692 ),
32693 Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
32694 }
32695 }
32696
32697 #[cfg(napi_runtime_os_threads)]
32698 #[test]
32699 fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
32700 use std::sync::mpsc;
32716
32717 let (done_tx, done_rx) = mpsc::channel();
32718 let runner = std::thread::spawn(move || {
32719 let deadline = Duration::from_millis(1000);
32720 let metrics = Arc::new(RuntimeMetrics::default());
32721 let options = RuntimeOptions {
32722 flavor: RuntimeFlavor::MultiThread,
32723 worker_threads: 1,
32724 max_blocking_tasks: 1,
32725 thread_name_prefix: "deadline-reset".to_string(),
32726 park_deadline: Some(deadline),
32727 drain_linger: DEFAULT_DRAIN_LINGER,
32728 };
32729 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32730
32731 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32734 let td_exec = Arc::clone(&executor);
32735 let completed = Arc::new(AtomicBool::new(false));
32736 let td_flag = Arc::clone(&completed);
32737 let td_body = async move {
32738 let mut inner = std::pin::pin!(async move {
32739 assert_eq!(gate_rx.await.unwrap(), 9);
32740 });
32741 td_exec.block_on(inner.as_mut());
32742 td_flag.store(true, Ordering::SeqCst);
32743 };
32744 let sched = Arc::clone(&executor);
32745 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
32746 executor.schedule(td_runnable);
32747 wait_until("the sole driver parked", || {
32748 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
32749 });
32750
32751 let hold_until = Instant::now() + deadline * 5 / 2;
32757 while Instant::now() < hold_until {
32758 std::thread::sleep(Duration::from_millis(25));
32759 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32760 }
32761 assert!(
32762 !completed.load(Ordering::SeqCst),
32763 "the driver must still be parked awaiting its late wake"
32764 );
32765 gate_tx.send(9).unwrap();
32766 futures::executor::block_on(td_task);
32767 assert!(
32768 completed.load(Ordering::SeqCst),
32769 "the late wake must complete the parked driver"
32770 );
32771 drop(executor);
32773 let _ = done_tx.send(());
32774 });
32775
32776 match done_rx.recv_timeout(Duration::from_secs(30)) {
32777 Ok(()) => join_within(
32778 "the MT deadline-reset runner exits after executor teardown",
32779 runner,
32780 Duration::from_secs(30),
32781 ),
32782 Err(error) => panic!(
32783 "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
32784 ),
32785 }
32786 }
32787
32788 #[cfg(napi_runtime_os_threads)]
32789 #[test]
32790 fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
32791 use std::sync::mpsc;
32796
32797 let (done_tx, done_rx) = mpsc::channel();
32798 let runner = std::thread::spawn(move || {
32799 let options = RuntimeOptions {
32800 flavor: RuntimeFlavor::MultiThread,
32801 worker_threads: 2,
32802 max_blocking_tasks: 2,
32803 thread_name_prefix: "foreign-exempt".to_string(),
32804 park_deadline: Some(Duration::from_millis(40)),
32805 drain_linger: DEFAULT_DRAIN_LINGER,
32806 };
32807 let executor =
32808 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32809
32810 let (value_tx, value_rx) = oneshot::channel::<usize>();
32811 let waker_thread = std::thread::spawn(move || {
32812 std::thread::sleep(Duration::from_millis(300));
32814 let _ = value_tx.send(7);
32815 });
32816
32817 let started = Instant::now();
32818 let mut output = None;
32819 {
32820 let mut future = std::pin::pin!(async {
32821 output = Some(value_rx.await.unwrap());
32822 });
32823 executor.block_on(future.as_mut());
32825 }
32826 assert_eq!(output, Some(7));
32827 assert!(
32828 started.elapsed() >= Duration::from_millis(250),
32829 "the park must genuinely have outlived the 40ms deadline many times over"
32830 );
32831 waker_thread.join().unwrap();
32832 drop(executor);
32837 let _ = done_tx.send(());
32838 });
32839
32840 match done_rx.recv_timeout(Duration::from_secs(10)) {
32841 Ok(()) => join_within(
32842 "the exempt foreign-park runner exits after executor teardown",
32843 runner,
32844 Duration::from_secs(30),
32845 ),
32846 Err(error) => {
32847 panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
32848 }
32849 }
32850 }
32851
32852 #[cfg(napi_runtime_os_threads)]
32853 #[test]
32854 fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
32855 use std::sync::mpsc;
32870
32871 let (done_tx, done_rx) = mpsc::channel();
32872 let runner = std::thread::spawn(move || {
32873 let options = RuntimeOptions {
32874 flavor: RuntimeFlavor::MultiThread,
32875 worker_threads: 1,
32876 max_blocking_tasks: 1,
32877 thread_name_prefix: "edge-wake".to_string(),
32878 park_deadline: Some(Duration::from_millis(40)),
32879 drain_linger: DEFAULT_DRAIN_LINGER,
32880 };
32881 let executor =
32882 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32883
32884 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32886 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
32888
32889 let hook_exec = Arc::clone(&executor);
32890 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
32891 *slot.borrow_mut() = Some(Box::new(move || {
32892 let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32893 let handle = hook_exec.schedule_blocking(move || {
32897 let _ = gate_tx.send(5usize);
32898 0usize
32899 });
32900 drop(handle); let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32902 hook_saw_tx.send((before, after)).unwrap();
32903 }));
32904 });
32905
32906 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32909 let mut output = None;
32910 {
32911 let mut future = std::pin::pin!(async {
32912 output = Some(gate_rx.await.unwrap());
32913 });
32914 executor.block_on(future.as_mut());
32915 }
32916 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
32921 assert_eq!(
32922 output,
32923 Some(5),
32924 "the edge-delivered wake's job must have run and completed the future"
32925 );
32926
32927 let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
32928 assert_eq!(
32929 before, 1,
32930 "the hook must observe the parker STILL registered (race window)"
32931 );
32932 assert_eq!(
32933 after, 0,
32934 "the racing wake_one must have popped the parker (wake DELIVERED)"
32935 );
32936 drop(executor);
32938 let _ = done_tx.send(());
32939 });
32940
32941 match done_rx.recv_timeout(Duration::from_secs(10)) {
32942 Ok(()) => join_within(
32943 "the expiry-edge-wake runner exits after executor teardown",
32944 runner,
32945 Duration::from_secs(30),
32946 ),
32947 Err(error) => panic!(
32948 "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
32949 ),
32950 }
32951 }
32952
32953 #[cfg(napi_runtime_os_threads)]
32954 #[test]
32955 fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
32956 use std::sync::mpsc;
32968
32969 let (done_tx, done_rx) = mpsc::channel();
32970 let runner = std::thread::spawn(move || {
32971 let options = RuntimeOptions {
32972 flavor: RuntimeFlavor::MultiThread,
32973 worker_threads: 1,
32974 max_blocking_tasks: 1,
32975 thread_name_prefix: "verdict-blocking".to_string(),
32976 park_deadline: Some(Duration::from_millis(40)),
32977 drain_linger: DEFAULT_DRAIN_LINGER,
32978 };
32979 let executor =
32980 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32981 executor
32982 .active_drainers
32983 .store(executor.max_drainers, Ordering::SeqCst);
32984
32985 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32987 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
32990
32991 let hook_exec = Arc::clone(&executor);
32992 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
32993 *slot.borrow_mut() = Some(Box::new(move || {
32994 let handle = hook_exec.schedule_blocking(move || {
32995 let _ = gate_tx.send(11usize);
32996 0usize
32997 });
32998 drop(handle); hook_saw_tx
33000 .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
33001 .unwrap();
33002 }));
33003 });
33004
33005 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33008 let mut output = None;
33009 {
33010 let mut future = std::pin::pin!(async {
33011 output = Some(gate_rx.await.unwrap());
33012 });
33013 executor.block_on(future.as_mut());
33014 }
33015 assert_eq!(
33016 output,
33017 Some(11),
33018 "the enqueued job must have been run by the surviving driver via run_one"
33019 );
33020
33021 let registered_at_hook = hook_saw_rx
33022 .recv()
33023 .expect("the verdict hook must have fired");
33024 assert_eq!(
33025 registered_at_hook, 0,
33026 "the hook fires post-deregister: no permit can be delivered, only the fingerprint's enqueue counter can catch this submission"
33027 );
33028 executor.active_drainers.store(0, Ordering::SeqCst);
33029 let _ = done_tx.send(());
33030 });
33031
33032 match done_rx.recv_timeout(Duration::from_secs(10)) {
33033 Ok(()) => runner.join().unwrap(),
33034 Err(error) => panic!(
33035 "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
33036 ),
33037 }
33038 }
33039
33040 #[cfg(napi_runtime_os_threads)]
33041 #[test]
33042 fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
33043 use std::sync::mpsc;
33049
33050 struct ReadyAfterPublication(Arc<AtomicBool>);
33051
33052 impl Future for ReadyAfterPublication {
33053 type Output = ();
33054
33055 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
33056 if self.0.load(Ordering::SeqCst) {
33057 Poll::Ready(())
33058 } else {
33059 Poll::Pending
33060 }
33061 }
33062 }
33063
33064 let (done_tx, done_rx) = mpsc::channel();
33065 let runner = std::thread::spawn(move || {
33066 let options = RuntimeOptions {
33067 flavor: RuntimeFlavor::MultiThread,
33068 worker_threads: 1,
33069 max_blocking_tasks: 1,
33070 thread_name_prefix: "final-verdict-publication".to_string(),
33071 park_deadline: Some(Duration::from_millis(40)),
33072 drain_linger: DEFAULT_DRAIN_LINGER,
33073 };
33074 let executor =
33075 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33076
33077 let ready = Arc::new(AtomicBool::new(false));
33078 let baseline_fingerprint = executor.metrics.progress_fingerprint();
33079 let baseline_publications = executor.deadlock_state.publications();
33080 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
33081 let hook_exec = Arc::clone(&executor);
33082 let hook_ready = Arc::clone(&ready);
33083 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33084 *slot.borrow_mut() = Some(Box::new(move || {
33085 let publication_executor = Arc::clone(&hook_exec);
33086 std::thread::spawn(move || {
33087 let mut admission = publication_executor
33088 .begin_deadlock_admission()
33089 .expect("the deadline detector must enable admission accounting");
33090 hook_ready.store(true, Ordering::SeqCst);
33091 admission.mark_published();
33092 })
33093 .join()
33094 .unwrap();
33095 hook_saw_tx
33096 .send((
33097 hook_exec.deadlock_state.publications(),
33098 !hook_exec.has_queued_runnable(),
33099 hook_exec
33100 .blocking_queue
33101 .lock()
33102 .unwrap_or_else(std::sync::PoisonError::into_inner)
33103 .is_empty(),
33104 hook_exec.next_timer_deadline().is_none(),
33105 hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
33106 hook_exec.parked_drivers.count.load(Ordering::SeqCst),
33107 ))
33108 .unwrap();
33109 }));
33110 });
33111
33112 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33113 let mut future = std::pin::pin!(ReadyAfterPublication(ready));
33114 assert_eq!(
33115 executor.block_on(future.as_mut()),
33116 BlockOnOutcome::Completed,
33117 "the publication epoch must veto the first verdict and permit the next poll"
33118 );
33119 let (
33120 publications,
33121 runnable_queue_empty,
33122 blocking_queue_empty,
33123 timer_heap_empty,
33124 fingerprint_unchanged,
33125 parked_drivers,
33126 ) = hook_saw_rx
33127 .recv()
33128 .expect("the final-verdict hook must have fired");
33129 assert_eq!(
33130 publications,
33131 baseline_publications
33132 .checked_add(1)
33133 .expect("test publication epoch must not overflow"),
33134 "the cross-thread admission must publish exactly one epoch"
33135 );
33136 assert!(
33137 runnable_queue_empty,
33138 "the publication test must not queue a runnable"
33139 );
33140 assert!(
33141 blocking_queue_empty,
33142 "the publication test must not queue blocking work"
33143 );
33144 assert!(
33145 timer_heap_empty,
33146 "the publication test must not register a timer"
33147 );
33148 assert!(
33149 fingerprint_unchanged,
33150 "ordinary metrics progress must not veto the publication-only verdict"
33151 );
33152 assert_eq!(
33153 parked_drivers, 0,
33154 "the publication must land after the cooperative parker was deregistered"
33155 );
33156 let _ = done_tx.send(());
33157 });
33158
33159 match done_rx.recv_timeout(Duration::from_secs(10)) {
33160 Ok(()) => runner.join().unwrap(),
33161 Err(error) => panic!(
33162 "a publication-only cross-thread admission was not observed by the final verdict ({error})"
33163 ),
33164 }
33165 }
33166
33167 #[cfg(napi_runtime_os_threads)]
33168 #[test]
33169 fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
33170 use std::sync::mpsc;
33178
33179 let executor = multi_thread_executor(
33180 1,
33181 Some(Duration::from_millis(40)),
33182 "deadline-selected-handoff",
33183 );
33184 let owner = executor.fresh_owner_token();
33185 executor.active_blocking_owners.register(owner);
33186 executor
33187 .active_blocking
33188 .store(executor.max_blocking, Ordering::SeqCst);
33189
33190 let ready = Arc::new(AtomicBool::new(false));
33191 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33192 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33193 let (selected_tx, selected_rx) = mpsc::channel();
33194 let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
33195 let (released_tx, released_rx) = mpsc::channel();
33196 let (renewed_tx, renewed_rx) = mpsc::channel();
33197 let release_executor = Arc::clone(&executor);
33198 let renewal_executor = Arc::clone(&executor);
33199 let release_ready = Arc::clone(&ready);
33200 let releaser = std::thread::spawn(move || {
33201 let reservation =
33202 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33203 .expect("the synthetic exact-owner lane must start reserved");
33204 reservation_ready_tx.send(()).unwrap();
33205 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
33206 *slot.borrow_mut() = Some(Box::new(move || {
33207 selected_tx.send(()).unwrap();
33208 allow_unpark_rx.recv().unwrap();
33209 let renewed = renewal_executor
33210 .active_blocking_owners
33211 .reserve(owner)
33212 .expect("the released exact-owner lane must be reservable before unpark");
33213 release_ready.store(true, Ordering::SeqCst);
33214 renewed_tx.send(renewed).unwrap();
33215 }));
33216 });
33217 release_reservation_rx.recv().unwrap();
33218 released_tx
33219 .send(release_executor.release_owner_lane_and_wake(reservation))
33220 .unwrap();
33221 });
33222 reservation_ready_rx
33223 .recv_timeout(Duration::from_secs(2))
33224 .expect("the exact-owner reservation must be established before the park");
33225
33226 let baseline_fingerprint = executor.metrics.progress_fingerprint();
33227 let baseline_publications = executor.deadlock_state.publications();
33228 let expiry_executor = Arc::clone(&executor);
33229 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
33230 *slot.borrow_mut() = Some(Box::new(move || {
33231 release_reservation_tx.send(()).unwrap();
33232 selected_rx
33233 .recv_timeout(Duration::from_secs(2))
33234 .expect("reservation release must select the still-registered exact-owner driver");
33235 assert_eq!(
33236 expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
33237 0,
33238 "handoff selection must remove the driver before pausing ahead of unpark"
33239 );
33240 }));
33241 });
33242
33243 let verdict_executor = Arc::clone(&executor);
33244 let verdict_ready = Arc::clone(&ready);
33245 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33246 *slot.borrow_mut() = Some(Box::new(move || {
33247 assert!(
33248 verdict_executor.active_blocking_owners.is_available(owner),
33249 "reservation release must publish lane availability before handoff selection"
33250 );
33251 assert!(!verdict_executor.has_queued_runnable());
33252 assert!(
33253 verdict_executor
33254 .blocking_queue
33255 .lock()
33256 .unwrap_or_else(std::sync::PoisonError::into_inner)
33257 .is_empty()
33258 );
33259 assert!(verdict_executor.next_timer_deadline().is_none());
33260 assert!(
33261 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33262 "ordinary metrics progress must not save the selected-handoff verdict"
33263 );
33264 assert_eq!(
33265 verdict_executor.deadlock_state.publications(),
33266 baseline_publications
33267 );
33268 assert!(
33269 !verdict_ready.load(Ordering::SeqCst),
33270 "the selected handoff must still be paused before unpark at the gated verdict"
33271 );
33272 allow_unpark_tx.send(()).unwrap();
33273 }));
33274 });
33275 let exact_owner_executor = Arc::clone(&executor);
33276 DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
33277 *slot.borrow_mut() = Some(Box::new(move |parker| {
33278 assert!(
33279 parker.consume_permit(),
33280 "the selected handoff must unpark before exact-owner final predicates run"
33281 );
33282 assert!(
33283 parker.has_owner_handoff(),
33284 "consuming the ordinary permit must leave the exact-owner handoff pending"
33285 );
33286 assert!(
33287 !exact_owner_executor
33288 .active_blocking_owners
33289 .is_available(owner),
33290 "the renewed reservation must prevent lane availability from saving the verdict"
33291 );
33292 }));
33293 });
33294
33295 {
33296 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33297 let _owner = BlockingOwnerGuard::enter(Some(owner));
33298 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33299 dependency: BlockingDependency {
33300 job: BlockingJobId(u64::MAX - 80),
33301 owner: Some(owner)
33302 },
33303 ready: Arc::clone(&ready),
33304 });
33305 assert_eq!(
33306 executor.block_on(future.as_mut()),
33307 BlockOnOutcome::Completed
33308 );
33309 }
33310 assert!(
33311 released_rx
33312 .recv_timeout(Duration::from_secs(2))
33313 .expect("the selected handoff publication must finish"),
33314 "reservation release must have selected the cooperative driver"
33315 );
33316 let renewed = renewed_rx
33317 .recv_timeout(Duration::from_secs(2))
33318 .expect("the selected handoff must renew the owner reservation before unpark");
33319 releaser.join().unwrap();
33320
33321 executor.active_blocking_owners.release(owner, renewed);
33322 executor.active_blocking.store(0, Ordering::SeqCst);
33323 executor.active_blocking_owners.unregister(owner);
33324 executor.begin_shutdown();
33325 executor.wait_until_scheduler_idle();
33326 }
33327
33328 #[cfg(napi_runtime_os_threads)]
33329 #[test]
33330 fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
33331 use std::sync::mpsc;
33336
33337 let executor = multi_thread_executor(
33338 1,
33339 Some(Duration::from_millis(40)),
33340 "deadline-owner-no-parker",
33341 );
33342 let owner = executor.fresh_owner_token();
33343 executor.active_blocking_owners.register(owner);
33344 executor
33345 .active_blocking
33346 .store(executor.max_blocking, Ordering::SeqCst);
33347
33348 let ready = Arc::new(AtomicBool::new(false));
33349 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33350 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33351 let (released_tx, released_rx) = mpsc::channel();
33352 let release_executor = Arc::clone(&executor);
33353 let release_ready = Arc::clone(&ready);
33354 let releaser = std::thread::spawn(move || {
33355 let reservation =
33356 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33357 .expect("the synthetic exact-owner lane must start reserved");
33358 reservation_ready_tx.send(()).unwrap();
33359 release_reservation_rx.recv().unwrap();
33360 let woke = release_executor.release_owner_lane_and_wake(reservation);
33361 release_ready.store(true, Ordering::SeqCst);
33362 released_tx.send(woke).unwrap();
33363 });
33364 reservation_ready_rx
33365 .recv_timeout(Duration::from_secs(2))
33366 .expect("the exact-owner reservation must be established before the park");
33367
33368 let baseline_fingerprint = executor.metrics.progress_fingerprint();
33369 let baseline_publications = executor.deadlock_state.publications();
33370 let verdict_executor = Arc::clone(&executor);
33371 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33372 *slot.borrow_mut() = Some(Box::new(move || {
33373 assert_eq!(
33374 verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
33375 0,
33376 "the release must begin only after deadline deregistration"
33377 );
33378 release_reservation_tx.send(()).unwrap();
33379 assert!(
33380 !released_rx
33381 .recv_timeout(Duration::from_secs(2))
33382 .expect("the no-parker owner release must finish"),
33383 "no registered parker may receive the released owner lane"
33384 );
33385 assert!(verdict_executor.active_blocking_owners.is_available(owner));
33386 assert!(!verdict_executor.has_queued_runnable());
33387 assert!(
33388 verdict_executor
33389 .blocking_queue
33390 .lock()
33391 .unwrap_or_else(std::sync::PoisonError::into_inner)
33392 .is_empty()
33393 );
33394 assert!(verdict_executor.next_timer_deadline().is_none());
33395 assert!(
33396 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33397 "ordinary metrics progress must not save the no-parker release verdict"
33398 );
33399 assert_eq!(
33400 verdict_executor.deadlock_state.publications(),
33401 baseline_publications
33402 );
33403 }));
33404 });
33405
33406 {
33407 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33408 let _owner = BlockingOwnerGuard::enter(Some(owner));
33409 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33410 dependency: BlockingDependency {
33411 job: BlockingJobId(u64::MAX - 81),
33412 owner: Some(owner)
33413 },
33414 ready: Arc::clone(&ready),
33415 });
33416 assert_eq!(
33417 executor.block_on(future.as_mut()),
33418 BlockOnOutcome::Completed
33419 );
33420 }
33421 releaser.join().unwrap();
33422
33423 executor.active_blocking.store(0, Ordering::SeqCst);
33424 executor.active_blocking_owners.unregister(owner);
33425 executor.begin_shutdown();
33426 executor.wait_until_scheduler_idle();
33427 }
33428
33429 #[cfg(napi_runtime_os_threads)]
33430 #[test]
33431 fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
33432 use std::sync::mpsc;
33446
33447 let controller = Arc::new(RuntimeController::new());
33448 controller
33449 .configure(RuntimeOptions {
33450 flavor: RuntimeFlavor::MultiThread,
33451 worker_threads: 2,
33452 max_blocking_tasks: 1,
33453 thread_name_prefix: "shutdown-gated-verdict".to_string(),
33454 park_deadline: Some(Duration::from_millis(40)),
33455 drain_linger: DEFAULT_DRAIN_LINGER,
33456 })
33457 .expect("the shutdown race test configuration must be accepted");
33458
33459 let backend = controller.backend();
33460 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33461 panic!("the configured backend must be MultiThread");
33462 };
33463 let executor_id = executor.id;
33464 let weak_executor = Arc::downgrade(executor);
33465 drop(backend);
33466
33467 let (task_polled_tx, task_polled_rx) = mpsc::channel();
33468 let task = controller
33469 .try_spawn(async move {
33470 task_polled_tx.send(()).unwrap();
33471 std::future::pending::<()>().await;
33472 })
33473 .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
33474 task_polled_rx
33475 .recv_timeout(Duration::from_secs(2))
33476 .expect("the accepted task must publish an abortable pending waker");
33477
33478 let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
33479 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33480 let (driver_result_tx, driver_result_rx) = mpsc::channel();
33481 let driver_controller = Arc::clone(&controller);
33482 let driver = std::thread::spawn(move || {
33483 DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
33484 *slot.borrow_mut() = Some(Box::new(move || {
33485 gate_closed_tx.send(()).unwrap();
33486 release_verdict_rx.recv().unwrap();
33487 }));
33488 });
33489
33490 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33491 let mut pending = std::pin::pin!(NeverReady);
33492 let result = catch_unwind(AssertUnwindSafe(|| {
33493 driver_controller.block_on(pending.as_mut())
33494 }));
33495 let result = match result {
33496 Ok(outcome) => Ok(outcome),
33497 Err(payload) => {
33498 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33499 discard_panic_payload(payload);
33500 Err(was_deadlock)
33501 }
33502 };
33503 driver_result_tx.send(result).unwrap();
33504 });
33505
33506 gate_closed_rx
33507 .recv_timeout(Duration::from_secs(2))
33508 .expect("the cooperative driver must pause with the verdict gate closed");
33509
33510 let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
33511 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33512 let shutdown_controller = Arc::clone(&controller);
33513 let shutdown = std::thread::spawn(move || {
33514 DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
33515 *slot.borrow_mut() = Some(Box::new(move || {
33516 abort_blocked_tx.send(()).unwrap();
33517 }));
33518 });
33519 shutdown_result_tx
33520 .send(shutdown_controller.shutdown())
33521 .unwrap();
33522 });
33523
33524 abort_blocked_rx
33525 .recv_timeout(Duration::from_secs(2))
33526 .expect("the accepted task's abort wake must reach the closed verdict gate");
33527 let lifecycle_is_stopping = {
33528 let state = controller
33529 .state
33530 .lock()
33531 .unwrap_or_else(std::sync::PoisonError::into_inner);
33532 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
33533 };
33534 let stop_was_published = weak_executor
33535 .upgrade()
33536 .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
33537
33538 release_verdict_tx.send(()).unwrap();
33539
33540 let driver_result = driver_result_rx
33541 .recv_timeout(Duration::from_secs(2))
33542 .expect("the gated driver must resolve after observing shutdown");
33543 let shutdown_result = shutdown_result_rx
33544 .recv_timeout(Duration::from_secs(5))
33545 .expect("shutdown must finish after the verdict gate reopens");
33546 join_within(
33551 "the gated cooperative driver exits after teardown",
33552 driver,
33553 Duration::from_secs(30),
33554 );
33555 join_within(
33556 "the shutdown thread exits after teardown",
33557 shutdown,
33558 Duration::from_secs(30),
33559 );
33560
33561 assert!(
33562 lifecycle_is_stopping,
33563 "the abort wake must occur after Running -> Stopping"
33564 );
33565 assert!(
33566 stop_was_published,
33567 "generation stop must be visible before shutdown invokes accepted-task aborts"
33568 );
33569 assert_eq!(
33570 driver_result,
33571 Ok(BlockOnOutcome::Stopped),
33572 "shutdown must not be reported as a gated BlockOnDeadlock"
33573 );
33574 shutdown_result.unwrap();
33575 assert!(
33576 futures::executor::block_on(task).is_err(),
33577 "the accepted pending task must resolve as cancelled"
33578 );
33579 }
33580
33581 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
33582 #[test]
33583 fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
33584 use std::sync::mpsc;
33589
33590 let controller = Arc::new(RuntimeController::new());
33591 controller
33592 .configure(RuntimeOptions {
33593 flavor: RuntimeFlavor::MultiThread,
33594 worker_threads: 2,
33595 max_blocking_tasks: 1,
33596 thread_name_prefix: "verdict-before-shutdown".to_string(),
33597 park_deadline: Some(Duration::from_millis(40)),
33598 drain_linger: DEFAULT_DRAIN_LINGER,
33599 })
33600 .expect("the verdict-first test configuration must be accepted");
33601
33602 let backend = controller.backend();
33603 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33604 panic!("the configured backend must be MultiThread");
33605 };
33606 let executor_id = executor.id;
33607 drop(backend);
33608
33609 let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
33610 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33611 let (driver_result_tx, driver_result_rx) = mpsc::channel();
33612 let driver_controller = Arc::clone(&controller);
33613 let driver = std::thread::spawn(move || {
33614 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33615 *slot.borrow_mut() = Some(Box::new(move || {
33616 verdict_locked_tx.send(()).unwrap();
33617 release_verdict_rx.recv().unwrap();
33618 }));
33619 });
33620
33621 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33622 let mut pending = std::pin::pin!(NeverReady);
33623 let result = catch_unwind(AssertUnwindSafe(|| {
33624 driver_controller.block_on(pending.as_mut())
33625 }));
33626 let result = match result {
33627 Ok(outcome) => Ok(outcome),
33628 Err(payload) => {
33629 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33630 discard_panic_payload(payload);
33631 Err(was_deadlock)
33632 }
33633 };
33634 driver_result_tx.send(result).unwrap();
33635 });
33636
33637 verdict_locked_rx
33638 .recv_timeout(Duration::from_secs(2))
33639 .expect("the verdict must hold the synchronized stop-publication guard");
33640
33641 let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
33642 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33643 let shutdown_controller = Arc::clone(&controller);
33644 let shutdown = std::thread::spawn(move || {
33645 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
33646 *slot.borrow_mut() = Some(Box::new(move || {
33647 shutdown_waiting_tx.send(()).unwrap();
33648 }));
33649 });
33650 shutdown_result_tx
33651 .send(shutdown_controller.shutdown())
33652 .unwrap();
33653 });
33654
33655 shutdown_waiting_rx
33656 .recv_timeout(Duration::from_secs(2))
33657 .expect("shutdown must reach the lifecycle-to-publication lock edge");
33658 let early_shutdown = shutdown_result_rx
33659 .recv_timeout(Duration::from_millis(100))
33660 .ok();
33661 let shutdown_was_blocked = early_shutdown.is_none();
33662 release_verdict_tx.send(()).unwrap();
33663
33664 let driver_result = driver_result_rx
33665 .recv_timeout(Duration::from_secs(2))
33666 .expect("the verdict-first driver must finish after release");
33667 let shutdown_result = early_shutdown.unwrap_or_else(|| {
33668 shutdown_result_rx
33669 .recv_timeout(Duration::from_secs(5))
33670 .expect("shutdown must finish after the verdict releases publication")
33671 });
33672 driver.join().unwrap();
33673 shutdown.join().unwrap();
33674
33675 assert!(
33676 shutdown_was_blocked,
33677 "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
33678 );
33679 assert_eq!(
33680 driver_result,
33681 Err(true),
33682 "a verdict that linearizes before shutdown must retain the typed deadlock"
33683 );
33684 shutdown_result.unwrap();
33685 }
33686
33687 #[cfg(not(target_family = "wasm"))]
33688 #[test]
33689 fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
33690 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
33691
33692 if std::env::var_os(CHILD_ENV).is_some() {
33693 let executor =
33694 multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
33695 executor
33698 .active_drainers
33699 .store(executor.max_drainers, Ordering::SeqCst);
33700
33701 let hook_returned = Arc::new(AtomicBool::new(false));
33702 let hook_flag = Arc::clone(&hook_returned);
33703 let hook_executor = Arc::clone(&executor);
33704 let previous_hook = std::panic::take_hook();
33705 std::panic::set_hook(Box::new(move |_| {
33706 let handle = hook_executor.schedule_blocking(|| ());
33707 drop(handle);
33708 hook_flag.store(true, Ordering::SeqCst);
33709 }));
33710
33711 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33712 let result = catch_unwind(AssertUnwindSafe(|| {
33713 let mut pending = std::pin::pin!(std::future::pending::<()>());
33714 executor.block_on(pending.as_mut());
33715 }));
33716 std::panic::set_hook(previous_hook);
33717
33718 let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
33719 assert!(
33720 payload.downcast_ref::<BlockOnDeadlock>().is_some(),
33721 "the deadline must retain its structured diagnostic"
33722 );
33723 assert!(
33724 hook_returned.load(Ordering::SeqCst),
33725 "the panic hook's scheduler submission must return before unwind"
33726 );
33727
33728 executor.active_drainers.store(0, Ordering::SeqCst);
33729 executor.begin_shutdown();
33730 executor.wait_until_scheduler_idle();
33731 return;
33732 }
33733
33734 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
33735 .arg("--exact")
33736 .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
33737 .arg("--nocapture")
33738 .env(CHILD_ENV, "1")
33739 .stdout(std::process::Stdio::piped())
33740 .stderr(std::process::Stdio::piped())
33741 .spawn()
33742 .expect("the panic-hook subprocess must start");
33743 let deadline = Instant::now() + Duration::from_secs(5);
33744 let status = loop {
33745 if let Some(status) = child
33746 .try_wait()
33747 .expect("the subprocess status must be readable")
33748 {
33749 break status;
33750 }
33751 if Instant::now() >= deadline {
33752 let _ = child.kill();
33753 let output = child
33754 .wait_with_output()
33755 .expect("the timed-out subprocess must be reaped");
33756 panic!(
33757 "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
33758 String::from_utf8_lossy(&output.stdout),
33759 String::from_utf8_lossy(&output.stderr)
33760 );
33761 }
33762 std::thread::sleep(Duration::from_millis(10));
33763 };
33764 let output = child
33765 .wait_with_output()
33766 .expect("the completed subprocess output must be read");
33767 assert!(
33768 status.success(),
33769 "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
33770 status.code(),
33771 String::from_utf8_lossy(&output.stdout),
33772 String::from_utf8_lossy(&output.stderr)
33773 );
33774 }
33775
33776 #[cfg(panic = "unwind")]
33787 #[test]
33788 fn contained_waker_separates_wake_and_drop_panics() {
33789 struct PanicWakeAndDrop;
33790
33791 impl std::task::Wake for PanicWakeAndDrop {
33792 fn wake(self: Arc<Self>) {
33793 panic!("intentional consuming wake panic");
33794 }
33795
33796 fn wake_by_ref(self: &Arc<Self>) {
33797 panic!("intentional borrowed wake panic");
33798 }
33799 }
33800
33801 impl Drop for PanicWakeAndDrop {
33802 fn drop(&mut self) {
33803 panic!("intentional RawWaker destructor panic");
33804 }
33805 }
33806
33807 wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
33808 }
33809
33810 struct TestHostileRawWakerState {
33811 clones: AtomicUsize,
33812 drop_panics: AtomicUsize,
33813 panic_on_clone: Option<usize>,
33814 }
33815
33816 struct TestHostileRawWakerToken {
33817 state: Arc<TestHostileRawWakerState>,
33818 panic_on_drop: bool,
33819 }
33820
33821 unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
33822 let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
33823 let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
33824 assert_ne!(
33825 token.state.panic_on_clone,
33826 Some(clone),
33827 "intentional RawWaker clone panic"
33828 );
33829 let clone = Box::new(TestHostileRawWakerToken {
33830 state: Arc::clone(&token.state),
33831 panic_on_drop: true,
33832 });
33833 std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
33834 }
33835
33836 unsafe fn wake_test_hostile_waker(data: *const ()) {
33837 unsafe { drop_test_hostile_waker(data) };
33838 }
33839
33840 unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
33841
33842 unsafe fn drop_test_hostile_waker(data: *const ()) {
33843 let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
33844 if token.panic_on_drop {
33845 token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
33846 panic!("intentional cloned RawWaker destructor panic");
33847 }
33848 }
33849
33850 static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
33851 clone_test_hostile_waker,
33852 wake_test_hostile_waker,
33853 wake_test_hostile_waker_by_ref,
33854 drop_test_hostile_waker,
33855 );
33856
33857 fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
33858 let token = Box::new(TestHostileRawWakerToken {
33859 state,
33860 panic_on_drop: false,
33861 });
33862 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
33863 unsafe { Waker::from_raw(raw) }
33864 }
33865
33866 struct CountingWake(AtomicUsize);
33868 impl std::task::Wake for CountingWake {
33869 fn wake(self: Arc<Self>) {
33870 self.0.fetch_add(1, Ordering::SeqCst);
33871 }
33872 }
33873
33874 type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
33876
33877 struct StubHostTimerDriver {
33881 timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
33882 }
33883
33884 impl StubHostTimerDriver {
33885 fn new() -> Self {
33886 Self {
33887 timers: Mutex::new(FxHashMap::default()),
33888 }
33889 }
33890 }
33891
33892 impl TimerDriver for StubHostTimerDriver {
33893 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33894 let mut timers = self.timers.lock().unwrap();
33895 if let Some(cell) = timers.get(&id) {
33896 *cell.0.lock().unwrap() = waker;
33897 return;
33898 }
33899 let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
33900 timers.insert(id, Arc::clone(&cell));
33901 std::thread::spawn(move || {
33902 let now = Instant::now();
33903 if deadline > now {
33904 std::thread::sleep(deadline - now);
33905 }
33906 if !cell.1.load(Ordering::SeqCst) {
33907 cell.0.lock().unwrap().wake_by_ref();
33908 }
33909 });
33910 }
33911
33912 fn cancel(&self, id: TimerId) {
33913 let removed = self.timers.lock().unwrap().remove(&id);
33914 if let Some(cell) = removed {
33915 cell.1.store(true, Ordering::SeqCst);
33916 }
33917 }
33918 }
33919
33920 struct ManualStubDriver {
33926 live: AtomicBool,
33927 registers: Mutex<Vec<(TimerId, Instant)>>,
33928 cancels: Mutex<Vec<TimerId>>,
33929 pending: Mutex<FxHashMap<TimerId, Waker>>,
33930 swept: AtomicBool,
33931 }
33932
33933 impl ManualStubDriver {
33934 fn new() -> Arc<Self> {
33935 Arc::new(Self {
33936 live: AtomicBool::new(true),
33937 registers: Mutex::new(Vec::new()),
33938 cancels: Mutex::new(Vec::new()),
33939 pending: Mutex::new(FxHashMap::default()),
33940 swept: AtomicBool::new(false),
33941 })
33942 }
33943
33944 fn kill(&self) {
33945 self.live.store(false, Ordering::SeqCst);
33946 }
33947 }
33948
33949 impl TimerDriver for ManualStubDriver {
33950 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33951 self.registers.lock().unwrap().push((id, deadline));
33952 self.pending.lock().unwrap().insert(id, waker);
33953 }
33954
33955 fn cancel(&self, id: TimerId) {
33956 self.cancels.lock().unwrap().push(id);
33957 self.pending.lock().unwrap().remove(&id);
33958 }
33959
33960 fn is_live(&self) -> bool {
33961 self.live.load(Ordering::SeqCst)
33962 }
33963
33964 fn on_swept(&self) {
33965 self.swept.store(true, Ordering::SeqCst);
33968 let wakers: Vec<Waker> = self
33969 .pending
33970 .lock()
33971 .unwrap()
33972 .drain()
33973 .map(|(_, w)| w)
33974 .collect();
33975 for waker in wakers {
33976 waker.wake();
33977 }
33978 }
33979 }
33980
33981 struct PanickingTimerDriver {
33982 swept: AtomicBool,
33983 }
33984
33985 impl TimerDriver for PanickingTimerDriver {
33986 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33987
33988 fn cancel(&self, _id: TimerId) {}
33989
33990 fn is_live(&self) -> bool {
33991 panic!("intentional timer-driver liveness panic");
33992 }
33993
33994 fn on_swept(&self) {
33995 self.swept.store(true, Ordering::SeqCst);
33996 panic!("intentional timer-driver sweep panic");
33997 }
33998 }
33999
34000 struct PanickingRegisterTimerDriver {
34001 registers: AtomicUsize,
34002 cancels: Mutex<Vec<TimerId>>,
34003 pending: Mutex<FxHashMap<TimerId, Waker>>,
34004 }
34005
34006 impl PanickingRegisterTimerDriver {
34007 fn new() -> Arc<Self> {
34008 Arc::new(Self {
34009 registers: AtomicUsize::new(0),
34010 cancels: Mutex::new(Vec::new()),
34011 pending: Mutex::new(FxHashMap::default()),
34012 })
34013 }
34014 }
34015
34016 impl TimerDriver for PanickingRegisterTimerDriver {
34017 fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
34018 self.registers.fetch_add(1, Ordering::SeqCst);
34019 self.pending.lock().unwrap().insert(id, waker);
34020 panic!("intentional timer-driver register panic after arming");
34021 }
34022
34023 fn cancel(&self, id: TimerId) {
34024 self.cancels.lock().unwrap().push(id);
34025 self.pending.lock().unwrap().remove(&id);
34026 }
34027 }
34028
34029 struct PanicBeforeRetainingTimerDriver {
34030 registers: AtomicUsize,
34031 }
34032
34033 impl TimerDriver for PanicBeforeRetainingTimerDriver {
34034 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34035 self.registers.fetch_add(1, Ordering::SeqCst);
34036 panic!("intentional timer-driver register panic before retaining its waker");
34037 }
34038
34039 fn cancel(&self, _id: TimerId) {}
34040 }
34041
34042 struct PanickingCancelDropTimerDriver {
34043 cancels: Arc<AtomicUsize>,
34044 drops: Arc<AtomicUsize>,
34045 }
34046
34047 impl TimerDriver for PanickingCancelDropTimerDriver {
34048 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34049
34050 fn cancel(&self, _id: TimerId) {
34051 self.cancels.fetch_add(1, Ordering::SeqCst);
34052 panic!("intentional timer-driver cancel panic");
34053 }
34054 }
34055
34056 impl Drop for PanickingCancelDropTimerDriver {
34057 fn drop(&mut self) {
34058 self.drops.fetch_add(1, Ordering::SeqCst);
34059 panic!("intentional timer-driver destructor panic");
34060 }
34061 }
34062
34063 struct UnregisterLaterThenPanicTimerDriver {
34064 registry: Weak<TimerDriverRegistry>,
34065 later: Mutex<Option<TimerDriverId>>,
34066 registers: AtomicUsize,
34067 }
34068
34069 impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
34070 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34071 self.registers.fetch_add(1, Ordering::SeqCst);
34072 let later = *self.later.lock().unwrap();
34073 if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
34074 registry.unregister(later);
34075 }
34076 panic!("intentional earlier timer-driver panic after unregistering a later driver");
34077 }
34078
34079 fn cancel(&self, _id: TimerId) {}
34080 }
34081
34082 struct ReentrantLivenessDriver {
34083 registry: std::sync::Weak<TimerDriverRegistry>,
34084 registration: Mutex<Option<TimerDriverId>>,
34085 }
34086
34087 impl TimerDriver for ReentrantLivenessDriver {
34088 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34089
34090 fn cancel(&self, _id: TimerId) {}
34091
34092 fn is_live(&self) -> bool {
34093 let registration = *self.registration.lock().unwrap();
34094 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
34095 registry.unregister(id);
34096 }
34097 false
34098 }
34099 }
34100
34101 struct ReentrantMultiUnregisterTimerDriver {
34102 registry: std::sync::Weak<TimerDriverRegistry>,
34103 registrations: Mutex<Vec<TimerDriverId>>,
34104 }
34105
34106 impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
34107 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34108
34109 fn cancel(&self, _id: TimerId) {}
34110
34111 fn is_live(&self) -> bool {
34112 if let Some(registry) = self.registry.upgrade() {
34113 for registration in self.registrations.lock().unwrap().iter().copied() {
34114 registry.unregister(registration);
34115 }
34116 }
34117 false
34118 }
34119 }
34120
34121 struct ReentrantSweepTimerDriver {
34122 registry: std::sync::Weak<TimerDriverRegistry>,
34123 registration: Mutex<Option<TimerDriverId>>,
34124 sweeps: AtomicUsize,
34125 removed_before_sweep: AtomicBool,
34126 }
34127
34128 impl TimerDriver for ReentrantSweepTimerDriver {
34129 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34130
34131 fn cancel(&self, _id: TimerId) {}
34132
34133 fn is_live(&self) -> bool {
34134 false
34135 }
34136
34137 fn on_swept(&self) {
34138 self.sweeps.fetch_add(1, Ordering::SeqCst);
34139 let registration = self.registration.lock().unwrap().take();
34140 if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
34141 let state = registry
34142 .state
34143 .try_lock()
34144 .expect("the sweep callback must run outside the timer-driver registry lock");
34145 assert!(
34146 !state.entries.contains_key(®istration),
34147 "the dead driver must be unlinked before its sweep callback runs"
34148 );
34149 self.removed_before_sweep.store(true, Ordering::SeqCst);
34150 drop(state);
34151 registry.unregister(registration);
34154 }
34155 }
34156 }
34157
34158 struct ReentrantDropDriver {
34159 registry: std::sync::Weak<TimerDriverRegistry>,
34160 fallback: TimerDriverId,
34161 }
34162
34163 impl TimerDriver for ReentrantDropDriver {
34164 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34165
34166 fn cancel(&self, _id: TimerId) {}
34167 }
34168
34169 impl Drop for ReentrantDropDriver {
34170 fn drop(&mut self) {
34171 if let Some(registry) = self.registry.upgrade() {
34172 registry.unregister(self.fallback);
34173 }
34174 }
34175 }
34176
34177 #[derive(Default)]
34180 struct WakeFlag(AtomicBool);
34181
34182 impl std::task::Wake for WakeFlag {
34183 fn wake(self: Arc<Self>) {
34184 self.0.store(true, Ordering::SeqCst);
34185 }
34186 }
34187
34188 #[cfg(napi_runtime_os_threads)]
34189 fn multi_thread_executor(
34190 workers: usize,
34191 park_deadline: Option<Duration>,
34192 prefix: &str,
34193 ) -> Arc<MultiThreadExecutor> {
34194 let options = RuntimeOptions {
34195 flavor: RuntimeFlavor::MultiThread,
34196 worker_threads: workers,
34197 max_blocking_tasks: workers,
34198 thread_name_prefix: prefix.to_string(),
34199 park_deadline,
34200 drain_linger: DEFAULT_DRAIN_LINGER,
34201 };
34202 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
34203 }
34204
34205 fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
34208 let registry = Arc::new(TimerDriverRegistry::default());
34209 registry.register(driver);
34210 registry
34211 }
34212
34213 #[cfg(napi_runtime_os_threads)]
34214 fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
34215 make_sleep(
34217 &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
34218 &Arc::new(TimerDriverRegistry::default()),
34219 deadline,
34220 )
34221 }
34222
34223 #[cfg(napi_runtime_os_threads)]
34224 #[test]
34225 fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
34226 use std::sync::mpsc;
34227
34228 let executor = multi_thread_executor(
34229 1,
34230 Some(Duration::from_secs(1)),
34231 "timer-registration-admission",
34232 );
34233 let (entered_tx, entered_rx) = mpsc::channel();
34234 let (release_tx, release_rx) = mpsc::channel();
34235 let registration_executor = Arc::clone(&executor);
34236 let registration = std::thread::spawn(move || {
34237 TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
34238 *slot.borrow_mut() = Some(Box::new(move || {
34239 entered_tx.send(()).unwrap();
34240 release_rx.recv().unwrap();
34241 }));
34242 });
34243 registration_executor.register_timer(
34244 1,
34245 Instant::now() + Duration::from_secs(60),
34246 futures::task::noop_waker(),
34247 &Arc::new(AtomicBool::new(false)),
34248 );
34249 });
34250
34251 entered_rx
34252 .recv_timeout(Duration::from_secs(2))
34253 .expect("timer registration must pause after entering verdict admission");
34254 let verdict = executor.try_begin_deadlock_verdict();
34255 let admission_blocked_verdict = verdict.is_none();
34256 drop(verdict);
34257 release_tx.send(()).unwrap();
34258 registration.join().unwrap();
34259
34260 assert!(
34261 admission_blocked_verdict,
34262 "a timer registration must remain announced until its heap entry is visible"
34263 );
34264 assert!(
34265 executor.next_timer_deadline().is_some(),
34266 "the admitted registration must publish its timer after release"
34267 );
34268 executor.begin_shutdown();
34269 executor.wait_until_scheduler_idle();
34270 }
34271
34272 #[cfg(napi_runtime_os_threads)]
34273 #[derive(Clone, Copy, Debug)]
34274 enum TimerFireAdmissionPath {
34275 Timekeeper,
34276 Cooperative,
34277 Shutdown,
34278 }
34279
34280 #[cfg(napi_runtime_os_threads)]
34281 struct ScheduleAfterTimerWakeRelease {
34282 executor: Arc<MultiThreadExecutor>,
34283 runnable: Mutex<Option<Runnable>>,
34284 entered: std::sync::mpsc::Sender<()>,
34285 release: Mutex<std::sync::mpsc::Receiver<()>>,
34286 scheduled: std::sync::mpsc::Sender<()>,
34287 }
34288
34289 #[cfg(napi_runtime_os_threads)]
34290 impl ScheduleAfterTimerWakeRelease {
34291 fn publish(&self) {
34292 self.entered.send(()).unwrap();
34293 self
34294 .release
34295 .lock()
34296 .unwrap_or_else(std::sync::PoisonError::into_inner)
34297 .recv()
34298 .unwrap();
34299 let runnable = self
34300 .runnable
34301 .lock()
34302 .unwrap_or_else(std::sync::PoisonError::into_inner)
34303 .take();
34304 if let Some(runnable) = runnable {
34305 self.executor.schedule(runnable);
34306 }
34307 self.scheduled.send(()).unwrap();
34308 }
34309 }
34310
34311 #[cfg(napi_runtime_os_threads)]
34312 impl std::task::Wake for ScheduleAfterTimerWakeRelease {
34313 fn wake(self: Arc<Self>) {
34314 self.publish();
34315 }
34316
34317 fn wake_by_ref(self: &Arc<Self>) {
34318 self.publish();
34319 }
34320 }
34321
34322 #[cfg(napi_runtime_os_threads)]
34323 fn insert_timer_for_fire_admission_test(
34324 executor: &MultiThreadExecutor,
34325 id: TimerId,
34326 deadline: Instant,
34327 waker: Waker,
34328 fired: Arc<AtomicBool>,
34329 ) {
34330 let mut inner = executor
34331 .timers
34332 .inner
34333 .lock()
34334 .unwrap_or_else(std::sync::PoisonError::into_inner);
34335 inner.entries.insert(id, HeapTimerEntry { waker, fired });
34336 inner.queue.push(deadline, id);
34337 }
34338
34339 #[cfg(napi_runtime_os_threads)]
34340 fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
34341 use std::sync::mpsc;
34342
34343 let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
34344 let (runnable, task) = async_task::spawn(async {}, |_| {});
34345 task.detach();
34346 let (entered_tx, entered_rx) = mpsc::channel();
34347 let (release_tx, release_rx) = mpsc::channel();
34348 let (scheduled_tx, scheduled_rx) = mpsc::channel();
34349 let fired = Arc::new(AtomicBool::new(false));
34350 let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
34351 executor: Arc::clone(&executor),
34352 runnable: Mutex::new(Some(runnable)),
34353 entered: entered_tx,
34354 release: Mutex::new(release_rx),
34355 scheduled: scheduled_tx,
34356 }));
34357
34358 let fire_thread = match path {
34359 TimerFireAdmissionPath::Timekeeper => {
34360 executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
34361 None
34362 }
34363 TimerFireAdmissionPath::Cooperative => {
34364 insert_timer_for_fire_admission_test(
34365 &executor,
34366 1,
34367 Instant::now(),
34368 waker,
34369 Arc::clone(&fired),
34370 );
34371 let fire_executor = Arc::clone(&executor);
34372 Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
34373 }
34374 TimerFireAdmissionPath::Shutdown => {
34375 insert_timer_for_fire_admission_test(
34376 &executor,
34377 1,
34378 Instant::now() + Duration::from_secs(60),
34379 waker,
34380 Arc::clone(&fired),
34381 );
34382 let fire_executor = Arc::clone(&executor);
34383 Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
34384 }
34385 };
34386
34387 entered_rx
34391 .recv_timeout(Duration::from_secs(10))
34392 .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
34393 assert!(
34394 executor.next_timer_deadline().is_none(),
34395 "{path:?} must exercise the remove-before-wake window"
34396 );
34397 assert!(
34398 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
34399 "{path:?} must retain admission while its waker is publishing"
34400 );
34401 let verdict = executor.try_begin_deadlock_verdict();
34402 let admission_blocked_verdict = verdict.is_none();
34403 drop(verdict);
34404
34405 release_tx.send(()).unwrap();
34406 scheduled_rx
34407 .recv_timeout(Duration::from_secs(10))
34408 .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
34409 if let Some(fire_thread) = fire_thread {
34410 fire_thread.join().unwrap();
34411 }
34412 assert!(
34413 admission_blocked_verdict,
34414 "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
34415 );
34416
34417 executor.begin_shutdown();
34418 executor.wait_until_scheduler_idle();
34419 }
34420
34421 #[cfg(napi_runtime_os_threads)]
34422 #[test]
34423 fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
34424 for path in [
34425 TimerFireAdmissionPath::Timekeeper,
34426 TimerFireAdmissionPath::Cooperative,
34427 TimerFireAdmissionPath::Shutdown,
34428 ] {
34429 assert_timer_fire_path_holds_deadlock_admission(path);
34430 }
34431 }
34432
34433 #[cfg(napi_runtime_os_threads)]
34434 #[test]
34435 fn multi_thread_sleep_fires_from_foreign_block_on() {
34436 use std::sync::mpsc;
34441
34442 let (done_tx, done_rx) = mpsc::channel();
34443 let runner = std::thread::spawn(move || {
34444 let executor = multi_thread_executor(2, None, "timer-foreign");
34445 let started = Instant::now();
34446 let mut future = std::pin::pin!(async {
34447 heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
34448 });
34449 executor.block_on(future.as_mut());
34450 assert!(
34451 started.elapsed() >= Duration::from_millis(80),
34452 "the sleep must not resolve before its deadline"
34453 );
34454 let _ = done_tx.send(());
34455 });
34456
34457 match done_rx.recv_timeout(Duration::from_secs(10)) {
34458 Ok(()) => runner.join().unwrap(),
34459 Err(error) => {
34460 panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
34461 }
34462 }
34463 }
34464
34465 #[cfg(napi_runtime_os_threads)]
34466 #[test]
34467 fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
34468 use std::sync::mpsc;
34471
34472 let (done_tx, done_rx) = mpsc::channel();
34473 let runner = std::thread::spawn(move || {
34474 let executor = multi_thread_executor(1, None, "timer-drainer");
34475
34476 let sleep_exec = Arc::clone(&executor);
34479 let sleep_done = Arc::new(AtomicBool::new(false));
34480 let sleep_flag = Arc::clone(&sleep_done);
34481 let sched = Arc::clone(&executor);
34482 let (sleep_runnable, sleep_task) = async_task::spawn(
34483 async move {
34484 heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
34485 sleep_flag.store(true, Ordering::SeqCst);
34486 },
34487 move |r| sched.schedule(r),
34488 );
34489 executor.schedule(sleep_runnable);
34490 wait_until("the timekeeper parked on the 60s deadline", || {
34491 executor
34492 .timers
34493 .inner
34494 .lock()
34495 .unwrap()
34496 .timekeeper_parker
34497 .is_some()
34498 });
34499
34500 let counter = Arc::new(AtomicUsize::new(0));
34502 let mut tasks = Vec::new();
34503 for _ in 0..32 {
34504 let counter = Arc::clone(&counter);
34505 let sched = Arc::clone(&executor);
34506 let (runnable, task) = async_task::spawn(
34507 async move {
34508 counter.fetch_add(1, Ordering::SeqCst);
34509 },
34510 move |r| sched.schedule(r),
34511 );
34512 executor.schedule(runnable);
34513 tasks.push(task);
34514 }
34515 for task in tasks {
34516 futures::executor::block_on(task);
34517 }
34518 assert_eq!(counter.load(Ordering::SeqCst), 32);
34519 assert!(
34520 !sleep_done.load(Ordering::SeqCst),
34521 "the 60s timer must still be pending: the work ran DURING the timer wait"
34522 );
34523 executor.shutdown_timers();
34525 futures::executor::block_on(sleep_task);
34526 let _ = done_tx.send(());
34527 });
34528
34529 match done_rx.recv_timeout(Duration::from_secs(15)) {
34530 Ok(()) => runner.join().unwrap(),
34531 Err(error) => {
34532 panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
34533 }
34534 }
34535 }
34536
34537 #[cfg(napi_runtime_os_threads)]
34538 #[test]
34539 fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
34540 let executor = multi_thread_executor(2, None, "timer-lazy-start");
34541 assert!(
34542 executor.timers.thread.lock().unwrap().is_none(),
34543 "constructing a runtime without timers must not create an extra OS thread"
34544 );
34545
34546 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34547 assert!(
34548 executor.timers.thread.lock().unwrap().is_none(),
34549 "an unpolled sleep has not registered a deadline"
34550 );
34551 let waker = futures::task::noop_waker();
34552 let mut cx = Context::from_waker(&waker);
34553 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34554 assert!(
34555 executor.timers.thread.lock().unwrap().is_some(),
34556 "the first timer registration must start the dedicated service"
34557 );
34558 }
34559
34560 #[cfg(napi_runtime_os_threads)]
34561 #[test]
34562 fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
34563 use std::sync::mpsc;
34564
34565 let executor = multi_thread_executor(1, None, "timer-closed-start");
34566 executor.shutdown_timers();
34567 let handle_guard = executor.timers.thread.lock().unwrap();
34568 let ensure_executor = Arc::clone(&executor);
34569 let (completed_tx, completed_rx) = mpsc::channel();
34570 let ensure = std::thread::spawn(move || {
34571 ensure_executor.timers.ensure_started().unwrap();
34572 completed_tx.send(()).unwrap();
34573 });
34574
34575 completed_rx.recv_timeout(Duration::from_secs(10)).expect(
34578 "ensure_started consulted or joined the timer handle before observing the closed heap",
34579 );
34580 drop(handle_guard);
34581 ensure.join().unwrap();
34582 }
34583
34584 #[cfg(napi_runtime_os_threads)]
34585 #[test]
34586 fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
34587 const TIMERS: u64 = 4096;
34588
34589 let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
34590 let base = Instant::now() + Duration::from_secs(3600);
34591 for id in 0..TIMERS {
34592 executor.register_timer(
34593 id,
34594 base + Duration::from_secs(id),
34595 futures::task::noop_waker(),
34596 &Arc::new(AtomicBool::new(false)),
34597 );
34598 }
34599 {
34600 let inner = executor.timers.inner.lock().unwrap();
34601 assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
34602 assert_eq!(inner.queue.len(), inner.entries.len());
34603 }
34604
34605 for id in 1..TIMERS {
34608 executor.cancel_timer(id);
34609 }
34610 {
34611 let inner = executor.timers.inner.lock().unwrap();
34612 assert_eq!(inner.entries.len(), 1);
34613 assert_eq!(inner.queue.len(), 1);
34614 assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
34615 }
34616
34617 executor.cancel_timer(0);
34618 {
34619 let inner = executor.timers.inner.lock().unwrap();
34620 assert!(inner.entries.is_empty());
34621 assert!(inner.queue.is_empty());
34622 }
34623 executor.shutdown_timers();
34624 executor.timers.join();
34625 }
34626
34627 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
34628 #[test]
34629 fn timer_registration_is_transactional_when_timekeeper_start_fails() {
34630 let executor = multi_thread_executor(2, None, "timer-start-failure");
34631 executor
34632 .timers
34633 .fail_next_start
34634 .store(true, Ordering::SeqCst);
34635
34636 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34637 let waker = Waker::from(Arc::clone(&wake_count));
34638 let baseline_waker_refs = Arc::strong_count(&wake_count);
34639 let mut cx = Context::from_waker(&waker);
34640 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34641
34642 let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
34643 assert!(
34644 failure.is_err(),
34645 "the injected timer-thread start failure must panic the poll"
34646 );
34647 {
34648 let inner = executor.timers.inner.lock().unwrap();
34649 assert!(
34650 inner.entries.is_empty(),
34651 "a failed start must not retain a timer entry"
34652 );
34653 assert!(
34654 inner.queue.is_empty(),
34655 "a failed start must not retain a stale heap node"
34656 );
34657 assert!(
34658 inner.timekeeper_parker.is_none(),
34659 "a failed start must not publish a timekeeper parker"
34660 );
34661 }
34662 assert!(
34663 executor.timers.thread.lock().unwrap().is_none(),
34664 "a failed start must not retain a thread handle"
34665 );
34666 assert_eq!(
34667 Arc::strong_count(&wake_count),
34668 baseline_waker_refs,
34669 "a failed start must release the cloned timer waker"
34670 );
34671
34672 assert!(
34673 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34674 "the same Sleep must be safely retryable after a transient start failure"
34675 );
34676 {
34677 let inner = executor.timers.inner.lock().unwrap();
34678 assert_eq!(
34679 inner.entries.len(),
34680 1,
34681 "the retry must publish exactly one timer entry"
34682 );
34683 assert_eq!(
34684 inner.queue.len(),
34685 1,
34686 "the retry must publish exactly one heap node"
34687 );
34688 }
34689 assert!(
34690 executor.timers.thread.lock().unwrap().is_some(),
34691 "the retry must start the dedicated timekeeper"
34692 );
34693
34694 drop(sleep);
34695 assert!(
34696 executor.timers.inner.lock().unwrap().entries.is_empty(),
34697 "dropping the retried Sleep must cancel its registration"
34698 );
34699 executor.shutdown_timers();
34700 executor.wait_until_scheduler_idle();
34701 assert!(
34702 executor.timers.thread.lock().unwrap().is_none(),
34703 "shutdown after a recovered start failure must join the timekeeper"
34704 );
34705 }
34706
34707 #[cfg(napi_runtime_os_threads)]
34708 #[test]
34709 fn pending_timer_preserves_every_configured_rayon_worker() {
34710 use std::sync::mpsc;
34711
34712 let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
34713 let waker = futures::task::noop_waker();
34714 let mut cx = Context::from_waker(&waker);
34715 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34716 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34717 wait_until("the dedicated timer thread to park", || {
34718 executor
34719 .timers
34720 .inner
34721 .lock()
34722 .unwrap()
34723 .timekeeper_parker
34724 .is_some()
34725 });
34726
34727 let released = Arc::new((Mutex::new(false), Condvar::new()));
34728 let (started_tx, started_rx) = mpsc::channel();
34729 let (completed_tx, completed_rx) = mpsc::channel();
34730 for _ in 0..2 {
34731 let released = Arc::clone(&released);
34732 let started_tx = started_tx.clone();
34733 let completed_tx = completed_tx.clone();
34734 executor.pool.spawn(move || {
34735 started_tx.send(()).unwrap();
34736 let (lock, changed) = &*released;
34737 let mut released = lock
34738 .lock()
34739 .unwrap_or_else(std::sync::PoisonError::into_inner);
34740 while !*released {
34741 released = changed
34742 .wait(released)
34743 .unwrap_or_else(std::sync::PoisonError::into_inner);
34744 }
34745 completed_tx.send(()).unwrap();
34746 });
34747 }
34748 drop(started_tx);
34749 drop(completed_tx);
34750
34751 let first = started_rx.recv_timeout(Duration::from_secs(2));
34752 let second = started_rx.recv_timeout(Duration::from_secs(2));
34753 {
34754 let (lock, changed) = &*released;
34755 *lock
34756 .lock()
34757 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
34758 changed.notify_all();
34759 }
34760 drop(sleep);
34761 for _ in 0..2 {
34762 completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34763 }
34764
34765 assert!(
34766 first.is_ok(),
34767 "the first configured Rayon worker did not start"
34768 );
34769 assert!(
34770 second.is_ok(),
34771 "a pending timer consumed one of the two configured Rayon workers"
34772 );
34773 }
34774
34775 #[cfg(napi_runtime_os_threads)]
34776 #[test]
34777 fn timekeeper_never_enters_a_blocking_closure() {
34778 use std::{
34779 sync::{Barrier, mpsc},
34780 time::Duration,
34781 };
34782
34783 let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
34784 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34785 let waker = Waker::from(Arc::clone(&wake_count));
34786 let mut cx = Context::from_waker(&waker);
34787 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
34788 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34789 wait_until(
34790 "the timekeeper parked before blocking work was submitted",
34791 || {
34792 executor
34793 .timers
34794 .inner
34795 .lock()
34796 .unwrap()
34797 .timekeeper_parker
34798 .is_some()
34799 },
34800 );
34801
34802 let release = Arc::new(Barrier::new(2));
34803 let blocker_release = Arc::clone(&release);
34804 let (started_tx, started_rx) = mpsc::sync_channel(1);
34805 let blocking = executor.schedule_blocking(move || {
34806 started_tx.send(()).unwrap();
34807 blocker_release.wait();
34808 });
34809 started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
34812
34813 wait_until(
34814 "the timer waker to fire while the Rayon worker is blocked",
34815 || wake_count.0.load(Ordering::SeqCst) != 0,
34816 );
34817 release.wait();
34818 futures::executor::block_on(blocking).unwrap();
34819 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
34820 }
34821
34822 #[cfg(napi_runtime_os_threads)]
34823 #[test]
34824 fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
34825 use std::sync::mpsc;
34826
34827 struct PendingEntered(Option<mpsc::Sender<()>>);
34828
34829 impl Future for PendingEntered {
34830 type Output = ();
34831
34832 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
34833 if let Some(entered) = self.0.take() {
34834 entered.send(()).unwrap();
34835 }
34836 Poll::Pending
34837 }
34838 }
34839
34840 let options = RuntimeOptions {
34841 flavor: RuntimeFlavor::MultiThread,
34842 worker_threads: 2,
34843 max_blocking_tasks: 1,
34844 thread_name_prefix: "nested-timer-wake".to_string(),
34845 park_deadline: None,
34846 drain_linger: DEFAULT_DRAIN_LINGER,
34847 };
34848 let executor =
34849 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
34850
34851 let timer_exec = Arc::clone(&executor);
34852 let (timer_tx, timer_rx) = mpsc::channel();
34853 let timer_scheduler = Arc::clone(&executor);
34854 let (timer_runnable, timer_task) = async_task::spawn(
34855 async move {
34856 heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
34857 timer_tx.send(()).unwrap();
34858 },
34859 move |runnable| timer_scheduler.schedule(runnable),
34860 );
34861 executor.schedule(timer_runnable);
34862 wait_until(
34863 "the dedicated timekeeper parked before nested block_on",
34864 || {
34865 executor
34866 .timers
34867 .inner
34868 .lock()
34869 .unwrap()
34870 .timekeeper_parker
34871 .is_some()
34872 },
34873 );
34874
34875 let (entered_tx, entered_rx) = mpsc::channel();
34876 let nested_exec = Arc::clone(&executor);
34877 let nested_scheduler = Arc::clone(&executor);
34878 let (nested_runnable, nested_task) = async_task::spawn(
34879 async move {
34880 let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
34881 let outcome = nested_exec.block_on(pending.as_mut());
34882 drop(pending);
34883 assert_eq!(
34884 outcome,
34885 BlockOnOutcome::Stopped,
34886 "shutdown must stop the raw nested block_on driver"
34887 );
34888 },
34889 move |runnable| nested_scheduler.schedule(runnable),
34890 );
34891 executor.schedule(nested_runnable);
34892 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34893
34894 let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
34895 let (release_tx, release_rx) = mpsc::channel();
34896 let blocking = executor.schedule_blocking(move || {
34897 blocking_started_tx.send(()).unwrap();
34898 release_rx.recv().unwrap();
34899 });
34900 blocking_started_rx
34901 .recv_timeout(Duration::from_secs(2))
34902 .expect("a general worker must service blocking work");
34903 timer_rx
34904 .recv_timeout(Duration::from_secs(2))
34905 .expect("the timer wake must reach the nested cooperative driver");
34906
34907 release_tx.send(()).unwrap();
34908 futures::executor::block_on(blocking).unwrap();
34909 executor.begin_shutdown();
34910 assert_eq!(
34911 futures::executor::block_on(nested_task.fallible()),
34912 Some(())
34913 );
34914 futures::executor::block_on(timer_task);
34915 }
34916
34917 #[cfg(napi_runtime_os_threads)]
34918 #[test]
34919 fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
34920 use std::sync::mpsc;
34924
34925 let (done_tx, done_rx) = mpsc::channel();
34926 let runner = std::thread::spawn(move || {
34927 let executor = multi_thread_executor(2, None, "timer-rearm");
34928
34929 let long_exec = Arc::clone(&executor);
34930 let long_done = Arc::new(AtomicBool::new(false));
34931 let long_flag = Arc::clone(&long_done);
34932 let sched = Arc::clone(&executor);
34933 let (long_runnable, long_task) = async_task::spawn(
34934 async move {
34935 heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
34936 long_flag.store(true, Ordering::SeqCst);
34937 },
34938 move |r| sched.schedule(r),
34939 );
34940 executor.schedule(long_runnable);
34941 wait_until("the timekeeper parked on the 60s sentinel", || {
34942 executor
34943 .timers
34944 .inner
34945 .lock()
34946 .unwrap()
34947 .timekeeper_parker
34948 .is_some()
34949 });
34950
34951 let started = Instant::now();
34952 let mut future = std::pin::pin!(async {
34953 heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
34954 });
34955 executor.block_on(future.as_mut());
34956 let elapsed = started.elapsed();
34957 assert!(
34958 elapsed >= Duration::from_millis(60),
34959 "fired before its deadline: {elapsed:?}"
34960 );
34961 assert!(
34962 !long_done.load(Ordering::SeqCst),
34963 "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
34964 timekeeper, not by the sentinel expiring"
34965 );
34966 executor.shutdown_timers();
34967 futures::executor::block_on(long_task);
34968 let _ = done_tx.send(());
34969 });
34970
34971 match done_rx.recv_timeout(Duration::from_secs(15)) {
34972 Ok(()) => runner.join().unwrap(),
34973 Err(error) => panic!(
34974 "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
34975 ),
34976 }
34977 }
34978
34979 #[cfg(napi_runtime_os_threads)]
34980 #[test]
34981 fn multi_thread_sleep_cancels_registration_on_drop() {
34982 let executor = multi_thread_executor(1, None, "timer-cancel");
34985 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34986 let waker = Waker::from(Arc::clone(&wake_count));
34987 let mut cx = Context::from_waker(&waker);
34988
34989 let deadline = Instant::now() + Duration::from_millis(250);
34993 let mut sleep = heap_sleep(&executor, deadline);
34994 assert!(
34995 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34996 "must register, not resolve"
34997 );
34998 assert!(
34999 executor.next_timer_deadline().is_some(),
35000 "the registration must be in the heap"
35001 );
35002 drop(sleep);
35003 assert!(
35004 executor.next_timer_deadline().is_none(),
35005 "cancel-on-drop must retire the heap registration"
35006 );
35007 while Instant::now() < deadline + Duration::from_millis(25) {
35012 std::thread::sleep(Duration::from_millis(10));
35013 }
35014 executor.fire_due_timers();
35015 assert_eq!(
35016 wake_count.0.load(Ordering::SeqCst),
35017 0,
35018 "a dropped Sleep's waker must never fire"
35019 );
35020 }
35021
35022 #[cfg(napi_runtime_os_threads)]
35023 #[test]
35024 fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
35025 let executor = multi_thread_executor(2, None, "timer-cancel-retire");
35026 let waker = futures::task::noop_waker();
35027 let mut cx = Context::from_waker(&waker);
35028 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
35029
35030 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35031 wait_until(
35032 "the timekeeper parked on the timer that will be cancelled",
35033 || {
35034 executor
35035 .timers
35036 .inner
35037 .lock()
35038 .unwrap()
35039 .timekeeper_parker
35040 .is_some()
35041 },
35042 );
35043
35044 drop(sleep);
35045 wait_until(
35046 "the cancelled timer's timekeeper returned to its idle wait",
35047 || {
35048 executor.next_timer_deadline().is_none()
35049 && executor
35050 .timers
35051 .inner
35052 .lock()
35053 .unwrap()
35054 .timekeeper_parker
35055 .is_none()
35056 },
35057 );
35058 }
35059
35060 #[cfg(napi_runtime_os_threads)]
35061 #[test]
35062 fn timer_waker_replacement_drops_outside_heap_lock() {
35063 use std::sync::mpsc;
35064
35065 struct DropOtherTimer(Mutex<Option<Sleep>>);
35066
35067 #[expect(
35068 clippy::manual_noop_waker,
35069 reason = "the no-op wake type has a reentrant Drop used by this regression"
35070 )]
35071 impl std::task::Wake for DropOtherTimer {
35072 fn wake(self: Arc<Self>) {}
35073 }
35074
35075 impl Drop for DropOtherTimer {
35076 fn drop(&mut self) {
35077 drop(self.0.lock().unwrap().take());
35078 }
35079 }
35080
35081 let (done_tx, done_rx) = mpsc::channel();
35082 let runner = std::thread::spawn(move || {
35083 let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
35084 let deadline = Instant::now() + Duration::from_secs(120);
35085 let mut other = heap_sleep(&executor, deadline);
35086 let noop = futures::task::noop_waker();
35087 let mut noop_cx = Context::from_waker(&noop);
35088 assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
35089
35090 let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
35091 let mut primary = heap_sleep(&executor, deadline);
35092 let mut old_cx = Context::from_waker(&old);
35093 assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
35094 drop(old);
35095
35096 let replacement = futures::task::noop_waker();
35097 let mut replacement_cx = Context::from_waker(&replacement);
35098 assert!(
35099 Pin::new(&mut primary)
35100 .poll(&mut replacement_cx)
35101 .is_pending()
35102 );
35103 drop(primary);
35104 executor.shutdown_timers();
35105 done_tx.send(()).unwrap();
35106 });
35107 done_rx
35110 .recv_timeout(Duration::from_secs(10))
35111 .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
35112 runner.join().unwrap();
35113 }
35114
35115 #[cfg(napi_runtime_os_threads)]
35116 #[test]
35117 fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
35118 use std::sync::mpsc;
35119
35120 struct DropFinalExecutorOnWake {
35121 executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
35122 completed: mpsc::Sender<()>,
35123 }
35124
35125 impl std::task::Wake for DropFinalExecutorOnWake {
35126 fn wake(self: Arc<Self>) {
35127 let executor = self
35128 .executor
35129 .lock()
35130 .unwrap_or_else(std::sync::PoisonError::into_inner)
35131 .take()
35132 .expect("the timer waker must own the final executor reference");
35133 assert_eq!(
35134 ON_TIMER_THREAD.with(std::cell::Cell::get),
35135 Some(executor.id),
35136 "the dedicated timer thread must deliver the timer wake"
35137 );
35138 drop(executor);
35139 self
35140 .completed
35141 .send(())
35142 .expect("the test receiver must outlive the timer-thread callback");
35143 }
35144 }
35145
35146 let executor = multi_thread_executor(1, None, "timer-final-arc");
35147 let (completed_tx, completed_rx) = mpsc::channel();
35148 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
35149 {
35150 let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
35151 executor: Mutex::new(Some(Arc::clone(&executor))),
35152 completed: completed_tx,
35153 }));
35154 let mut cx = Context::from_waker(&waker);
35155 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35156 }
35157 wait_until(
35158 "the timer thread parked before owning the final executor Arc",
35159 || {
35160 executor
35161 .timers
35162 .inner
35163 .lock()
35164 .unwrap()
35165 .timekeeper_parker
35166 .is_some()
35167 },
35168 );
35169
35170 drop(executor);
35174 completed_rx
35177 .recv_timeout(Duration::from_secs(10))
35178 .expect("dropping the final executor Arc on the timer thread attempted to join itself");
35179 }
35180
35181 #[cfg(not(target_family = "wasm"))]
35182 #[test]
35183 fn multi_thread_shutdown_with_pending_timers_drain_fires() {
35184 use std::sync::mpsc;
35189
35190 let (done_tx, done_rx) = mpsc::channel();
35191 let runner = std::thread::spawn(move || {
35192 let controller = Arc::new(RuntimeController::new());
35193 let backend = controller.backend();
35195 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
35196 panic!("default native backend must be MultiThread");
35197 };
35198 let executor = Arc::downgrade(executor);
35199 let mut late = heap_sleep(
35200 &executor
35201 .upgrade()
35202 .expect("the initial executor must be live"),
35203 Instant::now() + Duration::from_secs(120),
35204 );
35205 drop(backend);
35206
35207 let (parked_tx, parked_rx) = mpsc::channel();
35208 let block_controller = Arc::clone(&controller);
35209 let block_exec = Weak::clone(&executor);
35210 let blocker = std::thread::spawn(move || {
35211 let mut future = std::pin::pin!(async {
35212 let executor = block_exec
35213 .upgrade()
35214 .expect("the runtime must remain live while blocking");
35215 let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
35216 drop(executor);
35217 parked_tx.send(()).unwrap();
35218 sleep.await;
35219 });
35220 block_controller.block_on(future.as_mut());
35221 });
35222 parked_rx.recv().unwrap();
35223 wait_until("the 120s sleep registered in the heap", || {
35224 executor
35225 .upgrade()
35226 .is_some_and(|executor| executor.next_timer_deadline().is_some())
35227 });
35228
35229 controller
35231 .shutdown()
35232 .expect("runtime shutdown must succeed");
35233 blocker
35234 .join()
35235 .expect("the blocked sleep must be drain-fired by shutdown, not hang");
35236
35237 let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
35240 let mut cx = Context::from_waker(&waker);
35241 assert!(
35242 Pin::new(&mut late).poll(&mut cx).is_ready(),
35243 "a Sleep polled after shutdown must fire early, never park a closing runtime"
35244 );
35245 let _ = done_tx.send(());
35246 });
35247
35248 match done_rx.recv_timeout(Duration::from_secs(10)) {
35249 Ok(()) => runner.join().unwrap(),
35250 Err(error) => {
35251 panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
35252 }
35253 }
35254 }
35255
35256 #[cfg(napi_runtime_os_threads)]
35257 #[test]
35258 fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
35259 use std::sync::mpsc;
35268
35269 let (done_tx, done_rx) = mpsc::channel();
35270 let runner = std::thread::spawn(move || {
35271 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
35272
35273 let task_exec = Arc::clone(&executor);
35274 let body = async move {
35275 let started = Instant::now();
35276 let mut inner = std::pin::pin!(async {
35277 heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
35278 });
35279 task_exec.block_on(inner.as_mut());
35281 assert!(started.elapsed() >= Duration::from_millis(250));
35282 };
35283 let sched = Arc::clone(&executor);
35284 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
35285 executor.schedule(runnable);
35286
35287 futures::executor::block_on(task);
35291 drop(executor);
35297 let _ = done_tx.send(());
35298 });
35299
35300 match done_rx.recv_timeout(Duration::from_secs(10)) {
35301 Ok(()) => join_within(
35302 "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
35303 runner,
35304 Duration::from_secs(30),
35305 ),
35306 Err(error) => {
35307 panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
35308 }
35309 }
35310 }
35311
35312 #[cfg(napi_runtime_os_threads)]
35313 #[test]
35314 fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
35315 use std::sync::mpsc;
35322
35323 struct GateWake {
35326 gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
35327 }
35328 impl std::task::Wake for GateWake {
35329 fn wake(self: Arc<Self>) {
35330 let gate = self.gate.lock().unwrap().take();
35331 if let Some(gate) = gate {
35332 let _ = gate.send(23);
35333 }
35334 }
35335 }
35336
35337 let (done_tx, done_rx) = mpsc::channel();
35338 let runner = std::thread::spawn(move || {
35339 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
35340 executor
35344 .active_drainers
35345 .store(executor.max_drainers, Ordering::SeqCst);
35346
35347 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
35350 let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
35351
35352 let hook_exec = Arc::clone(&executor);
35353 let hook_gate = Arc::clone(&gate_tx);
35354 let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
35357 let hook_sleep_slot = Arc::clone(&parked_sleep);
35358 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
35359 *slot.borrow_mut() = Some(Box::new(move || {
35360 let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
35361 let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
35362 let mut cx = Context::from_waker(&waker);
35363 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35364 *hook_sleep_slot.lock().unwrap() = Some(sleep);
35365 }));
35366 });
35367
35368 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
35371 let mut output = None;
35372 {
35373 let mut future = std::pin::pin!(async {
35374 output = Some(gate_rx.await.unwrap());
35375 });
35376 executor.block_on(future.as_mut());
35377 }
35378 DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
35384 assert_eq!(
35385 output,
35386 Some(23),
35387 "the edge-registered timer must have fired and released the gate"
35388 );
35389 executor.active_drainers.store(0, Ordering::SeqCst);
35390 drop(executor);
35393 let _ = done_tx.send(());
35394 });
35395
35396 match done_rx.recv_timeout(Duration::from_secs(10)) {
35397 Ok(()) => join_within(
35398 "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
35399 runner,
35400 Duration::from_secs(30),
35401 ),
35402 Err(error) => panic!(
35403 "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
35404 ),
35405 }
35406 }
35407
35408 #[cfg(panic = "unwind")]
35409 #[test]
35410 fn current_thread_sleep_without_host_driver_fails_loud() {
35411 let metrics = Arc::new(RuntimeMetrics::default());
35415 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35416 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
35417
35418 let payload = catch_unwind(AssertUnwindSafe(|| {
35419 let _sleep = make_sleep(
35420 &backend,
35421 &Arc::new(TimerDriverRegistry::default()),
35422 Instant::now() + Duration::from_millis(10),
35423 );
35424 }))
35425 .expect_err("sleep_until on CurrentThread without a driver must panic");
35426 let message = payload
35427 .downcast_ref::<String>()
35428 .cloned()
35429 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
35430 .expect("panic payload must be a message");
35431 assert!(
35432 message.contains("CurrentThread runtime has no live timer driver registered"),
35433 "the diagnostic must name the missing driver, got: {message}"
35434 );
35435 }
35436
35437 #[test]
35438 fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
35439 let registry = TimerDriverRegistry::default();
35443 let a = ManualStubDriver::new();
35444 let b = ManualStubDriver::new();
35445 let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
35446 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
35447 assert_ne!(a_id, b_id, "registration handles must be distinct");
35448 assert_eq!(
35449 registry.current().map(|(id, _)| id),
35450 Some(b_id),
35451 "newest live registrant serves"
35452 );
35453 assert!(registry.has_live_driver());
35454
35455 b.kill();
35458 assert_eq!(
35459 registry.current().map(|(id, _)| id),
35460 Some(a_id),
35461 "dead newest must be skipped"
35462 );
35463 b.live.store(true, Ordering::SeqCst);
35466 assert_eq!(
35467 registry.current().map(|(id, _)| id),
35468 Some(a_id),
35469 "swept entries stay gone"
35470 );
35471
35472 registry.unregister(a_id);
35475 assert!(registry.current().is_none());
35476 assert!(
35477 !registry.has_live_driver(),
35478 "an emptied registry must not report a live driver"
35479 );
35480 }
35481
35482 #[cfg(panic = "unwind")]
35483 #[test]
35484 fn timer_driver_registry_contains_callback_panics_and_falls_back() {
35485 let registry = TimerDriverRegistry::default();
35486 let fallback = ManualStubDriver::new();
35487 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35488 let panicking = Arc::new(PanickingTimerDriver {
35489 swept: AtomicBool::new(false),
35490 });
35491 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35492
35493 assert_eq!(
35494 registry.current().map(|(id, _)| id),
35495 Some(fallback_id),
35496 "a panicking liveness callback must be treated as a dead driver"
35497 );
35498 assert!(
35499 panicking.swept.load(Ordering::SeqCst),
35500 "a panicking liveness callback must still run its contained sweep hook"
35501 );
35502 assert!(
35503 registry.has_live_driver(),
35504 "the healthy fallback must remain selectable"
35505 );
35506 }
35507
35508 #[cfg(napi_runtime_os_threads)]
35509 #[test]
35510 fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
35511 use std::{sync::mpsc, time::Duration};
35512
35513 let registry = Arc::new(TimerDriverRegistry::default());
35514 let fallback = ManualStubDriver::new();
35515 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35516 let reentrant = Arc::new(ReentrantLivenessDriver {
35517 registry: Arc::downgrade(®istry),
35518 registration: Mutex::new(None),
35519 });
35520 let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35521 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
35522
35523 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
35524 let selection_registry = Arc::clone(®istry);
35525 let selection_thread = std::thread::spawn(move || {
35526 selection_tx
35527 .send(selection_registry.current().map(|(id, _)| id))
35528 .unwrap();
35529 });
35530 assert_eq!(
35531 selection_rx
35532 .recv_timeout(Duration::from_secs(10))
35533 .expect("is_live re-entry deadlocked"),
35534 Some(fallback_id)
35535 );
35536 selection_thread.join().unwrap();
35537
35538 let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
35539 registry: Arc::downgrade(®istry),
35540 fallback: fallback_id,
35541 });
35542 let drop_id = registry.register(Arc::clone(&drop_driver));
35543 drop(drop_driver);
35544
35545 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
35546 let drop_registry = Arc::clone(®istry);
35547 let drop_thread = std::thread::spawn(move || {
35548 drop_registry.unregister(drop_id);
35549 drop_tx.send(()).unwrap();
35550 });
35551 drop_rx
35552 .recv_timeout(Duration::from_secs(10))
35553 .expect("driver destructor re-entry deadlocked");
35554 drop_thread.join().unwrap();
35555 assert!(
35556 registry.current().is_none(),
35557 "the destructor must be able to unregister the fallback"
35558 );
35559 }
35560
35561 #[test]
35562 fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
35563 const DRIVER_PAIRS: usize = 2_048;
35564
35565 let registry = Arc::new(TimerDriverRegistry::default());
35566 let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
35567 let mut live = Vec::with_capacity(DRIVER_PAIRS);
35568 let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
35569 for _ in 0..DRIVER_PAIRS {
35570 let driver = Arc::new(ReentrantSweepTimerDriver {
35571 registry: Arc::downgrade(®istry),
35572 registration: Mutex::new(None),
35573 sweeps: AtomicUsize::new(0),
35574 removed_before_sweep: AtomicBool::new(false),
35575 });
35576 let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35577 *driver.registration.lock().unwrap() = Some(id);
35578 dead.push(driver);
35579
35580 let driver = ManualStubDriver::new();
35581 live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
35582 live.push(driver);
35583 }
35584 let dead_tail = Arc::new(ReentrantSweepTimerDriver {
35585 registry: Arc::downgrade(®istry),
35586 registration: Mutex::new(None),
35587 sweeps: AtomicUsize::new(0),
35588 removed_before_sweep: AtomicBool::new(false),
35589 });
35590 let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
35591 *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
35592 dead.push(dead_tail);
35593
35594 let selection = registry.live();
35595 assert_eq!(
35596 selection
35597 .as_slice()
35598 .iter()
35599 .map(|(id, _)| *id)
35600 .collect::<Vec<_>>(),
35601 live_ids,
35602 "sweeping every interleaved dead host must retain live registration order"
35603 );
35604 drop(selection);
35605 assert!(
35606 dead.iter().all(|driver| {
35607 driver.sweeps.load(Ordering::SeqCst) == 1
35608 && driver.removed_before_sweep.load(Ordering::SeqCst)
35609 && driver.registration.lock().unwrap().is_none()
35610 }),
35611 "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
35612 );
35613
35614 {
35615 let state = registry
35616 .state
35617 .lock()
35618 .unwrap_or_else(std::sync::PoisonError::into_inner);
35619 assert_eq!(state.entries.len(), DRIVER_PAIRS);
35620 assert_eq!(state.oldest, live_ids.first().copied());
35621 assert_eq!(state.newest, live_ids.last().copied());
35622 assert_eq!(
35623 state
35624 .ordered_snapshot()
35625 .iter()
35626 .map(|(id, _)| *id)
35627 .collect::<Vec<_>>(),
35628 live_ids,
35629 "keyed removals must preserve the linked order of every live host"
35630 );
35631 }
35632 let second_selection = registry.live();
35633 assert_eq!(
35634 second_selection
35635 .as_slice()
35636 .iter()
35637 .map(|(id, _)| *id)
35638 .collect::<Vec<_>>(),
35639 live_ids,
35640 "a second selection must retain the same live order"
35641 );
35642 drop(second_selection);
35643 assert!(
35644 dead
35645 .iter()
35646 .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
35647 "permanently swept hosts must not receive repeated eviction callbacks"
35648 );
35649 assert_eq!(
35650 registry.current().map(|(id, _)| id),
35651 live_ids.last().copied(),
35652 "newest-live compatibility selection must survive the mass sweep"
35653 );
35654
35655 drop(live);
35656 drop(dead);
35657 }
35658
35659 #[cfg(not(target_family = "wasm"))]
35660 #[test]
35661 fn timer_driver_registry_snapshot_contains_each_driver_drop() {
35662 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
35663
35664 if std::env::var_os(CHILD_ENV).is_some() {
35665 let registry = Arc::new(TimerDriverRegistry::default());
35666 let fallback = ManualStubDriver::new();
35667 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35668 let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
35669 registry: Arc::downgrade(®istry),
35670 registrations: Mutex::new(Vec::new()),
35671 });
35672 registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35673
35674 let first_cancels = Arc::new(AtomicUsize::new(0));
35675 let first_drops = Arc::new(AtomicUsize::new(0));
35676 let first = Arc::new(PanickingCancelDropTimerDriver {
35677 cancels: Arc::clone(&first_cancels),
35678 drops: Arc::clone(&first_drops),
35679 });
35680 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35681 drop(first);
35682
35683 let second_cancels = Arc::new(AtomicUsize::new(0));
35684 let second_drops = Arc::new(AtomicUsize::new(0));
35685 let second = Arc::new(PanickingCancelDropTimerDriver {
35686 cancels: Arc::clone(&second_cancels),
35687 drops: Arc::clone(&second_drops),
35688 });
35689 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35690 drop(second);
35691 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
35692
35693 assert_eq!(
35694 registry.current().map(|(id, _)| id),
35695 Some(fallback_id),
35696 "selection must retry after reentrant removals and preserve the healthy fallback"
35697 );
35698 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35699 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35700 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35701 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35702 assert_eq!(
35703 registry
35704 .state
35705 .lock()
35706 .unwrap_or_else(std::sync::PoisonError::into_inner)
35707 .entries
35708 .len(),
35709 1
35710 );
35711 return;
35712 }
35713
35714 let output = std::process::Command::new(std::env::current_exe().unwrap())
35715 .arg("--exact")
35716 .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
35717 .arg("--nocapture")
35718 .env(CHILD_ENV, "1")
35719 .output()
35720 .expect("the hostile timer-driver snapshot subprocess test must start");
35721 assert!(
35722 output.status.success(),
35723 "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35724 output.status.code(),
35725 String::from_utf8_lossy(&output.stdout),
35726 String::from_utf8_lossy(&output.stderr)
35727 );
35728 }
35729
35730 #[cfg(not(target_family = "wasm"))]
35731 #[test]
35732 fn timer_driver_registry_live_result_contains_each_driver_drop() {
35733 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
35734
35735 if std::env::var_os(CHILD_ENV).is_some() {
35736 let registry = TimerDriverRegistry::default();
35737
35738 let first_cancels = Arc::new(AtomicUsize::new(0));
35739 let first_drops = Arc::new(AtomicUsize::new(0));
35740 let first = Arc::new(PanickingCancelDropTimerDriver {
35741 cancels: Arc::clone(&first_cancels),
35742 drops: Arc::clone(&first_drops),
35743 });
35744 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35745 drop(first);
35746
35747 let second_cancels = Arc::new(AtomicUsize::new(0));
35748 let second_drops = Arc::new(AtomicUsize::new(0));
35749 let second = Arc::new(PanickingCancelDropTimerDriver {
35750 cancels: Arc::clone(&second_cancels),
35751 drops: Arc::clone(&second_drops),
35752 });
35753 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35754 drop(second);
35755
35756 let live = registry.live();
35757 assert_eq!(live.len(), 2);
35758 registry.unregister(first_id);
35759 registry.unregister(second_id);
35760 drop(live);
35761
35762 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35763 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35764 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35765 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35766 assert!(
35767 registry
35768 .state
35769 .lock()
35770 .unwrap_or_else(std::sync::PoisonError::into_inner)
35771 .entries
35772 .is_empty()
35773 );
35774 return;
35775 }
35776
35777 let output = std::process::Command::new(std::env::current_exe().unwrap())
35778 .arg("--exact")
35779 .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
35780 .arg("--nocapture")
35781 .env(CHILD_ENV, "1")
35782 .output()
35783 .expect("the hostile live timer-driver snapshot subprocess test must start");
35784 assert!(
35785 output.status.success(),
35786 "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35787 output.status.code(),
35788 String::from_utf8_lossy(&output.stdout),
35789 String::from_utf8_lossy(&output.stderr)
35790 );
35791 }
35792
35793 #[cfg(not(target_family = "wasm"))]
35794 #[test]
35795 fn timer_driver_registry_drop_contains_each_driver_destructor() {
35796 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
35797
35798 if std::env::var_os(CHILD_ENV).is_some() {
35799 let first_drops = Arc::new(AtomicUsize::new(0));
35800 let second_drops = Arc::new(AtomicUsize::new(0));
35801 let registry = TimerDriverRegistry::default();
35802 let first = Arc::new(PanickingCancelDropTimerDriver {
35803 cancels: Arc::new(AtomicUsize::new(0)),
35804 drops: Arc::clone(&first_drops),
35805 });
35806 let second = Arc::new(PanickingCancelDropTimerDriver {
35807 cancels: Arc::new(AtomicUsize::new(0)),
35808 drops: Arc::clone(&second_drops),
35809 });
35810 registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35811 registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35812 drop(first);
35813 drop(second);
35814
35815 drop(registry);
35816
35817 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35818 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35819 return;
35820 }
35821
35822 let output = std::process::Command::new(std::env::current_exe().unwrap())
35823 .arg("--exact")
35824 .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
35825 .arg("--nocapture")
35826 .env(CHILD_ENV, "1")
35827 .output()
35828 .expect("the hostile timer-driver registry drop subprocess test must start");
35829 assert!(
35830 output.status.success(),
35831 "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
35832 output.status.code(),
35833 String::from_utf8_lossy(&output.stdout),
35834 String::from_utf8_lossy(&output.stderr)
35835 );
35836 }
35837
35838 #[test]
35839 fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
35840 const DRIVERS: usize = 512;
35841
35842 let metrics = Arc::new(RuntimeMetrics::default());
35843 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35844 let backend =
35845 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35846 let registry = Arc::new(TimerDriverRegistry::default());
35847 let drivers = (0..DRIVERS)
35848 .map(|_| {
35849 let driver = ManualStubDriver::new();
35850 registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35851 driver
35852 })
35853 .collect::<Vec<_>>();
35854 let mut sleep = make_sleep(
35855 &backend,
35856 ®istry,
35857 Instant::now() + Duration::from_secs(60),
35858 );
35859 let mut cx = Context::from_waker(Waker::noop());
35860
35861 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35862 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35863 let timer_id = drivers[0].registers.lock().unwrap()[0].0;
35864 match &sleep.inner {
35865 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
35866 #[cfg(napi_runtime_os_threads)]
35867 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35868 }
35869 for driver in &drivers {
35870 assert_eq!(
35871 driver.registers.lock().unwrap().len(),
35872 2,
35873 "a re-poll must refresh each live driver exactly once"
35874 );
35875 }
35876
35877 for driver in drivers.iter().step_by(2) {
35878 driver.kill();
35879 }
35880 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35881 match &sleep.inner {
35882 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
35883 #[cfg(napi_runtime_os_threads)]
35884 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35885 }
35886 for (index, driver) in drivers.iter().enumerate() {
35887 let registers = driver.registers.lock().unwrap().len();
35888 let cancels = driver.cancels.lock().unwrap().clone();
35889 if index % 2 == 0 {
35890 assert_eq!(registers, 2);
35891 assert_eq!(cancels, vec![timer_id]);
35892 } else {
35893 assert_eq!(registers, 3);
35894 assert!(cancels.is_empty());
35895 }
35896 }
35897
35898 drop(sleep);
35899 for driver in drivers {
35900 assert_eq!(
35901 driver.cancels.lock().unwrap().as_slice(),
35902 &[timer_id],
35903 "every physical arm must be cancelled exactly once"
35904 );
35905 }
35906 assert_eq!(registry.pending_len(), 0);
35907 assert!(!executor.host_timers.has_pending());
35908 }
35909
35910 #[cfg(panic = "unwind")]
35911 #[test]
35912 fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
35913 let metrics = Arc::new(RuntimeMetrics::default());
35914 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35915 let backend =
35916 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35917 let healthy = ManualStubDriver::new();
35918 let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
35919 let deadline = Instant::now() + Duration::from_secs(60);
35920 let mut sleep = make_sleep(&backend, ®istry, deadline);
35921 let mut cx = Context::from_waker(Waker::noop());
35922
35923 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35924 let timer_id = healthy.registers.lock().unwrap()[0].0;
35925
35926 let panicking = PanickingRegisterTimerDriver::new();
35927 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35928 let result = catch_unwind(AssertUnwindSafe(|| {
35929 let _ = Pin::new(&mut sleep).poll(&mut cx);
35930 }));
35931 assert!(
35932 result.is_err(),
35933 "the hostile register panic must retain its normal propagation"
35934 );
35935 assert_eq!(
35936 healthy.registers.lock().unwrap().len(),
35937 2,
35938 "the previously armed driver must have completed its refresh before the later panic"
35939 );
35940 assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
35941 assert!(
35942 panicking.pending.lock().unwrap().contains_key(&timer_id),
35943 "the hostile driver must model an arm committed before it panics"
35944 );
35945
35946 drop(sleep);
35947 assert_eq!(
35948 healthy.cancels.lock().unwrap().as_slice(),
35949 &[timer_id],
35950 "unwind must preserve cancellation ownership for the previously armed driver"
35951 );
35952 assert_eq!(
35953 panicking.cancels.lock().unwrap().as_slice(),
35954 &[timer_id],
35955 "unwind must preserve cancellation ownership for the driver that armed and then panicked"
35956 );
35957 assert!(panicking.pending.lock().unwrap().is_empty());
35958 assert_eq!(registry.pending_len(), 0);
35959 assert!(!executor.host_timers.has_pending());
35960 }
35961
35962 #[cfg(not(target_family = "wasm"))]
35963 #[test]
35964 fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
35965 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
35966
35967 if std::env::var_os(CHILD_ENV).is_some() {
35968 let metrics = Arc::new(RuntimeMetrics::default());
35969 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35970 let backend =
35971 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35972 let registry = Arc::new(TimerDriverRegistry::default());
35973 let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
35974 registry: Arc::downgrade(®istry),
35975 later: Mutex::new(None),
35976 registers: AtomicUsize::new(0),
35977 });
35978 registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
35979
35980 let later_cancels = Arc::new(AtomicUsize::new(0));
35981 let later_drops = Arc::new(AtomicUsize::new(0));
35982 let later = Arc::new(PanickingCancelDropTimerDriver {
35983 cancels: Arc::clone(&later_cancels),
35984 drops: Arc::clone(&later_drops),
35985 });
35986 let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
35987 *earlier.later.lock().unwrap() = Some(later_id);
35988 drop(later);
35989
35990 let mut sleep = make_sleep(
35991 &backend,
35992 ®istry,
35993 Instant::now() + Duration::from_secs(60),
35994 );
35995 let mut cx = Context::from_waker(Waker::noop());
35996 let result = catch_unwind(AssertUnwindSafe(|| {
35997 let _ = Pin::new(&mut sleep).poll(&mut cx);
35998 }));
35999
36000 assert!(
36001 result.is_err(),
36002 "the earlier driver's register panic must still propagate"
36003 );
36004 assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
36005 assert_eq!(
36006 later_drops.load(Ordering::SeqCst),
36007 0,
36008 "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
36009 );
36010
36011 drop(sleep);
36012 assert_eq!(
36013 later_cancels.load(Ordering::SeqCst),
36014 1,
36015 "the later driver must remain owned for cancellation despite its registry removal"
36016 );
36017 assert_eq!(
36018 later_drops.load(Ordering::SeqCst),
36019 1,
36020 "the later driver's final destructor must run once behind containment"
36021 );
36022 assert_eq!(registry.pending_len(), 0);
36023 assert!(!executor.host_timers.has_pending());
36024 return;
36025 }
36026
36027 let output = std::process::Command::new(std::env::current_exe().unwrap())
36028 .arg("--exact")
36029 .arg(
36030 "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
36031 )
36032 .arg("--nocapture")
36033 .env(CHILD_ENV, "1")
36034 .output()
36035 .expect("the hostile host timer snapshot subprocess test must start");
36036 assert!(
36037 output.status.success(),
36038 "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
36039 output.status.code(),
36040 String::from_utf8_lossy(&output.stdout),
36041 String::from_utf8_lossy(&output.stderr)
36042 );
36043 }
36044
36045 #[cfg(not(target_family = "wasm"))]
36046 #[test]
36047 fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
36048 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
36049
36050 if std::env::var_os(CHILD_ENV).is_some() {
36051 let metrics = Arc::new(RuntimeMetrics::default());
36052 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36053 let backend =
36054 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36055 let cancels = Arc::new(AtomicUsize::new(0));
36056 let drops = Arc::new(AtomicUsize::new(0));
36057 let hostile = Arc::new(PanickingCancelDropTimerDriver {
36058 cancels: Arc::clone(&cancels),
36059 drops: Arc::clone(&drops),
36060 });
36061 let healthy = ManualStubDriver::new();
36062 let registry = Arc::new(TimerDriverRegistry::default());
36063 let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
36064 registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
36065 let mut sleep = make_sleep(
36066 &backend,
36067 ®istry,
36068 Instant::now() + Duration::from_secs(60),
36069 );
36070 let mut cx = Context::from_waker(Waker::noop());
36071
36072 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36073 let timer_id = healthy.registers.lock().unwrap()[0].0;
36074
36075 registry.unregister(hostile_id);
36078 drop(hostile);
36079 drop(sleep);
36080 assert_eq!(cancels.load(Ordering::SeqCst), 1);
36081 assert_eq!(drops.load(Ordering::SeqCst), 1);
36082 assert_eq!(
36083 healthy.cancels.lock().unwrap().as_slice(),
36084 &[timer_id],
36085 "one hostile cancellation must not skip later armed drivers"
36086 );
36087 assert_eq!(registry.pending_len(), 0);
36088 assert!(!executor.host_timers.has_pending());
36089 return;
36090 }
36091
36092 let output = std::process::Command::new(std::env::current_exe().unwrap())
36093 .arg("--exact")
36094 .arg(
36095 "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
36096 )
36097 .arg("--nocapture")
36098 .env(CHILD_ENV, "1")
36099 .output()
36100 .expect("the hostile timer-driver cancellation subprocess test must start");
36101 assert!(
36102 output.status.success(),
36103 "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
36104 output.status.code(),
36105 String::from_utf8_lossy(&output.stdout),
36106 String::from_utf8_lossy(&output.stderr)
36107 );
36108 }
36109
36110 #[cfg(not(target_family = "wasm"))]
36111 #[test]
36112 fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
36113 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
36114
36115 if std::env::var_os(CHILD_ENV).is_some() {
36116 let metrics = Arc::new(RuntimeMetrics::default());
36117 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36118 let backend =
36119 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36120 let driver = ManualStubDriver::new();
36121 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36122 let state = Arc::new(TestHostileRawWakerState {
36123 clones: AtomicUsize::new(0),
36124 drop_panics: AtomicUsize::new(0),
36125 panic_on_clone: Some(2),
36126 });
36127
36128 let result = catch_unwind(AssertUnwindSafe(|| {
36129 let mut sleep = make_sleep(
36130 &backend,
36131 ®istry,
36132 Instant::now() + Duration::from_secs(60),
36133 );
36134 let waker = test_hostile_waker(Arc::clone(&state));
36135 let mut cx = Context::from_waker(&waker);
36136 let _ = Pin::new(&mut sleep).poll(&mut cx);
36137 }));
36138 assert!(
36139 result.is_err(),
36140 "the original RawWaker clone panic must still propagate"
36141 );
36142 assert_eq!(state.clones.load(Ordering::SeqCst), 2);
36143 assert_eq!(
36144 state.drop_panics.load(Ordering::SeqCst),
36145 1,
36146 "the first clone must be destroyed once behind a contained boundary"
36147 );
36148 assert!(driver.registers.lock().unwrap().is_empty());
36149 assert_eq!(registry.pending_len(), 0);
36150 assert!(!executor.host_timers.has_pending());
36151 return;
36152 }
36153
36154 let output = std::process::Command::new(std::env::current_exe().unwrap())
36155 .arg("--exact")
36156 .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
36157 .arg("--nocapture")
36158 .env(CHILD_ENV, "1")
36159 .output()
36160 .expect("the hostile RawWaker subprocess test must start");
36161 assert!(
36162 output.status.success(),
36163 "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
36164 output.status.code(),
36165 String::from_utf8_lossy(&output.stdout),
36166 String::from_utf8_lossy(&output.stderr)
36167 );
36168 }
36169
36170 #[cfg(not(target_family = "wasm"))]
36171 #[test]
36172 fn host_sleep_register_unwind_contains_unretained_waker_drop() {
36173 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
36174
36175 if std::env::var_os(CHILD_ENV).is_some() {
36176 let metrics = Arc::new(RuntimeMetrics::default());
36177 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36178 let backend =
36179 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36180 let driver = Arc::new(PanicBeforeRetainingTimerDriver {
36181 registers: AtomicUsize::new(0),
36182 });
36183 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36184 let state = Arc::new(TestHostileRawWakerState {
36185 clones: AtomicUsize::new(0),
36186 drop_panics: AtomicUsize::new(0),
36187 panic_on_clone: None,
36188 });
36189 let mut sleep = make_sleep(
36190 &backend,
36191 ®istry,
36192 Instant::now() + Duration::from_secs(60),
36193 );
36194 let waker = test_hostile_waker(Arc::clone(&state));
36195 let mut cx = Context::from_waker(&waker);
36196
36197 let result = catch_unwind(AssertUnwindSafe(|| {
36198 let _ = Pin::new(&mut sleep).poll(&mut cx);
36199 }));
36200 assert!(
36201 result.is_err(),
36202 "the timer-driver register panic must still propagate"
36203 );
36204 assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
36205 assert_eq!(state.clones.load(Ordering::SeqCst), 3);
36206 assert_eq!(
36207 state.drop_panics.load(Ordering::SeqCst),
36208 1,
36209 "the unretained register argument must be destroyed once behind the proxy boundary"
36210 );
36211 assert_eq!(registry.pending_len(), 1);
36212 assert!(executor.host_timers.has_pending());
36213
36214 drop(sleep);
36215 assert_eq!(
36216 state.drop_panics.load(Ordering::SeqCst),
36217 3,
36218 "repoll and registry wakers must retain their existing contained cleanup"
36219 );
36220 assert_eq!(registry.pending_len(), 0);
36221 assert!(!executor.host_timers.has_pending());
36222 return;
36223 }
36224
36225 let output = std::process::Command::new(std::env::current_exe().unwrap())
36226 .arg("--exact")
36227 .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
36228 .arg("--nocapture")
36229 .env(CHILD_ENV, "1")
36230 .output()
36231 .expect("the hostile register-waker subprocess test must start");
36232 assert!(
36233 output.status.success(),
36234 "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
36235 output.status.code(),
36236 String::from_utf8_lossy(&output.stdout),
36237 String::from_utf8_lossy(&output.stderr)
36238 );
36239 }
36240
36241 #[test]
36242 fn host_sleep_rearms_on_next_live_driver_after_eviction() {
36243 let metrics = Arc::new(RuntimeMetrics::default());
36249 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36250 let backend =
36251 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36252
36253 let a = ManualStubDriver::new();
36254 let b = ManualStubDriver::new();
36255 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36256 let deadline = Instant::now() + Duration::from_secs(60);
36257
36258 let (timer_id, b_id) = {
36259 let mut sleep = std::pin::pin!(make_sleep(&backend, ®istry, deadline));
36260 let mut cx = Context::from_waker(Waker::noop());
36261
36262 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36264 let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
36265 assert_eq!(armed_deadline, deadline);
36266 assert!(
36267 executor.host_timers.has_pending(),
36268 "first poll must arm the wake-token"
36269 );
36270
36271 a.kill();
36275 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36276 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36277 assert_eq!(
36278 b.registers.lock().unwrap().as_slice(),
36279 &[(timer_id, deadline)],
36280 "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
36281 );
36282 assert_eq!(
36283 a.cancels.lock().unwrap().as_slice(),
36284 &[timer_id],
36285 "the dead driver gets a best-effort cancel for the old arm"
36286 );
36287 assert_eq!(
36288 registry.current().map(|(id, _)| id),
36289 Some(b_id),
36290 "the dead driver was evicted"
36291 );
36292 assert!(
36293 executor.host_timers.has_pending(),
36294 "the wake-token survives the re-arm (still exactly this sleep's entry)"
36295 );
36296 (timer_id, b_id)
36297 };
36298
36299 let _ = b_id;
36302 assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
36303 assert!(
36304 !executor.host_timers.has_pending(),
36305 "drop must retire the wake-token"
36306 );
36307 }
36308
36309 #[cfg(napi_runtime_os_threads)]
36310 #[test]
36311 fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
36312 let metrics = Arc::new(RuntimeMetrics::default());
36313 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36314 let backend =
36315 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36316 let responsive = Arc::new(StubHostTimerDriver::new());
36317 let starved = ManualStubDriver::new();
36318 let registry = Arc::new(TimerDriverRegistry::default());
36319 registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
36320 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36323
36324 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
36325 let waker = Waker::from(Arc::clone(&wake_count));
36326 let mut cx = Context::from_waker(&waker);
36327 let deadline = Instant::now() + Duration::from_millis(500);
36332 let mut sleep = make_sleep(&backend, ®istry, deadline);
36333 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36334
36335 let responsive_timer = *responsive
36336 .timers
36337 .lock()
36338 .unwrap()
36339 .keys()
36340 .next()
36341 .expect("responsive timer must be armed");
36342 assert_eq!(
36343 starved.registers.lock().unwrap().as_slice(),
36344 &[(responsive_timer, deadline)],
36345 "every live host must receive the same timer identity and absolute deadline"
36346 );
36347
36348 let fire_bound = Instant::now() + Duration::from_secs(30);
36355 while wake_count.0.load(Ordering::SeqCst) == 0 {
36356 assert!(
36357 Instant::now() < fire_bound,
36358 "the responsive host must wake a sleep whose newest host is live but starved"
36359 );
36360 std::thread::sleep(Duration::from_millis(10));
36361 }
36362 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36365 assert_eq!(
36366 starved.cancels.lock().unwrap().as_slice(),
36367 &[responsive_timer],
36368 "completion through the winning host must cancel the starved losing arm"
36369 );
36370 }
36371
36372 #[cfg(napi_runtime_os_threads)]
36373 #[test]
36374 fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
36375 use std::sync::mpsc;
36376
36377 struct ReentrantTimerRegistrationWake {
36378 registry: Weak<TimerDriverRegistry>,
36379 wakes: AtomicUsize,
36380 reentered: AtomicBool,
36381 }
36382
36383 impl std::task::Wake for ReentrantTimerRegistrationWake {
36384 fn wake(self: Arc<Self>) {
36385 self.wakes.fetch_add(1, Ordering::SeqCst);
36386 if let Some(registry) = self.registry.upgrade() {
36387 let _ = registry.has_live_driver();
36388 self.reentered.store(true, Ordering::SeqCst);
36389 }
36390 }
36391 }
36392
36393 let metrics = Arc::new(RuntimeMetrics::default());
36394 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36395 let backend =
36396 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36397 let registry = Arc::new(TimerDriverRegistry::default());
36398 let starved = ManualStubDriver::new();
36399 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36400
36401 let deadline = Instant::now() + Duration::from_secs(2);
36408 let mut first = make_sleep(&backend, ®istry, deadline);
36409 let mut second = make_sleep(&backend, ®istry, deadline);
36410 let first_wake = Arc::new(ReentrantTimerRegistrationWake {
36411 registry: Arc::downgrade(®istry),
36412 wakes: AtomicUsize::new(0),
36413 reentered: AtomicBool::new(false),
36414 });
36415 let second_wake = Arc::new(ReentrantTimerRegistrationWake {
36416 registry: Arc::downgrade(®istry),
36417 wakes: AtomicUsize::new(0),
36418 reentered: AtomicBool::new(false),
36419 });
36420 let first_waker = Waker::from(Arc::clone(&first_wake));
36421 let second_waker = Waker::from(Arc::clone(&second_wake));
36422 let mut first_cx = Context::from_waker(&first_waker);
36423 let mut second_cx = Context::from_waker(&second_waker);
36424
36425 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36426 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36427 let initial = starved.registers.lock().unwrap().clone();
36428 let first_id = initial[0].0;
36429 let second_id = initial[1].0;
36430 assert_ne!(first_id, second_id);
36431 assert_eq!(
36432 registry.pending_len(),
36433 2,
36434 "one stable repoll entry is retained per sleep"
36435 );
36436
36437 let responsive = Arc::new(StubHostTimerDriver::new());
36438 let (registered_tx, registered_rx) = mpsc::sync_channel(1);
36439 let registration_registry = Arc::clone(®istry);
36440 let registration_driver = Arc::clone(&responsive);
36441 let registration = std::thread::spawn(move || {
36442 let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
36443 registered_tx.send(id).unwrap();
36444 });
36445 registered_rx
36449 .recv_timeout(Duration::from_secs(10))
36450 .expect("late driver registration or repoll callback re-entry deadlocked");
36451 registration.join().unwrap();
36452
36453 assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
36454 assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
36455 assert!(first_wake.reentered.load(Ordering::SeqCst));
36456 assert!(second_wake.reentered.load(Ordering::SeqCst));
36457 assert!(
36458 responsive.timers.lock().unwrap().is_empty(),
36459 "registration requests bounded re-polls instead of invoking driver callbacks inline"
36460 );
36461
36462 assert!(
36467 Instant::now() < deadline,
36468 "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
36469 this runner is starved beyond the margin this test budgets for"
36470 );
36471 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36472 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36473 let responsive_ids = responsive
36474 .timers
36475 .lock()
36476 .unwrap()
36477 .keys()
36478 .copied()
36479 .collect::<FxHashSet<_>>();
36480 assert_eq!(
36481 responsive_ids,
36482 FxHashSet::from_iter([first_id, second_id]),
36483 "every pending sleep must re-arm its existing identity on the new driver"
36484 );
36485 assert_eq!(
36486 starved
36487 .registers
36488 .lock()
36489 .unwrap()
36490 .iter()
36491 .filter(|(id, _)| *id == first_id)
36492 .count(),
36493 2,
36494 "the old host receives only the normal waker refresh on the one re-poll"
36495 );
36496 assert_eq!(
36497 starved
36498 .registers
36499 .lock()
36500 .unwrap()
36501 .iter()
36502 .filter(|(id, _)| *id == second_id)
36503 .count(),
36504 2,
36505 "the old host receives only the normal waker refresh on the one re-poll"
36506 );
36507 assert_eq!(
36508 registry.pending_len(),
36509 2,
36510 "late registration must not duplicate pending state"
36511 );
36512
36513 let fire_bound = Instant::now() + Duration::from_secs(30);
36520 while first_wake.wakes.load(Ordering::SeqCst) < 2
36521 || second_wake.wakes.load(Ordering::SeqCst) < 2
36522 {
36523 assert!(
36524 Instant::now() < fire_bound,
36525 "the late responsive host must fire both timers while the original host remains starved"
36526 );
36527 std::thread::sleep(Duration::from_millis(10));
36528 }
36529 assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
36530 assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
36531
36532 let mut cancelled = starved.cancels.lock().unwrap().clone();
36533 cancelled.sort_unstable();
36534 let mut expected = vec![first_id, second_id];
36535 expected.sort_unstable();
36536 assert_eq!(
36537 cancelled, expected,
36538 "completion must cancel both losing starved-host arms"
36539 );
36540 assert_eq!(
36541 registry.pending_len(),
36542 0,
36543 "completion retires every repoll entry"
36544 );
36545 assert!(!executor.host_timers.has_pending());
36546 }
36547
36548 #[cfg(panic = "unwind")]
36549 #[test]
36550 fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
36551 let metrics = Arc::new(RuntimeMetrics::default());
36555 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36556 let backend =
36557 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36558
36559 let a = ManualStubDriver::new();
36560 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36561 let mut sleep = std::pin::pin!(make_sleep(
36562 &backend,
36563 ®istry,
36564 Instant::now() + Duration::from_secs(60)
36565 ));
36566 let mut cx = Context::from_waker(Waker::noop());
36567 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36568
36569 a.kill();
36570 let payload = catch_unwind(AssertUnwindSafe(|| {
36571 let _ = sleep.as_mut().poll(&mut cx);
36572 }))
36573 .expect_err("a sleep with every driver dead must panic, not pend silently");
36574 let message = payload
36575 .downcast_ref::<String>()
36576 .cloned()
36577 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
36578 .expect("panic payload must be a message");
36579 assert!(
36580 message.contains("lost every live timer driver mid-sleep"),
36581 "the diagnostic must name the mid-flight driver loss, got: {message}"
36582 );
36583 }
36584
36585 #[test]
36586 fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
36587 let metrics = Arc::new(RuntimeMetrics::default());
36594 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36595 let backend =
36596 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36597
36598 let a = ManualStubDriver::new();
36599 let b = ManualStubDriver::new();
36600 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36601 let mut sleep = std::pin::pin!(make_sleep(
36602 &backend,
36603 ®istry,
36604 Instant::now() + Duration::from_secs(60)
36605 ));
36606
36607 let flag = Arc::new(WakeFlag::default());
36609 let waker = Waker::from(Arc::clone(&flag));
36610 let mut cx = Context::from_waker(&waker);
36611 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36612 assert!(!flag.0.load(Ordering::SeqCst));
36613
36614 a.kill();
36617 registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36618 let _ = registry.has_live_driver();
36619
36620 assert!(
36621 a.swept.load(Ordering::SeqCst),
36622 "the sweep must notify the swept driver"
36623 );
36624 assert!(
36625 flag.0.load(Ordering::SeqCst),
36626 "a sleep armed on the swept driver must be woken into re-selection, not stranded"
36627 );
36628
36629 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36631 assert_eq!(
36632 b.registers.lock().unwrap().len(),
36633 1,
36634 "the re-poll must re-arm on the live driver"
36635 );
36636 }
36637
36638 #[cfg(napi_runtime_os_threads)]
36639 #[test]
36640 fn current_thread_sleep_fires_through_stub_host_driver() {
36641 use std::sync::mpsc;
36644
36645 let (done_tx, done_rx) = mpsc::channel();
36646 let runner = std::thread::spawn(move || {
36647 let metrics = Arc::new(RuntimeMetrics::default());
36648 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36649 let backend =
36650 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36651 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36652
36653 let started = Instant::now();
36654 let mut future = std::pin::pin!(async {
36655 make_sleep(
36656 &backend,
36657 ®istry_with(driver),
36658 Instant::now() + Duration::from_millis(60),
36659 )
36660 .await;
36661 });
36662 executor.block_on(future.as_mut());
36663 assert!(started.elapsed() >= Duration::from_millis(60));
36664 assert!(
36665 !executor.host_timers.has_pending(),
36666 "a completed sleep must retire its pending-registry entry"
36667 );
36668 let _ = done_tx.send(());
36669 });
36670
36671 match done_rx.recv_timeout(Duration::from_secs(10)) {
36672 Ok(()) => runner.join().unwrap(),
36673 Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
36674 }
36675 }
36676
36677 #[test]
36678 fn current_thread_timer_shutdown_retires_pending_repoll_state() {
36679 let metrics = Arc::new(RuntimeMetrics::default());
36680 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36681 let backend =
36682 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36683 let drivers = Arc::new(TimerDriverRegistry::default());
36684 let driver = ManualStubDriver::new();
36685 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36686 let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
36687 let mut cx = Context::from_waker(Waker::noop());
36688
36689 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36690 let timer_id = driver.registers.lock().unwrap()[0].0;
36691 assert_eq!(drivers.pending_len(), 1);
36692
36693 executor.host_timers.shutdown();
36694 assert_eq!(
36695 drivers.pending_len(),
36696 0,
36697 "shutdown must retire the registration-repoll index before the Sleep is dropped"
36698 );
36699 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36700 assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
36701 }
36702
36703 #[cfg(napi_runtime_os_threads)]
36704 #[test]
36705 fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
36706 use std::sync::mpsc;
36707
36708 let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
36709 let backend = controller.backend();
36710 let drivers = Arc::new(TimerDriverRegistry::default());
36711 let driver = ManualStubDriver::new();
36712 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36713 let sleep = make_sleep(
36714 &backend,
36715 &drivers,
36716 Instant::now() + Duration::from_secs(120),
36717 );
36718 drop(backend);
36719
36720 let (polled_tx, polled_rx) = mpsc::channel();
36721 let block_controller = Arc::clone(&controller);
36722 let blocker = std::thread::spawn(move || {
36723 let mut announced = false;
36724 let mut sleep = std::pin::pin!(sleep);
36725 let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
36726 let poll = sleep.as_mut().poll(cx);
36727 if !announced {
36728 announced = true;
36729 polled_tx.send(()).unwrap();
36730 }
36731 poll
36732 }));
36733 block_controller.block_on(future.as_mut());
36734 });
36735 polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
36736
36737 let (shutdown_tx, shutdown_rx) = mpsc::channel();
36738 let shutdown_controller = Arc::clone(&controller);
36739 let shutdown = std::thread::spawn(move || {
36740 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
36741 });
36742 shutdown_rx
36743 .recv_timeout(Duration::from_secs(2))
36744 .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
36745 .unwrap();
36746 shutdown.join().unwrap();
36747 blocker.join().unwrap();
36748 assert_eq!(driver.cancels.lock().unwrap().len(), 1);
36749 }
36750
36751 #[cfg(panic = "unwind")]
36752 #[test]
36753 fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
36754 use std::sync::mpsc;
36765
36766 let (done_tx, done_rx) = mpsc::channel();
36767 let runner = std::thread::spawn(move || {
36768 let metrics = Arc::new(RuntimeMetrics::default());
36769 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36773 let backend =
36774 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36775 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36776
36777 let payload = catch_unwind(AssertUnwindSafe(|| {
36778 let mut future = std::pin::pin!(async {
36779 make_sleep(
36780 &backend,
36781 ®istry_with(driver),
36782 Instant::now() + Duration::from_millis(60),
36783 )
36784 .await;
36785 });
36786 executor.block_on(future.as_mut());
36787 }))
36788 .expect_err("a threadless park must self-detect even while a host timer is pending");
36789 let diagnostic = payload
36790 .downcast_ref::<BlockOnDeadlock>()
36791 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36792 assert_eq!(
36793 diagnostic.kind,
36794 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
36795 );
36796 assert_eq!(
36797 diagnostic.park_deadline, None,
36798 "the certain case carries no deadline"
36799 );
36800 let message = diagnostic.to_string();
36801 assert!(
36802 message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
36803 "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
36804 );
36805 let _ = done_tx.send(());
36806 });
36807
36808 match done_rx.recv_timeout(Duration::from_secs(10)) {
36809 Ok(()) => runner.join().unwrap(),
36810 Err(error) => panic!(
36811 "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})"
36812 ),
36813 }
36814 }
36815
36816 #[cfg(panic = "unwind")]
36817 #[test]
36818 fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
36819 use std::sync::mpsc;
36828
36829 let (done_tx, done_rx) = mpsc::channel();
36830 let runner = std::thread::spawn(move || {
36831 let metrics = Arc::new(RuntimeMetrics::default());
36832 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36833 let backend =
36834 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36835 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36836
36837 let _unpolled = make_sleep(
36840 &backend,
36841 ®istry_with(driver),
36842 Instant::now() + Duration::from_secs(60),
36843 );
36844
36845 let payload = catch_unwind(AssertUnwindSafe(|| {
36846 let mut future = std::pin::pin!(NeverReady);
36847 executor.block_on(future.as_mut());
36848 }))
36849 .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
36850 let diagnostic = payload
36851 .downcast_ref::<BlockOnDeadlock>()
36852 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36853 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
36854 let _ = done_tx.send(());
36855 });
36856
36857 match done_rx.recv_timeout(Duration::from_secs(10)) {
36858 Ok(()) => runner.join().unwrap(),
36859 Err(error) => panic!(
36860 "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
36861 ),
36862 }
36863 }
36864
36865 #[cfg(napi_runtime_os_threads)]
36866 #[test]
36867 fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
36868 use std::sync::mpsc;
36873
36874 let (done_tx, done_rx) = mpsc::channel();
36875 let runner = std::thread::spawn(move || {
36876 let metrics = Arc::new(RuntimeMetrics::default());
36877 let executor = Arc::new(CurrentThreadExecutor::with_detection(
36889 metrics,
36890 false,
36891 Some(Duration::from_secs(2)),
36892 ));
36893 let backend =
36894 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36895 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36896
36897 let started = Instant::now();
36898 let mut future = std::pin::pin!(async {
36899 make_sleep(
36900 &backend,
36901 ®istry_with(driver),
36902 Instant::now() + Duration::from_millis(200),
36903 )
36904 .await;
36905 });
36906 executor.block_on(future.as_mut());
36907 assert!(started.elapsed() >= Duration::from_millis(200));
36908 let _ = done_tx.send(());
36909 });
36910
36911 match done_rx.recv_timeout(Duration::from_secs(30)) {
36912 Ok(()) => runner.join().unwrap(),
36913 Err(error) => {
36914 panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
36915 }
36916 }
36917 }
36918
36919 #[test]
36920 fn host_timer_unrepresentable_grace_remains_live() {
36921 let registry = HostTimerRegistry::default();
36922 let fired = Arc::new(AtomicBool::new(false));
36923 let drivers = Arc::new(TimerDriverRegistry::default());
36924 let repoll = Arc::new(HostTimerRepoll::default());
36925 assert!(
36926 Instant::now().checked_add(Duration::MAX).is_none(),
36927 "Duration::MAX must exercise the unrepresentable-grace path"
36928 );
36929 assert!(
36930 registry.register(
36931 1,
36932 Instant::now(),
36933 futures::task::noop_waker(),
36934 &fired,
36935 &drivers,
36936 &repoll,
36937 ),
36938 "the open registry must accept the timer"
36939 );
36940
36941 assert!(
36942 registry.has_live_wait(Duration::MAX),
36943 "an unrepresentable grace deadline is effectively unbounded"
36944 );
36945 registry.remove(1);
36946 }
36947
36948 #[cfg(napi_runtime_os_threads)]
36951 #[test]
36952 fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
36953 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
36958 let expected = diagnostic.to_string();
36959 let payload: Box<dyn Any + Send> = Box::new(diagnostic);
36960 assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
36961 }
36962 #[cfg(napi_runtime_wasi_threads)]
36970 #[test]
36971 fn wasi_threads_reports_a_thread_capable_worker_ceiling() {
36972 let ceiling = crate::max_async_runtime_worker_threads();
36976 assert_eq!(
36977 ceiling, 255,
36978 "the threaded WASI target must report Rayon's realizable ceiling"
36979 );
36980 assert!(
36981 ceiling >= 2,
36982 "a ceiling below two would make MultiThread unconfigurable"
36983 );
36984 }
36985
36986 #[cfg(napi_runtime_wasi_threads)]
36987 #[test]
36988 fn wasi_threads_default_flavor_is_still_current_thread() {
36989 assert_eq!(
36993 RuntimeOptions::default().flavor,
36994 RuntimeFlavor::CurrentThread,
36995 "the threaded WASI target must not silently default to MultiThread"
36996 );
36997 }
36998
36999 #[cfg(napi_runtime_wasi_threads)]
37000 #[test]
37001 fn wasi_threads_multi_thread_validates_to_the_two_worker_minimum() {
37002 let validated = RuntimeOptions {
37008 flavor: RuntimeFlavor::MultiThread,
37009 ..RuntimeOptions::default()
37010 }
37011 .validate()
37012 .expect("MultiThread must validate on the threaded WASI target");
37013 assert_eq!(validated.worker_threads, 2);
37014 assert_eq!(validated.max_blocking_tasks, 1);
37015 }
37016
37017 #[cfg(napi_runtime_wasi_threads)]
37018 #[test]
37019 fn threadless_build_is_false_on_the_threads_target() {
37020 assert!(
37025 !THREADLESS_BUILD,
37026 "the threaded WASI target has real OS threads"
37027 );
37028 assert!(
37029 cfg!(napi_runtime_os_threads),
37030 "the threaded WASI target must build the OS-thread machinery"
37031 );
37032 }
37033
37034 #[cfg(napi_runtime_wasi_threads)]
37046 fn grow_one_page_elsewhere() -> usize {
37047 std::thread::spawn(|| {
37048 let old = core::arch::wasm32::memory_grow::<0>(1);
37049 assert_ne!(old, usize::MAX, "the WASI host must let the memory grow");
37050 napi_sys::wasi_heap_sync::grow_zero()
37051 })
37052 .join()
37053 .expect("the growing thread must finish")
37054 }
37055
37056 #[cfg(napi_runtime_wasi_threads)]
37057 #[test]
37058 fn wasi_threads_multi_thread_task_polls_refresh_the_memory_size() {
37059 let controller = multi_thread_controller("wasi-handoff-task", 2, 1);
37062 let pages = grow_one_page_elsewhere();
37063 let refreshes = napi_sys::wasi_heap_sync::handoff_refreshes();
37064 let seen = futures::executor::block_on(
37065 controller.spawn(async { napi_sys::wasi_heap_sync::local_pages() }),
37066 )
37067 .expect("the task must finish");
37068 assert!(
37069 seen >= pages,
37070 "the worker polled with a stale memory size: {seen} < {pages} pages"
37071 );
37072 assert!(
37073 napi_sys::wasi_heap_sync::handoff_refreshes() > refreshes,
37074 "the handoff refresh must be counted"
37075 );
37076 controller.shutdown().expect("runtime must stop");
37077 }
37078
37079 #[cfg(napi_runtime_wasi_threads)]
37080 #[test]
37081 fn wasi_threads_current_thread_task_polls_refresh_the_memory_size() {
37082 use std::sync::mpsc;
37085
37086 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37087 RuntimeMetrics::default(),
37088 )));
37089 let (seen_tx, seen_rx) = mpsc::channel();
37090 let wake_executor = Arc::clone(&executor);
37091 let (runnable, task) = async_task::spawn(
37092 async move {
37093 let _ = seen_tx.send(napi_sys::wasi_heap_sync::local_pages());
37094 },
37095 move |runnable| wake_executor.schedule(runnable),
37096 );
37097 executor.schedule(runnable);
37098 let pages = grow_one_page_elsewhere();
37099 executor.drive_host_turn();
37100 let seen = seen_rx
37101 .try_recv()
37102 .expect("one host turn must poll the task");
37103 futures::executor::block_on(task);
37104 assert!(
37105 seen >= pages,
37106 "the host turn polled with a stale memory size: {seen} < {pages} pages"
37107 );
37108 }
37109
37110 #[cfg(napi_runtime_wasi_threads)]
37111 #[test]
37112 fn wasi_threads_block_on_polls_refresh_the_memory_size() {
37113 for controller in [
37117 current_thread_controller("wasi-handoff-block-on"),
37118 multi_thread_controller("wasi-handoff-parking-block-on", 2, 1),
37119 ] {
37120 let pages = grow_one_page_elsewhere();
37121 let mut seen = 0;
37122 {
37123 let mut future = std::pin::pin!(async {
37124 seen = napi_sys::wasi_heap_sync::local_pages();
37125 });
37126 assert_eq!(
37127 controller.block_on(future.as_mut()),
37128 BlockOnOutcome::Completed
37129 );
37130 }
37131 assert!(
37132 seen >= pages,
37133 "block_on polled with a stale memory size: {seen} < {pages} pages"
37134 );
37135 controller.shutdown().expect("runtime must stop");
37136 }
37137 }
37138
37139 #[cfg(napi_runtime_wasi_threads)]
37140 #[test]
37141 fn wasi_threads_blocking_closures_refresh_the_memory_size() {
37142 let controller = Arc::new(multi_thread_controller("wasi-handoff-blocking", 2, 1));
37146 let pages = grow_one_page_elsewhere();
37147 let block_on_controller = Arc::clone(&controller);
37148 let (start_seen, block_on_pages, block_on_seen) =
37149 futures::executor::block_on(controller.spawn_blocking(move || {
37150 let start_seen = napi_sys::wasi_heap_sync::local_pages();
37151 let block_on_pages = grow_one_page_elsewhere();
37152 let mut block_on_seen = 0;
37153 {
37154 let mut future = std::pin::pin!(async {
37155 block_on_seen = napi_sys::wasi_heap_sync::local_pages();
37156 });
37157 assert_eq!(
37158 block_on_controller.block_on(future.as_mut()),
37159 BlockOnOutcome::Completed
37160 );
37161 }
37162 (start_seen, block_on_pages, block_on_seen)
37163 }))
37164 .expect("the blocking closure must finish");
37165 assert!(
37166 start_seen >= pages,
37167 "the blocking closure started with a stale memory size: {start_seen} < {pages} pages"
37168 );
37169 assert!(
37170 block_on_seen >= block_on_pages,
37171 "the cooperative block_on polled with a stale memory size: \
37172 {block_on_seen} < {block_on_pages} pages"
37173 );
37174 controller.shutdown().expect("runtime must stop");
37175 }
37176
37177 #[cfg(napi_runtime_wasi_threads)]
37178 #[test]
37179 fn wasi_threads_inline_blocking_refreshes_the_memory_size() {
37180 let controller = current_thread_controller("wasi-handoff-inline-blocking");
37183 let pages = grow_one_page_elsewhere();
37184 let seen =
37185 futures::executor::block_on(controller.spawn_blocking(napi_sys::wasi_heap_sync::local_pages))
37186 .expect("the inline blocking closure must finish");
37187 assert!(
37188 seen >= pages,
37189 "the inline blocking closure ran with a stale memory size: {seen} < {pages} pages"
37190 );
37191 controller.shutdown().expect("runtime must stop");
37192 }
37193
37194 #[cfg(not(napi_runtime_os_threads))]
37198 #[test]
37199 fn threadless_wasm_still_rejects_multi_thread() {
37200 let error = RuntimeOptions {
37201 flavor: RuntimeFlavor::MultiThread,
37202 ..RuntimeOptions::default()
37203 }
37204 .validate()
37205 .expect_err("threadless WebAssembly cannot run the multi-thread runtime");
37206 let message = error.to_string();
37207 assert!(
37208 message.contains("threadless"),
37209 "the rejection must name the property that causes it, not the family: {message}"
37210 );
37211 assert!(
37212 message.contains("wasm32-wasip1-threads"),
37213 "the rejection must name the target that does work: {message}"
37214 );
37215 }
37216
37217 #[cfg(napi_runtime_os_threads)]
37218 #[test]
37219 fn multi_thread_pool_starts_and_joins_every_worker() {
37220 let options = RuntimeOptions {
37224 flavor: RuntimeFlavor::MultiThread,
37225 worker_threads: 3,
37226 max_blocking_tasks: 2,
37227 thread_name_prefix: "rd-os-threads-join".to_string(),
37228 park_deadline: None,
37229 drain_linger: DEFAULT_DRAIN_LINGER,
37230 };
37231 let executor = Arc::new(
37232 MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default()))
37233 .expect("a three-worker pool must build"),
37234 );
37235 assert_eq!(executor.pool.current_num_threads(), 3);
37236
37237 let ran = Arc::new(AtomicBool::new(false));
37238 let task_ran = Arc::clone(&ran);
37239 let (runnable, task) = async_task::spawn(
37240 async move {
37241 task_ran.store(true, Ordering::SeqCst);
37242 },
37243 |_| {},
37244 );
37245 executor.schedule(runnable);
37246 futures::executor::block_on(task);
37247 assert!(
37248 ran.load(Ordering::SeqCst),
37249 "the pool must run scheduled work"
37250 );
37251
37252 let lifecycle = Arc::clone(&executor.worker_lifecycle);
37253 executor.begin_shutdown();
37254 executor.wait_until_scheduler_idle();
37255 let deadline = Instant::now() + Duration::from_secs(10);
37261 while Arc::strong_count(&executor) > 1 && Instant::now() < deadline {
37262 std::thread::yield_now();
37263 }
37264 let executor = Arc::into_inner(executor)
37265 .expect("no drainer may outlive `wait_until_scheduler_idle` for long");
37266 drop(executor);
37267 lifecycle.wait_for_all_workers();
37268 assert_eq!(
37269 lifecycle.remaining(),
37270 0,
37271 "every physical worker must have exited before the join returns"
37272 );
37273 }
37274
37275 #[cfg(napi_runtime_os_threads)]
37276 #[test]
37277 fn current_thread_queues_concurrent_blocking_admission() {
37278 use std::sync::mpsc;
37285
37286 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37287 RuntimeMetrics::default(),
37288 )));
37289 let work = GenerationWork::new();
37290 let (outcome_tx, outcome_rx) = mpsc::channel();
37291
37292 let owner_registration = work
37293 .try_register_work()
37294 .expect("the lane owner's blocking job must be accepted");
37295 let concurrent_work = Arc::clone(&work);
37296 let concurrent_executor = Arc::clone(&executor);
37297 let task_executor = Arc::clone(&executor);
37298 let wake_executor = Arc::clone(&executor);
37299 let (runnable, task) = async_task::spawn(
37300 async move {
37301 let concurrent = task_executor
37302 .schedule_blocking(
37303 move || {
37304 std::thread::spawn(move || {
37305 let registration = concurrent_work
37306 .try_register_work()
37307 .expect("the concurrent blocking job must be accepted");
37308 concurrent_executor.schedule_blocking(|| 7usize, registration)
37309 })
37310 .join()
37311 .expect("the concurrent submitter thread must finish")
37312 },
37313 owner_registration,
37314 )
37315 .await
37316 .expect("the lane owner's blocking job must finish");
37317 let _ = outcome_tx.send(concurrent.await);
37318 },
37319 move |runnable| wake_executor.schedule(runnable),
37320 );
37321
37322 executor.schedule(runnable);
37323 let mut turns = 0usize;
37324 let outcome = loop {
37325 executor.drive_host_turn();
37326 if let Ok(outcome) = outcome_rx.try_recv() {
37327 break outcome;
37328 }
37329 turns += 1;
37330 assert!(
37331 turns <= 8,
37332 "queued blocking admission must make bounded host-turn progress"
37333 );
37334 };
37335 futures::executor::block_on(task);
37336 assert_eq!(
37337 outcome.expect("a concurrent blocking submission must be queued, not rejected"),
37338 7
37339 );
37340 }
37341}