1use std::{
5 any::Any,
6 collections::VecDeque,
7 fmt,
8 future::Future,
9 mem::ManuallyDrop,
10 panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
11 pin::Pin,
12 sync::{
13 Arc, Condvar, LazyLock, Mutex, Weak,
14 atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
15 },
16 task::{Context, Poll, Waker},
17 time::{Duration, Instant},
18};
19
20use arc_swap::ArcSwapOption;
21use rustc_hash::{FxHashMap, FxHashSet};
22
23use crate::MAX_ASYNC_RUNTIME_WORKER_THREADS;
24#[cfg(all(test, napi_runtime_os_threads))]
26use async_task::Task;
27use async_task::{FallibleTask, Runnable};
28use futures::{
29 FutureExt,
30 future::{AbortHandle, AbortRegistration, Abortable},
31};
32
33#[cfg(napi_runtime_os_threads)]
34use crossbeam_deque::{Injector, Steal};
35#[cfg(napi_runtime_os_threads)]
36use futures::channel::oneshot;
37#[cfg(all(test, napi_runtime_os_threads))]
38use rayon::max_num_threads;
39#[cfg(napi_runtime_os_threads)]
40use rayon::{ThreadPool, ThreadPoolBuilder};
41
42fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
45 counter
46 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
47 .expect(exhausted)
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum RuntimeFlavor {
52 CurrentThread,
53 MultiThread,
54}
55
56#[derive(Debug, Clone)]
57pub struct RuntimeOptions {
58 pub flavor: RuntimeFlavor,
59 pub worker_threads: usize,
77 pub max_blocking_tasks: usize,
78 pub thread_name_prefix: String,
79 pub park_deadline: Option<Duration>,
92 pub drain_linger: Duration,
107}
108
109#[derive(Debug, Clone, Copy, Default)]
116pub struct RuntimeOptionsPatch {
117 pub flavor: Option<RuntimeFlavor>,
118 pub worker_threads: Option<usize>,
119 pub max_blocking_tasks: Option<usize>,
120 pub drain_linger: Option<Duration>,
123}
124
125impl RuntimeOptionsPatch {
126 fn apply_to(self, options: &mut RuntimeOptions) {
127 if let Some(flavor) = self.flavor {
128 options.flavor = flavor;
129 }
130 if let Some(worker_threads) = self.worker_threads {
131 options.worker_threads = worker_threads;
132 }
133 if let Some(max_blocking_tasks) = self.max_blocking_tasks {
134 options.max_blocking_tasks = max_blocking_tasks;
135 }
136 if let Some(drain_linger) = self.drain_linger {
137 options.drain_linger = drain_linger;
138 }
139 }
140}
141
142impl Default for RuntimeOptions {
143 fn default() -> Self {
144 let worker_threads = std::thread::available_parallelism()
145 .map_or(1, usize::from)
146 .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
147 Self {
148 flavor: if cfg!(target_family = "wasm") {
149 RuntimeFlavor::CurrentThread
150 } else {
151 RuntimeFlavor::MultiThread
152 },
153 worker_threads,
154 max_blocking_tasks: worker_threads,
155 thread_name_prefix: "napi-async-runtime".to_string(),
156 park_deadline: None,
157 drain_linger: DEFAULT_DRAIN_LINGER,
158 }
159 }
160}
161
162impl RuntimeOptions {
163 fn validate(self) -> Result<Self, RuntimeConfigError> {
164 #[cfg(not(napi_runtime_os_threads))]
165 let rayon_max_threads = None;
166 #[cfg(napi_runtime_os_threads)]
167 let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
168 self.validate_with_rayon_max_threads(rayon_max_threads)
169 }
170
171 fn validate_with_rayon_max_threads(
172 mut self,
173 rayon_max_threads: Option<usize>,
174 ) -> Result<Self, RuntimeConfigError> {
175 if self.worker_threads == 0 {
176 return Err(RuntimeConfigError(
177 "worker_threads must be greater than zero".to_string(),
178 ));
179 }
180 if self.max_blocking_tasks == 0 {
181 return Err(RuntimeConfigError(
182 "max_blocking_tasks must be greater than zero".to_string(),
183 ));
184 }
185 self.drain_linger = self.drain_linger.min(MAX_DRAIN_LINGER);
192 if self.flavor == RuntimeFlavor::CurrentThread {
193 self.worker_threads = 1;
194 self.max_blocking_tasks = 1;
195 } else {
196 let Some(rayon_max_threads) = rayon_max_threads else {
197 return Err(RuntimeConfigError(
198 "the multi-thread runtime is unavailable in this threadless WebAssembly build \
199 (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
200 .to_string(),
201 ));
202 };
203 if rayon_max_threads < 2 {
204 return Err(RuntimeConfigError(
205 "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
206 ));
207 }
208 self.worker_threads = self
216 .worker_threads
217 .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
218 .min(rayon_max_threads);
219 let blocking_capacity = self.worker_threads - 1;
220 self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
221 }
222 Ok(self)
223 }
224}
225
226pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
235
236pub const DRAIN_LINGER_ENV: &str = "NAPI_RUNTIME_DRAIN_LINGER_US";
260
261pub const DEFAULT_DRAIN_LINGER_MICROS: u64 = 500;
265
266pub const MAX_DRAIN_LINGER_MICROS: u64 = 100_000;
273
274const DEFAULT_DRAIN_LINGER: Duration = Duration::from_micros(DEFAULT_DRAIN_LINGER_MICROS);
276
277const MAX_DRAIN_LINGER: Duration = Duration::from_micros(MAX_DRAIN_LINGER_MICROS);
279
280#[cfg(napi_runtime_os_threads)]
294const DRAIN_LINGER_FRAME_FACTOR: u32 = 16;
295
296#[cfg(napi_runtime_os_threads)]
299const MAX_DRAIN_LINGER_FRAME_RESIDENCE: Duration = Duration::from_secs(60);
300
301const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
317
318#[derive(Debug, Clone)]
319pub struct RuntimeConfigError(String);
320
321impl fmt::Display for RuntimeConfigError {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 f.write_str(&self.0)
324 }
325}
326
327impl std::error::Error for RuntimeConfigError {}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331pub enum BlockOnDeadlockKind {
332 CurrentThreadCertain,
337 CurrentThreadCertainTimerBacked,
344 CurrentThreadDeadline,
347 MultiThreadCooperativeDeadline,
351}
352
353#[derive(Debug, Clone)]
361pub struct BlockOnDeadlock {
362 pub kind: BlockOnDeadlockKind,
363 pub park_deadline: Option<Duration>,
366}
367
368impl BlockOnDeadlock {
369 fn current_thread_certain() -> Self {
370 Self {
371 kind: BlockOnDeadlockKind::CurrentThreadCertain,
372 park_deadline: None,
373 }
374 }
375
376 fn current_thread_certain_timer_backed() -> Self {
377 Self {
378 kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
379 park_deadline: None,
380 }
381 }
382
383 fn current_thread_deadline(deadline: Duration) -> Self {
384 Self {
385 kind: BlockOnDeadlockKind::CurrentThreadDeadline,
386 park_deadline: Some(deadline),
387 }
388 }
389
390 #[cfg(napi_runtime_os_threads)]
393 fn multi_thread_cooperative(deadline: Duration) -> Self {
394 Self {
395 kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
396 park_deadline: Some(deadline),
397 }
398 }
399}
400
401impl fmt::Display for BlockOnDeadlock {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 let deadline_ms = self
404 .park_deadline
405 .map_or(0, |deadline| deadline.as_millis());
406 match self.kind {
407 BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
408 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
409 park with an empty task queue and no pending wake on a threadless build -- no other \
410 thread exists that could ever deliver the wake, so this park would never end. This is \
411 the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
412 (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
413 dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
414 continuation is the one now parking.",
415 ),
416 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
417 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
418 park with an empty task queue on a threadless build while a host timer is pending. \
419 The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
420 only run on the very thread that is now parking, so it can never fire, and no other \
421 thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
422 hazard: native code called `block_on` on a future that (transitively) awaits a JS \
423 continuation, but the only thread that could run that continuation is the one now \
424 parking.",
425 ),
426 BlockOnDeadlockKind::CurrentThreadDeadline => write!(
427 f,
428 "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
429 {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
430 task queue, no pending wake and ZERO runtime progress across the whole window. This is \
431 the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
432 awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
433 once this thread returns to the event loop. If this park is legitimate, raise or unset \
434 the deadline.",
435 ),
436 BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
437 f,
438 "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
439 parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
440 no runnable or blocking work available and ZERO executor progress across the whole \
441 window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
442 The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
443 If this park is legitimate, raise or unset the deadline.",
444 ),
445 }
446 }
447}
448
449impl std::error::Error for BlockOnDeadlock {}
450
451#[derive(Debug)]
452pub struct JoinError {
453 message: String,
454}
455
456impl JoinError {
457 fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
458 Self {
459 message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
463 deadlock.to_string()
464 } else if let Some(message) = panic.downcast_ref::<&str>() {
465 (*message).to_string()
466 } else if let Some(message) = panic.downcast_ref::<String>() {
467 message.clone()
468 } else {
469 "async runtime task panicked".to_string()
470 },
471 }
472 }
473}
474
475impl fmt::Display for JoinError {
476 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477 f.write_str(&self.message)
478 }
479}
480
481impl std::error::Error for JoinError {}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484enum BlockOnOutcome {
485 Completed,
486 Stopped,
487}
488
489fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
490 if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
495 std::mem::forget(nested_payload);
496 }
497}
498
499#[derive(Default)]
500struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
501
502impl RetainedPanicPayload {
503 fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
504 if self.0.is_some() {
505 discard_panic_payload(payload);
506 } else {
507 self.0 = Some(payload);
508 }
509 }
510
511 fn is_some(&self) -> bool {
512 self.0.is_some()
513 }
514
515 fn resume(mut self) -> ! {
516 resume_unwind(
517 self
518 .0
519 .take()
520 .expect("a retained panic payload must remain owned"),
521 )
522 }
523}
524
525impl Drop for RetainedPanicPayload {
526 fn drop(&mut self) {
527 if let Some(payload) = self.0.take() {
528 discard_panic_payload(payload);
529 }
530 }
531}
532
533fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
534 match catch_unwind(AssertUnwindSafe(function)) {
535 Ok(value) => Some(value),
536 Err(payload) => {
537 discard_panic_payload(payload);
538 None
539 }
540 }
541}
542
543struct ContainedArc<T: ?Sized> {
544 value: Option<Arc<T>>,
545}
546
547impl<T: ?Sized> ContainedArc<T> {
548 fn new(value: Arc<T>) -> Self {
549 Self { value: Some(value) }
550 }
551
552 fn get(&self) -> &T {
553 self
554 .value
555 .as_deref()
556 .expect("the contained Arc must remain owned")
557 }
558
559 #[cfg(test)]
560 fn into_inner(mut self) -> Arc<T> {
561 self
562 .value
563 .take()
564 .expect("the contained Arc must remain owned")
565 }
566}
567
568impl<T: ?Sized> Drop for ContainedArc<T> {
569 fn drop(&mut self) {
570 if let Some(value) = self.value.take() {
571 let _ = catch_unwind_contained(|| drop(value));
572 }
573 }
574}
575
576struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
577 values: Vec<(I, Arc<T>)>,
578}
579
580impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
581 fn new(values: Vec<(I, Arc<T>)>) -> Self {
582 Self { values }
583 }
584
585 fn as_slice(&self) -> &[(I, Arc<T>)] {
586 &self.values
587 }
588
589 fn len(&self) -> usize {
590 self.values.len()
591 }
592
593 fn is_empty(&self) -> bool {
594 self.values.is_empty()
595 }
596
597 fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
598 self
599 .values
600 .pop()
601 .map(|(id, value)| (id, ContainedArc::new(value)))
602 }
603}
604
605impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
606 fn drop(&mut self) {
607 while let Some((_, value)) = self.values.pop() {
608 let _ = catch_unwind_contained(|| drop(value));
609 }
610 }
611}
612
613struct PanicContainedWaker {
614 waker: ManuallyDrop<Waker>,
615 generation: Option<u64>,
616}
617
618impl std::task::Wake for PanicContainedWaker {
619 fn wake(self: Arc<Self>) {
620 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
621 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
622 }
623
624 fn wake_by_ref(self: &Arc<Self>) {
625 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
626 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
627 }
628}
629
630impl Drop for PanicContainedWaker {
631 fn drop(&mut self) {
632 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
633 let _ = catch_unwind_contained(|| {
634 unsafe { ManuallyDrop::drop(&mut self.waker) };
636 });
637 }
638}
639
640fn panic_contained_waker(waker: Waker) -> Waker {
641 Waker::from(Arc::new(PanicContainedWaker {
642 waker: ManuallyDrop::new(waker),
643 generation: active_runtime_generation(),
644 }))
645}
646
647struct PanicContainedWakerSource {
648 waker: ManuallyDrop<Waker>,
649 generation: Option<u64>,
650}
651
652impl PanicContainedWakerSource {
653 fn new(waker: Waker) -> Self {
654 Self {
655 waker: ManuallyDrop::new(waker),
656 generation: active_runtime_generation(),
657 }
658 }
659
660 fn as_waker(&self) -> &Waker {
661 &self.waker
662 }
663}
664
665impl Drop for PanicContainedWakerSource {
666 fn drop(&mut self) {
667 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
668 let _ = catch_unwind_contained(|| {
669 unsafe { ManuallyDrop::drop(&mut self.waker) };
671 });
672 }
673}
674
675struct PanicContainedWakerCache {
676 source: PanicContainedWakerSource,
677 contained: Waker,
678}
679
680impl PanicContainedWakerCache {
681 fn new(waker: &Waker) -> Self {
682 let generation = active_runtime_generation();
683 let _generation = RuntimeGenerationGuard::enter_optional(generation);
684 let source = PanicContainedWakerSource::new(waker.clone());
685 let contained = panic_contained_waker(source.as_waker().clone());
686 Self { source, contained }
687 }
688
689 fn update(&mut self, waker: &Waker) {
690 let will_wake = {
691 let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
692 self.source.as_waker().will_wake(waker)
693 };
694 if !will_wake {
695 *self = Self::new(waker);
696 }
697 }
698
699 fn contained(&self) -> &Waker {
700 &self.contained
701 }
702}
703
704fn wake_waker_contained(waker: Waker) {
705 let _ = catch_unwind_contained(|| waker.wake_by_ref());
710 let _ = catch_unwind_contained(|| drop(waker));
711}
712
713fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
714 match catch_unwind(AssertUnwindSafe(function)) {
715 Ok(value) => Ok(value),
716 Err(payload) => {
717 let error = JoinError::from_panic(&*payload);
718 discard_panic_payload(payload);
719 Err(error)
720 }
721 }
722}
723
724fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
725 let error = JoinError::from_panic(&*payload);
726 discard_panic_payload(payload);
727 error
728}
729
730struct ContainedTaskOutput<T> {
731 output: ManuallyDrop<T>,
732 generation: u64,
733}
734
735impl<T> ContainedTaskOutput<T> {
736 fn new(output: T, generation: u64) -> Self {
737 Self {
738 output: ManuallyDrop::new(output),
739 generation,
740 }
741 }
742
743 fn into_inner(mut self) -> T {
744 let output = unsafe { ManuallyDrop::take(&mut self.output) };
747 std::mem::forget(self);
748 output
749 }
750}
751
752impl<T> Drop for ContainedTaskOutput<T> {
753 fn drop(&mut self) {
754 let _generation = RuntimeGenerationGuard::enter(self.generation);
755 let _ = catch_unwind_contained(|| {
756 unsafe { ManuallyDrop::drop(&mut self.output) };
759 });
760 }
761}
762
763#[cfg(napi_runtime_os_threads)]
764#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
765struct BlockingJobId(u64);
766
767#[cfg(napi_runtime_os_threads)]
768static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
769
770#[cfg(all(test, napi_runtime_os_threads))]
771thread_local! {
772 static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
773 const { std::cell::RefCell::new(None) };
774 static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
775 const { std::cell::RefCell::new(None) };
776 static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
777 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
778 const { std::cell::RefCell::new(None) };
779 static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
780 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
781 const { std::cell::RefCell::new(None) };
782 static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
783 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
784 const { std::cell::RefCell::new(None) };
785 static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
786 const { std::cell::RefCell::new(None) };
787 static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
788 const { std::cell::RefCell::new(None) };
789 static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
790 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
791 const { std::cell::RefCell::new(None) };
792 static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
793 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
794 const { std::cell::RefCell::new(None) };
795 static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
796 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
797 const { std::cell::RefCell::new(None) };
798 static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
799 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
800 const { std::cell::RefCell::new(None) };
801 static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
802 const { std::cell::RefCell::new(None) };
803 static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
804 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
805 const { std::cell::RefCell::new(None) };
806 static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
807 const { std::cell::RefCell::new(None) };
808}
809
810#[cfg(all(test, napi_runtime_os_threads))]
811struct WorkerTlsDropProbe {
812 action: Option<WorkerTlsDropAction>,
813}
814
815#[cfg(all(test, napi_runtime_os_threads))]
829#[cfg_attr(target_family = "wasm", allow(dead_code))]
832enum WorkerTlsDropAction {
833 Record { completed: Arc<AtomicUsize> },
837 ReenterLifecycle {
838 controller: Arc<RuntimeController>,
839 completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
840 },
841}
842
843#[cfg(all(test, napi_runtime_os_threads))]
844impl Drop for WorkerTlsDropProbe {
845 fn drop(&mut self) {
846 match self
847 .action
848 .take()
849 .expect("worker TLS drop probe action already consumed")
850 {
851 WorkerTlsDropAction::Record { completed } => {
852 completed.fetch_add(1, Ordering::SeqCst);
853 }
854 WorkerTlsDropAction::ReenterLifecycle {
859 controller,
860 completed,
861 } => {
862 let start = controller.start().map_err(|error| error.to_string());
863 let shutdown = controller.shutdown().map_err(|error| error.to_string());
864 let _ = completed.send((start, shutdown));
865 }
866 }
867 }
868}
869
870#[cfg(all(test, napi_runtime_os_threads))]
871fn run_dependency_claim_test_hook() {
872 if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
873 hook();
874 }
875}
876
877#[cfg(all(test, napi_runtime_os_threads))]
878fn run_current_thread_dispatch_claim_test_hook() {
879 if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
880 {
881 hook();
882 }
883}
884
885#[cfg(all(test, napi_runtime_os_threads))]
886fn run_current_thread_dispatch_publication_blocked_test_hook() {
887 if let Some(hook) =
888 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
889 {
890 hook();
891 }
892}
893
894#[cfg(all(test, napi_runtime_os_threads))]
895fn run_current_thread_dispatch_recovery_test_hook() {
896 if let Some(hook) =
897 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
898 {
899 hook();
900 }
901}
902
903#[cfg(all(test, napi_runtime_os_threads))]
904fn run_current_thread_failed_publication_retired_test_hook() {
905 if let Some(hook) =
906 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
907 {
908 hook();
909 }
910}
911
912#[cfg(all(test, napi_runtime_os_threads))]
913fn run_current_thread_dispatch_call_test_hook() {
914 if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
915 hook();
916 }
917}
918
919#[cfg(all(test, napi_runtime_os_threads))]
920fn run_current_thread_dispatch_returned_test_hook() {
921 if let Some(hook) =
922 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
923 {
924 hook();
925 }
926}
927
928#[cfg(all(test, napi_runtime_os_threads))]
929fn run_current_thread_blocking_claim_test_hook() {
930 if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
931 {
932 hook();
933 }
934}
935
936#[cfg(all(test, napi_runtime_os_threads))]
937fn run_current_thread_runnable_claim_test_hook() {
938 if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
939 {
940 hook();
941 }
942}
943
944#[cfg(all(test, napi_runtime_os_threads))]
945fn run_multi_thread_work_claimed_test_hook() {
946 if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
947 hook();
948 }
949}
950
951#[cfg(all(test, napi_runtime_os_threads))]
952fn run_multi_thread_work_started_test_hook() {
953 if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
954 hook();
955 }
956}
957
958#[cfg(all(test, napi_runtime_os_threads))]
959fn run_dependency_predicate_test_hook() {
960 if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
961 hook();
962 }
963}
964
965#[cfg(all(test, napi_runtime_os_threads))]
966fn run_before_owned_blocking_queue_claim_test_hook() {
967 if let Some(hook) =
968 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
969 {
970 hook();
971 }
972}
973
974#[cfg(napi_runtime_os_threads)]
975#[derive(Clone, Copy, Debug, PartialEq, Eq)]
976struct BlockingDependency {
977 job: BlockingJobId,
978 owner: Option<BlockingOwnerToken>,
979}
980
981#[cfg(napi_runtime_os_threads)]
982#[derive(Debug)]
983struct DependencyClaim {
984 claimed: AtomicBool,
985 transition: Mutex<()>,
986}
987
988#[cfg(napi_runtime_os_threads)]
989impl DependencyClaim {
990 fn new() -> Self {
991 Self {
992 claimed: AtomicBool::new(false),
993 transition: Mutex::new(()),
994 }
995 }
996
997 fn is_live(&self) -> bool {
998 !self.claimed.load(Ordering::Acquire)
999 }
1000
1001 fn cancel_link(&self, link: &DependencyLink) {
1002 let _transition = self
1003 .transition
1004 .lock()
1005 .unwrap_or_else(std::sync::PoisonError::into_inner);
1006 link.cancel();
1007 }
1008
1009 fn try_claim(&self, link: &DependencyLink) -> bool {
1010 let _transition = self
1011 .transition
1012 .lock()
1013 .unwrap_or_else(std::sync::PoisonError::into_inner);
1014 #[cfg(test)]
1015 run_dependency_claim_test_hook();
1016 if self.claimed.load(Ordering::Acquire) || !link.is_live() {
1017 return false;
1018 }
1019 self.claimed.store(true, Ordering::Release);
1020 true
1021 }
1022}
1023
1024#[cfg(napi_runtime_os_threads)]
1025#[derive(Debug)]
1026struct DependencyLink {
1027 live: AtomicBool,
1028 parent: Option<Weak<DependencyLink>>,
1029}
1030
1031#[cfg(napi_runtime_os_threads)]
1032impl DependencyLink {
1033 fn root() -> Arc<Self> {
1034 Arc::new(Self {
1035 live: AtomicBool::new(true),
1036 parent: None,
1037 })
1038 }
1039
1040 fn derived(parent: &Arc<Self>) -> Arc<Self> {
1041 Arc::new(Self {
1042 live: AtomicBool::new(true),
1043 parent: Some(Arc::downgrade(parent)),
1044 })
1045 }
1046
1047 fn is_live(&self) -> bool {
1048 if !self.live.load(Ordering::Acquire) {
1049 return false;
1050 }
1051 let Some(parent) = &self.parent else {
1052 return true;
1053 };
1054 let Some(mut current) = parent.upgrade() else {
1055 return false;
1056 };
1057 loop {
1058 if !current.live.load(Ordering::Acquire) {
1059 return false;
1060 }
1061 let Some(parent) = ¤t.parent else {
1062 return true;
1063 };
1064 let Some(next) = parent.upgrade() else {
1065 return false;
1066 };
1067 current = next;
1068 }
1069 }
1070
1071 fn cancel(&self) {
1072 self.live.store(false, Ordering::Release);
1073 }
1074}
1075
1076#[cfg(napi_runtime_os_threads)]
1077#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1078struct DependencyPublicationIdentity {
1079 job: BlockingJobId,
1080 claim: *const DependencyClaim,
1081 link: *const DependencyLink,
1082}
1083
1084#[cfg(napi_runtime_os_threads)]
1085#[derive(Clone, Debug)]
1086struct DependencyPublication {
1087 dependency: BlockingDependency,
1088 claim: Arc<DependencyClaim>,
1089 link: Arc<DependencyLink>,
1090}
1091
1092#[cfg(napi_runtime_os_threads)]
1093impl DependencyPublication {
1094 fn owned(dependency: BlockingDependency) -> Self {
1095 Self {
1096 dependency,
1097 claim: Arc::new(DependencyClaim::new()),
1098 link: DependencyLink::root(),
1099 }
1100 }
1101
1102 fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1103 Self {
1104 dependency: BlockingDependency {
1105 owner: self.dependency.owner.or(owner),
1106 ..self.dependency
1107 },
1108 claim: Arc::clone(&self.claim),
1109 link: DependencyLink::derived(&self.link),
1110 }
1111 }
1112
1113 fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1114 self.dependency.owner = self.dependency.owner.or(owner);
1115 self
1116 }
1117
1118 fn same_as(&self, other: &Self) -> bool {
1119 self.dependency == other.dependency
1120 && Arc::ptr_eq(&self.claim, &other.claim)
1121 && Arc::ptr_eq(&self.link, &other.link)
1122 }
1123
1124 fn identity(&self) -> DependencyPublicationIdentity {
1125 DependencyPublicationIdentity {
1126 job: self.dependency.job,
1127 claim: Arc::as_ptr(&self.claim),
1128 link: Arc::as_ptr(&self.link),
1129 }
1130 }
1131
1132 fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1133 Some(DependencyPublicationIdentity {
1134 job: self.dependency.job,
1135 claim: Arc::as_ptr(&self.claim),
1136 link: self.link.parent.as_ref()?.as_ptr(),
1137 })
1138 }
1139
1140 fn is_derived_from(&self, source: &Self) -> bool {
1141 self.immediate_source_identity() == Some(source.identity())
1142 }
1143
1144 fn is_live(&self) -> bool {
1145 self.claim.is_live() && self.link.is_live()
1146 }
1147
1148 fn cancel(&self) {
1149 self.claim.cancel_link(&self.link);
1150 }
1151
1152 fn try_claim(&self) -> bool {
1153 self.claim.try_claim(&self.link)
1154 }
1155}
1156
1157#[cfg(napi_runtime_os_threads)]
1158struct TaskDependencyEntry {
1159 sequence: u64,
1160 publication: DependencyPublication,
1161}
1162
1163#[cfg(napi_runtime_os_threads)]
1164struct SelectedDependencyPublication {
1165 sequence: u64,
1166 publication: DependencyPublication,
1167}
1168
1169#[cfg(napi_runtime_os_threads)]
1170#[derive(Clone, Copy)]
1171enum OwnerDependencyMode {
1172 Available,
1173 Exact,
1174}
1175
1176#[cfg(napi_runtime_os_threads)]
1177#[derive(Default)]
1178struct TaskDependencyState {
1179 current: VecDeque<TaskDependencyEntry>,
1180 next_sequence: u64,
1181 unowned_sequences: VecDeque<u64>,
1182 owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1183 waiter: Option<Waker>,
1193}
1194
1195#[cfg(napi_runtime_os_threads)]
1196impl TaskDependencyState {
1197 fn append(&mut self, publication: DependencyPublication) -> u64 {
1200 let sequence = self.next_sequence;
1201 self.next_sequence = self
1202 .next_sequence
1203 .checked_add(1)
1204 .expect("task dependency sequence space exhausted");
1205 match publication.dependency.owner {
1206 Some(owner) => self
1207 .owner_sequences
1208 .entry(owner)
1209 .or_default()
1210 .push_back(sequence),
1211 None => self.unowned_sequences.push_back(sequence),
1212 }
1213 self.current.push_back(TaskDependencyEntry {
1214 sequence,
1215 publication,
1216 });
1217 sequence
1218 }
1219
1220 fn entry_in(
1221 current: &VecDeque<TaskDependencyEntry>,
1222 sequence: u64,
1223 ) -> Option<&TaskDependencyEntry> {
1224 let front = current.front()?.sequence;
1225 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1226 let entry = current.get(index)?;
1227 (entry.sequence == sequence).then_some(entry)
1228 }
1229
1230 fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1231 Self::entry_in(&self.current, sequence)
1232 }
1233
1234 fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1235 let front = self.current.front()?.sequence;
1236 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1237 let entry = self.current.get_mut(index)?;
1238 (entry.sequence == sequence).then_some(entry)
1239 }
1240
1241 fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1242 match dependency.owner {
1243 Some(owner) => {
1244 let remove_owner = self
1245 .owner_sequences
1246 .get_mut(&owner)
1247 .is_some_and(|sequences| {
1248 if sequences.front() == Some(&sequence) {
1249 sequences.pop_front();
1250 }
1251 sequences.is_empty()
1252 });
1253 if remove_owner {
1254 self.owner_sequences.remove(&owner);
1255 }
1256 }
1257 None => {
1258 if self.unowned_sequences.front() == Some(&sequence) {
1259 self.unowned_sequences.pop_front();
1260 }
1261 }
1262 }
1263 }
1264
1265 fn clear_indexes(&mut self) {
1266 self.unowned_sequences.clear();
1267 self.owner_sequences.clear();
1268 }
1269}
1270
1271#[cfg(napi_runtime_os_threads)]
1272struct TaskDependency {
1273 state: Mutex<TaskDependencyState>,
1274 owner_hint: Option<BlockingOwnerToken>,
1275 has_current: AtomicBool,
1276 generation: u64,
1277 #[cfg(test)]
1278 entry_visits: AtomicUsize,
1279}
1280
1281#[cfg(napi_runtime_os_threads)]
1282impl Default for TaskDependency {
1283 fn default() -> Self {
1284 Self::new(u64::MAX, None)
1285 }
1286}
1287
1288#[cfg(napi_runtime_os_threads)]
1289impl TaskDependency {
1290 fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1291 Self {
1292 state: Mutex::new(TaskDependencyState::default()),
1293 owner_hint,
1294 has_current: AtomicBool::new(false),
1295 generation,
1296 #[cfg(test)]
1297 entry_visits: AtomicUsize::new(0),
1298 }
1299 }
1300
1301 fn new_task(generation: u64) -> Self {
1302 Self::new(generation, None)
1303 }
1304
1305 fn notify_waiter(&self, waiter: Option<Waker>) {
1306 if let Some(waiter) = waiter {
1307 let _generation = RuntimeGenerationGuard::enter(self.generation);
1308 wake_waker_contained(waiter);
1309 }
1310 }
1311
1312 fn clear_waiter(&self) {
1313 let waiter = self
1314 .state
1315 .lock()
1316 .unwrap_or_else(std::sync::PoisonError::into_inner)
1317 .waiter
1318 .take();
1319 if let Some(waiter) = waiter {
1320 let _generation = RuntimeGenerationGuard::enter(self.generation);
1321 let _ = catch_unwind_contained(|| drop(waiter));
1322 }
1323 }
1324
1325 fn clear(&self) {
1326 let waiter = {
1327 let mut state = self
1328 .state
1329 .lock()
1330 .unwrap_or_else(std::sync::PoisonError::into_inner);
1331 if state.current.is_empty() {
1332 None
1333 } else {
1334 for current in state.current.drain(..) {
1335 current.publication.cancel();
1336 }
1337 state.clear_indexes();
1338 self.has_current.store(false, Ordering::Release);
1339 state.waiter.take()
1340 }
1341 };
1342 self.notify_waiter(waiter);
1343 }
1344
1345 fn retire(&self, sequence: u64) {
1355 let waiter = {
1356 let mut state = self
1357 .state
1358 .lock()
1359 .unwrap_or_else(std::sync::PoisonError::into_inner);
1360 let cancelled = match state.entry_mut(sequence) {
1361 Some(current) => {
1362 self.note_entry_visit();
1363 current.publication.cancel();
1364 true
1365 }
1366 None => false,
1367 };
1368 if cancelled {
1369 self.prune_stale_prefix(&mut state);
1370 state.waiter.take()
1371 } else {
1372 None
1373 }
1374 };
1375 self.notify_waiter(waiter);
1376 }
1377
1378 fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1379 let Some(first_publication) = publications.first() else {
1380 return;
1381 };
1382 let publication_identities = (publications.len() > 1).then(|| {
1383 publications
1384 .iter()
1385 .map(DependencyPublication::identity)
1386 .collect::<FxHashSet<_>>()
1387 });
1388 let waiter = {
1389 let mut state = self
1390 .state
1391 .lock()
1392 .unwrap_or_else(std::sync::PoisonError::into_inner);
1393 let removed = state
1394 .current
1395 .iter_mut()
1396 .filter_map(|current| {
1397 self.note_entry_visit();
1398 let is_derived = publication_identities.as_ref().map_or_else(
1399 || current.publication.is_derived_from(first_publication),
1400 |identities| {
1401 current
1402 .publication
1403 .immediate_source_identity()
1404 .is_some_and(|identity| identities.contains(&identity))
1405 },
1406 );
1407 if !is_derived {
1408 return None;
1409 }
1410 current.publication.cancel();
1411 Some(current.sequence)
1412 })
1413 .collect::<Vec<_>>();
1414 if removed.is_empty() {
1417 None
1418 } else {
1419 self.prune_stale_prefix(&mut state);
1420 state.waiter.take()
1421 }
1422 };
1423 self.notify_waiter(waiter);
1424 }
1425
1426 fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1429 let publication = DependencyPublication::owned(BlockingDependency {
1430 owner: dependency.owner.or(self.owner_hint),
1431 ..dependency
1432 });
1433 self.set_entry(publication)
1434 }
1435
1436 fn holds_live(&self, sequence: u64) -> bool {
1442 let state = self
1443 .state
1444 .lock()
1445 .unwrap_or_else(std::sync::PoisonError::into_inner);
1446 TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1447 self.note_entry_visit();
1448 entry.publication.is_live()
1449 })
1450 }
1451
1452 fn set_borrowed(&self, publication: &DependencyPublication) {
1453 let _sequence = self.set_entry(publication.derived(self.owner_hint));
1454 }
1455
1456 fn set_entry(&self, publication: DependencyPublication) -> u64 {
1457 let (sequence, waiter) = {
1458 let mut state = self
1459 .state
1460 .lock()
1461 .unwrap_or_else(std::sync::PoisonError::into_inner);
1462 let sequence = state.append(publication);
1465 self.has_current.store(true, Ordering::Release);
1466 (sequence, state.waiter.take())
1467 };
1468 self.notify_waiter(waiter);
1469 sequence
1470 }
1471
1472 fn note_entry_visit(&self) {
1473 #[cfg(test)]
1474 self.entry_visits.fetch_add(1, Ordering::Relaxed);
1475 }
1476
1477 fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1478 while state.current.front().is_some_and(|current| {
1479 self.note_entry_visit();
1480 !current.publication.is_live()
1481 }) {
1482 let current = state
1483 .current
1484 .pop_front()
1485 .expect("the stale prefix entry must exist");
1486 state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1487 }
1488 self
1489 .has_current
1490 .store(!state.current.is_empty(), Ordering::Release);
1491 }
1492
1493 fn first_live_sequence(
1494 &self,
1495 current: &VecDeque<TaskDependencyEntry>,
1496 sequences: &mut VecDeque<u64>,
1497 ) -> Option<u64> {
1498 loop {
1499 let sequence = *sequences.front()?;
1500 let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1501 sequences.pop_front();
1502 continue;
1503 };
1504 self.note_entry_visit();
1505 if entry.publication.is_live() {
1506 return Some(sequence);
1507 }
1508 sequences.pop_front();
1509 }
1510 }
1511
1512 fn first_live_owner_sequence(
1513 &self,
1514 state: &mut TaskDependencyState,
1515 owner: BlockingOwnerToken,
1516 ) -> Option<u64> {
1517 let sequence = {
1518 let current = &state.current;
1519 state
1520 .owner_sequences
1521 .get_mut(&owner)
1522 .and_then(|sequences| self.first_live_sequence(current, sequences))
1523 };
1524 if sequence.is_none() {
1525 state.owner_sequences.remove(&owner);
1526 }
1527 sequence
1528 }
1529
1530 fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1531 self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1532 }
1533
1534 #[cfg(test)]
1535 fn get(&self) -> Option<DependencyPublication> {
1536 self
1537 .state
1538 .lock()
1539 .unwrap_or_else(std::sync::PoisonError::into_inner)
1540 .current
1541 .iter()
1542 .find(|current| current.publication.is_live())
1543 .map(|current| current.publication.clone())
1544 }
1545
1546 fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1547 self
1548 .state
1549 .lock()
1550 .unwrap_or_else(std::sync::PoisonError::into_inner)
1551 .current
1552 .iter()
1553 .filter(|current| {
1554 self.note_entry_visit();
1555 current.publication.is_live()
1556 })
1557 .map(|current| current.publication.clone())
1558 .collect()
1559 }
1560
1561 fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1562 let mut state = self
1563 .state
1564 .lock()
1565 .unwrap_or_else(std::sync::PoisonError::into_inner);
1566 self.prune_stale_prefix(&mut state);
1567 let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1568 let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1569 let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1570 (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1571 (Some(_) | None, Some(unowned)) => (unowned, true),
1572 (Some(exact), None) => (exact, false),
1573 (None, None) => return None,
1574 };
1575 if bind_owner {
1576 let removed = state.unowned_sequences.pop_front();
1577 debug_assert_eq!(removed, Some(sequence));
1578 let current = state
1579 .entry_mut(sequence)
1580 .expect("the selected dependency must remain indexed");
1581 debug_assert_eq!(current.publication.dependency.owner, None);
1582 current.publication.dependency.owner = Some(owner);
1583 state
1587 .owner_sequences
1588 .entry(owner)
1589 .or_default()
1590 .push_front(sequence);
1591 }
1592 let publication = state
1593 .entry(sequence)
1594 .expect("the selected dependency must remain indexed")
1595 .publication
1596 .clone();
1597 Some(SelectedDependencyPublication {
1598 sequence,
1599 publication,
1600 })
1601 }
1602
1603 fn has_live_owner_dependency(
1604 &self,
1605 owner: BlockingOwnerToken,
1606 mode: OwnerDependencyMode,
1607 ) -> bool {
1608 #[cfg(all(test, napi_runtime_os_threads))]
1609 run_dependency_predicate_test_hook();
1610 let mut state = self
1611 .state
1612 .lock()
1613 .unwrap_or_else(std::sync::PoisonError::into_inner);
1614 self.prune_stale_prefix(&mut state);
1615 if self.first_live_owner_sequence(&mut state, owner).is_some() {
1616 return true;
1617 }
1618 matches!(mode, OwnerDependencyMode::Available)
1619 && self.first_live_unowned_sequence(&mut state).is_some()
1620 }
1621
1622 #[cfg(test)]
1623 fn get_dependency(&self) -> Option<BlockingDependency> {
1624 self.get().map(|publication| publication.dependency)
1625 }
1626
1627 #[cfg(test)]
1628 fn get_dependencies(&self) -> Vec<BlockingDependency> {
1629 self
1630 .snapshot_all_live()
1631 .into_iter()
1632 .map(|publication| publication.dependency)
1633 .collect()
1634 }
1635
1636 #[cfg(test)]
1637 fn reset_entry_visits(&self) {
1638 self.entry_visits.store(0, Ordering::Relaxed);
1639 }
1640
1641 #[cfg(test)]
1642 fn entry_visits(&self) -> usize {
1643 self.entry_visits.load(Ordering::Relaxed)
1644 }
1645
1646 fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1650 let mut state = self
1651 .state
1652 .lock()
1653 .unwrap_or_else(std::sync::PoisonError::into_inner);
1654 let Some(current) = state.entry(selected.sequence) else {
1655 return false;
1656 };
1657 self.note_entry_visit();
1658 if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1659 return false;
1660 }
1661 let dependency = current.publication.dependency;
1662 state
1663 .entry_mut(selected.sequence)
1664 .expect("the claimed dependency must remain indexed")
1665 .publication
1666 .cancel();
1667 state.remove_sequence_indexes(selected.sequence, dependency);
1668 self.prune_stale_prefix(&mut state);
1669 true
1670 }
1671
1672 fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1673 let _generation = RuntimeGenerationGuard::enter(self.generation);
1674 let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1675 return Vec::new();
1676 };
1677 let (previous, unused, current) = {
1678 let mut state = self
1679 .state
1680 .lock()
1681 .unwrap_or_else(std::sync::PoisonError::into_inner);
1682 let current = state
1683 .current
1684 .iter()
1685 .filter(|current| current.publication.is_live())
1686 .map(|current| current.publication.clone())
1687 .collect();
1688 if state
1689 .waiter
1690 .as_ref()
1691 .is_some_and(|previous| previous.will_wake(&waker))
1692 {
1693 (None, Some(waker), current)
1694 } else {
1695 (state.waiter.replace(waker), None, current)
1696 }
1697 };
1698 if let Some(previous) = previous {
1699 let _ = catch_unwind_contained(|| drop(previous));
1700 }
1701 if let Some(unused) = unused {
1702 let _ = catch_unwind_contained(|| drop(unused));
1703 }
1704 current
1705 }
1706
1707 fn begin_poll(&self) {
1708 if !self.has_current.load(Ordering::Acquire) {
1709 return;
1710 }
1711 let waiter = {
1712 let mut state = self
1713 .state
1714 .lock()
1715 .unwrap_or_else(std::sync::PoisonError::into_inner);
1716 if state.current.is_empty() {
1717 self.has_current.store(false, Ordering::Release);
1718 None
1719 } else {
1720 for previous in state.current.drain(..) {
1721 previous.publication.cancel();
1722 }
1723 state.clear_indexes();
1724 self.has_current.store(false, Ordering::Release);
1725 state.waiter.take()
1726 }
1727 };
1728 self.notify_waiter(waiter);
1729 }
1730
1731 #[cfg(test)]
1732 fn register_waiter(&self, waker: &Waker) {
1733 let _ = self.register_waiter_and_get(waker);
1734 }
1735}
1736
1737#[cfg(napi_runtime_os_threads)]
1738impl Drop for TaskDependency {
1739 fn drop(&mut self) {
1740 let state = self
1741 .state
1742 .get_mut()
1743 .unwrap_or_else(std::sync::PoisonError::into_inner);
1744 for current in state.current.drain(..) {
1745 current.publication.cancel();
1746 }
1747 if let Some(waiter) = state.waiter.take() {
1748 let _generation = RuntimeGenerationGuard::enter(self.generation);
1749 let _ = catch_unwind_contained(|| drop(waiter));
1750 }
1751 }
1752}
1753
1754#[cfg(napi_runtime_os_threads)]
1755thread_local! {
1756 static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1757 const { std::cell::RefCell::new(Vec::new()) };
1758}
1759
1760#[cfg(napi_runtime_os_threads)]
1761struct TaskDependencyGuard(Arc<TaskDependency>);
1762
1763#[cfg(napi_runtime_os_threads)]
1764impl TaskDependencyGuard {
1765 fn enter(dependency: &Arc<TaskDependency>) -> Self {
1766 let dependency = Arc::clone(dependency);
1767 CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1768 Self(dependency)
1769 }
1770}
1771
1772#[cfg(napi_runtime_os_threads)]
1773impl Drop for TaskDependencyGuard {
1774 fn drop(&mut self) {
1775 CURRENT_DEPENDENCY_STACK.with(|stack| {
1776 let current = stack
1777 .borrow_mut()
1778 .pop()
1779 .expect("blocking dependency context underflow");
1780 debug_assert!(Arc::ptr_eq(¤t, &self.0));
1781 });
1782 }
1783}
1784
1785#[cfg(napi_runtime_os_threads)]
1786struct BlockOnDependencyGuard {
1787 dependency: Arc<TaskDependency>,
1788 _context: TaskDependencyGuard,
1789}
1790
1791#[cfg(napi_runtime_os_threads)]
1792impl BlockOnDependencyGuard {
1793 fn enter(generation: u64) -> Self {
1794 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1795 let dependency = Arc::new(TaskDependency::new(generation, owner));
1796 let context = TaskDependencyGuard::enter(&dependency);
1797 Self {
1798 dependency,
1799 _context: context,
1800 }
1801 }
1802
1803 fn clear(&self) {
1804 self.dependency.clear();
1805 }
1806}
1807
1808#[cfg(napi_runtime_os_threads)]
1823struct PublishedBlockingDependency {
1824 state: Weak<TaskDependency>,
1825 sequence: u64,
1826}
1827
1828#[cfg(napi_runtime_os_threads)]
1847#[derive(Default)]
1848struct PublishedBlockingDependencies {
1849 first: Option<PublishedBlockingDependency>,
1850 rest: Vec<PublishedBlockingDependency>,
1851}
1852
1853#[cfg(napi_runtime_os_threads)]
1854impl PublishedBlockingDependency {
1855 fn still_stands(&self) -> bool {
1870 self
1871 .state
1872 .upgrade()
1873 .is_some_and(|state| state.holds_live(self.sequence))
1874 }
1875}
1876
1877#[cfg(napi_runtime_os_threads)]
1878impl PublishedBlockingDependencies {
1879 fn prune(&mut self) {
1904 if self.rest.is_empty() {
1905 if self
1906 .first
1907 .as_ref()
1908 .is_some_and(|first| first.state.strong_count() == 0)
1909 {
1910 self.first = None;
1911 }
1912 return;
1913 }
1914 self.rest.retain(PublishedBlockingDependency::still_stands);
1915 if self
1916 .first
1917 .as_ref()
1918 .is_some_and(|first| !first.still_stands())
1919 {
1920 self.first = self.rest.pop();
1921 }
1922 }
1923
1924 fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1925 self.prune();
1926 let target = Arc::as_ptr(&state);
1927 if let Some(existing) = self
1928 .first
1929 .iter_mut()
1930 .chain(self.rest.iter_mut())
1931 .find(|published| std::ptr::eq(published.state.as_ptr(), target))
1932 {
1933 if state.holds_live(existing.sequence) {
1944 return;
1945 }
1946 existing.sequence = state.set_owned(dependency);
1950 return;
1951 }
1952 let sequence = state.set_owned(dependency);
1953 let published = PublishedBlockingDependency {
1954 state: Arc::downgrade(&state),
1955 sequence,
1956 };
1957 match &mut self.first {
1958 slot @ None => *slot = Some(published),
1959 Some(_) => self.rest.push(published),
1960 }
1961 }
1962
1963 fn retire_all(self) {
1973 for published in self.first.into_iter().chain(self.rest) {
1974 if let Some(state) = published.state.upgrade() {
1975 state.retire(published.sequence);
1976 }
1977 }
1978 }
1979}
1980
1981#[cfg(napi_runtime_os_threads)]
1982fn record_blocking_dependency(
1983 published: &mut PublishedBlockingDependencies,
1984 dependency: BlockingDependency,
1985) {
1986 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1987 if let Some(current) = current {
1988 let dependency = BlockingDependency {
1989 owner: dependency
1990 .owner
1991 .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
1992 ..dependency
1993 };
1994 published.publish(current, dependency);
1995 }
1996}
1997
1998#[cfg(napi_runtime_os_threads)]
1999fn propagate_blocking_dependency(publication: DependencyPublication) {
2000 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2001 if let Some(current) = current {
2002 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
2003 let publication = publication.with_owner_if_none(owner);
2004 current.set_borrowed(&publication);
2005 }
2006}
2007
2008#[cfg(napi_runtime_os_threads)]
2009fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
2010 published.retire_all();
2014}
2015
2016#[cfg(napi_runtime_os_threads)]
2017fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
2018 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2019 if let Some(current) = current {
2020 current.clear_if_publications(publications);
2021 }
2022}
2023
2024enum JoinHandleInner<T> {
2025 Task {
2026 task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2027 awaiter: Option<PanicContainedWakerCache>,
2028 #[cfg(napi_runtime_os_threads)]
2029 dependency: Arc<TaskDependency>,
2030 },
2031 #[cfg(napi_runtime_os_threads)]
2032 Blocking {
2033 receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2034 dependency: BlockingDependency,
2035 published: PublishedBlockingDependencies,
2038 },
2039 Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2040}
2041
2042pub struct JoinHandle<T>(JoinHandleInner<T>);
2043
2044impl<T> Unpin for JoinHandle<T> {}
2045
2046impl<T> JoinHandle<T> {
2047 fn detach_task(&mut self) {
2048 let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2049 match inner {
2050 JoinHandleInner::Task {
2051 task,
2052 awaiter: _,
2053 #[cfg(napi_runtime_os_threads)]
2054 dependency,
2055 } => {
2056 #[cfg(napi_runtime_os_threads)]
2057 let current_dependencies = dependency.snapshot_all_live();
2058 #[cfg(napi_runtime_os_threads)]
2059 dependency.clear_waiter();
2060 task.detach();
2061 #[cfg(napi_runtime_os_threads)]
2062 let _ = catch_unwind_contained(|| {
2066 clear_propagated_blocking_dependencies(¤t_dependencies);
2067 });
2068 }
2069 #[cfg(napi_runtime_os_threads)]
2070 JoinHandleInner::Blocking {
2071 receiver,
2072 dependency: _,
2073 published,
2074 } => {
2075 let _ = catch_unwind_contained(|| drop(receiver));
2079 let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2082 }
2083 JoinHandleInner::Ready(result) => {
2084 let _ = catch_unwind_contained(|| drop(result));
2085 }
2086 }
2087 }
2088
2089 pub fn detach(mut self) {
2090 self.detach_task();
2091 }
2092}
2093
2094impl<T> Drop for JoinHandle<T> {
2095 fn drop(&mut self) {
2096 self.detach_task();
2100 }
2101}
2102
2103impl<T> Future for JoinHandle<T> {
2104 type Output = Result<T, JoinError>;
2105
2106 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2107 match &mut self.get_mut().0 {
2108 JoinHandleInner::Task {
2109 task,
2110 awaiter: awaiter_slot,
2111 #[cfg(napi_runtime_os_threads)]
2112 dependency,
2113 } => {
2114 let awaiter = match awaiter_slot {
2115 Some(awaiter) => {
2116 awaiter.update(cx.waker());
2117 awaiter
2118 }
2119 None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2120 };
2121 let contained_waker = awaiter.contained();
2122 let mut contained_cx = Context::from_waker(contained_waker);
2123 let poll = Pin::new(task).poll(&mut contained_cx);
2124 #[cfg(napi_runtime_os_threads)]
2125 if poll.is_pending() {
2126 for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2127 propagate_blocking_dependency(current_dependency);
2128 }
2129 } else {
2130 dependency.clear_waiter();
2131 }
2132 if poll.is_ready() {
2133 *awaiter_slot = None;
2134 }
2135 match poll {
2136 Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2137 Poll::Ready(None) => Poll::Ready(Err(JoinError {
2138 message: "async runtime stopped before the task completed".to_string(),
2139 })),
2140 Poll::Pending => Poll::Pending,
2141 }
2142 }
2143 #[cfg(napi_runtime_os_threads)]
2144 JoinHandleInner::Blocking {
2145 receiver,
2146 dependency,
2147 published,
2148 } => {
2149 let poll = Pin::new(receiver).poll(cx);
2150 if poll.is_pending() {
2151 record_blocking_dependency(published, *dependency);
2152 }
2153 match poll {
2154 Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2155 Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2156 message: "async runtime stopped before the blocking task completed".to_string(),
2157 })),
2158 Poll::Pending => Poll::Pending,
2159 }
2160 }
2161 JoinHandleInner::Ready(result) => Poll::Ready(
2162 result
2163 .take()
2164 .expect("JoinHandle polled after completion")
2165 .map(ContainedTaskOutput::into_inner),
2166 ),
2167 }
2168 }
2169}
2170
2171#[derive(Debug, Clone, Copy)]
2172pub struct RuntimeMetricsSnapshot {
2173 pub flavor: RuntimeFlavor,
2174 pub worker_threads: usize,
2175 pub max_blocking_tasks: usize,
2176 pub tasks_spawned: u64,
2177 pub tasks_completed: u64,
2178 pub tasks_panicked: u64,
2179 pub runnable_schedules: u64,
2180 pub runnable_polls: u64,
2181 pub queued_runnables: u64,
2182 pub max_queued_runnables: u64,
2183 pub active_runnables: u64,
2184 pub max_active_runnables: u64,
2185 pub blocking_tasks_started: u64,
2186 pub blocking_tasks_completed: u64,
2187 pub active_blocking_tasks: u64,
2188 pub max_active_blocking_tasks: u64,
2189}
2190
2191#[derive(Default)]
2192struct RuntimeMetrics {
2193 tasks_spawned: AtomicU64,
2194 tasks_completed: AtomicU64,
2195 tasks_panicked: AtomicU64,
2196 runnable_schedules: AtomicU64,
2197 runnable_polls: AtomicU64,
2198 queued_runnables: AtomicU64,
2199 max_queued_runnables: AtomicU64,
2200 active_runnables: AtomicU64,
2201 max_active_runnables: AtomicU64,
2202 blocking_tasks_scheduled: AtomicU64,
2208 blocking_tasks_started: AtomicU64,
2209 blocking_tasks_completed: AtomicU64,
2210 active_blocking_tasks: AtomicU64,
2211 max_active_blocking_tasks: AtomicU64,
2212 reset_generation: AtomicU64,
2216 reset_lock: Mutex<()>,
2217}
2218
2219#[cfg(test)]
2220thread_local! {
2221 static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2222 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2223 const { std::cell::RefCell::new(None) };
2224}
2225
2226#[cfg(test)]
2227fn run_runtime_metrics_high_water_test_hook() {
2228 if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2229 hook();
2230 }
2231}
2232
2233impl RuntimeMetrics {
2234 fn runnable_scheduled(&self) {
2235 self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2236 let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2237 #[cfg(test)]
2238 run_runtime_metrics_high_water_test_hook();
2239 self
2240 .max_queued_runnables
2241 .fetch_max(queued, Ordering::Relaxed);
2242 }
2243
2244 fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2245 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2246 self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2247 let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2248 #[cfg(test)]
2249 run_runtime_metrics_high_water_test_hook();
2250 self
2251 .max_active_runnables
2252 .fetch_max(active, Ordering::Relaxed);
2253 ActiveRunnableGuard { metrics: self }
2254 }
2255
2256 #[cfg(any(napi_runtime_os_threads, test))]
2257 fn runnable_cancelled(&self) {
2258 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2259 }
2260
2261 fn blocking_scheduled(&self) {
2265 self
2266 .blocking_tasks_scheduled
2267 .fetch_add(1, Ordering::Relaxed);
2268 }
2269
2270 fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2271 self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2272 if count_active_lane {
2273 let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2274 #[cfg(test)]
2275 run_runtime_metrics_high_water_test_hook();
2276 self
2277 .max_active_blocking_tasks
2278 .fetch_max(active, Ordering::Relaxed);
2279 }
2280 BlockingTaskGuard {
2281 metrics: Arc::clone(self),
2282 count_active_lane,
2283 }
2284 }
2285
2286 fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2287 let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2288 let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2289 let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2290 RuntimeMetricsSnapshot {
2291 flavor: options.flavor,
2292 worker_threads: options.worker_threads,
2293 max_blocking_tasks: options.max_blocking_tasks,
2294 tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2295 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2296 tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2297 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2298 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2299 queued_runnables,
2300 max_queued_runnables: self
2301 .max_queued_runnables
2302 .load(Ordering::Relaxed)
2303 .max(queued_runnables),
2304 active_runnables,
2305 max_active_runnables: self
2306 .max_active_runnables
2307 .load(Ordering::Relaxed)
2308 .max(active_runnables),
2309 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2310 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2311 active_blocking_tasks,
2312 max_active_blocking_tasks: self
2313 .max_active_blocking_tasks
2314 .load(Ordering::Relaxed)
2315 .max(active_blocking_tasks),
2316 }
2317 }
2318
2319 fn progress_fingerprint(&self) -> ProgressFingerprint {
2328 loop {
2329 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2330 if !reset_generation.is_multiple_of(2) {
2331 std::hint::spin_loop();
2332 continue;
2333 }
2334 let fingerprint = ProgressFingerprint {
2335 reset_generation,
2336 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2337 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2338 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2339 blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2340 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2341 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2342 };
2343 if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2344 return fingerprint;
2345 }
2346 }
2347 }
2348
2349 fn reset(&self) {
2350 let reset_guard = self
2351 .reset_lock
2352 .lock()
2353 .unwrap_or_else(std::sync::PoisonError::into_inner);
2354 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2362 let Some(resetting_generation) = reset_generation.checked_add(1) else {
2363 drop(reset_guard);
2364 panic!("runtime metrics reset generation space exhausted");
2365 };
2366 let Some(settled_generation) = resetting_generation.checked_add(1) else {
2367 drop(reset_guard);
2368 panic!("runtime metrics reset generation space exhausted");
2369 };
2370 self
2371 .reset_generation
2372 .store(resetting_generation, Ordering::SeqCst);
2373 self.tasks_spawned.store(0, Ordering::Relaxed);
2374 self.tasks_completed.store(0, Ordering::Relaxed);
2375 self.tasks_panicked.store(0, Ordering::Relaxed);
2376 self.runnable_schedules.store(0, Ordering::Relaxed);
2377 self.runnable_polls.store(0, Ordering::Relaxed);
2378 self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2379 self.blocking_tasks_started.store(0, Ordering::Relaxed);
2380 self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2381 self
2382 .reset_generation
2383 .store(settled_generation, Ordering::SeqCst);
2384 }
2385}
2386
2387#[derive(Clone, Copy, PartialEq, Eq)]
2389struct ProgressFingerprint {
2390 reset_generation: u64,
2391 runnable_schedules: u64,
2392 runnable_polls: u64,
2393 tasks_completed: u64,
2394 blocking_tasks_scheduled: u64,
2395 blocking_tasks_started: u64,
2396 blocking_tasks_completed: u64,
2397}
2398
2399struct ActiveRunnableGuard<'a> {
2400 metrics: &'a RuntimeMetrics,
2401}
2402
2403impl Drop for ActiveRunnableGuard<'_> {
2404 fn drop(&mut self) {
2405 self
2406 .metrics
2407 .active_runnables
2408 .fetch_sub(1, Ordering::Relaxed);
2409 }
2410}
2411
2412struct BlockingTaskGuard {
2413 metrics: Arc<RuntimeMetrics>,
2414 count_active_lane: bool,
2415}
2416
2417impl Drop for BlockingTaskGuard {
2418 fn drop(&mut self) {
2419 if self.count_active_lane {
2420 self
2421 .metrics
2422 .active_blocking_tasks
2423 .fetch_sub(1, Ordering::Relaxed);
2424 }
2425 self
2426 .metrics
2427 .blocking_tasks_completed
2428 .fetch_add(1, Ordering::Relaxed);
2429 }
2430}
2431
2432static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2433
2434fn next_runtime_generation() -> u64 {
2435 next_unique_id(
2436 &NEXT_RUNTIME_GENERATION,
2437 "async runtime generation id space exhausted",
2438 )
2439}
2440
2441thread_local! {
2442 static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2443 const { std::cell::Cell::new(None) };
2444}
2445
2446fn active_runtime_generation() -> Option<u64> {
2447 ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2448}
2449
2450struct RuntimeGenerationGuard {
2451 previous: Option<u64>,
2452}
2453
2454impl RuntimeGenerationGuard {
2455 fn enter(generation: u64) -> Self {
2456 Self::enter_optional(Some(generation))
2457 }
2458
2459 fn enter_optional(generation: Option<u64>) -> Self {
2460 Self {
2461 previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2462 }
2463 }
2464}
2465
2466impl Drop for RuntimeGenerationGuard {
2467 fn drop(&mut self) {
2468 ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2469 }
2470}
2471
2472struct ContainedFuture<F> {
2473 future: ManuallyDrop<F>,
2474 generation: u64,
2475}
2476
2477impl<F> ContainedFuture<F> {
2478 fn new(future: F, generation: u64) -> Self {
2479 Self {
2480 future: ManuallyDrop::new(future),
2481 generation,
2482 }
2483 }
2484}
2485
2486impl<F: Future> Future for ContainedFuture<F> {
2487 type Output = F::Output;
2488
2489 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2490 let this = unsafe { self.get_unchecked_mut() };
2492 let _generation = RuntimeGenerationGuard::enter(this.generation);
2493 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2496 }
2497}
2498
2499impl<F> Drop for ContainedFuture<F> {
2500 fn drop(&mut self) {
2501 let _generation = RuntimeGenerationGuard::enter(self.generation);
2502 let _ = catch_unwind_contained(|| {
2503 unsafe { ManuallyDrop::drop(&mut self.future) };
2505 });
2506 }
2507}
2508
2509#[derive(Default)]
2510struct GenerationStop {
2511 stopping: AtomicBool,
2512 publication: Mutex<()>,
2513 parkers: Mutex<Vec<Weak<DriverParker>>>,
2514}
2515
2516impl GenerationStop {
2517 fn is_stopping(&self) -> bool {
2518 self.stopping.load(Ordering::Acquire)
2519 }
2520
2521 fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2522 #[cfg(all(test, napi_runtime_os_threads))]
2523 run_before_generation_stop_publication_lock_test_hook();
2524 self.runnable_claim_guard()
2525 }
2526
2527 fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2528 GenerationStopPublicationGuard {
2529 stop: self,
2530 _publication: self
2531 .publication
2532 .lock()
2533 .unwrap_or_else(std::sync::PoisonError::into_inner),
2534 }
2535 }
2536
2537 fn publish_stopping(&self) {
2538 self.publication_guard().publish_stopping();
2539 }
2540
2541 fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2542 {
2543 let mut parkers = self
2544 .parkers
2545 .lock()
2546 .unwrap_or_else(std::sync::PoisonError::into_inner);
2547 parkers.retain(|candidate| candidate.strong_count() != 0);
2548 parkers.push(Arc::downgrade(parker));
2549 }
2550 if self.is_stopping() {
2551 parker.unpark();
2552 }
2553 GenerationStopGuard {
2554 stop: Arc::clone(self),
2555 parker: Arc::downgrade(parker),
2556 }
2557 }
2558
2559 fn wake_all(&self) -> usize {
2564 let mut count = 0;
2565 let mut registered = self
2566 .parkers
2567 .lock()
2568 .unwrap_or_else(std::sync::PoisonError::into_inner);
2569 registered.retain(|candidate| {
2570 let Some(parker) = candidate.upgrade() else {
2571 return false;
2572 };
2573 count += 1;
2574 parker.unpark();
2577 true
2578 });
2579 count
2580 }
2581
2582 fn begin_shutdown(&self) {
2583 self.publish_stopping();
2584 let parkers = {
2585 let mut registered = self
2586 .parkers
2587 .lock()
2588 .unwrap_or_else(std::sync::PoisonError::into_inner);
2589 let parkers = registered
2590 .iter()
2591 .filter_map(Weak::upgrade)
2592 .collect::<Vec<_>>();
2593 registered.clear();
2594 parkers
2595 };
2596 for parker in parkers {
2597 parker.unpark();
2598 }
2599 }
2600}
2601
2602struct GenerationStopPublicationGuard<'a> {
2603 stop: &'a GenerationStop,
2604 _publication: std::sync::MutexGuard<'a, ()>,
2605}
2606
2607impl GenerationStopPublicationGuard<'_> {
2608 fn is_stopping(&self) -> bool {
2609 self.stop.is_stopping()
2610 }
2611
2612 fn publish_stopping(&self) {
2613 self.stop.stopping.store(true, Ordering::Release);
2614 }
2615}
2616
2617struct GenerationStopGuard {
2618 stop: Arc<GenerationStop>,
2619 parker: Weak<DriverParker>,
2620}
2621
2622impl Drop for GenerationStopGuard {
2623 fn drop(&mut self) {
2624 let Some(parker) = self.parker.upgrade() else {
2625 return;
2626 };
2627 let mut registered = self
2628 .stop
2629 .parkers
2630 .lock()
2631 .unwrap_or_else(std::sync::PoisonError::into_inner);
2632 if let Some(index) = registered.iter().position(|candidate| {
2633 candidate
2634 .upgrade()
2635 .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2636 }) {
2637 registered.swap_remove(index);
2638 }
2639 }
2640}
2641
2642struct GenerationWorkState {
2643 closed: bool,
2644 next_task_id: u64,
2645 active: usize,
2646 abort_handles: FxHashMap<u64, AbortHandle>,
2647}
2648
2649struct GenerationWork {
2650 id: u64,
2651 state: Mutex<GenerationWorkState>,
2652 idle: Condvar,
2653}
2654
2655impl GenerationWork {
2656 fn new() -> Arc<Self> {
2657 Arc::new(Self {
2658 id: next_runtime_generation(),
2659 state: Mutex::new(GenerationWorkState {
2660 closed: false,
2661 next_task_id: 0,
2662 active: 0,
2663 abort_handles: FxHashMap::default(),
2664 }),
2665 idle: Condvar::new(),
2666 })
2667 }
2668
2669 fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2670 let (abort_handle, abort_registration) = AbortHandle::new_pair();
2671 let mut state = self
2672 .state
2673 .lock()
2674 .unwrap_or_else(std::sync::PoisonError::into_inner);
2675 if state.closed {
2676 return None;
2677 }
2678 let task_id = state.next_task_id;
2679 let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2680 drop(state);
2681 panic!("async runtime generation task id space exhausted");
2682 };
2683 state.next_task_id = next_task_id;
2684 state.active += 1;
2685 state.abort_handles.insert(task_id, abort_handle);
2686 Some((
2687 abort_registration,
2688 GenerationWorkGuard {
2689 work: Arc::clone(self),
2690 task_id: Some(task_id),
2691 },
2692 ))
2693 }
2694
2695 fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2696 let mut state = self
2697 .state
2698 .lock()
2699 .unwrap_or_else(std::sync::PoisonError::into_inner);
2700 if state.closed {
2701 return None;
2702 }
2703 state.active += 1;
2704 Some(GenerationWorkGuard {
2705 work: Arc::clone(self),
2706 task_id: None,
2707 })
2708 }
2709
2710 fn close_and_abort(&self) {
2711 let abort_handles = {
2712 let mut state = self
2713 .state
2714 .lock()
2715 .unwrap_or_else(std::sync::PoisonError::into_inner);
2716 state.closed = true;
2717 state.abort_handles.values().cloned().collect::<Vec<_>>()
2718 };
2719 for abort_handle in abort_handles {
2720 abort_handle.abort();
2721 }
2722 }
2723
2724 fn wait_until_idle(&self) {
2725 self.wait_until_idle_retiring(|| false, || {});
2726 }
2727
2728 fn has_active_work(&self) -> bool {
2730 self
2731 .state
2732 .lock()
2733 .unwrap_or_else(std::sync::PoisonError::into_inner)
2734 .active
2735 != 0
2736 }
2737
2738 fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2746 let mut state = self
2747 .state
2748 .lock()
2749 .unwrap_or_else(std::sync::PoisonError::into_inner);
2750 while state.active != 0 {
2751 if has_deferred() {
2752 drop(state);
2753 retire();
2754 state = self
2755 .state
2756 .lock()
2757 .unwrap_or_else(std::sync::PoisonError::into_inner);
2758 continue;
2759 }
2760 state = self
2761 .idle
2762 .wait(state)
2763 .unwrap_or_else(std::sync::PoisonError::into_inner);
2764 }
2765 }
2766
2767 fn notify_idle_waiters(&self) {
2768 let _state = self
2769 .state
2770 .lock()
2771 .unwrap_or_else(std::sync::PoisonError::into_inner);
2772 self.idle.notify_all();
2773 }
2774}
2775
2776struct GenerationWorkGuard {
2777 work: Arc<GenerationWork>,
2778 task_id: Option<u64>,
2779}
2780
2781impl Drop for GenerationWorkGuard {
2782 fn drop(&mut self) {
2783 let mut state = self
2784 .work
2785 .state
2786 .lock()
2787 .unwrap_or_else(std::sync::PoisonError::into_inner);
2788 if let Some(task_id) = self.task_id {
2789 state.abort_handles.remove(&task_id);
2790 }
2791 state.active = state
2792 .active
2793 .checked_sub(1)
2794 .expect("generation work count underflow");
2795 if state.active == 0 {
2796 self.work.idle.notify_all();
2797 }
2798 }
2799}
2800
2801struct RegisteredTaskFuture<F> {
2807 future: ManuallyDrop<F>,
2808 registration: Option<GenerationWorkGuard>,
2809 generation: u64,
2810}
2811
2812impl<F> RegisteredTaskFuture<F> {
2813 fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2814 Self {
2815 future: ManuallyDrop::new(future),
2816 registration: Some(registration),
2817 generation,
2818 }
2819 }
2820}
2821
2822impl<F: Future> Future for RegisteredTaskFuture<F> {
2823 type Output = F::Output;
2824
2825 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2826 let this = unsafe { self.get_unchecked_mut() };
2828 let _generation = RuntimeGenerationGuard::enter(this.generation);
2829 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2832 }
2833}
2834
2835impl<F> Drop for RegisteredTaskFuture<F> {
2836 fn drop(&mut self) {
2837 let _generation = RuntimeGenerationGuard::enter(self.generation);
2838 let _ = catch_unwind_contained(|| {
2839 unsafe { ManuallyDrop::drop(&mut self.future) };
2841 });
2842 drop(self.registration.take());
2845 }
2846}
2847
2848#[cfg(napi_runtime_os_threads)]
2849struct RegisteredBlockingFunction<F> {
2850 function: Option<F>,
2851 registration: Option<GenerationWorkGuard>,
2852 generation: u64,
2853}
2854
2855#[cfg(napi_runtime_os_threads)]
2856impl<F> RegisteredBlockingFunction<F> {
2857 fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2858 Self {
2859 function: Some(function),
2860 registration: Some(registration),
2861 generation,
2862 }
2863 }
2864
2865 fn run<T>(mut self) -> Result<T, JoinError>
2866 where
2867 F: FnOnce() -> T,
2868 {
2869 let _generation = RuntimeGenerationGuard::enter(self.generation);
2870 let result = catch_unwind_join_error(
2871 self
2872 .function
2873 .take()
2874 .expect("blocking function must be available"),
2875 );
2876 drop(self.registration.take());
2877 result
2878 }
2879}
2880
2881#[cfg(napi_runtime_os_threads)]
2882impl<F> Drop for RegisteredBlockingFunction<F> {
2883 fn drop(&mut self) {
2884 let _generation = RuntimeGenerationGuard::enter(self.generation);
2885 if let Some(function) = self.function.take() {
2886 let _ = catch_unwind_contained(|| drop(function));
2887 }
2888 drop(self.registration.take());
2892 }
2893}
2894
2895fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2896 let _active = metrics.runnable_started();
2897 let _ = catch_unwind_contained(|| runnable.run());
2898}
2899
2900#[cfg(napi_runtime_os_threads)]
2901enum RunnableClaim {
2902 Empty,
2903 Run(Runnable),
2904 Cancel(Runnable),
2905}
2906
2907static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2908static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2909
2910fn next_current_thread_dispatch_id() -> u64 {
2911 next_unique_id(
2912 &NEXT_CURRENT_THREAD_DISPATCH_ID,
2913 "CurrentThread dispatch id space exhausted",
2914 )
2915 .checked_add(1)
2916 .expect("CurrentThread dispatch id space exhausted")
2917}
2918
2919struct CurrentThreadBlockingAdmission {
2920 state: Mutex<CurrentThreadBlockingAdmissionState>,
2921}
2922
2923struct CurrentThreadBlockingAdmissionState {
2924 owner: Option<BlockingOwnerToken>,
2925 closed: bool,
2926 #[cfg(napi_runtime_os_threads)]
2927 queue: BlockingQueue,
2928}
2929
2930struct CurrentThreadExecutor {
2931 id: u64,
2932 generation: u64,
2933 stop: Arc<GenerationStop>,
2934 queue: Mutex<CurrentThreadQueue>,
2935 blocking_admission: CurrentThreadBlockingAdmission,
2936 draining: AtomicBool,
2937 scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2938 scheduler_idle: Condvar,
2939 dispatch_pending: AtomicU64,
2943 recovery_dispatch: AtomicU64,
2947 cancelling_failed_dispatch: AtomicBool,
2953 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2954 task_drivers: &'static CurrentThreadTaskDriverRegistry,
2955 metrics: Arc<RuntimeMetrics>,
2956 threadless: bool,
2964 park_deadline: Option<Duration>,
2965 host_timers: Arc<HostTimerRegistry>,
2975 generation_work: ArcSwapOption<GenerationWork>,
2980}
2981
2982impl Drop for CurrentThreadExecutor {
2983 fn drop(&mut self) {
2984 self.retire_rejected_work();
2987 }
2988}
2989
2990struct CurrentThreadQueue {
2991 closed: bool,
2992 runnables: VecDeque<Runnable>,
2993 rejected: Vec<Runnable>,
3008 #[cfg(napi_runtime_os_threads)]
3012 rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
3013}
3014
3015#[derive(Default)]
3016struct CurrentThreadSchedulerState {
3017 active_host_turns: usize,
3018 active_dispatch_calls: usize,
3019 dispatch_publications: Vec<CurrentThreadDispatchPublication>,
3020}
3021
3022struct CurrentThreadDispatchPublication {
3023 dispatch: u64,
3024 republish_requested: bool,
3025}
3026
3027#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3028enum CurrentThreadDispatchRequest {
3029 Accepted,
3030 Coalesced,
3031 Unavailable,
3032}
3033
3034#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3035struct CurrentThreadTaskDispatchFailure {
3036 dispatch: u64,
3037 epoch: u64,
3038}
3039
3040#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3041enum CurrentThreadHostDispatchResult {
3042 Accepted,
3043 Unavailable,
3044 Failed(CurrentThreadTaskDispatchFailure),
3045}
3046
3047enum CurrentThreadDispatchPublicationStep {
3048 Republish,
3049 Complete(Option<CurrentThreadHostDispatchResult>),
3050}
3051
3052struct CurrentThreadHostTurn {
3053 executor: Arc<CurrentThreadExecutor>,
3054 draining: bool,
3055 _generation: RuntimeGenerationGuard,
3056}
3057
3058impl CurrentThreadHostTurn {
3059 fn drive(&mut self) {
3060 let executor = Arc::clone(&self.executor);
3061 executor.drive_admitted_host_turn(self);
3062 }
3063
3064 fn release_draining(&mut self) {
3065 if !self.draining {
3066 return;
3067 }
3068 let _scheduler = self
3069 .executor
3070 .scheduler_idle_lock
3071 .lock()
3072 .unwrap_or_else(std::sync::PoisonError::into_inner);
3073 self.executor.draining.store(false, Ordering::Release);
3074 self.draining = false;
3075 }
3076}
3077
3078impl Drop for CurrentThreadHostTurn {
3079 fn drop(&mut self) {
3080 let mut scheduler = self
3081 .executor
3082 .scheduler_idle_lock
3083 .lock()
3084 .unwrap_or_else(std::sync::PoisonError::into_inner);
3085 if self.draining {
3086 self.executor.draining.store(false, Ordering::Release);
3087 self.draining = false;
3088 }
3089 scheduler.active_host_turns = scheduler
3090 .active_host_turns
3091 .checked_sub(1)
3092 .expect("CurrentThread active host-turn count underflow");
3093 self.executor.scheduler_idle.notify_all();
3094 }
3095}
3096
3097#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3104pub struct CurrentThreadTaskCallbackLease {
3105 _host_turn: CurrentThreadHostTurn,
3106 _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3107}
3108
3109impl CurrentThreadTaskCallbackLease {
3110 fn new(host_turn: CurrentThreadHostTurn) -> Self {
3111 Self {
3112 _host_turn: host_turn,
3113 _not_send: std::marker::PhantomData,
3114 }
3115 }
3116}
3117
3118struct CurrentThreadDispatchCall<'a> {
3119 executor: &'a CurrentThreadExecutor,
3120 publication: Option<u64>,
3121 _generation: RuntimeGenerationGuard,
3122}
3123
3124impl CurrentThreadDispatchCall<'_> {
3125 fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3126 let dispatch = self
3127 .publication
3128 .expect("the CurrentThread dispatch call must own a publication");
3129 let index = scheduler
3130 .dispatch_publications
3131 .iter()
3132 .position(|publication| publication.dispatch == dispatch)
3133 .expect("the CurrentThread dispatch publication must remain registered");
3134 scheduler.dispatch_publications.swap_remove(index);
3135 self.publication = None;
3136 }
3137}
3138
3139impl Drop for CurrentThreadDispatchCall<'_> {
3140 fn drop(&mut self) {
3141 let mut scheduler = self
3142 .executor
3143 .scheduler_idle_lock
3144 .lock()
3145 .unwrap_or_else(std::sync::PoisonError::into_inner);
3146 if let Some(dispatch) = self.publication.take()
3147 && let Some(index) = scheduler
3148 .dispatch_publications
3149 .iter()
3150 .position(|publication| publication.dispatch == dispatch)
3151 {
3152 scheduler.dispatch_publications.swap_remove(index);
3153 }
3154 scheduler.active_dispatch_calls = scheduler
3155 .active_dispatch_calls
3156 .checked_sub(1)
3157 .expect("CurrentThread active dispatch-call count underflow");
3158 self.executor.scheduler_idle.notify_all();
3159 }
3160}
3161
3162thread_local! {
3163 static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3174}
3175
3176struct CurrentThreadScheduleScope;
3179
3180impl CurrentThreadScheduleScope {
3181 fn enter() -> Self {
3182 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3183 depth.set(
3184 depth
3185 .get()
3186 .checked_add(1)
3187 .expect("CurrentThread schedule depth exhausted"),
3188 );
3189 });
3190 Self
3191 }
3192
3193 fn is_active() -> bool {
3195 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3196 }
3197}
3198
3199impl Drop for CurrentThreadScheduleScope {
3200 fn drop(&mut self) {
3201 CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3202 depth.set(
3203 depth
3204 .get()
3205 .checked_sub(1)
3206 .expect("CurrentThread schedule depth underflow"),
3207 );
3208 });
3209 }
3210}
3211
3212thread_local! {
3213 static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3234 const { std::cell::Cell::new(false) };
3235}
3236
3237struct CurrentThreadRejectedWorkRetirementScope;
3240
3241impl CurrentThreadRejectedWorkRetirementScope {
3242 fn enter() -> Option<Self> {
3246 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3247 if active.replace(true) {
3248 return None;
3252 }
3253 Some(Self)
3254 })
3255 }
3256}
3257
3258impl Drop for CurrentThreadRejectedWorkRetirementScope {
3259 fn drop(&mut self) {
3260 CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3261 }
3262}
3263
3264struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3265
3266impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3267 fn drop(&mut self) {
3268 self
3269 .0
3270 .cancelling_failed_dispatch
3271 .store(false, Ordering::Release);
3272 }
3273}
3274
3275enum CurrentThreadDispatchFailureAction<'a> {
3276 None,
3277 Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3278 Publish {
3279 dispatch: u64,
3280 call: CurrentThreadDispatchCall<'a>,
3281 },
3282}
3283
3284struct CurrentThreadDispatchFailureRecovery<'a> {
3285 action: CurrentThreadDispatchFailureAction<'a>,
3286 _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3287}
3288
3289struct CurrentThreadRegistryCancellationGuard<'a> {
3290 registry: &'a CurrentThreadTaskDriverRegistry,
3291 failure: Option<CurrentThreadTaskDispatchFailure>,
3292}
3293
3294impl CurrentThreadRegistryCancellationGuard<'_> {
3295 fn new(
3296 registry: &CurrentThreadTaskDriverRegistry,
3297 failure: CurrentThreadTaskDispatchFailure,
3298 ) -> CurrentThreadRegistryCancellationGuard<'_> {
3299 CurrentThreadRegistryCancellationGuard {
3300 registry,
3301 failure: Some(failure),
3302 }
3303 }
3304}
3305
3306impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3307 fn drop(&mut self) {
3308 if let Some(failure) = self.failure.take() {
3309 let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3310 }
3311 }
3312}
3313
3314struct CurrentThreadBlockingGuard<'a> {
3315 executor: &'a CurrentThreadExecutor,
3316 owner: BlockingOwnerToken,
3317 previous_owner: Option<BlockingOwnerToken>,
3318 owns_lane: bool,
3319}
3320
3321impl CurrentThreadBlockingGuard<'_> {
3322 fn counts_active_lane(&self) -> bool {
3323 self.owns_lane
3324 }
3325}
3326
3327impl Drop for CurrentThreadBlockingGuard<'_> {
3328 fn drop(&mut self) {
3329 BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3330 if !self.owns_lane {
3331 return;
3332 }
3333 let mut state = self
3334 .executor
3335 .blocking_admission
3336 .state
3337 .lock()
3338 .unwrap_or_else(std::sync::PoisonError::into_inner);
3339 debug_assert_eq!(state.owner, Some(self.owner));
3340 state.owner = None;
3341 drop(state);
3342 self.executor.stop.wake_all();
3343 }
3344}
3345
3346impl CurrentThreadExecutor {
3347 const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3350 #[cfg(napi_runtime_os_threads)]
3352 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3353 const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3357
3358 #[cfg(test)]
3362 fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3363 Self::with_detection(metrics, THREADLESS_BUILD, None)
3364 }
3365
3366 #[cfg(test)]
3372 fn with_detection(
3373 metrics: Arc<RuntimeMetrics>,
3374 threadless: bool,
3375 park_deadline: Option<Duration>,
3376 ) -> Self {
3377 Self::with_generation(
3378 metrics,
3379 threadless,
3380 park_deadline,
3381 u64::MAX,
3382 Arc::new(GenerationStop::default()),
3383 )
3384 }
3385
3386 fn with_generation(
3387 metrics: Arc<RuntimeMetrics>,
3388 threadless: bool,
3389 park_deadline: Option<Duration>,
3390 generation: u64,
3391 stop: Arc<GenerationStop>,
3392 ) -> Self {
3393 Self {
3394 id: next_unique_id(
3395 &NEXT_RUNTIME_EXECUTOR_ID,
3396 "async runtime executor id space exhausted",
3397 ),
3398 generation,
3399 stop,
3400 queue: Mutex::new(CurrentThreadQueue {
3401 closed: false,
3402 runnables: VecDeque::new(),
3403 rejected: Vec::new(),
3404 #[cfg(napi_runtime_os_threads)]
3405 rejected_blocking: Vec::new(),
3406 }),
3407 blocking_admission: CurrentThreadBlockingAdmission {
3408 state: Mutex::new(CurrentThreadBlockingAdmissionState {
3409 owner: None,
3410 closed: false,
3411 #[cfg(napi_runtime_os_threads)]
3412 queue: BlockingQueue {
3413 closed: false,
3414 head: None,
3415 tail: None,
3416 jobs: FxHashMap::default(),
3417 },
3418 }),
3419 },
3420 draining: AtomicBool::new(false),
3421 scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3422 scheduler_idle: Condvar::new(),
3423 dispatch_pending: AtomicU64::new(0),
3424 recovery_dispatch: AtomicU64::new(0),
3425 cancelling_failed_dispatch: AtomicBool::new(false),
3426 task_dispatch: dispatch_current_thread_tasks,
3427 task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3428 metrics,
3429 threadless,
3430 park_deadline,
3431 host_timers: Arc::new(HostTimerRegistry::default()),
3432 generation_work: ArcSwapOption::empty(),
3433 }
3434 }
3435
3436 fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3437 self.generation_work.store(Some(Arc::clone(work)));
3438 }
3439
3440 fn has_rejected_work(&self) -> bool {
3443 let queue = self
3444 .queue
3445 .lock()
3446 .unwrap_or_else(std::sync::PoisonError::into_inner);
3447 #[cfg(napi_runtime_os_threads)]
3448 let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3449 #[cfg(not(napi_runtime_os_threads))]
3450 let has_rejected_blocking = false;
3451 !queue.rejected.is_empty() || has_rejected_blocking
3452 }
3453
3454 fn scheduler_work_pending(&self) -> bool {
3456 let idle = self
3457 .scheduler_idle_lock
3458 .lock()
3459 .unwrap_or_else(std::sync::PoisonError::into_inner);
3460 idle.active_host_turns != 0 || idle.active_dispatch_calls != 0
3461 }
3462
3463 fn retire_rejected_work(&self) {
3472 if !self.has_rejected_work() {
3473 return;
3474 }
3475 let _generation = RuntimeGenerationGuard::enter(self.generation);
3476 loop {
3477 #[cfg(napi_runtime_os_threads)]
3478 let (rejected, rejected_blocking) = {
3479 let mut queue = self
3480 .queue
3481 .lock()
3482 .unwrap_or_else(std::sync::PoisonError::into_inner);
3483 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3484 return;
3485 }
3486 (
3487 std::mem::take(&mut queue.rejected),
3488 std::mem::take(&mut queue.rejected_blocking),
3489 )
3490 };
3491 #[cfg(not(napi_runtime_os_threads))]
3492 let rejected = {
3493 let mut queue = self
3494 .queue
3495 .lock()
3496 .unwrap_or_else(std::sync::PoisonError::into_inner);
3497 if queue.rejected.is_empty() {
3498 return;
3499 }
3500 std::mem::take(&mut queue.rejected)
3501 };
3502 for runnable in rejected {
3503 let _ = catch_unwind_contained(|| drop(runnable));
3504 }
3505 #[cfg(napi_runtime_os_threads)]
3506 for job in rejected_blocking {
3507 let _ = catch_unwind_contained(|| drop(job));
3508 }
3509 }
3510 }
3511
3512 fn retire_rejected_work_off_waker_stack(&self) {
3518 if CurrentThreadScheduleScope::is_active() {
3519 return;
3520 }
3521 self.retire_rejected_work();
3522 }
3523
3524 fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3534 self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3535 }
3536
3537 fn request_rejected_work_retirement(self: &Arc<Self>) {
3638 if !self.has_rejected_work() {
3639 return;
3640 }
3641 let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3642 return;
3647 };
3648 for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3649 if !self.needs_rejected_work_retirement_dispatch() {
3650 return;
3651 }
3652 if matches!(
3653 self.request_drain_with(next_current_thread_dispatch_id),
3654 CurrentThreadDispatchRequest::Unavailable
3655 ) {
3656 return;
3658 }
3659 }
3660 if self.publish_final_rejected_work_retirement()
3661 && self.needs_rejected_work_retirement_dispatch()
3662 {
3663 let _ = self.request_drain_with(next_current_thread_dispatch_id);
3682 }
3683 }
3684
3685 fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3712 if !self.needs_rejected_work_retirement_dispatch() {
3713 return false;
3714 }
3715 let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3716 return false;
3719 };
3720 let dispatch = next_current_thread_dispatch_id();
3721 let dispatch_call = {
3722 let mut scheduler = self.lock_scheduler_for_publication();
3723 self.dispatch_pending.store(dispatch, Ordering::Release);
3724 self
3725 .begin_host_dispatch_publication(&mut scheduler, dispatch)
3726 .expect("a fresh CurrentThread retirement dispatch must own its publication")
3727 };
3728 let result = self.publish_host_dispatch(dispatch, dispatch_call);
3729 {
3730 let _queue = self
3734 .queue
3735 .lock()
3736 .unwrap_or_else(std::sync::PoisonError::into_inner);
3737 drop(cancelling);
3738 }
3739 if let Some(work) = self.generation_work.load_full() {
3742 work.notify_idle_waiters();
3743 }
3744 result == CurrentThreadHostDispatchResult::Accepted
3745 }
3746
3747 fn fresh_owner_token(&self) -> BlockingOwnerToken {
3748 BlockingOwnerToken {
3749 executor_id: self.id,
3750 frame: next_unique_id(
3751 &NEXT_BLOCKING_FRAME,
3752 "blocking owner frame id space exhausted",
3753 ),
3754 }
3755 }
3756
3757 fn enter_blocking_owner(
3758 &self,
3759 owner: BlockingOwnerToken,
3760 owns_lane: bool,
3761 ) -> CurrentThreadBlockingGuard<'_> {
3762 let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3763 CurrentThreadBlockingGuard {
3764 executor: self,
3765 owner,
3766 previous_owner,
3767 owns_lane,
3768 }
3769 }
3770
3771 fn schedule_blocking<F, T>(
3772 self: &Arc<Self>,
3773 function: F,
3774 registration: GenerationWorkGuard,
3775 ) -> JoinHandle<T>
3776 where
3777 F: FnOnce() -> T + Send + 'static,
3778 T: Send + 'static,
3779 {
3780 self.metrics.blocking_scheduled();
3782 let mut function = Some(function);
3783 let mut registration = Some(registration);
3784 let ambient_owner = BLOCKING_OWNER
3785 .with(std::cell::Cell::get)
3786 .filter(|owner| owner.executor_id == self.id);
3787 let mut run_owner = None;
3788 let mut owns_lane = false;
3789 let mut rejected = false;
3790
3791 #[cfg(napi_runtime_os_threads)]
3792 let (sender, receiver) = oneshot::channel();
3793 #[cfg(napi_runtime_os_threads)]
3794 let dependency = BlockingDependency {
3795 job: BlockingJobId(next_unique_id(
3796 &NEXT_BLOCKING_JOB_ID,
3797 "blocking job id space exhausted",
3798 )),
3799 owner: ambient_owner,
3800 };
3801 #[cfg(napi_runtime_os_threads)]
3802 let mut queued = false;
3803
3804 {
3805 let mut state = self
3806 .blocking_admission
3807 .state
3808 .lock()
3809 .unwrap_or_else(std::sync::PoisonError::into_inner);
3810 #[cfg(all(test, napi_runtime_os_threads))]
3811 run_current_thread_blocking_claim_test_hook();
3812 if self.stop.is_stopping()
3816 || state.closed
3817 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3818 {
3819 rejected = true;
3820 } else if ambient_owner.is_some() && state.owner == ambient_owner {
3821 run_owner = ambient_owner;
3822 } else {
3823 #[cfg(napi_runtime_os_threads)]
3824 let queue_empty = state.queue.is_empty();
3825 #[cfg(not(napi_runtime_os_threads))]
3826 let queue_empty = true;
3827
3828 if state.owner.is_none() && queue_empty {
3829 let owner = self.fresh_owner_token();
3830 state.owner = Some(owner);
3831 run_owner = Some(owner);
3832 owns_lane = true;
3833 } else {
3834 #[cfg(napi_runtime_os_threads)]
3835 {
3836 let function = function
3837 .take()
3838 .expect("blocking function must be available");
3839 let registration = registration
3840 .take()
3841 .expect("blocking registration must be available");
3842 let generation = self.generation;
3843 let function = RegisteredBlockingFunction::new(function, registration, generation);
3844 state.queue.push(QueuedBlocking {
3845 id: dependency.job,
3846 run: Box::new(move || {
3847 let _generation = RuntimeGenerationGuard::enter(generation);
3848 let result = function
3849 .run()
3850 .map(|output| ContainedTaskOutput::new(output, generation));
3851 let _ = sender.send(result);
3852 }),
3853 });
3854 queued = true;
3855 }
3856 #[cfg(not(napi_runtime_os_threads))]
3857 {
3858 rejected = true;
3865 }
3866 }
3867 }
3868 }
3869
3870 #[cfg(napi_runtime_os_threads)]
3871 if queued {
3872 self.stop.wake_all();
3873 if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3874 self.request_drain();
3875 }
3876 return JoinHandle(JoinHandleInner::Blocking {
3877 receiver,
3878 dependency,
3879 published: PublishedBlockingDependencies::default(),
3880 });
3881 }
3882
3883 if rejected {
3884 let _generation = RuntimeGenerationGuard::enter(self.generation);
3885 let _ = catch_unwind_contained(|| drop(function.take()));
3886 drop(registration.take());
3887 return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3888 message: "the async runtime stopped before the blocking task could start".to_string(),
3889 }))));
3890 }
3891
3892 let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3893 let blocking = self.enter_blocking_owner(owner, owns_lane);
3894 let _registration = registration
3895 .take()
3896 .expect("blocking registration must be available");
3897 let _generation = RuntimeGenerationGuard::enter(self.generation);
3898 let result = {
3899 let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3900 catch_unwind_join_error(
3901 function
3902 .take()
3903 .expect("blocking function must be available"),
3904 )
3905 }
3906 .map(|output| ContainedTaskOutput::new(output, self.generation));
3907 drop(blocking);
3908 #[cfg(napi_runtime_os_threads)]
3909 if owns_lane && !self.draining.load(Ordering::Acquire) {
3910 self.service_queued_blocking_work();
3914 }
3915 JoinHandle(JoinHandleInner::Ready(Some(result)))
3916 }
3917
3918 #[cfg(napi_runtime_os_threads)]
3919 fn take_queued_blocking(
3920 &self,
3921 ) -> Option<(
3922 Box<dyn FnOnce() + Send + 'static>,
3923 CurrentThreadBlockingGuard<'_>,
3924 )> {
3925 let (job, owner) = {
3926 let mut state = self
3927 .blocking_admission
3928 .state
3929 .lock()
3930 .unwrap_or_else(std::sync::PoisonError::into_inner);
3931 #[cfg(all(test, napi_runtime_os_threads))]
3932 run_current_thread_blocking_claim_test_hook();
3933 if self.stop.is_stopping()
3934 || state.closed
3935 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3936 || state.owner.is_some()
3937 {
3938 return None;
3939 }
3940 let job = state.queue.pop_front()?;
3941 let owner = self.fresh_owner_token();
3942 state.owner = Some(owner);
3943 (job, owner)
3944 };
3945 Some((job, self.enter_blocking_owner(owner, true)))
3946 }
3947
3948 #[cfg(napi_runtime_os_threads)]
3949 fn run_one_blocking(&self) -> bool {
3950 let Some((job, blocking)) = self.take_queued_blocking() else {
3951 return false;
3952 };
3953 let _generation = RuntimeGenerationGuard::enter(self.generation);
3954 {
3955 let _task = self.metrics.blocking_started(true);
3956 let _ = catch_unwind_contained(job);
3957 }
3958 drop(blocking);
3959 true
3960 }
3961
3962 #[cfg(napi_runtime_os_threads)]
3963 fn service_queued_blocking_work(&self) {
3964 while self.run_one_blocking() {}
3965 }
3966
3967 #[cfg(napi_runtime_os_threads)]
3968 fn has_serviceable_blocking_work(&self) -> bool {
3969 let state = self
3970 .blocking_admission
3971 .state
3972 .lock()
3973 .unwrap_or_else(std::sync::PoisonError::into_inner);
3974 !state.closed && state.owner.is_none() && !state.queue.is_empty()
3975 }
3976
3977 #[cfg(napi_runtime_os_threads)]
3978 fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3979 let Some(ambient_owner) = BLOCKING_OWNER
3980 .with(std::cell::Cell::get)
3981 .filter(|owner| owner.executor_id == self.id)
3982 else {
3983 return false;
3984 };
3985 let Some(current) = dependency.select_for_owner(ambient_owner) else {
3986 return false;
3987 };
3988 let job = {
3989 let mut state = self
3990 .blocking_admission
3991 .state
3992 .lock()
3993 .unwrap_or_else(std::sync::PoisonError::into_inner);
3994 #[cfg(all(test, napi_runtime_os_threads))]
3995 run_current_thread_blocking_claim_test_hook();
3996 if self.stop.is_stopping()
3997 || state.closed
3998 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3999 || state.owner != Some(ambient_owner)
4000 {
4001 return false;
4002 }
4003 if dependency.try_claim(¤t) {
4004 state.queue.remove(current.publication.dependency.job)
4005 } else {
4006 None
4007 }
4008 };
4009 let Some(job) = job else {
4010 return false;
4011 };
4012 let _generation = RuntimeGenerationGuard::enter(self.generation);
4013 let _task = self.metrics.blocking_started(false);
4014 let _ = catch_unwind_contained(job);
4015 true
4016 }
4017
4018 fn close_blocking_admission(&self) {
4019 #[cfg(napi_runtime_os_threads)]
4020 let queued = {
4021 let mut state = self
4022 .blocking_admission
4023 .state
4024 .lock()
4025 .unwrap_or_else(std::sync::PoisonError::into_inner);
4026 state.closed = true;
4027 state.queue.closed = true;
4028 state.queue.take_all()
4029 };
4030 #[cfg(not(napi_runtime_os_threads))]
4031 {
4032 let mut state = self
4033 .blocking_admission
4034 .state
4035 .lock()
4036 .unwrap_or_else(std::sync::PoisonError::into_inner);
4037 state.closed = true;
4038 }
4039 #[cfg(napi_runtime_os_threads)]
4040 for job in queued {
4041 let _ = catch_unwind_contained(|| drop(job));
4042 }
4043 }
4044
4045 #[cfg(test)]
4046 fn with_task_dispatch(
4047 metrics: Arc<RuntimeMetrics>,
4048 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4049 ) -> Self {
4050 Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4051 }
4052
4053 #[cfg(test)]
4054 fn with_task_dispatch_registry(
4055 metrics: Arc<RuntimeMetrics>,
4056 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4057 task_drivers: &'static CurrentThreadTaskDriverRegistry,
4058 ) -> Self {
4059 let mut executor = Self::new(metrics);
4060 executor.task_dispatch = task_dispatch;
4061 executor.task_drivers = task_drivers;
4062 executor
4063 }
4064
4065 fn schedule(self: &Arc<Self>, runnable: Runnable) {
4070 let _scope = CurrentThreadScheduleScope::enter();
4071 self.schedule_runnable(runnable);
4072 }
4073
4074 fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4081 self.schedule_runnable(runnable);
4082 }
4083
4084 fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4085 self.metrics.runnable_scheduled();
4086 {
4087 let mut queue = self
4088 .queue
4089 .lock()
4090 .unwrap_or_else(std::sync::PoisonError::into_inner);
4091 if queue.closed {
4096 self
4103 .metrics
4104 .queued_runnables
4105 .fetch_sub(1, Ordering::Relaxed);
4106 queue.rejected.push(runnable);
4107 drop(queue);
4108 if let Some(work) = self.generation_work.load_full() {
4109 work.notify_idle_waiters();
4110 }
4111 return;
4112 }
4113 if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4114 self
4118 .metrics
4119 .queued_runnables
4120 .fetch_sub(1, Ordering::Relaxed);
4121 queue.rejected.push(runnable);
4122 return;
4123 }
4124 queue.runnables.push_back(runnable);
4125 }
4126 self.stop.wake_all();
4130 if !self.draining.load(Ordering::Acquire) {
4131 self.request_drain();
4132 }
4133 }
4134
4135 fn request_drain(self: &Arc<Self>) {
4144 let _ = self.request_drain_with(next_current_thread_dispatch_id);
4145 }
4146
4147 fn lock_scheduler_for_publication(
4148 &self,
4149 ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
4150 #[cfg(all(test, napi_runtime_os_threads))]
4151 match self.scheduler_idle_lock.try_lock() {
4152 Ok(scheduler) => scheduler,
4153 Err(std::sync::TryLockError::WouldBlock) => {
4154 run_current_thread_dispatch_publication_blocked_test_hook();
4155 self
4156 .scheduler_idle_lock
4157 .lock()
4158 .unwrap_or_else(std::sync::PoisonError::into_inner)
4159 }
4160 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4161 }
4162 #[cfg(any(not(test), not(napi_runtime_os_threads)))]
4163 self
4164 .scheduler_idle_lock
4165 .lock()
4166 .unwrap_or_else(std::sync::PoisonError::into_inner)
4167 }
4168
4169 fn request_drain_with(
4170 self: &Arc<Self>,
4171 next_dispatch: impl FnOnce() -> u64,
4172 ) -> CurrentThreadDispatchRequest {
4173 let mut scheduler = self.lock_scheduler_for_publication();
4174 if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4175 return CurrentThreadDispatchRequest::Unavailable;
4176 }
4177 if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4178 return CurrentThreadDispatchRequest::Coalesced;
4179 }
4180 let dispatch = next_dispatch();
4181 if self
4182 .dispatch_pending
4183 .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4184 .is_err()
4185 {
4186 return CurrentThreadDispatchRequest::Coalesced;
4187 }
4188 let dispatch_call = self
4189 .begin_host_dispatch_publication(&mut scheduler, dispatch)
4190 .expect("a fresh CurrentThread dispatch must own its publication");
4191 drop(scheduler);
4192 match self.publish_host_dispatch(dispatch, dispatch_call) {
4193 CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4194 CurrentThreadDispatchRequest::Accepted
4195 }
4196 CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4197 }
4198 }
4199
4200 fn claim_dispatch_call<'a>(
4201 &'a self,
4202 scheduler: &mut CurrentThreadSchedulerState,
4203 ) -> CurrentThreadDispatchCall<'a> {
4204 let generation = RuntimeGenerationGuard::enter(self.generation);
4205 let active_dispatch_calls = scheduler
4206 .active_dispatch_calls
4207 .checked_add(1)
4208 .expect("CurrentThread active dispatch-call count exhausted");
4209 scheduler.active_dispatch_calls = active_dispatch_calls;
4210 CurrentThreadDispatchCall {
4211 executor: self,
4212 publication: None,
4213 _generation: generation,
4214 }
4215 }
4216
4217 fn begin_host_dispatch_publication<'a>(
4218 &'a self,
4219 scheduler: &mut CurrentThreadSchedulerState,
4220 dispatch: u64,
4221 ) -> Option<CurrentThreadDispatchCall<'a>> {
4222 if let Some(publication) = scheduler
4224 .dispatch_publications
4225 .iter_mut()
4226 .find(|publication| publication.dispatch == dispatch)
4227 {
4228 publication.republish_requested = true;
4229 return None;
4230 }
4231 let mut dispatch_call = self.claim_dispatch_call(scheduler);
4232 scheduler
4233 .dispatch_publications
4234 .push(CurrentThreadDispatchPublication {
4235 dispatch,
4236 republish_requested: false,
4237 });
4238 dispatch_call.publication = Some(dispatch);
4239 Some(dispatch_call)
4240 }
4241
4242 fn publish_host_dispatch(
4243 self: &Arc<Self>,
4244 dispatch: u64,
4245 mut dispatch_call: CurrentThreadDispatchCall<'_>,
4246 ) -> CurrentThreadHostDispatchResult {
4247 let mut panic_payload = RetainedPanicPayload::default();
4248 loop {
4249 let result = match catch_unwind(AssertUnwindSafe(|| {
4250 #[cfg(all(test, napi_runtime_os_threads))]
4251 run_current_thread_dispatch_call_test_hook();
4252 let result = (self.task_dispatch)(dispatch);
4253 #[cfg(all(test, napi_runtime_os_threads))]
4254 run_current_thread_dispatch_returned_test_hook();
4255 result
4256 })) {
4257 Ok(result) => Some(result),
4258 Err(payload) => {
4259 panic_payload.capture(payload);
4260 None
4261 }
4262 };
4263
4264 let step = catch_unwind(AssertUnwindSafe(|| {
4265 let (republish, cancelling, failure, result) = {
4266 let mut scheduler = self
4267 .scheduler_idle_lock
4268 .lock()
4269 .unwrap_or_else(std::sync::PoisonError::into_inner);
4270 let index = scheduler
4271 .dispatch_publications
4272 .iter()
4273 .position(|publication| publication.dispatch == dispatch)
4274 .expect("the CurrentThread dispatch publication must remain registered");
4275 let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4276 let republish = dispatch_remains_pending
4277 && !self.stop.is_stopping()
4278 && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4279 && scheduler.dispatch_publications[index].republish_requested;
4280 if republish {
4281 if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4282 let _ = self.task_drivers.rearm_failed_publication(failure);
4283 }
4284 scheduler.dispatch_publications[index].republish_requested = false;
4285 (true, None, None, result)
4286 } else {
4287 dispatch_call.finish_publication(&mut scheduler);
4288 let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4289 #[cfg(all(test, napi_runtime_os_threads))]
4290 run_current_thread_failed_publication_retired_test_hook();
4291 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4292 } else {
4293 None
4294 };
4295 let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4296 && dispatch_remains_pending)
4297 .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4298 .flatten();
4299 let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4300 && !dispatch_remains_pending
4301 {
4302 Some(CurrentThreadHostDispatchResult::Accepted)
4305 } else {
4306 result
4307 };
4308 (false, cancelling, failure, result)
4309 }
4310 };
4311
4312 if republish {
4313 return CurrentThreadDispatchPublicationStep::Republish;
4314 }
4315 if let Some(cancelling) = cancelling {
4316 self.cancel_work_after_host_dispatch_failure(cancelling);
4317 }
4318 if let Some(failure) = failure {
4319 self.run_host_dispatch_failure(failure.action);
4320 }
4321 CurrentThreadDispatchPublicationStep::Complete(result)
4322 }));
4323
4324 match step {
4325 Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4326 Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4327 if panic_payload.is_some() {
4328 panic_payload.resume();
4329 }
4330 return result
4331 .expect("a CurrentThread host dispatch without a result must resume its panic");
4332 }
4333 Err(payload) => {
4334 panic_payload.capture(payload);
4335 panic_payload.resume();
4336 }
4337 }
4338 }
4339 }
4340
4341 fn claim_host_turn(
4342 self: &Arc<Self>,
4343 scheduler: &mut CurrentThreadSchedulerState,
4344 ) -> CurrentThreadHostTurn {
4345 scheduler.active_host_turns = scheduler
4346 .active_host_turns
4347 .checked_add(1)
4348 .expect("CurrentThread active host-turn count exhausted");
4349 self.draining.store(true, Ordering::Release);
4350 CurrentThreadHostTurn {
4351 executor: Arc::clone(self),
4352 draining: true,
4353 _generation: RuntimeGenerationGuard::enter(self.generation),
4354 }
4355 }
4356
4357 fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4358 let mut scheduler = self
4359 .scheduler_idle_lock
4360 .lock()
4361 .unwrap_or_else(std::sync::PoisonError::into_inner);
4362 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4363 return None;
4364 }
4365 if dispatch == 0
4366 || self
4367 .dispatch_pending
4368 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4369 .is_err()
4370 {
4371 return None;
4372 }
4373 #[cfg(all(test, napi_runtime_os_threads))]
4374 run_current_thread_dispatch_claim_test_hook();
4375 let _ =
4376 self
4377 .recovery_dispatch
4378 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4379 Some(self.claim_host_turn(&mut scheduler))
4380 }
4381
4382 #[cfg(test)]
4383 fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
4384 let mut scheduler = self
4385 .scheduler_idle_lock
4386 .lock()
4387 .unwrap_or_else(std::sync::PoisonError::into_inner);
4388 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4389 return None;
4390 }
4391 Some(self.claim_host_turn(&mut scheduler))
4392 }
4393
4394 #[cfg(test)]
4395 fn drive_host_turn(self: &Arc<Self>) {
4396 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4397 let host_turn = if dispatch == 0 {
4398 self.try_admit_host_turn_without_dispatch()
4399 } else {
4400 self.try_admit_host_turn(dispatch)
4401 };
4402 if let Some(mut host_turn) = host_turn {
4403 host_turn.drive();
4404 }
4405 }
4406
4407 #[cfg(test)]
4408 fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4409 if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4410 host_turn.drive();
4411 }
4412 }
4413
4414 fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4418 self.retire_rejected_work_off_waker_stack();
4421 #[cfg(napi_runtime_os_threads)]
4422 let mut runnable_streak = 0usize;
4423 for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4424 #[cfg(napi_runtime_os_threads)]
4425 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4426 runnable_streak = 0;
4427 if self.run_one_blocking() {
4428 continue;
4429 }
4430 }
4431 if self.drain_one() {
4432 #[cfg(napi_runtime_os_threads)]
4433 {
4434 runnable_streak += 1;
4435 }
4436 continue;
4437 }
4438 #[cfg(napi_runtime_os_threads)]
4439 if self.run_one_blocking() {
4440 runnable_streak = 0;
4441 continue;
4442 }
4443 break;
4444 }
4445 host_turn.release_draining();
4450 self.request_drain_if_work_remains();
4451 }
4452
4453 fn request_drain_if_queued(self: &Arc<Self>) {
4454 self.retire_rejected_work_off_waker_stack();
4455 if !self.has_serviceable_work() {
4456 return;
4457 }
4458 let mut scheduler = self.lock_scheduler_for_publication();
4459 if self.stop.is_stopping()
4460 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4461 || self.draining.load(Ordering::Acquire)
4462 {
4463 return;
4464 }
4465 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4466 if dispatch == 0 {
4467 drop(scheduler);
4468 self.request_drain_if_work_remains();
4469 return;
4470 }
4471
4472 let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4476 return;
4477 };
4478 drop(scheduler);
4479 let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4480 }
4481
4482 fn retire_unavailable_host_dispatch_locked(
4483 &self,
4484 dispatch: u64,
4485 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4486 if self
4487 .dispatch_pending
4488 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4489 .is_err()
4490 {
4491 return None;
4492 }
4493 if self
4494 .recovery_dispatch
4495 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4496 .is_ok()
4497 {
4498 self.begin_failed_dispatch_cancellation()
4499 } else {
4500 None
4501 }
4502 }
4503
4504 fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4505 let failure = {
4507 let mut scheduler = self
4508 .scheduler_idle_lock
4509 .lock()
4510 .unwrap_or_else(std::sync::PoisonError::into_inner);
4511 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4512 };
4513 if let Some(failure) = failure {
4514 self.run_host_dispatch_failure(failure.action);
4515 }
4516 self.retire_rejected_work_off_waker_stack();
4517 }
4518
4519 fn begin_host_dispatch_failure_locked<'a>(
4520 &'a self,
4521 scheduler: &mut CurrentThreadSchedulerState,
4522 failure: CurrentThreadTaskDispatchFailure,
4523 ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4524 if !self.task_drivers.is_current_failure(failure) {
4525 return None;
4526 }
4527 let registry_cancellation =
4528 CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4529 let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4530 Some(CurrentThreadDispatchFailureRecovery {
4531 action,
4532 _registry_cancellation: registry_cancellation,
4533 })
4534 }
4535
4536 fn begin_host_dispatch_failure_transition_locked<'a>(
4537 &'a self,
4538 scheduler: &mut CurrentThreadSchedulerState,
4539 dispatch: u64,
4540 ) -> CurrentThreadDispatchFailureAction<'a> {
4541 if dispatch == 0
4542 || self
4543 .dispatch_pending
4544 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4545 .is_err()
4546 {
4547 return CurrentThreadDispatchFailureAction::None;
4548 }
4549 #[cfg(all(test, napi_runtime_os_threads))]
4550 run_current_thread_dispatch_recovery_test_hook();
4551
4552 if self
4553 .recovery_dispatch
4554 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4555 .is_ok()
4556 {
4557 return self.begin_failed_dispatch_cancellation().map_or(
4561 CurrentThreadDispatchFailureAction::None,
4562 CurrentThreadDispatchFailureAction::Cancel,
4563 );
4564 }
4565
4566 if self.stop.is_stopping()
4567 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4568 || self.draining.load(Ordering::Acquire)
4569 || !self.has_serviceable_work()
4570 {
4571 return CurrentThreadDispatchFailureAction::None;
4572 }
4573
4574 let replacement = next_current_thread_dispatch_id();
4578 self.recovery_dispatch.store(replacement, Ordering::Release);
4579 self.dispatch_pending.store(replacement, Ordering::Release);
4580 let dispatch_call = self
4581 .begin_host_dispatch_publication(scheduler, replacement)
4582 .expect("a reserved CurrentThread replacement must own its publication");
4583 CurrentThreadDispatchFailureAction::Publish {
4584 dispatch: replacement,
4585 call: dispatch_call,
4586 }
4587 }
4588
4589 #[cfg(test)]
4590 fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4591 let failure = {
4592 let mut scheduler = self
4593 .scheduler_idle_lock
4594 .lock()
4595 .unwrap_or_else(std::sync::PoisonError::into_inner);
4596 self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4597 };
4598 self.run_host_dispatch_failure(failure);
4599 }
4600
4601 fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4602 match failure {
4603 CurrentThreadDispatchFailureAction::None => {}
4604 CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4605 self.cancel_work_after_host_dispatch_failure(cancelling);
4606 }
4607 CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4608 let _ = self.publish_host_dispatch(dispatch, call);
4609 }
4610 }
4611 }
4612
4613 fn has_serviceable_work(&self) -> bool {
4614 let has_queued_runnable = {
4615 let queue = self
4616 .queue
4617 .lock()
4618 .unwrap_or_else(std::sync::PoisonError::into_inner);
4619 !queue.closed && !queue.runnables.is_empty()
4620 };
4621 #[cfg(napi_runtime_os_threads)]
4622 let has_queued_blocking = self.has_serviceable_blocking_work();
4623 #[cfg(not(napi_runtime_os_threads))]
4624 let has_queued_blocking = false;
4625 has_queued_runnable || has_queued_blocking
4626 }
4627
4628 fn request_drain_if_work_remains(self: &Arc<Self>) {
4629 if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4630 self.request_drain();
4631 }
4632 }
4633
4634 fn begin_failed_dispatch_cancellation(
4635 &self,
4636 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4637 if self
4638 .cancelling_failed_dispatch
4639 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4640 .is_err()
4641 {
4642 return None;
4643 }
4644 self.dispatch_pending.store(0, Ordering::Release);
4645 self.recovery_dispatch.store(0, Ordering::Release);
4646 Some(CurrentThreadDispatchCancellationGuard(self))
4647 }
4648
4649 fn cancel_work_after_host_dispatch_failure(
4688 self: &Arc<Self>,
4689 cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4690 ) {
4691 let mut cancelling = Some(cancelling);
4692 #[cfg(napi_runtime_os_threads)]
4695 let queued_blocking = {
4696 let mut state = self
4697 .blocking_admission
4698 .state
4699 .lock()
4700 .unwrap_or_else(std::sync::PoisonError::into_inner);
4701 state.queue.take_all()
4702 };
4703 {
4704 let mut queue = self
4705 .queue
4706 .lock()
4707 .unwrap_or_else(std::sync::PoisonError::into_inner);
4708 let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4709 self
4711 .metrics
4712 .queued_runnables
4713 .fetch_sub(moved, Ordering::Relaxed);
4714 let runnables = std::mem::take(&mut queue.runnables);
4715 queue.rejected.extend(runnables);
4716 #[cfg(napi_runtime_os_threads)]
4717 queue.rejected_blocking.extend(queued_blocking);
4718 if CurrentThreadScheduleScope::is_active() {
4719 drop(cancelling.take());
4724 drop(queue);
4725 if let Some(work) = self.generation_work.load_full() {
4728 work.notify_idle_waiters();
4729 }
4730 self.request_rejected_work_retirement();
4734 return;
4735 }
4736 }
4737
4738 let _generation = RuntimeGenerationGuard::enter(self.generation);
4744 loop {
4745 #[cfg(napi_runtime_os_threads)]
4746 let (rejected, rejected_blocking) = {
4747 let mut queue = self
4748 .queue
4749 .lock()
4750 .unwrap_or_else(std::sync::PoisonError::into_inner);
4751 if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4752 drop(cancelling.take());
4753 break;
4754 }
4755 (
4756 std::mem::take(&mut queue.rejected),
4757 std::mem::take(&mut queue.rejected_blocking),
4758 )
4759 };
4760 #[cfg(not(napi_runtime_os_threads))]
4761 let rejected = {
4762 let mut queue = self
4763 .queue
4764 .lock()
4765 .unwrap_or_else(std::sync::PoisonError::into_inner);
4766 if queue.rejected.is_empty() {
4767 drop(cancelling.take());
4768 break;
4769 }
4770 std::mem::take(&mut queue.rejected)
4771 };
4772 for runnable in rejected {
4773 let _ = catch_unwind_contained(|| drop(runnable));
4774 }
4775 #[cfg(napi_runtime_os_threads)]
4776 for job in rejected_blocking {
4777 let _ = catch_unwind_contained(|| drop(job));
4778 }
4779 }
4780 }
4781
4782 fn take_runnable(&self) -> Option<Runnable> {
4783 let scheduler = self
4788 .scheduler_idle_lock
4789 .lock()
4790 .unwrap_or_else(std::sync::PoisonError::into_inner);
4791 #[cfg(all(test, napi_runtime_os_threads))]
4792 run_current_thread_runnable_claim_test_hook();
4793 let stop_publication = self.stop.publication_guard();
4794 if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4795 return None;
4796 }
4797 let runnable = {
4798 let mut queue = self
4799 .queue
4800 .lock()
4801 .unwrap_or_else(std::sync::PoisonError::into_inner);
4802 if queue.closed {
4803 None
4804 } else {
4805 queue.runnables.pop_front()
4806 }
4807 };
4808 drop(stop_publication);
4809 drop(scheduler);
4810 runnable
4811 }
4812
4813 fn drain_one(&self) -> bool {
4814 let runnable = self.take_runnable();
4815 if let Some(runnable) = runnable {
4816 #[cfg(napi_runtime_os_threads)]
4817 let _non_owner = BlockingOwnerGuard::enter(None);
4818 run_runnable(&self.metrics, runnable);
4819 true
4820 } else {
4821 false
4822 }
4823 }
4824
4825 fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4847 let parker = Arc::new(DriverParker::default());
4848 let _stop_registration = self.stop.register(&parker);
4849 #[cfg(napi_runtime_os_threads)]
4850 let dependency = BlockOnDependencyGuard::enter(self.generation);
4851 let waker = std::task::Waker::from(Arc::clone(&parker));
4852 let mut cx = Context::from_waker(&waker);
4853 #[cfg(napi_runtime_os_threads)]
4854 let mut runnable_streak = 0usize;
4855 loop {
4856 if self.stop.is_stopping() {
4857 return BlockOnOutcome::Stopped;
4858 }
4859 #[cfg(napi_runtime_os_threads)]
4860 dependency.clear();
4861 if future.as_mut().poll(&mut cx).is_ready() {
4862 return BlockOnOutcome::Completed;
4863 }
4864 if self.stop.is_stopping() {
4868 return BlockOnOutcome::Stopped;
4869 }
4870 #[cfg(napi_runtime_os_threads)]
4871 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4872 runnable_streak = 0;
4873 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4874 continue;
4875 }
4876 }
4877 if self.drain_one() {
4878 #[cfg(napi_runtime_os_threads)]
4879 {
4880 runnable_streak += 1;
4881 }
4882 continue;
4883 }
4884 #[cfg(napi_runtime_os_threads)]
4885 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4886 runnable_streak = 0;
4887 continue;
4888 }
4889 #[cfg(napi_runtime_os_threads)]
4890 {
4891 runnable_streak = 0;
4892 }
4893 if parker.consume_permit() {
4897 continue;
4898 }
4899 if self.threadless {
4911 if self.host_timers.has_pending() {
4912 std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
4913 }
4914 std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
4915 }
4916 match self.park_deadline {
4917 None => parker.park(),
4921 Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
4922 DeadlineParkOutcome::Woken => {}
4923 DeadlineParkOutcome::Expired(armed) => {
4924 if parker.consume_permit()
4939 || self.metrics.progress_fingerprint() != armed
4940 || self.host_timers.has_live_wait(deadline)
4941 {
4942 continue;
4943 }
4944 std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
4945 }
4946 },
4947 }
4948 }
4949 }
4950
4951 fn begin_shutdown(&self) {
4952 let _generation = RuntimeGenerationGuard::enter(self.generation);
4953 self.stop.begin_shutdown();
4954 self.close_blocking_admission();
4955 let (queued, rejected) = {
4956 let mut queue = self
4957 .queue
4958 .lock()
4959 .unwrap_or_else(std::sync::PoisonError::into_inner);
4960 queue.closed = true;
4961 #[cfg(napi_runtime_os_threads)]
4962 let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
4963 let rejected = std::mem::take(&mut queue.rejected);
4964 #[cfg(napi_runtime_os_threads)]
4965 let rejected = (rejected, rejected_blocking);
4966 (std::mem::take(&mut queue.runnables), rejected)
4967 };
4968 self.dispatch_pending.store(0, Ordering::Release);
4969 self.recovery_dispatch.store(0, Ordering::Release);
4970 self.task_drivers.retire_for_shutdown();
4971 for runnable in queued {
4972 self
4973 .metrics
4974 .queued_runnables
4975 .fetch_sub(1, Ordering::Relaxed);
4976 let _ = catch_unwind_contained(|| drop(runnable));
4979 }
4980 #[cfg(napi_runtime_os_threads)]
4983 let (rejected, rejected_blocking) = rejected;
4984 for runnable in rejected {
4985 let _ = catch_unwind_contained(|| drop(runnable));
4986 }
4987 #[cfg(napi_runtime_os_threads)]
4988 for job in rejected_blocking {
4989 let _ = catch_unwind_contained(|| drop(job));
4990 }
4991 self.host_timers.shutdown();
4992 }
4993
4994 fn wait_until_scheduler_idle(&self) {
4995 self.retire_rejected_work();
4998 let mut idle = self
4999 .scheduler_idle_lock
5000 .lock()
5001 .unwrap_or_else(std::sync::PoisonError::into_inner);
5002 while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
5003 idle = self
5004 .scheduler_idle
5005 .wait(idle)
5006 .unwrap_or_else(std::sync::PoisonError::into_inner);
5007 }
5008 debug_assert!(
5009 idle.dispatch_publications.is_empty(),
5010 "an idle CurrentThread scheduler must not retain dispatch publications"
5011 );
5012 }
5013}
5014
5015#[cfg(napi_runtime_os_threads)]
5028thread_local! {
5029 static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5030 static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5034}
5035
5036#[cfg(napi_runtime_os_threads)]
5042#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5043struct SchedulerDriverRole {
5044 executor_id: u64,
5045 can_run_blocking: bool,
5046}
5047
5048#[cfg(napi_runtime_os_threads)]
5049thread_local! {
5050 static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5051 const { std::cell::Cell::new(None) };
5052}
5053
5054#[cfg(napi_runtime_os_threads)]
5071thread_local! {
5072 static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5073 const { std::cell::Cell::new(None) };
5074}
5075
5076#[cfg(napi_runtime_os_threads)]
5077struct OnPoolWorkerGuard {
5078 previous_worker: Option<u64>,
5079 previous_driver: Option<SchedulerDriverRole>,
5080}
5081
5082#[cfg(napi_runtime_os_threads)]
5083impl OnPoolWorkerGuard {
5084 fn enter(id: u64) -> Self {
5088 Self::enter_with_role(id, true)
5089 }
5090
5091 fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5092 Self {
5093 previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5094 previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5095 flag.replace(Some(SchedulerDriverRole {
5096 executor_id: id,
5097 can_run_blocking,
5098 }))
5099 }),
5100 }
5101 }
5102}
5103
5104#[cfg(napi_runtime_os_threads)]
5105impl Drop for OnPoolWorkerGuard {
5106 fn drop(&mut self) {
5107 IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5108 ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5109 }
5110}
5111
5112#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5125struct BlockingOwnerToken {
5126 executor_id: u64,
5127 frame: u64,
5128}
5129
5130#[cfg(napi_runtime_os_threads)]
5131enum OwnedBlockingOutcome {
5132 Ineligible,
5133 OwnerBusy(BlockingOwnerToken),
5134 ReservationReleased {
5135 owner: BlockingOwnerToken,
5136 ran: bool,
5137 },
5138}
5139
5140static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5143#[cfg(napi_runtime_os_threads)]
5144static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5145
5146thread_local! {
5149 static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5150 const { std::cell::Cell::new(None) };
5151}
5152
5153#[cfg(napi_runtime_os_threads)]
5160struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5161
5162#[cfg(napi_runtime_os_threads)]
5163impl BlockingOwnerGuard {
5164 fn enter(token: Option<BlockingOwnerToken>) -> Self {
5165 Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5166 }
5167}
5168
5169#[cfg(napi_runtime_os_threads)]
5170impl Drop for BlockingOwnerGuard {
5171 fn drop(&mut self) {
5172 BLOCKING_OWNER.with(|cell| cell.set(self.0));
5173 }
5174}
5175
5176#[cfg(napi_runtime_os_threads)]
5177#[derive(Default)]
5178struct ActiveBlockingOwners {
5179 owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5183}
5184
5185#[cfg(napi_runtime_os_threads)]
5186impl ActiveBlockingOwners {
5187 fn register(&self, owner: BlockingOwnerToken) {
5188 let previous = self
5189 .owners
5190 .lock()
5191 .unwrap_or_else(std::sync::PoisonError::into_inner)
5192 .insert(owner, None);
5193 debug_assert!(previous.is_none());
5194 }
5195
5196 fn unregister(&self, owner: BlockingOwnerToken) {
5197 let removed = self
5198 .owners
5199 .lock()
5200 .unwrap_or_else(std::sync::PoisonError::into_inner)
5201 .remove(&owner);
5202 debug_assert!(removed.is_some());
5203 }
5204
5205 fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5206 let mut owners = self
5207 .owners
5208 .lock()
5209 .unwrap_or_else(std::sync::PoisonError::into_inner);
5210 let reservation = owners.get_mut(&owner)?;
5211 if reservation.is_some() {
5212 return None;
5213 }
5214 let id = next_unique_id(
5215 &NEXT_OWNER_RESERVATION,
5216 "blocking owner reservation id space exhausted",
5217 );
5218 *reservation = Some(id);
5219 Some(id)
5220 }
5221
5222 fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5223 let mut owners = self
5224 .owners
5225 .lock()
5226 .unwrap_or_else(std::sync::PoisonError::into_inner);
5227 if let Some(current) = owners.get_mut(&owner)
5228 && *current == Some(reservation)
5229 {
5230 *current = None;
5231 }
5232 }
5233
5234 fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5235 self
5236 .owners
5237 .lock()
5238 .unwrap_or_else(std::sync::PoisonError::into_inner)
5239 .get(&owner)
5240 .is_some_and(Option::is_none)
5241 }
5242}
5243
5244#[cfg(napi_runtime_os_threads)]
5245struct ActiveBlockingOwnerGuard<'a> {
5246 owners: &'a ActiveBlockingOwners,
5247 owner: BlockingOwnerToken,
5248}
5249
5250#[cfg(napi_runtime_os_threads)]
5251impl<'a> ActiveBlockingOwnerGuard<'a> {
5252 fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5253 owners.register(owner);
5254 Self { owners, owner }
5255 }
5256}
5257
5258#[cfg(napi_runtime_os_threads)]
5259impl Drop for ActiveBlockingOwnerGuard<'_> {
5260 fn drop(&mut self) {
5261 self.owners.unregister(self.owner);
5262 }
5263}
5264
5265#[cfg(napi_runtime_os_threads)]
5266struct OwnerLaneReservation<'a> {
5267 owners: &'a ActiveBlockingOwners,
5268 owner: BlockingOwnerToken,
5269 reservation: u64,
5270}
5271
5272#[cfg(napi_runtime_os_threads)]
5273impl<'a> OwnerLaneReservation<'a> {
5274 fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5275 let reservation = owners.reserve(owner)?;
5276 Some(Self {
5277 owners,
5278 owner,
5279 reservation,
5280 })
5281 }
5282}
5283
5284#[cfg(napi_runtime_os_threads)]
5285impl Drop for OwnerLaneReservation<'_> {
5286 fn drop(&mut self) {
5287 self.owners.release(self.owner, self.reservation);
5288 }
5289}
5290
5291#[cfg(napi_runtime_os_threads)]
5295type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5296
5297#[cfg(napi_runtime_os_threads)]
5298struct QueuedBlocking {
5299 id: BlockingJobId,
5300 run: BlockingClosure,
5301}
5302
5303#[cfg(napi_runtime_os_threads)]
5304struct BlockingQueueNode {
5305 previous: Option<BlockingJobId>,
5306 next: Option<BlockingJobId>,
5307 run: Box<dyn FnOnce() + Send + 'static>,
5308}
5309
5310#[cfg(napi_runtime_os_threads)]
5311struct BlockingQueue {
5312 closed: bool,
5313 head: Option<BlockingJobId>,
5314 tail: Option<BlockingJobId>,
5315 jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5316}
5317
5318#[cfg(napi_runtime_os_threads)]
5319impl BlockingQueue {
5320 fn push(&mut self, job: QueuedBlocking) {
5321 let previous = self.tail;
5322 let replaced = self.jobs.insert(
5323 job.id,
5324 BlockingQueueNode {
5325 previous,
5326 next: None,
5327 run: job.run,
5328 },
5329 );
5330 debug_assert!(replaced.is_none());
5331 if let Some(previous) = previous {
5332 self
5333 .jobs
5334 .get_mut(&previous)
5335 .expect("blocking queue tail must be indexed")
5336 .next = Some(job.id);
5337 } else {
5338 self.head = Some(job.id);
5339 }
5340 self.tail = Some(job.id);
5341 }
5342
5343 fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5344 self.head.and_then(|id| self.remove(id))
5345 }
5346
5347 fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5348 let node = self.jobs.remove(&id)?;
5349 if let Some(previous) = node.previous {
5350 self
5351 .jobs
5352 .get_mut(&previous)
5353 .expect("blocking queue predecessor must be indexed")
5354 .next = node.next;
5355 } else {
5356 self.head = node.next;
5357 }
5358 if let Some(next) = node.next {
5359 self
5360 .jobs
5361 .get_mut(&next)
5362 .expect("blocking queue successor must be indexed")
5363 .previous = node.previous;
5364 } else {
5365 self.tail = node.previous;
5366 }
5367 Some(node.run)
5368 }
5369
5370 fn is_empty(&self) -> bool {
5371 debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5372 debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5373 self.jobs.is_empty()
5374 }
5375
5376 fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5377 self.head = None;
5378 self.tail = None;
5379 std::mem::take(&mut self.jobs)
5380 .into_values()
5381 .map(|node| node.run)
5382 .collect()
5383 }
5384}
5385
5386#[cfg(napi_runtime_os_threads)]
5387struct WorkerLifecycle {
5388 remaining: Mutex<usize>,
5389 handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5390}
5391
5392#[cfg(napi_runtime_os_threads)]
5393impl WorkerLifecycle {
5394 fn new(worker_threads: usize) -> Arc<Self> {
5395 Arc::new(Self {
5396 remaining: Mutex::new(worker_threads),
5397 handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5398 })
5399 }
5400
5401 fn retain(&self, handle: std::thread::JoinHandle<()>) {
5402 self
5403 .handles
5404 .lock()
5405 .unwrap_or_else(std::sync::PoisonError::into_inner)
5406 .as_mut()
5407 .expect("runtime worker handles already joined")
5408 .push(handle);
5409 }
5410
5411 fn worker_exited(&self) {
5412 let mut remaining = self
5413 .remaining
5414 .lock()
5415 .unwrap_or_else(std::sync::PoisonError::into_inner);
5416 *remaining = remaining
5417 .checked_sub(1)
5418 .expect("runtime worker exit count underflow");
5419 }
5420
5421 fn wait_for_all_workers(&self) {
5422 let handles = self
5423 .handles
5424 .lock()
5425 .unwrap_or_else(std::sync::PoisonError::into_inner)
5426 .take()
5427 .expect("runtime worker handles already joined");
5428 for handle in handles {
5429 if let Err(payload) = handle.join() {
5430 discard_panic_payload(payload);
5431 }
5432 }
5433 }
5434
5435 #[cfg(test)]
5436 fn remaining(&self) -> usize {
5437 *self
5438 .remaining
5439 .lock()
5440 .unwrap_or_else(std::sync::PoisonError::into_inner)
5441 }
5442}
5443
5444#[cfg(napi_runtime_os_threads)]
5445struct MultiThreadExecutor {
5446 generation: u64,
5447 stop: Arc<GenerationStop>,
5448 id: u64,
5451 pool: ThreadPool,
5452 injector: Injector<Runnable>,
5459 blocking_queue: Mutex<BlockingQueue>,
5460 active_drainers: AtomicUsize,
5461 active_blocking: AtomicUsize,
5462 scheduler_idle_lock: Mutex<usize>,
5470 scheduler_idle: Condvar,
5471 worker_lifecycle: Arc<WorkerLifecycle>,
5472 parked_drivers: ParkedDrivers,
5480 active_blocking_owners: ActiveBlockingOwners,
5481 #[cfg(test)]
5482 owner_lending_attempts: AtomicUsize,
5483 #[cfg(test)]
5493 drain_linger_registrations: AtomicUsize,
5494 #[cfg(test)]
5495 drain_linger_expiries: AtomicUsize,
5496 max_drainers: usize,
5497 max_blocking: usize,
5498 park_deadline: Option<Duration>,
5505 drain_linger: Option<Duration>,
5510 deadlock_state: Arc<MultiThreadDeadlockState>,
5514 timers: TimerHeap,
5517 metrics: Arc<RuntimeMetrics>,
5518}
5519
5520#[cfg(napi_runtime_os_threads)]
5521impl MultiThreadExecutor {
5522 const RUNNABLE_BUDGET: usize = 64;
5525 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5528
5529 #[cfg(test)]
5530 fn new(
5531 options: &RuntimeOptions,
5532 metrics: Arc<RuntimeMetrics>,
5533 ) -> Result<Self, RuntimeConfigError> {
5534 Self::new_for_generation(
5535 options,
5536 metrics,
5537 u64::MAX,
5538 Arc::new(GenerationStop::default()),
5539 )
5540 }
5541
5542 fn new_for_generation(
5543 options: &RuntimeOptions,
5544 metrics: Arc<RuntimeMetrics>,
5545 generation: u64,
5546 stop: Arc<GenerationStop>,
5547 ) -> Result<Self, RuntimeConfigError> {
5548 let id = next_unique_id(
5549 &NEXT_RUNTIME_EXECUTOR_ID,
5550 "async runtime executor id space exhausted",
5551 );
5552 let thread_name_prefix = options.thread_name_prefix.clone();
5553 let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5554 options.park_deadline.is_some(),
5555 ));
5556 let timers = TimerHeap::new(
5557 generation,
5558 id,
5559 format!("{thread_name_prefix}-timer"),
5560 Arc::clone(&deadlock_state),
5561 );
5562 let pool_threads = options.worker_threads;
5566 let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5567 let exit_lifecycle = Arc::clone(&worker_lifecycle);
5568 let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5569 let pool = ThreadPoolBuilder::new()
5570 .num_threads(pool_threads)
5571 .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5572 .start_handler(move |_| {
5573 ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5574 })
5575 .exit_handler(move |_| {
5576 IN_SCHEDULER_DRIVER.with(|driver| {
5577 if driver.get().is_some_and(|role| role.executor_id == id) {
5578 driver.set(None);
5579 }
5580 });
5581 exit_lifecycle.worker_exited();
5587 })
5588 .spawn_handler(move |thread| {
5589 let mut builder = std::thread::Builder::new();
5590 if let Some(name) = thread.name() {
5591 builder = builder.name(name.to_string());
5592 }
5593 if let Some(stack_size) = thread.stack_size() {
5594 builder = builder.stack_size(stack_size);
5595 }
5596 let handle = builder.spawn(move || thread.run())?;
5597 spawn_lifecycle.retain(handle);
5598 Ok(())
5599 })
5600 .build()
5601 .map_err(|error| {
5602 worker_lifecycle.wait_for_all_workers();
5603 RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5604 })?;
5605 Ok(Self {
5606 generation,
5607 stop,
5608 id,
5609 pool,
5610 injector: Injector::new(),
5611 blocking_queue: Mutex::new(BlockingQueue {
5612 closed: false,
5613 head: None,
5614 tail: None,
5615 jobs: FxHashMap::default(),
5616 }),
5617 active_drainers: AtomicUsize::new(0),
5618 active_blocking: AtomicUsize::new(0),
5619 scheduler_idle_lock: Mutex::new(0),
5620 scheduler_idle: Condvar::new(),
5621 worker_lifecycle,
5622 parked_drivers: ParkedDrivers::default(),
5623 active_blocking_owners: ActiveBlockingOwners::default(),
5624 #[cfg(test)]
5625 owner_lending_attempts: AtomicUsize::new(0),
5626 #[cfg(test)]
5627 drain_linger_registrations: AtomicUsize::new(0),
5628 #[cfg(test)]
5629 drain_linger_expiries: AtomicUsize::new(0),
5630 max_drainers: pool_threads,
5631 max_blocking: options.max_blocking_tasks,
5632 park_deadline: options.park_deadline,
5633 drain_linger: {
5634 let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5638 (!budget.is_zero()).then_some(budget)
5639 },
5640 deadlock_state,
5641 timers,
5642 metrics,
5643 })
5644 }
5645
5646 fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5647 self.deadlock_state.begin_admission()
5648 }
5649
5650 fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5651 self.deadlock_state.try_begin_verdict()
5652 }
5653
5654 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5655 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5657 self.parked_drivers.wake_one_owner_dependency(owner)
5658 }
5659
5660 fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5661 let owner = reservation.owner;
5662 let _publication = self.deadlock_state.owner_handoff_publication_guard();
5666 drop(reservation);
5667 self.parked_drivers.wake_one_owner_dependency(owner)
5668 }
5669
5670 fn schedule(self: &Arc<Self>, runnable: Runnable) {
5671 let mut admission = self.begin_deadlock_admission();
5672 self.metrics.runnable_scheduled();
5675 if IN_SCHEDULER_DRIVER
5695 .with(std::cell::Cell::get)
5696 .is_some_and(|role| role.executor_id == self.id)
5697 && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5698 {
5699 match LIFO_SLOT.with(std::cell::Cell::take) {
5700 None => {
5701 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5702 if let Some(admission) = admission.as_mut() {
5703 admission.mark_published();
5704 }
5705 return;
5706 }
5707 Some((id, displaced)) if id == self.id => {
5711 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5712 self.push_to_queue_and_wake(displaced);
5713 if let Some(admission) = admission.as_mut() {
5714 admission.mark_published();
5715 }
5716 return;
5717 }
5718 Some(foreign) => {
5725 LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5726 }
5727 }
5728 }
5729 self.push_to_queue_and_wake(runnable);
5730 if let Some(admission) = admission.as_mut() {
5731 admission.mark_published();
5732 }
5733 }
5734
5735 fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5739 self.injector.push(runnable);
5740 std::sync::atomic::fence(Ordering::SeqCst);
5749 self.wake_for_new_work();
5750 }
5751
5752 fn wake_for_new_work(self: &Arc<Self>) {
5761 if !self.parked_drivers.wake_one() {
5762 self.ensure_drainer();
5763 }
5764 }
5765
5766 fn wake_for_blocking_work(self: &Arc<Self>) {
5767 if !self.parked_drivers.wake_one_blocking() {
5770 self.ensure_drainer();
5771 }
5772 }
5773
5774 #[cfg(test)]
5775 fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5776 where
5777 F: FnOnce() -> T + Send + 'static,
5778 T: Send + 'static,
5779 {
5780 self.schedule_blocking_result(move || catch_unwind_join_error(function))
5781 }
5782
5783 fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5784 where
5785 F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5786 T: Send + 'static,
5787 {
5788 let mut admission = self.begin_deadlock_admission();
5789 let (sender, receiver) = oneshot::channel();
5790 let generation = self.generation;
5791 let dependency = BlockingDependency {
5792 job: BlockingJobId(next_unique_id(
5793 &NEXT_BLOCKING_JOB_ID,
5794 "blocking job id space exhausted",
5795 )),
5796 owner: BLOCKING_OWNER
5797 .with(std::cell::Cell::get)
5798 .filter(|owner| owner.executor_id == self.id),
5799 };
5800 self.metrics.blocking_scheduled();
5805 let queued = QueuedBlocking {
5806 id: dependency.job,
5807 run: Box::new(move || {
5808 let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5809 let _ = sender.send(result);
5810 }),
5811 };
5812 let rejected = {
5813 let mut queue = self
5814 .blocking_queue
5815 .lock()
5816 .unwrap_or_else(std::sync::PoisonError::into_inner);
5817 if queue.closed {
5818 Some(queued)
5819 } else {
5820 queue.push(queued);
5821 None
5822 }
5823 };
5824 if let Some(rejected) = rejected {
5825 let _generation = RuntimeGenerationGuard::enter(self.generation);
5830 let _ = catch_unwind_contained(|| drop(rejected));
5831 } else {
5832 self.wake_for_blocking_work();
5834 if let Some(admission) = admission.as_mut() {
5835 admission.mark_published();
5836 }
5837 }
5838 JoinHandle(JoinHandleInner::Blocking {
5839 receiver,
5840 dependency,
5841 published: PublishedBlockingDependencies::default(),
5842 })
5843 }
5844
5845 fn ensure_drainer(self: &Arc<Self>) {
5846 loop {
5847 let active = self.active_drainers.load(Ordering::Acquire);
5848 if active >= self.max_drainers {
5849 return;
5850 }
5851 if self
5852 .active_drainers
5853 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5854 .is_ok()
5855 {
5856 let executor = Arc::clone(self);
5857 self.pool.spawn_fifo(move || executor.drain());
5858 return;
5859 }
5860 }
5861 }
5862
5863 fn drain(self: Arc<Self>) {
5864 let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5870 let _on_pool = OnPoolWorkerGuard::enter(self.id);
5873 let _slot_backstop = LifoSlotFlushGuard(&self);
5879
5880 let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5881 let mut linger: Option<DrainLinger> = None;
5884 let mut idle_deadline: Option<Instant> = None;
5890 let mut frame_deadline: Option<Instant> = None;
5896
5897 'frame: loop {
5898 let mut runnable_streak = 0usize;
5899 for _ in 0..Self::RUNNABLE_BUDGET {
5900 if self.run_one_fair(&mut runnable_streak) {
5901 idle_deadline = None;
5902 continue;
5903 }
5904 self.flush_lifo_slot();
5912 let Some(budget) = linger_budget else {
5934 break 'frame;
5935 };
5936 if self.stop.is_stopping() {
5937 break 'frame;
5938 }
5939 let now = Instant::now();
5940 let frame = *frame_deadline.get_or_insert_with(|| {
5946 now
5947 + budget
5948 .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
5949 .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
5950 });
5951 let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
5952 if now >= deadline {
5953 #[cfg(test)]
5960 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5961 break 'frame;
5962 }
5963 let state = match &linger {
5964 Some(state) => state,
5965 None => linger.insert(DrainLinger::new(&self)),
5966 };
5967 self
5968 .parked_drivers
5969 .register_with_role(&state.parker, true, None);
5970 #[cfg(test)]
5971 self
5972 .drain_linger_registrations
5973 .fetch_add(1, Ordering::SeqCst);
5974 if self.has_queued_work_for_role(true) {
5975 self.parked_drivers.deregister(&state.parker);
5976 continue 'frame;
5977 }
5978 if self.stop.is_stopping() {
5983 self.parked_drivers.deregister(&state.parker);
5984 break 'frame;
5985 }
5986 let woken = state.parker.park_timeout(deadline - now);
5987 self.parked_drivers.deregister(&state.parker);
5988 if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
5989 continue 'frame;
5990 }
5991 #[cfg(test)]
5996 self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5997 break 'frame;
5998 }
5999 break 'frame;
6004 }
6005
6006 self.flush_lifo_slot();
6009 self.finish_draining();
6010 }
6011
6012 fn pop_lifo_slot(&self) -> Option<Runnable> {
6016 LIFO_SLOT.with(|slot| match slot.take() {
6017 Some((id, runnable)) if id == self.id => Some(runnable),
6018 other => {
6019 slot.set(other);
6020 None
6021 }
6022 })
6023 }
6024
6025 fn flush_lifo_slot(self: &Arc<Self>) {
6032 let mut admission = self.begin_deadlock_admission();
6033 if let Some(runnable) = self.pop_lifo_slot() {
6034 self.push_to_queue_and_wake(runnable);
6035 if let Some(admission) = admission.as_mut() {
6036 admission.mark_published();
6037 }
6038 }
6039 }
6040
6041 fn fresh_owner_token(&self) -> BlockingOwnerToken {
6045 BlockingOwnerToken {
6046 executor_id: self.id,
6047 frame: next_unique_id(
6048 &NEXT_BLOCKING_FRAME,
6049 "blocking owner frame id space exhausted",
6050 ),
6051 }
6052 }
6053
6054 fn finish_draining(self: &Arc<Self>) {
6055 self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6065 let has_runnable = self.has_queued_runnable();
6066 let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6067 && !self
6068 .blocking_queue
6069 .lock()
6070 .unwrap_or_else(std::sync::PoisonError::into_inner)
6071 .is_empty();
6072 if has_runnable || has_blocking {
6073 self.ensure_drainer();
6074 }
6075 let idle = self
6081 .scheduler_idle_lock
6082 .lock()
6083 .unwrap_or_else(std::sync::PoisonError::into_inner);
6084 if *idle > 0 {
6085 self.scheduler_idle.notify_all();
6086 }
6087 }
6088
6089 fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6090 if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6091 self.cooperative_block_on(future)
6099 } else {
6100 self.parking_block_on(future)
6104 }
6105 }
6106
6107 fn parking_block_on(
6108 self: &Arc<Self>,
6109 mut future: Pin<&mut dyn Future<Output = ()>>,
6110 ) -> BlockOnOutcome {
6111 let parker = Arc::new(DriverParker::default());
6112 let _stop_registration = self.stop.register(&parker);
6113 let waker = Waker::from(Arc::clone(&parker));
6114 let mut cx = Context::from_waker(&waker);
6115 loop {
6116 if self.stop.is_stopping() {
6117 return BlockOnOutcome::Stopped;
6118 }
6119 if future.as_mut().poll(&mut cx).is_ready() {
6120 return BlockOnOutcome::Completed;
6121 }
6122 if self.stop.is_stopping() {
6123 return BlockOnOutcome::Stopped;
6124 }
6125 parker.park();
6126 }
6127 }
6128
6129 fn run_runnable(
6130 self: &Arc<Self>,
6131 runnable: Runnable,
6132 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6133 ) {
6134 let _non_owner = BlockingOwnerGuard::enter(None);
6137 let _active = self.metrics.runnable_started();
6138 #[cfg(test)]
6139 run_multi_thread_work_started_test_hook();
6140 drop(admission);
6144 let _ = catch_unwind_contained(|| runnable.run());
6145 }
6146
6147 fn cancel_runnable(
6148 &self,
6149 runnable: Runnable,
6150 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6151 ) {
6152 self.metrics.runnable_cancelled();
6153 drop(admission);
6156 let _generation = RuntimeGenerationGuard::enter(self.generation);
6157 let _ = catch_unwind_contained(|| drop(runnable));
6158 }
6159
6160 fn finish_runnable_claim(
6161 self: &Arc<Self>,
6162 claim: RunnableClaim,
6163 admission: Option<MultiThreadDeadlockAdmissionGuard>,
6164 ) -> bool {
6165 match claim {
6166 RunnableClaim::Empty => {
6167 drop(admission);
6168 false
6169 }
6170 RunnableClaim::Run(runnable) => {
6171 #[cfg(test)]
6172 run_multi_thread_work_claimed_test_hook();
6173 self.run_runnable(runnable, admission);
6174 true
6175 }
6176 RunnableClaim::Cancel(runnable) => {
6177 #[cfg(test)]
6178 run_multi_thread_work_claimed_test_hook();
6179 self.cancel_runnable(runnable, admission);
6180 true
6181 }
6182 }
6183 }
6184
6185 fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6186 match runnable {
6187 Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6188 Some(runnable) => RunnableClaim::Run(runnable),
6189 None => RunnableClaim::Empty,
6190 }
6191 }
6192
6193 fn steal_one(&self) -> Option<Runnable> {
6197 loop {
6198 match self.injector.steal() {
6199 Steal::Success(runnable) => return Some(runnable),
6200 Steal::Empty => return None,
6201 Steal::Retry => continue,
6202 }
6203 }
6204 }
6205
6206 #[cfg(test)]
6213 fn push_fifo_raw(&self, runnable: Runnable) {
6214 self.injector.push(runnable);
6215 std::sync::atomic::fence(Ordering::SeqCst);
6216 }
6217
6218 fn claim_fifo_runnable(&self) -> RunnableClaim {
6219 let stop_publication = self.stop.runnable_claim_guard();
6220 let runnable = self.steal_one();
6221 let stopping = stop_publication.is_stopping();
6222 drop(stop_publication);
6223 Self::runnable_claim(stopping, runnable)
6224 }
6225
6226 fn claim_runnable(&self) -> RunnableClaim {
6227 let stop_publication = self.stop.runnable_claim_guard();
6228 let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6229 let stopping = stop_publication.is_stopping();
6230 drop(stop_publication);
6231 Self::runnable_claim(stopping, runnable)
6232 }
6233
6234 fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6235 let admission = self.begin_deadlock_admission();
6236 let claim = self.claim_fifo_runnable();
6237 self.finish_runnable_claim(claim, admission)
6238 }
6239
6240 fn run_one_runnable(self: &Arc<Self>) -> bool {
6242 let admission = self.begin_deadlock_admission();
6243 let claim = self.claim_runnable();
6246 self.finish_runnable_claim(claim, admission)
6247 }
6248
6249 fn run_one_blocking(self: &Arc<Self>) -> bool {
6250 if let Some((blocking, admission)) = self.take_blocking() {
6251 let _generation = RuntimeGenerationGuard::enter(self.generation);
6252 {
6253 let _task = self.metrics.blocking_started(true);
6254 #[cfg(test)]
6255 run_multi_thread_work_started_test_hook();
6256 drop(admission);
6259 let owner = self.fresh_owner_token();
6260 let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6261 let _owner = BlockingOwnerGuard::enter(Some(owner));
6262 let _ = catch_unwind_contained(blocking);
6267 }
6268 self.active_blocking.fetch_sub(1, Ordering::Release);
6269 return true;
6270 }
6271 false
6272 }
6273
6274 fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6278 self.run_one_fair_with_exact(runnable_streak, None)
6279 }
6280
6281 fn run_one_fair_with_exact(
6285 self: &Arc<Self>,
6286 runnable_streak: &mut usize,
6287 exact_dependency: Option<&TaskDependency>,
6288 ) -> bool {
6289 let mut exact_attempted = false;
6290 if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6291 *runnable_streak = 0;
6292 if let Some(dependency) = exact_dependency {
6293 exact_attempted = true;
6294 if self.try_owned_blocking_over_cap(dependency) {
6295 return true;
6296 }
6297 }
6298 if self.run_one_blocking() {
6299 return true;
6300 }
6301 }
6302 if self.run_one_runnable() {
6303 *runnable_streak += 1;
6304 return true;
6305 }
6306 if !exact_attempted
6307 && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6308 {
6309 *runnable_streak = 0;
6310 return true;
6311 }
6312 if self.run_one_blocking() {
6313 *runnable_streak = 0;
6314 return true;
6315 }
6316 false
6317 }
6318
6319 #[cfg(test)]
6320 fn run_one(self: &Arc<Self>) -> bool {
6321 let mut runnable_streak = 0;
6322 self.run_one_fair(&mut runnable_streak)
6323 }
6324
6325 fn run_owned_blocking_over_cap(
6335 self: &Arc<Self>,
6336 dependency: &TaskDependency,
6337 ) -> OwnedBlockingOutcome {
6338 #[cfg(test)]
6339 self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6340
6341 let Some(ambient_owner) = BLOCKING_OWNER
6342 .with(std::cell::Cell::get)
6343 .filter(|owner| owner.executor_id == self.id)
6344 else {
6345 return OwnedBlockingOutcome::Ineligible;
6346 };
6347 let Some(current) = dependency.select_for_owner(ambient_owner) else {
6348 return OwnedBlockingOutcome::Ineligible;
6349 };
6350 let admission = self.begin_deadlock_admission();
6351 let owner = current
6352 .publication
6353 .dependency
6354 .owner
6355 .expect("selected dependency must carry its owner");
6356 let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6357 else {
6358 return OwnedBlockingOutcome::OwnerBusy(owner);
6359 };
6360 #[cfg(test)]
6361 run_before_owned_blocking_queue_claim_test_hook();
6362 let job = {
6363 let mut queue = self
6364 .blocking_queue
6365 .lock()
6366 .unwrap_or_else(std::sync::PoisonError::into_inner);
6367 if queue.closed || self.stop.is_stopping() {
6371 None
6372 } else if dependency.try_claim(¤t) {
6376 queue.remove(current.publication.dependency.job)
6377 } else {
6378 None
6379 }
6380 };
6381 let ran = if let Some(job) = job {
6382 #[cfg(test)]
6383 run_multi_thread_work_claimed_test_hook();
6384 let _generation = RuntimeGenerationGuard::enter(self.generation);
6385 {
6386 let _task = self.metrics.blocking_started(false);
6387 #[cfg(test)]
6388 run_multi_thread_work_started_test_hook();
6389 drop(admission);
6392 let nested_owner = self.fresh_owner_token();
6393 let _active_owner =
6394 ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6395 let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6396 let _ = catch_unwind_contained(job);
6397 }
6398 true
6399 } else {
6400 drop(admission);
6401 false
6402 };
6403 self.release_owner_lane_and_wake(reservation);
6406 OwnedBlockingOutcome::ReservationReleased { owner, ran }
6407 }
6408
6409 fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6410 matches!(
6411 self.run_owned_blocking_over_cap(dependency),
6412 OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6413 )
6414 }
6415
6416 fn service_owner_handoff(
6417 self: &Arc<Self>,
6418 parker: &DriverParker,
6419 dependency: &TaskDependency,
6420 can_run_blocking: bool,
6421 ) -> Option<bool> {
6422 let handoff_owner = parker.take_owner_handoff()?;
6423 if !can_run_blocking {
6424 self.wake_one_owner_dependency(handoff_owner);
6425 return Some(false);
6426 }
6427 match self.run_owned_blocking_over_cap(dependency) {
6428 OwnedBlockingOutcome::Ineligible => {
6429 self.wake_one_owner_dependency(handoff_owner);
6430 Some(false)
6431 }
6432 OwnedBlockingOutcome::OwnerBusy(owner) => {
6433 if owner != handoff_owner {
6434 self.wake_one_owner_dependency(handoff_owner);
6435 }
6436 Some(false)
6437 }
6438 OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6439 if owner != handoff_owner {
6440 self.wake_one_owner_dependency(handoff_owner);
6441 }
6442 Some(ran)
6443 }
6444 }
6445 }
6446
6447 fn forward_owner_handoff(&self, parker: &DriverParker) {
6448 if let Some(owner) = parker.take_owner_handoff() {
6449 self.wake_one_owner_dependency(owner);
6450 }
6451 }
6452
6453 fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6454 let Some(ambient_owner) = BLOCKING_OWNER
6455 .with(std::cell::Cell::get)
6456 .filter(|owner| owner.executor_id == self.id)
6457 else {
6458 return false;
6459 };
6460 self.active_blocking_owners.is_available(ambient_owner)
6461 && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6462 }
6463
6464 fn has_exact_owner_work_for_role(
6465 &self,
6466 parker: &DriverParker,
6467 dependency: &TaskDependency,
6468 can_run_blocking: bool,
6469 ) -> bool {
6470 parker.has_owner_handoff()
6471 || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6472 }
6473
6474 fn cooperative_block_on(
6483 self: &Arc<Self>,
6484 mut future: Pin<&mut dyn Future<Output = ()>>,
6485 ) -> BlockOnOutcome {
6486 let parker = Arc::new(DriverParker::default());
6491 let _stop_registration = self.stop.register(&parker);
6492 let dependency = BlockOnDependencyGuard::enter(self.generation);
6493 let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6494 let can_run_blocking =
6495 driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6496 let _exit = CooperativeDriverExitGuard {
6497 executor: self,
6498 parker: &parker,
6499 can_run_blocking,
6500 };
6501 let waker = std::task::Waker::from(Arc::clone(&parker));
6506 let mut cx = Context::from_waker(&waker);
6507 let mut runnable_streak = 0usize;
6515 let mut work_streak = 0usize;
6516 let mut force_fifo = false;
6517 loop {
6518 if self.stop.is_stopping() {
6519 return BlockOnOutcome::Stopped;
6520 }
6521 dependency.clear();
6522 if future.as_mut().poll(&mut cx).is_ready() {
6523 return BlockOnOutcome::Completed;
6524 }
6525 if self.stop.is_stopping() {
6526 return BlockOnOutcome::Stopped;
6527 }
6528 if self
6529 .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6530 .is_some_and(|ran| ran)
6531 {
6532 continue;
6533 }
6534 if force_fifo {
6535 force_fifo = false;
6536 if self.run_one_fifo_runnable() {
6537 runnable_streak += 1;
6538 work_streak += 1;
6539 continue;
6540 }
6541 }
6542 let ran_work = if can_run_blocking {
6543 self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6544 } else if self.run_one_runnable() {
6545 runnable_streak += 1;
6546 true
6547 } else {
6548 false
6549 };
6550 if ran_work {
6551 work_streak += 1;
6552 if work_streak >= Self::RUNNABLE_BUDGET {
6553 self.flush_lifo_slot();
6558 work_streak = 0;
6559 force_fifo = true;
6560 }
6561 continue;
6562 }
6563 runnable_streak = 0;
6564 work_streak = 0;
6565 self.flush_lifo_slot();
6570 self.parked_drivers.register_with_role(
6574 &parker,
6575 can_run_blocking,
6576 Some(Arc::clone(&dependency.dependency)),
6577 );
6578 if self.has_queued_work_for_role(can_run_blocking)
6579 || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6580 {
6581 self.parked_drivers.deregister(&parker);
6582 continue;
6583 }
6584 if self.stop.is_stopping() {
6585 return BlockOnOutcome::Stopped;
6586 }
6587 if let Some(next) = self.next_timer_deadline() {
6602 let now = Instant::now();
6603 if next > now {
6604 parker.park_timeout(next - now);
6605 }
6606 self.parked_drivers.deregister(&parker);
6607 self.fire_due_timers();
6608 continue;
6609 }
6610 match self.park_deadline {
6611 None => parker.park(),
6612 Some(deadline) => {
6613 let deadlock_publications = self.deadlock_state.publications();
6619 match park_with_deadline(&parker, deadline, &self.metrics) {
6620 DeadlineParkOutcome::Woken => {}
6621 DeadlineParkOutcome::Expired(armed) => {
6622 #[cfg(test)]
6623 run_deadline_expiry_test_hook();
6624 self.parked_drivers.deregister(&parker);
6661 if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6662 continue;
6663 }
6664 #[cfg(test)]
6667 run_deadline_verdict_test_hook();
6668 if self.metrics.progress_fingerprint() != armed {
6669 continue;
6670 }
6671 if self.next_timer_deadline().is_some() {
6680 continue;
6681 }
6682 #[cfg(test)]
6685 run_deadline_final_verdict_test_hook();
6686 let Some(verdict) = self.try_begin_deadlock_verdict() else {
6687 continue;
6688 };
6689 #[cfg(test)]
6690 run_deadline_gated_verdict_test_hook();
6691 let stop_publication = self.stop.publication_guard();
6696 #[cfg(test)]
6697 run_deadline_synchronized_stop_test_hook();
6698 if stop_publication.is_stopping() {
6699 return BlockOnOutcome::Stopped;
6700 }
6701 let owner_handoff_publication = self
6702 .deadlock_state
6703 .owner_handoff_publication_guard()
6704 .expect("a deadline verdict requires exact-owner publication");
6705 #[cfg(test)]
6706 run_deadline_exact_owner_verdict_test_hook(&parker);
6707 if self.has_exact_owner_work_for_role(
6715 &parker,
6716 &dependency.dependency,
6717 can_run_blocking,
6718 ) || parker.consume_permit()
6719 || self.has_queued_work_for_role(can_run_blocking)
6720 || self.deadlock_state.publications() != deadlock_publications
6721 || self.metrics.progress_fingerprint() != armed
6722 || self.next_timer_deadline().is_some()
6723 {
6724 continue;
6725 }
6726 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6727 drop(owner_handoff_publication);
6732 drop(stop_publication);
6733 drop(verdict);
6734 std::panic::panic_any(diagnostic);
6735 }
6736 }
6737 }
6738 }
6739 self.parked_drivers.deregister(&parker);
6742 }
6743 }
6744
6745 fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6751 if self.has_queued_runnable() {
6752 return true;
6753 }
6754 can_run_blocking
6755 && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6756 && !self
6757 .blocking_queue
6758 .lock()
6759 .unwrap_or_else(std::sync::PoisonError::into_inner)
6760 .is_empty()
6761 }
6762
6763 fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6764 if self.has_queued_runnable() {
6765 self.wake_for_new_work();
6766 }
6767 if self.stop.is_stopping() {
6771 return;
6772 }
6773 if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6774 && !self
6775 .blocking_queue
6776 .lock()
6777 .unwrap_or_else(std::sync::PoisonError::into_inner)
6778 .is_empty()
6779 {
6780 let consumed = can_run_blocking && self.run_one_blocking();
6788 let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6789 && !self
6790 .blocking_queue
6791 .lock()
6792 .unwrap_or_else(std::sync::PoisonError::into_inner)
6793 .is_empty();
6794 if !self.stop.is_stopping() && (!consumed || has_residue) {
6795 self.wake_for_blocking_work();
6796 }
6797 }
6798 }
6799
6800 fn has_queued_runnable(&self) -> bool {
6801 std::sync::atomic::fence(Ordering::SeqCst);
6808 !self.injector.is_empty()
6809 }
6810
6811 fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6815 let admission = self.begin_deadlock_admission();
6816 loop {
6817 let active = self.active_blocking.load(Ordering::Acquire);
6818 if active >= self.max_blocking {
6819 return None;
6820 }
6821 let mut queue = self
6822 .blocking_queue
6823 .lock()
6824 .unwrap_or_else(std::sync::PoisonError::into_inner);
6825 if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6826 return None;
6827 }
6828 if self
6829 .active_blocking
6830 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6831 .is_ok()
6832 {
6833 let blocking = queue
6834 .pop_front()
6835 .expect("a non-empty blocking queue must have a FIFO head");
6836 drop(queue);
6837 #[cfg(test)]
6838 run_multi_thread_work_claimed_test_hook();
6839 return Some((blocking, admission));
6840 }
6841 }
6842 }
6843}
6844
6845#[cfg(napi_runtime_os_threads)]
6846struct MultiThreadDeadlockState {
6847 enabled: bool,
6848 verdict_closed: AtomicBool,
6849 admissions: AtomicUsize,
6850 publications: AtomicU64,
6851 owner_handoff_publication: Mutex<()>,
6852}
6853
6854#[cfg(napi_runtime_os_threads)]
6855impl MultiThreadDeadlockState {
6856 fn new(enabled: bool) -> Self {
6857 Self {
6858 enabled,
6859 verdict_closed: AtomicBool::new(false),
6860 admissions: AtomicUsize::new(0),
6861 publications: AtomicU64::new(0),
6862 owner_handoff_publication: Mutex::new(()),
6863 }
6864 }
6865
6866 fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6867 if !self.enabled {
6868 return None;
6869 }
6870 loop {
6871 while self.verdict_closed.load(Ordering::SeqCst) {
6872 #[cfg(test)]
6873 run_deadlock_closed_gate_admission_test_hook();
6874 std::hint::spin_loop();
6875 }
6876 self.admissions.fetch_add(1, Ordering::SeqCst);
6877 if !self.verdict_closed.load(Ordering::SeqCst) {
6878 return Some(MultiThreadDeadlockAdmissionGuard {
6879 state: Arc::clone(self),
6880 published: false,
6881 });
6882 }
6883 self.admissions.fetch_sub(1, Ordering::SeqCst);
6884 }
6885 }
6886
6887 fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6888 debug_assert!(
6889 self.enabled,
6890 "a deadline verdict requires an enabled admission state"
6891 );
6892 if self
6893 .verdict_closed
6894 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6895 .is_err()
6896 {
6897 return None;
6898 }
6899 if self.admissions.load(Ordering::SeqCst) != 0 {
6900 self.verdict_closed.store(false, Ordering::SeqCst);
6901 return None;
6902 }
6903 Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6904 }
6905
6906 fn publications(&self) -> u64 {
6907 self.publications.load(Ordering::SeqCst)
6908 }
6909
6910 fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
6911 self.enabled.then(|| {
6912 self
6913 .owner_handoff_publication
6914 .lock()
6915 .unwrap_or_else(std::sync::PoisonError::into_inner)
6916 })
6917 }
6918}
6919
6920#[cfg(napi_runtime_os_threads)]
6921struct MultiThreadDeadlockAdmissionGuard {
6922 state: Arc<MultiThreadDeadlockState>,
6923 published: bool,
6924}
6925
6926#[cfg(napi_runtime_os_threads)]
6927impl MultiThreadDeadlockAdmissionGuard {
6928 fn mark_published(&mut self) {
6929 self.published = true;
6930 }
6931}
6932
6933#[cfg(napi_runtime_os_threads)]
6934impl Drop for MultiThreadDeadlockAdmissionGuard {
6935 fn drop(&mut self) {
6936 if self.published {
6937 self.state.publications.fetch_add(1, Ordering::SeqCst);
6938 }
6939 self.state.admissions.fetch_sub(1, Ordering::SeqCst);
6942 }
6943}
6944
6945#[cfg(napi_runtime_os_threads)]
6946struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
6947
6948#[cfg(napi_runtime_os_threads)]
6949impl Drop for MultiThreadDeadlockVerdictGuard {
6950 fn drop(&mut self) {
6951 self.0.verdict_closed.store(false, Ordering::SeqCst);
6952 }
6953}
6954
6955#[cfg(napi_runtime_os_threads)]
6961struct CooperativeDriverExitGuard<'a> {
6962 executor: &'a Arc<MultiThreadExecutor>,
6963 parker: &'a Arc<DriverParker>,
6964 can_run_blocking: bool,
6965}
6966
6967#[cfg(napi_runtime_os_threads)]
6968impl Drop for CooperativeDriverExitGuard<'_> {
6969 fn drop(&mut self) {
6970 let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
6971 let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
6972 let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
6973 let _ = catch_unwind_contained(|| {
6974 self
6975 .executor
6976 .compensate_queued_work_wake(self.can_run_blocking);
6977 });
6978 }
6979}
6980
6981#[cfg(napi_runtime_os_threads)]
6987struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
6988
6989#[cfg(napi_runtime_os_threads)]
6990impl Drop for LifoSlotFlushGuard<'_> {
6991 fn drop(&mut self) {
6992 self.0.flush_lifo_slot();
6993 }
6994}
6995
6996#[cfg(napi_runtime_os_threads)]
7003struct DrainLinger {
7004 parker: Arc<DriverParker>,
7005 _stop_registration: GenerationStopGuard,
7006}
7007
7008#[cfg(napi_runtime_os_threads)]
7009impl DrainLinger {
7010 fn new(executor: &MultiThreadExecutor) -> Self {
7011 let parker = Arc::new(DriverParker::default());
7012 let stop_registration = executor.stop.register(&parker);
7013 Self {
7014 parker,
7015 _stop_registration: stop_registration,
7016 }
7017 }
7018}
7019
7020const PARKER_EMPTY: usize = 0;
7042const PARKER_NOTIFIED: usize = 1;
7044const PARKER_SLEEPING: usize = 2;
7046
7047#[derive(Default)]
7051struct DriverParker {
7052 state: AtomicUsize,
7053 lock: Mutex<()>,
7054 condvar: std::sync::Condvar,
7055 #[cfg(napi_runtime_os_threads)]
7056 owner_handoff_pending: AtomicBool,
7057 #[cfg(napi_runtime_os_threads)]
7058 owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7059}
7060
7061impl DriverParker {
7062 #[cfg(napi_runtime_os_threads)]
7063 fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7064 let mut pending = self
7065 .owner_handoff
7066 .lock()
7067 .unwrap_or_else(std::sync::PoisonError::into_inner);
7068 debug_assert!(pending.is_none());
7069 *pending = Some(owner);
7070 self.owner_handoff_pending.store(true, Ordering::Release);
7071 }
7072
7073 #[cfg(napi_runtime_os_threads)]
7074 fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7075 if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7076 return None;
7077 }
7078 self
7079 .owner_handoff
7080 .lock()
7081 .unwrap_or_else(std::sync::PoisonError::into_inner)
7082 .take()
7083 }
7084
7085 #[cfg(napi_runtime_os_threads)]
7086 fn has_owner_handoff(&self) -> bool {
7087 self.owner_handoff_pending.load(Ordering::Acquire)
7088 }
7089
7090 fn unpark(&self) {
7097 if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7098 drop(
7103 self
7104 .lock
7105 .lock()
7106 .unwrap_or_else(std::sync::PoisonError::into_inner),
7107 );
7108 self.condvar.notify_one();
7109 }
7110 }
7111
7112 fn consume_permit(&self) -> bool {
7117 self
7118 .state
7119 .compare_exchange(
7120 PARKER_NOTIFIED,
7121 PARKER_EMPTY,
7122 Ordering::SeqCst,
7123 Ordering::SeqCst,
7124 )
7125 .is_ok()
7126 }
7127
7128 fn park(&self) {
7132 if self.consume_permit() {
7134 return;
7135 }
7136 let mut guard = self
7137 .lock
7138 .lock()
7139 .unwrap_or_else(std::sync::PoisonError::into_inner);
7140 match self.state.compare_exchange(
7141 PARKER_EMPTY,
7142 PARKER_SLEEPING,
7143 Ordering::SeqCst,
7144 Ordering::SeqCst,
7145 ) {
7146 Ok(_) => {}
7147 Err(_) => {
7151 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7152 return;
7153 }
7154 }
7155 loop {
7156 guard = self
7157 .condvar
7158 .wait(guard)
7159 .unwrap_or_else(std::sync::PoisonError::into_inner);
7160 if self
7161 .state
7162 .compare_exchange(
7163 PARKER_NOTIFIED,
7164 PARKER_EMPTY,
7165 Ordering::SeqCst,
7166 Ordering::SeqCst,
7167 )
7168 .is_ok()
7169 {
7170 return;
7171 }
7172 }
7174 }
7175
7176 fn park_timeout(&self, timeout: Duration) -> bool {
7182 if self.consume_permit() {
7183 return true;
7184 }
7185 let mut guard = self
7186 .lock
7187 .lock()
7188 .unwrap_or_else(std::sync::PoisonError::into_inner);
7189 match self.state.compare_exchange(
7190 PARKER_EMPTY,
7191 PARKER_SLEEPING,
7192 Ordering::SeqCst,
7193 Ordering::SeqCst,
7194 ) {
7195 Ok(_) => {}
7196 Err(_) => {
7200 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7201 return true;
7202 }
7203 }
7204 let Some(deadline) = Instant::now().checked_add(timeout) else {
7205 loop {
7209 guard = self
7210 .condvar
7211 .wait(guard)
7212 .unwrap_or_else(std::sync::PoisonError::into_inner);
7213 if self
7214 .state
7215 .compare_exchange(
7216 PARKER_NOTIFIED,
7217 PARKER_EMPTY,
7218 Ordering::SeqCst,
7219 Ordering::SeqCst,
7220 )
7221 .is_ok()
7222 {
7223 return true;
7224 }
7225 }
7226 };
7227 loop {
7228 if self
7229 .state
7230 .compare_exchange(
7231 PARKER_NOTIFIED,
7232 PARKER_EMPTY,
7233 Ordering::SeqCst,
7234 Ordering::SeqCst,
7235 )
7236 .is_ok()
7237 {
7238 return true;
7239 }
7240 let now = Instant::now();
7241 if now >= deadline {
7242 return match self.state.compare_exchange(
7247 PARKER_SLEEPING,
7248 PARKER_EMPTY,
7249 Ordering::SeqCst,
7250 Ordering::SeqCst,
7251 ) {
7252 Ok(_) => false,
7253 Err(_) => {
7254 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7255 true
7256 }
7257 };
7258 }
7259 let (returned_guard, _timed_out) = self
7260 .condvar
7261 .wait_timeout(guard, deadline - now)
7262 .unwrap_or_else(std::sync::PoisonError::into_inner);
7263 guard = returned_guard;
7264 }
7267 }
7268}
7269
7270impl std::task::Wake for DriverParker {
7271 fn wake(self: Arc<Self>) {
7272 self.unpark();
7273 }
7274
7275 fn wake_by_ref(self: &Arc<Self>) {
7276 self.unpark();
7277 }
7278}
7279
7280enum DeadlineParkOutcome {
7282 Woken,
7284 Expired(ProgressFingerprint),
7293}
7294
7295fn park_with_deadline(
7304 parker: &DriverParker,
7305 deadline: Duration,
7306 metrics: &RuntimeMetrics,
7307) -> DeadlineParkOutcome {
7308 let mut armed = metrics.progress_fingerprint();
7309 loop {
7310 if parker.park_timeout(deadline) {
7311 return DeadlineParkOutcome::Woken;
7312 }
7313 let current = metrics.progress_fingerprint();
7314 if current != armed {
7315 armed = current;
7316 continue;
7317 }
7318 return DeadlineParkOutcome::Expired(armed);
7319 }
7320}
7321
7322#[cfg(all(test, napi_runtime_os_threads))]
7330thread_local! {
7331 static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7332 const { std::cell::RefCell::new(None) };
7333}
7334
7335#[cfg(all(test, napi_runtime_os_threads))]
7336fn run_deadline_expiry_test_hook() {
7337 if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7338 hook();
7339 }
7340}
7341
7342#[cfg(all(test, napi_runtime_os_threads))]
7349thread_local! {
7350 static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7351 const { std::cell::RefCell::new(None) };
7352}
7353
7354#[cfg(all(test, napi_runtime_os_threads))]
7355fn run_deadline_verdict_test_hook() {
7356 if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7357 hook();
7358 }
7359}
7360
7361#[cfg(all(test, napi_runtime_os_threads))]
7366thread_local! {
7367 static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7368 const { std::cell::RefCell::new(None) };
7369}
7370
7371#[cfg(all(test, napi_runtime_os_threads))]
7372fn run_deadline_final_verdict_test_hook() {
7373 if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7374 hook();
7375 }
7376}
7377
7378#[cfg(all(test, napi_runtime_os_threads))]
7383thread_local! {
7384 static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7385 const { std::cell::RefCell::new(None) };
7386}
7387
7388#[cfg(all(test, napi_runtime_os_threads))]
7389fn run_deadline_gated_verdict_test_hook() {
7390 if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7391 hook();
7392 }
7393}
7394
7395#[cfg(all(test, napi_runtime_os_threads))]
7399thread_local! {
7400 static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7401 const { std::cell::RefCell::new(None) };
7402}
7403
7404#[cfg(all(test, napi_runtime_os_threads))]
7405fn run_deadline_synchronized_stop_test_hook() {
7406 if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7407 hook();
7408 }
7409}
7410
7411#[cfg(all(test, napi_runtime_os_threads))]
7415type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7416
7417#[cfg(all(test, napi_runtime_os_threads))]
7418thread_local! {
7419 static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7420 std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7421 const { std::cell::RefCell::new(None) };
7422}
7423
7424#[cfg(all(test, napi_runtime_os_threads))]
7425fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7426 if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7427 hook(parker);
7428 }
7429}
7430
7431#[cfg(all(test, napi_runtime_os_threads))]
7435thread_local! {
7436 static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7437 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7438 const { std::cell::RefCell::new(None) };
7439}
7440
7441#[cfg(all(test, napi_runtime_os_threads))]
7442fn run_before_generation_stop_publication_lock_test_hook() {
7443 if let Some(hook) =
7444 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7445 {
7446 hook();
7447 }
7448}
7449
7450#[cfg(all(test, napi_runtime_os_threads))]
7454thread_local! {
7455 static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7456 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7457 const { std::cell::RefCell::new(None) };
7458}
7459
7460#[cfg(all(test, napi_runtime_os_threads))]
7461fn run_deadlock_closed_gate_admission_test_hook() {
7462 if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7463 {
7464 hook();
7465 }
7466}
7467
7468#[cfg(all(test, napi_runtime_os_threads))]
7472thread_local! {
7473 static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7474 const { std::cell::RefCell::new(None) };
7475}
7476
7477#[cfg(all(test, napi_runtime_os_threads))]
7478fn run_timer_registration_admission_test_hook() {
7479 if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7480 hook();
7481 }
7482}
7483
7484#[cfg(all(test, napi_runtime_os_threads))]
7485thread_local! {
7486 static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7487 const { std::cell::RefCell::new(None) };
7488}
7489
7490#[cfg(all(test, napi_runtime_os_threads))]
7491fn run_owner_handoff_selected_test_hook() {
7492 if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7493 hook();
7494 }
7495}
7496
7497#[cfg(napi_runtime_os_threads)]
7500#[derive(Default)]
7501struct ParkedDrivers {
7502 parked: Mutex<Vec<ParkedDriver>>,
7503 count: AtomicUsize,
7506}
7507
7508#[cfg(napi_runtime_os_threads)]
7509struct ParkedDriver {
7510 parker: Arc<DriverParker>,
7511 can_run_blocking: bool,
7512 dependency: Option<Arc<TaskDependency>>,
7513}
7514
7515#[cfg(napi_runtime_os_threads)]
7516impl ParkedDrivers {
7517 #[cfg(test)]
7520 fn register(&self, parker: &Arc<DriverParker>) {
7521 self.register_with_role(parker, true, None);
7522 }
7523
7524 fn register_with_role(
7525 &self,
7526 parker: &Arc<DriverParker>,
7527 can_run_blocking: bool,
7528 dependency: Option<Arc<TaskDependency>>,
7529 ) {
7530 let mut parked = self
7531 .parked
7532 .lock()
7533 .unwrap_or_else(std::sync::PoisonError::into_inner);
7534 parked.push(ParkedDriver {
7535 parker: Arc::clone(parker),
7536 can_run_blocking,
7537 dependency,
7538 });
7539 self.count.store(parked.len(), Ordering::SeqCst);
7540 }
7541
7542 fn deregister(&self, parker: &Arc<DriverParker>) {
7546 let removed = {
7547 let mut parked = self
7548 .parked
7549 .lock()
7550 .unwrap_or_else(std::sync::PoisonError::into_inner);
7551 let removed = parked
7552 .iter()
7553 .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7554 .map(|index| parked.swap_remove(index));
7555 self.count.store(parked.len(), Ordering::SeqCst);
7556 removed
7557 };
7558 drop(removed);
7559 }
7560
7561 fn wake_one(&self) -> bool {
7584 if self.count.load(Ordering::SeqCst) == 0 {
7585 return false;
7589 }
7590 let driver = {
7591 let mut parked = self
7592 .parked
7593 .lock()
7594 .unwrap_or_else(std::sync::PoisonError::into_inner);
7595 let driver = parked.pop();
7596 self.count.store(parked.len(), Ordering::SeqCst);
7597 driver
7598 };
7599 match driver {
7600 Some(driver) => {
7601 driver.parker.unpark();
7602 true
7603 }
7604 None => false,
7605 }
7606 }
7607
7608 fn wake_one_blocking(&self) -> bool {
7611 if self.count.load(Ordering::SeqCst) == 0 {
7612 return false;
7613 }
7614 let driver = {
7615 let mut parked = self
7616 .parked
7617 .lock()
7618 .unwrap_or_else(std::sync::PoisonError::into_inner);
7619 let driver = parked
7620 .iter()
7621 .rposition(|driver| driver.can_run_blocking)
7622 .map(|index| parked.swap_remove(index));
7623 self.count.store(parked.len(), Ordering::SeqCst);
7624 driver
7625 };
7626 match driver {
7627 Some(driver) => {
7628 driver.parker.unpark();
7629 true
7630 }
7631 None => false,
7632 }
7633 }
7634
7635 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7640 loop {
7641 if self.count.load(Ordering::SeqCst) == 0 {
7642 return false;
7643 }
7644 let candidates = {
7645 let parked = self
7646 .parked
7647 .lock()
7648 .unwrap_or_else(std::sync::PoisonError::into_inner);
7649 parked
7650 .iter()
7651 .rev()
7652 .filter_map(|driver| {
7653 if !driver.can_run_blocking {
7654 return None;
7655 }
7656 driver
7657 .dependency
7658 .as_ref()
7659 .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7660 })
7661 .collect::<Vec<_>>()
7662 };
7663 let selected = candidates.into_iter().find(|(_, dependency)| {
7664 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7665 });
7666 let Some((selected_parker, selected_dependency)) = selected else {
7667 return false;
7668 };
7669 let driver = {
7670 let mut parked = self
7671 .parked
7672 .lock()
7673 .unwrap_or_else(std::sync::PoisonError::into_inner);
7674 let driver = parked
7675 .iter()
7676 .rposition(|driver| {
7677 Arc::ptr_eq(&driver.parker, &selected_parker)
7678 && driver
7679 .dependency
7680 .as_ref()
7681 .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7682 })
7683 .map(|index| parked.swap_remove(index));
7684 if let Some(driver) = &driver {
7689 driver.parker.grant_owner_handoff(owner);
7690 }
7691 self.count.store(parked.len(), Ordering::SeqCst);
7692 driver
7693 };
7694 if let Some(driver) = driver {
7695 #[cfg(test)]
7696 run_owner_handoff_selected_test_hook();
7697 driver.parker.unpark();
7698 return true;
7699 }
7700 }
7701 }
7702
7703 fn wake_all(&self) {
7706 if self.count.load(Ordering::SeqCst) == 0 {
7707 return;
7708 }
7709 let drivers = {
7710 let mut parked = self
7711 .parked
7712 .lock()
7713 .unwrap_or_else(std::sync::PoisonError::into_inner);
7714 let drivers = parked.drain(..).collect::<Vec<_>>();
7715 self.count.store(0, Ordering::SeqCst);
7716 drivers
7717 };
7718 for driver in drivers {
7719 driver.parker.unpark();
7720 }
7721 }
7722}
7723
7724pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7769 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7803
7804 fn is_live(&self) -> bool {
7805 true
7806 }
7807
7808 fn on_swept(&self) {}
7811}
7812
7813#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7814pub struct CurrentThreadTaskDriverId(u64);
7815
7816type CurrentThreadTaskDriverSnapshot =
7817 ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7818
7819#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7820pub struct CurrentThreadTaskDelivery {
7821 driver: CurrentThreadTaskDriverId,
7822 capability: u64,
7823 dispatch: u64,
7824}
7825
7826impl CurrentThreadTaskDelivery {
7827 pub fn capability(self) -> u64 {
7829 self.capability
7830 }
7831}
7832
7833struct CurrentThreadTaskDeliveryState {
7834 delivery: CurrentThreadTaskDelivery,
7835 claimed: bool,
7836 accepted: bool,
7837}
7838
7839struct CurrentThreadTaskDriverEntry {
7840 driver: Arc<dyn CurrentThreadTaskDriver>,
7841 delivery: Option<CurrentThreadTaskDeliveryState>,
7842 pending: Option<u64>,
7843 previous: Option<CurrentThreadTaskDriverId>,
7844 next: Option<CurrentThreadTaskDriverId>,
7845}
7846
7847#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7848enum CurrentThreadDispatchPhase {
7849 #[default]
7850 Pending,
7851 Serviced,
7852 Cancelling,
7853}
7854
7855#[derive(Default)]
7856struct CurrentThreadDispatchState {
7857 phase: CurrentThreadDispatchPhase,
7858 epoch: u64,
7859 publications: usize,
7860 references: usize,
7861 accepted: bool,
7862 accepted_failure: bool,
7863}
7864
7865impl CurrentThreadDispatchState {
7866 fn is_serviced(&self) -> bool {
7867 self.phase == CurrentThreadDispatchPhase::Serviced
7868 }
7869
7870 fn is_cancelling(&self) -> bool {
7871 self.phase == CurrentThreadDispatchPhase::Cancelling
7872 }
7873
7874 fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7875 CurrentThreadTaskDispatchFailure {
7876 dispatch,
7877 epoch: self.epoch,
7878 }
7879 }
7880}
7881
7882#[derive(Default)]
7883struct CurrentThreadTaskDriverRegistryState {
7884 entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7885 oldest: Option<CurrentThreadTaskDriverId>,
7886 newest: Option<CurrentThreadTaskDriverId>,
7887 delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7888 dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7889}
7890
7891impl CurrentThreadTaskDriverRegistryState {
7892 fn insert_driver(
7893 &mut self,
7894 id: CurrentThreadTaskDriverId,
7895 driver: Arc<dyn CurrentThreadTaskDriver>,
7896 ) {
7897 let previous = self.newest;
7898 let replaced = self.entries.insert(
7899 id,
7900 CurrentThreadTaskDriverEntry {
7901 driver,
7902 delivery: None,
7903 pending: None,
7904 previous,
7905 next: None,
7906 },
7907 );
7908 assert!(
7909 replaced.is_none(),
7910 "CurrentThread task driver id must be unique"
7911 );
7912 if let Some(previous) = previous {
7913 self
7914 .entries
7915 .get_mut(&previous)
7916 .expect("the newest CurrentThread task driver must remain registered")
7917 .next = Some(id);
7918 } else {
7919 self.oldest = Some(id);
7920 }
7921 self.newest = Some(id);
7922 }
7923
7924 fn remove_driver(
7925 &mut self,
7926 id: CurrentThreadTaskDriverId,
7927 ) -> Option<CurrentThreadTaskDriverEntry> {
7928 let entry = self.entries.remove(&id)?;
7929 if let Some(previous) = entry.previous {
7930 self
7931 .entries
7932 .get_mut(&previous)
7933 .expect("the previous CurrentThread task driver must remain registered")
7934 .next = entry.next;
7935 } else {
7936 self.oldest = entry.next;
7937 }
7938 if let Some(next) = entry.next {
7939 self
7940 .entries
7941 .get_mut(&next)
7942 .expect("the next CurrentThread task driver must remain registered")
7943 .previous = entry.previous;
7944 } else {
7945 self.newest = entry.previous;
7946 }
7947 if let Some(delivery) = &entry.delivery {
7948 let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
7949 debug_assert_eq!(
7950 indexed,
7951 Some(id),
7952 "delivery capability index must match its driver"
7953 );
7954 }
7955 Some(entry)
7956 }
7957
7958 fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7959 let mut snapshot = Vec::with_capacity(self.entries.len());
7960 let mut current = self.oldest;
7961 while let Some(id) = current {
7962 let entry = self
7963 .entries
7964 .get(&id)
7965 .expect("CurrentThread task driver order must reference a live entry");
7966 snapshot.push((id, Arc::clone(&entry.driver)));
7967 current = entry.next;
7968 }
7969 debug_assert_eq!(
7970 snapshot.len(),
7971 self.entries.len(),
7972 "CurrentThread task driver order must contain every entry exactly once"
7973 );
7974 snapshot
7975 }
7976
7977 fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
7978 if self.entries.len() != expected.len() {
7979 return false;
7980 }
7981 let mut current = self.oldest;
7982 for expected in expected {
7983 if current != Some(*expected) {
7984 return false;
7985 }
7986 current = self
7987 .entries
7988 .get(expected)
7989 .expect("CurrentThread task driver order must reference a live entry")
7990 .next;
7991 }
7992 current.is_none()
7993 }
7994}
7995
7996struct CurrentThreadTaskDriverSelection {
7997 live: CurrentThreadTaskDriverSnapshot,
7998 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7999}
8000
8001#[derive(Default)]
8002struct CurrentThreadTaskDeliveryCompletion {
8003 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8004 redispatch: bool,
8005}
8006
8007struct CurrentThreadTaskDispatchCompletion {
8008 result: CurrentThreadHostDispatchResult,
8009 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8010}
8011
8012struct CurrentThreadTaskDispatchPublicationGuard<'a> {
8013 registry: &'a CurrentThreadTaskDriverRegistry,
8014 dispatch: Option<u64>,
8015}
8016
8017impl CurrentThreadTaskDispatchPublicationGuard<'_> {
8018 fn finish(mut self) -> CurrentThreadHostDispatchResult {
8019 let dispatch = self
8020 .dispatch
8021 .expect("the CurrentThread task dispatch publication must remain armed");
8022 let result = self.registry.finish_publication(dispatch);
8023 self.dispatch = None;
8024 result
8025 }
8026}
8027
8028impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
8029 fn drop(&mut self) {
8030 if let Some(dispatch) = self.dispatch.take() {
8031 self.registry.abort_publication(dispatch);
8032 }
8033 }
8034}
8035
8036#[derive(Default)]
8037struct CurrentThreadTaskDriverRegistry {
8038 state: Mutex<CurrentThreadTaskDriverRegistryState>,
8039 next_id: AtomicU64,
8040 next_delivery: AtomicU64,
8041}
8042
8043impl CurrentThreadTaskDriverRegistry {
8044 fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8045 let id = next_unique_id(
8046 &self.next_id,
8047 "CurrentThread task driver id space exhausted",
8048 );
8049 let id = CurrentThreadTaskDriverId(id);
8050 self
8051 .state
8052 .lock()
8053 .unwrap_or_else(std::sync::PoisonError::into_inner)
8054 .insert_driver(id, driver);
8055 id
8056 }
8057
8058 fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8059 let (removed, failed_dispatches) = {
8060 let mut state = self
8061 .state
8062 .lock()
8063 .unwrap_or_else(std::sync::PoisonError::into_inner);
8064 let removed = state.remove_driver(id);
8065 let mut failed_dispatches = Vec::new();
8066 if let Some(entry) = &removed {
8067 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8068 }
8069 (removed, failed_dispatches)
8070 };
8071 let _ = catch_unwind_contained(|| drop(removed));
8072 CurrentThreadTaskDeliveryCompletion {
8073 failed_dispatches,
8074 redispatch: false,
8075 }
8076 }
8077
8078 fn live(&self) -> CurrentThreadTaskDriverSelection {
8079 let mut failed_dispatches = Vec::new();
8080 loop {
8081 let snapshot = CurrentThreadTaskDriverSnapshot::new({
8082 let state = self
8083 .state
8084 .lock()
8085 .unwrap_or_else(std::sync::PoisonError::into_inner);
8086 state.ordered_snapshot()
8087 });
8088 let mut dead_ids = FxHashSet::default();
8089 let mut live_ids = Vec::new();
8090 for (id, driver) in snapshot.as_slice() {
8091 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8092 live_ids.push(*id);
8093 } else {
8094 dead_ids.insert(*id);
8095 }
8096 }
8097
8098 let (live, swept, retry) = {
8099 let mut state = self
8100 .state
8101 .lock()
8102 .unwrap_or_else(std::sync::PoisonError::into_inner);
8103 let mut swept = Vec::new();
8104 let mut current = state.oldest;
8105 while let Some(id) = current {
8106 let next = state
8107 .entries
8108 .get(&id)
8109 .expect("CurrentThread task driver order must reference a live entry")
8110 .next;
8111 if dead_ids.contains(&id)
8112 && let Some(entry) = state.remove_driver(id)
8113 {
8114 swept.push(entry);
8115 }
8116 current = next;
8117 }
8118 for entry in &swept {
8119 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8120 }
8121 let retry = !state.ordered_ids_match(&live_ids);
8122 let live = if retry {
8123 Vec::new()
8124 } else {
8125 state.ordered_snapshot()
8126 };
8127 (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8128 };
8129
8130 drop(snapshot);
8133 for entry in swept {
8134 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8135 let _ = catch_unwind_contained(|| drop(entry));
8136 }
8137 if !retry {
8138 return CurrentThreadTaskDriverSelection {
8139 live,
8140 failed_dispatches,
8141 };
8142 }
8143 }
8144 }
8145
8146 #[cfg(test)]
8147 fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8148 let mut live = self.live().live;
8149 live.pop().map(|(id, driver)| (id, driver.into_inner()))
8150 }
8151
8152 fn next_delivery(
8153 &self,
8154 driver: CurrentThreadTaskDriverId,
8155 dispatch: u64,
8156 ) -> CurrentThreadTaskDelivery {
8157 let capability = next_unique_id(
8158 &self.next_delivery,
8159 "CurrentThread task delivery id space exhausted",
8160 )
8161 .checked_add(1)
8162 .expect("CurrentThread task delivery id space exhausted");
8163 CurrentThreadTaskDelivery {
8164 driver,
8165 capability,
8166 dispatch,
8167 }
8168 }
8169
8170 fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8171 let dispatch_state = state
8172 .dispatches
8173 .get_mut(&dispatch)
8174 .expect("a referenced CurrentThread dispatch must remain registered");
8175 dispatch_state.references = dispatch_state
8176 .references
8177 .checked_add(1)
8178 .expect("CurrentThread dispatch reference count exhausted");
8179 }
8180
8181 fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8182 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8183 return;
8184 };
8185 dispatch_state.references = dispatch_state
8186 .references
8187 .checked_sub(1)
8188 .expect("CurrentThread dispatch reference count underflow");
8189 }
8190
8191 fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8192 let publication = {
8193 let mut state = self
8194 .state
8195 .lock()
8196 .unwrap_or_else(std::sync::PoisonError::into_inner);
8197 let dispatch_state = state.dispatches.entry(dispatch).or_default();
8198 if dispatch_state.is_serviced() {
8199 return CurrentThreadTaskDispatchCompletion {
8200 result: CurrentThreadHostDispatchResult::Accepted,
8201 failed_dispatches: Vec::new(),
8202 };
8203 }
8204 if dispatch_state.is_cancelling() {
8205 return CurrentThreadTaskDispatchCompletion {
8206 result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8207 failed_dispatches: Vec::new(),
8208 };
8209 }
8210 dispatch_state.publications = dispatch_state
8211 .publications
8212 .checked_add(1)
8213 .expect("CurrentThread dispatch publication count exhausted");
8214 CurrentThreadTaskDispatchPublicationGuard {
8215 registry: self,
8216 dispatch: Some(dispatch),
8217 }
8218 };
8219
8220 let CurrentThreadTaskDriverSelection {
8221 mut live,
8222 mut failed_dispatches,
8223 } = self.live();
8224 let planned_deliveries = {
8225 let mut state = self
8226 .state
8227 .lock()
8228 .unwrap_or_else(std::sync::PoisonError::into_inner);
8229 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8230 return CurrentThreadTaskDispatchCompletion {
8231 result: CurrentThreadHostDispatchResult::Unavailable,
8232 failed_dispatches,
8233 };
8234 };
8235 if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8236 Vec::new()
8237 } else {
8238 let mut reserved_deliveries = Vec::with_capacity(live.len());
8242 for (id, _) in live.as_slice().iter().rev() {
8243 let Some(entry) = state.entries.get(id) else {
8244 continue;
8245 };
8246 if entry.delivery.is_none() {
8247 reserved_deliveries.push(self.next_delivery(*id, dispatch));
8248 }
8249 }
8250 state.delivery_drivers.reserve(reserved_deliveries.len());
8251 let mut reserved_deliveries = reserved_deliveries.into_iter();
8252 let mut planned_deliveries = Vec::with_capacity(live.len());
8253 let mut retired = Vec::with_capacity(live.len());
8254 let mut accepted_reservation = false;
8255 for (id, _) in live.as_slice().iter().rev() {
8256 let Some(entry) = state.entries.get(id) else {
8257 continue;
8258 };
8259 if let Some(delivery) = &entry.delivery
8260 && delivery.delivery.dispatch == dispatch
8261 {
8262 accepted_reservation |= delivery.accepted;
8263 continue;
8264 }
8265 if entry.delivery.is_some() {
8266 let previous = state
8267 .entries
8268 .get_mut(id)
8269 .expect("the CurrentThread task driver must remain registered")
8270 .pending
8271 .replace(dispatch);
8272 if previous != Some(dispatch) {
8273 if let Some(previous) = previous {
8274 Self::remove_dispatch_reference(&mut state, previous);
8275 }
8276 Self::add_dispatch_reference(&mut state, dispatch);
8277 }
8278 retired.push(previous);
8279 accepted_reservation = true;
8280 continue;
8281 }
8282 let delivery = reserved_deliveries
8283 .next()
8284 .expect("every available CurrentThread host must have a reserved delivery");
8285 let pending = state
8286 .entries
8287 .get_mut(id)
8288 .expect("the CurrentThread task driver must remain registered")
8289 .pending
8290 .take();
8291 let accepted = pending == Some(dispatch);
8292 if !accepted && pending.is_some() {
8293 retired.push(pending);
8294 }
8295 match pending {
8296 Some(pending) if pending == dispatch => {}
8297 Some(pending) => {
8298 Self::remove_dispatch_reference(&mut state, pending);
8299 Self::add_dispatch_reference(&mut state, dispatch);
8300 }
8301 None => Self::add_dispatch_reference(&mut state, dispatch),
8302 }
8303 let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8304 assert!(
8305 indexed.is_none(),
8306 "CurrentThread delivery capability must be unique"
8307 );
8308 state
8309 .entries
8310 .get_mut(id)
8311 .expect("the CurrentThread task driver must remain registered")
8312 .delivery = Some(CurrentThreadTaskDeliveryState {
8313 delivery,
8314 claimed: false,
8315 accepted,
8316 });
8317 accepted_reservation |= accepted;
8318 planned_deliveries.push((*id, delivery));
8319 }
8320 let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8321 debug_assert!(
8322 all_reserved_deliveries_installed,
8323 "every reserved CurrentThread delivery must be installed"
8324 );
8325 if accepted_reservation {
8326 state
8327 .dispatches
8328 .get_mut(&dispatch)
8329 .expect("the publishing dispatch state must remain registered")
8330 .accepted = true;
8331 }
8332 for dispatch in retired.into_iter().flatten() {
8333 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8334 }
8335 planned_deliveries
8336 }
8337 };
8338
8339 let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8344 let mut planned_deliveries = planned_deliveries.into_iter();
8345 let mut next_delivery = planned_deliveries.next();
8346 while let Some((id, driver)) = live.pop() {
8347 let Some((planned_id, delivery)) = next_delivery else {
8348 break;
8349 };
8350 if planned_id == id {
8351 deliveries.push((id, driver, delivery));
8352 next_delivery = planned_deliveries.next();
8353 } else {
8354 next_delivery = Some((planned_id, delivery));
8355 }
8356 }
8357 debug_assert!(
8358 next_delivery.is_none() && planned_deliveries.len() == 0,
8359 "every planned CurrentThread delivery must retain its selected driver"
8360 );
8361
8362 for (id, driver, delivery) in deliveries {
8363 if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8364 self.accept_delivery(id, delivery);
8365 } else {
8366 let (entry, failures) = self.reject_delivery(id, delivery);
8367 failed_dispatches.extend(failures);
8368 if let Some(entry) = entry {
8369 let _ = catch_unwind_contained(|| entry.driver.on_swept());
8370 let _ = catch_unwind_contained(|| drop(entry));
8371 }
8372 }
8373 }
8374
8375 let result = publication.finish();
8376 debug_assert!(
8377 !failed_dispatches
8378 .iter()
8379 .any(|failure| failure.dispatch == dispatch),
8380 "the active publication must report its own terminal outcome directly"
8381 );
8382 CurrentThreadTaskDispatchCompletion {
8383 result,
8384 failed_dispatches,
8385 }
8386 }
8387
8388 #[cfg(test)]
8389 fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8390 self.dispatch_with_completion(dispatch).result
8391 }
8392
8393 fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8394 let mut state = self
8395 .state
8396 .lock()
8397 .unwrap_or_else(std::sync::PoisonError::into_inner);
8398 let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8399 entry
8400 .delivery
8401 .as_ref()
8402 .is_some_and(|current| current.delivery == delivery)
8403 }) {
8404 state
8405 .entries
8406 .get_mut(&id)
8407 .expect("the matching CurrentThread task driver must remain registered")
8408 .delivery
8409 .as_mut()
8410 .expect("the matching delivery must remain installed")
8411 .accepted = true;
8412 true
8413 } else {
8414 false
8419 };
8420 if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8421 dispatch_state.accepted = true;
8422 dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8423 }
8424 }
8425
8426 fn reject_delivery(
8427 &self,
8428 id: CurrentThreadTaskDriverId,
8429 delivery: CurrentThreadTaskDelivery,
8430 ) -> (
8431 Option<CurrentThreadTaskDriverEntry>,
8432 Vec<CurrentThreadTaskDispatchFailure>,
8433 ) {
8434 let mut state = self
8435 .state
8436 .lock()
8437 .unwrap_or_else(std::sync::PoisonError::into_inner);
8438 if !state.entries.get(&id).is_some_and(|entry| {
8439 entry
8440 .delivery
8441 .as_ref()
8442 .is_some_and(|current| current.delivery == delivery)
8443 }) {
8444 return (None, Vec::new());
8445 }
8446 let entry = state
8447 .remove_driver(id)
8448 .expect("the matching CurrentThread task driver must remain registered");
8449 let mut failures = Vec::new();
8450 Self::finish_entry_references(&mut state, &entry, &mut failures);
8451 (Some(entry), failures)
8452 }
8453
8454 fn finish_delivery(
8455 &self,
8456 delivery: CurrentThreadTaskDelivery,
8457 failed: bool,
8458 ) -> CurrentThreadTaskDeliveryCompletion {
8459 let mut state = self
8460 .state
8461 .lock()
8462 .unwrap_or_else(std::sync::PoisonError::into_inner);
8463 if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8464 entry
8465 .delivery
8466 .as_ref()
8467 .is_some_and(|current| current.delivery == delivery)
8468 }) {
8469 return CurrentThreadTaskDeliveryCompletion::default();
8470 }
8471 let current = state
8472 .entries
8473 .get_mut(&delivery.driver)
8474 .expect("the matching CurrentThread task driver must remain registered")
8475 .delivery
8476 .take()
8477 .expect("the matching delivery must remain installed");
8478 let indexed = state.delivery_drivers.remove(&delivery.capability);
8479 debug_assert_eq!(
8480 indexed,
8481 Some(delivery.driver),
8482 "delivery capability index must match its driver"
8483 );
8484 let redispatch = state
8485 .entries
8486 .get(&delivery.driver)
8487 .expect("the matching CurrentThread task driver must remain registered")
8488 .pending
8489 .is_some_and(|dispatch| {
8490 state
8491 .dispatches
8492 .get(&dispatch)
8493 .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8494 });
8495 let mut failed_dispatches = Vec::new();
8496 Self::finish_dispatch_reference(
8501 &mut state,
8502 delivery.dispatch,
8503 true,
8504 failed || !current.claimed,
8505 &mut failed_dispatches,
8506 );
8507 CurrentThreadTaskDeliveryCompletion {
8508 failed_dispatches,
8509 redispatch,
8510 }
8511 }
8512
8513 fn claim_delivery(&self, capability: u64) -> Option<u64> {
8514 let mut state = self
8515 .state
8516 .lock()
8517 .unwrap_or_else(std::sync::PoisonError::into_inner);
8518 let driver = *state.delivery_drivers.get(&capability)?;
8519 let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8520 if delivery.capability != capability {
8521 return None;
8522 }
8523 let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8524 if dispatch_state.is_serviced()
8525 || dispatch_state.is_cancelling()
8526 || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8527 {
8528 return None;
8529 }
8530 let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8531 current.claimed = true;
8532 current.accepted = true;
8533 state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8534 Some(delivery.dispatch)
8535 }
8536
8537 fn mark_serviced(&self, dispatch: u64) {
8538 let mut state = self
8539 .state
8540 .lock()
8541 .unwrap_or_else(std::sync::PoisonError::into_inner);
8542 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8543 return;
8544 };
8545 dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8546 let mut removed_references = 0usize;
8547 for entry in state.entries.values_mut() {
8548 if entry.pending == Some(dispatch) {
8549 entry.pending = None;
8550 removed_references = removed_references
8551 .checked_add(1)
8552 .expect("CurrentThread dispatch reference count exhausted");
8553 }
8554 }
8555 let dispatch_state = state
8556 .dispatches
8557 .get_mut(&dispatch)
8558 .expect("the serviced CurrentThread dispatch must remain registered");
8559 dispatch_state.references = dispatch_state
8560 .references
8561 .checked_sub(removed_references)
8562 .expect("CurrentThread dispatch reference count underflow");
8563 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8564 }
8565
8566 fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8567 let delivery = {
8568 let state = self
8569 .state
8570 .lock()
8571 .unwrap_or_else(std::sync::PoisonError::into_inner);
8572 state
8573 .delivery_drivers
8574 .get(&capability)
8575 .and_then(|driver| state.entries.get(driver))
8576 .and_then(|entry| entry.delivery.as_ref())
8577 .map(|delivery| delivery.delivery)
8578 .filter(|delivery| delivery.capability == capability)
8579 };
8580 delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8581 self.finish_delivery(delivery, true)
8582 })
8583 }
8584
8585 fn finish_dispatch_reference(
8586 state: &mut CurrentThreadTaskDriverRegistryState,
8587 dispatch: u64,
8588 accepted: bool,
8589 failed: bool,
8590 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8591 ) {
8592 let (publications, references) = {
8593 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8594 return;
8595 };
8596 dispatch_state.references = dispatch_state
8597 .references
8598 .checked_sub(1)
8599 .expect("CurrentThread dispatch reference count underflow");
8600 dispatch_state.accepted |= accepted;
8601 dispatch_state.accepted_failure |= accepted && failed;
8602 (dispatch_state.publications, dispatch_state.references)
8603 };
8604 if references != 0 || publications != 0 {
8605 return;
8606 }
8607 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8608 return;
8609 };
8610 if dispatch_state.accepted_failure
8611 && !dispatch_state.is_serviced()
8612 && !dispatch_state.is_cancelling()
8613 {
8614 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8615 failures.push(dispatch_state.failure(dispatch));
8616 } else if !dispatch_state.is_cancelling() {
8617 state.dispatches.remove(&dispatch);
8618 }
8619 }
8620
8621 fn finish_entry_references(
8622 state: &mut CurrentThreadTaskDriverRegistryState,
8623 entry: &CurrentThreadTaskDriverEntry,
8624 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8625 ) {
8626 if let Some(delivery) = &entry.delivery {
8627 Self::finish_dispatch_reference(
8628 state,
8629 delivery.delivery.dispatch,
8630 delivery.accepted,
8631 delivery.accepted,
8632 failures,
8633 );
8634 }
8635 if let Some(pending) = entry.pending {
8636 Self::finish_dispatch_reference(state, pending, true, true, failures);
8637 }
8638 }
8639
8640 fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8641 let mut state = self
8642 .state
8643 .lock()
8644 .unwrap_or_else(std::sync::PoisonError::into_inner);
8645 let publications = {
8646 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8647 return CurrentThreadHostDispatchResult::Unavailable;
8648 };
8649 dispatch_state.publications = dispatch_state
8650 .publications
8651 .checked_sub(1)
8652 .expect("CurrentThread dispatch publication count underflow");
8653 dispatch_state.publications
8654 };
8655 if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8656 return CurrentThreadHostDispatchResult::Accepted;
8657 }
8658 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8659 return CurrentThreadHostDispatchResult::Unavailable;
8660 };
8661 if dispatch_state.is_cancelling() {
8662 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8663 }
8664 if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8665 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8666 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8667 }
8668 let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8669 state.dispatches.remove(&dispatch);
8670 if accepted {
8671 CurrentThreadHostDispatchResult::Accepted
8672 } else {
8673 CurrentThreadHostDispatchResult::Unavailable
8674 }
8675 }
8676
8677 fn abort_publication(&self, dispatch: u64) {
8678 let mut state = self
8679 .state
8680 .lock()
8681 .unwrap_or_else(std::sync::PoisonError::into_inner);
8682 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8683 return;
8684 };
8685 let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8686 return;
8687 };
8688 dispatch_state.publications = publications;
8689 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8690 }
8691
8692 fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8693 let mut state = self
8694 .state
8695 .lock()
8696 .unwrap_or_else(std::sync::PoisonError::into_inner);
8697 let can_rearm = state
8698 .dispatches
8699 .get(&failure.dispatch)
8700 .is_some_and(|dispatch_state| {
8701 dispatch_state.is_cancelling()
8702 && dispatch_state.epoch == failure.epoch
8703 && dispatch_state.publications == 0
8704 })
8705 && !Self::has_dispatch_reference(&state, failure.dispatch);
8706 if !can_rearm {
8707 return false;
8708 }
8709 let dispatch_state = state
8710 .dispatches
8711 .get_mut(&failure.dispatch)
8712 .expect("the exact failed CurrentThread dispatch must remain registered");
8713 let epoch = dispatch_state
8714 .epoch
8715 .checked_add(1)
8716 .expect("CurrentThread dispatch failure epoch space exhausted");
8717 *dispatch_state = CurrentThreadDispatchState {
8718 epoch,
8719 ..CurrentThreadDispatchState::default()
8720 };
8721 true
8722 }
8723
8724 fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8725 self
8726 .state
8727 .lock()
8728 .unwrap_or_else(std::sync::PoisonError::into_inner)
8729 .dispatches
8730 .get(&failure.dispatch)
8731 .is_some_and(|dispatch_state| {
8732 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8733 })
8734 }
8735
8736 fn retire_unreferenced_dispatch(
8737 state: &mut CurrentThreadTaskDriverRegistryState,
8738 dispatch: Option<u64>,
8739 ) {
8740 let Some(dispatch) = dispatch else {
8741 return;
8742 };
8743 if Self::has_dispatch_reference(state, dispatch) {
8744 return;
8745 }
8746 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8747 return;
8748 };
8749 if dispatch_state.publications == 0
8750 && !dispatch_state.is_cancelling()
8751 && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8752 {
8753 state.dispatches.remove(&dispatch);
8754 }
8755 }
8756
8757 fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8758 state
8759 .dispatches
8760 .get(&dispatch)
8761 .is_some_and(|dispatch_state| dispatch_state.references != 0)
8762 }
8763
8764 fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8765 let mut state = self
8766 .state
8767 .lock()
8768 .unwrap_or_else(std::sync::PoisonError::into_inner);
8769 if state
8770 .dispatches
8771 .get(&failure.dispatch)
8772 .is_some_and(|dispatch_state| {
8773 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8774 })
8775 {
8776 state.dispatches.remove(&failure.dispatch);
8777 }
8778 }
8779
8780 fn retire_for_shutdown(&self) {
8781 let mut state = self
8782 .state
8783 .lock()
8784 .unwrap_or_else(std::sync::PoisonError::into_inner);
8785 state.dispatches.clear();
8786 for entry in state.entries.values_mut() {
8787 entry.pending = None;
8788 }
8789 }
8790}
8791
8792static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8793 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8794
8795pub fn register_current_thread_task_driver(
8796 driver: Arc<dyn CurrentThreadTaskDriver>,
8797) -> CurrentThreadTaskDriverId {
8798 CURRENT_THREAD_TASK_DRIVERS.register(driver)
8799}
8800
8801pub fn request_current_thread_task_drain() {
8806 RUNTIME.request_current_thread_drain();
8807}
8808
8809pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8810 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8811}
8812
8813fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8814 let CurrentThreadTaskDispatchCompletion {
8815 result,
8816 failed_dispatches,
8817 } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8818 complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8819 failed_dispatches,
8820 redispatch: false,
8821 });
8822 result
8823}
8824
8825pub type TimerId = u64;
8826
8827static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8829
8830pub trait TimerDriver: Send + Sync + 'static {
8836 fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8840 fn cancel(&self, id: TimerId);
8843 fn is_live(&self) -> bool {
8848 true
8849 }
8850 fn on_swept(&self) {}
8860}
8861
8862#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8866pub struct TimerDriverId(u64);
8867
8868type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8869
8870struct TimerDriverEntry {
8871 driver: Arc<dyn TimerDriver>,
8872 previous: Option<TimerDriverId>,
8873 next: Option<TimerDriverId>,
8874}
8875
8876#[derive(Default)]
8877struct TimerDriverRegistryState {
8878 entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8879 oldest: Option<TimerDriverId>,
8880 newest: Option<TimerDriverId>,
8881}
8882
8883impl TimerDriverRegistryState {
8884 fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8885 let previous = self.newest;
8886 let replaced = self.entries.insert(
8887 id,
8888 TimerDriverEntry {
8889 driver,
8890 previous,
8891 next: None,
8892 },
8893 );
8894 assert!(replaced.is_none(), "timer driver id must be unique");
8895 if let Some(previous) = previous {
8896 self
8897 .entries
8898 .get_mut(&previous)
8899 .expect("the newest timer driver must remain registered")
8900 .next = Some(id);
8901 } else {
8902 self.oldest = Some(id);
8903 }
8904 self.newest = Some(id);
8905 }
8906
8907 fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
8908 let entry = self.entries.remove(&id)?;
8909 if let Some(previous) = entry.previous {
8910 self
8911 .entries
8912 .get_mut(&previous)
8913 .expect("the previous timer driver must remain registered")
8914 .next = entry.next;
8915 } else {
8916 self.oldest = entry.next;
8917 }
8918 if let Some(next) = entry.next {
8919 self
8920 .entries
8921 .get_mut(&next)
8922 .expect("the next timer driver must remain registered")
8923 .previous = entry.previous;
8924 } else {
8925 self.newest = entry.previous;
8926 }
8927 Some(entry)
8928 }
8929
8930 fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
8931 let mut snapshot = Vec::with_capacity(self.entries.len());
8932 let mut current = self.oldest;
8933 while let Some(id) = current {
8934 let entry = self
8935 .entries
8936 .get(&id)
8937 .expect("timer driver order must reference a live entry");
8938 snapshot.push((id, Arc::clone(&entry.driver)));
8939 current = entry.next;
8940 }
8941 debug_assert_eq!(
8942 snapshot.len(),
8943 self.entries.len(),
8944 "timer driver order must contain every entry exactly once"
8945 );
8946 snapshot
8947 }
8948
8949 fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
8950 if self.entries.len() != expected.len() {
8951 return false;
8952 }
8953 let mut current = self.oldest;
8954 for expected in expected {
8955 if current != Some(*expected) {
8956 return false;
8957 }
8958 current = self
8959 .entries
8960 .get(expected)
8961 .expect("timer driver order must reference a live entry")
8962 .next;
8963 }
8964 current.is_none()
8965 }
8966}
8967
8968#[derive(Default)]
8986pub struct TimerDriverRegistry {
8987 state: Mutex<TimerDriverRegistryState>,
8991 pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
8996 next_id: AtomicU64,
8997}
8998
8999impl Drop for TimerDriverRegistry {
9000 fn drop(&mut self) {
9001 let state = self
9002 .state
9003 .get_mut()
9004 .unwrap_or_else(std::sync::PoisonError::into_inner);
9005 state.oldest = None;
9006 state.newest = None;
9007 let entries = std::mem::take(&mut state.entries);
9008 for (_, entry) in entries {
9009 let _ = catch_unwind_contained(|| drop(entry));
9010 }
9011 }
9012}
9013
9014impl TimerDriverRegistry {
9015 pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9017 let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
9018 let id = TimerDriverId(id);
9019 self
9020 .state
9021 .lock()
9022 .unwrap_or_else(std::sync::PoisonError::into_inner)
9023 .insert_driver(id, driver);
9024 let pending = {
9029 let mut entries = self
9030 .pending
9031 .lock()
9032 .unwrap_or_else(std::sync::PoisonError::into_inner);
9033 let mut pending = Vec::with_capacity(entries.len());
9034 entries.retain(|_, repoll| {
9035 if let Some(repoll) = repoll.upgrade() {
9036 pending.push(repoll);
9037 true
9038 } else {
9039 false
9040 }
9041 });
9042 pending
9043 };
9044 for repoll in pending {
9045 repoll.wake();
9046 }
9047 id
9048 }
9049
9050 pub fn unregister(&self, id: TimerDriverId) {
9052 let removed = self
9053 .state
9054 .lock()
9055 .unwrap_or_else(std::sync::PoisonError::into_inner)
9056 .remove_driver(id);
9057 let _ = catch_unwind_contained(|| drop(removed));
9060 }
9061
9062 fn live(&self) -> HostTimerDriverSnapshot {
9073 loop {
9074 let snapshot = HostTimerDriverSnapshot::new({
9075 let state = self
9076 .state
9077 .lock()
9078 .unwrap_or_else(std::sync::PoisonError::into_inner);
9079 state.ordered_snapshot()
9080 });
9081 let mut dead_ids = FxHashSet::default();
9082 let mut live_ids = Vec::new();
9083 for (id, driver) in snapshot.as_slice() {
9084 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9085 live_ids.push(*id);
9086 } else {
9087 dead_ids.insert(*id);
9088 }
9089 }
9090
9091 let (live, swept, retry) = {
9092 let mut state = self
9093 .state
9094 .lock()
9095 .unwrap_or_else(std::sync::PoisonError::into_inner);
9096 let mut swept = Vec::new();
9097 let mut current = state.oldest;
9098 while let Some(id) = current {
9099 let next = state
9100 .entries
9101 .get(&id)
9102 .expect("timer driver order must reference a live entry")
9103 .next;
9104 if dead_ids.contains(&id)
9105 && let Some(entry) = state.remove_driver(id)
9106 {
9107 swept.push(entry);
9108 }
9109 current = next;
9110 }
9111 let retry = !state.ordered_ids_match(&live_ids);
9112 let live = if retry {
9113 Vec::new()
9114 } else {
9115 state.ordered_snapshot()
9116 };
9117 (HostTimerDriverSnapshot::new(live), swept, retry)
9118 };
9119
9120 drop(snapshot);
9123 for entry in swept {
9124 let _ = catch_unwind_contained(|| entry.driver.on_swept());
9125 let _ = catch_unwind_contained(|| drop(entry));
9126 }
9127 if !retry {
9128 return live;
9129 }
9130 }
9131 }
9132
9133 #[cfg(test)]
9134 fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9135 let mut live = self.live();
9136 live.pop().map(|(id, driver)| (id, driver.into_inner()))
9137 }
9138
9139 pub fn has_live_driver(&self) -> bool {
9141 !self.live().is_empty()
9142 }
9143
9144 fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9145 let replaced = self
9146 .pending
9147 .lock()
9148 .unwrap_or_else(std::sync::PoisonError::into_inner)
9149 .insert(id, Arc::downgrade(repoll));
9150 assert!(
9151 replaced.is_none(),
9152 "host timer {id} registered its repoll signal twice"
9153 );
9154 if repoll.is_closed() {
9157 self.remove_pending(id);
9158 }
9159 }
9160
9161 fn remove_pending(&self, id: TimerId) {
9162 let removed = self
9163 .pending
9164 .lock()
9165 .unwrap_or_else(std::sync::PoisonError::into_inner)
9166 .remove(&id);
9167 let _ = catch_unwind_contained(|| drop(removed));
9168 }
9169
9170 #[cfg(test)]
9171 fn pending_len(&self) -> usize {
9172 self
9173 .pending
9174 .lock()
9175 .unwrap_or_else(std::sync::PoisonError::into_inner)
9176 .len()
9177 }
9178}
9179
9180static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9183 LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9184
9185pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9191 TIMER_DRIVERS.register(driver)
9192}
9193
9194pub fn unregister_timer_driver(id: TimerDriverId) {
9198 TIMER_DRIVERS.unregister(id);
9199}
9200
9201pub fn has_live_timer_driver() -> bool {
9208 TIMER_DRIVERS.has_live_driver()
9209}
9210
9211#[derive(Default)]
9215struct HostTimerRepoll {
9216 state: Mutex<HostTimerRepollState>,
9217}
9218
9219#[derive(Default)]
9220struct HostTimerRepollState {
9221 closed: bool,
9222 notified: bool,
9223 waker: Option<Waker>,
9224}
9225
9226impl HostTimerRepoll {
9227 fn register(&self, waker: Waker) {
9228 let (replaced, wake) = {
9229 let mut state = self
9230 .state
9231 .lock()
9232 .unwrap_or_else(std::sync::PoisonError::into_inner);
9233 if state.closed {
9234 (Some(waker), None)
9235 } else if state.notified {
9236 state.notified = false;
9237 (state.waker.take(), Some(waker))
9238 } else {
9239 (state.waker.replace(waker), None)
9240 }
9241 };
9242 let _ = catch_unwind_contained(|| drop(replaced));
9243 if let Some(waker) = wake {
9244 wake_waker_contained(waker);
9245 }
9246 }
9247
9248 fn wake(&self) {
9249 let waker = {
9250 let mut state = self
9251 .state
9252 .lock()
9253 .unwrap_or_else(std::sync::PoisonError::into_inner);
9254 if state.closed {
9255 return;
9256 }
9257 if let Some(waker) = state.waker.take() {
9258 Some(waker)
9259 } else {
9260 state.notified = true;
9261 None
9262 }
9263 };
9264 if let Some(waker) = waker {
9265 wake_waker_contained(waker);
9266 }
9267 }
9268
9269 fn close(&self) {
9270 let waker = {
9271 let mut state = self
9272 .state
9273 .lock()
9274 .unwrap_or_else(std::sync::PoisonError::into_inner);
9275 state.closed = true;
9276 state.notified = false;
9277 state.waker.take()
9278 };
9279 let _ = catch_unwind_contained(|| drop(waker));
9280 }
9281
9282 fn is_closed(&self) -> bool {
9283 self
9284 .state
9285 .lock()
9286 .unwrap_or_else(std::sync::PoisonError::into_inner)
9287 .closed
9288 }
9289}
9290
9291#[derive(Default)]
9298struct HostTimerRegistry {
9299 state: Mutex<HostTimerRegistryState>,
9300}
9301
9302#[derive(Default)]
9303struct HostTimerRegistryState {
9304 closed: bool,
9305 timers: FxHashMap<TimerId, HostTimerEntry>,
9306}
9307
9308struct HostTimerEntry {
9309 deadline: Instant,
9310 waker: Waker,
9311 fired: Arc<AtomicBool>,
9312 drivers: Weak<TimerDriverRegistry>,
9313 repoll: Arc<HostTimerRepoll>,
9314}
9315
9316impl HostTimerRegistry {
9317 fn register(
9318 &self,
9319 id: TimerId,
9320 deadline: Instant,
9321 waker: Waker,
9322 fired: &Arc<AtomicBool>,
9323 drivers: &Arc<TimerDriverRegistry>,
9324 repoll: &Arc<HostTimerRepoll>,
9325 ) -> bool {
9326 let replaced = {
9327 let mut state = self
9328 .state
9329 .lock()
9330 .unwrap_or_else(std::sync::PoisonError::into_inner);
9331 if state.closed || fired.load(Ordering::SeqCst) {
9332 fired.store(true, Ordering::SeqCst);
9333 Err(waker)
9334 } else {
9335 Ok(state.timers.insert(
9336 id,
9337 HostTimerEntry {
9338 deadline,
9339 waker,
9340 fired: Arc::clone(fired),
9341 drivers: Arc::downgrade(drivers),
9342 repoll: Arc::clone(repoll),
9343 },
9344 ))
9345 }
9346 };
9347 match replaced {
9348 Ok(Some(entry)) => {
9349 let _ = catch_unwind_contained(|| drop(entry));
9350 true
9351 }
9352 Ok(None) => true,
9353 Err(waker) => {
9354 let _ = catch_unwind_contained(|| drop(waker));
9355 false
9356 }
9357 }
9358 }
9359
9360 fn remove(&self, id: TimerId) {
9361 let removed = self
9362 .state
9363 .lock()
9364 .unwrap_or_else(std::sync::PoisonError::into_inner)
9365 .timers
9366 .remove(&id);
9367 if let Some(entry) = removed {
9368 let _ = catch_unwind_contained(|| drop(entry));
9369 }
9370 }
9371
9372 fn shutdown(&self) {
9373 let timers = {
9374 let mut state = self
9375 .state
9376 .lock()
9377 .unwrap_or_else(std::sync::PoisonError::into_inner);
9378 state.closed = true;
9379 state
9380 .timers
9381 .drain()
9382 .map(|(id, entry)| {
9383 entry.fired.store(true, Ordering::SeqCst);
9384 (id, entry)
9385 })
9386 .collect::<Vec<_>>()
9387 };
9388 for (id, entry) in timers {
9389 entry.repoll.close();
9390 if let Some(drivers) = entry.drivers.upgrade() {
9391 drivers.remove_pending(id);
9392 }
9393 wake_waker_contained(entry.waker);
9394 }
9395 }
9396
9397 fn has_pending(&self) -> bool {
9402 !self
9403 .state
9404 .lock()
9405 .unwrap_or_else(std::sync::PoisonError::into_inner)
9406 .timers
9407 .is_empty()
9408 }
9409
9410 fn has_live_wait(&self, grace: Duration) -> bool {
9419 let now = Instant::now();
9420 self
9421 .state
9422 .lock()
9423 .unwrap_or_else(std::sync::PoisonError::into_inner)
9424 .timers
9425 .values()
9426 .any(|entry| {
9427 entry
9428 .deadline
9429 .checked_add(grace)
9430 .is_none_or(|deadline| deadline > now)
9431 })
9432 }
9433}
9434
9435struct HostTimerPendingGuard {
9439 registry: Arc<HostTimerRegistry>,
9440 drivers: Arc<TimerDriverRegistry>,
9441 repoll: Arc<HostTimerRepoll>,
9442 id: TimerId,
9443}
9444
9445impl Drop for HostTimerPendingGuard {
9446 fn drop(&mut self) {
9447 self.repoll.close();
9448 self.registry.remove(self.id);
9449 self.drivers.remove_pending(self.id);
9450 }
9451}
9452
9453#[cfg(napi_runtime_os_threads)]
9459struct TimerHeap {
9460 inner: Arc<Mutex<TimerHeapInner>>,
9461 parker: Arc<DriverParker>,
9462 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9463 deadlock_state: Arc<MultiThreadDeadlockState>,
9464 #[cfg(test)]
9465 fail_next_start: AtomicBool,
9466 generation: u64,
9467 executor_id: u64,
9468 thread_name: String,
9469}
9470
9471#[cfg(napi_runtime_os_threads)]
9472#[derive(Default)]
9473struct TimerHeapInner {
9474 queue: TimerDeadlineQueue,
9478 entries: FxHashMap<TimerId, HeapTimerEntry>,
9479 timekeeper_parker: Option<Arc<DriverParker>>,
9483 closed: bool,
9487}
9488
9489#[cfg(napi_runtime_os_threads)]
9490struct HeapTimerEntry {
9491 waker: Waker,
9492 fired: Arc<AtomicBool>,
9497}
9498
9499#[cfg(napi_runtime_os_threads)]
9500#[derive(Default)]
9501struct TimerDeadlineQueue {
9502 nodes: Vec<(Instant, TimerId)>,
9503 positions: FxHashMap<TimerId, usize>,
9504}
9505
9506#[cfg(napi_runtime_os_threads)]
9507impl TimerDeadlineQueue {
9508 #[cfg(test)]
9509 fn len(&self) -> usize {
9510 self.nodes.len()
9511 }
9512
9513 #[cfg(test)]
9514 fn is_empty(&self) -> bool {
9515 self.nodes.is_empty()
9516 }
9517
9518 fn clear(&mut self) {
9519 self.nodes.clear();
9520 self.positions.clear();
9521 }
9522
9523 fn peek(&self) -> Option<(Instant, TimerId)> {
9524 self.nodes.first().copied()
9525 }
9526
9527 fn push(&mut self, deadline: Instant, id: TimerId) {
9528 let index = self.nodes.len();
9529 assert!(
9530 self.positions.insert(id, index).is_none(),
9531 "timer deadline queue received duplicate id {id}"
9532 );
9533 self.nodes.push((deadline, id));
9534 self.sift_up(index);
9535 }
9536
9537 fn pop(&mut self) -> Option<(Instant, TimerId)> {
9538 let first = self.nodes.first().copied()?;
9539 self.positions.remove(&first.1);
9540 let last = self
9541 .nodes
9542 .pop()
9543 .expect("the non-empty timer heap must have a final node");
9544 if !self.nodes.is_empty() {
9545 self.nodes[0] = last;
9546 self.positions.insert(last.1, 0);
9547 self.sift_down(0);
9548 }
9549 Some(first)
9550 }
9551
9552 fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9553 let index = self.positions.remove(&id)?;
9554 let removed = self.nodes[index];
9555 let last = self.nodes.pop().expect("an indexed timer node must exist");
9556 if index < self.nodes.len() {
9557 self.nodes[index] = last;
9558 self.positions.insert(last.1, index);
9559 if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9560 self.sift_up(index);
9561 } else {
9562 self.sift_down(index);
9563 }
9564 }
9565 Some(removed)
9566 }
9567
9568 fn sift_up(&mut self, mut index: usize) {
9569 while index > 0 {
9570 let parent = (index - 1) / 2;
9571 if self.nodes[parent] <= self.nodes[index] {
9572 break;
9573 }
9574 self.swap(parent, index);
9575 index = parent;
9576 }
9577 }
9578
9579 fn sift_down(&mut self, mut index: usize) {
9580 loop {
9581 let left = index * 2 + 1;
9582 if left >= self.nodes.len() {
9583 return;
9584 }
9585 let right = left + 1;
9586 let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9587 right
9588 } else {
9589 left
9590 };
9591 if self.nodes[index] <= self.nodes[child] {
9592 return;
9593 }
9594 self.swap(index, child);
9595 index = child;
9596 }
9597 }
9598
9599 fn swap(&mut self, left: usize, right: usize) {
9600 self.nodes.swap(left, right);
9601 self.positions.insert(self.nodes[left].1, left);
9602 self.positions.insert(self.nodes[right].1, right);
9603 }
9604}
9605
9606#[cfg(napi_runtime_os_threads)]
9607impl TimerHeapInner {
9608 fn next_deadline(&self) -> Option<Instant> {
9609 self.queue.peek().map(|(deadline, _)| deadline)
9610 }
9611}
9612
9613#[cfg(napi_runtime_os_threads)]
9614impl TimerHeap {
9615 fn new(
9616 generation: u64,
9617 executor_id: u64,
9618 thread_name: String,
9619 deadlock_state: Arc<MultiThreadDeadlockState>,
9620 ) -> Self {
9621 Self {
9622 inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9623 parker: Arc::new(DriverParker::default()),
9624 thread: Mutex::new(None),
9625 deadlock_state,
9626 #[cfg(test)]
9627 fail_next_start: AtomicBool::new(false),
9628 generation,
9629 executor_id,
9630 thread_name,
9631 }
9632 }
9633
9634 fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9635 if self
9636 .inner
9637 .lock()
9638 .unwrap_or_else(std::sync::PoisonError::into_inner)
9639 .closed
9640 {
9641 return Ok(());
9642 }
9643 let current_thread = std::thread::current().id();
9644 loop {
9645 let finished = {
9646 let mut thread = self
9647 .thread
9648 .lock()
9649 .unwrap_or_else(std::sync::PoisonError::into_inner);
9650 match thread.as_ref() {
9651 Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9652 Some(handle) if !handle.is_finished() => return Ok(()),
9653 Some(_) => thread.take(),
9654 None => {
9655 if self
9658 .inner
9659 .lock()
9660 .unwrap_or_else(std::sync::PoisonError::into_inner)
9661 .closed
9662 {
9663 return Ok(());
9664 }
9665 #[cfg(test)]
9666 if self.fail_next_start.swap(false, Ordering::SeqCst) {
9667 return Err(RuntimeConfigError(
9668 "injected runtime timer thread creation failure".to_string(),
9669 ));
9670 }
9671
9672 let inner = Arc::clone(&self.inner);
9673 let parker = Arc::clone(&self.parker);
9674 let deadlock_state = Arc::clone(&self.deadlock_state);
9675 let generation = self.generation;
9676 let executor_id = self.executor_id;
9677 let handle = std::thread::Builder::new()
9678 .name(self.thread_name.clone())
9679 .spawn(move || {
9680 ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9683 let _generation = RuntimeGenerationGuard::enter(generation);
9684 timer_timekeeper_main(&inner, &parker, &deadlock_state);
9685 })
9686 .map_err(|error| {
9687 RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9688 })?;
9689 *thread = Some(handle);
9690 return Ok(());
9691 }
9692 }
9693 };
9694
9695 if let Some(finished) = finished
9699 && finished.thread().id() != current_thread
9700 && let Err(payload) = finished.join()
9701 {
9702 discard_panic_payload(payload);
9703 }
9704 if self
9705 .inner
9706 .lock()
9707 .unwrap_or_else(std::sync::PoisonError::into_inner)
9708 .closed
9709 {
9710 return Ok(());
9711 }
9712 }
9713 }
9714
9715 fn close(&self) -> Vec<Waker> {
9716 let mut inner = self
9717 .inner
9718 .lock()
9719 .unwrap_or_else(std::sync::PoisonError::into_inner);
9720 inner.closed = true;
9721 inner.queue.clear();
9722 inner.timekeeper_parker = None;
9723 inner
9724 .entries
9725 .drain()
9726 .map(|(_, entry)| {
9727 entry.fired.store(true, Ordering::SeqCst);
9728 entry.waker
9729 })
9730 .collect()
9731 }
9732
9733 fn join(&self) {
9734 let handle = self
9735 .thread
9736 .lock()
9737 .unwrap_or_else(std::sync::PoisonError::into_inner)
9738 .take();
9739 if let Some(handle) = handle {
9740 if handle.thread().id() == std::thread::current().id() {
9741 drop(handle);
9745 return;
9746 }
9747 if let Err(payload) = handle.join() {
9748 discard_panic_payload(payload);
9749 }
9750 }
9751 }
9752}
9753
9754#[cfg(napi_runtime_os_threads)]
9755impl Drop for TimerHeap {
9756 fn drop(&mut self) {
9757 let mut admission = self.deadlock_state.begin_admission();
9758 let wakers = self.close();
9759 if !wakers.is_empty()
9760 && let Some(admission) = admission.as_mut()
9761 {
9762 admission.mark_published();
9763 }
9764 self.parker.unpark();
9765 for waker in wakers {
9766 wake_waker_contained(waker);
9767 }
9768 drop(admission);
9769 self.join();
9770 }
9771}
9772
9773#[cfg(napi_runtime_os_threads)]
9774fn timer_timekeeper_main(
9775 inner: &Mutex<TimerHeapInner>,
9776 parker: &Arc<DriverParker>,
9777 deadlock_state: &Arc<MultiThreadDeadlockState>,
9778) {
9779 loop {
9780 let mut admission = deadlock_state.begin_admission();
9784 let (due, deadline) = {
9785 let mut inner = inner
9786 .lock()
9787 .unwrap_or_else(std::sync::PoisonError::into_inner);
9788 if inner.closed {
9789 inner.timekeeper_parker = None;
9790 return;
9791 }
9792 let now = Instant::now();
9793 let mut due = Vec::new();
9794 while let Some((deadline, id)) = inner.queue.peek() {
9795 if deadline > now {
9796 break;
9797 }
9798 inner.queue.pop();
9799 if let Some(entry) = inner.entries.remove(&id) {
9800 entry.fired.store(true, Ordering::SeqCst);
9801 due.push(entry.waker);
9802 }
9803 }
9804 let deadline = inner.next_deadline();
9805 inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9806 (due, deadline)
9807 };
9808
9809 if !due.is_empty() {
9810 if let Some(admission) = admission.as_mut() {
9811 admission.mark_published();
9812 }
9813 for waker in due {
9814 wake_waker_contained(waker);
9815 }
9816 continue;
9817 }
9818 drop(admission);
9821
9822 match deadline {
9823 Some(deadline) => {
9824 let now = Instant::now();
9825 if deadline > now {
9826 parker.park_timeout(deadline - now);
9827 }
9828 }
9829 None => parker.park(),
9830 }
9831 }
9832}
9833
9834#[cfg(napi_runtime_os_threads)]
9835impl MultiThreadExecutor {
9836 fn register_timer(
9842 self: &Arc<Self>,
9843 id: TimerId,
9844 deadline: Instant,
9845 waker: Waker,
9846 fired: &Arc<AtomicBool>,
9847 ) {
9848 let mut admission = self.begin_deadlock_admission();
9849 #[cfg(test)]
9850 run_timer_registration_admission_test_hook();
9851 if let Err(error) = self.timers.ensure_started() {
9855 let _ = catch_unwind_contained(|| drop(waker));
9856 panic!("{error}");
9857 }
9858 let (became_earliest, published, discarded_waker) = {
9859 let mut inner = self
9860 .timers
9861 .inner
9862 .lock()
9863 .unwrap_or_else(std::sync::PoisonError::into_inner);
9864 if inner.closed || fired.load(Ordering::SeqCst) {
9865 fired.store(true, Ordering::SeqCst);
9866 (false, false, Some(waker))
9867 } else if let Some(entry) = inner.entries.get_mut(&id) {
9868 (
9870 false,
9871 false,
9872 Some(std::mem::replace(&mut entry.waker, waker)),
9873 )
9874 } else {
9875 let previous = inner.next_deadline();
9876 inner.entries.insert(
9877 id,
9878 HeapTimerEntry {
9879 waker,
9880 fired: Arc::clone(fired),
9881 },
9882 );
9883 inner.queue.push(deadline, id);
9884 (
9885 previous.is_none_or(|previous| deadline < previous),
9886 true,
9887 None,
9888 )
9889 }
9890 };
9891 if published && let Some(admission) = admission.as_mut() {
9892 admission.mark_published();
9893 }
9894 if let Some(waker) = discarded_waker {
9895 let _ = catch_unwind_contained(|| drop(waker));
9898 }
9899 if became_earliest {
9900 self.note_earlier_timer();
9902 }
9903 }
9904
9905 fn cancel_timer(&self, id: TimerId) {
9909 let (removed, earliest_changed) = {
9910 let mut inner = self
9911 .timers
9912 .inner
9913 .lock()
9914 .unwrap_or_else(std::sync::PoisonError::into_inner);
9915 let previous = inner.next_deadline();
9916 let removed = inner.entries.remove(&id);
9917 if removed.is_some() {
9918 let queued = inner.queue.remove(id);
9919 debug_assert!(
9920 queued.is_some(),
9921 "a live timer entry must have one indexed deadline node"
9922 );
9923 }
9924 let current = inner.next_deadline();
9925 (removed, previous != current)
9926 };
9927 if let Some(entry) = removed {
9928 let _ = catch_unwind_contained(|| drop(entry));
9931 }
9932 if earliest_changed {
9933 self.timers.parker.unpark();
9934 self.parked_drivers.wake_all();
9935 }
9936 }
9937
9938 fn next_timer_deadline(&self) -> Option<Instant> {
9940 self
9941 .timers
9942 .inner
9943 .lock()
9944 .unwrap_or_else(std::sync::PoisonError::into_inner)
9945 .next_deadline()
9946 }
9947
9948 fn fire_due_timers(&self) {
9953 let mut admission = self.begin_deadlock_admission();
9954 let due: Vec<Waker> = {
9955 let mut inner = self
9956 .timers
9957 .inner
9958 .lock()
9959 .unwrap_or_else(std::sync::PoisonError::into_inner);
9960 let now = Instant::now();
9961 let mut due = Vec::new();
9962 while let Some((deadline, id)) = inner.queue.peek() {
9963 if deadline > now {
9964 break;
9965 }
9966 inner.queue.pop();
9967 if let Some(entry) = inner.entries.remove(&id) {
9968 entry.fired.store(true, Ordering::SeqCst);
9969 due.push(entry.waker);
9970 }
9971 }
9972 due
9973 };
9974 if !due.is_empty()
9975 && let Some(admission) = admission.as_mut()
9976 {
9977 admission.mark_published();
9978 }
9979 for waker in due {
9980 wake_waker_contained(waker);
9983 }
9984 }
9985
9986 fn note_earlier_timer(self: &Arc<Self>) {
9990 self.timers.parker.unpark();
9991 self.parked_drivers.wake_one();
9992 }
9993
9994 fn shutdown_timers(&self) {
10000 let mut admission = self.begin_deadlock_admission();
10001 let wakers = self.timers.close();
10002 if !wakers.is_empty()
10003 && let Some(admission) = admission.as_mut()
10004 {
10005 admission.mark_published();
10006 }
10007 for waker in wakers {
10008 wake_waker_contained(waker);
10012 }
10013 self.timers.parker.unpark();
10014 }
10015
10016 fn begin_shutdown(&self) {
10017 let _generation = RuntimeGenerationGuard::enter(self.generation);
10018 self.stop.begin_shutdown();
10023 let queued = {
10024 let mut queue = self
10025 .blocking_queue
10026 .lock()
10027 .unwrap_or_else(std::sync::PoisonError::into_inner);
10028 queue.closed = true;
10031 queue.take_all()
10032 };
10033 for queued in queued {
10038 let _ = catch_unwind_contained(|| drop(queued));
10039 }
10040 self.shutdown_timers();
10041 }
10042
10043 fn scheduler_work_pending(&self) -> bool {
10047 self.active_drainers.load(Ordering::Acquire) != 0
10048 }
10049
10050 fn wait_until_scheduler_idle(&self) {
10051 self.timers.join();
10052 let mut idle = self
10053 .scheduler_idle_lock
10054 .lock()
10055 .unwrap_or_else(std::sync::PoisonError::into_inner);
10056 *idle += 1;
10062 while self.active_drainers.load(Ordering::Acquire) != 0 {
10063 idle = self
10064 .scheduler_idle
10065 .wait(idle)
10066 .unwrap_or_else(std::sync::PoisonError::into_inner);
10067 }
10068 *idle -= 1;
10069 }
10070}
10071
10072pub struct Sleep {
10077 deadline: Instant,
10078 inner: SleepInner,
10079}
10080
10081enum SleepInner {
10082 #[cfg(napi_runtime_os_threads)]
10083 Heap(HeapSleep),
10084 Host(HostSleep),
10085}
10086
10087#[cfg(napi_runtime_os_threads)]
10088struct HeapSleep {
10089 id: TimerId,
10090 executor: Weak<MultiThreadExecutor>,
10091 fired: Arc<AtomicBool>,
10092 registered: bool,
10093}
10094
10095struct HostSleep {
10096 id: TimerId,
10097 drivers: Arc<TimerDriverRegistry>,
10101 armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10105 registry: Arc<HostTimerRegistry>,
10108 fired: Arc<AtomicBool>,
10109 repoll: Arc<HostTimerRepoll>,
10113 pending: Option<HostTimerPendingGuard>,
10118}
10119
10120fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10121 let _ = catch_unwind_contained(|| driver.cancel(id));
10122 let _ = catch_unwind_contained(|| drop(driver));
10123}
10124
10125fn cancel_host_timer_drivers(
10126 id: TimerId,
10127 armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10128) {
10129 for (_, driver) in armed.drain() {
10130 cancel_host_timer_driver(id, driver);
10131 }
10132}
10133
10134impl Future for Sleep {
10135 type Output = ();
10136
10137 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10138 let deadline = self.deadline;
10139 let this = self.get_mut();
10140 match &mut this.inner {
10141 #[cfg(napi_runtime_os_threads)]
10142 SleepInner::Heap(heap) => {
10143 if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10144 if heap.registered {
10145 if let Some(executor) = heap.executor.upgrade() {
10148 executor.cancel_timer(heap.id);
10149 }
10150 heap.registered = false;
10151 }
10152 return Poll::Ready(());
10153 }
10154 let Some(executor) = heap.executor.upgrade() else {
10155 heap.fired.store(true, Ordering::SeqCst);
10156 heap.registered = false;
10157 return Poll::Ready(());
10158 };
10159 executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10160 heap.registered = true;
10161 if heap.fired.load(Ordering::SeqCst) {
10165 heap.registered = false;
10166 return Poll::Ready(());
10167 }
10168 Poll::Pending
10169 }
10170 SleepInner::Host(host) => {
10171 if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10172 cancel_host_timer_drivers(host.id, &mut host.armed);
10173 host.pending.take();
10174 return Poll::Ready(());
10175 }
10176 let repoll_waker = cx.waker().clone();
10181 let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10182 Ok(waker) => waker,
10183 Err(payload) => {
10184 let _ = catch_unwind_contained(|| drop(repoll_waker));
10185 resume_unwind(payload);
10186 }
10187 };
10188 host.repoll.register(repoll_waker);
10189 if !host.registry.register(
10190 host.id,
10191 deadline,
10192 registry_waker,
10193 &host.fired,
10194 &host.drivers,
10195 &host.repoll,
10196 ) {
10197 cancel_host_timer_drivers(host.id, &mut host.armed);
10198 if host.pending.take().is_none() {
10199 host.repoll.close();
10200 }
10201 return Poll::Ready(());
10202 }
10203 if host.pending.is_none() {
10204 host.drivers.register_pending(host.id, &host.repoll);
10212 host.pending = Some(HostTimerPendingGuard {
10213 registry: Arc::clone(&host.registry),
10214 drivers: Arc::clone(&host.drivers),
10215 repoll: Arc::clone(&host.repoll),
10216 id: host.id,
10217 });
10218 }
10219 let current = host.drivers.live();
10223 assert!(
10224 !current.as_slice().is_empty(),
10225 "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10226 that registered timer drivers (via \
10227 `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10228 one per importing env through `registerTimerHost`) have all been torn down, so this \
10229 `sleep_until` can never be woken."
10230 );
10231 let missing = current
10237 .as_slice()
10238 .iter()
10239 .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10240 .count();
10241 host.armed.reserve(missing);
10242 for (current_id, current_driver) in current.as_slice() {
10243 host
10244 .armed
10245 .entry(*current_id)
10246 .or_insert_with(|| Arc::clone(current_driver));
10247 }
10248 for (current_id, _) in current.as_slice() {
10249 let armed_driver = host
10250 .armed
10251 .get(current_id)
10252 .expect("every live timer driver must have cancellation ownership before callbacks");
10253 armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10254 }
10255 let current_ids = current
10256 .as_slice()
10257 .iter()
10258 .map(|(current_id, _)| *current_id)
10259 .collect::<FxHashSet<_>>();
10260 let retired = host
10261 .armed
10262 .keys()
10263 .filter(|armed_id| !current_ids.contains(armed_id))
10264 .copied()
10265 .collect::<Vec<_>>();
10266 for retired in retired {
10267 let old_driver = host
10268 .armed
10269 .remove(&retired)
10270 .expect("the retired timer driver must remain armed");
10271 cancel_host_timer_driver(host.id, old_driver);
10272 }
10273 Poll::Pending
10274 }
10275 }
10276 }
10277}
10278
10279impl Drop for Sleep {
10280 fn drop(&mut self) {
10281 match &mut self.inner {
10282 #[cfg(napi_runtime_os_threads)]
10283 SleepInner::Heap(heap) => {
10284 if heap.registered
10285 && let Some(executor) = heap.executor.upgrade()
10286 {
10287 executor.cancel_timer(heap.id);
10288 }
10289 }
10290 SleepInner::Host(host) => {
10291 host.repoll.close();
10294 cancel_host_timer_drivers(host.id, &mut host.armed);
10295 }
10297 }
10298 }
10299}
10300
10301pub fn sleep_until(deadline: Instant) -> Sleep {
10306 make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10307}
10308
10309fn make_sleep(
10313 backend: &RuntimeBackend,
10314 host_drivers: &Arc<TimerDriverRegistry>,
10315 deadline: Instant,
10316) -> Sleep {
10317 let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10318 match &backend.executor {
10319 #[cfg(napi_runtime_os_threads)]
10320 RuntimeExecutor::MultiThread(executor) => Sleep {
10321 deadline,
10322 inner: SleepInner::Heap(HeapSleep {
10323 id,
10324 executor: Arc::downgrade(executor),
10325 fired: Arc::new(AtomicBool::new(false)),
10326 registered: false,
10327 }),
10328 },
10329 RuntimeExecutor::CurrentThread(executor) => {
10330 assert!(
10338 host_drivers.has_live_driver(),
10339 "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10340 single-thread flavor delegates timers to the host event loop. Install one via \
10341 `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10342 a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10343 );
10344 Sleep {
10351 deadline,
10352 inner: SleepInner::Host(HostSleep {
10353 id,
10354 drivers: Arc::clone(host_drivers),
10355 armed: FxHashMap::default(),
10356 registry: Arc::clone(&executor.host_timers),
10357 fired: Arc::new(AtomicBool::new(false)),
10358 repoll: Arc::new(HostTimerRepoll::default()),
10359 pending: None,
10360 }),
10361 }
10362 }
10363 }
10364}
10365
10366#[derive(Clone)]
10367enum RuntimeExecutor {
10368 CurrentThread(Arc<CurrentThreadExecutor>),
10369 #[cfg(napi_runtime_os_threads)]
10370 MultiThread(Arc<MultiThreadExecutor>),
10371}
10372
10373enum WeakRuntimeExecutor {
10374 CurrentThread(Weak<CurrentThreadExecutor>),
10375 #[cfg(napi_runtime_os_threads)]
10376 MultiThread(Weak<MultiThreadExecutor>),
10377}
10378
10379impl WeakRuntimeExecutor {
10380 fn schedule(&self, runnable: Runnable) {
10381 match self {
10382 Self::CurrentThread(executor) => {
10383 if let Some(executor) = executor.upgrade() {
10384 executor.schedule(runnable);
10385 }
10386 }
10387 #[cfg(napi_runtime_os_threads)]
10388 Self::MultiThread(executor) => {
10389 if let Some(executor) = executor.upgrade() {
10390 executor.schedule(runnable);
10391 }
10392 }
10393 }
10394 }
10395}
10396
10397#[derive(Clone)]
10398struct RuntimeBackend {
10399 work: Arc<GenerationWork>,
10400 executor: RuntimeExecutor,
10401}
10402
10403impl RuntimeBackend {
10404 fn new(
10405 options: &RuntimeOptions,
10406 metrics: Arc<RuntimeMetrics>,
10407 ) -> Result<Self, RuntimeConfigError> {
10408 #[cfg(test)]
10409 if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10410 return Err(RuntimeConfigError(
10411 "injected async runtime backend creation failure".to_string(),
10412 ));
10413 }
10414
10415 let work = GenerationWork::new();
10416 let stop = Arc::new(GenerationStop::default());
10417 let executor = match options.flavor {
10418 RuntimeFlavor::CurrentThread => {
10419 let executor = CurrentThreadExecutor::with_generation(
10420 metrics,
10421 THREADLESS_BUILD,
10422 options.park_deadline,
10423 work.id,
10424 Arc::clone(&stop),
10425 );
10426 executor.attach_generation_work(&work);
10427 RuntimeExecutor::CurrentThread(Arc::new(executor))
10428 }
10429 RuntimeFlavor::MultiThread => {
10430 #[cfg(napi_runtime_os_threads)]
10431 {
10432 RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10433 options, metrics, work.id, stop,
10434 )?))
10435 }
10436 #[cfg(not(napi_runtime_os_threads))]
10437 {
10438 let _ = metrics;
10439 return Err(RuntimeConfigError(
10440 "the multi-thread runtime is unavailable in this threadless WebAssembly build \
10441 (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
10442 .to_string(),
10443 ));
10444 }
10445 }
10446 };
10447 Ok(Self { work, executor })
10448 }
10449
10450 #[cfg(test)]
10451 fn from_executor(executor: RuntimeExecutor) -> Self {
10452 let work = GenerationWork::new();
10453 if let RuntimeExecutor::CurrentThread(executor) = &executor {
10454 executor.attach_generation_work(&work);
10455 }
10456 Self { work, executor }
10457 }
10458
10459 fn generation(&self) -> u64 {
10460 self.work.id
10461 }
10462
10463 fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10464 match &self.executor {
10465 RuntimeExecutor::CurrentThread(executor) => {
10466 WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10467 }
10468 #[cfg(napi_runtime_os_threads)]
10469 RuntimeExecutor::MultiThread(executor) => {
10470 WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10471 }
10472 }
10473 }
10474
10475 fn schedule_submission(&self, runnable: Runnable) {
10479 match &self.executor {
10480 RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10481 #[cfg(napi_runtime_os_threads)]
10482 RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10483 }
10484 }
10485
10486 fn spawn_registered_blocking<F, T>(
10487 &self,
10488 function: F,
10489 registration: GenerationWorkGuard,
10490 metrics: &Arc<RuntimeMetrics>,
10491 ) -> JoinHandle<T>
10492 where
10493 F: FnOnce() -> T + Send + 'static,
10494 T: Send + 'static,
10495 {
10496 match &self.executor {
10497 RuntimeExecutor::CurrentThread(executor) => {
10498 let _ = metrics;
10499 executor.schedule_blocking(function, registration)
10500 }
10501 #[cfg(napi_runtime_os_threads)]
10502 RuntimeExecutor::MultiThread(executor) => {
10503 let generation = self.generation();
10504 let _ = metrics;
10505 let function = RegisteredBlockingFunction::new(function, registration, generation);
10506 executor.schedule_blocking_result(move || function.run())
10507 }
10508 }
10509 }
10510
10511 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10512 match &self.executor {
10513 RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10514 #[cfg(napi_runtime_os_threads)]
10515 RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10516 }
10517 }
10518
10519 fn generation_stop(&self) -> Arc<GenerationStop> {
10520 match &self.executor {
10521 RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10522 #[cfg(napi_runtime_os_threads)]
10523 RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10524 }
10525 }
10526
10527 fn begin_shutdown(&self) {
10528 let _generation = RuntimeGenerationGuard::enter(self.generation());
10529 self.work.close_and_abort();
10530 match &self.executor {
10531 RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10532 #[cfg(napi_runtime_os_threads)]
10533 RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10534 }
10535 }
10536
10537 fn wait_until_idle(&self) {
10538 match &self.executor {
10539 RuntimeExecutor::CurrentThread(executor) => {
10540 self.work.wait_until_idle_retiring(
10546 || executor.has_rejected_work(),
10547 || executor.retire_rejected_work(),
10548 );
10549 executor.wait_until_scheduler_idle();
10550 }
10551 #[cfg(napi_runtime_os_threads)]
10552 RuntimeExecutor::MultiThread(executor) => {
10553 self.work.wait_until_idle();
10554 executor.wait_until_scheduler_idle();
10555 }
10556 }
10557 }
10558
10559 fn work_pending(&self) -> bool {
10571 if self.work.has_active_work() {
10572 return true;
10573 }
10574 match &self.executor {
10575 RuntimeExecutor::CurrentThread(executor) => {
10576 executor.has_rejected_work() || executor.scheduler_work_pending()
10577 }
10578 #[cfg(napi_runtime_os_threads)]
10579 RuntimeExecutor::MultiThread(executor) => executor.scheduler_work_pending(),
10580 }
10581 }
10582
10583 #[cfg(napi_runtime_os_threads)]
10584 fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10585 match &self.executor {
10586 RuntimeExecutor::CurrentThread(_) => None,
10587 RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10588 }
10589 }
10590
10591 fn stop_identity(&self) -> RuntimeStopIdentity {
10592 RuntimeStopIdentity {
10593 generation: self.generation(),
10594 #[cfg(napi_runtime_os_threads)]
10595 executor_id: match &self.executor {
10596 RuntimeExecutor::CurrentThread(_) => None,
10597 RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10598 },
10599 }
10600 }
10601
10602 fn is_current(&self) -> bool {
10603 self.stop_identity().is_current()
10604 }
10605}
10606
10607#[derive(Clone, Copy)]
10608struct RuntimeStopIdentity {
10609 generation: u64,
10610 #[cfg(napi_runtime_os_threads)]
10611 executor_id: Option<u64>,
10612}
10613
10614impl RuntimeStopIdentity {
10615 fn is_current(self) -> bool {
10616 if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10617 return true;
10618 }
10619 #[cfg(napi_runtime_os_threads)]
10620 if let Some(executor_id) = self.executor_id
10621 && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10622 || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10623 {
10624 return true;
10625 }
10626 false
10627 }
10628}
10629
10630enum RuntimeLifecycle {
10631 Initial,
10632 StoppedBeforeFirstUse,
10633 Running(RuntimeBackend),
10634 Stopping(RuntimeStopIdentity),
10635 StoppingWithoutBackend(ZeroBackendShutdownTarget),
10636 Stopped,
10637}
10638
10639#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10640enum ZeroBackendShutdownTarget {
10641 StoppedBeforeFirstUse,
10642 Stopped,
10643}
10644
10645impl ZeroBackendShutdownTarget {
10646 fn into_lifecycle(self) -> RuntimeLifecycle {
10647 match self {
10648 Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10649 Self::Stopped => RuntimeLifecycle::Stopped,
10650 }
10651 }
10652}
10653
10654struct RuntimeState {
10655 options: RuntimeOptions,
10656 lifecycle: RuntimeLifecycle,
10657 rejected_drops: usize,
10658 draining: Option<ShutdownDrain>,
10683 phase_ever_opened: bool,
10703}
10704
10705impl RuntimeState {
10706 fn open_phase_one(&mut self, drain: ShutdownDrain) {
10710 self.phase_ever_opened = true;
10711 self.draining = Some(drain);
10712 }
10713
10714 fn forget_shutdown_handoff(&mut self) {
10731 debug_assert!(
10739 !matches!(
10740 self.draining,
10741 Some(ShutdownDrain::Backend(_) | ShutdownDrain::Joining(_) | ShutdownDrain::ZeroBackend(_))
10742 ),
10743 "a start must never publish over an outstanding backend drain"
10744 );
10745 self.draining = None;
10746 }
10747}
10748
10749enum ShutdownDrain {
10752 Backend(RuntimeBackend),
10755 Joining(Option<RuntimeBackend>),
10775 Settled,
10778 ZeroBackend(ZeroBackendShutdownTarget),
10795}
10796
10797#[cfg(test)]
10798thread_local! {
10799 static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10800 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10801 static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10802 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10803 static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10804 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10805 static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10806 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10807 static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10808 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10809 static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10810 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10811 static AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK:
10812 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10813 static BETWEEN_SHUTDOWN_PHASES_TEST_HOOK:
10814 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10815 static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10816 std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10817}
10818
10819#[cfg(test)]
10820fn run_before_runtime_submission_lock_test_hook() {
10821 if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10822 {
10823 hook();
10824 }
10825}
10826
10827#[cfg(test)]
10828fn run_after_async_work_registration_test_hook() {
10829 if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10830 {
10831 hook();
10832 }
10833}
10834
10835#[cfg(test)]
10836fn run_after_blocking_work_registration_test_hook() {
10837 if let Some(hook) =
10838 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10839 {
10840 hook();
10841 }
10842}
10843
10844#[cfg(test)]
10845fn run_after_current_thread_host_admission_test_hook() {
10846 if let Some(hook) =
10847 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10848 {
10849 hook();
10850 }
10851}
10852
10853#[cfg(test)]
10854fn run_before_initial_start_wait_test_hook() {
10855 if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10856 hook();
10857 }
10858}
10859
10860#[cfg(test)]
10861fn run_after_generation_stop_publication_test_hook() {
10862 if let Some(hook) =
10863 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10864 {
10865 hook();
10866 }
10867}
10868
10869#[cfg(test)]
10873fn run_after_shutdown_drain_taken_test_hook() {
10874 if let Some(hook) = AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10875 hook();
10876 }
10877}
10878
10879#[cfg(test)]
10883fn run_between_shutdown_phases_test_hook() {
10884 if let Some(hook) = BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10885 hook();
10886 }
10887}
10888
10889thread_local! {
10890 static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
10891 const { std::cell::Cell::new(0) };
10892}
10893
10894struct RejectedSubmissionDropContext;
10895
10896impl RejectedSubmissionDropContext {
10897 fn enter() -> Self {
10898 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
10899 Self
10900 }
10901
10902 fn is_current() -> bool {
10903 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
10904 }
10905}
10906
10907impl Drop for RejectedSubmissionDropContext {
10908 fn drop(&mut self) {
10909 IN_REJECTED_SUBMISSION_DROP.with(|depth| {
10910 depth.set(
10911 depth
10912 .get()
10913 .checked_sub(1)
10914 .expect("rejected submission drop depth underflow"),
10915 );
10916 });
10917 }
10918}
10919
10920struct RuntimeController {
10921 state: Mutex<RuntimeState>,
10922 lifecycle_changed: Condvar,
10923 metrics: Arc<RuntimeMetrics>,
10924 current_backend: ArcSwapOption<RuntimeBackend>,
10930}
10931
10932struct RejectedSubmissionGuard<'a> {
10933 controller: &'a RuntimeController,
10934}
10935
10936impl Drop for RejectedSubmissionGuard<'_> {
10937 fn drop(&mut self) {
10938 let mut state = self
10939 .controller
10940 .state
10941 .lock()
10942 .unwrap_or_else(std::sync::PoisonError::into_inner);
10943 state.rejected_drops = state
10944 .rejected_drops
10945 .checked_sub(1)
10946 .expect("rejected submission drop count underflow");
10947 self.controller.lifecycle_changed.notify_all();
10948 }
10949}
10950
10951impl RuntimeController {
10952 fn new() -> Self {
10953 let options = RuntimeOptions::default()
10954 .validate()
10955 .expect("default async runtime options must be valid");
10956 Self {
10957 state: Mutex::new(RuntimeState {
10958 options,
10959 lifecycle: RuntimeLifecycle::Initial,
10960 rejected_drops: 0,
10961 draining: None,
10962 phase_ever_opened: false,
10963 }),
10964 lifecycle_changed: Condvar::new(),
10965 metrics: Arc::new(RuntimeMetrics::default()),
10966 current_backend: ArcSwapOption::empty(),
10967 }
10968 }
10969
10970 fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10971 let options = options.validate()?;
10972 let mut state = self
10973 .state
10974 .lock()
10975 .unwrap_or_else(std::sync::PoisonError::into_inner);
10976 Self::ensure_configuration_mutable(&state)?;
10977 state.options = options;
10978 Ok(())
10979 }
10980
10981 fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10982 self.configure_partial_inner(patch, |_| {})
10983 }
10984
10985 fn configure_partial_inner(
10986 &self,
10987 patch: RuntimeOptionsPatch,
10988 after_merge: impl FnOnce(&RuntimeOptions),
10989 ) -> Result<(), RuntimeConfigError> {
10990 let mut state = self
10991 .state
10992 .lock()
10993 .unwrap_or_else(std::sync::PoisonError::into_inner);
10994 Self::ensure_configuration_mutable(&state)?;
10995
10996 let mut options = state.options.clone();
10997 patch.apply_to(&mut options);
10998 after_merge(&options);
10999 let options = options.validate()?;
11000 state.options = options;
11001 Ok(())
11002 }
11003
11004 fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11005 if matches!(
11006 &state.lifecycle,
11007 RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
11008 ) {
11009 return Ok(());
11010 }
11011 Err(RuntimeConfigError(
11012 "the async runtime configuration is frozen; configure it before the first async call"
11013 .to_string(),
11014 ))
11015 }
11016
11017 fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11018 let Some(active_generation) = active_runtime_generation() else {
11019 return Ok(());
11020 };
11021 let lifecycle_generation = match &state.lifecycle {
11022 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11023 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11024 RuntimeLifecycle::Initial
11025 | RuntimeLifecycle::StoppedBeforeFirstUse
11026 | RuntimeLifecycle::StoppingWithoutBackend(_)
11027 | RuntimeLifecycle::Stopped => None,
11028 };
11029 if lifecycle_generation == Some(active_generation) {
11030 return Ok(());
11031 }
11032 Err(RuntimeConfigError(
11033 "cannot access or control the async runtime from work owned by a retired generation"
11034 .to_string(),
11035 ))
11036 }
11037
11038 fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
11039 Self::ensure_active_generation_current(state)?;
11040 match &state.lifecycle {
11041 RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
11042 RuntimeLifecycle::StoppedBeforeFirstUse
11043 | RuntimeLifecycle::Stopping(_)
11044 | RuntimeLifecycle::StoppingWithoutBackend(_)
11045 | RuntimeLifecycle::Stopped => {
11046 return Err(RuntimeConfigError(
11047 "the async runtime is stopped; call start before submitting work".to_string(),
11048 ));
11049 }
11050 RuntimeLifecycle::Initial => {}
11051 }
11052 if state.rejected_drops != 0 {
11053 return Err(RuntimeConfigError(
11054 "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
11055 ));
11056 }
11057 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11058 state.lifecycle = RuntimeLifecycle::Running(backend.clone());
11059 state.forget_shutdown_handoff();
11060 self.current_backend.store(Some(Arc::new(backend.clone())));
11061 Ok(backend)
11062 }
11063
11064 fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
11065 let mut state = self
11066 .state
11067 .lock()
11068 .unwrap_or_else(std::sync::PoisonError::into_inner);
11069 self.backend_locked(&mut state)
11070 }
11071
11072 fn backend(&self) -> RuntimeBackend {
11073 self.try_backend().unwrap_or_else(|error| panic!("{error}"))
11074 }
11075
11076 fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
11077 let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
11078 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11079 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11080 RuntimeLifecycle::Initial
11081 | RuntimeLifecycle::StoppedBeforeFirstUse
11082 | RuntimeLifecycle::StoppingWithoutBackend(_)
11083 | RuntimeLifecycle::Stopped => None,
11084 });
11085 state.rejected_drops += 1;
11086 generation
11087 }
11088
11089 #[inline]
11097 fn fast_register<R>(
11098 &self,
11099 register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
11100 ) -> Option<(Arc<RuntimeBackend>, R)> {
11101 let guard = self.current_backend.load();
11102 let backend = guard.as_ref()?; if let Some(active) = active_runtime_generation()
11104 && active != backend.generation()
11105 {
11106 return None; }
11108 let registration = register(&backend.work)?; Some((Arc::clone(backend), registration))
11112 }
11113
11114 fn try_spawn_tracked<F, T>(
11115 &self,
11116 future: F,
11117 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11118 where
11119 F: Future<Output = T> + Send + 'static,
11120 T: Send + 'static,
11121 {
11122 #[cfg(test)]
11123 run_before_runtime_submission_lock_test_hook();
11124
11125 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
11126 #[cfg(test)]
11127 run_after_async_work_registration_test_hook();
11128 return Ok(spawn_registered(
11129 &backend,
11130 Arc::clone(&self.metrics),
11131 future,
11132 registration,
11133 ));
11134 }
11135
11136 let (backend, registration) = {
11137 let mut state = self
11138 .state
11139 .lock()
11140 .unwrap_or_else(std::sync::PoisonError::into_inner);
11141 let backend = match self.backend_locked(&mut state) {
11142 Ok(backend) => backend,
11143 Err(error) => {
11144 let generation = Self::begin_rejected_drop(&mut state);
11145 return Err((error, future, generation));
11146 }
11147 };
11148 let Some(registration) = backend.work.try_register_async() else {
11149 let generation = Self::begin_rejected_drop(&mut state);
11150 return Err((
11151 RuntimeConfigError(
11152 "the async runtime is stopped; call start before submitting work".to_string(),
11153 ),
11154 future,
11155 generation,
11156 ));
11157 };
11158 (backend, registration)
11159 };
11160 #[cfg(test)]
11161 run_after_async_work_registration_test_hook();
11162 Ok(spawn_registered(
11163 &backend,
11164 Arc::clone(&self.metrics),
11165 future,
11166 registration,
11167 ))
11168 }
11169
11170 fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11171 where
11172 F: Future<Output = T> + Send + 'static,
11173 T: Send + 'static,
11174 {
11175 match self.try_spawn_tracked(future) {
11176 Ok(handle) => Ok(handle),
11177 Err((error, future, _generation)) => {
11178 drop(RejectedSubmissionGuard { controller: self });
11179 Err((error, future))
11180 }
11181 }
11182 }
11183
11184 fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
11185 where
11186 F: Future<Output = T> + Send + 'static,
11187 T: Send + 'static,
11188 {
11189 match self.try_spawn_tracked(future) {
11190 Ok(handle) => handle,
11191 Err((error, future, generation)) => {
11192 let _rejection = RejectedSubmissionGuard { controller: self };
11193 let _drop_context = RejectedSubmissionDropContext::enter();
11194 let _generation = generation.map(RuntimeGenerationGuard::enter);
11195 let _ = catch_unwind_contained(|| drop(future));
11196 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11197 message: error.to_string(),
11198 }))))
11199 }
11200 }
11201 }
11202
11203 fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
11204 where
11205 F: Future<Output = ()> + Send + 'static,
11206 {
11207 match self.try_spawn(future) {
11208 Ok(handle) => {
11209 handle.detach();
11210 Ok(())
11211 }
11212 Err((_, future)) => Err(future),
11213 }
11214 }
11215
11216 fn try_spawn_blocking_tracked<F, T>(
11217 &self,
11218 function: F,
11219 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11220 where
11221 F: FnOnce() -> T + Send + 'static,
11222 T: Send + 'static,
11223 {
11224 #[cfg(test)]
11225 run_before_runtime_submission_lock_test_hook();
11226
11227 if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
11228 #[cfg(test)]
11229 run_after_blocking_work_registration_test_hook();
11230 return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
11231 }
11232
11233 let (backend, registration) = {
11234 let mut state = self
11235 .state
11236 .lock()
11237 .unwrap_or_else(std::sync::PoisonError::into_inner);
11238 let backend = match self.backend_locked(&mut state) {
11239 Ok(backend) => backend,
11240 Err(error) => {
11241 let generation = Self::begin_rejected_drop(&mut state);
11242 return Err((error, function, generation));
11243 }
11244 };
11245 let Some(registration) = backend.work.try_register_work() else {
11246 let generation = Self::begin_rejected_drop(&mut state);
11247 return Err((
11248 RuntimeConfigError(
11249 "the async runtime is stopped; call start before submitting work".to_string(),
11250 ),
11251 function,
11252 generation,
11253 ));
11254 };
11255 (backend, registration)
11256 };
11257 #[cfg(test)]
11258 run_after_blocking_work_registration_test_hook();
11259 Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11260 }
11261
11262 fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11263 where
11264 F: FnOnce() -> T + Send + 'static,
11265 T: Send + 'static,
11266 {
11267 match self.try_spawn_blocking_tracked(function) {
11268 Ok(handle) => Ok(handle),
11269 Err((error, function, _generation)) => {
11270 drop(RejectedSubmissionGuard { controller: self });
11271 Err((error, function))
11272 }
11273 }
11274 }
11275
11276 fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11277 where
11278 F: FnOnce() -> T + Send + 'static,
11279 T: Send + 'static,
11280 {
11281 match self.try_spawn_blocking_tracked(function) {
11282 Ok(handle) => handle,
11283 Err((error, function, generation)) => {
11284 let _rejection = RejectedSubmissionGuard { controller: self };
11285 let _drop_context = RejectedSubmissionDropContext::enter();
11286 let _generation = generation.map(RuntimeGenerationGuard::enter);
11287 let _ = catch_unwind_contained(|| drop(function));
11288 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11289 message: error.to_string(),
11290 }))))
11291 }
11292 }
11293 }
11294
11295 #[cfg(test)]
11296 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11297 self
11298 .try_block_on(future)
11299 .unwrap_or_else(|error| panic!("{error}"))
11300 }
11301
11302 fn try_block_on_scope(
11303 &self,
11304 ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11305 let mut state = self
11306 .state
11307 .lock()
11308 .unwrap_or_else(std::sync::PoisonError::into_inner);
11309 let backend = match self.backend_locked(&mut state) {
11310 Ok(backend) => backend,
11311 Err(error) => {
11312 let generation = Self::begin_rejected_drop(&mut state);
11313 return Err((error, generation));
11314 }
11315 };
11316 let Some(registration) = backend.work.try_register_work() else {
11317 let generation = Self::begin_rejected_drop(&mut state);
11318 return Err((
11319 RuntimeConfigError(
11320 "the async runtime is stopped; call start before submitting work".to_string(),
11321 ),
11322 generation,
11323 ));
11324 };
11325 Ok((backend, registration))
11326 }
11327
11328 fn try_block_on(
11329 &self,
11330 future: Pin<&mut dyn Future<Output = ()>>,
11331 ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11332 let (backend, registration) = match self.try_block_on_scope() {
11333 Ok(scope) => scope,
11334 Err((error, _generation)) => {
11335 drop(RejectedSubmissionGuard { controller: self });
11336 return Err(error);
11337 }
11338 };
11339 let _registration = registration;
11340 let _generation = RuntimeGenerationGuard::enter(backend.generation());
11341 Ok(backend.block_on(future))
11342 }
11343
11344 fn start(&self) -> Result<(), RuntimeConfigError> {
11345 let mut state = self
11346 .state
11347 .lock()
11348 .unwrap_or_else(std::sync::PoisonError::into_inner);
11349 loop {
11350 Self::ensure_active_generation_current(&state)?;
11351 match &state.lifecycle {
11352 RuntimeLifecycle::Initial => {
11356 if state.rejected_drops == 0 {
11357 state.forget_shutdown_handoff();
11362 return Ok(());
11363 }
11364 if RejectedSubmissionDropContext::is_current() {
11365 return Err(RuntimeConfigError(
11366 "cannot start the async runtime while a rejected submission is being destroyed"
11367 .to_string(),
11368 ));
11369 }
11370 #[cfg(test)]
11371 run_before_initial_start_wait_test_hook();
11372 state = self
11373 .lifecycle_changed
11374 .wait(state)
11375 .unwrap_or_else(std::sync::PoisonError::into_inner);
11376 }
11377 RuntimeLifecycle::Running(_) => {
11378 state.forget_shutdown_handoff();
11379 return Ok(());
11380 }
11381 RuntimeLifecycle::Stopping(identity) => {
11382 if identity.is_current() {
11383 return Err(RuntimeConfigError(
11384 "cannot restart the async runtime from work in the generation being stopped"
11385 .to_string(),
11386 ));
11387 }
11388 state = self
11389 .lifecycle_changed
11390 .wait(state)
11391 .unwrap_or_else(std::sync::PoisonError::into_inner);
11392 }
11393 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11394 if RejectedSubmissionDropContext::is_current() {
11395 return Err(RuntimeConfigError(
11396 "cannot restart the async runtime while a rejected submission is being destroyed"
11397 .to_string(),
11398 ));
11399 }
11400 state = self
11401 .lifecycle_changed
11402 .wait(state)
11403 .unwrap_or_else(std::sync::PoisonError::into_inner);
11404 }
11405 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11406 if state.rejected_drops == 0 {
11407 if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11408 state.lifecycle = RuntimeLifecycle::Initial;
11409 state.forget_shutdown_handoff();
11410 return Ok(());
11411 }
11412 break;
11413 }
11414 if RejectedSubmissionDropContext::is_current() {
11415 return Err(RuntimeConfigError(
11416 "cannot restart the async runtime while a rejected submission is being destroyed"
11417 .to_string(),
11418 ));
11419 }
11420 state = self
11421 .lifecycle_changed
11422 .wait(state)
11423 .unwrap_or_else(std::sync::PoisonError::into_inner);
11424 }
11425 }
11426 }
11427 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11428 self.current_backend.store(Some(Arc::new(backend.clone())));
11429 state.lifecycle = RuntimeLifecycle::Running(backend);
11430 state.forget_shutdown_handoff();
11431 Ok(())
11432 }
11433
11434 fn options(&self) -> RuntimeOptions {
11435 self
11436 .state
11437 .lock()
11438 .unwrap_or_else(std::sync::PoisonError::into_inner)
11439 .options
11440 .clone()
11441 }
11442
11443 fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11444 let state = self
11445 .state
11446 .lock()
11447 .unwrap_or_else(std::sync::PoisonError::into_inner);
11448 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11449 return None;
11450 };
11451 match &backend.executor {
11452 RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11453 #[cfg(napi_runtime_os_threads)]
11454 RuntimeExecutor::MultiThread(_) => None,
11455 }
11456 }
11457
11458 fn request_current_thread_drain(&self) {
11459 if let Some(executor) = self.running_current_thread_executor() {
11460 executor.request_drain_if_queued();
11461 }
11462 }
11463
11464 fn retire_rejected_current_thread_work(&self) {
11465 if let Some(executor) = self.running_current_thread_executor() {
11466 executor.retire_rejected_work_off_waker_stack();
11467 }
11468 }
11469
11470 fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11471 let executor = {
11472 let state = self
11473 .state
11474 .lock()
11475 .unwrap_or_else(std::sync::PoisonError::into_inner);
11476 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11477 return;
11478 };
11479 match &backend.executor {
11480 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11481 #[cfg(napi_runtime_os_threads)]
11482 RuntimeExecutor::MultiThread(_) => return,
11483 }
11484 };
11485 executor.cancel_host_dispatch(failure);
11486 }
11487
11488 #[cfg(test)]
11489 fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11490 let executor = {
11491 let state = self
11492 .state
11493 .lock()
11494 .unwrap_or_else(std::sync::PoisonError::into_inner);
11495 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11496 return;
11497 };
11498 match &backend.executor {
11499 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11500 #[cfg(napi_runtime_os_threads)]
11501 RuntimeExecutor::MultiThread(_) => return,
11502 }
11503 };
11504 executor.cancel_host_dispatch_for_test(dispatch);
11505 }
11506
11507 fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11508 let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11509 self.drive_current_thread_dispatch_with_lease(dispatch, true)
11510 }
11511
11512 fn drive_current_thread_dispatch_with_lease(
11513 &self,
11514 dispatch: u64,
11515 mark_serviced: bool,
11516 ) -> Option<CurrentThreadTaskCallbackLease> {
11517 let mut host_turn = {
11518 let state = self
11519 .state
11520 .lock()
11521 .unwrap_or_else(std::sync::PoisonError::into_inner);
11522 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11523 return None;
11524 };
11525 let executor = match &backend.executor {
11526 RuntimeExecutor::CurrentThread(executor) => executor,
11527 #[cfg(napi_runtime_os_threads)]
11528 RuntimeExecutor::MultiThread(_) => return None,
11529 };
11530 let host_turn = executor.try_admit_host_turn(dispatch)?;
11531 if mark_serviced {
11532 CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11533 }
11534 host_turn
11535 };
11536 #[cfg(test)]
11537 run_after_current_thread_host_admission_test_hook();
11538 host_turn.drive();
11539 Some(CurrentThreadTaskCallbackLease::new(host_turn))
11540 }
11541
11542 #[cfg(test)]
11543 fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11544 self
11545 .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11546 .is_some()
11547 }
11548
11549 fn begin_shutdown(&self) -> Result<bool, RuntimeConfigError> {
11588 let backend = {
11589 let mut state = self
11590 .state
11591 .lock()
11592 .unwrap_or_else(std::sync::PoisonError::into_inner);
11593 Self::ensure_active_generation_current(&state)?;
11599 if let Some(drain) = &state.draining {
11600 let outstanding = match drain {
11601 ShutdownDrain::Backend(backend) => Some(backend.clone()),
11605 ShutdownDrain::Joining(backend) => backend.clone(),
11606 ShutdownDrain::ZeroBackend(_) => {
11614 if RejectedSubmissionDropContext::is_current() {
11615 return Err(RuntimeConfigError(
11616 "cannot shut down the async runtime while a rejected submission is being destroyed"
11617 .to_string(),
11618 ));
11619 }
11620 return Ok(state.rejected_drops != 0);
11621 }
11622 ShutdownDrain::Settled => None,
11623 };
11624 drop(state);
11634 return Ok(outstanding.is_some_and(|backend| backend.work_pending()));
11635 }
11636 loop {
11637 Self::ensure_active_generation_current(&state)?;
11638 match &state.lifecycle {
11639 RuntimeLifecycle::Initial => {
11640 if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11641 return Err(RuntimeConfigError(
11642 "cannot shut down the async runtime while a rejected submission is being destroyed"
11643 .to_string(),
11644 ));
11645 }
11646 let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11647 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11648 if state.rejected_drops != 0 {
11649 state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11656 self.lifecycle_changed.notify_all();
11657 return Ok(true);
11658 }
11659 state.lifecycle = target.into_lifecycle();
11660 state.open_phase_one(ShutdownDrain::Settled);
11661 self.lifecycle_changed.notify_all();
11662 return Ok(false);
11663 }
11664 RuntimeLifecycle::Running(backend) => {
11665 if backend.is_current() {
11666 return Err(RuntimeConfigError(
11667 "cannot shut down the async runtime from work running on that runtime".to_string(),
11668 ));
11669 }
11670 let identity = backend.stop_identity();
11671 let stop = backend.generation_stop();
11672 let stop_publication = stop.publication_guard();
11677 stop_publication.publish_stopping();
11678 let RuntimeLifecycle::Running(backend) =
11679 std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11680 else {
11681 unreachable!();
11682 };
11683 self.current_backend.store(None);
11684 state.open_phase_one(ShutdownDrain::Backend(backend.clone()));
11688 drop(stop_publication);
11692 break backend;
11693 }
11694 RuntimeLifecycle::Stopping(identity) => {
11695 if identity.is_current() {
11696 return Err(RuntimeConfigError(
11697 "cannot wait for async runtime shutdown from work in the generation being stopped"
11698 .to_string(),
11699 ));
11700 }
11701 state = self
11702 .lifecycle_changed
11703 .wait(state)
11704 .unwrap_or_else(std::sync::PoisonError::into_inner);
11705 }
11706 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11707 if RejectedSubmissionDropContext::is_current() {
11708 return Err(RuntimeConfigError(
11709 "cannot shut down the async runtime while a rejected submission is being destroyed"
11710 .to_string(),
11711 ));
11712 }
11713 state = self
11714 .lifecycle_changed
11715 .wait(state)
11716 .unwrap_or_else(std::sync::PoisonError::into_inner);
11717 }
11718 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11719 if state.rejected_drops == 0 {
11720 state.open_phase_one(ShutdownDrain::Settled);
11721 return Ok(false);
11722 }
11723 if RejectedSubmissionDropContext::is_current() {
11724 return Err(RuntimeConfigError(
11725 "cannot shut down the async runtime while a rejected submission is being destroyed"
11726 .to_string(),
11727 ));
11728 }
11729 let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11730 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11731 } else {
11732 ZeroBackendShutdownTarget::Stopped
11733 };
11734 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11738 state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11739 self.lifecycle_changed.notify_all();
11740 return Ok(true);
11741 }
11742 }
11743 }
11744 };
11745
11746 #[cfg(test)]
11747 run_after_generation_stop_publication_test_hook();
11748 backend.begin_shutdown();
11749 Ok(backend.work_pending())
11750 }
11751
11752 fn runtime_work_pending(&self) -> bool {
11761 let backend = {
11762 let state = self
11763 .state
11764 .lock()
11765 .unwrap_or_else(std::sync::PoisonError::into_inner);
11766 match (&state.draining, &state.lifecycle) {
11767 (Some(ShutdownDrain::Backend(backend) | ShutdownDrain::Joining(Some(backend))), _) => {
11770 backend.clone()
11771 }
11772 (Some(ShutdownDrain::ZeroBackend(_)), _) => return state.rejected_drops != 0,
11778 (None, RuntimeLifecycle::Running(backend)) => backend.clone(),
11779 (Some(ShutdownDrain::Joining(None) | ShutdownDrain::Settled), _) | (None, _) => {
11784 return false;
11785 }
11786 }
11787 };
11788 backend.work_pending()
11789 }
11790
11791 fn finish_shutdown(&self) -> Result<(), RuntimeConfigError> {
11824 const STOPPING_WAIT_ERROR: &str =
11825 "cannot wait for async runtime shutdown from work in the generation being stopped";
11826 const REJECTED_DROP_ERROR: &str =
11827 "cannot shut down the async runtime while a rejected submission is being destroyed";
11828 const NO_PHASE_ERROR: &str =
11829 "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown";
11830
11831 let backend = {
11832 let mut state = self
11833 .state
11834 .lock()
11835 .unwrap_or_else(std::sync::PoisonError::into_inner);
11836 match &state.draining {
11839 Some(ShutdownDrain::Settled) => {
11840 state.draining = None;
11842 return Ok(());
11843 }
11844 Some(ShutdownDrain::Backend(backend)) => {
11845 if backend.is_current() {
11846 return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
11847 }
11848 }
11849 Some(ShutdownDrain::ZeroBackend(_)) => loop {
11857 let target = match (&state.draining, &state.lifecycle) {
11858 (
11859 Some(ShutdownDrain::ZeroBackend(parked)),
11860 RuntimeLifecycle::StoppingWithoutBackend(published),
11861 ) if published == parked => *parked,
11862 _ => return Ok(()),
11866 };
11867 if state.rejected_drops == 0 {
11868 state.draining = None;
11873 state.lifecycle = target.into_lifecycle();
11874 self.lifecycle_changed.notify_all();
11875 return Ok(());
11876 }
11877 if RejectedSubmissionDropContext::is_current() {
11878 return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
11879 }
11880 state = self
11885 .lifecycle_changed
11886 .wait(state)
11887 .unwrap_or_else(std::sync::PoisonError::into_inner);
11888 },
11889 Some(ShutdownDrain::Joining(_)) | None => {
11894 if !state.phase_ever_opened {
11895 return Err(RuntimeConfigError(NO_PHASE_ERROR.to_string()));
11899 }
11900 loop {
11908 match &state.lifecycle {
11909 RuntimeLifecycle::Stopping(identity) => {
11910 if identity.is_current() {
11911 return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
11912 }
11913 }
11914 RuntimeLifecycle::StoppingWithoutBackend(_) => {
11915 if RejectedSubmissionDropContext::is_current() {
11916 return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
11917 }
11918 }
11919 RuntimeLifecycle::StoppedBeforeFirstUse
11924 | RuntimeLifecycle::Stopped
11925 | RuntimeLifecycle::Initial
11926 | RuntimeLifecycle::Running(_) => return Ok(()),
11927 }
11928 state = self
11929 .lifecycle_changed
11930 .wait(state)
11931 .unwrap_or_else(std::sync::PoisonError::into_inner);
11932 }
11933 }
11934 }
11935 let Some(ShutdownDrain::Backend(backend)) = state.draining.take() else {
11943 unreachable!();
11944 };
11945 state.draining = Some(ShutdownDrain::Joining(Some(backend.clone())));
11946 backend
11947 };
11948
11949 #[cfg(test)]
11950 run_after_shutdown_drain_taken_test_hook();
11951 backend.wait_until_idle();
11952
11953 #[cfg(napi_runtime_os_threads)]
11954 let worker_lifecycle = backend.worker_lifecycle();
11955 {
11963 let mut state = self
11964 .state
11965 .lock()
11966 .unwrap_or_else(std::sync::PoisonError::into_inner);
11967 debug_assert!(
11968 matches!(state.draining, Some(ShutdownDrain::Joining(Some(_)))),
11969 "nothing may disturb an in-flight drain marker before its publication"
11970 );
11971 state.draining = Some(ShutdownDrain::Joining(None));
11972 }
11973 drop(backend);
11974
11975 #[cfg(napi_runtime_os_threads)]
11976 if let Some(worker_lifecycle) = worker_lifecycle {
11977 worker_lifecycle.wait_for_all_workers();
11978 }
11979
11980 let mut state = self
11981 .state
11982 .lock()
11983 .unwrap_or_else(std::sync::PoisonError::into_inner);
11984 while state.rejected_drops != 0 {
11985 state = self
11986 .lifecycle_changed
11987 .wait(state)
11988 .unwrap_or_else(std::sync::PoisonError::into_inner);
11989 }
11990 debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
11991 debug_assert!(
11992 matches!(state.draining, Some(ShutdownDrain::Joining(None))),
11993 "nothing may retire an in-flight drain marker but its own publication"
11994 );
11995 state.draining = None;
11999 state.lifecycle = RuntimeLifecycle::Stopped;
12000 self.lifecycle_changed.notify_all();
12001 Ok(())
12002 }
12003
12004 fn shutdown(&self) -> Result<(), RuntimeConfigError> {
12017 self.begin_shutdown()?;
12018 #[cfg(test)]
12019 run_between_shutdown_phases_test_hook();
12020 self.finish_shutdown()
12021 }
12022}
12023
12024static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
12025
12026pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
12027 RUNTIME.configure(options)
12028}
12029
12030pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
12031 RUNTIME.configure_partial(patch)
12032}
12033
12034pub fn configured_options() -> RuntimeOptions {
12035 RUNTIME.options()
12036}
12037
12038pub fn is_multi_threaded() -> bool {
12039 RUNTIME.options().flavor == RuntimeFlavor::MultiThread
12040}
12041
12042fn spawn_registered<F, T>(
12043 backend: &RuntimeBackend,
12044 metrics: Arc<RuntimeMetrics>,
12045 future: F,
12046 registration: (AbortRegistration, GenerationWorkGuard),
12047) -> JoinHandle<T>
12048where
12049 F: Future<Output = T> + Send + 'static,
12050 T: Send + 'static,
12051{
12052 metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
12053 let generation = backend.generation();
12054 let (abort_registration, work_registration) = registration;
12055 #[cfg(napi_runtime_os_threads)]
12056 let dependency = Arc::new(TaskDependency::new_task(generation));
12057 #[cfg(napi_runtime_os_threads)]
12058 let task_dependency = Arc::clone(&dependency);
12059 let wrapped = async move {
12060 let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
12061 let mut abortable = std::pin::pin!(abortable);
12062 let polled = futures::future::poll_fn(|cx| {
12063 #[cfg(napi_runtime_os_threads)]
12064 task_dependency.begin_poll();
12065 #[cfg(napi_runtime_os_threads)]
12066 let _dependency = TaskDependencyGuard::enter(&task_dependency);
12067 abortable.as_mut().poll(cx)
12068 });
12069 match AssertUnwindSafe(polled).catch_unwind().await {
12070 Ok(Ok(output)) => {
12071 metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
12072 Ok(ContainedTaskOutput::new(output, generation))
12073 }
12074 Ok(Err(_)) => Err(JoinError {
12075 message: "async runtime stopped before the task completed".to_string(),
12076 }),
12077 Err(panic) => {
12078 metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
12079 Err(panic_payload_to_join_error(panic))
12080 }
12081 }
12082 };
12083 let scheduler = backend.downgrade_executor();
12084 let (runnable, task) = async_task::spawn(
12088 RegisteredTaskFuture::new(wrapped, work_registration, generation),
12089 move |runnable| {
12090 scheduler.schedule(runnable);
12091 },
12092 );
12093 backend.schedule_submission(runnable);
12094 JoinHandle(JoinHandleInner::Task {
12095 task: task.fallible(),
12096 awaiter: None,
12097 #[cfg(napi_runtime_os_threads)]
12098 dependency,
12099 })
12100}
12101
12102pub fn spawn<F, T>(future: F) -> JoinHandle<T>
12103where
12104 F: Future<Output = T> + Send + 'static,
12105 T: Send + 'static,
12106{
12107 RUNTIME.spawn(future)
12108}
12109
12110pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12122where
12123 F: Future<Output = T> + Send + 'static,
12124 T: Send + 'static,
12125{
12126 RUNTIME.try_spawn(future)
12127}
12128
12129pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
12130where
12131 F: Future<Output = ()> + Send + 'static,
12132{
12133 RUNTIME.try_spawn_detached(future)
12134}
12135
12136pub fn spawn_detached<F>(future: F)
12137where
12138 F: Future<Output = ()> + Send + 'static,
12139{
12140 spawn(future).detach();
12141}
12142
12143pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
12144where
12145 F: FnOnce() -> T + Send + 'static,
12146 T: Send + 'static,
12147{
12148 RUNTIME.spawn_blocking(function)
12149}
12150
12151pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12159where
12160 F: FnOnce() -> T + Send + 'static,
12161 T: Send + 'static,
12162{
12163 RUNTIME.try_spawn_blocking(function)
12164}
12165
12166fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
12167 let (backend, registration) = match controller.try_block_on_scope() {
12168 Ok(scope) => scope,
12169 Err((error, generation)) => {
12170 let _rejection = RejectedSubmissionGuard { controller };
12171 let _drop_context = RejectedSubmissionDropContext::enter();
12172 let _generation = generation.map(RuntimeGenerationGuard::enter);
12173 let _ = catch_unwind_contained(|| drop(future));
12174 panic!("{error}");
12175 }
12176 };
12177 let generation = backend.generation();
12178 let _registration = registration;
12179 let _generation = RuntimeGenerationGuard::enter(generation);
12180 let mut output = None;
12181 {
12182 let future = ContainedFuture::new(future, generation);
12183 let mut erased = std::pin::pin!(async {
12184 output = Some(future.await);
12185 });
12186 let _ = backend.block_on(erased.as_mut());
12187 }
12192 output.expect("async runtime returned before the future completed")
12193}
12194
12195pub fn block_on<F: Future>(future: F) -> F::Output {
12196 block_on_with_controller(&RUNTIME, future)
12197}
12198
12199pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
12200 try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
12201}
12202
12203pub fn try_block_on_dyn(
12213 future: Pin<&mut dyn Future<Output = ()>>,
12214) -> Result<(), RuntimeConfigError> {
12215 match RUNTIME.try_block_on(future)? {
12216 BlockOnOutcome::Completed => Ok(()),
12217 BlockOnOutcome::Stopped => Err(RuntimeConfigError(
12218 "the async runtime stopped before block_on completed its future".to_string(),
12219 )),
12220 }
12221}
12222
12223pub fn start() -> Result<(), RuntimeConfigError> {
12224 RUNTIME.start()
12225}
12226
12227pub fn shutdown() -> Result<(), RuntimeConfigError> {
12235 RUNTIME.shutdown()
12236}
12237
12238pub fn begin_shutdown() -> Result<bool, RuntimeConfigError> {
12261 RUNTIME.begin_shutdown()
12262}
12263
12264pub fn runtime_work_pending() -> bool {
12269 RUNTIME.runtime_work_pending()
12270}
12271
12272pub fn finish_shutdown() -> Result<(), RuntimeConfigError> {
12285 RUNTIME.finish_shutdown()
12286}
12287
12288fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
12289 for failure in completion.failed_dispatches {
12290 RUNTIME.cancel_current_thread_dispatch(failure);
12291 }
12292 if completion.redispatch {
12293 RUNTIME.request_current_thread_drain();
12294 }
12295 RUNTIME.retire_rejected_current_thread_work();
12301}
12302
12303pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12312 complete_current_thread_task_delivery(
12313 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
12314 );
12315}
12316
12317pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12323 complete_current_thread_task_delivery(
12324 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
12325 );
12326}
12327
12328pub fn cancel_current_thread_task_dispatch(delivery: u64) {
12335 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
12336}
12337
12338pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
12347 RUNTIME.drive_current_thread_tasks(delivery)
12348}
12349
12350pub fn reset_metrics() {
12351 RUNTIME.metrics.reset();
12352}
12353
12354pub fn metrics() -> RuntimeMetricsSnapshot {
12355 let options = RUNTIME.options();
12356 RUNTIME.metrics.snapshot(&options)
12357}
12358
12359#[cfg(test)]
12360#[cfg_attr(target_family = "wasm", allow(dead_code))]
12365mod tests {
12366 use super::*;
12367
12368 static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12392 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12393 LazyLock::new(Mutex::default);
12394 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12395 static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12396 static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12397 static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12398 LazyLock::new(Mutex::default);
12399 static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12400 static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12401 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12402 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12403 LazyLock::new(Mutex::default);
12404 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
12405 static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
12406 CurrentThreadTaskDriverRegistry,
12407 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12408 static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12409 static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12410 static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12411 LazyLock::new(Mutex::default);
12412 static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12413 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12414 static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12415 LazyLock::new(Mutex::default);
12416 static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12417 static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12418 LazyLock::new(Mutex::default);
12419 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12420 LazyLock::new(Mutex::default);
12421 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
12422 CurrentThreadTaskDriverRegistry,
12423 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12424 static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12425 LazyLock::new(Mutex::default);
12426 static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12427 static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12428 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12429 static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12430 CurrentThreadTaskDriverRegistry,
12431 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12432 static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12433 CurrentThreadTaskDriverRegistry,
12434 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12435 static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12436 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12437 static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12438 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12439 static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12440 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12441 static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12442 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12443 static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12444 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12445 static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
12446 CurrentThreadTaskDriverRegistry,
12447 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12448 static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12449 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12450 static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12451
12452 thread_local! {
12453 static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
12454 const { std::cell::Cell::new(0) };
12455 static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
12456 const { std::cell::Cell::new(0) };
12457 }
12458
12459 #[cfg(napi_runtime_os_threads)]
12460 struct StopPollProbe {
12461 polls: Arc<AtomicUsize>,
12462 first_poll: Option<std::sync::mpsc::Sender<()>>,
12463 }
12464
12465 #[cfg(napi_runtime_os_threads)]
12466 impl Future for StopPollProbe {
12467 type Output = ();
12468
12469 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12470 self.polls.fetch_add(1, Ordering::SeqCst);
12471 if let Some(first_poll) = self.first_poll.take() {
12472 first_poll.send(()).unwrap();
12473 }
12474 Poll::Pending
12475 }
12476 }
12477
12478 #[cfg(napi_runtime_os_threads)]
12479 struct PanickingJoinAwaiter {
12480 wakes: Arc<AtomicUsize>,
12481 drops: Arc<AtomicUsize>,
12482 }
12483
12484 #[cfg(napi_runtime_os_threads)]
12485 impl std::task::Wake for PanickingJoinAwaiter {
12486 fn wake(self: Arc<Self>) {
12487 self.wakes.fetch_add(1, Ordering::SeqCst);
12488 panic!("intentional JoinHandle awaiter wake panic");
12489 }
12490
12491 fn wake_by_ref(self: &Arc<Self>) {
12492 self.wakes.fetch_add(1, Ordering::SeqCst);
12493 panic!("intentional JoinHandle awaiter wake-by-ref panic");
12494 }
12495 }
12496
12497 #[cfg(napi_runtime_os_threads)]
12498 impl Drop for PanickingJoinAwaiter {
12499 fn drop(&mut self) {
12500 self.drops.fetch_add(1, Ordering::SeqCst);
12501 panic!("intentional JoinHandle awaiter destructor panic");
12502 }
12503 }
12504
12505 #[cfg(napi_runtime_os_threads)]
12506 fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
12507 Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
12508 }
12509
12510 #[cfg(napi_runtime_os_threads)]
12511 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12512 enum JoinAwaiterWakerEventKind {
12513 Clone,
12514 Wake,
12515 WakeByRef,
12516 Drop,
12517 }
12518
12519 #[cfg(napi_runtime_os_threads)]
12520 struct JoinAwaiterWakerState {
12521 events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
12522 }
12523
12524 #[cfg(napi_runtime_os_threads)]
12525 impl JoinAwaiterWakerState {
12526 fn record(&self, kind: JoinAwaiterWakerEventKind) {
12527 self
12528 .events
12529 .lock()
12530 .unwrap_or_else(std::sync::PoisonError::into_inner)
12531 .push((kind, active_runtime_generation()));
12532 }
12533
12534 fn clear(&self) {
12535 self
12536 .events
12537 .lock()
12538 .unwrap_or_else(std::sync::PoisonError::into_inner)
12539 .clear();
12540 }
12541
12542 fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
12543 self
12544 .events
12545 .lock()
12546 .unwrap_or_else(std::sync::PoisonError::into_inner)
12547 .clone()
12548 }
12549 }
12550
12551 #[cfg(napi_runtime_os_threads)]
12552 struct JoinAwaiterWakerToken {
12553 state: Arc<JoinAwaiterWakerState>,
12554 }
12555
12556 #[cfg(napi_runtime_os_threads)]
12557 unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
12558 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12559 token.state.record(JoinAwaiterWakerEventKind::Clone);
12560 let clone = Box::new(JoinAwaiterWakerToken {
12561 state: Arc::clone(&token.state),
12562 });
12563 std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
12564 }
12565
12566 #[cfg(napi_runtime_os_threads)]
12567 unsafe fn wake_join_awaiter_waker(data: *const ()) {
12568 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12569 token.state.record(JoinAwaiterWakerEventKind::Wake);
12570 }
12571
12572 #[cfg(napi_runtime_os_threads)]
12573 unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
12574 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12575 token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
12576 }
12577
12578 #[cfg(napi_runtime_os_threads)]
12579 unsafe fn drop_join_awaiter_waker(data: *const ()) {
12580 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12581 token.state.record(JoinAwaiterWakerEventKind::Drop);
12582 }
12583
12584 #[cfg(napi_runtime_os_threads)]
12585 static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
12586 clone_join_awaiter_waker,
12587 wake_join_awaiter_waker,
12588 wake_join_awaiter_waker_by_ref,
12589 drop_join_awaiter_waker,
12590 );
12591
12592 #[cfg(napi_runtime_os_threads)]
12593 fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
12594 let token = Box::new(JoinAwaiterWakerToken { state });
12595 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
12596 unsafe { Waker::from_raw(raw) }
12597 }
12598
12599 #[cfg(napi_runtime_os_threads)]
12600 fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
12601 wait_until("the block_on driver to sleep", || {
12602 stop
12603 .parkers
12604 .lock()
12605 .unwrap_or_else(std::sync::PoisonError::into_inner)
12606 .iter()
12607 .filter_map(Weak::upgrade)
12608 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12609 });
12610 }
12611
12612 #[cfg(panic = "unwind")]
12613 #[test]
12614 fn unique_ids_fail_before_wraparound() {
12615 let counter = AtomicU64::new(u64::MAX - 1);
12616 assert_eq!(
12617 next_unique_id(&counter, "test id space exhausted"),
12618 u64::MAX - 1
12619 );
12620
12621 let exhausted = catch_unwind(AssertUnwindSafe(|| {
12622 next_unique_id(&counter, "test id space exhausted");
12623 }));
12624 assert!(exhausted.is_err());
12625 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
12626 }
12627
12628 #[cfg(panic = "unwind")]
12629 #[test]
12630 fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
12631 let work = GenerationWork::new();
12632 {
12633 let mut state = work.state.lock().unwrap();
12634 state.next_task_id = u64::MAX;
12635 }
12636
12637 let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
12638 assert!(exhausted.is_err());
12639
12640 let state = work.state.lock().unwrap();
12641 assert_eq!(state.next_task_id, u64::MAX);
12642 assert_eq!(state.active, 0);
12643 assert!(state.abort_handles.is_empty());
12644 }
12645
12646 #[cfg(panic = "unwind")]
12647 #[test]
12648 fn caught_panic_payload_destructor_runs() {
12649 struct DropSignal(Arc<AtomicBool>);
12650
12651 impl Drop for DropSignal {
12652 fn drop(&mut self) {
12653 self.0.store(true, Ordering::SeqCst);
12654 }
12655 }
12656
12657 let dropped = Arc::new(AtomicBool::new(false));
12658 let payload = DropSignal(Arc::clone(&dropped));
12659
12660 assert!(
12661 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
12662 "the original panic must remain contained"
12663 );
12664 assert!(
12665 dropped.load(Ordering::SeqCst),
12666 "an ordinary caught panic payload must be destroyed"
12667 );
12668 }
12669
12670 #[cfg(panic = "unwind")]
12671 #[test]
12672 fn hostile_caught_panic_payload_destructor_remains_contained() {
12673 struct PanicOnDrop(Arc<AtomicBool>);
12674
12675 impl Drop for PanicOnDrop {
12676 fn drop(&mut self) {
12677 self.0.store(true, Ordering::SeqCst);
12678 panic!("caught panic payload destructor escaped");
12679 }
12680 }
12681
12682 let dropped = Arc::new(AtomicBool::new(false));
12683 let payload = PanicOnDrop(Arc::clone(&dropped));
12684 let result = catch_unwind(AssertUnwindSafe(|| {
12685 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12686 }));
12687
12688 assert!(
12689 result.is_ok(),
12690 "the payload destructor's nested panic must remain contained"
12691 );
12692 assert!(
12693 result.unwrap().is_none(),
12694 "the original panic must remain contained"
12695 );
12696 assert!(
12697 dropped.load(Ordering::SeqCst),
12698 "the hostile payload destructor must be attempted"
12699 );
12700 }
12701
12702 fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12703 CurrentThreadHostDispatchResult::Accepted
12704 }
12705
12706 fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12707 CurrentThreadHostDispatchResult::Unavailable
12708 }
12709
12710 fn count_current_thread_recovery_host_dispatch(
12711 _dispatch: u64,
12712 ) -> CurrentThreadHostDispatchResult {
12713 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
12714 CurrentThreadHostDispatchResult::Accepted
12715 }
12716
12717 fn reset_current_thread_recovery_host_dispatches() {
12718 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12719 }
12720
12721 fn current_thread_recovery_host_dispatches() -> usize {
12722 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
12723 }
12724
12725 fn accept_only_first_current_thread_host_dispatch(
12726 _dispatch: u64,
12727 ) -> CurrentThreadHostDispatchResult {
12728 if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
12729 let previous = dispatches.get();
12730 dispatches.set(previous + 1);
12731 previous == 0
12732 }) {
12733 CurrentThreadHostDispatchResult::Accepted
12734 } else {
12735 CurrentThreadHostDispatchResult::Unavailable
12736 }
12737 }
12738
12739 fn reset_current_thread_one_shot_host_dispatches() {
12740 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12741 }
12742
12743 fn current_thread_one_shot_host_dispatches() -> usize {
12744 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
12745 }
12746
12747 fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12748 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12749 CurrentThreadHostDispatchResult::Accepted
12750 }
12751
12752 fn count_current_thread_blocking_budget_host_dispatch(
12753 _dispatch: u64,
12754 ) -> CurrentThreadHostDispatchResult {
12755 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12756 CurrentThreadHostDispatchResult::Accepted
12757 }
12758
12759 fn count_current_thread_admission_host_dispatch(
12760 _dispatch: u64,
12761 ) -> CurrentThreadHostDispatchResult {
12762 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12763 CurrentThreadHostDispatchResult::Accepted
12764 }
12765
12766 fn count_current_thread_interleaved_recovery_host_dispatch(
12767 _dispatch: u64,
12768 ) -> CurrentThreadHostDispatchResult {
12769 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12770 CurrentThreadHostDispatchResult::Accepted
12771 }
12772
12773 fn dispatch_late_registration_current_thread_tasks(
12774 dispatch: u64,
12775 ) -> CurrentThreadHostDispatchResult {
12776 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
12777 }
12778
12779 fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12780 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
12781 .lock()
12782 .unwrap()
12783 .push(dispatch);
12784 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
12785 }
12786
12787 fn dispatch_authoritative_panic_current_thread_tasks(
12788 dispatch: u64,
12789 ) -> CurrentThreadHostDispatchResult {
12790 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12791 .lock()
12792 .unwrap()
12793 .push(dispatch);
12794 match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
12795 0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
12796 2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
12797 _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
12798 }
12799 }
12800
12801 fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12802 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12803 }
12804
12805 fn dispatch_waker_stack_failure_current_thread_tasks(
12806 dispatch: u64,
12807 ) -> CurrentThreadHostDispatchResult {
12808 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12809 }
12810
12811 fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
12812 dispatch: u64,
12813 ) -> CurrentThreadHostDispatchResult {
12814 CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12815 }
12816
12817 fn dispatch_submission_failure_current_thread_tasks(
12818 dispatch: u64,
12819 ) -> CurrentThreadHostDispatchResult {
12820 CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12821 }
12822
12823 fn dispatch_retirement_turn_current_thread_tasks(
12824 dispatch: u64,
12825 ) -> CurrentThreadHostDispatchResult {
12826 CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
12827 }
12828
12829 fn dispatch_nested_retirement_current_thread_tasks(
12830 dispatch: u64,
12831 ) -> CurrentThreadHostDispatchResult {
12832 CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
12833 }
12834
12835 fn dispatch_sustained_producer_current_thread_tasks(
12836 dispatch: u64,
12837 ) -> CurrentThreadHostDispatchResult {
12838 CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
12839 }
12840
12841 fn dispatch_retirement_retry_current_thread_tasks(
12842 dispatch: u64,
12843 ) -> CurrentThreadHostDispatchResult {
12844 CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
12845 }
12846
12847 fn dispatch_final_retirement_offer_current_thread_tasks(
12848 dispatch: u64,
12849 ) -> CurrentThreadHostDispatchResult {
12850 CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
12851 }
12852
12853 fn dispatch_inline_final_offer_current_thread_tasks(
12854 dispatch: u64,
12855 ) -> CurrentThreadHostDispatchResult {
12856 CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
12857 }
12858
12859 fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
12860 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12861 .lock()
12862 .unwrap()
12863 .push(dispatch);
12864 assert!(
12865 !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12866 "injected CurrentThread host dispatch panic"
12867 );
12868 CurrentThreadHostDispatchResult::Accepted
12869 }
12870
12871 fn panic_once_current_thread_armed_republish_host_dispatch(
12872 dispatch: u64,
12873 ) -> CurrentThreadHostDispatchResult {
12874 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12875 .lock()
12876 .unwrap()
12877 .push(dispatch);
12878 assert!(
12879 !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12880 "injected armed CurrentThread host dispatch panic"
12881 );
12882 CurrentThreadHostDispatchResult::Accepted
12883 }
12884
12885 fn record_current_thread_republish_rearm_dispatch(
12886 dispatch: u64,
12887 ) -> CurrentThreadHostDispatchResult {
12888 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
12889 .lock()
12890 .unwrap()
12891 .push(dispatch);
12892 CurrentThreadHostDispatchResult::Accepted
12893 }
12894
12895 fn reject_initial_current_thread_publication_once(
12896 dispatch: u64,
12897 ) -> CurrentThreadHostDispatchResult {
12898 let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12899 .lock()
12900 .unwrap();
12901 dispatches.push(dispatch);
12902 if dispatches.len() == 1 {
12903 CurrentThreadHostDispatchResult::Unavailable
12904 } else {
12905 CurrentThreadHostDispatchResult::Accepted
12906 }
12907 }
12908
12909 fn reject_current_thread_replacement_publication_once(
12910 dispatch: u64,
12911 ) -> CurrentThreadHostDispatchResult {
12912 let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12913 .lock()
12914 .unwrap();
12915 dispatches.push(dispatch);
12916 if dispatches.len() == 2 {
12917 CurrentThreadHostDispatchResult::Unavailable
12918 } else {
12919 CurrentThreadHostDispatchResult::Accepted
12920 }
12921 }
12922
12923 fn fail_current_thread_replacement_publication_once(
12924 dispatch: u64,
12925 ) -> CurrentThreadHostDispatchResult {
12926 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12927 .lock()
12928 .unwrap()
12929 .push(dispatch);
12930 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
12931 }
12932
12933 fn count_current_thread_shutdown_republish_host_dispatch(
12934 _dispatch: u64,
12935 ) -> CurrentThreadHostDispatchResult {
12936 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12937 CurrentThreadHostDispatchResult::Accepted
12938 }
12939
12940 fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12941 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12942 CurrentThreadHostDispatchResult::Accepted
12943 }
12944
12945 fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
12946 let pending = executor.dispatch_pending.load(Ordering::Acquire);
12947 if pending != 0 {
12948 return pending;
12949 }
12950 let dispatch = next_current_thread_dispatch_id();
12951 match executor.dispatch_pending.compare_exchange(
12952 0,
12953 dispatch,
12954 Ordering::AcqRel,
12955 Ordering::Acquire,
12956 ) {
12957 Ok(_) => dispatch,
12958 Err(pending) => pending,
12959 }
12960 }
12961
12962 fn current_thread_dispatch_failure(
12963 dispatch: u64,
12964 epoch: u64,
12965 ) -> CurrentThreadTaskDispatchFailure {
12966 CurrentThreadTaskDispatchFailure { dispatch, epoch }
12967 }
12968
12969 fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
12970 let executor = controller
12971 .running_current_thread_executor()
12972 .expect("the test controller must have a running CurrentThread executor");
12973 current_thread_dispatch(&executor)
12974 }
12975
12976 #[test]
12977 fn current_thread_executor_drives_spawned_tasks() {
12978 let metrics = Arc::new(RuntimeMetrics::default());
12979 let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
12980 let scheduler = Arc::clone(&executor);
12981 let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
12982 scheduler.schedule(runnable);
12983 });
12984
12985 executor.schedule(runnable);
12986
12987 let mut output = None;
12988 {
12989 let mut future = std::pin::pin!(async {
12990 output = Some(task.await);
12991 });
12992 executor.block_on(future.as_mut());
12993 }
12994 assert_eq!(output, Some(42));
12995 assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
12996 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
12997 }
12998
12999 #[cfg(napi_runtime_os_threads)]
13000 #[test]
13001 fn current_thread_shutdown_wake_does_not_repoll_user_future() {
13002 use std::sync::mpsc;
13003
13004 let executor = Arc::new(CurrentThreadExecutor::with_detection(
13005 Arc::new(RuntimeMetrics::default()),
13006 false,
13007 None,
13008 ));
13009 let polls = Arc::new(AtomicUsize::new(0));
13010 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13011 let (outcome_tx, outcome_rx) = mpsc::channel();
13012 let driver_executor = Arc::clone(&executor);
13013 let driver_polls = Arc::clone(&polls);
13014 let driver = std::thread::spawn(move || {
13015 let mut future = std::pin::pin!(StopPollProbe {
13016 polls: driver_polls,
13017 first_poll: Some(first_poll_tx)
13018 });
13019 outcome_tx
13020 .send(driver_executor.block_on(future.as_mut()))
13021 .unwrap();
13022 });
13023
13024 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13025 wait_for_generation_parker_to_sleep(&executor.stop);
13026 executor.stop.begin_shutdown();
13027
13028 assert_eq!(
13029 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13030 BlockOnOutcome::Stopped
13031 );
13032 driver.join().unwrap();
13033 assert_eq!(
13034 polls.load(Ordering::SeqCst),
13035 1,
13036 "a shutdown wake must be checked before the next CurrentThread user poll"
13037 );
13038 }
13039
13040 #[cfg(napi_runtime_os_threads)]
13041 #[test]
13042 fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
13043 use std::sync::mpsc;
13044
13045 let metrics = Arc::new(RuntimeMetrics::default());
13046 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13047 Arc::clone(&metrics),
13048 accept_current_thread_host_dispatch,
13049 ));
13050 let polls = Arc::new(AtomicUsize::new(0));
13051 let task_polls = Arc::clone(&polls);
13052 let (runnable, task) = async_task::spawn(
13053 async move {
13054 task_polls.fetch_add(1, Ordering::SeqCst);
13055 },
13056 |_| {},
13057 );
13058 executor.schedule(runnable);
13059 let dispatch = current_thread_dispatch(&executor);
13060
13061 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
13062 let (release_claim_tx, release_claim_rx) = mpsc::channel();
13063 let driver_executor = Arc::clone(&executor);
13064 let driver = std::thread::spawn(move || {
13065 CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
13066 *slot.borrow_mut() = Some(Box::new(move || {
13067 claim_locked_tx.send(()).unwrap();
13068 release_claim_rx.recv().unwrap();
13069 }));
13070 });
13071 driver_executor.drive_host_turn_for_dispatch(dispatch);
13072 });
13073 claim_locked_rx
13074 .recv_timeout(Duration::from_secs(2))
13075 .expect("the admitted host turn must pause before its runnable claim");
13076
13077 executor.stop.publish_stopping();
13078 release_claim_tx.send(()).unwrap();
13079 driver.join().unwrap();
13080
13081 assert_eq!(
13082 polls.load(Ordering::SeqCst),
13083 0,
13084 "stop publication must outrank a later runnable claim by an already-admitted host turn"
13085 );
13086 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13087
13088 executor.begin_shutdown();
13089 assert!(
13090 futures::executor::block_on(task.fallible()).is_none(),
13091 "shutdown must cancel the runnable rejected by the stopped claim"
13092 );
13093 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13094 }
13095
13096 #[cfg(napi_runtime_os_threads)]
13097 #[test]
13098 fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
13099 use std::sync::mpsc;
13100
13101 let metrics = Arc::new(RuntimeMetrics::default());
13102 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13103 Arc::clone(&metrics),
13104 accept_current_thread_host_dispatch,
13105 ));
13106 let polls = Arc::new(AtomicUsize::new(0));
13107 let task_polls = Arc::clone(&polls);
13108 let (runnable, task) = async_task::spawn(
13109 async move {
13110 task_polls.fetch_add(1, Ordering::SeqCst);
13111 },
13112 |_| {},
13113 );
13114 executor.schedule(runnable);
13115
13116 let queue = executor
13117 .queue
13118 .lock()
13119 .unwrap_or_else(std::sync::PoisonError::into_inner);
13120 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
13121 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
13122 let cancelling_executor = Arc::clone(&executor);
13123 let cancellation = std::thread::spawn(move || {
13124 let scheduler = cancelling_executor
13125 .scheduler_idle_lock
13126 .lock()
13127 .unwrap_or_else(std::sync::PoisonError::into_inner);
13128 let cancelling = cancelling_executor
13129 .begin_failed_dispatch_cancellation()
13130 .expect("the test must publish terminal dispatch cancellation");
13131 drop(scheduler);
13132 cancellation_published_tx.send(()).unwrap();
13133 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
13134 cancellation_done_tx.send(()).unwrap();
13135 });
13136 cancellation_published_rx
13137 .recv_timeout(Duration::from_secs(2))
13138 .expect("terminal dispatch cancellation must publish before queue draining");
13139
13140 let claimant_executor = Arc::clone(&executor);
13141 let claimant = std::thread::spawn(move || claimant_executor.drain_one());
13142 assert!(
13143 !claimant.join().unwrap(),
13144 "a runnable claim after terminal cancellation publication must be rejected"
13145 );
13146 assert_eq!(polls.load(Ordering::SeqCst), 0);
13147 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13148 assert!(
13149 matches!(
13150 cancellation_done_rx.try_recv(),
13151 Err(mpsc::TryRecvError::Empty)
13152 ),
13153 "terminal cancellation must still be waiting to drain the queued runnable"
13154 );
13155
13156 drop(queue);
13157 cancellation_done_rx
13158 .recv_timeout(Duration::from_secs(2))
13159 .expect("terminal cancellation must drain the rejected runnable");
13160 cancellation.join().unwrap();
13161 assert!(
13162 futures::executor::block_on(task.fallible()).is_none(),
13163 "terminal cancellation must cancel the runnable it prevented from starting"
13164 );
13165 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13166 }
13167
13168 #[cfg(napi_runtime_os_threads)]
13169 #[test]
13170 fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
13171 use std::sync::mpsc;
13172
13173 let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
13174 let polls = Arc::new(AtomicUsize::new(0));
13175 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13176 let (outcome_tx, outcome_rx) = mpsc::channel();
13177 let driver_executor = Arc::clone(&executor);
13178 let driver_polls = Arc::clone(&polls);
13179 let driver = std::thread::spawn(move || {
13180 let mut future = std::pin::pin!(StopPollProbe {
13181 polls: driver_polls,
13182 first_poll: Some(first_poll_tx)
13183 });
13184 outcome_tx
13185 .send(driver_executor.block_on(future.as_mut()))
13186 .unwrap();
13187 });
13188
13189 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13190 wait_for_generation_parker_to_sleep(&executor.stop);
13191 executor.stop.begin_shutdown();
13192
13193 assert_eq!(
13194 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13195 BlockOnOutcome::Stopped
13196 );
13197 driver.join().unwrap();
13198 assert_eq!(
13199 polls.load(Ordering::SeqCst),
13200 1,
13201 "a shutdown wake must be checked before the next parking block_on user poll"
13202 );
13203 }
13204
13205 #[cfg(napi_runtime_os_threads)]
13206 #[test]
13207 fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
13208 use std::sync::mpsc;
13209
13210 let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
13211 let polls = Arc::new(AtomicUsize::new(0));
13212 let (first_poll_tx, first_poll_rx) = mpsc::channel();
13213 let (outcome_tx, outcome_rx) = mpsc::channel();
13214 let driver_executor = Arc::clone(&executor);
13215 let driver_polls = Arc::clone(&polls);
13216 let driver = std::thread::spawn(move || {
13217 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
13218 let mut future = std::pin::pin!(StopPollProbe {
13219 polls: driver_polls,
13220 first_poll: Some(first_poll_tx)
13221 });
13222 outcome_tx
13223 .send(driver_executor.block_on(future.as_mut()))
13224 .unwrap();
13225 });
13226
13227 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13228 wait_for_generation_parker_to_sleep(&executor.stop);
13229 executor.stop.begin_shutdown();
13230
13231 assert_eq!(
13232 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13233 BlockOnOutcome::Stopped
13234 );
13235 driver.join().unwrap();
13236 assert_eq!(
13237 polls.load(Ordering::SeqCst),
13238 1,
13239 "a shutdown wake must be checked before the next cooperative user poll"
13240 );
13241 }
13242
13243 #[test]
13244 fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
13245 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13246 Arc::new(RuntimeMetrics::default()),
13247 accept_current_thread_host_dispatch,
13248 ));
13249 let pending = u64::MAX - 1;
13250 executor.dispatch_pending.store(pending, Ordering::Release);
13251 let mut allocations = 0;
13252
13253 executor.request_drain_with(|| {
13254 allocations += 1;
13255 u64::MAX
13256 });
13257
13258 assert_eq!(allocations, 0);
13259 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
13260 }
13261
13262 #[cfg(napi_runtime_os_threads)]
13263 #[test]
13264 fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
13265 use std::sync::mpsc;
13266
13267 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13268 Arc::new(RuntimeMetrics::default()),
13269 accept_current_thread_host_dispatch,
13270 ));
13271 let dispatch = current_thread_dispatch(&executor);
13272 let scheduler = executor
13273 .scheduler_idle_lock
13274 .lock()
13275 .unwrap_or_else(std::sync::PoisonError::into_inner);
13276 let (claiming_tx, claiming_rx) = mpsc::channel();
13277 let (claimed_tx, claimed_rx) = mpsc::channel();
13278 let claiming_executor = Arc::clone(&executor);
13279 let claimant = std::thread::spawn(move || {
13280 claiming_tx.send(()).unwrap();
13281 claimed_tx
13282 .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
13283 .unwrap();
13284 });
13285 claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13286
13287 executor.stop.begin_shutdown();
13288 drop(scheduler);
13289
13290 assert!(
13291 !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13292 "an old-generation dispatch must not claim a host turn after stop becomes visible"
13293 );
13294 claimant.join().unwrap();
13295 assert_eq!(
13296 executor.dispatch_pending.load(Ordering::Acquire),
13297 dispatch,
13298 "rejecting the stopped claim must not consume its stale capability"
13299 );
13300 }
13301
13302 #[cfg(napi_runtime_os_threads)]
13303 #[test]
13304 fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
13305 use std::sync::mpsc;
13306
13307 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13308 let metrics = Arc::new(RuntimeMetrics::default());
13309 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13310 Arc::clone(&metrics),
13311 count_current_thread_stopped_host_dispatch,
13312 ));
13313 let scheduler = executor
13314 .scheduler_idle_lock
13315 .lock()
13316 .unwrap_or_else(std::sync::PoisonError::into_inner);
13317 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13318 let scheduling_executor = Arc::clone(&executor);
13319 let scheduling = std::thread::spawn(move || {
13320 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13321 *slot.borrow_mut() = Some(Box::new(move || {
13322 publication_blocked_tx.send(()).unwrap();
13323 }));
13324 });
13325 let scheduler = Arc::clone(&scheduling_executor);
13326 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13327 scheduler.schedule(runnable);
13328 });
13329 task.detach();
13330 scheduling_executor.schedule(runnable);
13331 });
13332
13333 publication_blocked_rx
13334 .recv_timeout(Duration::from_secs(2))
13335 .expect("dispatch publication must block behind the scheduler-idle mutex");
13336 executor.begin_shutdown();
13337 drop(scheduler);
13338 scheduling.join().unwrap();
13339 executor.wait_until_scheduler_idle();
13340
13341 assert_eq!(
13342 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
13343 0,
13344 "a publication that reaches the scheduler-idle barrier after stop must not call the host"
13345 );
13346 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13347 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13348 }
13349
13350 #[cfg(napi_runtime_os_threads)]
13351 #[test]
13352 fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
13353 use std::{sync::mpsc, time::Duration};
13354
13355 let metrics = Arc::new(RuntimeMetrics::default());
13356 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13357 Arc::clone(&metrics),
13358 accept_current_thread_host_dispatch,
13359 ));
13360 let scheduler = Arc::clone(&executor);
13361 let completed = Arc::new(AtomicBool::new(false));
13362 let completed_task = Arc::clone(&completed);
13363 let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
13364 let shared = async move {
13365 wake_rx.await.expect("wake sender dropped");
13366 }
13367 .boxed()
13368 .shared();
13369 let (runnable, task) = async_task::spawn(
13370 async move {
13371 shared.await;
13372 completed_task.store(true, Ordering::SeqCst);
13373 },
13374 move |runnable| scheduler.schedule(runnable),
13375 );
13376
13377 executor.schedule(runnable);
13378 assert!(
13379 !completed.load(Ordering::SeqCst),
13380 "accepted host dispatch must defer the first poll"
13381 );
13382 executor.drive_host_turn();
13383
13384 let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
13385 std::thread::spawn(move || {
13386 wake_tx.send(()).expect("shared wake receiver dropped");
13387 send_returned_tx.send(()).unwrap();
13388 });
13389 send_returned_rx
13390 .recv_timeout(Duration::from_secs(1))
13391 .expect("Shared wake re-entered its own waker mutex through inline polling");
13392 assert!(
13393 !completed.load(Ordering::SeqCst),
13394 "the wake must remain queued until a fresh host turn"
13395 );
13396
13397 executor.drive_host_turn();
13398 futures::executor::block_on(task);
13399 assert!(completed.load(Ordering::SeqCst));
13400 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13401 }
13402
13403 #[cfg(napi_runtime_os_threads)]
13404 #[test]
13405 fn current_thread_hostless_wake_is_enqueue_only() {
13406 use std::{sync::mpsc, time::Duration};
13407
13408 struct LockOnSecondPoll {
13409 gate: Arc<Mutex<()>>,
13410 first: bool,
13411 waker: Option<mpsc::Sender<Waker>>,
13412 completed: Arc<AtomicBool>,
13413 }
13414
13415 impl Future for LockOnSecondPoll {
13416 type Output = ();
13417
13418 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
13419 if self.first {
13420 self.first = false;
13421 self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
13422 return Poll::Pending;
13423 }
13424 drop(self.gate.lock().unwrap());
13425 self.completed.store(true, Ordering::SeqCst);
13426 Poll::Ready(())
13427 }
13428 }
13429
13430 let metrics = Arc::new(RuntimeMetrics::default());
13431 let executor = Arc::new(CurrentThreadExecutor::new(metrics));
13432 let gate = Arc::new(Mutex::new(()));
13433 let completed = Arc::new(AtomicBool::new(false));
13434 let (waker_tx, waker_rx) = mpsc::channel();
13435 let scheduler = Arc::clone(&executor);
13436 let (runnable, task) = async_task::spawn(
13437 LockOnSecondPoll {
13438 gate: Arc::clone(&gate),
13439 first: true,
13440 waker: Some(waker_tx),
13441 completed: Arc::clone(&completed),
13442 },
13443 move |runnable| scheduler.schedule(runnable),
13444 );
13445 executor.schedule(runnable);
13446 executor.drive_host_turn();
13447 let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
13448
13449 let (returned_tx, returned_rx) = mpsc::channel();
13450 let wake_gate = Arc::clone(&gate);
13451 let waking = std::thread::spawn(move || {
13452 let _held = wake_gate.lock().unwrap();
13453 waker.wake();
13454 returned_tx.send(()).unwrap();
13455 });
13456 returned_rx
13457 .recv_timeout(Duration::from_secs(1))
13458 .expect("hostless wake must enqueue without polling under the caller's lock");
13459 waking.join().unwrap();
13460 assert!(!completed.load(Ordering::SeqCst));
13461
13462 executor.drive_host_turn();
13463 futures::executor::block_on(task);
13464 assert!(completed.load(Ordering::SeqCst));
13465 }
13466
13467 #[cfg(napi_runtime_os_threads)]
13468 #[test]
13469 fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
13470 use std::sync::mpsc;
13471
13472 let controller = Arc::new(current_thread_controller(
13473 "current-thread-hostless-late-work",
13474 ));
13475 let backend = controller.backend();
13476 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
13477 panic!("configured CurrentThread backend must create a CurrentThread executor");
13478 };
13479 let executor = Arc::clone(executor);
13480 drop(backend);
13481
13482 let (value_tx, value_rx) = oneshot::channel();
13483 let (done_tx, done_rx) = mpsc::channel();
13484 let block_on_controller = Arc::clone(&controller);
13485 let runner = std::thread::spawn(move || {
13486 let mut output = None;
13487 let outcome = {
13488 let mut future = std::pin::pin!(async {
13489 output = Some(value_rx.await.expect("scheduled sender must deliver"));
13490 });
13491 block_on_controller.block_on(future.as_mut())
13492 };
13493 done_tx.send((outcome, output)).unwrap();
13494 });
13495
13496 wait_until(
13497 "the hostless CurrentThread block_on driver to sleep",
13498 || {
13499 executor
13500 .stop
13501 .parkers
13502 .lock()
13503 .unwrap_or_else(std::sync::PoisonError::into_inner)
13504 .iter()
13505 .filter_map(Weak::upgrade)
13506 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13507 },
13508 );
13509
13510 controller
13511 .try_spawn_detached(async move {
13512 value_tx
13513 .send(37usize)
13514 .expect("block_on receiver must remain alive");
13515 })
13516 .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
13517
13518 let (outcome, output) = done_rx
13519 .recv_timeout(Duration::from_secs(2))
13520 .expect("queue publication must wake the sleeping explicit driver");
13521 assert_eq!(outcome, BlockOnOutcome::Completed);
13522 assert_eq!(output, Some(37));
13523 runner.join().unwrap();
13524 controller.shutdown().unwrap();
13525 }
13526
13527 #[cfg(napi_runtime_os_threads)]
13528 #[test]
13529 fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
13530 use std::sync::mpsc;
13531
13532 struct BlockingPoll {
13533 entered: Option<mpsc::Sender<()>>,
13534 release: mpsc::Receiver<()>,
13535 }
13536
13537 impl Future for BlockingPoll {
13538 type Output = ();
13539
13540 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
13541 self.entered.take().unwrap().send(()).unwrap();
13542 self.release.recv().unwrap();
13543 Poll::Ready(())
13544 }
13545 }
13546
13547 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13548 Arc::new(RuntimeMetrics::default()),
13549 reject_current_thread_host_dispatch,
13550 ));
13551 let (value_tx, value_rx) = oneshot::channel();
13552 let (older_done_tx, older_done_rx) = mpsc::channel();
13553 let older_executor = Arc::clone(&executor);
13554 let older = std::thread::spawn(move || {
13555 let mut value = None;
13556 {
13557 let mut future = std::pin::pin!(async {
13558 value = Some(value_rx.await.unwrap());
13559 });
13560 assert_eq!(
13561 older_executor.block_on(future.as_mut()),
13562 BlockOnOutcome::Completed
13563 );
13564 }
13565 older_done_tx.send(value.unwrap()).unwrap();
13566 });
13567 wait_until("the older CurrentThread driver to sleep", || {
13568 executor
13569 .stop
13570 .parkers
13571 .lock()
13572 .unwrap_or_else(std::sync::PoisonError::into_inner)
13573 .iter()
13574 .filter_map(Weak::upgrade)
13575 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13576 });
13577
13578 let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
13579 let (release_newer_tx, release_newer_rx) = mpsc::channel();
13580 let newer_executor = Arc::clone(&executor);
13581 let newer = std::thread::spawn(move || {
13582 let mut future = std::pin::pin!(BlockingPoll {
13583 entered: Some(newer_entered_tx),
13584 release: release_newer_rx
13585 });
13586 assert_eq!(
13587 newer_executor.block_on(future.as_mut()),
13588 BlockOnOutcome::Completed
13589 );
13590 });
13591 newer_entered_rx
13592 .recv_timeout(Duration::from_secs(1))
13593 .expect("the newer driver must be blocked inside its poll");
13594
13595 let scheduler = Arc::clone(&executor);
13596 let (runnable, task) = async_task::spawn(
13597 async move {
13598 value_tx.send(41usize).unwrap();
13599 },
13600 move |runnable| scheduler.schedule(runnable),
13601 );
13602 task.detach();
13603 executor.schedule(runnable);
13604
13605 assert_eq!(
13606 older_done_rx
13607 .recv_timeout(Duration::from_secs(2))
13608 .expect("queue publication must also wake the older sleeping driver"),
13609 41
13610 );
13611 release_newer_tx.send(()).unwrap();
13612 older.join().unwrap();
13613 newer.join().unwrap();
13614 }
13615
13616 #[test]
13617 fn current_thread_shutdown_cancels_host_queued_task() {
13618 use std::sync::mpsc;
13619
13620 struct DropSignal(mpsc::Sender<()>);
13621
13622 impl Drop for DropSignal {
13623 fn drop(&mut self) {
13624 self.0.send(()).unwrap();
13625 }
13626 }
13627
13628 let metrics = Arc::new(RuntimeMetrics::default());
13629 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13630 Arc::clone(&metrics),
13631 accept_current_thread_host_dispatch,
13632 ));
13633 let scheduler = Arc::clone(&executor);
13634 let (dropped_tx, dropped_rx) = mpsc::channel();
13635 let signal = DropSignal(dropped_tx);
13636 let (runnable, task) = async_task::spawn(
13637 async move {
13638 let _signal = signal;
13639 futures::future::pending::<()>().await;
13640 },
13641 move |runnable| scheduler.schedule(runnable),
13642 );
13643
13644 executor.schedule(runnable);
13645 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13646 executor.begin_shutdown();
13647
13648 dropped_rx
13649 .recv_timeout(Duration::from_secs(1))
13650 .expect("shutdown must drop a task queued behind a host turn");
13651 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13652 assert!(
13653 futures::executor::block_on(task.fallible()).is_none(),
13654 "the queued task handle must resolve as cancelled"
13655 );
13656 }
13657
13658 #[test]
13659 fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
13660 reset_current_thread_recovery_host_dispatches();
13661 let metrics = Arc::new(RuntimeMetrics::default());
13662 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13663 Arc::clone(&metrics),
13664 count_current_thread_recovery_host_dispatch,
13665 ));
13666 let scheduler = Arc::clone(&executor);
13667 let completed = Arc::new(AtomicBool::new(false));
13668 let completed_task = Arc::clone(&completed);
13669 let (runnable, task) = async_task::spawn(
13670 async move {
13671 completed_task.store(true, Ordering::SeqCst);
13672 },
13673 move |runnable| scheduler.schedule(runnable),
13674 );
13675
13676 executor.schedule(runnable);
13677 assert_eq!(current_thread_recovery_host_dispatches(), 1);
13678 let lost_dispatch = current_thread_dispatch(&executor);
13679
13680 executor.request_drain_if_queued();
13684 assert_eq!(
13685 current_thread_recovery_host_dispatches(),
13686 2,
13687 "the newly registered host must receive the pending executor dispatch"
13688 );
13689 assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13690 executor.drive_host_turn_for_dispatch(lost_dispatch);
13691 futures::executor::block_on(task);
13692 assert!(completed.load(Ordering::SeqCst));
13693 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13694
13695 let second_completed = Arc::new(AtomicBool::new(false));
13696 let second_completed_task = Arc::clone(&second_completed);
13697 let second_scheduler = Arc::clone(&executor);
13698 let (second_runnable, second_task) = async_task::spawn(
13699 async move {
13700 second_completed_task.store(true, Ordering::SeqCst);
13701 },
13702 move |runnable| second_scheduler.schedule(runnable),
13703 );
13704 executor.schedule(second_runnable);
13705 assert_eq!(
13706 current_thread_recovery_host_dispatches(),
13707 3,
13708 "work queued after the stale callback must receive a new dispatch"
13709 );
13710 let third_dispatch = current_thread_dispatch(&executor);
13711
13712 executor.drive_host_turn_for_dispatch(lost_dispatch);
13715 assert!(!second_completed.load(Ordering::SeqCst));
13716 executor.drive_host_turn_for_dispatch(third_dispatch);
13717 futures::executor::block_on(second_task);
13718 assert!(second_completed.load(Ordering::SeqCst));
13719
13720 let third_scheduler = Arc::clone(&executor);
13721 let (third_runnable, third_task) =
13722 async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
13723 executor.schedule(third_runnable);
13724 assert_eq!(
13725 current_thread_recovery_host_dispatches(),
13726 4,
13727 "a stale callback must not leave the dispatch latch stuck"
13728 );
13729 executor.drive_host_turn();
13730 futures::executor::block_on(third_task);
13731 }
13732
13733 #[test]
13734 fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
13735 let metrics = Arc::new(RuntimeMetrics::default());
13736 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13737 Arc::clone(&metrics),
13738 accept_current_thread_host_dispatch,
13739 ));
13740 let scheduler = Arc::clone(&executor);
13741 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13742 scheduler.schedule(runnable)
13743 });
13744
13745 executor.schedule(runnable);
13746 let failed_dispatch = current_thread_dispatch(&executor);
13747 executor.cancel_host_dispatch_for_test(failed_dispatch);
13748 let recovery_dispatch = current_thread_dispatch(&executor);
13749 assert_eq!(
13750 executor.recovery_dispatch.load(Ordering::Acquire),
13751 recovery_dispatch
13752 );
13753
13754 executor.request_drain_if_queued();
13755 let new_host_dispatch = current_thread_dispatch(&executor);
13756 assert_eq!(new_host_dispatch, recovery_dispatch);
13757 assert_eq!(
13758 executor.recovery_dispatch.load(Ordering::Acquire),
13759 recovery_dispatch
13760 );
13761 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13762
13763 executor.drive_host_turn_for_dispatch(new_host_dispatch);
13764 assert_eq!(futures::executor::block_on(task), 17);
13765 }
13766
13767 #[cfg(napi_runtime_os_threads)]
13768 #[test]
13769 fn current_thread_host_admission_serializes_dispatch_publication() {
13770 use std::sync::mpsc;
13771
13772 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13773 let metrics = Arc::new(RuntimeMetrics::default());
13774 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13775 Arc::clone(&metrics),
13776 count_current_thread_admission_host_dispatch,
13777 ));
13778 let first_scheduler = Arc::clone(&executor);
13779 let (first_runnable, first_task) =
13780 async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
13781 first_task.detach();
13782 executor.schedule(first_runnable);
13783 let dispatch = current_thread_dispatch(&executor);
13784 assert_eq!(
13785 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13786 1
13787 );
13788
13789 let (claimed_tx, claimed_rx) = mpsc::channel();
13790 let (release_tx, release_rx) = mpsc::channel();
13791 let driver_executor = Arc::clone(&executor);
13792 let driver = std::thread::spawn(move || {
13793 CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
13794 *slot.borrow_mut() = Some(Box::new(move || {
13795 claimed_tx.send(()).unwrap();
13796 release_rx.recv().unwrap();
13797 }));
13798 });
13799 driver_executor.drive_host_turn_for_dispatch(dispatch);
13800 });
13801 claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13802
13803 let second_ran = Arc::new(AtomicBool::new(false));
13804 let second_ran_task = Arc::clone(&second_ran);
13805 let scheduling_executor = Arc::clone(&executor);
13806 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13807 let (scheduled_tx, scheduled_rx) = mpsc::channel();
13808 let scheduling = std::thread::spawn(move || {
13809 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13810 *slot.borrow_mut() = Some(Box::new(move || {
13811 publication_blocked_tx.send(()).unwrap();
13812 }));
13813 });
13814 let scheduler = Arc::clone(&scheduling_executor);
13815 let (runnable, task) = async_task::spawn(
13816 async move {
13817 second_ran_task.store(true, Ordering::SeqCst);
13818 },
13819 move |runnable| scheduler.schedule(runnable),
13820 );
13821 task.detach();
13822 scheduling_executor.schedule(runnable);
13823 scheduled_tx.send(()).unwrap();
13824 });
13825
13826 publication_blocked_rx
13827 .recv_timeout(Duration::from_secs(2))
13828 .unwrap();
13829 assert!(
13830 matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
13831 "dispatch publication must wait until host admission publishes its draining role"
13832 );
13833 assert_eq!(
13834 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13835 1,
13836 "the admission gap must not publish a duplicate host dispatch"
13837 );
13838
13839 release_tx.send(()).unwrap();
13840 scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13841 scheduling.join().unwrap();
13842 driver.join().unwrap();
13843 if !second_ran.load(Ordering::SeqCst) {
13844 executor.drive_host_turn();
13845 }
13846 assert!(second_ran.load(Ordering::SeqCst));
13847 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13848 }
13849
13850 #[test]
13851 fn current_thread_host_turn_yields_after_runnable_budget() {
13852 struct SelfWakingFuture {
13853 remaining: usize,
13854 }
13855
13856 impl Future for SelfWakingFuture {
13857 type Output = ();
13858
13859 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
13860 if self.remaining == 0 {
13861 return Poll::Ready(());
13862 }
13863 self.remaining -= 1;
13864 cx.waker().wake_by_ref();
13865 Poll::Pending
13866 }
13867 }
13868
13869 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13870 let metrics = Arc::new(RuntimeMetrics::default());
13871 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13872 Arc::clone(&metrics),
13873 count_current_thread_budget_host_dispatch,
13874 ));
13875 let scheduler = Arc::clone(&executor);
13876 let (runnable, task) = async_task::spawn(
13877 SelfWakingFuture {
13878 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
13879 },
13880 move |runnable| scheduler.schedule(runnable),
13881 );
13882
13883 executor.schedule(runnable);
13884 executor.drive_host_turn();
13885
13886 assert_eq!(
13887 metrics.runnable_polls.load(Ordering::Relaxed),
13888 CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
13889 );
13890 assert_eq!(
13891 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13892 2,
13893 "remaining hot work must continue through a fresh host turn"
13894 );
13895
13896 executor.drive_host_turn();
13897 futures::executor::block_on(task);
13898 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13899 }
13900
13901 #[cfg(napi_runtime_os_threads)]
13902 #[test]
13903 fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
13904 const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
13905
13906 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13907 let metrics = Arc::new(RuntimeMetrics::default());
13908 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13909 Arc::clone(&metrics),
13910 count_current_thread_blocking_budget_host_dispatch,
13911 ));
13912 let work = GenerationWork::new();
13913 let observed = Arc::new(Mutex::new(Vec::new()));
13914
13915 let task_executor = Arc::clone(&executor);
13916 let wake_executor = Arc::clone(&executor);
13917 let producer_executor = Arc::clone(&executor);
13918 let producer_work = Arc::clone(&work);
13919 let producer_observed = Arc::clone(&observed);
13920 let outer_registration = work
13921 .try_register_work()
13922 .expect("the outer blocking job must be accepted");
13923 let (runnable, task) = async_task::spawn(
13924 async move {
13925 let queued = task_executor
13926 .schedule_blocking(
13927 move || {
13928 std::thread::spawn(move || {
13929 (0..QUEUED_BLOCKING_JOBS)
13930 .map(|index| {
13931 let registration = producer_work
13932 .try_register_work()
13933 .expect("queued blocking work must be accepted");
13934 let observed = Arc::clone(&producer_observed);
13935 producer_executor.schedule_blocking(
13936 move || {
13937 observed
13938 .lock()
13939 .unwrap_or_else(std::sync::PoisonError::into_inner)
13940 .push(index);
13941 },
13942 registration,
13943 )
13944 })
13945 .collect::<Vec<_>>()
13946 })
13947 .join()
13948 .expect("the blocking producer must finish")
13949 },
13950 outer_registration,
13951 )
13952 .await
13953 .expect("the inline blocking job must finish");
13954 for handle in queued {
13955 handle.await.expect("queued blocking work must finish");
13956 }
13957 },
13958 move |runnable| wake_executor.schedule(runnable),
13959 );
13960
13961 executor.schedule(runnable);
13962 executor.drive_host_turn();
13963
13964 let first_turn = observed
13965 .lock()
13966 .unwrap_or_else(std::sync::PoisonError::into_inner)
13967 .clone();
13968 assert!(
13969 !first_turn.is_empty(),
13970 "the first host turn must service queued blocking work"
13971 );
13972 assert!(
13973 first_turn.len() < QUEUED_BLOCKING_JOBS,
13974 "inline lane release must not drain the entire blocking FIFO in one host turn"
13975 );
13976 assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
13977 assert_eq!(
13978 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13979 2,
13980 "remaining blocking work must continue through a fresh host turn"
13981 );
13982
13983 let mut continuation_turns = 0;
13984 while observed
13985 .lock()
13986 .unwrap_or_else(std::sync::PoisonError::into_inner)
13987 .len()
13988 < QUEUED_BLOCKING_JOBS
13989 {
13990 continuation_turns += 1;
13991 assert!(
13992 continuation_turns <= QUEUED_BLOCKING_JOBS,
13993 "queued blocking work failed to make bounded host-turn progress"
13994 );
13995 executor.drive_host_turn();
13996 }
13997 executor.drive_host_turn();
13998 futures::executor::block_on(task);
13999
14000 assert_eq!(
14001 *observed
14002 .lock()
14003 .unwrap_or_else(std::sync::PoisonError::into_inner),
14004 (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
14005 "blocking jobs must preserve FIFO order across host turns"
14006 );
14007 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14008 }
14009
14010 #[cfg(napi_runtime_os_threads)]
14011 #[test]
14012 fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
14013 use std::sync::mpsc;
14014
14015 struct SelfWakingFuture {
14016 remaining: usize,
14017 }
14018
14019 impl Future for SelfWakingFuture {
14020 type Output = ();
14021
14022 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14023 if self.remaining == 0 {
14024 return Poll::Ready(());
14025 }
14026 self.remaining -= 1;
14027 cx.waker().wake_by_ref();
14028 Poll::Pending
14029 }
14030 }
14031
14032 let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
14033 let old_generation = controller.backend().generation();
14034 controller
14035 .try_spawn_detached(SelfWakingFuture {
14036 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14037 })
14038 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
14039 let dispatch = controller_current_thread_dispatch(&controller);
14040
14041 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14042 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14043 let driver_controller = Arc::clone(&controller);
14044 let driver = std::thread::spawn(move || {
14045 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14046 *slot.borrow_mut() = Some(Box::new(move || {
14047 dispatch_entered_tx.send(()).unwrap();
14048 release_dispatch_rx.recv().unwrap();
14049 }));
14050 });
14051 driver_controller.drive_current_thread_dispatch(dispatch, false);
14052 });
14053 dispatch_entered_rx
14054 .recv_timeout(Duration::from_secs(2))
14055 .expect("the bounded host turn must begin publishing its continuation");
14056
14057 let (shutdown_tx, shutdown_rx) = mpsc::channel();
14058 let shutdown_controller = Arc::clone(&controller);
14059 let shutdown = std::thread::spawn(move || {
14060 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14061 });
14062 wait_until("CurrentThread shutdown to leave Running", || {
14063 let state = controller
14064 .state
14065 .lock()
14066 .unwrap_or_else(std::sync::PoisonError::into_inner);
14067 !matches!(
14068 &state.lifecycle,
14069 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14070 )
14071 });
14072
14073 let (restart_tx, restart_rx) = mpsc::channel();
14074 let restart_controller = Arc::clone(&controller);
14075 let restart = std::thread::spawn(move || {
14076 let result = restart_controller
14077 .start()
14078 .map(|()| restart_controller.backend().generation());
14079 restart_tx.send(result).unwrap();
14080 });
14081
14082 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14083 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14084 let shutdown_overlapped = early_shutdown.is_some();
14085 let restart_overlapped = early_restart.is_some();
14086 release_dispatch_tx.send(()).unwrap();
14087 driver.join().unwrap();
14088
14089 let shutdown_result = early_shutdown.unwrap_or_else(|| {
14090 shutdown_rx
14091 .recv_timeout(Duration::from_secs(2))
14092 .expect("shutdown must finish after continuation publication retires")
14093 });
14094 shutdown_result.unwrap();
14095 let new_generation = early_restart.unwrap_or_else(|| {
14096 restart_rx
14097 .recv_timeout(Duration::from_secs(2))
14098 .expect("restart must follow continuation publication and shutdown")
14099 });
14100 let new_generation = new_generation.unwrap();
14101 shutdown.join().unwrap();
14102 restart.join().unwrap();
14103
14104 assert!(
14105 !shutdown_overlapped,
14106 "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
14107 );
14108 assert!(
14109 !restart_overlapped,
14110 "restart must not overlap an old-generation continuation dispatch"
14111 );
14112 assert_ne!(new_generation, old_generation);
14113 controller.shutdown().unwrap();
14114 }
14115
14116 #[cfg(napi_runtime_os_threads)]
14117 #[test]
14118 fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
14119 use std::sync::mpsc;
14120
14121 let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
14122 let old_generation = controller.backend().generation();
14123 let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
14124 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14125 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14126 let submit_controller = Arc::clone(&controller);
14127 let reentrant_controller = Arc::clone(&controller);
14128 let submitter = std::thread::spawn(move || {
14129 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14130 *slot.borrow_mut() = Some(Box::new(move || {
14131 reentrant_shutdown_tx
14132 .send(reentrant_controller.shutdown())
14133 .unwrap();
14134 dispatch_entered_tx.send(()).unwrap();
14135 release_dispatch_rx.recv().unwrap();
14136 }));
14137 });
14138 submit_controller
14139 .try_spawn_detached(std::future::pending::<()>())
14140 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
14141 });
14142
14143 let reentrant_shutdown = reentrant_shutdown_rx
14144 .recv_timeout(Duration::from_secs(2))
14145 .expect("dispatch publication must not self-deadlock on reentrant shutdown");
14146 dispatch_entered_rx
14147 .recv_timeout(Duration::from_secs(2))
14148 .expect("the initial host dispatch publication must reach the injected pause");
14149
14150 let (shutdown_tx, shutdown_rx) = mpsc::channel();
14151 let shutdown_controller = Arc::clone(&controller);
14152 let shutdown = std::thread::spawn(move || {
14153 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14154 });
14155 wait_until("CurrentThread shutdown to leave Running", || {
14156 let state = controller
14157 .state
14158 .lock()
14159 .unwrap_or_else(std::sync::PoisonError::into_inner);
14160 !matches!(
14161 &state.lifecycle,
14162 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14163 )
14164 });
14165
14166 let (restart_tx, restart_rx) = mpsc::channel();
14167 let restart_controller = Arc::clone(&controller);
14168 let restart = std::thread::spawn(move || {
14169 let result = restart_controller
14170 .start()
14171 .map(|()| restart_controller.backend().generation());
14172 restart_tx.send(result).unwrap();
14173 });
14174
14175 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14176 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14177 let shutdown_overlapped = early_shutdown.is_some();
14178 let restart_overlapped = early_restart.is_some();
14179 release_dispatch_tx.send(()).unwrap();
14180 submitter.join().unwrap();
14181
14182 let shutdown_result = early_shutdown.unwrap_or_else(|| {
14183 shutdown_rx
14184 .recv_timeout(Duration::from_secs(2))
14185 .expect("shutdown must finish after initial dispatch publication retires")
14186 });
14187 shutdown_result.unwrap();
14188 let new_generation = early_restart.unwrap_or_else(|| {
14189 restart_rx
14190 .recv_timeout(Duration::from_secs(2))
14191 .expect("restart must follow initial dispatch publication and shutdown")
14192 });
14193 let new_generation = new_generation.unwrap();
14194 shutdown.join().unwrap();
14195 restart.join().unwrap();
14196
14197 let reentrant_error = reentrant_shutdown
14198 .expect_err("dispatch publication must be generation-scoped scheduler work");
14199 assert!(
14200 reentrant_error
14201 .to_string()
14202 .contains("work running on that runtime")
14203 );
14204 assert!(
14205 !shutdown_overlapped,
14206 "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
14207 );
14208 assert!(
14209 !restart_overlapped,
14210 "restart must not overlap an old-generation host dispatch call"
14211 );
14212 assert_ne!(new_generation, old_generation);
14213 controller.shutdown().unwrap();
14214 }
14215
14216 #[cfg(panic = "unwind")]
14217 #[test]
14218 fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
14219 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14220 .lock()
14221 .unwrap()
14222 .clear();
14223 CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14224 let metrics = Arc::new(RuntimeMetrics::default());
14225 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14226 Arc::clone(&metrics),
14227 panic_once_current_thread_host_dispatch,
14228 ));
14229 let scheduler = Arc::clone(&executor);
14230 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14231 scheduler.schedule(runnable)
14232 });
14233
14234 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14235 assert!(
14236 publication.is_err(),
14237 "the injected host dispatch panic must unwind"
14238 );
14239 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14240 {
14241 let scheduler = executor
14242 .scheduler_idle_lock
14243 .lock()
14244 .unwrap_or_else(std::sync::PoisonError::into_inner);
14245 assert_eq!(scheduler.active_dispatch_calls, 0);
14246 assert!(scheduler.dispatch_publications.is_empty());
14247 }
14248
14249 executor.request_drain_if_queued();
14250
14251 assert_ne!(dispatch, 0);
14252 assert_eq!(
14253 *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14254 .lock()
14255 .unwrap(),
14256 vec![dispatch, dispatch],
14257 "a later host registration must retry the same pending capability"
14258 );
14259 {
14260 let scheduler = executor
14261 .scheduler_idle_lock
14262 .lock()
14263 .unwrap_or_else(std::sync::PoisonError::into_inner);
14264 assert_eq!(scheduler.active_dispatch_calls, 0);
14265 assert!(scheduler.dispatch_publications.is_empty());
14266 }
14267 executor.drive_host_turn_for_dispatch(dispatch);
14268 assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
14269 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14270 }
14271
14272 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14273 #[test]
14274 fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
14275 use std::sync::mpsc;
14276
14277 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14278 .lock()
14279 .unwrap()
14280 .clear();
14281 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14282 let metrics = Arc::new(RuntimeMetrics::default());
14283 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14284 Arc::clone(&metrics),
14285 panic_once_current_thread_armed_republish_host_dispatch,
14286 ));
14287 let scheduler = Arc::clone(&executor);
14288 let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
14289 scheduler.schedule(runnable)
14290 });
14291
14292 let (called_tx, called_rx) = mpsc::channel();
14293 let (release_tx, release_rx) = mpsc::channel();
14294 let publishing_executor = Arc::clone(&executor);
14295 let publication = std::thread::spawn(move || {
14296 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14297 *slot.borrow_mut() = Some(Box::new(move || {
14298 called_tx.send(()).unwrap();
14299 release_rx.recv().unwrap();
14300 }));
14301 });
14302 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14303 });
14304 called_rx
14305 .recv_timeout(Duration::from_secs(2))
14306 .expect("the first publication must pause before calling the host dispatcher");
14307
14308 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14309 executor.request_drain_if_queued();
14310 let publication_state = {
14311 let scheduler = executor
14312 .scheduler_idle_lock
14313 .lock()
14314 .unwrap_or_else(std::sync::PoisonError::into_inner);
14315 (
14316 scheduler.active_dispatch_calls,
14317 scheduler.dispatch_publications.len(),
14318 scheduler.dispatch_publications[0].dispatch,
14319 scheduler.dispatch_publications[0].republish_requested,
14320 )
14321 };
14322 release_tx.send(()).unwrap();
14323
14324 let publication = publication.join().unwrap();
14325 assert!(
14326 publication.is_err(),
14327 "the original host-dispatch panic must resume"
14328 );
14329 assert_ne!(dispatch, 0);
14330 assert_eq!(publication_state, (1, 1, dispatch, true));
14331 assert_eq!(
14332 *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14333 .lock()
14334 .unwrap(),
14335 vec![dispatch, dispatch],
14336 "the armed request must retry automatically before the original panic resumes"
14337 );
14338 {
14339 let scheduler = executor
14340 .scheduler_idle_lock
14341 .lock()
14342 .unwrap_or_else(std::sync::PoisonError::into_inner);
14343 assert_eq!(scheduler.active_dispatch_calls, 0);
14344 assert!(scheduler.dispatch_publications.is_empty());
14345 }
14346 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14347 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14348
14349 executor.drive_host_turn_for_dispatch(dispatch);
14350 assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
14351 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14352 }
14353
14354 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14355 #[test]
14356 fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
14357 use std::sync::mpsc;
14358
14359 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14360 .lock()
14361 .unwrap()
14362 .clear();
14363 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
14364 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14365 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14366 {
14367 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14368 .state
14369 .lock()
14370 .unwrap_or_else(std::sync::PoisonError::into_inner);
14371 assert!(state.entries.is_empty());
14372 assert!(state.dispatches.is_empty());
14373 }
14374 let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14375 .register(Arc::new(FailDuringAuthoritativePanicPublication));
14376 let metrics = Arc::new(RuntimeMetrics::default());
14377 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14378 Arc::clone(&metrics),
14379 dispatch_authoritative_panic_current_thread_tasks,
14380 &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
14381 ));
14382 let scheduler = Arc::clone(&executor);
14383 let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
14384 scheduler.schedule(runnable)
14385 });
14386
14387 let (called_tx, called_rx) = mpsc::channel();
14388 let (release_tx, release_rx) = mpsc::channel();
14389 let publishing_executor = Arc::clone(&executor);
14390 let publication = std::thread::spawn(move || {
14391 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14392 *slot.borrow_mut() = Some(Box::new(move || {
14393 called_tx.send(()).unwrap();
14394 release_rx.recv().unwrap();
14395 }));
14396 });
14397 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14398 });
14399 called_rx
14400 .recv_timeout(Duration::from_secs(2))
14401 .expect("the first publication must pause before raising its panic");
14402
14403 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14404 executor.request_drain_if_queued();
14405 release_tx.send(()).unwrap();
14406
14407 let payload = publication
14408 .join()
14409 .unwrap()
14410 .expect_err("the first host-dispatch panic must resume");
14411 assert!(
14412 payload.is::<FirstHostileCurrentThreadPanic>(),
14413 "the first panic must remain authoritative over nested recovery"
14414 );
14415 assert_eq!(
14416 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14417 1,
14418 "the later hostile payload must be discarded under containment"
14419 );
14420 assert_eq!(
14421 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14422 0,
14423 "the authoritative payload must remain owned by the resumed unwind"
14424 );
14425 discard_panic_payload(payload);
14426 assert_eq!(
14427 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14428 1
14429 );
14430
14431 let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14432 .lock()
14433 .unwrap()
14434 .clone();
14435 assert_eq!(dispatches.len(), 3);
14436 let replacement_dispatch = dispatches[2];
14437 assert_eq!(dispatches[0], failed_dispatch);
14438 assert_eq!(dispatches[1], failed_dispatch);
14439 assert_ne!(replacement_dispatch, failed_dispatch);
14440 assert_eq!(
14441 executor.dispatch_pending.load(Ordering::Acquire),
14442 replacement_dispatch
14443 );
14444 assert_eq!(
14445 executor.recovery_dispatch.load(Ordering::Acquire),
14446 replacement_dispatch
14447 );
14448 {
14449 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14450 .state
14451 .lock()
14452 .unwrap_or_else(std::sync::PoisonError::into_inner);
14453 assert!(
14454 !state.dispatches.contains_key(&failed_dispatch),
14455 "nested recovery unwind must retire the exact failed epoch"
14456 );
14457 assert!(!state.dispatches.contains_key(&replacement_dispatch));
14458 }
14459
14460 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
14461 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14462 let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14463 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14464 executor.request_drain_if_queued();
14465
14466 assert_eq!(
14467 *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14468 .lock()
14469 .unwrap(),
14470 vec![
14471 failed_dispatch,
14472 failed_dispatch,
14473 replacement_dispatch,
14474 replacement_dispatch
14475 ],
14476 "retry must retain the replacement reserved before the nested panic"
14477 );
14478 let delivery = healthy.deliveries()[0];
14479 assert_eq!(
14480 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
14481 Some(replacement_dispatch)
14482 );
14483 let mut host_turn = executor
14484 .try_admit_host_turn(replacement_dispatch)
14485 .expect("the retried replacement delivery must be admitted");
14486 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14487 host_turn.drive();
14488 assert!(
14489 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14490 .finish_delivery(delivery, false)
14491 .failed_dispatches
14492 .is_empty()
14493 );
14494 assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
14495 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14496
14497 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
14498 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14499 .state
14500 .lock()
14501 .unwrap_or_else(std::sync::PoisonError::into_inner);
14502 assert!(state.entries.is_empty());
14503 assert!(state.dispatches.is_empty());
14504 }
14505
14506 #[cfg(napi_runtime_os_threads)]
14507 #[test]
14508 fn current_thread_shutdown_suppresses_queued_republish() {
14509 use std::sync::mpsc;
14510
14511 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14512 let metrics = Arc::new(RuntimeMetrics::default());
14513 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14514 Arc::clone(&metrics),
14515 count_current_thread_shutdown_republish_host_dispatch,
14516 ));
14517 let scheduler = Arc::clone(&executor);
14518 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14519 scheduler.schedule(runnable)
14520 });
14521
14522 let (returned_tx, returned_rx) = mpsc::channel();
14523 let (release_tx, release_rx) = mpsc::channel();
14524 let publishing_executor = Arc::clone(&executor);
14525 let publication = std::thread::spawn(move || {
14526 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14527 *slot.borrow_mut() = Some(Box::new(move || {
14528 returned_tx.send(()).unwrap();
14529 release_rx.recv().unwrap();
14530 }));
14531 });
14532 publishing_executor.schedule(runnable);
14533 });
14534 returned_rx
14535 .recv_timeout(Duration::from_secs(2))
14536 .expect("the initial publication must reach its completion barrier");
14537
14538 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14539 executor.request_drain_if_queued();
14540 let publication_state = {
14541 let scheduler = executor
14542 .scheduler_idle_lock
14543 .lock()
14544 .unwrap_or_else(std::sync::PoisonError::into_inner);
14545 (
14546 scheduler.active_dispatch_calls,
14547 scheduler.dispatch_publications.len(),
14548 scheduler.dispatch_publications[0].dispatch,
14549 scheduler.dispatch_publications[0].republish_requested,
14550 )
14551 };
14552 executor.begin_shutdown();
14553 release_tx.send(()).unwrap();
14554 publication.join().unwrap();
14555 executor.wait_until_scheduler_idle();
14556
14557 assert_ne!(dispatch, 0);
14558 assert_eq!(publication_state, (1, 1, dispatch, true));
14559 assert_eq!(
14560 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
14561 1
14562 );
14563 {
14564 let scheduler = executor
14565 .scheduler_idle_lock
14566 .lock()
14567 .unwrap_or_else(std::sync::PoisonError::into_inner);
14568 assert_eq!(scheduler.active_dispatch_calls, 0);
14569 assert!(scheduler.dispatch_publications.is_empty());
14570 }
14571 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14572 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14573 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14574 assert_eq!(futures::executor::block_on(task.fallible()), None);
14575 }
14576
14577 #[cfg(napi_runtime_os_threads)]
14578 #[test]
14579 fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
14580 use std::sync::mpsc;
14581
14582 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
14583 .lock()
14584 .unwrap()
14585 .clear();
14586 let metrics = Arc::new(RuntimeMetrics::default());
14587 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14588 Arc::clone(&metrics),
14589 record_current_thread_republish_rearm_dispatch,
14590 ));
14591 let scheduler = Arc::clone(&executor);
14592 let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
14593 scheduler.schedule(runnable)
14594 });
14595
14596 let (first_returned_tx, first_returned_rx) = mpsc::channel();
14597 let (release_first_tx, release_first_rx) = mpsc::channel();
14598 let (second_returned_tx, second_returned_rx) = mpsc::channel();
14599 let (release_second_tx, release_second_rx) = mpsc::channel();
14600 let publishing_executor = Arc::clone(&executor);
14601 let publication = std::thread::spawn(move || {
14602 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14603 *slot.borrow_mut() = Some(Box::new(move || {
14604 first_returned_tx.send(()).unwrap();
14605 release_first_rx.recv().unwrap();
14606 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14607 *slot.borrow_mut() = Some(Box::new(move || {
14608 second_returned_tx.send(()).unwrap();
14609 release_second_rx.recv().unwrap();
14610 }));
14611 });
14612 }));
14613 });
14614 publishing_executor.schedule(runnable);
14615 });
14616 first_returned_rx
14617 .recv_timeout(Duration::from_secs(2))
14618 .expect("the first publication must reach its completion barrier");
14619
14620 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14621 executor.request_drain_if_queued();
14622 executor.request_drain_if_queued();
14623 let first_publication_state = {
14624 let scheduler = executor
14625 .scheduler_idle_lock
14626 .lock()
14627 .unwrap_or_else(std::sync::PoisonError::into_inner);
14628 (
14629 scheduler.active_dispatch_calls,
14630 scheduler.dispatch_publications.len(),
14631 scheduler.dispatch_publications[0].dispatch,
14632 scheduler.dispatch_publications[0].republish_requested,
14633 )
14634 };
14635 release_first_tx.send(()).unwrap();
14636 second_returned_rx
14637 .recv_timeout(Duration::from_secs(2))
14638 .expect("the second publication must reach its completion barrier");
14639
14640 executor.request_drain_if_queued();
14641 executor.request_drain_if_queued();
14642 let second_publication_state = {
14643 let scheduler = executor
14644 .scheduler_idle_lock
14645 .lock()
14646 .unwrap_or_else(std::sync::PoisonError::into_inner);
14647 (
14648 scheduler.active_dispatch_calls,
14649 scheduler.dispatch_publications.len(),
14650 scheduler.dispatch_publications[0].dispatch,
14651 scheduler.dispatch_publications[0].republish_requested,
14652 )
14653 };
14654 release_second_tx.send(()).unwrap();
14655 publication.join().unwrap();
14656
14657 assert_ne!(dispatch, 0);
14658 assert_eq!(first_publication_state, (1, 1, dispatch, true));
14659 assert_eq!(second_publication_state, (1, 1, dispatch, true));
14660 assert_eq!(
14661 *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
14662 vec![dispatch, dispatch, dispatch],
14663 "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
14664 );
14665 {
14666 let scheduler = executor
14667 .scheduler_idle_lock
14668 .lock()
14669 .unwrap_or_else(std::sync::PoisonError::into_inner);
14670 assert_eq!(scheduler.active_dispatch_calls, 0);
14671 assert!(scheduler.dispatch_publications.is_empty());
14672 }
14673 executor.drive_host_turn_for_dispatch(dispatch);
14674 assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
14675 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14676 }
14677
14678 #[cfg(napi_runtime_os_threads)]
14679 #[test]
14680 fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
14681 use std::sync::mpsc;
14682
14683 CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14684 .lock()
14685 .unwrap()
14686 .clear();
14687 let metrics = Arc::new(RuntimeMetrics::default());
14688 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14689 Arc::clone(&metrics),
14690 reject_initial_current_thread_publication_once,
14691 ));
14692 let scheduler = Arc::clone(&executor);
14693 let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14694 scheduler.schedule(runnable)
14695 });
14696
14697 let (returned_tx, returned_rx) = mpsc::channel();
14698 let (release_tx, release_rx) = mpsc::channel();
14699 let publishing_executor = Arc::clone(&executor);
14700 let publication = std::thread::spawn(move || {
14701 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14702 *slot.borrow_mut() = Some(Box::new(move || {
14703 returned_tx.send(()).unwrap();
14704 release_rx.recv().unwrap();
14705 }));
14706 });
14707 publishing_executor.schedule(runnable);
14708 });
14709 returned_rx
14710 .recv_timeout(Duration::from_secs(2))
14711 .expect("the unavailable initial publication must reach its completion barrier");
14712
14713 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14714 executor.request_drain_if_queued();
14715 let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14716 .lock()
14717 .unwrap()
14718 .len();
14719
14720 release_tx.send(()).unwrap();
14721 publication.join().unwrap();
14722
14723 let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14724 .lock()
14725 .unwrap()
14726 .clone();
14727 assert_ne!(dispatch, 0);
14728 assert_eq!(
14729 attempts_while_unavailable_is_paused, 1,
14730 "a racing host request must join the in-flight publication instead of retiring it independently"
14731 );
14732 assert_eq!(
14733 dispatches,
14734 vec![dispatch, dispatch],
14735 "the publication owner must retry the same internal capability after the new host joins"
14736 );
14737 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14738 executor.drive_host_turn_for_dispatch(dispatch);
14739 assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
14740 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14741 }
14742
14743 #[cfg(napi_runtime_os_threads)]
14744 #[test]
14745 fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
14746 use std::sync::mpsc;
14747
14748 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14749 .lock()
14750 .unwrap()
14751 .clear();
14752 let metrics = Arc::new(RuntimeMetrics::default());
14753 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14754 Arc::clone(&metrics),
14755 reject_current_thread_replacement_publication_once,
14756 ));
14757 let scheduler = Arc::clone(&executor);
14758 let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
14759 scheduler.schedule(runnable)
14760 });
14761 executor.schedule(runnable);
14762 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14763 assert_ne!(failed_dispatch, 0);
14764
14765 let (returned_tx, returned_rx) = mpsc::channel();
14766 let (release_tx, release_rx) = mpsc::channel();
14767 let cancelling_executor = Arc::clone(&executor);
14768 let cancellation = std::thread::spawn(move || {
14769 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14770 *slot.borrow_mut() = Some(Box::new(move || {
14771 returned_tx.send(()).unwrap();
14772 release_rx.recv().unwrap();
14773 }));
14774 });
14775 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14776 });
14777 returned_rx
14778 .recv_timeout(Duration::from_secs(2))
14779 .expect("the unavailable replacement publication must reach its completion barrier");
14780
14781 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14782 executor.request_drain_if_queued();
14783 let attempts_while_unavailable_is_paused =
14784 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14785 .lock()
14786 .unwrap()
14787 .len();
14788
14789 release_tx.send(()).unwrap();
14790 cancellation.join().unwrap();
14791
14792 let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14793 .lock()
14794 .unwrap()
14795 .clone();
14796 assert_ne!(replacement_dispatch, 0);
14797 assert_ne!(replacement_dispatch, failed_dispatch);
14798 assert_eq!(
14799 attempts_while_unavailable_is_paused, 2,
14800 "a racing host request must join the in-flight replacement publication"
14801 );
14802 assert_eq!(
14803 dispatches,
14804 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14805 "republication must reuse the sole replacement capability instead of allocating another"
14806 );
14807 assert_eq!(
14808 executor.dispatch_pending.load(Ordering::Acquire),
14809 replacement_dispatch
14810 );
14811 assert_eq!(
14812 executor.recovery_dispatch.load(Ordering::Acquire),
14813 replacement_dispatch
14814 );
14815 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14816
14817 executor.drive_host_turn_for_dispatch(replacement_dispatch);
14818 assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
14819 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14820 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14821 }
14822
14823 #[cfg(napi_runtime_os_threads)]
14824 #[test]
14825 fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
14826 use std::sync::mpsc;
14827
14828 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14829 .lock()
14830 .unwrap()
14831 .clear();
14832 {
14833 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14834 .state
14835 .lock()
14836 .unwrap_or_else(std::sync::PoisonError::into_inner);
14837 assert!(state.entries.is_empty());
14838 assert!(state.dispatches.is_empty());
14839 }
14840 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14841 let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14842 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14843 let metrics = Arc::new(RuntimeMetrics::default());
14844 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14845 Arc::clone(&metrics),
14846 fail_current_thread_replacement_publication_once,
14847 &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
14848 ));
14849 let scheduler = Arc::clone(&executor);
14850 let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
14851 scheduler.schedule(runnable)
14852 });
14853 executor.schedule(runnable);
14854 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14855 assert_ne!(failed_dispatch, 0);
14856 let initial_delivery = initial.deliveries()[0];
14857 let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14858 .finish_delivery(initial_delivery, true);
14859 let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
14860 panic!("the initial accepted delivery must report one exact failure");
14861 };
14862 assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
14863 let failed_dispatch_failure = *failed_dispatch_failure;
14864 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
14865 let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14866 .register(Arc::new(FailDuringReplacementRepublishPublication));
14867
14868 let (returned_tx, returned_rx) = mpsc::channel();
14869 let (release_tx, release_rx) = mpsc::channel();
14870 let cancelling_executor = Arc::clone(&executor);
14871 let cancellation = std::thread::spawn(move || {
14872 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14873 *slot.borrow_mut() = Some(Box::new(move || {
14874 returned_tx.send(()).unwrap();
14875 release_rx.recv().unwrap();
14876 }));
14877 });
14878 cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
14879 });
14880 returned_rx
14881 .recv_timeout(Duration::from_secs(2))
14882 .expect("the failed replacement publication must reach its completion barrier");
14883
14884 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14885 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14886 let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14887 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14888 executor.request_drain_if_queued();
14889 let publication_state = {
14890 let scheduler = executor
14891 .scheduler_idle_lock
14892 .lock()
14893 .unwrap_or_else(std::sync::PoisonError::into_inner);
14894 (
14895 scheduler.active_dispatch_calls,
14896 scheduler.dispatch_publications.len(),
14897 scheduler.dispatch_publications[0].dispatch,
14898 scheduler.dispatch_publications[0].republish_requested,
14899 )
14900 };
14901 release_tx.send(()).unwrap();
14902 cancellation.join().unwrap();
14903
14904 assert_ne!(replacement_dispatch, 0);
14905 assert_ne!(replacement_dispatch, failed_dispatch);
14906 assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
14907 assert_eq!(
14908 *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14909 .lock()
14910 .unwrap(),
14911 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14912 "the newly armed host request must receive the exact replacement before cancellation"
14913 );
14914 assert_eq!(
14915 executor.dispatch_pending.load(Ordering::Acquire),
14916 replacement_dispatch
14917 );
14918 assert_eq!(
14919 executor.recovery_dispatch.load(Ordering::Acquire),
14920 replacement_dispatch
14921 );
14922 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14923
14924 let delivery = healthy.deliveries()[0];
14925 assert_eq!(delivery.dispatch, replacement_dispatch);
14926 assert_eq!(
14927 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14928 .claim_delivery(delivery.capability()),
14929 Some(replacement_dispatch)
14930 );
14931 let mut host_turn = executor
14932 .try_admit_host_turn(replacement_dispatch)
14933 .expect("the rearmed replacement delivery must be admitted");
14934 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14935 host_turn.drive();
14936 assert!(
14937 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14938 .finish_delivery(delivery, false)
14939 .failed_dispatches
14940 .is_empty()
14941 );
14942 assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
14943 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14944 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14945
14946 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
14947 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
14948 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14949 .state
14950 .lock()
14951 .unwrap_or_else(std::sync::PoisonError::into_inner);
14952 assert!(state.entries.is_empty());
14953 assert!(state.dispatches.is_empty());
14954 }
14955
14956 #[cfg(napi_runtime_os_threads)]
14957 #[test]
14958 fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
14959 use std::sync::mpsc;
14960
14961 {
14962 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14963 .state
14964 .lock()
14965 .unwrap_or_else(std::sync::PoisonError::into_inner);
14966 assert!(state.entries.is_empty());
14967 assert!(state.dispatches.is_empty());
14968 }
14969 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14970 let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14971 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14972 let metrics = Arc::new(RuntimeMetrics::default());
14973 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14974 Arc::clone(&metrics),
14975 dispatch_stale_failure_current_thread_tasks,
14976 &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
14977 ));
14978 let scheduler = Arc::clone(&executor);
14979 let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
14980 scheduler.schedule(runnable)
14981 });
14982 executor.schedule(runnable);
14983
14984 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14985 let initial_delivery = initial.deliveries()[0];
14986 let delayed_completion =
14987 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
14988 let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
14989 panic!("the initial accepted delivery must report one exact failure");
14990 };
14991 assert_eq!(
14992 *delayed_failure,
14993 current_thread_dispatch_failure(dispatch, 0)
14994 );
14995 let delayed_failure = *delayed_failure;
14996
14997 let (returned_tx, returned_rx) = mpsc::channel();
14998 let (release_tx, release_rx) = mpsc::channel();
14999 let publishing_executor = Arc::clone(&executor);
15000 let publication = std::thread::spawn(move || {
15001 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15002 *slot.borrow_mut() = Some(Box::new(move || {
15003 returned_tx.send(()).unwrap();
15004 release_rx.recv().unwrap();
15005 }));
15006 });
15007 publishing_executor.request_drain_if_queued();
15008 });
15009 returned_rx
15010 .recv_timeout(Duration::from_secs(2))
15011 .expect("the old failed epoch must pause before executor recovery");
15012
15013 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15014 let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15015 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15016 executor.request_drain_if_queued();
15017 release_tx.send(()).unwrap();
15018 publication.join().unwrap();
15019
15020 let healthy_delivery = healthy.deliveries()[0];
15021 let delayed_initial_delivery = initial.deliveries()[1];
15022 {
15023 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15024 .state
15025 .lock()
15026 .unwrap_or_else(std::sync::PoisonError::into_inner);
15027 let dispatch_state = state
15028 .dispatches
15029 .get(&dispatch)
15030 .expect("the rearmed dispatch must remain registered");
15031 assert_eq!(dispatch_state.epoch, 1);
15032 assert!(!dispatch_state.is_cancelling());
15033 }
15034
15035 executor.cancel_host_dispatch(delayed_failure);
15036
15037 assert_eq!(
15038 executor.dispatch_pending.load(Ordering::Acquire),
15039 dispatch,
15040 "the delayed old-epoch failure must not consume the rearmed executor capability"
15041 );
15042 assert_eq!(
15043 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15044 .state
15045 .lock()
15046 .unwrap_or_else(std::sync::PoisonError::into_inner)
15047 .dispatches
15048 .get(&dispatch)
15049 .map(|state| (state.epoch, state.phase)),
15050 Some((1, CurrentThreadDispatchPhase::Pending)),
15051 "the delayed old-epoch cancellation must not remove the accepted rearm"
15052 );
15053
15054 assert_eq!(
15055 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
15056 Some(dispatch)
15057 );
15058 let mut host_turn = executor
15059 .try_admit_host_turn(dispatch)
15060 .expect("the current-epoch delivery must be admitted");
15061 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15062 host_turn.drive();
15063 assert!(
15064 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15065 .finish_delivery(healthy_delivery, false)
15066 .failed_dispatches
15067 .is_empty()
15068 );
15069 assert!(
15070 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15071 .finish_delivery(delayed_initial_delivery, false)
15072 .failed_dispatches
15073 .is_empty()
15074 );
15075 assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
15076 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15077
15078 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
15079 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
15080 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15081 .state
15082 .lock()
15083 .unwrap_or_else(std::sync::PoisonError::into_inner);
15084 assert!(state.entries.is_empty());
15085 assert!(state.dispatches.is_empty());
15086 }
15087
15088 #[cfg(napi_runtime_os_threads)]
15089 #[test]
15090 fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
15091 use std::sync::mpsc;
15092
15093 {
15094 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15095 .state
15096 .lock()
15097 .unwrap_or_else(std::sync::PoisonError::into_inner);
15098 assert!(state.entries.is_empty());
15099 assert!(state.dispatches.is_empty());
15100 }
15101 let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15102 .register(Arc::new(FailDuringLateRegistrationPublication));
15103
15104 let metrics = Arc::new(RuntimeMetrics::default());
15105 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15106 Arc::clone(&metrics),
15107 dispatch_late_registration_current_thread_tasks,
15108 &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
15109 ));
15110 let scheduler = Arc::clone(&executor);
15111 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15112 scheduler.schedule(runnable)
15113 });
15114 metrics.runnable_scheduled();
15115 executor
15116 .queue
15117 .lock()
15118 .unwrap_or_else(std::sync::PoisonError::into_inner)
15119 .runnables
15120 .push_back(runnable);
15121 let replacement_dispatch = next_current_thread_dispatch_id();
15122 executor
15123 .recovery_dispatch
15124 .store(replacement_dispatch, Ordering::Release);
15125 executor
15126 .dispatch_pending
15127 .store(replacement_dispatch, Ordering::Release);
15128
15129 let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
15130 let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
15131 let publishing_executor = Arc::clone(&executor);
15132 let publication = std::thread::spawn(move || {
15133 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15134 *slot.borrow_mut() = Some(Box::new(move || {
15135 owner_retired_tx.send(()).unwrap();
15136 request_blocked_rx
15137 .recv_timeout(Duration::from_secs(2))
15138 .expect("the binding-like drain request must wait for the failure transition");
15139 }));
15140 });
15141 publishing_executor.request_drain_if_queued();
15142 });
15143 owner_retired_rx
15144 .recv_timeout(Duration::from_secs(2))
15145 .expect("the failed replacement publication must retire its owner");
15146
15147 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15150 let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15151 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15152 let requesting_executor = Arc::clone(&executor);
15153 let request = std::thread::spawn(move || {
15154 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
15155 *slot.borrow_mut() = Some(Box::new(move || {
15156 request_blocked_tx.send(()).unwrap();
15157 }));
15158 });
15159 requesting_executor.request_drain_if_queued();
15160 });
15161
15162 publication.join().unwrap();
15163 request.join().unwrap();
15164
15165 assert!(
15166 healthy.deliveries().is_empty(),
15167 "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
15168 );
15169 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15170 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15171 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15172 assert_eq!(futures::executor::block_on(task.fallible()), None);
15173 {
15174 let scheduler = executor
15175 .scheduler_idle_lock
15176 .lock()
15177 .unwrap_or_else(std::sync::PoisonError::into_inner);
15178 assert_eq!(scheduler.active_dispatch_calls, 0);
15179 assert!(scheduler.dispatch_publications.is_empty());
15180 }
15181
15182 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
15183
15184 let scheduler = Arc::clone(&executor);
15185 let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
15186 scheduler.schedule(runnable)
15187 });
15188 executor.schedule(later_runnable);
15189 let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15190 let later_delivery = healthy.deliveries()[0];
15191 assert_eq!(
15192 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
15193 Some(later_dispatch)
15194 );
15195 let mut host_turn = executor
15196 .try_admit_host_turn(later_dispatch)
15197 .expect("the later exact delivery must be admitted");
15198 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
15199 host_turn.drive();
15200 assert!(
15201 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15202 .finish_delivery(later_delivery, false)
15203 .failed_dispatches
15204 .is_empty()
15205 );
15206 assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
15207 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15208
15209 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
15210 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15211 .state
15212 .lock()
15213 .unwrap_or_else(std::sync::PoisonError::into_inner);
15214 assert!(state.entries.is_empty());
15215 assert!(state.dispatches.is_empty());
15216 }
15217
15218 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
15219 #[test]
15220 fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
15221 {
15222 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15223 .lock()
15224 .unwrap()
15225 .clear();
15226 {
15227 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15228 .state
15229 .lock()
15230 .unwrap_or_else(std::sync::PoisonError::into_inner);
15231 assert!(state.entries.is_empty());
15232 assert!(state.dispatches.is_empty());
15233 }
15234 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15235 .next_delivery
15236 .store(0, Ordering::Relaxed);
15237 let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15238 .register(Arc::new(FailDuringNestedUnwindPublication));
15239
15240 let metrics = Arc::new(RuntimeMetrics::default());
15241 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15242 Arc::clone(&metrics),
15243 dispatch_nested_unwind_current_thread_tasks,
15244 &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
15245 ));
15246 let scheduler = Arc::clone(&executor);
15247 let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
15248 scheduler.schedule(runnable)
15249 });
15250
15251 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15252 *slot.borrow_mut() = Some(Box::new(|| {
15253 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15254 .next_delivery
15255 .store(u64::MAX, Ordering::Relaxed);
15256 }));
15257 });
15258 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
15259 assert!(
15260 publication.is_err(),
15261 "replacement delivery-id exhaustion must unwind through the outer failed publication"
15262 );
15263
15264 let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15265 .lock()
15266 .unwrap()
15267 .clone();
15268 assert_eq!(dispatches.len(), 2);
15269 let failed_dispatch = dispatches[0];
15270 let replacement_dispatch = dispatches[1];
15271 assert_ne!(failed_dispatch, replacement_dispatch);
15272 assert_eq!(
15273 executor.dispatch_pending.load(Ordering::Acquire),
15274 replacement_dispatch
15275 );
15276 assert_eq!(
15277 executor.recovery_dispatch.load(Ordering::Acquire),
15278 replacement_dispatch
15279 );
15280 {
15281 let scheduler = executor
15282 .scheduler_idle_lock
15283 .lock()
15284 .unwrap_or_else(std::sync::PoisonError::into_inner);
15285 assert_eq!(scheduler.active_dispatch_calls, 0);
15286 assert!(scheduler.dispatch_publications.is_empty());
15287 }
15288 {
15289 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15290 .state
15291 .lock()
15292 .unwrap_or_else(std::sync::PoisonError::into_inner);
15293 assert!(
15294 !state.dispatches.contains_key(&failed_dispatch),
15295 "outer unwind cleanup must retire the exact old cancelling dispatch"
15296 );
15297 assert!(
15298 !state.dispatches.contains_key(&replacement_dispatch),
15299 "the replacement publication guard must release its aborted registry state"
15300 );
15301 }
15302
15303 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15304 .next_delivery
15305 .store(1, Ordering::Relaxed);
15306 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
15307 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15308 let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15309 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15310
15311 executor.request_drain_if_queued();
15312
15313 assert_eq!(
15314 *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
15315 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15316 "retry must reuse the replacement reserved before the nested unwind"
15317 );
15318 let delivery = healthy.deliveries()[0];
15319 assert_eq!(delivery.dispatch, replacement_dispatch);
15320 assert_eq!(
15321 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
15322 Some(replacement_dispatch)
15323 );
15324 let mut host_turn = executor
15325 .try_admit_host_turn(replacement_dispatch)
15326 .expect("the retried replacement delivery must be admitted");
15327 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15328 host_turn.drive();
15329 assert!(
15330 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15331 .finish_delivery(delivery, false)
15332 .failed_dispatches
15333 .is_empty()
15334 );
15335 assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
15336 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15337
15338 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
15339 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15340 .state
15341 .lock()
15342 .unwrap_or_else(std::sync::PoisonError::into_inner);
15343 assert!(state.entries.is_empty());
15344 assert!(state.dispatches.is_empty());
15345 }
15346
15347 #[test]
15348 fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
15349 reset_current_thread_recovery_host_dispatches();
15350 let metrics = Arc::new(RuntimeMetrics::default());
15351 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15352 Arc::clone(&metrics),
15353 count_current_thread_recovery_host_dispatch,
15354 ));
15355
15356 let scheduler = Arc::clone(&executor);
15357 let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
15358 scheduler.schedule(runnable)
15359 });
15360 executor.schedule(runnable);
15361 assert_eq!(current_thread_recovery_host_dispatches(), 1);
15362 let failed_dispatch = current_thread_dispatch(&executor);
15363
15364 executor.cancel_host_dispatch_for_test(failed_dispatch);
15367 assert_eq!(
15368 current_thread_recovery_host_dispatches(),
15369 2,
15370 "cancellation must request one replacement without another submission"
15371 );
15372 let replacement_dispatch = current_thread_dispatch(&executor);
15373 assert_ne!(replacement_dispatch, failed_dispatch);
15374 assert_eq!(
15375 executor.recovery_dispatch.load(Ordering::Acquire),
15376 replacement_dispatch
15377 );
15378
15379 executor.drive_host_turn_for_dispatch(replacement_dispatch);
15380 assert_eq!(futures::executor::block_on(task), 1);
15381 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15382 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15383 }
15384
15385 #[test]
15386 fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
15387 reset_current_thread_recovery_host_dispatches();
15388 let metrics = Arc::new(RuntimeMetrics::default());
15389 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15390 Arc::clone(&metrics),
15391 count_current_thread_recovery_host_dispatch,
15392 ));
15393 let backend =
15394 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15395
15396 let registration = backend
15397 .work
15398 .try_register_async()
15399 .expect("the backend must accept work");
15400 let task = spawn_registered(
15401 &backend,
15402 Arc::clone(&metrics),
15403 std::future::pending::<()>(),
15404 registration,
15405 );
15406 let failed_dispatch = current_thread_dispatch(&executor);
15407 executor.cancel_host_dispatch_for_test(failed_dispatch);
15408 let replacement_dispatch = current_thread_dispatch(&executor);
15409
15410 executor.cancel_host_dispatch_for_test(replacement_dispatch);
15411
15412 assert_eq!(
15413 current_thread_recovery_host_dispatches(),
15414 2,
15415 "a second scheduler failure must not create an unbounded retry loop"
15416 );
15417 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15418 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15419 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15420 assert_eq!(
15421 futures::executor::block_on(task).unwrap_err().to_string(),
15422 "async runtime stopped before the task completed",
15423 "terminal host failure must settle the original operation as cancelled"
15424 );
15425 assert_eq!(backend.work.state.lock().unwrap().active, 0);
15426
15427 let later_registration = backend
15428 .work
15429 .try_register_async()
15430 .expect("terminal cancellation must reopen acceptance");
15431 let later_task = spawn_registered(
15432 &backend,
15433 Arc::clone(&metrics),
15434 async { 2usize },
15435 later_registration,
15436 );
15437 assert_eq!(
15438 current_thread_recovery_host_dispatches(),
15439 3,
15440 "terminal cancellation must not permanently close the executor"
15441 );
15442 executor.drive_host_turn();
15443 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
15444 }
15445
15446 #[cfg(napi_runtime_os_threads)]
15455 #[test]
15456 fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
15457 use std::{sync::mpsc, time::Duration};
15458
15459 let (done_tx, done_rx) = mpsc::channel();
15460 std::thread::spawn(move || {
15461 let metrics = Arc::new(RuntimeMetrics::default());
15462 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15463 Arc::clone(&metrics),
15464 accept_current_thread_host_dispatch,
15465 ));
15466
15467 let scheduler_a = Arc::clone(&executor);
15469 let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15470 scheduler_a.schedule(runnable)
15471 });
15472 let shared = async move { task_a.fallible().await }.boxed().shared();
15473 let shared_for_b = shared.clone();
15474
15475 let scheduler_b = Arc::clone(&executor);
15477 let (runnable_b, task_b) = async_task::spawn(
15478 async move {
15479 shared_for_b.await;
15480 },
15481 move |runnable| scheduler_b.schedule(runnable),
15482 );
15483 executor.schedule(runnable_b);
15484 executor.drive_host_turn();
15485 assert_eq!(
15486 metrics.queued_runnables.load(Ordering::Relaxed),
15487 0,
15488 "B must be parked and unscheduled after its first poll"
15489 );
15490
15491 executor.schedule(runnable_a);
15494 let failed_dispatch = current_thread_dispatch(&executor);
15495 executor.cancel_host_dispatch_for_test(failed_dispatch);
15496 let replacement_dispatch = current_thread_dispatch(&executor);
15497 executor.cancel_host_dispatch_for_test(replacement_dispatch);
15498
15499 assert!(
15500 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15501 "the cancellation sweep must reopen the executor"
15502 );
15503 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15504 assert!(
15505 futures::executor::block_on(task_b.fallible()).is_none(),
15506 "B's rejected wake must still cancel B"
15507 );
15508 drop(shared);
15509 done_tx.send(()).unwrap();
15510 });
15511 done_rx
15512 .recv_timeout(Duration::from_secs(5))
15513 .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
15514 }
15515
15516 #[cfg(napi_runtime_os_threads)]
15526 #[test]
15527 fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
15528 use std::{
15529 sync::{Barrier, mpsc},
15530 time::Duration,
15531 };
15532
15533 let (done_tx, done_rx) = mpsc::channel();
15534 std::thread::spawn(move || {
15535 let metrics = Arc::new(RuntimeMetrics::default());
15536 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15537 Arc::clone(&metrics),
15538 accept_current_thread_host_dispatch,
15539 ));
15540 let backend =
15541 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15542 let generation = backend.generation();
15543
15544 let (abort_a, guard_a) = backend
15546 .work
15547 .try_register_async()
15548 .expect("the backend must accept work");
15549 let scheduler_a = Arc::clone(&executor);
15550 let (runnable_a, task_a) = async_task::spawn(
15551 RegisteredTaskFuture::new(
15552 Abortable::new(std::future::pending::<()>(), abort_a),
15553 guard_a,
15554 generation,
15555 ),
15556 move |runnable| scheduler_a.schedule(runnable),
15557 );
15558 let shared = async move { task_a.fallible().await.is_some() }
15559 .boxed()
15560 .shared();
15561 let shared_for_b = shared.clone();
15562
15563 let (abort_b, guard_b) = backend
15567 .work
15568 .try_register_async()
15569 .expect("the backend must accept work");
15570 let wake_in_flight = Arc::new(Barrier::new(2));
15571 let wake_release = Arc::new(Barrier::new(2));
15572 let scheduler_b = Arc::clone(&executor);
15573 let (runnable_b, task_b) = async_task::spawn(
15574 RegisteredTaskFuture::new(
15575 Abortable::new(
15576 async move {
15577 shared_for_b.await;
15578 },
15579 abort_b,
15580 ),
15581 guard_b,
15582 generation,
15583 ),
15584 {
15585 let wake_in_flight = Arc::clone(&wake_in_flight);
15586 let wake_release = Arc::clone(&wake_release);
15587 move |runnable| {
15588 wake_in_flight.wait();
15589 wake_release.wait();
15590 scheduler_b.schedule(runnable);
15591 }
15592 },
15593 );
15594 executor.schedule(runnable_b);
15595 executor.drive_host_turn();
15596 assert_eq!(
15597 metrics.queued_runnables.load(Ordering::Relaxed),
15598 0,
15599 "B must be parked and unscheduled after its first poll"
15600 );
15601
15602 let waker_thread = std::thread::spawn(move || drop(runnable_a));
15606 wake_in_flight.wait();
15607
15608 backend.begin_shutdown();
15611 wake_release.wait();
15612 backend.wait_until_idle();
15613
15614 waker_thread
15615 .join()
15616 .expect("the waker thread must not panic");
15617 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15618 assert!(
15619 futures::executor::block_on(task_b.fallible()).is_none(),
15620 "B's rejected wake must still cancel B"
15621 );
15622 assert_eq!(backend.work.state.lock().unwrap().active, 0);
15623 drop(shared);
15624 done_tx.send(()).unwrap();
15625 });
15626 done_rx
15627 .recv_timeout(Duration::from_secs(5))
15628 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
15629 }
15630
15631 #[cfg(napi_runtime_os_threads)]
15641 #[test]
15642 fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
15643 use std::{sync::mpsc, time::Duration};
15644
15645 let (done_tx, done_rx) = mpsc::channel();
15646 std::thread::spawn(move || {
15647 {
15648 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15649 .state
15650 .lock()
15651 .unwrap_or_else(std::sync::PoisonError::into_inner);
15652 assert!(state.entries.is_empty());
15653 assert!(state.dispatches.is_empty());
15654 }
15655 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
15656 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15657 ));
15658 let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15659 .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15660 let metrics = Arc::new(RuntimeMetrics::default());
15661 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15662 Arc::clone(&metrics),
15663 dispatch_waker_stack_failure_current_thread_tasks,
15664 &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15665 ));
15666
15667 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15669 let shared = rx.map(|_| ()).boxed().shared();
15670 let shared_for_b = shared.clone();
15671 let scheduler_b = Arc::clone(&executor);
15672 let (runnable_b, task_b) = async_task::spawn(
15673 async move {
15674 shared_for_b.await;
15675 },
15676 move |runnable| scheduler_b.schedule(runnable),
15677 );
15678 executor.schedule(runnable_b);
15679
15680 let delivery = driver.deliveries()[0];
15683 let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15684 .claim_delivery(delivery.capability())
15685 .expect("the accepted delivery must be claimable");
15686 let mut host_turn = executor
15687 .try_admit_host_turn(dispatch)
15688 .expect("the claimed delivery must be admitted");
15689 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15690 host_turn.drive();
15691 drop(host_turn);
15692 let completion =
15693 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15694 assert!(completion.failed_dispatches.is_empty());
15695 assert!(!completion.redispatch);
15696 assert_eq!(
15697 metrics.queued_runnables.load(Ordering::Relaxed),
15698 0,
15699 "B must be parked and unscheduled after its first poll"
15700 );
15701
15702 driver.fail.store(true, Ordering::SeqCst);
15704 tx.send(()).expect("B must still hold its Shared clone");
15708 assert_eq!(
15709 driver.deliveries().len(),
15710 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
15714 "the wake must publish the dispatch, its one replacement, and the \
15715 retirement turns the terminal transition owes itself"
15716 );
15717 assert!(
15718 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15719 "the terminal failure must reopen the executor before the waker returns"
15720 );
15721 assert!(
15722 executor.has_rejected_work(),
15723 "B must be moved aside, not dropped on the waker's stack"
15724 );
15725 assert_eq!(
15726 metrics.queued_runnables.load(Ordering::Relaxed),
15727 0,
15728 "the moved runnable carries its queued debit"
15729 );
15730 let mut task_b = task_b.fallible();
15731 assert!(
15732 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15733 "B must still be pending: nothing may be dropped on the waker's stack"
15734 );
15735
15736 executor.drive_host_turn();
15739 assert!(!executor.has_rejected_work());
15740 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15741 assert!(
15742 poll_ready_within(&mut task_b, Duration::from_secs(5))
15743 .expect("the host turn must cancel B")
15744 .is_none(),
15745 "the terminal failure must still cancel B"
15746 );
15747 drop(shared);
15748
15749 CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
15750 let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15751 .state
15752 .lock()
15753 .unwrap_or_else(std::sync::PoisonError::into_inner);
15754 assert!(state.entries.is_empty());
15755 assert!(state.dispatches.is_empty());
15756 done_tx.send(()).unwrap();
15757 });
15758 done_rx
15759 .recv_timeout(Duration::from_secs(5))
15760 .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
15761 }
15762
15763 #[cfg(napi_runtime_os_threads)]
15767 fn poll_ready_within<F: Future + Unpin>(
15768 future: &mut F,
15769 timeout: std::time::Duration,
15770 ) -> Option<F::Output> {
15771 let deadline = std::time::Instant::now() + timeout;
15772 let mut cx = Context::from_waker(futures::task::noop_waker_ref());
15773 loop {
15774 if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
15775 return Some(output);
15776 }
15777 if std::time::Instant::now() >= deadline {
15778 return None;
15779 }
15780 std::thread::sleep(std::time::Duration::from_millis(1));
15781 }
15782 }
15783
15784 #[cfg(napi_runtime_os_threads)]
15792 #[test]
15793 fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
15794 use std::{sync::mpsc, time::Duration};
15795
15796 const WAIT: Duration = Duration::from_secs(5);
15797 let (done_tx, done_rx) = mpsc::channel();
15798 std::thread::spawn(move || {
15799 let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
15800 {
15801 let state = registry
15802 .state
15803 .lock()
15804 .unwrap_or_else(std::sync::PoisonError::into_inner);
15805 assert!(state.entries.is_empty());
15806 assert!(state.dispatches.is_empty());
15807 }
15808 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
15809 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15810 let metrics = Arc::new(RuntimeMetrics::default());
15811 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15812 Arc::clone(&metrics),
15813 dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
15814 registry,
15815 ));
15816
15817 let service = |index: usize| {
15819 let delivery = driver.deliveries()[index];
15820 let dispatch = registry
15821 .claim_delivery(delivery.capability())
15822 .expect("the accepted delivery must be claimable");
15823 let mut host_turn = executor
15824 .try_admit_host_turn(dispatch)
15825 .expect("the claimed delivery must be admitted");
15826 registry.mark_serviced(dispatch);
15827 host_turn.drive();
15828 drop(host_turn);
15829 let completion = registry.finish_delivery(delivery, false);
15830 assert!(completion.failed_dispatches.is_empty());
15831 assert!(!completion.redispatch);
15832 };
15833
15834 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15836 let shared = rx.map(|_| ()).boxed().shared();
15837 let shared_for_b = shared.clone();
15838 let scheduler_b = Arc::clone(&executor);
15839 let (runnable_b, task_b) = async_task::spawn(
15840 async move {
15841 shared_for_b.await;
15842 },
15843 move |runnable| scheduler_b.schedule(runnable),
15844 );
15845 executor.schedule(runnable_b);
15846 service(0);
15847 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15848
15849 driver.fail.store(true, Ordering::SeqCst);
15852 tx.send(()).expect("B must still hold its Shared clone");
15853 const AFTER_WAKE: usize =
15857 1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
15858 assert_eq!(driver.deliveries().len(), AFTER_WAKE);
15859 let mut task_b = task_b.fallible();
15860 assert!(
15861 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15862 "B must still be pending: nothing may be dropped on the waker's stack"
15863 );
15864
15865 driver.fail.store(false, Ordering::SeqCst);
15868 let scheduler_later = Arc::clone(&executor);
15869 let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
15870 scheduler_later.schedule(runnable)
15871 });
15872 executor.schedule(runnable_later);
15873 assert_eq!(
15874 driver.deliveries().len(),
15875 AFTER_WAKE + 1,
15876 "a submission after the terminal failure must publish a fresh delivery"
15877 );
15878 assert!(
15879 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15880 "the executor must be open for the healthy host"
15881 );
15882
15883 service(AFTER_WAKE);
15886 let mut task_later = task_later.fallible();
15887 assert_eq!(
15888 poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
15889 Some(42),
15890 "the later task must complete on the delivered host turn"
15891 );
15892 assert!(
15893 poll_ready_within(&mut task_b, WAIT)
15894 .expect("B must have been cancelled by the host turn")
15895 .is_none(),
15896 "the terminal failure must still cancel the work queued when it happened"
15897 );
15898 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15899 drop(shared);
15900
15901 registry.unregister(registration);
15902 let state = registry
15903 .state
15904 .lock()
15905 .unwrap_or_else(std::sync::PoisonError::into_inner);
15906 assert!(state.entries.is_empty());
15907 assert!(state.dispatches.is_empty());
15908 done_tx.send(()).unwrap();
15909 });
15910 done_rx
15911 .recv_timeout(WAIT)
15912 .expect("the healthy host never got a delivery for the later submission");
15913 }
15914
15915 #[cfg(napi_runtime_os_threads)]
15935 #[test]
15936 fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
15937 use std::{sync::mpsc, time::Duration};
15938
15939 const WAIT: Duration = Duration::from_secs(5);
15940 let (done_tx, done_rx) = mpsc::channel();
15941 std::thread::spawn(move || {
15942 let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
15943 {
15944 let state = registry
15945 .state
15946 .lock()
15947 .unwrap_or_else(std::sync::PoisonError::into_inner);
15948 assert!(state.entries.is_empty());
15949 assert!(state.dispatches.is_empty());
15950 }
15951 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
15952 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15953 let metrics = Arc::new(RuntimeMetrics::default());
15954 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15955 Arc::clone(&metrics),
15956 dispatch_retirement_turn_current_thread_tasks,
15957 registry,
15958 ));
15959
15960 let service = |index: usize| {
15962 let delivery = driver.deliveries()[index];
15963 let dispatch = registry
15964 .claim_delivery(delivery.capability())
15965 .expect("the accepted delivery must be claimable");
15966 let mut host_turn = executor
15967 .try_admit_host_turn(dispatch)
15968 .expect("the claimed delivery must be admitted");
15969 registry.mark_serviced(dispatch);
15970 host_turn.drive();
15971 drop(host_turn);
15972 let completion = registry.finish_delivery(delivery, false);
15973 assert!(completion.failed_dispatches.is_empty());
15974 assert!(!completion.redispatch);
15975 };
15976
15977 let (tx, rx) = futures::channel::oneshot::channel::<()>();
15979 let shared = rx.map(|_| ()).boxed().shared();
15980 let shared_for_b = shared.clone();
15981 let scheduler_b = Arc::clone(&executor);
15982 let (runnable_b, task_b) = async_task::spawn(
15983 async move {
15984 shared_for_b.await;
15985 },
15986 move |runnable| scheduler_b.schedule(runnable),
15987 );
15988 executor.schedule(runnable_b);
15989 service(0);
15990 assert_eq!(
15991 metrics.queued_runnables.load(Ordering::Relaxed),
15992 0,
15993 "B must be parked and unscheduled after its first poll"
15994 );
15995
15996 driver.fail_budget.store(2, Ordering::SeqCst);
15999 tx.send(()).expect("B must still hold its Shared clone");
16000
16001 assert_eq!(
16002 driver.deliveries().len(),
16003 4,
16004 "the wake must publish the dispatch, its one replacement, and the \
16005 retirement turn the terminal transition owes itself; without that \
16006 third publication no host turn ever follows and B stays pending \
16007 forever"
16008 );
16009 assert!(
16010 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16011 "the terminal failure must reopen the executor before the waker returns"
16012 );
16013 assert!(
16014 executor.has_rejected_work(),
16015 "B must be moved aside, not dropped on the waker's stack"
16016 );
16017 assert_eq!(
16018 metrics.queued_runnables.load(Ordering::Relaxed),
16019 0,
16020 "the moved runnable carries its queued debit"
16021 );
16022 let mut task_b = task_b.fallible();
16023 assert!(
16024 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16025 "B must still be pending: nothing may be dropped on the waker's stack"
16026 );
16027
16028 service(3);
16031 assert!(
16032 !executor.has_rejected_work(),
16033 "the retirement turn must cancel the moved work on its own stack"
16034 );
16035 assert!(
16036 poll_ready_within(&mut task_b, WAIT)
16037 .expect("the retirement turn must cancel B")
16038 .is_none(),
16039 "the terminal failure must still cancel the work queued when it happened"
16040 );
16041 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16042 drop(shared);
16043
16044 registry.unregister(registration);
16045 let state = registry
16046 .state
16047 .lock()
16048 .unwrap_or_else(std::sync::PoisonError::into_inner);
16049 assert!(state.entries.is_empty());
16050 assert!(state.dispatches.is_empty());
16051 done_tx.send(()).unwrap();
16052 });
16053 done_rx.recv_timeout(WAIT).expect(
16054 "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
16055 `queue.rejected` and its JoinHandle never settled",
16056 );
16057 }
16058
16059 #[cfg(napi_runtime_os_threads)]
16089 #[test]
16090 fn current_thread_nested_retirement_failure_keeps_its_continuation() {
16091 use std::{sync::mpsc, time::Duration};
16092
16093 const WAIT: Duration = Duration::from_secs(5);
16094 let (done_tx, done_rx) = mpsc::channel();
16095 std::thread::spawn(move || {
16096 let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
16097 {
16098 let state = registry
16099 .state
16100 .lock()
16101 .unwrap_or_else(std::sync::PoisonError::into_inner);
16102 assert!(state.entries.is_empty());
16103 assert!(state.dispatches.is_empty());
16104 }
16105 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16106 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16107 let metrics = Arc::new(RuntimeMetrics::default());
16108 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16109 Arc::clone(&metrics),
16110 dispatch_nested_retirement_current_thread_tasks,
16111 registry,
16112 ));
16113
16114 let service = |index: usize| {
16116 let delivery = driver.deliveries()[index];
16117 let dispatch = registry
16118 .claim_delivery(delivery.capability())
16119 .expect("the accepted delivery must be claimable");
16120 let mut host_turn = executor
16121 .try_admit_host_turn(dispatch)
16122 .expect("the claimed delivery must be admitted");
16123 registry.mark_serviced(dispatch);
16124 host_turn.drive();
16125 drop(host_turn);
16126 let completion = registry.finish_delivery(delivery, false);
16127 assert!(completion.failed_dispatches.is_empty());
16128 assert!(!completion.redispatch);
16129 };
16130
16131 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16134 let shared = rx.map(|_| ()).boxed().shared();
16135 let shared_for_b = shared.clone();
16136 let scheduler_b = Arc::clone(&executor);
16137 let (runnable_b, task_b) = async_task::spawn(
16138 async move {
16139 shared_for_b.await;
16140 },
16141 move |runnable| scheduler_b.schedule(runnable),
16142 );
16143 executor.schedule(runnable_b);
16144 service(0);
16145 assert_eq!(
16146 metrics.queued_runnables.load(Ordering::Relaxed),
16147 0,
16148 "B must be parked and unscheduled after its first poll"
16149 );
16150
16151 let scheduler_c = Arc::clone(&executor);
16156 let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
16157 scheduler_c.schedule(runnable);
16158 });
16159 let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
16160 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16161 let producer_executor = Arc::clone(&executor);
16162 let producer = std::thread::spawn(move || {
16163 let runnable = gate_rx
16164 .recv()
16165 .expect("the retirement turn must open the window");
16166 producer_executor.schedule(runnable);
16167 queued_tx.send(()).expect("W must still be waiting");
16168 });
16169 driver.during_delivery(3, move || {
16170 gate_tx.send(runnable_c).expect("the producer must be live");
16171 queued_rx
16172 .recv_timeout(WAIT)
16173 .expect("the producer must queue C before the retirement turn fails");
16174 });
16175
16176 driver.fail_budget.store(4, Ordering::SeqCst);
16181 tx.send(()).expect("B must still hold its Shared clone");
16182 producer.join().expect("the producer must not panic");
16183
16184 assert_eq!(
16185 driver.deliveries().len(),
16186 6,
16187 "the nested terminal transition may not publish on this stack, but the \
16188 retirement request it hands back must be serviced after the outer \
16189 publication unwinds; without that sixth delivery no host turn ever \
16190 follows and both B and C stay pending forever"
16191 );
16192 assert!(
16193 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16194 "both terminal failures must reopen the executor before the waker returns"
16195 );
16196 assert!(
16197 executor.has_rejected_work(),
16198 "B and C must be moved aside, not dropped on the waker's stack"
16199 );
16200 assert_eq!(
16201 metrics.queued_runnables.load(Ordering::Relaxed),
16202 0,
16203 "the moved runnables carry their queued debit"
16204 );
16205 let mut task_b = task_b.fallible();
16206 let mut task_c = task_c.fallible();
16207 assert!(
16208 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16209 "B must still be pending: nothing may be dropped on the waker's stack"
16210 );
16211 assert!(
16212 poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
16213 "C must still be pending: nothing may be dropped on the waker's stack"
16214 );
16215
16216 service(5);
16220 assert!(
16221 !executor.has_rejected_work(),
16222 "the retirement turn must cancel both moved runnables on its own stack"
16223 );
16224 assert!(
16225 poll_ready_within(&mut task_b, WAIT)
16226 .expect("the retirement turn must cancel B")
16227 .is_none(),
16228 "the terminal failure must still cancel the work queued when it happened"
16229 );
16230 assert!(
16231 poll_ready_within(&mut task_c, WAIT)
16232 .expect("the retirement turn must cancel C")
16233 .is_none(),
16234 "the nested terminal failure must cancel the work it moved aside too"
16235 );
16236 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16237 drop(shared);
16238
16239 registry.unregister(registration);
16240 let state = registry
16241 .state
16242 .lock()
16243 .unwrap_or_else(std::sync::PoisonError::into_inner);
16244 assert!(state.entries.is_empty());
16245 assert!(state.dispatches.is_empty());
16246 done_tx.send(()).unwrap();
16247 });
16248 done_rx.recv_timeout(WAIT).expect(
16249 "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
16250 runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
16251 );
16252 }
16253
16254 #[cfg(napi_runtime_os_threads)]
16289 #[test]
16290 fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
16291 use std::{sync::mpsc, time::Duration};
16292
16293 const WAIT: Duration = Duration::from_secs(5);
16294 const PRODUCER_BUDGET: usize = 4096;
16297 const BOUNDED_DELIVERIES: usize =
16300 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16301
16302 let (done_tx, done_rx) = mpsc::channel();
16303 std::thread::spawn(move || {
16304 let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
16305 {
16306 let state = registry
16307 .state
16308 .lock()
16309 .unwrap_or_else(std::sync::PoisonError::into_inner);
16310 assert!(state.entries.is_empty());
16311 assert!(state.dispatches.is_empty());
16312 }
16313 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16314 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16315 let metrics = Arc::new(RuntimeMetrics::default());
16316 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16317 Arc::clone(&metrics),
16318 dispatch_sustained_producer_current_thread_tasks,
16319 registry,
16320 ));
16321 let backend =
16322 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16323
16324 let service = |index: usize| {
16326 let delivery = driver.deliveries()[index];
16327 let dispatch = registry
16328 .claim_delivery(delivery.capability())
16329 .expect("the accepted delivery must be claimable");
16330 let mut host_turn = executor
16331 .try_admit_host_turn(dispatch)
16332 .expect("the claimed delivery must be admitted");
16333 registry.mark_serviced(dispatch);
16334 host_turn.drive();
16335 drop(host_turn);
16336 let completion = registry.finish_delivery(delivery, false);
16337 assert!(completion.failed_dispatches.is_empty());
16338 assert!(!completion.redispatch);
16339 };
16340
16341 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16345 let shared = rx.map(|_| ()).boxed().shared();
16346 let shared_for_b = shared.clone();
16347 let work_registration = backend
16348 .work
16349 .try_register_async()
16350 .expect("the backend must accept work");
16351 let task_b = spawn_registered(
16352 &backend,
16353 Arc::clone(&metrics),
16354 async move {
16355 shared_for_b.await;
16356 },
16357 work_registration,
16358 );
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 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16366
16367 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16372 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16373 let producer_executor = Arc::clone(&executor);
16374 let producer = std::thread::spawn(move || {
16375 let mut submissions = 0usize;
16376 while queue_one_rx.recv().is_ok() {
16377 let scheduler = Arc::clone(&producer_executor);
16378 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16379 scheduler.schedule(runnable);
16380 });
16381 task.detach();
16382 producer_executor.schedule(runnable);
16383 submissions += 1;
16384 if queued_tx.send(()).is_err() {
16385 break;
16386 }
16387 }
16388 submissions
16389 });
16390 driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
16391
16392 driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
16395 let before = driver.deliveries().len();
16396 tx.send(()).expect("B must still hold its Shared clone");
16397 let during_wake = driver.deliveries().len() - before;
16398 let unspent = driver.disarm_producer();
16399 let submissions = producer.join().expect("the producer must not panic");
16400
16401 assert_eq!(
16402 during_wake, BOUNDED_DELIVERIES,
16403 "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
16404 on the waker's stack plus its one final offer; without a budget it \
16405 services the producer's stream instead and this wake never returns"
16406 );
16407 assert_eq!(
16408 submissions, during_wake,
16409 "the producer must have queued a runnable during every publication"
16410 );
16411 assert!(
16412 unspent > 0,
16413 "the producer must still have been willing to submit when the turns stopped"
16414 );
16415 assert!(
16416 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16417 "every terminal failure, and the final offer, must reopen the executor \
16418 before the waker returns"
16419 );
16420 assert!(
16421 executor.has_rejected_work(),
16422 "the moved work must be parked, not dropped on the waker's stack"
16423 );
16424 assert_eq!(
16425 executor.dispatch_pending.load(Ordering::Acquire),
16426 0,
16427 "a host that refused every offer including the final one leaves none \
16428 outstanding: this is the documented end state for a host that never accepts"
16429 );
16430 assert_eq!(
16431 metrics.queued_runnables.load(Ordering::Relaxed),
16432 0,
16433 "the moved runnables carry their queued debit"
16434 );
16435 let mut task_b = Box::pin(task_b);
16436 assert!(
16437 poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16438 "B must still be pending: nothing may be dropped on the waker's stack"
16439 );
16440 assert_eq!(
16441 backend.work.state.lock().unwrap().active,
16442 1,
16443 "B's generation guard rides with its parked runnable"
16444 );
16445
16446 backend.wait_until_idle();
16450 assert!(!executor.has_rejected_work());
16451 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16452 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16453 assert!(
16454 poll_ready_within(&mut task_b, WAIT)
16455 .expect("the idle wait must cancel B")
16456 .is_err(),
16457 "the terminal failure must still cancel the work queued when it happened"
16458 );
16459 drop(shared);
16460
16461 registry.unregister(registration);
16462 let state = registry
16463 .state
16464 .lock()
16465 .unwrap_or_else(std::sync::PoisonError::into_inner);
16466 assert!(state.entries.is_empty());
16467 assert!(state.dispatches.is_empty());
16468 done_tx.send(()).unwrap();
16469 });
16470 done_rx.recv_timeout(WAIT).expect(
16471 "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
16472 the wake never returned",
16473 );
16474 }
16475
16476 #[cfg(napi_runtime_os_threads)]
16510 #[test]
16511 fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
16512 use std::{sync::mpsc, time::Duration};
16513
16514 const WAIT: Duration = Duration::from_secs(5);
16515 const BUDGETED_DELIVERIES: usize =
16519 2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16520
16521 let (done_tx, done_rx) = mpsc::channel();
16522 std::thread::spawn(move || {
16523 struct DropFlag(Arc<AtomicBool>);
16524 impl Drop for DropFlag {
16525 fn drop(&mut self) {
16526 self.0.store(true, Ordering::SeqCst);
16527 }
16528 }
16529
16530 let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
16531 {
16532 let state = registry
16533 .state
16534 .lock()
16535 .unwrap_or_else(std::sync::PoisonError::into_inner);
16536 assert!(state.entries.is_empty());
16537 assert!(state.dispatches.is_empty());
16538 }
16539 let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16540 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16541 let metrics = Arc::new(RuntimeMetrics::default());
16542 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16543 Arc::clone(&metrics),
16544 dispatch_final_retirement_offer_current_thread_tasks,
16545 registry,
16546 ));
16547 let backend =
16548 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16549
16550 let service = |index: usize| {
16551 let delivery = driver.deliveries()[index];
16552 let dispatch = registry
16553 .claim_delivery(delivery.capability())
16554 .expect("the accepted delivery must be claimable");
16555 let mut host_turn = executor
16556 .try_admit_host_turn(dispatch)
16557 .expect("the claimed delivery must be admitted");
16558 registry.mark_serviced(dispatch);
16559 host_turn.drive();
16560 drop(host_turn);
16561 let completion = registry.finish_delivery(delivery, false);
16562 assert!(completion.failed_dispatches.is_empty());
16563 assert!(!completion.redispatch);
16564 };
16565
16566 let dropped = Arc::new(AtomicBool::new(false));
16567 let flag = DropFlag(Arc::clone(&dropped));
16568 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16569 let shared = rx.map(|_| ()).boxed().shared();
16570 let shared_for_b = shared.clone();
16571 let work_registration = backend
16572 .work
16573 .try_register_async()
16574 .expect("the backend must accept work");
16575 let task_b = spawn_registered(
16576 &backend,
16577 Arc::clone(&metrics),
16578 async move {
16579 let _flag = flag;
16580 shared_for_b.await;
16581 },
16582 work_registration,
16583 );
16584 service(0);
16585 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16586 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16587
16588 let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16589 let (queued_tx, queued_rx) = mpsc::channel::<()>();
16590 let producer_executor = Arc::clone(&executor);
16591 let producer = std::thread::spawn(move || {
16592 let mut submissions = 0usize;
16593 while queue_one_rx.recv().is_ok() {
16594 let scheduler = Arc::clone(&producer_executor);
16595 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16596 scheduler.schedule(runnable);
16597 });
16598 task.detach();
16599 producer_executor.schedule(runnable);
16600 submissions += 1;
16601 if queued_tx.send(()).is_err() {
16602 break;
16603 }
16604 }
16605 submissions
16606 });
16607 driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
16610 driver
16611 .fail_budget
16612 .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
16613
16614 let before = driver.deliveries().len();
16615 tx.send(()).expect("B must still hold its Shared clone");
16616 let during_wake = driver.deliveries().len() - before;
16617 let unspent = driver.disarm_producer();
16618 let submissions = producer.join().expect("the producer must not panic");
16619
16620 assert_eq!(
16621 during_wake,
16622 BUDGETED_DELIVERIES + 1,
16623 "the budget must be followed by the one offer a producer cannot \
16624 inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
16625 deliveries and never asks the host that would have accepted"
16626 );
16627 assert_eq!(
16628 submissions, BUDGETED_DELIVERIES,
16629 "the producer must have queued a runnable during every budgeted \
16630 publication and gone quiescent before the final offer"
16631 );
16632 assert_eq!(
16633 unspent, 0,
16634 "the producer must have run dry exactly as the budget ran out"
16635 );
16636 assert_eq!(
16637 driver.fail_budget.load(Ordering::SeqCst),
16638 0,
16639 "the host must have refused every budgeted delivery and no more"
16640 );
16641 assert!(
16642 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16643 "the final offer must reopen the executor before the waker returns"
16644 );
16645 assert!(
16646 executor.has_rejected_work(),
16647 "nothing may be dropped on the waker's stack"
16648 );
16649 assert_ne!(
16650 executor.dispatch_pending.load(Ordering::Acquire),
16651 0,
16652 "the wake must leave a dispatch outstanding: that accepted delivery is \
16653 the continuation the parked work has"
16654 );
16655 let mut task_b = Box::pin(task_b);
16656 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16657 assert!(!dropped.load(Ordering::SeqCst));
16658 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16659
16660 service(driver.deliveries().len() - 1);
16663 assert!(
16664 !executor.has_rejected_work(),
16665 "that host turn is a depth-0 stack and must retire everything the wake \
16666 moved aside"
16667 );
16668 assert!(
16669 poll_ready_within(&mut task_b, WAIT)
16670 .expect("the final offer's host turn must cancel B")
16671 .is_err()
16672 );
16673 assert!(
16674 dropped.load(Ordering::SeqCst),
16675 "B's future must be destroyed, on the host's stack and not the waker's"
16676 );
16677 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16678 assert_eq!(
16679 backend.work.state.lock().unwrap().active,
16680 0,
16681 "no GenerationWorkGuard may outlive the retirement"
16682 );
16683 drop(shared);
16684
16685 registry.unregister(registration);
16686 let state = registry
16687 .state
16688 .lock()
16689 .unwrap_or_else(std::sync::PoisonError::into_inner);
16690 assert!(state.entries.is_empty());
16691 assert!(state.dispatches.is_empty());
16692 done_tx.send(()).unwrap();
16693 });
16694 done_rx.recv_timeout(WAIT).expect(
16695 "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16696 );
16697 }
16698
16699 #[cfg(napi_runtime_os_threads)]
16717 #[test]
16718 fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
16719 use std::{sync::mpsc, time::Duration};
16720
16721 const WAIT: Duration = Duration::from_secs(5);
16722 let (done_tx, done_rx) = mpsc::channel();
16723 std::thread::spawn(move || {
16724 struct DropFlag(Arc<AtomicBool>);
16725 impl Drop for DropFlag {
16726 fn drop(&mut self) {
16727 self.0.store(true, Ordering::SeqCst);
16728 }
16729 }
16730
16731 let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
16732 {
16733 let state = registry
16734 .state
16735 .lock()
16736 .unwrap_or_else(std::sync::PoisonError::into_inner);
16737 assert!(state.entries.is_empty());
16738 assert!(state.dispatches.is_empty());
16739 }
16740 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16741 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16742 let metrics = Arc::new(RuntimeMetrics::default());
16743 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16744 Arc::clone(&metrics),
16745 dispatch_retirement_retry_current_thread_tasks,
16746 registry,
16747 ));
16748 let backend =
16749 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16750
16751 let service = |index: usize| {
16752 let delivery = driver.deliveries()[index];
16753 let dispatch = registry
16754 .claim_delivery(delivery.capability())
16755 .expect("the accepted delivery must be claimable");
16756 let mut host_turn = executor
16757 .try_admit_host_turn(dispatch)
16758 .expect("the claimed delivery must be admitted");
16759 registry.mark_serviced(dispatch);
16760 host_turn.drive();
16761 drop(host_turn);
16762 let completion = registry.finish_delivery(delivery, false);
16763 assert!(completion.failed_dispatches.is_empty());
16764 assert!(!completion.redispatch);
16765 };
16766
16767 let dropped = Arc::new(AtomicBool::new(false));
16768 let flag = DropFlag(Arc::clone(&dropped));
16769 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16770 let shared = rx.map(|_| ()).boxed().shared();
16771 let shared_for_b = shared.clone();
16772 let work_registration = backend
16773 .work
16774 .try_register_async()
16775 .expect("the backend must accept work");
16776 let task_b = spawn_registered(
16777 &backend,
16778 Arc::clone(&metrics),
16779 async move {
16780 let _flag = flag;
16781 shared_for_b.await;
16782 },
16783 work_registration,
16784 );
16785 service(0);
16786 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16787 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16788
16789 let before = driver.deliveries().len();
16794 driver.fail_budget.store(3, Ordering::SeqCst);
16795 tx.send(()).expect("B must still hold its Shared clone");
16796 let during_wake = driver.deliveries().len() - before;
16797
16798 assert_eq!(
16799 during_wake, 4,
16800 "a refused offer owes another turn just as a nested transition does: \
16801 work is parked and no dispatch is outstanding"
16802 );
16803 assert_ne!(
16804 executor.dispatch_pending.load(Ordering::Acquire),
16805 0,
16806 "the accepted second offer is the continuation the parked work has"
16807 );
16808 assert!(executor.has_rejected_work());
16809 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16810 let mut task_b = Box::pin(task_b);
16811 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16812 assert!(!dropped.load(Ordering::SeqCst));
16813
16814 service(driver.deliveries().len() - 1);
16816 assert!(!executor.has_rejected_work());
16817 assert!(
16818 poll_ready_within(&mut task_b, WAIT)
16819 .expect("the accepted offer's host turn must cancel B")
16820 .is_err()
16821 );
16822 assert!(dropped.load(Ordering::SeqCst));
16823 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16824 assert_eq!(backend.work.state.lock().unwrap().active, 0);
16825 drop(shared);
16826
16827 registry.unregister(registration);
16828 let state = registry
16829 .state
16830 .lock()
16831 .unwrap_or_else(std::sync::PoisonError::into_inner);
16832 assert!(state.entries.is_empty());
16833 assert!(state.dispatches.is_empty());
16834 done_tx.send(()).unwrap();
16835 });
16836 done_rx
16837 .recv_timeout(WAIT)
16838 .expect("a refused retirement offer left B stranded with a healthy host");
16839 }
16840
16841 #[cfg(napi_runtime_os_threads)]
16873 #[test]
16874 fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
16875 use std::{sync::mpsc, time::Duration};
16876
16877 const WAIT: Duration = Duration::from_secs(5);
16878 const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16880
16881 let (done_tx, done_rx) = mpsc::channel();
16882 std::thread::spawn(move || {
16883 struct DropFlag(Arc<AtomicBool>);
16884 impl Drop for DropFlag {
16885 fn drop(&mut self) {
16886 self.0.store(true, Ordering::SeqCst);
16887 }
16888 }
16889
16890 let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
16891 {
16892 let state = registry
16893 .state
16894 .lock()
16895 .unwrap_or_else(std::sync::PoisonError::into_inner);
16896 assert!(state.entries.is_empty());
16897 assert!(state.dispatches.is_empty());
16898 }
16899 let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16900 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16901 let metrics = Arc::new(RuntimeMetrics::default());
16902 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16903 Arc::clone(&metrics),
16904 dispatch_inline_final_offer_current_thread_tasks,
16905 registry,
16906 ));
16907 let backend =
16908 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16909
16910 let service = |index: usize| {
16911 let delivery = driver.deliveries()[index];
16912 let dispatch = registry
16913 .claim_delivery(delivery.capability())
16914 .expect("the accepted delivery must be claimable");
16915 let mut host_turn = executor
16916 .try_admit_host_turn(dispatch)
16917 .expect("the claimed delivery must be admitted");
16918 registry.mark_serviced(dispatch);
16919 host_turn.drive();
16920 drop(host_turn);
16921 let completion = registry.finish_delivery(delivery, false);
16922 assert!(completion.failed_dispatches.is_empty());
16923 assert!(!completion.redispatch);
16924 };
16925
16926 let dropped = Arc::new(AtomicBool::new(false));
16927 let flag = DropFlag(Arc::clone(&dropped));
16928 let (tx, rx) = futures::channel::oneshot::channel::<()>();
16929 let shared = rx.map(|_| ()).boxed().shared();
16930 let shared_for_b = shared.clone();
16931 let work_registration = backend
16932 .work
16933 .try_register_async()
16934 .expect("the backend must accept work");
16935 let task_b = spawn_registered(
16936 &backend,
16937 Arc::clone(&metrics),
16938 async move {
16939 let _flag = flag;
16940 shared_for_b.await;
16941 },
16942 work_registration,
16943 );
16944 service(0);
16945 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16946 assert_eq!(backend.work.state.lock().unwrap().active, 1);
16947
16948 let before = driver.deliveries().len();
16953 let serviced_inline = Arc::new(AtomicBool::new(false));
16954 {
16955 let executor = Arc::clone(&executor);
16956 let hook_driver = Arc::clone(&driver);
16957 let serviced_inline = Arc::clone(&serviced_inline);
16958 driver.during_delivery(before + REFUSED_DELIVERIES, move || {
16959 let delivery = *hook_driver
16960 .deliveries()
16961 .last()
16962 .expect("the final offer must be recorded before the hook runs");
16963 let dispatch = registry
16964 .claim_delivery(delivery.capability())
16965 .expect("the final offer must be claimable");
16966 let mut host_turn = executor
16967 .try_admit_host_turn(dispatch)
16968 .expect("the claimed final offer must be admitted");
16969 registry.mark_serviced(dispatch);
16970 host_turn.drive();
16971 drop(host_turn);
16972 let completion = registry.finish_delivery(delivery, false);
16973 assert!(completion.failed_dispatches.is_empty());
16974 serviced_inline.store(true, Ordering::SeqCst);
16975 });
16976 }
16977 driver
16978 .fail_budget
16979 .store(REFUSED_DELIVERIES, Ordering::SeqCst);
16980 tx.send(()).expect("B must still hold its Shared clone");
16981 let during_wake = driver.deliveries().len() - before;
16982
16983 assert!(
16984 serviced_inline.load(Ordering::SeqCst),
16985 "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
16986 );
16987 assert_eq!(
16988 during_wake,
16989 REFUSED_DELIVERIES + 2,
16990 "an accepted offer whose host turn could not act owes the same continuation a refused \
16991 one owes: work parked and no dispatch outstanding"
16992 );
16993 assert_ne!(
16994 executor.dispatch_pending.load(Ordering::Acquire),
16995 0,
16996 "the publication that follows the consumed offer is the continuation the parked work has"
16997 );
16998 assert!(executor.has_rejected_work());
16999 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17000 let mut task_b = Box::pin(task_b);
17001 assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17002 assert!(!dropped.load(Ordering::SeqCst));
17003
17004 service(driver.deliveries().len() - 1);
17006 assert!(!executor.has_rejected_work());
17007 assert!(
17008 poll_ready_within(&mut task_b, WAIT)
17009 .expect("the host turn that follows the consumed offer must cancel B")
17010 .is_err()
17011 );
17012 assert!(dropped.load(Ordering::SeqCst));
17013 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17014 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17015 drop(shared);
17016
17017 registry.unregister(registration);
17018 let state = registry
17019 .state
17020 .lock()
17021 .unwrap_or_else(std::sync::PoisonError::into_inner);
17022 assert!(state.entries.is_empty());
17023 assert!(state.dispatches.is_empty());
17024 done_tx.send(()).unwrap();
17025 });
17026 done_rx.recv_timeout(WAIT).expect(
17027 "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
17028 );
17029 }
17030
17031 #[cfg(napi_runtime_os_threads)]
17046 #[test]
17047 fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
17048 use std::time::Duration;
17049
17050 let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
17051 {
17052 let state = registry
17053 .state
17054 .lock()
17055 .unwrap_or_else(std::sync::PoisonError::into_inner);
17056 assert!(state.entries.is_empty());
17057 assert!(state.dispatches.is_empty());
17058 }
17059 let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
17060 let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17061 let metrics = Arc::new(RuntimeMetrics::default());
17062 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17063 Arc::clone(&metrics),
17064 dispatch_submission_failure_current_thread_tasks,
17065 registry,
17066 ));
17067 let backend =
17068 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17069
17070 driver.fail.store(true, Ordering::SeqCst);
17073
17074 let work_registration = backend
17075 .work
17076 .try_register_async()
17077 .expect("the backend must accept work");
17078 let task = spawn_registered(
17079 &backend,
17080 Arc::clone(&metrics),
17081 std::future::pending::<()>(),
17082 work_registration,
17083 );
17084
17085 assert_eq!(
17086 driver.deliveries().len(),
17087 2,
17088 "the submission must publish the dispatch and exactly one replacement"
17089 );
17090 assert!(
17091 !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
17092 "the terminal failure must reopen the executor"
17093 );
17094 assert!(
17095 !executor.has_rejected_work(),
17096 "a submission's own stack is not a waker's: the rejected work must be cancelled here, because nothing else will"
17097 );
17098 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17099 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17100
17101 let mut task = Box::pin(task);
17102 assert_eq!(
17103 poll_ready_within(&mut task, Duration::ZERO)
17104 .expect("the submission must settle without any later entry")
17105 .unwrap_err()
17106 .to_string(),
17107 "async runtime stopped before the task completed",
17108 "terminal host failure must settle the submitted operation as cancelled"
17109 );
17110
17111 registry.unregister(registration);
17112 let state = registry
17113 .state
17114 .lock()
17115 .unwrap_or_else(std::sync::PoisonError::into_inner);
17116 assert!(state.entries.is_empty());
17117 assert!(state.dispatches.is_empty());
17118 }
17119
17120 #[cfg(napi_runtime_os_threads)]
17134 #[test]
17135 fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
17136 use std::{
17137 sync::{Barrier, mpsc},
17138 time::Duration,
17139 };
17140
17141 let (done_tx, done_rx) = mpsc::channel();
17142 std::thread::spawn(move || {
17143 let metrics = Arc::new(RuntimeMetrics::default());
17144 let options = RuntimeOptions {
17145 flavor: RuntimeFlavor::MultiThread,
17146 worker_threads: 2,
17147 max_blocking_tasks: 1,
17148 thread_name_prefix: "mt-inline-drop".to_string(),
17149 park_deadline: None,
17150 drain_linger: DEFAULT_DRAIN_LINGER,
17151 };
17152 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17153 let backend =
17154 RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
17155 let generation = backend.generation();
17156
17157 let (abort_a, guard_a) = backend
17159 .work
17160 .try_register_async()
17161 .expect("the backend must accept work");
17162 let scheduler_a = Arc::clone(&executor);
17163 let (runnable_a, task_a) = async_task::spawn(
17164 RegisteredTaskFuture::new(
17165 Abortable::new(std::future::pending::<()>(), abort_a),
17166 guard_a,
17167 generation,
17168 ),
17169 move |runnable| scheduler_a.schedule(runnable),
17170 );
17171 let shared = async move { task_a.fallible().await.is_some() }
17172 .boxed()
17173 .shared();
17174 let shared_for_b = shared.clone();
17175
17176 let (abort_b, guard_b) = backend
17180 .work
17181 .try_register_async()
17182 .expect("the backend must accept work");
17183 let wake_in_flight = Arc::new(Barrier::new(2));
17184 let wake_release = Arc::new(Barrier::new(2));
17185 let scheduler_b = Arc::clone(&executor);
17186 let (runnable_b, task_b) = async_task::spawn(
17187 RegisteredTaskFuture::new(
17188 Abortable::new(
17189 async move {
17190 shared_for_b.await;
17191 },
17192 abort_b,
17193 ),
17194 guard_b,
17195 generation,
17196 ),
17197 {
17198 let wake_in_flight = Arc::clone(&wake_in_flight);
17199 let wake_release = Arc::clone(&wake_release);
17200 move |runnable| {
17201 wake_in_flight.wait();
17202 wake_release.wait();
17203 scheduler_b.schedule(runnable);
17204 }
17205 },
17206 );
17207 executor.schedule(runnable_b);
17212 wait_until("B parked after its first poll", || {
17213 metrics.runnable_polls.load(Ordering::SeqCst) == 1
17214 && metrics.active_runnables.load(Ordering::SeqCst) == 0
17215 && metrics.queued_runnables.load(Ordering::SeqCst) == 0
17216 });
17217
17218 let waker_thread = std::thread::spawn(move || drop(runnable_a));
17222 wake_in_flight.wait();
17223
17224 backend.begin_shutdown();
17227 wake_release.wait();
17228 backend.wait_until_idle();
17229
17230 waker_thread
17231 .join()
17232 .expect("the waker thread must not panic");
17233 assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
17234 assert!(
17235 futures::executor::block_on(task_b.fallible()).is_none(),
17236 "B's late wake must still cancel B"
17237 );
17238 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17239 drop(shared);
17240 done_tx.send(()).unwrap();
17241 });
17242 done_rx
17243 .recv_timeout(Duration::from_secs(5))
17244 .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
17245 }
17246
17247 #[cfg(napi_runtime_os_threads)]
17248 #[test]
17249 fn current_thread_recovery_reservation_blocks_new_host_publication() {
17250 use std::sync::mpsc;
17251
17252 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
17253 let metrics = Arc::new(RuntimeMetrics::default());
17254 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17255 Arc::clone(&metrics),
17256 count_current_thread_interleaved_recovery_host_dispatch,
17257 ));
17258 let backend =
17259 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17260
17261 let registration = backend
17262 .work
17263 .try_register_async()
17264 .expect("the backend must accept work");
17265 let task = spawn_registered(
17266 &backend,
17267 Arc::clone(&metrics),
17268 std::future::pending::<()>(),
17269 registration,
17270 );
17271 let failed_dispatch = current_thread_dispatch(&executor);
17272 assert_eq!(
17273 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17274 1
17275 );
17276
17277 let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
17278 let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
17279 let cancelling_executor = Arc::clone(&executor);
17280 let cancellation = std::thread::spawn(move || {
17281 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
17282 *slot.borrow_mut() = Some(Box::new(move || {
17283 recovery_cleared_tx.send(()).unwrap();
17284 release_recovery_rx.recv().unwrap();
17285 }));
17286 });
17287 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
17288 });
17289 recovery_cleared_rx
17290 .recv_timeout(Duration::from_secs(2))
17291 .expect("failed dispatch cancellation must pause after consuming the old capability");
17292
17293 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
17294 let (publication_done_tx, publication_done_rx) = mpsc::channel();
17295 let publishing_executor = Arc::clone(&executor);
17296 let publication = std::thread::spawn(move || {
17297 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
17298 *slot.borrow_mut() = Some(Box::new(move || {
17299 publication_blocked_tx.send(()).unwrap();
17300 }));
17301 });
17302 publishing_executor.request_drain_if_queued();
17303 publication_done_tx.send(()).unwrap();
17304 });
17305
17306 if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
17307 release_recovery_tx.send(()).unwrap();
17308 cancellation.join().unwrap();
17309 publication.join().unwrap();
17310 panic!("a newly registered host must wait for recovery reservation: {error}");
17311 }
17312 assert!(
17313 matches!(
17314 publication_done_rx.try_recv(),
17315 Err(mpsc::TryRecvError::Empty)
17316 ),
17317 "the new host must not publish in the consumed-to-recovery gap"
17318 );
17319 release_recovery_tx.send(()).unwrap();
17320
17321 cancellation.join().unwrap();
17322 publication_done_rx
17323 .recv_timeout(Duration::from_secs(2))
17324 .unwrap();
17325 publication.join().unwrap();
17326
17327 let replacement_dispatch = current_thread_dispatch(&executor);
17328 assert_ne!(replacement_dispatch, failed_dispatch);
17329 assert_eq!(
17330 executor.recovery_dispatch.load(Ordering::Acquire),
17331 replacement_dispatch
17332 );
17333 assert_eq!(
17334 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17335 3,
17336 "the original publication and two host attempts for its one replacement must run"
17337 );
17338
17339 executor.cancel_host_dispatch_for_test(replacement_dispatch);
17340
17341 assert_eq!(
17342 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17343 3,
17344 "failure of the tagged replacement must cancel work without a second replacement"
17345 );
17346 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17347 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17348 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17349 assert_eq!(
17350 futures::executor::block_on(task).unwrap_err().to_string(),
17351 "async runtime stopped before the task completed"
17352 );
17353
17354 let later_registration = backend
17355 .work
17356 .try_register_async()
17357 .expect("terminal cancellation must reopen acceptance");
17358 let later_task = spawn_registered(
17359 &backend,
17360 Arc::clone(&metrics),
17361 async { 2usize },
17362 later_registration,
17363 );
17364 assert_eq!(
17365 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17366 4
17367 );
17368 executor.drive_host_turn();
17369 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
17370 }
17371
17372 #[cfg(napi_runtime_os_threads)]
17373 #[test]
17374 fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
17375 use std::sync::mpsc;
17376
17377 struct BlockingDrop {
17378 entered: mpsc::Sender<()>,
17379 release: mpsc::Receiver<()>,
17380 }
17381
17382 impl Drop for BlockingDrop {
17383 fn drop(&mut self) {
17384 self.entered.send(()).unwrap();
17385 self.release.recv().unwrap();
17386 }
17387 }
17388
17389 reset_current_thread_recovery_host_dispatches();
17390 let metrics = Arc::new(RuntimeMetrics::default());
17391 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17392 Arc::clone(&metrics),
17393 count_current_thread_recovery_host_dispatch,
17394 ));
17395 let backend =
17396 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17397
17398 let holder_registration = backend
17399 .work
17400 .try_register_work()
17401 .expect("the backend must accept the blocking holder");
17402 let (holder_started_tx, holder_started_rx) = mpsc::channel();
17403 let (release_holder_tx, release_holder_rx) = mpsc::channel();
17404 let holder_executor = Arc::clone(&executor);
17405 let holder = std::thread::spawn(move || {
17406 let handle = holder_executor.schedule_blocking(
17407 move || {
17408 holder_started_tx.send(()).unwrap();
17409 release_holder_rx.recv().unwrap();
17410 },
17411 holder_registration,
17412 );
17413 futures::executor::block_on(handle).unwrap();
17414 });
17415 holder_started_rx
17416 .recv_timeout(Duration::from_secs(2))
17417 .expect("the first blocking call must hold the CurrentThread lane");
17418
17419 let cancelled_registration = backend
17420 .work
17421 .try_register_work()
17422 .expect("the backend must accept queued blocking work");
17423 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
17424 let (release_drop_tx, release_drop_rx) = mpsc::channel();
17425 let blocking_drop = BlockingDrop {
17426 entered: drop_entered_tx,
17427 release: release_drop_rx,
17428 };
17429 let cancelled = executor.schedule_blocking(
17430 move || {
17431 let _blocking_drop = blocking_drop;
17432 },
17433 cancelled_registration,
17434 );
17435
17436 let async_registration = backend
17437 .work
17438 .try_register_async()
17439 .expect("the backend must accept queued async work");
17440 let pending = spawn_registered(
17441 &backend,
17442 Arc::clone(&metrics),
17443 std::future::pending::<()>(),
17444 async_registration,
17445 );
17446 let failed_dispatch = current_thread_dispatch(&executor);
17447 executor.cancel_host_dispatch_for_test(failed_dispatch);
17448 let replacement_dispatch = current_thread_dispatch(&executor);
17449
17450 let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
17451 let cancelling_executor = Arc::clone(&executor);
17452 let cancellation = std::thread::spawn(move || {
17453 cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
17454 cancel_done_tx.send(()).unwrap();
17455 });
17456 drop_entered_rx
17457 .recv_timeout(Duration::from_secs(2))
17458 .expect("terminal dispatch cancellation must destroy the queued blocking capture");
17459 let active_during_drop = backend.work.state.lock().unwrap().active;
17460 let cancellation_blocked = cancel_done_rx.try_recv().is_err();
17461
17462 release_drop_tx.send(()).unwrap();
17463 cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17464 cancellation.join().unwrap();
17465
17466 assert_eq!(
17467 active_during_drop, 2,
17468 "the blocking capture's work registration must remain active until its destructor returns"
17469 );
17470 assert!(
17471 cancellation_blocked,
17472 "terminal cancellation must wait for capture destruction"
17473 );
17474 assert!(futures::executor::block_on(cancelled).is_err());
17475 assert!(futures::executor::block_on(pending).is_err());
17476 assert_eq!(backend.work.state.lock().unwrap().active, 1);
17477
17478 release_holder_tx.send(()).unwrap();
17479 holder.join().unwrap();
17480 assert_eq!(backend.work.state.lock().unwrap().active, 0);
17481 }
17482
17483 #[test]
17484 fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
17485 reset_current_thread_one_shot_host_dispatches();
17486 let metrics = Arc::new(RuntimeMetrics::default());
17487 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17488 Arc::clone(&metrics),
17489 accept_only_first_current_thread_host_dispatch,
17490 ));
17491
17492 let scheduler = Arc::clone(&executor);
17493 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
17494 scheduler.schedule(runnable)
17495 });
17496 executor.schedule(runnable);
17497 let failed_dispatch = current_thread_dispatch(&executor);
17498
17499 executor.cancel_host_dispatch_for_test(failed_dispatch);
17500
17501 assert_eq!(
17502 current_thread_one_shot_host_dispatches(),
17503 2,
17504 "the original and its one bounded replacement must be attempted"
17505 );
17506 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17507 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17508 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17509 assert!(
17510 futures::executor::block_on(task.fallible()).is_none(),
17511 "an unavailable replacement host must settle the original task as cancelled"
17512 );
17513 }
17514
17515 enum TestCurrentThreadTaskDriverBehavior {
17516 Healthy,
17517 PanicInLivenessAndSweep,
17518 PanicInDispatchAndSweep,
17519 }
17520
17521 struct TestCurrentThreadTaskDriver {
17522 behavior: TestCurrentThreadTaskDriverBehavior,
17523 dispatched: AtomicBool,
17524 }
17525
17526 #[derive(Default)]
17527 struct ManualCurrentThreadTaskDriver {
17528 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17529 }
17530
17531 struct FailDuringAuthoritativePanicPublication;
17532 struct FailDuringLateRegistrationPublication;
17533 struct FailDuringNestedUnwindPublication;
17534 struct FailDuringReplacementRepublishPublication;
17535 struct FirstHostileCurrentThreadPanic;
17536 struct LaterHostileCurrentThreadPanic;
17537
17538 impl Drop for FirstHostileCurrentThreadPanic {
17539 fn drop(&mut self) {
17540 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17541 panic!("the first retained CurrentThread panic payload was dropped");
17542 }
17543 }
17544
17545 impl Drop for LaterHostileCurrentThreadPanic {
17546 fn drop(&mut self) {
17547 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17548 panic!("a later CurrentThread panic payload escaped containment");
17549 }
17550 }
17551
17552 impl ManualCurrentThreadTaskDriver {
17553 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17554 self.deliveries.lock().unwrap().clone()
17555 }
17556 }
17557
17558 impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
17559 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17560 self.deliveries.lock().unwrap().push(delivery);
17561 true
17562 }
17563 }
17564
17565 struct SynchronouslyFailingCurrentThreadTaskDriver {
17570 registry: &'static CurrentThreadTaskDriverRegistry,
17571 fail: AtomicBool,
17572 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17573 }
17574
17575 impl SynchronouslyFailingCurrentThreadTaskDriver {
17576 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17577 Self {
17578 registry,
17579 fail: AtomicBool::new(false),
17580 deliveries: Mutex::default(),
17581 }
17582 }
17583
17584 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17585 self.deliveries.lock().unwrap().clone()
17586 }
17587 }
17588
17589 impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
17590 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17591 self.deliveries.lock().unwrap().push(delivery);
17592 if self.fail.load(Ordering::SeqCst) {
17593 let registry = self.registry;
17594 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17595 .join()
17596 .expect("the failing helper must not panic");
17597 assert!(
17598 completion.failed_dispatches.is_empty(),
17599 "the failed delivery must remain provisional until its publication retires"
17600 );
17601 }
17602 true
17603 }
17604 }
17605
17606 type DeliveryHook = Box<dyn FnOnce() + Send>;
17614
17615 struct BoundedFailureCurrentThreadTaskDriver {
17616 registry: &'static CurrentThreadTaskDriverRegistry,
17617 fail_budget: AtomicUsize,
17618 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17619 during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
17620 }
17621
17622 impl BoundedFailureCurrentThreadTaskDriver {
17623 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17624 Self {
17625 registry,
17626 fail_budget: AtomicUsize::new(0),
17627 deliveries: Mutex::default(),
17628 during_delivery: Mutex::default(),
17629 }
17630 }
17631
17632 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17633 self.deliveries.lock().unwrap().clone()
17634 }
17635
17636 fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
17641 *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
17642 }
17643 }
17644
17645 impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
17646 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17647 let index = {
17648 let mut deliveries = self.deliveries.lock().unwrap();
17649 deliveries.push(delivery);
17650 deliveries.len() - 1
17651 };
17652 let hook = {
17653 let mut slot = self.during_delivery.lock().unwrap();
17654 if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
17655 slot.take().map(|(_, hook)| hook)
17656 } else {
17657 None
17658 }
17659 };
17660 if let Some(hook) = hook {
17661 hook();
17662 }
17663 let budget = self.fail_budget.load(Ordering::SeqCst);
17664 if budget != 0 {
17665 self.fail_budget.store(budget - 1, Ordering::SeqCst);
17666 let registry = self.registry;
17667 let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17668 .join()
17669 .expect("the failing helper must not panic");
17670 assert!(
17671 completion.failed_dispatches.is_empty(),
17672 "the failed delivery must remain provisional until its publication retires"
17673 );
17674 }
17675 true
17676 }
17677 }
17678
17679 struct SustainedProducerGate {
17686 remaining: usize,
17687 queue_one: std::sync::mpsc::Sender<()>,
17688 queued: std::sync::mpsc::Receiver<()>,
17689 }
17690
17691 struct SustainedProducerCurrentThreadTaskDriver {
17692 registry: &'static CurrentThreadTaskDriverRegistry,
17693 fail_budget: AtomicUsize,
17696 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17697 producer: Mutex<Option<SustainedProducerGate>>,
17698 }
17699
17700 impl SustainedProducerCurrentThreadTaskDriver {
17701 fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17702 Self {
17703 registry,
17704 fail_budget: AtomicUsize::new(0),
17705 deliveries: Mutex::default(),
17706 producer: Mutex::default(),
17707 }
17708 }
17709
17710 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17711 self.deliveries.lock().unwrap().clone()
17712 }
17713
17714 fn arm_producer(
17715 &self,
17716 remaining: usize,
17717 queue_one: std::sync::mpsc::Sender<()>,
17718 queued: std::sync::mpsc::Receiver<()>,
17719 ) {
17720 *self.producer.lock().unwrap() = Some(SustainedProducerGate {
17721 remaining,
17722 queue_one,
17723 queued,
17724 });
17725 }
17726
17727 fn disarm_producer(&self) -> usize {
17730 self
17731 .producer
17732 .lock()
17733 .unwrap()
17734 .take()
17735 .map_or(0, |gate| gate.remaining)
17736 }
17737 }
17738
17739 impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
17740 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17741 self.deliveries.lock().unwrap().push(delivery);
17742 {
17743 let mut slot = self.producer.lock().unwrap();
17744 if let Some(gate) = slot.as_mut()
17745 && gate.remaining != 0
17746 {
17747 gate.remaining -= 1;
17748 gate.queue_one.send(()).expect("the producer must be live");
17749 gate
17750 .queued
17751 .recv_timeout(std::time::Duration::from_secs(5))
17752 .expect("the producer must queue one runnable per publication");
17753 }
17754 }
17755 let budget = self.fail_budget.load(Ordering::SeqCst);
17756 if budget != 0 {
17757 if budget != usize::MAX {
17758 self.fail_budget.store(budget - 1, Ordering::SeqCst);
17759 }
17760 let completion = self.registry.finish_delivery(delivery, true);
17761 assert!(
17762 completion.failed_dispatches.is_empty(),
17763 "the failed delivery must remain provisional until its publication retires"
17764 );
17765 }
17766 true
17767 }
17768 }
17769
17770 impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
17771 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17772 let completion =
17773 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
17774 assert!(
17775 completion.failed_dispatches.is_empty(),
17776 "the failed delivery must remain provisional until its publication retires"
17777 );
17778 true
17779 }
17780 }
17781
17782 impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
17783 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17784 let completion =
17785 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
17786 assert!(
17787 completion.failed_dispatches.is_empty(),
17788 "the failed delivery must remain provisional until its publication retires"
17789 );
17790 true
17791 }
17792 }
17793
17794 impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
17795 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17796 let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
17797 assert!(
17798 completion.failed_dispatches.is_empty(),
17799 "the failed delivery must remain provisional until its publication retires"
17800 );
17801 true
17802 }
17803 }
17804
17805 impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
17806 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17807 let completion =
17808 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
17809 assert!(
17810 completion.failed_dispatches.is_empty(),
17811 "the failed delivery must remain provisional until its publication retires"
17812 );
17813 true
17814 }
17815 }
17816
17817 struct ReentrantCurrentThreadTaskDriver {
17818 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17819 registration: Mutex<Option<CurrentThreadTaskDriverId>>,
17820 }
17821
17822 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
17823 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17824 true
17825 }
17826
17827 fn is_live(&self) -> bool {
17828 let registration = *self.registration.lock().unwrap();
17829 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
17830 registry.unregister(id);
17831 }
17832 false
17833 }
17834 }
17835
17836 struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
17837 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17838 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17839 }
17840
17841 impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
17842 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17843 true
17844 }
17845
17846 fn is_live(&self) -> bool {
17847 if let Some(registry) = self.registry.upgrade() {
17848 for registration in self.registrations.lock().unwrap().iter().copied() {
17849 let completion = registry.unregister(registration);
17850 assert!(completion.failed_dispatches.is_empty());
17851 assert!(!completion.redispatch);
17852 }
17853 }
17854 false
17855 }
17856 }
17857
17858 struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17859 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17860 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17861 drops: Arc<AtomicUsize>,
17862 }
17863
17864 impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17865 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17866 if let Some(registry) = self.registry.upgrade() {
17867 for registration in self.registrations.lock().unwrap().iter().copied() {
17868 let completion = registry.unregister(registration);
17869 assert!(completion.failed_dispatches.is_empty());
17870 assert!(!completion.redispatch);
17871 }
17872 }
17873 true
17874 }
17875 }
17876
17877 impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17878 fn drop(&mut self) {
17879 self.drops.fetch_add(1, Ordering::SeqCst);
17880 panic!("intentional reentrant CurrentThread task-driver destructor panic");
17881 }
17882 }
17883
17884 struct PanickingDropCurrentThreadTaskDriver {
17885 drops: Arc<AtomicUsize>,
17886 }
17887
17888 impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
17889 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17890 true
17891 }
17892 }
17893
17894 impl Drop for PanickingDropCurrentThreadTaskDriver {
17895 fn drop(&mut self) {
17896 self.drops.fetch_add(1, Ordering::SeqCst);
17897 panic!("intentional CurrentThread task-driver destructor panic");
17898 }
17899 }
17900
17901 struct ReentrantCurrentThreadTaskDriverDrop {
17902 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17903 fallback: CurrentThreadTaskDriverId,
17904 }
17905
17906 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
17907 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17908 true
17909 }
17910 }
17911
17912 impl Drop for ReentrantCurrentThreadTaskDriverDrop {
17913 fn drop(&mut self) {
17914 if let Some(registry) = self.registry.upgrade() {
17915 registry.unregister(self.fallback);
17916 }
17917 }
17918 }
17919
17920 impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
17921 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17922 assert!(
17923 !matches!(
17924 self.behavior,
17925 TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
17926 ),
17927 "intentional task-driver dispatch panic"
17928 );
17929 self.dispatched.store(true, Ordering::SeqCst);
17930 true
17931 }
17932
17933 fn is_live(&self) -> bool {
17934 assert!(
17935 !matches!(
17936 self.behavior,
17937 TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
17938 ),
17939 "intentional task-driver liveness panic"
17940 );
17941 true
17942 }
17943
17944 fn on_swept(&self) {
17945 assert!(
17946 matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
17947 "intentional task-driver sweep panic"
17948 );
17949 }
17950 }
17951
17952 #[cfg(panic = "unwind")]
17953 #[test]
17954 fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
17955 let registry = CurrentThreadTaskDriverRegistry::default();
17956 let fallback = Arc::new(TestCurrentThreadTaskDriver {
17957 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
17958 dispatched: AtomicBool::new(false),
17959 });
17960 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
17961 registry.register(Arc::new(TestCurrentThreadTaskDriver {
17962 behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
17963 dispatched: AtomicBool::new(false),
17964 }));
17965 registry.register(Arc::new(TestCurrentThreadTaskDriver {
17966 behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
17967 dispatched: AtomicBool::new(false),
17968 }));
17969
17970 assert_eq!(
17971 registry.dispatch(37),
17972 CurrentThreadHostDispatchResult::Accepted,
17973 "a panicking newest host must fall back to a live host"
17974 );
17975 assert!(fallback.dispatched.load(Ordering::SeqCst));
17976 }
17977
17978 #[cfg(panic = "unwind")]
17979 #[test]
17980 fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
17981 let registry = CurrentThreadTaskDriverRegistry::default();
17982 let first = Arc::new(ManualCurrentThreadTaskDriver::default());
17983 let second = Arc::new(ManualCurrentThreadTaskDriver::default());
17984 registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
17985 registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
17986 registry
17987 .next_delivery
17988 .store(u64::MAX - 1, Ordering::Relaxed);
17989
17990 let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
17991 assert!(
17992 publication.is_err(),
17993 "delivery-id exhaustion on the second host must unwind the publication"
17994 );
17995 {
17996 let state = registry
17997 .state
17998 .lock()
17999 .unwrap_or_else(std::sync::PoisonError::into_inner);
18000 assert!(
18001 !state.dispatches.contains_key(&38),
18002 "unwind cleanup must release the exact registry publication barrier"
18003 );
18004 assert!(
18005 state
18006 .entries
18007 .values()
18008 .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
18009 "failed transactional reservation must leave every physical host slot clean"
18010 );
18011 }
18012 assert!(first.deliveries().is_empty());
18013 assert!(second.deliveries().is_empty());
18014
18015 registry.next_delivery.store(0, Ordering::Relaxed);
18016 assert_eq!(
18017 registry.dispatch(38),
18018 CurrentThreadHostDispatchResult::Accepted
18019 );
18020 let first_delivery = first.deliveries()[0];
18021 let second_delivery = second.deliveries()[0];
18022 assert_eq!(
18023 registry.claim_delivery(first_delivery.capability()),
18024 Some(38)
18025 );
18026 assert_eq!(
18027 registry.claim_delivery(second_delivery.capability()),
18028 Some(38)
18029 );
18030 registry.mark_serviced(38);
18031 assert!(
18032 registry
18033 .finish_delivery(first_delivery, false)
18034 .failed_dispatches
18035 .is_empty()
18036 );
18037 assert!(
18038 registry
18039 .finish_delivery(second_delivery, false)
18040 .failed_dispatches
18041 .is_empty()
18042 );
18043
18044 assert_eq!(
18045 registry.dispatch(39),
18046 CurrentThreadHostDispatchResult::Accepted
18047 );
18048 let first_later = first.deliveries()[1];
18049 let second_later = second.deliveries()[1];
18050 assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
18051 assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
18052 registry.mark_serviced(39);
18053 assert!(
18054 registry
18055 .finish_delivery(first_later, false)
18056 .failed_dispatches
18057 .is_empty()
18058 );
18059 assert!(
18060 registry
18061 .finish_delivery(second_later, false)
18062 .failed_dispatches
18063 .is_empty()
18064 );
18065 assert!(
18066 registry
18067 .state
18068 .lock()
18069 .unwrap_or_else(std::sync::PoisonError::into_inner)
18070 .dispatches
18071 .is_empty()
18072 );
18073 let state = registry
18074 .state
18075 .lock()
18076 .unwrap_or_else(std::sync::PoisonError::into_inner);
18077 assert!(
18078 state
18079 .entries
18080 .values()
18081 .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
18082 );
18083 }
18084
18085 #[test]
18086 fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
18087 let registry = CurrentThreadTaskDriverRegistry::default();
18088 let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
18089 let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
18090 let dispatch = 40;
18091
18092 assert_eq!(
18093 registry.dispatch(dispatch),
18094 CurrentThreadHostDispatchResult::Accepted
18095 );
18096 let failed_delivery = failed.deliveries()[0];
18097 let failure = current_thread_dispatch_failure(dispatch, 0);
18098 assert_eq!(
18099 registry
18100 .finish_delivery(failed_delivery, true)
18101 .failed_dispatches,
18102 vec![failure]
18103 );
18104 registry.unregister(failed_id);
18105
18106 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18107 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18108 assert!(
18109 registry.rearm_failed_publication(failure),
18110 "a closed unreferenced dispatch must reopen for its armed publication owner"
18111 );
18112 assert_eq!(
18113 registry.dispatch(dispatch),
18114 CurrentThreadHostDispatchResult::Accepted
18115 );
18116 let replacement_delivery = replacement.deliveries()[0];
18117 assert_eq!(replacement_delivery.dispatch, dispatch);
18118 assert_eq!(
18119 registry.claim_delivery(replacement_delivery.capability()),
18120 Some(dispatch)
18121 );
18122 registry.mark_serviced(dispatch);
18123 assert!(
18124 registry
18125 .finish_delivery(replacement_delivery, false)
18126 .failed_dispatches
18127 .is_empty()
18128 );
18129 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18130 }
18131
18132 #[test]
18133 fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
18134 struct RacingTaskDriver {
18135 executor: Arc<CurrentThreadExecutor>,
18136 drives: bool,
18137 dispatches: Mutex<Vec<u64>>,
18138 }
18139
18140 impl CurrentThreadTaskDriver for RacingTaskDriver {
18141 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18142 self.dispatches.lock().unwrap().push(delivery.dispatch);
18143 if self.drives {
18144 self
18145 .executor
18146 .drive_host_turn_for_dispatch(delivery.dispatch);
18147 }
18148 true
18149 }
18150 }
18151
18152 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
18153 Arc::new(RuntimeMetrics::default()),
18154 accept_current_thread_host_dispatch,
18155 ));
18156 let polls = Arc::new(AtomicUsize::new(0));
18157 let task_polls = Arc::clone(&polls);
18158 let scheduler = Arc::clone(&executor);
18159 let (runnable, task) = async_task::spawn(
18160 async move {
18161 task_polls.fetch_add(1, Ordering::SeqCst);
18162 },
18163 move |runnable| scheduler.schedule(runnable),
18164 );
18165 executor.schedule(runnable);
18166 let dispatch = current_thread_dispatch(&executor);
18167
18168 let registry = CurrentThreadTaskDriverRegistry::default();
18169 let responsive = Arc::new(RacingTaskDriver {
18170 executor: Arc::clone(&executor),
18171 drives: true,
18172 dispatches: Mutex::new(Vec::new()),
18173 });
18174 let starved = Arc::new(RacingTaskDriver {
18175 executor: Arc::clone(&executor),
18176 drives: false,
18177 dispatches: Mutex::new(Vec::new()),
18178 });
18179 registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
18180 registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
18182
18183 assert_eq!(
18184 registry.dispatch(dispatch),
18185 CurrentThreadHostDispatchResult::Accepted
18186 );
18187 assert_eq!(
18188 responsive.dispatches.lock().unwrap().as_slice(),
18189 &[dispatch]
18190 );
18191 assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
18192 assert_eq!(
18193 polls.load(Ordering::SeqCst),
18194 1,
18195 "the responsive older host must win even when the newest host is starved"
18196 );
18197
18198 executor.drive_host_turn_for_dispatch(dispatch);
18201 assert_eq!(polls.load(Ordering::SeqCst), 1);
18202 futures::executor::block_on(task);
18203 }
18204
18205 #[test]
18206 fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
18207 const HOSTS: usize = 1_024;
18208
18209 let registry = CurrentThreadTaskDriverRegistry::default();
18210 let mut ids = Vec::with_capacity(HOSTS);
18211 let mut drivers = Vec::with_capacity(HOSTS);
18212 for _ in 0..HOSTS {
18213 let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
18214 ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
18215 drivers.push(driver);
18216 }
18217
18218 let dispatch = u64::MAX - 300;
18219 assert_eq!(
18220 registry.dispatch(dispatch),
18221 CurrentThreadHostDispatchResult::Accepted
18222 );
18223 let deliveries = drivers
18224 .iter()
18225 .map(|driver| driver.deliveries()[0])
18226 .collect::<Vec<_>>();
18227 {
18228 let state = registry
18229 .state
18230 .lock()
18231 .unwrap_or_else(std::sync::PoisonError::into_inner);
18232 assert_eq!(state.delivery_drivers.len(), HOSTS);
18233 assert_eq!(
18234 state
18235 .dispatches
18236 .get(&dispatch)
18237 .map(|state| state.references),
18238 Some(HOSTS)
18239 );
18240 assert_eq!(
18241 state
18242 .ordered_snapshot()
18243 .iter()
18244 .map(|(id, _)| *id)
18245 .collect::<Vec<_>>(),
18246 ids,
18247 "keyed driver lookup must retain registration order"
18248 );
18249 }
18250
18251 for delivery in &deliveries {
18252 assert_eq!(
18253 registry.claim_delivery(delivery.capability()),
18254 Some(dispatch)
18255 );
18256 }
18257 registry.mark_serviced(dispatch);
18258 for delivery in deliveries {
18259 assert!(
18260 registry
18261 .finish_delivery(delivery, false)
18262 .failed_dispatches
18263 .is_empty()
18264 );
18265 }
18266 {
18267 let state = registry
18268 .state
18269 .lock()
18270 .unwrap_or_else(std::sync::PoisonError::into_inner);
18271 assert!(state.delivery_drivers.is_empty());
18272 assert!(state.dispatches.is_empty());
18273 }
18274
18275 let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
18276 for id in &removed {
18277 let completion = registry.unregister(*id);
18278 assert!(completion.failed_dispatches.is_empty());
18279 assert!(!completion.redispatch);
18280 }
18281 let expected = ids
18282 .iter()
18283 .copied()
18284 .filter(|id| !removed.contains(id))
18285 .collect::<Vec<_>>();
18286 let live = registry.live().live;
18287 assert_eq!(
18288 live
18289 .as_slice()
18290 .iter()
18291 .map(|(id, _)| *id)
18292 .collect::<Vec<_>>(),
18293 expected,
18294 "O(1) removals must preserve the relative order of surviving host registrations"
18295 );
18296 drop(live);
18297
18298 for id in expected {
18299 registry.unregister(id);
18300 }
18301 let state = registry
18302 .state
18303 .lock()
18304 .unwrap_or_else(std::sync::PoisonError::into_inner);
18305 assert!(state.entries.is_empty());
18306 assert!(state.oldest.is_none());
18307 assert!(state.newest.is_none());
18308 }
18309
18310 #[test]
18311 fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
18312 let registry = CurrentThreadTaskDriverRegistry::default();
18313 let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
18314 let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
18315 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
18316 registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
18317 registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
18318 registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
18319
18320 let dispatch = 41;
18321 assert_eq!(
18322 registry.dispatch(dispatch),
18323 CurrentThreadHostDispatchResult::Accepted
18324 );
18325 let failing_delivery = failing.deliveries()[0];
18326 let delayed_delivery = delayed.deliveries()[0];
18327 let healthy_delivery = healthy.deliveries()[0];
18328 assert_eq!(failing_delivery.dispatch, dispatch);
18329 assert_eq!(delayed_delivery.dispatch, dispatch);
18330 assert_eq!(healthy_delivery.dispatch, dispatch);
18331
18332 assert!(
18333 registry
18334 .finish_delivery(failing_delivery, true)
18335 .failed_dispatches
18336 .is_empty(),
18337 "one host failure must not recover while other accepted attempts remain"
18338 );
18339 assert_eq!(
18340 registry.claim_delivery(healthy_delivery.capability()),
18341 Some(dispatch)
18342 );
18343 registry.mark_serviced(dispatch);
18344 assert!(
18345 registry
18346 .finish_delivery(healthy_delivery, false)
18347 .failed_dispatches
18348 .is_empty()
18349 );
18350 assert_eq!(
18351 registry.claim_delivery(delayed_delivery.capability()),
18352 None,
18353 "a delayed losing delivery must become stale after another host services the dispatch"
18354 );
18355 assert!(
18356 registry
18357 .finish_delivery(delayed_delivery, true)
18358 .failed_dispatches
18359 .is_empty(),
18360 "a stale losing failure must not recover or cancel a serviced dispatch"
18361 );
18362 assert!(
18363 registry.state.lock().unwrap().dispatches.is_empty(),
18364 "all per-dispatch acknowledgement state must retire"
18365 );
18366 }
18367
18368 #[test]
18369 fn current_thread_unclaimed_callback_success_is_delivery_failure() {
18370 let registry = CurrentThreadTaskDriverRegistry::default();
18371 let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
18372 registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
18373
18374 let dispatch = 56;
18375 assert_eq!(
18376 registry.dispatch(dispatch),
18377 CurrentThreadHostDispatchResult::Accepted
18378 );
18379 let delivery = no_op.deliveries()[0];
18380 let completion = registry.finish_delivery(delivery, false);
18381 let failure = current_thread_dispatch_failure(dispatch, 0);
18382 assert_eq!(
18383 completion.failed_dispatches,
18384 vec![failure],
18385 "a callback return without an exact capability claim must enter recovery"
18386 );
18387 assert!(
18388 registry
18389 .state
18390 .lock()
18391 .unwrap()
18392 .dispatches
18393 .get(&dispatch)
18394 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18395 );
18396 registry.finish_cancellation(failure);
18397 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18398 }
18399
18400 #[test]
18401 fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
18402 struct FailDuringPublication {
18403 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18404 }
18405
18406 impl CurrentThreadTaskDriver for FailDuringPublication {
18407 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18408 let completion = self
18409 .registry
18410 .upgrade()
18411 .expect("the registry must outlive publication")
18412 .finish_delivery(delivery, true);
18413 assert!(
18414 completion.failed_dispatches.is_empty(),
18415 "failure must remain provisional until the broadcast closes"
18416 );
18417 true
18418 }
18419 }
18420
18421 struct RejectProvisional;
18422
18423 impl CurrentThreadTaskDriver for RejectProvisional {
18424 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18425 false
18426 }
18427 }
18428
18429 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18430 registry.register(Arc::new(RejectProvisional));
18431 registry.register(Arc::new(FailDuringPublication {
18434 registry: Arc::downgrade(®istry),
18435 }));
18436
18437 let failure = current_thread_dispatch_failure(57, 0);
18438 assert_eq!(
18439 registry.dispatch(57),
18440 CurrentThreadHostDispatchResult::Failed(failure),
18441 "the final provisional rejection must expose the earlier accepted failure"
18442 );
18443 assert!(
18444 registry
18445 .state
18446 .lock()
18447 .unwrap()
18448 .dispatches
18449 .get(&57)
18450 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18451 );
18452 assert_eq!(
18453 registry.dispatch(57),
18454 CurrentThreadHostDispatchResult::Failed(failure),
18455 "a racing replacement publication must preserve terminal failure until cancellation runs"
18456 );
18457 registry.finish_cancellation(failure);
18458 }
18459
18460 #[test]
18461 fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
18462 struct RejectProvisional;
18463
18464 impl CurrentThreadTaskDriver for RejectProvisional {
18465 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18466 false
18467 }
18468 }
18469
18470 let registry = CurrentThreadTaskDriverRegistry::default();
18471 registry.register(Arc::new(RejectProvisional));
18472 registry.register(Arc::new(RejectProvisional));
18473
18474 assert_eq!(
18475 registry.dispatch(58),
18476 CurrentThreadHostDispatchResult::Unavailable
18477 );
18478 assert!(
18479 registry.state.lock().unwrap().dispatches.is_empty(),
18480 "a broadcast with no accepted attempt must not enter failure recovery"
18481 );
18482 }
18483
18484 #[test]
18485 fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
18486 let registry = CurrentThreadTaskDriverRegistry::default();
18487 let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
18488 let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
18489
18490 assert_eq!(
18491 registry.dispatch(1),
18492 CurrentThreadHostDispatchResult::Accepted
18493 );
18494 let first = blocked.deliveries()[0];
18495 for dispatch in 2..=256 {
18496 assert_eq!(
18497 registry.dispatch(dispatch),
18498 CurrentThreadHostDispatchResult::Accepted
18499 );
18500 assert_eq!(
18501 blocked.deliveries().len(),
18502 1,
18503 "a blocked host must never receive a second queued or in-flight callback"
18504 );
18505 }
18506 {
18507 let state = registry.state.lock().unwrap();
18508 let entry = state
18509 .entries
18510 .get(&blocked_id)
18511 .expect("the blocked host must remain registered");
18512 assert_eq!(entry.pending, Some(256));
18513 assert_eq!(
18514 state.dispatches.len(),
18515 2,
18516 "only the in-flight and latest pending dispatches may retain registry state"
18517 );
18518 }
18519
18520 assert_eq!(registry.claim_delivery(first.capability()), Some(1));
18521 registry.mark_serviced(1);
18522 let completion = registry.finish_delivery(first, false);
18523 assert!(completion.failed_dispatches.is_empty());
18524 assert!(completion.redispatch);
18525 assert_eq!(
18526 registry.dispatch(256),
18527 CurrentThreadHostDispatchResult::Accepted
18528 );
18529 let deliveries = blocked.deliveries();
18530 assert_eq!(deliveries.len(), 2);
18531 assert_eq!(
18532 deliveries[1].dispatch, 256,
18533 "completion must schedule exactly the latest coalesced capability"
18534 );
18535 assert_eq!(registry.claim_delivery(first.capability()), None);
18536 assert_eq!(
18537 registry.claim_delivery(deliveries[1].capability()),
18538 Some(256)
18539 );
18540 registry.mark_serviced(256);
18541 assert!(
18542 registry
18543 .finish_delivery(deliveries[1], false)
18544 .failed_dispatches
18545 .is_empty()
18546 );
18547 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18548 }
18549
18550 #[test]
18551 fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
18552 struct RejectAfterFirst {
18553 calls: AtomicUsize,
18554 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
18555 }
18556
18557 impl CurrentThreadTaskDriver for RejectAfterFirst {
18558 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18559 self.deliveries.lock().unwrap().push(delivery);
18560 self.calls.fetch_add(1, Ordering::SeqCst) == 0
18561 }
18562 }
18563
18564 let registry = CurrentThreadTaskDriverRegistry::default();
18565 let host = Arc::new(RejectAfterFirst {
18566 calls: AtomicUsize::new(0),
18567 deliveries: Mutex::new(Vec::new()),
18568 });
18569 registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18570 assert_eq!(
18571 registry.dispatch(11),
18572 CurrentThreadHostDispatchResult::Accepted
18573 );
18574 let first = host.deliveries.lock().unwrap()[0];
18575 assert_eq!(
18576 registry.dispatch(12),
18577 CurrentThreadHostDispatchResult::Accepted
18578 );
18579
18580 assert_eq!(registry.claim_delivery(first.capability()), Some(11));
18581 registry.mark_serviced(11);
18582 let completion = registry.finish_delivery(first, false);
18583 assert!(completion.redispatch);
18584 let failure = current_thread_dispatch_failure(12, 0);
18585 assert_eq!(
18586 registry.dispatch(12),
18587 CurrentThreadHostDispatchResult::Failed(failure),
18588 "an accepted coalesced capability must report failure if its eventual host queue rejects it"
18589 );
18590 assert!(
18591 registry
18592 .state
18593 .lock()
18594 .unwrap()
18595 .dispatches
18596 .get(&12)
18597 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18598 );
18599 registry.finish_cancellation(failure);
18600 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18601 }
18602
18603 #[test]
18604 fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
18605 let registry = CurrentThreadTaskDriverRegistry::default();
18606 let old = Arc::new(ManualCurrentThreadTaskDriver::default());
18607 let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
18608 assert_eq!(
18609 registry.dispatch(73),
18610 CurrentThreadHostDispatchResult::Accepted
18611 );
18612 let old_delivery = old.deliveries()[0];
18613
18614 let completion = registry.unregister(old_id);
18615 let failure = current_thread_dispatch_failure(73, 0);
18616 assert_eq!(
18617 completion.failed_dispatches,
18618 vec![failure],
18619 "unregistering the final accepting host must retire its exact dispatch"
18620 );
18621 registry.finish_cancellation(failure);
18622 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18623 let replacement_id =
18624 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18625 assert_ne!(replacement_id, old_id);
18626 assert_eq!(
18627 registry.dispatch(74),
18628 CurrentThreadHostDispatchResult::Accepted
18629 );
18630 let replacement_delivery = replacement.deliveries()[0];
18631 assert_ne!(replacement_delivery.capability(), old_delivery.capability());
18632
18633 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18634 assert!(
18635 registry
18636 .finish_delivery(old_delivery, true)
18637 .failed_dispatches
18638 .is_empty()
18639 );
18640 assert!(
18641 registry
18642 .fail_delivery(old_delivery.capability())
18643 .failed_dispatches
18644 .is_empty()
18645 );
18646 assert_eq!(
18647 registry.claim_delivery(replacement_delivery.capability()),
18648 Some(74)
18649 );
18650 registry.mark_serviced(74);
18651 assert!(
18652 registry
18653 .finish_delivery(replacement_delivery, false)
18654 .failed_dispatches
18655 .is_empty()
18656 );
18657 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18658 }
18659
18660 #[test]
18661 fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
18662 struct QueuePendingThenReject {
18663 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18664 }
18665
18666 impl CurrentThreadTaskDriver for QueuePendingThenReject {
18667 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18668 assert_eq!(delivery.dispatch, 81);
18669 let completion = self
18670 .registry
18671 .upgrade()
18672 .expect("the registry must outlive publication")
18673 .dispatch_with_completion(82);
18674 assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
18675 assert!(completion.failed_dispatches.is_empty());
18676 false
18677 }
18678 }
18679
18680 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18681 registry.register(Arc::new(QueuePendingThenReject {
18682 registry: Arc::downgrade(®istry),
18683 }));
18684
18685 let completion = registry.dispatch_with_completion(81);
18686 assert_eq!(
18687 completion.result,
18688 CurrentThreadHostDispatchResult::Unavailable
18689 );
18690 let failure = current_thread_dispatch_failure(82, 0);
18691 assert_eq!(
18692 completion.failed_dispatches,
18693 vec![failure],
18694 "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18695 );
18696 assert!(
18697 registry
18698 .state
18699 .lock()
18700 .unwrap()
18701 .dispatches
18702 .get(&82)
18703 .is_some_and(CurrentThreadDispatchState::is_cancelling)
18704 );
18705 registry.finish_cancellation(failure);
18706 assert!(registry.state.lock().unwrap().dispatches.is_empty());
18707 }
18708
18709 #[test]
18710 fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
18711 let registry = CurrentThreadTaskDriverRegistry::default();
18712 let host = Arc::new(ManualCurrentThreadTaskDriver::default());
18713 let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18714 assert_eq!(
18715 registry.dispatch(91),
18716 CurrentThreadHostDispatchResult::Accepted
18717 );
18718 let old_delivery = host.deliveries()[0];
18719 assert_eq!(
18720 registry.dispatch(92),
18721 CurrentThreadHostDispatchResult::Accepted
18722 );
18723
18724 registry.retire_for_shutdown();
18725 {
18726 let state = registry.state.lock().unwrap();
18727 assert!(state.dispatches.is_empty());
18728 let entry = state
18729 .entries
18730 .get(&host_id)
18731 .expect("the host must remain registered");
18732 assert_eq!(entry.pending, None);
18733 assert!(
18734 entry.delivery.is_some(),
18735 "the physical callback remains single-flight until it actually retires"
18736 );
18737 }
18738 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18739
18740 assert_eq!(
18741 registry.dispatch(93),
18742 CurrentThreadHostDispatchResult::Accepted
18743 );
18744 assert_eq!(host.deliveries().len(), 1);
18745 let completion = registry.finish_delivery(old_delivery, true);
18746 assert!(
18747 completion.failed_dispatches.is_empty(),
18748 "a retired-generation failure must not cancel restarted work"
18749 );
18750 assert!(completion.redispatch);
18751 assert_eq!(
18752 registry.dispatch(93),
18753 CurrentThreadHostDispatchResult::Accepted
18754 );
18755 let restarted_delivery = host.deliveries()[1];
18756 assert_eq!(restarted_delivery.dispatch, 93);
18757 assert_eq!(
18758 registry.claim_delivery(restarted_delivery.capability()),
18759 Some(93)
18760 );
18761 registry.mark_serviced(93);
18762 assert!(
18763 registry
18764 .finish_delivery(restarted_delivery, false)
18765 .failed_dispatches
18766 .is_empty()
18767 );
18768
18769 registry.unregister(host_id);
18770 let state = registry.state.lock().unwrap();
18771 assert!(state.entries.is_empty());
18772 assert!(state.dispatches.is_empty());
18773 }
18774
18775 #[cfg(napi_runtime_os_threads)]
18776 #[test]
18777 fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
18778 use std::{sync::mpsc, time::Duration};
18779
18780 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18781 let fallback = Arc::new(TestCurrentThreadTaskDriver {
18782 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18783 dispatched: AtomicBool::new(false),
18784 });
18785 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18786 let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
18787 registry: Arc::downgrade(®istry),
18788 registration: Mutex::new(None),
18789 });
18790 let reentrant_id =
18791 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18792 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
18793
18794 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
18795 let selection_registry = Arc::clone(®istry);
18796 let selection_thread = std::thread::spawn(move || {
18797 selection_tx
18798 .send(selection_registry.current().map(|(id, _)| id))
18799 .unwrap();
18800 });
18801 assert_eq!(
18802 selection_rx
18803 .recv_timeout(Duration::from_secs(10))
18804 .expect("is_live re-entry deadlocked"),
18805 Some(fallback_id)
18806 );
18807 selection_thread.join().unwrap();
18808
18809 let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
18810 Arc::new(ReentrantCurrentThreadTaskDriverDrop {
18811 registry: Arc::downgrade(®istry),
18812 fallback: fallback_id,
18813 });
18814 let drop_id = registry.register(Arc::clone(&drop_driver));
18815 drop(drop_driver);
18816
18817 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
18818 let drop_registry = Arc::clone(®istry);
18819 let drop_thread = std::thread::spawn(move || {
18820 drop_registry.unregister(drop_id);
18821 drop_tx.send(()).unwrap();
18822 });
18823 drop_rx
18824 .recv_timeout(Duration::from_secs(10))
18825 .expect("driver destructor re-entry deadlocked");
18826 drop_thread.join().unwrap();
18827 assert!(
18828 registry.current().is_none(),
18829 "the destructor must unregister the fallback"
18830 );
18831 }
18832
18833 #[cfg(not(target_family = "wasm"))]
18834 #[test]
18835 fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
18836 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
18837
18838 if std::env::var_os(CHILD_ENV).is_some() {
18839 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18840 let fallback = Arc::new(TestCurrentThreadTaskDriver {
18841 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18842 dispatched: AtomicBool::new(false),
18843 });
18844 let fallback_id =
18845 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18846 let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
18847 registry: Arc::downgrade(®istry),
18848 registrations: Mutex::new(Vec::new()),
18849 });
18850 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18851
18852 let first_drops = Arc::new(AtomicUsize::new(0));
18853 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18854 drops: Arc::clone(&first_drops),
18855 });
18856 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18857 drop(first);
18858
18859 let second_drops = Arc::new(AtomicUsize::new(0));
18860 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18861 drops: Arc::clone(&second_drops),
18862 });
18863 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18864 drop(second);
18865 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
18866
18867 assert_eq!(
18868 registry.current().map(|(id, _)| id),
18869 Some(fallback_id),
18870 "selection must retry after reentrant removals and preserve the healthy fallback"
18871 );
18872 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18873 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18874 let state = registry
18875 .state
18876 .lock()
18877 .unwrap_or_else(std::sync::PoisonError::into_inner);
18878 assert_eq!(state.entries.len(), 1);
18879 assert!(state.entries.contains_key(&fallback_id));
18880 assert!(state.dispatches.is_empty());
18881 return;
18882 }
18883
18884 let output = std::process::Command::new(std::env::current_exe().unwrap())
18885 .arg("--exact")
18886 .arg(
18887 "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
18888 )
18889 .arg("--nocapture")
18890 .env(CHILD_ENV, "1")
18891 .output()
18892 .expect("the hostile task-driver snapshot subprocess test must start");
18893 assert!(
18894 output.status.success(),
18895 "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18896 output.status.code(),
18897 String::from_utf8_lossy(&output.stdout),
18898 String::from_utf8_lossy(&output.stderr)
18899 );
18900 }
18901
18902 #[cfg(not(target_family = "wasm"))]
18903 #[test]
18904 fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
18905 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
18906
18907 if std::env::var_os(CHILD_ENV).is_some() {
18908 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18909
18910 let first_drops = Arc::new(AtomicUsize::new(0));
18911 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18912 drops: Arc::clone(&first_drops),
18913 });
18914 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18915 drop(first);
18916
18917 let second_drops = Arc::new(AtomicUsize::new(0));
18918 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18919 drops: Arc::clone(&second_drops),
18920 });
18921 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18922 drop(second);
18923
18924 let reentrant_drops = Arc::new(AtomicUsize::new(0));
18925 let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18926 registry: Arc::downgrade(®istry),
18927 registrations: Mutex::new(Vec::new()),
18928 drops: Arc::clone(&reentrant_drops),
18929 });
18930 let reentrant_id =
18931 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18932 *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
18933 drop(reentrant);
18934
18935 let dispatch = 59;
18936 let failure = current_thread_dispatch_failure(dispatch, 0);
18937 assert_eq!(
18938 registry.dispatch(dispatch),
18939 CurrentThreadHostDispatchResult::Failed(failure),
18940 "reentrant removal of every provisional host must report accepted delivery failure"
18941 );
18942 assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
18943 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18944 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18945 registry.finish_cancellation(failure);
18946 let state = registry
18947 .state
18948 .lock()
18949 .unwrap_or_else(std::sync::PoisonError::into_inner);
18950 assert!(state.entries.is_empty());
18951 assert!(state.dispatches.is_empty());
18952 return;
18953 }
18954
18955 let output = std::process::Command::new(std::env::current_exe().unwrap())
18956 .arg("--exact")
18957 .arg(
18958 "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
18959 )
18960 .arg("--nocapture")
18961 .env(CHILD_ENV, "1")
18962 .output()
18963 .expect("the hostile task-driver delivery subprocess test must start");
18964 assert!(
18965 output.status.success(),
18966 "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18967 output.status.code(),
18968 String::from_utf8_lossy(&output.stdout),
18969 String::from_utf8_lossy(&output.stderr)
18970 );
18971 }
18972
18973 #[cfg(napi_runtime_os_threads)]
18976 #[test]
18977 fn dropping_join_handle_detaches_task_like_tokio() {
18978 let completed = Arc::new(AtomicBool::new(false));
18979 let completed_task = Arc::clone(&completed);
18980 let (runnable, task) = async_task::spawn(
18981 async move {
18982 completed_task.store(true, Ordering::SeqCst);
18983 Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
18984 },
18985 |_| {},
18986 );
18987 let handle = JoinHandle(JoinHandleInner::Task {
18988 task: task.fallible(),
18989 awaiter: None,
18990 dependency: Arc::new(TaskDependency::default()),
18991 });
18992
18993 drop(handle);
18994 runnable.run();
18995 assert!(completed.load(Ordering::SeqCst));
18996 }
18997
18998 #[cfg(not(target_family = "wasm"))]
18999 #[test]
19000 fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
19001 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
19002
19003 if std::env::var_os(CHILD_ENV).is_some() {
19004 let wakes = Arc::new(AtomicUsize::new(0));
19005 let drops = Arc::new(AtomicUsize::new(0));
19006 let (runnable, task) = async_task::spawn(
19007 async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
19008 |_| {},
19009 );
19010 let mut handle = JoinHandle(JoinHandleInner::Task {
19011 task: task.fallible(),
19012 awaiter: None,
19013 dependency: Arc::new(TaskDependency::default()),
19014 });
19015 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19016 {
19017 let mut cx = Context::from_waker(&waker);
19018 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19019 }
19020 drop(waker);
19021
19022 runnable.run();
19023
19024 let waker = futures::task::noop_waker();
19025 let mut cx = Context::from_waker(&waker);
19026 assert!(matches!(
19027 Pin::new(&mut handle).poll(&mut cx),
19028 Poll::Ready(Ok(()))
19029 ));
19030 assert_eq!(
19031 drops.load(Ordering::SeqCst),
19032 1,
19033 "a completed handle must not retain the caller's awaiter"
19034 );
19035 drop(handle);
19036 assert!(wakes.load(Ordering::SeqCst) >= 1);
19037 assert_eq!(drops.load(Ordering::SeqCst), 1);
19038 return;
19039 }
19040
19041 let output = std::process::Command::new(std::env::current_exe().unwrap())
19042 .arg("--exact")
19043 .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
19044 .arg("--nocapture")
19045 .env(CHILD_ENV, "1")
19046 .output()
19047 .expect("the JoinHandle completion awaiter subprocess must start");
19048 assert!(
19049 output.status.success(),
19050 "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19051 output.status.code(),
19052 String::from_utf8_lossy(&output.stdout),
19053 String::from_utf8_lossy(&output.stderr)
19054 );
19055 }
19056
19057 #[cfg(not(target_family = "wasm"))]
19058 #[test]
19059 fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
19060 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
19061
19062 if std::env::var_os(CHILD_ENV).is_some() {
19063 let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
19064 let wakes = Arc::new(AtomicUsize::new(0));
19065 let drops = Arc::new(AtomicUsize::new(0));
19066 let mut handle = controller.spawn(std::future::pending::<()>());
19067 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19068 {
19069 let mut cx = Context::from_waker(&waker);
19070 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19071 }
19072 drop(waker);
19073
19074 controller
19075 .shutdown()
19076 .expect("shutdown must survive the hostile awaiter");
19077 drop(handle);
19078 assert!(wakes.load(Ordering::SeqCst) >= 1);
19079 assert_eq!(drops.load(Ordering::SeqCst), 1);
19080 return;
19081 }
19082
19083 let output = std::process::Command::new(std::env::current_exe().unwrap())
19084 .arg("--exact")
19085 .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
19086 .arg("--nocapture")
19087 .env(CHILD_ENV, "1")
19088 .output()
19089 .expect("the JoinHandle shutdown awaiter subprocess must start");
19090 assert!(
19091 output.status.success(),
19092 "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19093 output.status.code(),
19094 String::from_utf8_lossy(&output.stdout),
19095 String::from_utf8_lossy(&output.stderr)
19096 );
19097 }
19098
19099 #[cfg(napi_runtime_os_threads)]
19100 #[test]
19101 fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
19102 let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
19103 let mut handle = controller.spawn(std::future::pending::<()>());
19104 let old_generation = controller.backend().generation();
19105 let state = Arc::new(JoinAwaiterWakerState {
19106 events: Mutex::new(Vec::new()),
19107 });
19108 let waker = {
19109 let _generation = RuntimeGenerationGuard::enter(old_generation);
19110 join_awaiter_waker(Arc::clone(&state))
19111 };
19112 {
19113 let _generation = RuntimeGenerationGuard::enter(old_generation);
19114 let mut cx = Context::from_waker(&waker);
19115 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19116 }
19117 {
19118 let _generation = RuntimeGenerationGuard::enter(old_generation);
19119 drop(waker);
19120 }
19121
19122 controller
19123 .shutdown()
19124 .expect("the first generation must stop");
19125 controller.start().expect("the runtime must restart");
19126 let new_generation = controller.backend().generation();
19127 assert_ne!(new_generation, old_generation);
19128 state.clear();
19129
19130 let replacement = futures::task::noop_waker();
19131 {
19132 let _generation = RuntimeGenerationGuard::enter(new_generation);
19133 let mut cx = Context::from_waker(&replacement);
19134 assert!(matches!(
19135 Pin::new(&mut handle).poll(&mut cx),
19136 Poll::Ready(Err(_))
19137 ));
19138 }
19139
19140 let events = state.snapshot();
19141 assert!(
19142 !events.is_empty(),
19143 "completion must retire the registered awaiter waker"
19144 );
19145 assert!(
19146 events.iter().all(|(kind, generation)| {
19147 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19148 }),
19149 "old-generation awaiter retirement escaped its generation: {events:?}"
19150 );
19151 controller
19152 .shutdown()
19153 .expect("the replacement generation must stop");
19154 }
19155
19156 #[cfg(napi_runtime_os_threads)]
19157 #[test]
19158 fn dropping_join_handle_retains_awaiter_generation_after_restart() {
19159 let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
19160 let mut handle = controller.spawn(std::future::pending::<()>());
19161 let old_generation = controller.backend().generation();
19162 let state = Arc::new(JoinAwaiterWakerState {
19163 events: Mutex::new(Vec::new()),
19164 });
19165 let waker = {
19166 let _generation = RuntimeGenerationGuard::enter(old_generation);
19167 join_awaiter_waker(Arc::clone(&state))
19168 };
19169 {
19170 let _generation = RuntimeGenerationGuard::enter(old_generation);
19171 let mut cx = Context::from_waker(&waker);
19172 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19173 }
19174 {
19175 let _generation = RuntimeGenerationGuard::enter(old_generation);
19176 drop(waker);
19177 }
19178
19179 controller
19180 .shutdown()
19181 .expect("the first generation must stop");
19182 controller.start().expect("the runtime must restart");
19183 let new_generation = controller.backend().generation();
19184 assert_ne!(new_generation, old_generation);
19185 state.clear();
19186
19187 {
19188 let _generation = RuntimeGenerationGuard::enter(new_generation);
19189 drop(handle);
19190 }
19191
19192 let events = state.snapshot();
19193 assert!(
19194 !events.is_empty(),
19195 "handle drop must retire the registered awaiter waker"
19196 );
19197 assert!(
19198 events.iter().all(|(kind, generation)| {
19199 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19200 }),
19201 "old-generation awaiter destruction escaped its generation: {events:?}"
19202 );
19203 controller
19204 .shutdown()
19205 .expect("the replacement generation must stop");
19206 }
19207
19208 #[cfg(napi_runtime_os_threads)]
19209 #[test]
19210 fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
19211 use std::{sync::mpsc, time::Duration};
19212
19213 let options = RuntimeOptions {
19214 flavor: RuntimeFlavor::MultiThread,
19215 worker_threads: 2,
19216 max_blocking_tasks: 1,
19217 thread_name_prefix: "worker-classification".to_string(),
19218 park_deadline: None,
19219 drain_linger: DEFAULT_DRAIN_LINGER,
19220 };
19221 let executor =
19222 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19223 let id = executor.id;
19224 let (tx, rx) = mpsc::sync_channel(1);
19225
19226 executor.pool.spawn(move || {
19227 tx.send((
19228 ON_POOL_WORKER.with(std::cell::Cell::get),
19229 IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
19230 ))
19231 .unwrap();
19232 });
19233
19234 let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
19235 assert_eq!(
19236 worker,
19237 Some(id),
19238 "nested Rayon work must use cooperative block_on"
19239 );
19240 assert_eq!(
19241 driver, None,
19242 "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
19243 );
19244 }
19245
19246 #[cfg(napi_runtime_os_threads)]
19247 #[test]
19248 fn multi_thread_block_on_does_not_park_pool_worker() {
19249 use std::sync::{Barrier, mpsc};
19254 use std::time::Duration;
19255
19256 let (done_tx, done_rx) = mpsc::channel();
19257 let runner = std::thread::spawn(move || {
19258 let metrics = Arc::new(RuntimeMetrics::default());
19259 let options = RuntimeOptions {
19260 flavor: RuntimeFlavor::MultiThread,
19261 worker_threads: 2,
19262 max_blocking_tasks: 2,
19263 thread_name_prefix: "rd1-test".to_string(),
19264 park_deadline: None,
19265 drain_linger: DEFAULT_DRAIN_LINGER,
19266 };
19267 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19268
19269 let task_count = 2usize;
19270 let barrier = Arc::new(Barrier::new(task_count));
19273
19274 let mut tasks = Vec::new();
19275 for _ in 0..task_count {
19276 let exec = Arc::clone(&executor);
19277 let barrier = Arc::clone(&barrier);
19278 let body = async move {
19279 barrier.wait();
19280 let inner_exec = Arc::clone(&exec);
19281 let mut inner = std::pin::pin!(async move {
19282 let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
19283 assert_eq!(value, 7);
19284 });
19285 exec.block_on(inner.as_mut());
19287 };
19288 let scheduler = Arc::clone(&executor);
19289 let (runnable, task) =
19290 async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
19291 executor.schedule(runnable);
19292 tasks.push(task);
19293 }
19294
19295 for task in tasks {
19296 futures::executor::block_on(task);
19297 }
19298 let _ = done_tx.send(());
19299 });
19300
19301 match done_rx.recv_timeout(Duration::from_secs(10)) {
19302 Ok(()) => runner.join().unwrap(),
19303 Err(error) => panic!(
19304 "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
19305 ),
19306 }
19307 }
19308
19309 #[cfg(napi_runtime_os_threads)]
19310 #[test]
19311 fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
19312 use std::sync::mpsc;
19331
19332 for round in 0..3 {
19333 let metrics = Arc::new(RuntimeMetrics::default());
19334 let options = RuntimeOptions {
19335 flavor: RuntimeFlavor::MultiThread,
19336 worker_threads: 2,
19337 max_blocking_tasks: 1,
19338 thread_name_prefix: format!("linger-starve-{round}"),
19339 park_deadline: None,
19340 drain_linger: DEFAULT_DRAIN_LINGER,
19341 };
19342 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19343
19344 let r_done = Arc::new(AtomicBool::new(false));
19345 let give_up = Arc::new(AtomicBool::new(false));
19346 let (done_tx, done_rx) = mpsc::channel::<Duration>();
19347
19348 let t1_executor = Arc::clone(&executor);
19354 let t1_started = Arc::new(AtomicBool::new(false));
19355 let t1_started_setter = Arc::clone(&t1_started);
19356 let t1_body = async move {
19357 t1_started_setter.store(true, Ordering::SeqCst);
19358 let deadline = Instant::now() + Duration::from_secs(10);
19359 while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
19360 if Instant::now() >= deadline {
19361 return;
19363 }
19364 std::hint::spin_loop();
19365 }
19366 };
19367 let t1_scheduler = Arc::clone(&executor);
19368 let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
19369 executor.schedule(t1_runnable);
19370 wait_until("the worker-pinning task to start", || {
19371 t1_started.load(Ordering::SeqCst)
19372 });
19373
19374 let blocking_executor = Arc::clone(&executor);
19376 let r_done_setter = Arc::clone(&r_done);
19377 let started = Instant::now();
19378 let handle = executor.schedule_blocking(move || {
19379 let (r_tx, r_rx) = oneshot::channel::<()>();
19380 blocking_executor.pool.spawn(move || {
19381 r_done_setter.store(true, Ordering::SeqCst);
19382 let _ = r_tx.send(());
19383 });
19384 let mut wait_for_r = std::pin::pin!(async move {
19385 let _ = r_rx.await;
19386 });
19387 blocking_executor.block_on(wait_for_r.as_mut());
19388 let _ = done_tx.send(started.elapsed());
19389 });
19390
19391 let arm_deadline = Instant::now() + Duration::from_secs(15);
19395 while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
19396 && !r_done.load(Ordering::SeqCst)
19397 {
19398 assert!(
19399 Instant::now() < arm_deadline,
19400 "timed out arming the linger-starvation interleaving"
19401 );
19402 std::thread::yield_now();
19403 }
19404
19405 let producer_executor = Arc::clone(&executor);
19408 let producer_r_done = Arc::clone(&r_done);
19409 let producer_give_up = Arc::clone(&give_up);
19410 let producer = std::thread::spawn(move || {
19411 let cadence = Duration::from_micros(200);
19412 let mut next_tick = Instant::now();
19413 while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
19414 let scheduler = Arc::clone(&producer_executor);
19415 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
19416 producer_executor.schedule(runnable);
19417 task.detach();
19418 next_tick += cadence;
19419 while Instant::now() < next_tick {
19420 std::hint::spin_loop();
19421 }
19422 }
19423 });
19424
19425 let outcome = done_rx.recv_timeout(Duration::from_secs(5));
19426 give_up.store(true, Ordering::SeqCst);
19427 producer.join().unwrap();
19428 match outcome {
19429 Ok(elapsed) => {
19430 futures::executor::block_on(handle).expect("blocking closure join");
19431 drop(t1_task);
19432 eprintln!("round {round}: completed in {elapsed:?}");
19433 }
19434 Err(error) => panic!(
19435 "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
19436 sub-budget periodic work must not keep a drain frame resident forever"
19437 ),
19438 }
19439 }
19440 }
19441
19442 #[cfg(napi_runtime_os_threads)]
19443 #[test]
19444 #[ignore = "microbenchmark: run explicitly in release mode"]
19445 fn parked_drivers_registry_cycle_microbench() {
19446 for &n in &[8usize, 64, 256] {
19453 let registry = ParkedDrivers::default();
19454 let parkers: Vec<Arc<DriverParker>> =
19455 (0..n).map(|_| Arc::new(DriverParker::default())).collect();
19456 for parker in &parkers {
19457 registry.register_with_role(parker, true, None);
19458 }
19459 let hot = &parkers[n - 1];
19460
19461 let iters = 2_000_000u32;
19462 let start = Instant::now();
19463 for _ in 0..iters {
19464 assert!(registry.wake_one());
19465 registry.deregister(hot);
19466 assert!(hot.consume_permit());
19467 registry.register_with_role(hot, true, None);
19468 }
19469 let wake_cycle = start.elapsed() / iters;
19470
19471 let start = Instant::now();
19472 for _ in 0..iters {
19473 registry.deregister(hot);
19474 registry.register_with_role(hot, true, None);
19475 }
19476 let timeout_cycle = start.elapsed() / iters;
19477
19478 let owner = BlockingOwnerToken {
19479 executor_id: u64::MAX,
19480 frame: u64::MAX,
19481 };
19482 let start = Instant::now();
19483 for _ in 0..iters {
19484 assert!(!registry.wake_one_owner_dependency(owner));
19485 }
19486 let owner_sweep = start.elapsed() / iters;
19487
19488 eprintln!(
19489 "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
19490 timeout-deregister+re-register {timeout_cycle:?}, \
19491 owner-dependency sweep {owner_sweep:?}"
19492 );
19493 }
19494 }
19495
19496 #[cfg(napi_runtime_os_threads)]
19497 #[test]
19498 fn multi_thread_block_on_from_caller_thread_still_parks() {
19499 let metrics = Arc::new(RuntimeMetrics::default());
19502 let options = RuntimeOptions {
19503 flavor: RuntimeFlavor::MultiThread,
19504 worker_threads: 3,
19505 max_blocking_tasks: 2,
19506 thread_name_prefix: "rd1-caller".to_string(),
19507 park_deadline: None,
19508 drain_linger: DEFAULT_DRAIN_LINGER,
19509 };
19510 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19511
19512 let mut output = None;
19513 let mut future = std::pin::pin!(async {
19514 output = Some(41usize + 1);
19515 });
19516 executor.block_on(future.as_mut());
19517 assert_eq!(output, Some(42));
19518 }
19519
19520 #[cfg(napi_runtime_os_threads)]
19521 #[test]
19522 fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
19523 use std::sync::mpsc;
19546 use std::time::Duration;
19547
19548 struct CallerProbe {
19553 task: Task<usize>,
19554 parked_tx: Option<mpsc::Sender<()>>,
19555 }
19556 impl Future for CallerProbe {
19557 type Output = usize;
19558 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
19559 let poll = Pin::new(&mut self.task).poll(cx);
19561 if poll.is_pending()
19562 && let Some(tx) = self.parked_tx.take()
19563 {
19564 let _ = tx.send(());
19566 }
19567 poll
19568 }
19569 }
19570
19571 let (done_tx, done_rx) = mpsc::channel();
19572 let runner = std::thread::spawn(move || {
19573 let metrics = Arc::new(RuntimeMetrics::default());
19574 let options = RuntimeOptions {
19575 flavor: RuntimeFlavor::MultiThread,
19576 worker_threads: 2,
19577 max_blocking_tasks: 2,
19578 thread_name_prefix: "rd10-caller".to_string(),
19579 park_deadline: None,
19580 drain_linger: DEFAULT_DRAIN_LINGER,
19581 };
19582 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19583
19584 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
19589 let scheduler = Arc::clone(&executor);
19590 let (runnable, task) = async_task::spawn(
19591 async move { gate_rx.await.expect("gate sender dropped") },
19592 move |r| {
19593 scheduler.schedule(r);
19594 },
19595 );
19596 executor.schedule(runnable);
19597
19598 let (parked_tx, parked_rx) = mpsc::channel::<()>();
19602 let releaser = std::thread::spawn(move || {
19603 if parked_rx.recv().is_ok() {
19604 let _ = gate_tx.send(42usize);
19605 }
19606 });
19607
19608 let mut output = None;
19611 {
19612 let mut future = std::pin::pin!(async {
19613 output = Some(
19614 CallerProbe {
19615 task,
19616 parked_tx: Some(parked_tx),
19617 }
19618 .await,
19619 );
19620 });
19621 executor.block_on(future.as_mut());
19622 }
19623
19624 releaser.join().unwrap();
19625 assert_eq!(
19626 output,
19627 Some(42usize),
19628 "block_on must return the pool-computed value"
19629 );
19630 let _ = done_tx.send(());
19631 });
19632
19633 match done_rx.recv_timeout(Duration::from_secs(10)) {
19634 Ok(()) => runner.join().unwrap(),
19635 Err(error) => panic!(
19636 "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
19637 ),
19638 }
19639 }
19640
19641 #[cfg(napi_runtime_os_threads)]
19642 #[test]
19643 fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
19644 use std::sync::{Barrier, mpsc};
19650 use std::time::Duration;
19651
19652 let (done_tx, done_rx) = mpsc::channel();
19653 let runner = std::thread::spawn(move || {
19654 let metrics = Arc::new(RuntimeMetrics::default());
19655 let options = RuntimeOptions {
19656 flavor: RuntimeFlavor::MultiThread,
19657 worker_threads: 2,
19658 max_blocking_tasks: 2,
19659 thread_name_prefix: "rd1-park".to_string(),
19660 park_deadline: None,
19661 drain_linger: DEFAULT_DRAIN_LINGER,
19662 };
19663 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19664
19665 let task_count = 2usize;
19666 let barrier = Arc::new(Barrier::new(task_count));
19667
19668 let mut senders = Vec::new();
19669 let mut tasks = Vec::new();
19670 for _ in 0..task_count {
19671 let (tx, rx) = oneshot::channel::<usize>();
19672 senders.push(tx);
19673 let exec = Arc::clone(&executor);
19674 let barrier = Arc::clone(&barrier);
19675 let body = async move {
19676 barrier.wait();
19678 let child_exec = Arc::clone(&exec);
19681 let (child_runnable, child_task) =
19682 async_task::spawn(async move { rx.await.unwrap() }, move |r| {
19683 child_exec.schedule(r)
19684 });
19685 exec.schedule(child_runnable);
19686 let mut inner = std::pin::pin!(async move {
19687 assert_eq!(child_task.await, 9usize);
19688 });
19689 exec.block_on(inner.as_mut());
19690 };
19691 let scheduler = Arc::clone(&executor);
19692 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19693 executor.schedule(runnable);
19694 tasks.push(task);
19695 }
19696
19697 std::thread::sleep(Duration::from_millis(200));
19699 for tx in senders {
19700 let _ = tx.send(9usize);
19701 }
19702
19703 for task in tasks {
19704 futures::executor::block_on(task);
19705 }
19706 let _ = done_tx.send(());
19707 });
19708
19709 match done_rx.recv_timeout(Duration::from_secs(10)) {
19710 Ok(()) => runner.join().unwrap(),
19711 Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
19712 }
19713 }
19714
19715 #[cfg(napi_runtime_os_threads)]
19721 fn wait_until(what: &str, condition: impl Fn() -> bool) {
19722 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
19723 while !condition() {
19724 assert!(
19725 std::time::Instant::now() < deadline,
19726 "timed out waiting for: {what}"
19727 );
19728 std::thread::yield_now();
19729 }
19730 }
19731
19732 #[cfg(napi_runtime_os_threads)]
19741 fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
19742 let deadline = std::time::Instant::now() + timeout;
19743 while !handle.is_finished() {
19744 assert!(
19745 std::time::Instant::now() < deadline,
19746 "thread wedged past its {timeout:?} bound while: {what}"
19747 );
19748 std::thread::sleep(Duration::from_millis(10));
19749 }
19750 handle.join().unwrap();
19751 }
19752
19753 #[cfg(napi_runtime_os_threads)]
19754 fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
19755 executor: &Arc<MultiThreadExecutor>,
19756 start_counter: &AtomicU64,
19757 claim: impl FnOnce() -> T + Send + 'static,
19758 ) -> T {
19759 use std::sync::mpsc;
19760
19761 let (claimed_tx, claimed_rx) = mpsc::channel();
19762 let (release_claim_tx, release_claim_rx) = mpsc::channel();
19763 let (started_tx, started_rx) = mpsc::channel();
19764 let (release_start_tx, release_start_rx) = mpsc::channel();
19765 let starts_before = start_counter.load(Ordering::SeqCst);
19766 let worker = std::thread::spawn(move || {
19767 MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
19768 *slot.borrow_mut() = Some(Box::new(move || {
19769 claimed_tx.send(()).unwrap();
19770 release_claim_rx.recv().unwrap();
19771 }));
19772 });
19773 MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
19774 *slot.borrow_mut() = Some(Box::new(move || {
19775 started_tx.send(()).unwrap();
19776 release_start_rx.recv().unwrap();
19777 }));
19778 });
19779 claim()
19780 });
19781 claimed_rx
19782 .recv_timeout(Duration::from_secs(2))
19783 .expect("the scheduler claim must pause after removing work");
19784
19785 let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19786 let verdict = executor.try_begin_deadlock_verdict();
19787 let claim_verdict_rejected = verdict.is_none();
19788 drop(verdict);
19789 release_claim_tx.send(()).unwrap();
19790
19791 started_rx
19792 .recv_timeout(Duration::from_secs(2))
19793 .expect("the scheduler claim must pause after publishing its start");
19794 let admission_active_after_start =
19795 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19796 let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
19797 let verdict = executor.try_begin_deadlock_verdict();
19798 let start_verdict_rejected = verdict.is_none();
19799 drop(verdict);
19800 release_start_tx.send(()).unwrap();
19801 let output = worker.join().unwrap();
19802
19803 assert!(
19804 admission_active,
19805 "removing scheduler work must retain a detector admission until start accounting"
19806 );
19807 assert!(
19808 claim_verdict_rejected,
19809 "a deadline verdict must not close while removed work has not published its start"
19810 );
19811 assert!(
19812 start_published,
19813 "work start accounting must publish before admission retires"
19814 );
19815 assert!(
19816 admission_active_after_start,
19817 "the detector admission must remain active until after start accounting"
19818 );
19819 assert!(
19820 start_verdict_rejected,
19821 "a deadline verdict must not close between start accounting and admission retirement"
19822 );
19823 output
19824 }
19825
19826 #[cfg(napi_runtime_os_threads)]
19827 #[test]
19828 fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
19829 let executor =
19830 multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
19831
19832 let fifo_ran = Arc::new(AtomicBool::new(false));
19833 let fifo_task_ran = Arc::clone(&fifo_ran);
19834 let (fifo, fifo_task) = async_task::spawn(
19835 async move {
19836 fifo_task_ran.store(true, Ordering::SeqCst);
19837 },
19838 |_| {},
19839 );
19840 executor.metrics.runnable_scheduled();
19841 executor.push_fifo_raw(fifo);
19842 let fifo_executor = Arc::clone(&executor);
19843 assert!(run_claim_with_deadlock_gate_assertion(
19844 &executor,
19845 &executor.metrics.runnable_polls,
19846 move || {
19847 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19848 fifo_executor.run_one_fifo_runnable()
19849 }
19850 ));
19851 futures::executor::block_on(fifo_task);
19852 assert!(fifo_ran.load(Ordering::SeqCst));
19853
19854 let fifo_order = Arc::new(Mutex::new(Vec::new()));
19855 let mut fifo_tasks = Vec::new();
19856 for name in ["first", "second"] {
19857 let task_order = Arc::clone(&fifo_order);
19858 let (runnable, task) = async_task::spawn(
19859 async move {
19860 task_order.lock().unwrap().push(name);
19861 },
19862 |_| {},
19863 );
19864 executor.metrics.runnable_scheduled();
19865 executor.push_fifo_raw(runnable);
19866 fifo_tasks.push(task);
19867 }
19868 for _ in 0..2 {
19869 let fifo_executor = Arc::clone(&executor);
19870 assert!(run_claim_with_deadlock_gate_assertion(
19871 &executor,
19872 &executor.metrics.runnable_polls,
19873 move || {
19874 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19875 fifo_executor.run_one_runnable()
19876 }
19877 ));
19878 }
19879 for task in fifo_tasks {
19880 futures::executor::block_on(task);
19881 }
19882 assert_eq!(
19883 *fifo_order.lock().unwrap(),
19884 ["first", "second"],
19885 "the detector-enabled normal fallback must preserve FIFO order"
19886 );
19887
19888 let lifo_ran = Arc::new(AtomicBool::new(false));
19889 let lifo_task_ran = Arc::clone(&lifo_ran);
19890 let (lifo, lifo_task) = async_task::spawn(
19891 async move {
19892 lifo_task_ran.store(true, Ordering::SeqCst);
19893 },
19894 |_| {},
19895 );
19896 executor.metrics.runnable_scheduled();
19897 let lifo_executor = Arc::clone(&executor);
19898 assert!(run_claim_with_deadlock_gate_assertion(
19899 &executor,
19900 &executor.metrics.runnable_polls,
19901 move || {
19902 let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
19903 LIFO_SLOT.with(|slot| {
19904 assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
19905 });
19906 lifo_executor.run_one_runnable()
19907 }
19908 ));
19909 futures::executor::block_on(lifo_task);
19910 assert!(lifo_ran.load(Ordering::SeqCst));
19911
19912 executor.begin_shutdown();
19913 executor.wait_until_scheduler_idle();
19914 }
19915
19916 #[cfg(napi_runtime_os_threads)]
19917 #[test]
19918 fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
19919 let executor =
19920 multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
19921
19922 let fifo_ran = Arc::new(AtomicBool::new(false));
19923 let fifo_job_ran = Arc::clone(&fifo_ran);
19924 executor.metrics.blocking_scheduled();
19925 executor
19926 .blocking_queue
19927 .lock()
19928 .unwrap()
19929 .push(QueuedBlocking {
19930 id: BlockingJobId(u64::MAX - 90),
19931 run: Box::new(move || {
19932 fifo_job_ran.store(true, Ordering::SeqCst);
19933 }),
19934 });
19935 let fifo_executor = Arc::clone(&executor);
19936 assert!(run_claim_with_deadlock_gate_assertion(
19937 &executor,
19938 &executor.metrics.blocking_tasks_started,
19939 move || {
19940 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19941 fifo_executor.run_one_blocking()
19942 }
19943 ));
19944 assert!(fifo_ran.load(Ordering::SeqCst));
19945
19946 let owner = executor.fresh_owner_token();
19947 let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19948 let exact_job = BlockingJobId(u64::MAX - 91);
19949 let exact_ran = Arc::new(AtomicBool::new(false));
19950 let exact_job_ran = Arc::clone(&exact_ran);
19951 executor.metrics.blocking_scheduled();
19952 executor
19953 .blocking_queue
19954 .lock()
19955 .unwrap()
19956 .push(QueuedBlocking {
19957 id: exact_job,
19958 run: Box::new(move || {
19959 exact_job_ran.store(true, Ordering::SeqCst);
19960 }),
19961 });
19962 let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
19963 dependency.set_owned(BlockingDependency {
19964 job: exact_job,
19965 owner: Some(owner),
19966 });
19967 let exact_executor = Arc::clone(&executor);
19968 assert!(run_claim_with_deadlock_gate_assertion(
19969 &executor,
19970 &executor.metrics.blocking_tasks_started,
19971 move || {
19972 let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
19973 let _owner = BlockingOwnerGuard::enter(Some(owner));
19974 exact_executor.try_owned_blocking_over_cap(&dependency)
19975 }
19976 ));
19977 assert!(exact_ran.load(Ordering::SeqCst));
19978
19979 drop(active_owner);
19980 executor.begin_shutdown();
19981 executor.wait_until_scheduler_idle();
19982 }
19983
19984 #[cfg(napi_runtime_os_threads)]
19985 #[test]
19986 fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
19987 use std::sync::mpsc;
19995 use std::time::Duration;
19996
19997 let registry = Arc::new(ParkedDrivers::default());
19998
19999 let guard = registry.parked.lock().unwrap();
20001 let (done_tx, done_rx) = mpsc::channel();
20002 let waker_thread = {
20003 let registry = Arc::clone(®istry);
20004 std::thread::spawn(move || {
20005 done_tx.send(registry.wake_one()).unwrap();
20006 })
20007 };
20008 let woke = done_rx
20009 .recv_timeout(Duration::from_secs(5))
20010 .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
20011 assert!(!woke, "an empty registry has nobody to wake");
20012 waker_thread.join().unwrap();
20013 drop(guard);
20014
20015 let parker = Arc::new(DriverParker::default());
20019 registry.register(&parker);
20020 let guard = registry.parked.lock().unwrap();
20021 let (done_tx, done_rx) = mpsc::channel();
20022 let waker_thread = {
20023 let registry = Arc::clone(®istry);
20024 std::thread::spawn(move || {
20025 done_tx.send(registry.wake_one()).unwrap();
20026 })
20027 };
20028 assert!(
20029 done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20030 "wake_one with a registered parker must take the slow path (block on the held lock)"
20031 );
20032 drop(guard);
20033 let woke = done_rx
20034 .recv_timeout(Duration::from_secs(5))
20035 .expect("slow-path wake_one must complete once the lock is released");
20036 assert!(
20037 woke,
20038 "the slow path must pop and wake the registered parker"
20039 );
20040 waker_thread.join().unwrap();
20041 parker.park();
20044 assert_eq!(
20045 registry.count.load(Ordering::SeqCst),
20046 0,
20047 "wake_one deregisters what it pops"
20048 );
20049 }
20050
20051 #[cfg(napi_runtime_os_threads)]
20052 #[test]
20053 fn driver_parker_permit_granted_while_running_is_not_lost() {
20054 use std::sync::mpsc;
20058 use std::time::Duration;
20059
20060 let parker = Arc::new(DriverParker::default());
20061 parker.unpark();
20063 parker.unpark();
20064
20065 let (tx, rx) = mpsc::channel();
20066 let driver = {
20067 let parker = Arc::clone(&parker);
20068 std::thread::spawn(move || {
20069 parker.park(); tx.send(1).unwrap();
20071 parker.park(); tx.send(2).unwrap();
20073 })
20074 };
20075
20076 assert_eq!(
20077 rx.recv_timeout(Duration::from_secs(5))
20078 .expect("a stored permit must not be lost"),
20079 1
20080 );
20081 assert!(
20082 rx.recv_timeout(Duration::from_millis(300)).is_err(),
20083 "two pre-park wakes must coalesce into one permit (second park must sleep)"
20084 );
20085 parker.unpark();
20086 assert_eq!(
20087 rx.recv_timeout(Duration::from_secs(5))
20088 .expect("unpark must wake a sleeping driver"),
20089 2
20090 );
20091 driver.join().unwrap();
20092 }
20093
20094 #[cfg(napi_runtime_os_threads)]
20095 #[test]
20096 fn driver_parker_unrepresentable_timeout_remains_wakeable() {
20097 use std::sync::mpsc;
20098
20099 let parker = Arc::new(DriverParker::default());
20100 assert!(
20101 Instant::now().checked_add(Duration::MAX).is_none(),
20102 "Duration::MAX must exercise the unrepresentable-deadline path"
20103 );
20104 let (done_tx, done_rx) = mpsc::channel();
20105 let driver = {
20106 let parker = Arc::clone(&parker);
20107 std::thread::spawn(move || {
20108 done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
20109 })
20110 };
20111 wait_until("the unbounded timeout driver to sleep", || {
20112 parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
20113 });
20114 assert!(
20115 done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
20116 "an unrepresentable timeout must not expire immediately"
20117 );
20118
20119 let unparker = {
20120 let parker = Arc::clone(&parker);
20121 std::thread::spawn(move || parker.unpark())
20122 };
20123 assert!(
20124 done_rx
20125 .recv_timeout(Duration::from_secs(1))
20126 .expect("an unrepresentable timeout must remain wakeable"),
20127 "a permit must be reported as a wake"
20128 );
20129 unparker.join().unwrap();
20130 driver.join().unwrap();
20131 }
20132
20133 #[cfg(napi_runtime_os_threads)]
20134 #[test]
20135 fn parked_drivers_wake_one_wakes_exactly_one() {
20136 use std::sync::mpsc;
20140 use std::time::Duration;
20141
20142 let registry = Arc::new(ParkedDrivers::default());
20143 let (woke_tx, woke_rx) = mpsc::channel();
20144 let mut drivers = Vec::new();
20145 for index in 0..2 {
20146 let registry = Arc::clone(®istry);
20147 let woke_tx = woke_tx.clone();
20148 drivers.push(std::thread::spawn(move || {
20149 let parker = Arc::new(DriverParker::default());
20150 registry.register(&parker);
20151 parker.park();
20152 registry.deregister(&parker);
20153 woke_tx.send(index).unwrap();
20154 }));
20155 }
20156 wait_until("both drivers registered as parked", || {
20157 registry.count.load(Ordering::SeqCst) == 2
20158 });
20159
20160 assert!(registry.wake_one(), "wake_one must wake a parked driver");
20161 woke_rx
20162 .recv_timeout(Duration::from_secs(5))
20163 .expect("exactly one driver must wake");
20164 assert!(
20165 woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20166 "the second driver must stay parked after a single wake_one"
20167 );
20168 assert_eq!(
20169 registry.count.load(Ordering::SeqCst),
20170 1,
20171 "one driver must remain registered"
20172 );
20173
20174 assert!(
20175 registry.wake_one(),
20176 "a second wake_one must wake the remaining driver"
20177 );
20178 woke_rx
20179 .recv_timeout(Duration::from_secs(5))
20180 .expect("the second driver must wake");
20181 for driver in drivers {
20182 driver.join().unwrap();
20183 }
20184 }
20185
20186 #[cfg(napi_runtime_os_threads)]
20187 #[test]
20188 fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
20189 use std::sync::mpsc;
20190
20191 struct ReenterRegistryOnDrop {
20192 registry: Arc<ParkedDrivers>,
20193 dropped: mpsc::Sender<()>,
20194 }
20195
20196 #[expect(
20197 clippy::manual_noop_waker,
20198 reason = "the test observes the waker payload's Drop"
20199 )]
20200 impl std::task::Wake for ReenterRegistryOnDrop {
20201 fn wake(self: Arc<Self>) {}
20202 }
20203
20204 impl Drop for ReenterRegistryOnDrop {
20205 fn drop(&mut self) {
20206 let parker = Arc::new(DriverParker::default());
20207 self.registry.register(&parker);
20208 self.registry.deregister(&parker);
20209 self.dropped.send(()).unwrap();
20210 }
20211 }
20212
20213 let registry = Arc::new(ParkedDrivers::default());
20214 let dependency = Arc::new(TaskDependency::default());
20215 let (dropped_tx, dropped_rx) = mpsc::channel();
20216 let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
20217 registry: Arc::clone(®istry),
20218 dropped: dropped_tx,
20219 }));
20220 dependency.register_waiter(&waiter);
20221 drop(waiter);
20222
20223 let parker = Arc::new(DriverParker::default());
20224 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20225 drop(dependency);
20226
20227 let retiring_registry = Arc::clone(®istry);
20228 let retiring_parker = Arc::clone(&parker);
20229 let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
20230 dropped_rx
20231 .recv_timeout(Duration::from_secs(1))
20232 .expect("retained dependency waiters must be destroyed after releasing the registry lock");
20233 retiring.join().unwrap();
20234 }
20235
20236 #[cfg(napi_runtime_os_threads)]
20237 #[test]
20238 fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
20239 use std::sync::mpsc;
20240
20241 let registry = Arc::new(ParkedDrivers::default());
20242 let owner = BlockingOwnerToken {
20243 executor_id: u64::MAX - 70,
20244 frame: u64::MAX - 71,
20245 };
20246 let dependency = Arc::new(TaskDependency::default());
20247 dependency.set_owned(BlockingDependency {
20248 job: BlockingJobId(u64::MAX - 72),
20249 owner: Some(owner),
20250 });
20251 let parker = Arc::new(DriverParker::default());
20252 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20253
20254 let (predicate_tx, predicate_rx) = mpsc::channel();
20255 let (release_tx, release_rx) = mpsc::channel();
20256 let selecting_registry = Arc::clone(®istry);
20257 let selector = std::thread::spawn(move || {
20258 DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
20259 *slot.borrow_mut() = Some(Box::new(move || {
20260 predicate_tx.send(()).unwrap();
20261 release_rx.recv().unwrap();
20262 }));
20263 });
20264 selecting_registry.wake_one_owner_dependency(owner)
20265 });
20266
20267 predicate_rx
20268 .recv_timeout(Duration::from_secs(2))
20269 .expect("owner selection must reach the dependency predicate");
20270 let registry_was_unlocked = registry.parked.try_lock().is_ok();
20271 release_tx.send(()).unwrap();
20272 assert!(
20273 selector.join().unwrap(),
20274 "the exact-owner parker must still be selected"
20275 );
20276
20277 assert!(
20278 registry_was_unlocked,
20279 "owner dependency predicates must not run while the parked-driver registry is locked"
20280 );
20281 assert!(parker.consume_permit());
20282 assert_eq!(parker.take_owner_handoff(), Some(owner));
20283 }
20284
20285 #[cfg(napi_runtime_os_threads)]
20286 #[test]
20287 fn owner_handoff_skips_newer_untagged_dependency() {
20288 let registry = ParkedDrivers::default();
20289 let owner = BlockingOwnerToken {
20290 executor_id: u64::MAX - 1,
20291 frame: u64::MAX - 2,
20292 };
20293 let owned_dependency = Arc::new(TaskDependency::default());
20294 owned_dependency.set_owned(BlockingDependency {
20295 job: BlockingJobId(u64::MAX - 3),
20296 owner: Some(owner),
20297 });
20298 let untagged_dependency = Arc::new(TaskDependency::default());
20299 untagged_dependency.set_owned(BlockingDependency {
20300 job: BlockingJobId(u64::MAX - 4),
20301 owner: None,
20302 });
20303 let owned = Arc::new(DriverParker::default());
20304 let untagged = Arc::new(DriverParker::default());
20305
20306 registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
20307 registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
20310
20311 assert!(registry.wake_one_owner_dependency(owner));
20312 assert!(
20313 owned.consume_permit(),
20314 "the exact owner dependency must receive the retry"
20315 );
20316 assert!(
20317 !untagged.consume_permit(),
20318 "an untagged dependency must not consume an exact owner handoff"
20319 );
20320 registry.deregister(&untagged);
20321 }
20322
20323 #[cfg(napi_runtime_os_threads)]
20324 #[test]
20325 fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
20326 let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
20327 let owner = executor.fresh_owner_token();
20328 let valid_dependency = Arc::new(TaskDependency::default());
20329 valid_dependency.set_owned(BlockingDependency {
20330 job: BlockingJobId(u64::MAX - 45),
20331 owner: Some(owner),
20332 });
20333 let withdrawn_dependency = Arc::new(TaskDependency::default());
20334 withdrawn_dependency.set_owned(BlockingDependency {
20335 job: BlockingJobId(u64::MAX - 46),
20336 owner: Some(owner),
20337 });
20338 let valid = Arc::new(DriverParker::default());
20339 let withdrawn = Arc::new(DriverParker::default());
20340
20341 executor
20342 .parked_drivers
20343 .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
20344 executor.parked_drivers.register_with_role(
20345 &withdrawn,
20346 true,
20347 Some(Arc::clone(&withdrawn_dependency)),
20348 );
20349 assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
20350 withdrawn_dependency.clear();
20351
20352 assert_eq!(
20353 executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
20354 Some(false),
20355 "a selected dependency withdrawn before retry must forward the owner handoff"
20356 );
20357 assert!(
20358 valid.consume_permit(),
20359 "the next live same-owner dependency must receive the forwarded wake"
20360 );
20361 assert_eq!(
20362 valid.take_owner_handoff(),
20363 Some(owner),
20364 "the forwarded wake must retain the exact owner identity"
20365 );
20366 }
20367
20368 #[cfg(napi_runtime_os_threads)]
20369 #[test]
20370 fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
20371 use std::sync::mpsc;
20372
20373 let executor = multi_thread_executor(2, None, "owner-handoff-publication");
20374 let owner = executor.fresh_owner_token();
20375 let fallback_dependency = Arc::new(TaskDependency::default());
20376 fallback_dependency.set_owned(BlockingDependency {
20377 job: BlockingJobId(u64::MAX - 47),
20378 owner: Some(owner),
20379 });
20380 let exiting_dependency = Arc::new(TaskDependency::default());
20381 exiting_dependency.set_owned(BlockingDependency {
20382 job: BlockingJobId(u64::MAX - 48),
20383 owner: Some(owner),
20384 });
20385 let fallback = Arc::new(DriverParker::default());
20386 let exiting = Arc::new(DriverParker::default());
20387
20388 executor.parked_drivers.register_with_role(
20389 &fallback,
20390 true,
20391 Some(Arc::clone(&fallback_dependency)),
20392 );
20393 executor.parked_drivers.register_with_role(
20394 &exiting,
20395 true,
20396 Some(Arc::clone(&exiting_dependency)),
20397 );
20398
20399 let (selected_tx, selected_rx) = mpsc::channel();
20400 let (release_selection_tx, release_selection_rx) = mpsc::channel();
20401 let selector_executor = Arc::clone(&executor);
20402 let selector = std::thread::spawn(move || {
20403 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
20404 *slot.borrow_mut() = Some(Box::new(move || {
20405 selected_tx.send(()).unwrap();
20406 release_selection_rx.recv().unwrap();
20407 }));
20408 });
20409 assert!(
20410 selector_executor
20411 .parked_drivers
20412 .wake_one_owner_dependency(owner)
20413 );
20414 });
20415 selected_rx
20416 .recv_timeout(Duration::from_secs(2))
20417 .expect("selection must pause after removing the exact-owner parker");
20418
20419 let (exited_tx, exited_rx) = mpsc::channel();
20420 let exiting_executor = Arc::clone(&executor);
20421 let exiting_driver = Arc::clone(&exiting);
20422 let driver = std::thread::spawn(move || {
20423 exiting_driver.park();
20424 exiting_executor.parked_drivers.deregister(&exiting_driver);
20425 exiting_executor.forward_owner_handoff(&exiting_driver);
20426 exited_tx.send(()).unwrap();
20427 });
20428 exiting.unpark();
20429 exited_rx
20430 .recv_timeout(Duration::from_secs(2))
20431 .expect("the direct future wake must let the selected driver exit");
20432
20433 release_selection_tx.send(()).unwrap();
20434 selector.join().unwrap();
20435 driver.join().unwrap();
20436
20437 assert!(
20438 fallback.consume_permit(),
20439 "an exiting selected driver must forward the owner handoff to the next dependency"
20440 );
20441 assert_eq!(
20442 fallback.take_owner_handoff(),
20443 Some(owner),
20444 "the forwarded handoff must retain the exact owner identity"
20445 );
20446 }
20447
20448 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20449 #[test]
20450 fn cooperative_panic_forwards_pending_owner_handoff() {
20451 use std::sync::mpsc;
20452
20453 struct PanicAfterWake {
20454 dependency: BlockingDependency,
20455 first_poll: Option<mpsc::Sender<()>>,
20456 }
20457
20458 impl Future for PanicAfterWake {
20459 type Output = ();
20460
20461 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20462 if let Some(first_poll) = self.first_poll.take() {
20463 let mut published = PublishedBlockingDependencies::default();
20466 record_blocking_dependency(&mut published, self.dependency);
20467 first_poll.send(()).unwrap();
20468 return Poll::Pending;
20469 }
20470 panic!("intentional cooperative driver panic after owner handoff");
20471 }
20472 }
20473
20474 let executor = multi_thread_executor(2, None, "panic-owner-handoff");
20475 let owner = executor.fresh_owner_token();
20476 let fallback_dependency = Arc::new(TaskDependency::default());
20477 fallback_dependency.set_owned(BlockingDependency {
20478 job: BlockingJobId(u64::MAX - 49),
20479 owner: Some(owner),
20480 });
20481 let fallback = Arc::new(DriverParker::default());
20482 executor.parked_drivers.register_with_role(
20483 &fallback,
20484 true,
20485 Some(Arc::clone(&fallback_dependency)),
20486 );
20487
20488 let (first_poll_tx, first_poll_rx) = mpsc::channel();
20489 let (panicked_tx, panicked_rx) = mpsc::channel();
20490 let driver_executor = Arc::clone(&executor);
20491 let driver = std::thread::spawn(move || {
20492 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20493 let mut future = std::pin::pin!(PanicAfterWake {
20494 dependency: BlockingDependency {
20495 job: BlockingJobId(u64::MAX - 50),
20496 owner: Some(owner)
20497 },
20498 first_poll: Some(first_poll_tx),
20499 });
20500 let panicked = catch_unwind(AssertUnwindSafe(|| {
20501 driver_executor.block_on(future.as_mut());
20502 }))
20503 .is_err();
20504 panicked_tx.send(panicked).unwrap();
20505 });
20506
20507 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20508 wait_until("the panicking cooperative driver to park", || {
20509 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20510 });
20511 assert!(
20512 executor.parked_drivers.wake_one_owner_dependency(owner),
20513 "the exact owner handoff must select the cooperative driver"
20514 );
20515 assert!(
20516 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20517 "the test future must unwind from its second poll"
20518 );
20519 driver.join().unwrap();
20520
20521 assert!(
20522 fallback.consume_permit(),
20523 "panic cleanup must forward the unconsumed owner handoff"
20524 );
20525 assert_eq!(
20526 fallback.take_owner_handoff(),
20527 Some(owner),
20528 "panic cleanup must preserve the exact owner identity"
20529 );
20530 }
20531
20532 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20533 #[test]
20534 fn cooperative_panic_flushes_lifo_slot() {
20535 struct ScheduleThenPanic {
20536 executor: Arc<MultiThreadExecutor>,
20537 ran: Arc<AtomicBool>,
20538 }
20539
20540 impl Future for ScheduleThenPanic {
20541 type Output = ();
20542
20543 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20544 let ran = Arc::clone(&self.ran);
20545 let scheduler = Arc::clone(&self.executor);
20546 let (runnable, task) = async_task::spawn(
20547 async move {
20548 ran.store(true, Ordering::SeqCst);
20549 },
20550 move |runnable| scheduler.schedule(runnable),
20551 );
20552 self.executor.schedule(runnable);
20553 task.detach();
20554 panic!("intentional cooperative driver panic with a populated LIFO slot");
20555 }
20556 }
20557
20558 let executor = multi_thread_executor(2, None, "panic-lifo-flush");
20559 executor
20560 .active_drainers
20561 .store(executor.max_drainers, Ordering::SeqCst);
20562 let ran = Arc::new(AtomicBool::new(false));
20563 let panicked = {
20564 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20565 let mut future = std::pin::pin!(ScheduleThenPanic {
20566 executor: Arc::clone(&executor),
20567 ran: Arc::clone(&ran),
20568 });
20569 catch_unwind(AssertUnwindSafe(|| {
20570 executor.block_on(future.as_mut());
20571 }))
20572 .is_err()
20573 };
20574
20575 assert!(panicked, "the test future must unwind from its first poll");
20576 assert!(
20577 executor.pop_lifo_slot().is_none(),
20578 "panic cleanup must empty the LIFO slot"
20579 );
20580 assert_eq!(
20581 executor.injector.len(),
20582 1,
20583 "panic cleanup must move the slot runnable to the shared FIFO"
20584 );
20585 assert!(
20586 executor.run_one(),
20587 "the flushed runnable must remain executable"
20588 );
20589 assert!(ran.load(Ordering::SeqCst));
20590 executor.active_drainers.store(0, Ordering::SeqCst);
20591 }
20592
20593 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20594 #[test]
20595 fn cooperative_panic_compensates_absorbed_queue_wake() {
20596 use std::sync::mpsc;
20597
20598 struct PanicAfterWake {
20599 first_poll: Option<mpsc::Sender<()>>,
20600 }
20601
20602 impl Future for PanicAfterWake {
20603 type Output = ();
20604
20605 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20606 if let Some(first_poll) = self.first_poll.take() {
20607 first_poll.send(()).unwrap();
20608 return Poll::Pending;
20609 }
20610 panic!("intentional cooperative driver panic after a queue wake");
20611 }
20612 }
20613
20614 let executor = multi_thread_executor(2, None, "panic-wake-compensation");
20615 executor
20616 .active_drainers
20617 .store(executor.max_drainers, Ordering::SeqCst);
20618 let fallback = Arc::new(DriverParker::default());
20619 executor.parked_drivers.register(&fallback);
20620 let (first_poll_tx, first_poll_rx) = mpsc::channel();
20621 let (panicked_tx, panicked_rx) = mpsc::channel();
20622 let driver_executor = Arc::clone(&executor);
20623 let driver = std::thread::spawn(move || {
20624 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20625 let mut future = std::pin::pin!(PanicAfterWake {
20626 first_poll: Some(first_poll_tx)
20627 });
20628 let panicked = catch_unwind(AssertUnwindSafe(|| {
20629 driver_executor.block_on(future.as_mut());
20630 }))
20631 .is_err();
20632 panicked_tx.send(panicked).unwrap();
20633 });
20634
20635 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20636 wait_until(
20637 "the panicking cooperative driver to park after the fallback",
20638 || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
20639 );
20640
20641 let ran = Arc::new(AtomicBool::new(false));
20642 let ran_task = Arc::clone(&ran);
20643 let scheduler = Arc::clone(&executor);
20644 let (runnable, task) = async_task::spawn(
20645 async move {
20646 ran_task.store(true, Ordering::SeqCst);
20647 },
20648 move |runnable| scheduler.schedule(runnable),
20649 );
20650 task.detach();
20651 executor.metrics.runnable_scheduled();
20652 executor.push_fifo_raw(runnable);
20653 assert!(
20654 executor.parked_drivers.wake_one(),
20655 "the queued runnable's wake must be absorbed by the cooperative driver"
20656 );
20657
20658 assert!(
20659 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20660 "the test future must unwind after absorbing the queue wake"
20661 );
20662 driver.join().unwrap();
20663 assert!(
20664 fallback.consume_permit(),
20665 "panic cleanup must compensate the absorbed queue wake to the fallback driver"
20666 );
20667 assert!(
20668 executor.run_one(),
20669 "the queued runnable must remain executable"
20670 );
20671 assert!(ran.load(Ordering::SeqCst));
20672 executor.active_drainers.store(0, Ordering::SeqCst);
20673 }
20674
20675 #[cfg(napi_runtime_os_threads)]
20676 #[test]
20677 fn multi_thread_future_wake_targets_its_own_parked_driver() {
20678 use std::sync::mpsc;
20686 use std::time::Duration;
20687
20688 let (done_tx, done_rx) = mpsc::channel();
20689 let runner = std::thread::spawn(move || {
20690 let metrics = Arc::new(RuntimeMetrics::default());
20691 let options = RuntimeOptions {
20692 flavor: RuntimeFlavor::MultiThread,
20693 worker_threads: 2,
20694 max_blocking_tasks: 2,
20695 thread_name_prefix: "wake-target".to_string(),
20696 park_deadline: None,
20697 drain_linger: DEFAULT_DRAIN_LINGER,
20698 };
20699 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20700
20701 let (tx1, rx1) = oneshot::channel::<usize>();
20702 let (tx2, rx2) = oneshot::channel::<usize>();
20703 let t1_done = Arc::new(AtomicBool::new(false));
20704 let t2_done = Arc::new(AtomicBool::new(false));
20705
20706 let t1_exec = Arc::clone(&executor);
20708 let t1_flag = Arc::clone(&t1_done);
20709 let t1_body = async move {
20710 let mut inner = std::pin::pin!(async move {
20711 assert_eq!(rx1.await.unwrap(), 7usize);
20712 });
20713 t1_exec.block_on(inner.as_mut());
20714 t1_flag.store(true, Ordering::SeqCst);
20715 };
20716 let sched1 = Arc::clone(&executor);
20717 let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
20718 executor.schedule(runnable1);
20719 wait_until("driver 1 parked", || {
20720 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20721 });
20722
20723 let t2_exec = Arc::clone(&executor);
20727 let t2_flag = Arc::clone(&t2_done);
20728 let t2_body = async move {
20729 let mut inner = std::pin::pin!(async move {
20730 assert_eq!(rx2.await.unwrap(), 9usize);
20731 });
20732 t2_exec.block_on(inner.as_mut());
20733 t2_flag.store(true, Ordering::SeqCst);
20734 };
20735 let sched2 = Arc::clone(&executor);
20736 let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
20737 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(runnable2);
20739 executor.ensure_drainer();
20740 wait_until("driver 2 parked", || {
20741 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20742 });
20743
20744 tx1.send(7usize).unwrap();
20747 wait_until("T1 completed via its own driver's wake", || {
20748 t1_done.load(Ordering::SeqCst)
20749 });
20750 futures::executor::block_on(task1);
20751 assert!(
20752 !t2_done.load(Ordering::SeqCst),
20753 "D2 must still be parked: its future was never completed"
20754 );
20755 wait_until(
20772 "the retiring drain frame registered its linger parker",
20773 || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
20774 );
20775 wait_until("the lingering drainer's idle budget expired", || {
20776 executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
20777 });
20778 assert_eq!(
20779 executor.parked_drivers.count.load(Ordering::SeqCst),
20780 1,
20781 "exactly the untargeted driver must remain parked after the linger expired"
20782 );
20783 assert!(
20784 !t2_done.load(Ordering::SeqCst),
20785 "D2 must still be parked after the lingering drainer expired"
20786 );
20787
20788 tx2.send(9usize).unwrap();
20790 futures::executor::block_on(task2);
20791 assert!(t2_done.load(Ordering::SeqCst));
20792 let _ = done_tx.send(());
20793 });
20794
20795 match done_rx.recv_timeout(Duration::from_secs(10)) {
20796 Ok(()) => runner.join().unwrap(),
20797 Err(error) => panic!(
20798 "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
20799 ),
20800 }
20801 }
20802
20803 #[cfg(napi_runtime_os_threads)]
20804 #[test]
20805 fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
20806 use std::sync::mpsc;
20814 use std::time::Duration;
20815
20816 let (done_tx, done_rx) = mpsc::channel();
20817 let runner = std::thread::spawn(move || {
20818 let metrics = Arc::new(RuntimeMetrics::default());
20819 let options = RuntimeOptions {
20820 flavor: RuntimeFlavor::MultiThread,
20821 worker_threads: 1,
20822 max_blocking_tasks: 1,
20823 thread_name_prefix: "wake-compensate".to_string(),
20824 park_deadline: None,
20825 drain_linger: DEFAULT_DRAIN_LINGER,
20826 };
20827 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20828
20829 let (gate_tx, gate_rx) = oneshot::channel::<()>();
20831 let td_exec = Arc::clone(&executor);
20832 let td_body = async move {
20833 let mut inner = std::pin::pin!(async move {
20834 gate_rx.await.unwrap();
20835 });
20836 td_exec.block_on(inner.as_mut());
20837 };
20838 let sched = Arc::clone(&executor);
20839 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
20840 executor.schedule(td_runnable);
20841 wait_until("the sole driver parked", || {
20842 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20843 });
20844
20845 let (ran_tx, ran_rx) = mpsc::channel::<()>();
20848 let sched_stranded = Arc::clone(&executor);
20849 let (stranded_runnable, stranded_task) = async_task::spawn(
20850 async move {
20851 ran_tx.send(()).unwrap();
20852 },
20853 move |r| sched_stranded.schedule(r),
20854 );
20855 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(stranded_runnable);
20857 stranded_task.detach();
20858
20859 {
20864 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20865 let mut ready = std::pin::pin!(async {});
20866 executor.block_on(ready.as_mut());
20867 }
20868
20869 ran_rx.recv_timeout(Duration::from_secs(5)).expect(
20870 "exit compensation must wake the parked driver to run the stranded queued runnable",
20871 );
20872
20873 gate_tx.send(()).unwrap();
20875 futures::executor::block_on(td_task);
20876 let _ = done_tx.send(());
20877 });
20878
20879 match done_rx.recv_timeout(Duration::from_secs(10)) {
20880 Ok(()) => runner.join().unwrap(),
20881 Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
20882 }
20883 }
20884
20885 #[cfg(napi_runtime_os_threads)]
20886 #[test]
20887 fn cooperative_exit_compensates_blocking_only_residue() {
20888 use std::sync::mpsc;
20889
20890 struct Controlled {
20891 ready: Arc<AtomicBool>,
20892 entered: Option<mpsc::Sender<()>>,
20893 }
20894
20895 impl Future for Controlled {
20896 type Output = ();
20897
20898 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20899 if let Some(entered) = self.entered.take() {
20900 entered.send(()).unwrap();
20901 }
20902 if self.ready.load(Ordering::SeqCst) {
20903 Poll::Ready(())
20904 } else {
20905 Poll::Pending
20906 }
20907 }
20908 }
20909
20910 let options = RuntimeOptions {
20911 flavor: RuntimeFlavor::MultiThread,
20912 worker_threads: 2,
20913 max_blocking_tasks: 1,
20914 thread_name_prefix: "blocking-exit-compensation".to_string(),
20915 park_deadline: None,
20916 drain_linger: DEFAULT_DRAIN_LINGER,
20917 };
20918 let executor =
20919 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20920 let ready = Arc::new(AtomicBool::new(false));
20921 let (entered_tx, entered_rx) = mpsc::channel();
20922 let (returned_tx, returned_rx) = mpsc::channel();
20923 let (release_tx, release_rx) = mpsc::channel();
20924 let task_exec = Arc::clone(&executor);
20925 let task_ready = Arc::clone(&ready);
20926 let scheduler = Arc::clone(&executor);
20927 let (runnable, task) = async_task::spawn(
20928 async move {
20929 let mut future = std::pin::pin!(Controlled {
20930 ready: task_ready,
20931 entered: Some(entered_tx)
20932 });
20933 task_exec.block_on(future.as_mut());
20934 returned_tx.send(()).unwrap();
20935 release_rx.recv().unwrap();
20936 },
20937 move |runnable| scheduler.schedule(runnable),
20938 );
20939 executor.schedule(runnable);
20940 entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
20941
20942 wait_until("the cooperative frame parked", || {
20952 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20953 });
20954
20955 let timer_exec = Arc::clone(&executor);
20956 let timer_scheduler = Arc::clone(&executor);
20957 let (timer_runnable, timer_task) = async_task::spawn(
20958 async move {
20959 heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
20960 },
20961 move |runnable| timer_scheduler.schedule(runnable),
20962 );
20963 executor.schedule(timer_runnable);
20964 wait_until("the dedicated timekeeper parked", || {
20965 executor
20966 .timers
20967 .inner
20968 .lock()
20969 .unwrap()
20970 .timekeeper_parker
20971 .is_some()
20972 });
20973 wait_until(
20978 "the cooperative frame re-parked in its timer-bounded park",
20979 || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
20980 );
20981
20982 ready.store(true, Ordering::SeqCst);
20983 let (blocking_tx, blocking_rx) = mpsc::channel();
20984 let blocking = executor.schedule_blocking(move || {
20985 blocking_tx.send(()).unwrap();
20986 });
20987 blocking_rx
20990 .recv_timeout(Duration::from_secs(10))
20991 .expect("blocking-only residue must be serviced by a Rayon worker");
20992 returned_rx
20993 .recv_timeout(Duration::from_secs(10))
20994 .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
20995 release_tx.send(()).unwrap();
20996 futures::executor::block_on(task);
20997 futures::executor::block_on(blocking).unwrap();
20998 executor.shutdown_timers();
20999 futures::executor::block_on(timer_task);
21000 }
21001
21002 #[cfg(napi_runtime_os_threads)]
21003 #[test]
21004 fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
21005 let options = RuntimeOptions {
21006 flavor: RuntimeFlavor::MultiThread,
21007 worker_threads: 2,
21008 max_blocking_tasks: 1,
21009 thread_name_prefix: "stopped-exit-compensation".to_string(),
21010 park_deadline: None,
21011 drain_linger: DEFAULT_DRAIN_LINGER,
21012 };
21013 let executor =
21014 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21015 let ran = Arc::new(AtomicBool::new(false));
21016 let blocking_ran = Arc::clone(&ran);
21017
21018 executor
21022 .active_drainers
21023 .store(executor.max_drainers, Ordering::SeqCst);
21024 let blocking = executor.schedule_blocking(move || {
21025 blocking_ran.store(true, Ordering::SeqCst);
21026 });
21027 assert!(!executor.blocking_queue.lock().unwrap().is_empty());
21028
21029 executor.stop.begin_shutdown();
21030 executor.compensate_queued_work_wake(true);
21031 let ran_after_stop = ran.load(Ordering::SeqCst);
21032
21033 executor.active_drainers.store(0, Ordering::SeqCst);
21034 executor.begin_shutdown();
21035 let blocking_result = futures::executor::block_on(blocking);
21036
21037 assert!(
21038 !ran_after_stop,
21039 "exit compensation started queued blocking work after stop"
21040 );
21041 assert!(
21042 blocking_result.is_err(),
21043 "shutdown must cancel the queued blocking job"
21044 );
21045 }
21046
21047 #[cfg(napi_runtime_os_threads)]
21048 #[test]
21049 fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
21050 use std::sync::mpsc;
21059 use std::time::Duration;
21060
21061 const ROUNDS: usize = 20_000;
21062
21063 let (done_tx, done_rx) = mpsc::channel();
21064 let registry = Arc::new(ParkedDrivers::default());
21065 let work = Arc::new(AtomicBool::new(false));
21066
21067 let consumer = {
21068 let registry = Arc::clone(®istry);
21069 let work = Arc::clone(&work);
21070 std::thread::spawn(move || {
21071 let parker = Arc::new(DriverParker::default());
21072 for _ in 0..ROUNDS {
21073 loop {
21074 if work.swap(false, Ordering::SeqCst) {
21075 break;
21076 }
21077 registry.register(&parker);
21080 if work.load(Ordering::SeqCst) {
21081 registry.deregister(&parker);
21082 continue;
21083 }
21084 parker.park();
21085 registry.deregister(&parker);
21086 }
21087 }
21088 done_tx.send(()).unwrap();
21089 })
21090 };
21091
21092 for _ in 0..ROUNDS {
21093 work.store(true, Ordering::SeqCst);
21094 let _ = registry.wake_one();
21098 while work.load(Ordering::SeqCst) {
21099 std::hint::spin_loop();
21100 }
21101 }
21102
21103 done_rx
21104 .recv_timeout(Duration::from_secs(30))
21105 .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
21106 consumer.join().unwrap();
21107 }
21108
21109 #[cfg(napi_runtime_os_threads)]
21110 #[test]
21111 fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
21112 let options = RuntimeOptions {
21116 flavor: RuntimeFlavor::MultiThread,
21117 worker_threads: 2,
21118 max_blocking_tasks: 2,
21119 thread_name_prefix: "lifo-order".to_string(),
21120 park_deadline: None,
21121 drain_linger: DEFAULT_DRAIN_LINGER,
21122 };
21123 let executor =
21124 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21125 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21126
21127 let old_order = Arc::clone(&order);
21130 let sched = Arc::clone(&executor);
21131 let (old_runnable, old_task) = async_task::spawn(
21132 async move {
21133 old_order.lock().unwrap().push("old");
21134 },
21135 move |r| sched.schedule(r),
21136 );
21137 executor.metrics.runnable_scheduled(); executor.push_fifo_raw(old_runnable);
21139 old_task.detach();
21140
21141 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21144 let new_order = Arc::clone(&order);
21145 let sched = Arc::clone(&executor);
21146 let (new_runnable, new_task) = async_task::spawn(
21147 async move {
21148 new_order.lock().unwrap().push("new");
21149 },
21150 move |r| sched.schedule(r),
21151 );
21152 executor.schedule(new_runnable);
21153 new_task.detach();
21154 assert_eq!(
21155 executor.injector.len(),
21156 1,
21157 "the slot-path schedule must not push to the shared FIFO"
21158 );
21159
21160 assert!(executor.run_one(), "run_one must pop the slot first");
21161 assert!(executor.run_one(), "run_one must then pop the FIFO");
21162 assert!(!executor.run_one(), "no work must remain");
21163 assert_eq!(
21164 *order.lock().unwrap(),
21165 vec!["new", "old"],
21166 "the slot runnable (newest) must run before older FIFO work"
21167 );
21168 }
21169
21170 #[cfg(napi_runtime_os_threads)]
21171 #[test]
21172 fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
21173 let options = RuntimeOptions {
21174 flavor: RuntimeFlavor::MultiThread,
21175 worker_threads: 2,
21176 max_blocking_tasks: 1,
21177 thread_name_prefix: "fifo-owner".to_string(),
21178 park_deadline: None,
21179 drain_linger: DEFAULT_DRAIN_LINGER,
21180 };
21181 let executor =
21182 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21183 let observed_owner = Arc::new(Mutex::new(None));
21184 let observed = Arc::clone(&observed_owner);
21185 let (runnable, task) = async_task::spawn(
21186 async move {
21187 *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21188 },
21189 |_| {},
21190 );
21191 task.detach();
21192 executor.metrics.runnable_scheduled();
21193 executor.push_fifo_raw(runnable);
21194 executor.active_drainers.store(1, Ordering::Release);
21195
21196 let owner = executor.fresh_owner_token();
21197 let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
21198 Arc::clone(&executor).drain();
21199
21200 assert_eq!(
21201 *observed_owner.lock().unwrap(),
21202 Some(None),
21203 "FIFO runnables must not borrow a blocking closure's over-cap privilege"
21204 );
21205 assert_eq!(
21206 BLOCKING_OWNER.with(std::cell::Cell::get),
21207 Some(owner),
21208 "the ambient blocking owner must be restored after the runnable"
21209 );
21210 }
21211
21212 #[cfg(napi_runtime_os_threads)]
21213 #[test]
21214 fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
21215 use std::sync::mpsc;
21219 use std::time::Duration;
21220
21221 let (done_tx, done_rx) = mpsc::channel();
21222 let runner = std::thread::spawn(move || {
21223 let options = RuntimeOptions {
21224 flavor: RuntimeFlavor::MultiThread,
21225 worker_threads: 1,
21226 max_blocking_tasks: 1,
21227 thread_name_prefix: "lifo-drain".to_string(),
21228 park_deadline: None,
21229 drain_linger: DEFAULT_DRAIN_LINGER,
21230 };
21231 let executor =
21232 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21233 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21234
21235 let p_exec = Arc::clone(&executor);
21236 let p_order = Arc::clone(&order);
21237 let p_body = async move {
21238 p_order.lock().unwrap().push("P");
21239 let c_order = Arc::clone(&p_order);
21241 let sched = Arc::clone(&p_exec);
21242 let (c_runnable, c_task) = async_task::spawn(
21243 async move {
21244 c_order.lock().unwrap().push("C");
21245 },
21246 move |r| sched.schedule(r),
21247 );
21248 p_exec.schedule(c_runnable);
21249 c_task.detach();
21250 };
21251
21252 let mut tasks = Vec::new();
21256 let sched = Arc::clone(&executor);
21257 let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
21258 executor.metrics.runnable_scheduled();
21259 executor.push_fifo_raw(p_runnable);
21260 tasks.push(p_task);
21261 for name in ["T1", "T2"] {
21262 let t_order = Arc::clone(&order);
21263 let sched = Arc::clone(&executor);
21264 let (t_runnable, t_task) = async_task::spawn(
21265 async move {
21266 t_order.lock().unwrap().push(name);
21267 },
21268 move |r| sched.schedule(r),
21269 );
21270 executor.metrics.runnable_scheduled();
21271 executor.push_fifo_raw(t_runnable);
21272 tasks.push(t_task);
21273 }
21274 executor.ensure_drainer();
21275
21276 for task in tasks {
21277 futures::executor::block_on(task);
21278 }
21279 wait_until("the detached child C ran", || {
21280 order.lock().unwrap().len() == 4
21281 });
21282 assert_eq!(
21283 *order.lock().unwrap(),
21284 vec!["P", "C", "T1", "T2"],
21285 "the slot child C must run right after its scheduler P, ahead of older FIFO work"
21286 );
21287 let _ = done_tx.send(());
21288 });
21289
21290 match done_rx.recv_timeout(Duration::from_secs(10)) {
21291 Ok(()) => runner.join().unwrap(),
21292 Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
21293 }
21294 }
21295
21296 #[cfg(napi_runtime_os_threads)]
21297 #[test]
21298 fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
21299 let options = RuntimeOptions {
21305 flavor: RuntimeFlavor::MultiThread,
21306 worker_threads: 2,
21307 max_blocking_tasks: 2,
21308 thread_name_prefix: "lifo-displace".to_string(),
21309 park_deadline: None,
21310 drain_linger: DEFAULT_DRAIN_LINGER,
21311 };
21312 let executor =
21313 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21314 executor
21315 .active_drainers
21316 .store(executor.max_drainers, Ordering::SeqCst);
21317 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21318
21319 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21320 for name in ["a", "b"] {
21321 let n_order = Arc::clone(&order);
21322 let sched = Arc::clone(&executor);
21323 let (runnable, task) = async_task::spawn(
21324 async move {
21325 n_order.lock().unwrap().push(name);
21326 },
21327 move |r| sched.schedule(r),
21328 );
21329 executor.schedule(runnable);
21330 task.detach();
21331 }
21332 assert_eq!(
21333 executor.injector.len(),
21334 1,
21335 "the displaced occupant (a) must have been pushed to the shared FIFO"
21336 );
21337
21338 assert!(executor.run_one(), "run_one must pop the slot (b) first");
21339 assert!(
21340 executor.run_one(),
21341 "run_one must then pop the displaced (a) from the FIFO"
21342 );
21343 assert!(!executor.run_one(), "no work must remain");
21344 assert_eq!(
21345 *order.lock().unwrap(),
21346 vec!["b", "a"],
21347 "the newest schedule must win the slot; the displaced one must survive via the FIFO"
21348 );
21349 executor.active_drainers.store(0, Ordering::SeqCst);
21350 }
21351
21352 #[cfg(napi_runtime_os_threads)]
21353 #[test]
21354 fn lifo_slot_is_executor_scoped() {
21355 let options = RuntimeOptions {
21360 flavor: RuntimeFlavor::MultiThread,
21361 worker_threads: 2,
21362 max_blocking_tasks: 2,
21363 thread_name_prefix: "lifo-scope".to_string(),
21364 park_deadline: None,
21365 drain_linger: DEFAULT_DRAIN_LINGER,
21366 };
21367 let exec1 =
21368 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21369 let exec2 =
21370 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21371 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21372 exec1
21373 .active_drainers
21374 .store(exec1.max_drainers, Ordering::SeqCst);
21375 exec2
21376 .active_drainers
21377 .store(exec2.max_drainers, Ordering::SeqCst);
21378 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21379
21380 let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
21382 let r1_order = Arc::clone(&order);
21383 let sched = Arc::clone(&exec1);
21384 let (r1, t1) = async_task::spawn(
21385 async move {
21386 r1_order.lock().unwrap().push("one");
21387 },
21388 move |r| sched.schedule(r),
21389 );
21390 exec1.schedule(r1);
21391 t1.detach();
21392 assert!(
21393 exec1.injector.is_empty(),
21394 "r1 must sit in the slot, not exec1's FIFO"
21395 );
21396
21397 {
21398 let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
21401 let r2_order = Arc::clone(&order);
21402 let sched = Arc::clone(&exec2);
21403 let (r2, t2) = async_task::spawn(
21404 async move {
21405 r2_order.lock().unwrap().push("two");
21406 },
21407 move |r| sched.schedule(r),
21408 );
21409 exec2.schedule(r2);
21410 t2.detach();
21411 assert_eq!(
21412 exec2.injector.len(),
21413 1,
21414 "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
21415 );
21416 assert!(
21417 exec2.pop_lifo_slot().is_none(),
21418 "exec2 must not pop exec1's slot entry (executor-scoped pop)"
21419 );
21420 assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
21421 }
21422
21423 assert!(
21425 exec1.run_one(),
21426 "exec1's slot entry must have survived the foreign activity"
21427 );
21428 assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
21429 exec1.active_drainers.store(0, Ordering::SeqCst);
21430 exec2.active_drainers.store(0, Ordering::SeqCst);
21431 }
21432
21433 #[cfg(napi_runtime_os_threads)]
21434 #[test]
21435 fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
21436 use std::sync::mpsc;
21443 use std::time::Duration;
21444
21445 const CHAIN: u64 = 200; fn spawn_link(
21448 executor: &Arc<MultiThreadExecutor>,
21449 remaining: u64,
21450 counter: Arc<AtomicU64>,
21451 done: mpsc::Sender<()>,
21452 ) {
21453 let body_exec = Arc::clone(executor);
21454 let body = async move {
21455 counter.fetch_add(1, Ordering::SeqCst);
21456 if remaining > 0 {
21457 spawn_link(&body_exec, remaining - 1, counter, done);
21458 } else {
21459 done.send(()).unwrap();
21460 }
21461 };
21462 let sched = Arc::clone(executor);
21463 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21464 executor.schedule(runnable);
21465 task.detach();
21466 }
21467
21468 let (done_tx, done_rx) = mpsc::channel();
21469 let runner = std::thread::spawn(move || {
21470 let metrics = Arc::new(RuntimeMetrics::default());
21471 let options = RuntimeOptions {
21472 flavor: RuntimeFlavor::MultiThread,
21473 worker_threads: 1,
21474 max_blocking_tasks: 1,
21475 thread_name_prefix: "lifo-budget".to_string(),
21476 park_deadline: None,
21477 drain_linger: DEFAULT_DRAIN_LINGER,
21478 };
21479 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21480
21481 let counter = Arc::new(AtomicU64::new(0));
21482 let (chain_tx, chain_rx) = mpsc::channel();
21483 spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
21484
21485 chain_rx
21486 .recv_timeout(Duration::from_secs(10))
21487 .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
21488 assert_eq!(
21489 counter.load(Ordering::SeqCst),
21490 CHAIN,
21491 "every chain link must have run"
21492 );
21493 wait_until("runnable counters settle to zero (no slot leak)", || {
21494 metrics.queued_runnables.load(Ordering::Relaxed) == 0
21495 && metrics.active_runnables.load(Ordering::Relaxed) == 0
21496 });
21497 let _ = done_tx.send(());
21498 });
21499
21500 match done_rx.recv_timeout(Duration::from_secs(15)) {
21501 Ok(()) => runner.join().unwrap(),
21502 Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
21503 }
21504 }
21505
21506 #[cfg(napi_runtime_os_threads)]
21507 #[test]
21508 fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
21509 let options = RuntimeOptions {
21516 flavor: RuntimeFlavor::MultiThread,
21517 worker_threads: 2,
21518 max_blocking_tasks: 2,
21519 thread_name_prefix: "lifo-flush".to_string(),
21520 park_deadline: None,
21521 drain_linger: DEFAULT_DRAIN_LINGER,
21522 };
21523 let executor =
21524 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21525 executor
21526 .active_drainers
21527 .store(executor.max_drainers, Ordering::SeqCst);
21528 let ran = Arc::new(AtomicBool::new(false));
21529
21530 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21531 let ran_flag = Arc::clone(&ran);
21532 let sched = Arc::clone(&executor);
21533 let (runnable, task) = async_task::spawn(
21534 async move {
21535 ran_flag.store(true, Ordering::SeqCst);
21536 },
21537 move |r| sched.schedule(r),
21538 );
21539 executor.schedule(runnable);
21540 task.detach();
21541 assert!(
21542 executor.injector.is_empty(),
21543 "the runnable must start in the slot"
21544 );
21545
21546 executor.flush_lifo_slot();
21547 assert!(
21548 executor.pop_lifo_slot().is_none(),
21549 "the slot must be empty after a flush"
21550 );
21551 assert_eq!(
21552 executor.injector.len(),
21553 1,
21554 "the flushed runnable must be on the shared FIFO"
21555 );
21556 assert!(executor.run_one(), "the flushed runnable must still run");
21557 assert!(
21558 ran.load(Ordering::SeqCst),
21559 "the flushed task must complete, not be dropped"
21560 );
21561 executor.active_drainers.store(0, Ordering::SeqCst);
21562 }
21563
21564 #[cfg(napi_runtime_os_threads)]
21565 #[test]
21566 fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
21567 let options = RuntimeOptions {
21572 flavor: RuntimeFlavor::MultiThread,
21573 worker_threads: 2,
21574 max_blocking_tasks: 2,
21575 thread_name_prefix: "lifo-owner".to_string(),
21576 park_deadline: None,
21577 drain_linger: DEFAULT_DRAIN_LINGER,
21578 };
21579 let executor =
21580 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21581 let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
21582
21583 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21584
21585 let observed_slot = Arc::clone(&observed);
21589 let sched = Arc::clone(&executor);
21590 let (runnable, task) = async_task::spawn(
21591 async move {
21592 *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21593 },
21594 move |r| sched.schedule(r),
21595 );
21596 executor.schedule(runnable);
21597 task.detach();
21598 assert!(
21599 executor.injector.is_empty(),
21600 "the runnable must sit in the slot"
21601 );
21602
21603 let token = executor.fresh_owner_token();
21607 let _owner = BlockingOwnerGuard::enter(Some(token));
21608 assert!(executor.run_one(), "the slot runnable must run");
21609 assert_eq!(
21610 *observed.lock().unwrap(),
21611 Some(None),
21612 "a slot runnable must observe a CLEARED blocking owner"
21613 );
21614 assert_eq!(
21615 BLOCKING_OWNER.with(std::cell::Cell::get),
21616 Some(token),
21617 "the ambient owner frame must be restored after the slot runnable"
21618 );
21619 }
21620
21621 #[cfg(napi_runtime_os_threads)]
21627 struct PingPongShared {
21628 count: AtomicU64,
21629 stop: AtomicBool,
21630 stop_after: u64,
21631 wakers: Mutex<[Option<std::task::Waker>; 2]>,
21632 }
21633
21634 #[cfg(napi_runtime_os_threads)]
21635 struct PingPong {
21636 shared: Arc<PingPongShared>,
21637 me: usize,
21638 }
21639
21640 #[cfg(napi_runtime_os_threads)]
21641 impl Future for PingPong {
21642 type Output = ();
21643 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21644 let shared = &self.shared;
21645 let polls = shared.count.fetch_add(1, Ordering::SeqCst);
21646 if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
21647 let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
21648 if let Some(partner) = partner {
21649 partner.wake();
21650 }
21651 return Poll::Ready(());
21652 }
21653 let mut wakers = shared.wakers.lock().unwrap();
21654 wakers[self.me] = Some(cx.waker().clone());
21655 if let Some(partner) = wakers[1 - self.me].take() {
21656 drop(wakers);
21657 partner.wake();
21658 }
21659 Poll::Pending
21660 }
21661 }
21662
21663 #[cfg(napi_runtime_os_threads)]
21664 fn spawn_ping_pong_pair(
21665 executor: &Arc<MultiThreadExecutor>,
21666 shared: &Arc<PingPongShared>,
21667 ) -> Vec<Task<()>> {
21668 let mut tasks = Vec::new();
21669 for me in 0..2 {
21670 let sched = Arc::clone(executor);
21671 let (runnable, task) = async_task::spawn(
21672 PingPong {
21673 shared: Arc::clone(shared),
21674 me,
21675 },
21676 move |r| sched.schedule(r),
21677 );
21678 executor.schedule(runnable);
21679 tasks.push(task);
21680 }
21681 tasks
21682 }
21683
21684 #[cfg(napi_runtime_os_threads)]
21685 #[test]
21686 fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
21687 use std::sync::mpsc;
21694 use std::time::Duration;
21695
21696 let (done_tx, done_rx) = mpsc::channel();
21697 let runner = std::thread::spawn(move || {
21698 let options = RuntimeOptions {
21699 flavor: RuntimeFlavor::MultiThread,
21700 worker_threads: 1,
21701 max_blocking_tasks: 0,
21702 thread_name_prefix: "lifo-streak".to_string(),
21703 park_deadline: None,
21704 drain_linger: DEFAULT_DRAIN_LINGER,
21705 };
21706 let executor =
21707 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21708
21709 let shared = Arc::new(PingPongShared {
21710 count: AtomicU64::new(0),
21711 stop: AtomicBool::new(false),
21712 stop_after: u64::MAX,
21713 wakers: Mutex::new([None, None]),
21714 });
21715 let pair = spawn_ping_pong_pair(&executor, &shared);
21716
21717 let (t3_tx, t3_rx) = mpsc::channel::<()>();
21719 let sched = Arc::clone(&executor);
21720 let (t3_runnable, t3_task) = async_task::spawn(
21721 async move {
21722 t3_tx.send(()).unwrap();
21723 },
21724 move |r| sched.schedule(r),
21725 );
21726 executor.schedule(t3_runnable);
21727
21728 t3_rx
21729 .recv_timeout(Duration::from_secs(10))
21730 .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
21731 assert!(
21732 shared.count.load(Ordering::SeqCst) >= 32,
21733 "the pair must have been bouncing through the slot before T3 got its turn"
21734 );
21735
21736 shared.stop.store(true, Ordering::SeqCst);
21738 let stored: Vec<_> = shared
21739 .wakers
21740 .lock()
21741 .unwrap()
21742 .iter_mut()
21743 .filter_map(Option::take)
21744 .collect();
21745 for waker in stored {
21746 waker.wake();
21747 }
21748 for task in pair {
21749 futures::executor::block_on(task);
21750 }
21751 futures::executor::block_on(t3_task);
21752 let _ = done_tx.send(());
21753 });
21754
21755 match done_rx.recv_timeout(Duration::from_secs(15)) {
21756 Ok(()) => runner.join().unwrap(),
21757 Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
21758 }
21759 }
21760
21761 #[cfg(napi_runtime_os_threads)]
21762 #[test]
21763 fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
21764 use std::sync::mpsc;
21771 use std::time::Duration;
21772
21773 const BOUNCES: u64 = 2_000; let (done_tx, done_rx) = mpsc::channel();
21776 let runner = std::thread::spawn(move || {
21777 let options = RuntimeOptions {
21778 flavor: RuntimeFlavor::MultiThread,
21779 worker_threads: 1,
21780 max_blocking_tasks: 1,
21781 thread_name_prefix: "lifo-blocking".to_string(),
21782 park_deadline: None,
21783 drain_linger: DEFAULT_DRAIN_LINGER,
21784 };
21785 let executor =
21786 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21787
21788 let shared = Arc::new(PingPongShared {
21789 count: AtomicU64::new(0),
21790 stop: AtomicBool::new(false),
21791 stop_after: BOUNCES,
21792 wakers: Mutex::new([None, None]),
21793 });
21794 let pair = spawn_ping_pong_pair(&executor, &shared);
21795
21796 let blocking = executor.schedule_blocking(|| 11usize);
21798
21799 assert_eq!(
21800 futures::executor::block_on(blocking).unwrap(),
21801 11,
21802 "the queued blocking job must complete despite the hot LIFO pair"
21803 );
21804 for task in pair {
21805 futures::executor::block_on(task);
21806 }
21807 assert!(
21808 shared.count.load(Ordering::SeqCst) >= BOUNCES,
21809 "the pair must have actually run its full bounce quota"
21810 );
21811 let _ = done_tx.send(());
21812 });
21813
21814 match done_rx.recv_timeout(Duration::from_secs(15)) {
21815 Ok(()) => runner.join().unwrap(),
21816 Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
21817 }
21818 }
21819
21820 #[cfg(napi_runtime_os_threads)]
21821 #[test]
21822 fn infinite_hot_runnable_yields_to_blocking_fifo() {
21823 use std::sync::mpsc;
21824
21825 struct HotFuture {
21826 stop: Arc<AtomicBool>,
21827 }
21828
21829 impl Future for HotFuture {
21830 type Output = ();
21831
21832 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21833 if self.stop.load(Ordering::SeqCst) {
21834 Poll::Ready(())
21835 } else {
21836 cx.waker().wake_by_ref();
21837 Poll::Pending
21838 }
21839 }
21840 }
21841
21842 let options = RuntimeOptions {
21843 flavor: RuntimeFlavor::MultiThread,
21844 worker_threads: 2,
21845 max_blocking_tasks: 1,
21846 thread_name_prefix: "blocking-fairness".to_string(),
21847 park_deadline: None,
21848 drain_linger: DEFAULT_DRAIN_LINGER,
21849 };
21850 let executor =
21851 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21852 let stop = Arc::new(AtomicBool::new(false));
21853 let scheduler = Arc::clone(&executor);
21854 let (runnable, task) = async_task::spawn(
21855 HotFuture {
21856 stop: Arc::clone(&stop),
21857 },
21858 move |r| scheduler.schedule(r),
21859 );
21860 task.detach();
21861 let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
21862 executor
21863 .blocking_queue
21864 .lock()
21865 .unwrap()
21866 .push(QueuedBlocking {
21867 id: BlockingJobId(u64::MAX),
21868 run: Box::new(move || blocking_tx.send(()).unwrap()),
21869 });
21870
21871 let _driver = OnPoolWorkerGuard::enter(executor.id);
21872 executor.schedule(runnable);
21873 let mut runnable_streak = 0;
21874 for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
21875 assert!(executor.run_one_fair(&mut runnable_streak));
21876 }
21877 blocking_rx
21878 .try_recv()
21879 .expect("an infinite self-waker must yield a bounded quantum to blocking work");
21880
21881 stop.store(true, Ordering::SeqCst);
21882 assert!(executor.run_one_fair(&mut runnable_streak));
21883 }
21884
21885 #[cfg(napi_runtime_os_threads)]
21886 #[test]
21887 fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
21888 use std::sync::mpsc;
21889
21890 struct HotFuture {
21891 stop: Arc<AtomicBool>,
21892 }
21893
21894 impl Future for HotFuture {
21895 type Output = ();
21896
21897 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21898 if self.stop.load(Ordering::SeqCst) {
21899 Poll::Ready(())
21900 } else {
21901 cx.waker().wake_by_ref();
21902 Poll::Pending
21903 }
21904 }
21905 }
21906
21907 let options = RuntimeOptions {
21908 flavor: RuntimeFlavor::MultiThread,
21909 worker_threads: 1,
21910 max_blocking_tasks: 1,
21911 thread_name_prefix: "exact-dependency-fairness".to_string(),
21912 park_deadline: None,
21913 drain_linger: DEFAULT_DRAIN_LINGER,
21914 };
21915 let executor =
21916 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21917 let stop = Arc::new(AtomicBool::new(false));
21918 let (queued_tx, queued_rx) = mpsc::channel();
21919 let (exact_tx, exact_rx) = mpsc::channel();
21920 let owner_executor = Arc::clone(&executor);
21921 let owner_stop = Arc::clone(&stop);
21922 let owner = executor.schedule_blocking(move || {
21923 let scheduler = Arc::clone(&owner_executor);
21924 let hot_stop = Arc::clone(&owner_stop);
21925 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
21926 scheduler.schedule(runnable);
21927 });
21928 owner_executor.schedule(runnable);
21929
21930 let exact_stop = Arc::clone(&owner_stop);
21931 let exact = owner_executor.schedule_blocking(move || {
21932 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
21933 41usize
21934 });
21935 queued_tx.send(()).unwrap();
21936
21937 let mut value = None;
21938 {
21939 let mut future = std::pin::pin!(async {
21940 value = Some(exact.await.unwrap());
21941 hot_task.await;
21942 });
21943 assert_eq!(
21944 owner_executor.block_on(future.as_mut()),
21945 BlockOnOutcome::Completed
21946 );
21947 }
21948 value.unwrap()
21949 });
21950
21951 queued_rx
21952 .recv_timeout(Duration::from_secs(2))
21953 .expect("the exact dependency must be queued behind the saturated owner");
21954 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
21955 stop.store(true, Ordering::SeqCst);
21956 assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
21957 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
21958 assert_eq!(
21959 exact_before_stop,
21960 Some(true),
21961 "a hot local runnable must yield to the exact dependency before it is stopped"
21962 );
21963 }
21964
21965 #[cfg(napi_runtime_os_threads)]
21966 #[test]
21967 fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
21968 use std::sync::mpsc;
21969
21970 struct BlockingPoll {
21971 started: Option<mpsc::Sender<()>>,
21972 released: Arc<(Mutex<bool>, Condvar)>,
21973 }
21974
21975 impl Future for BlockingPoll {
21976 type Output = ();
21977
21978 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21979 if let Some(started) = self.started.take() {
21980 started.send(()).unwrap();
21981 }
21982 let (released, changed) = &*self.released;
21983 let mut released = released
21984 .lock()
21985 .unwrap_or_else(std::sync::PoisonError::into_inner);
21986 while !*released {
21987 released = changed
21988 .wait(released)
21989 .unwrap_or_else(std::sync::PoisonError::into_inner);
21990 }
21991 Poll::Ready(())
21992 }
21993 }
21994
21995 let options = RuntimeOptions {
21996 flavor: RuntimeFlavor::MultiThread,
21997 worker_threads: 3,
21998 max_blocking_tasks: 2,
21999 thread_name_prefix: "exact-before-last-slot".to_string(),
22000 park_deadline: None,
22001 drain_linger: DEFAULT_DRAIN_LINGER,
22002 };
22003 let executor =
22004 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22005
22006 let released = Arc::new((Mutex::new(false), Condvar::new()));
22007 let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
22008 let mut blockers = Vec::new();
22009 for _ in 0..2 {
22010 let scheduler = Arc::clone(&executor);
22011 let blocker_released = Arc::clone(&released);
22012 let started = blocker_started_tx.clone();
22013 let (runnable, task) = async_task::spawn(
22014 BlockingPoll {
22015 started: Some(started),
22016 released: blocker_released,
22017 },
22018 move |runnable| scheduler.schedule(runnable),
22019 );
22020 executor.schedule(runnable);
22021 blockers.push(task);
22022 }
22023 drop(blocker_started_tx);
22024 for _ in 0..2 {
22025 blocker_started_rx
22026 .recv_timeout(Duration::from_secs(2))
22027 .expect("two workers must be gated before the owner starts");
22028 }
22029
22030 let dependency_started = Arc::new(AtomicBool::new(false));
22031 let owner_executor = Arc::clone(&executor);
22032 let owner_dependency_started = Arc::clone(&dependency_started);
22033 let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
22034 let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
22035 let owner_signal = Arc::clone(&dependency_signal);
22036 let owner = executor.schedule_blocking(move || {
22037 let unrelated_signal = Arc::clone(&owner_signal);
22038 let unrelated_started = Arc::clone(&owner_dependency_started);
22039 let unrelated = owner_executor.schedule_blocking(move || {
22040 let observed = unrelated_started.load(Ordering::SeqCst);
22041 unrelated_observed_tx.send(observed).unwrap();
22042 let (ready, changed) = &*unrelated_signal;
22043 let mut ready = ready
22044 .lock()
22045 .unwrap_or_else(std::sync::PoisonError::into_inner);
22046 while !*ready {
22047 ready = changed
22048 .wait(ready)
22049 .unwrap_or_else(std::sync::PoisonError::into_inner);
22050 }
22051 });
22052
22053 let exact_signal = Arc::clone(&owner_signal);
22054 let exact_started = Arc::clone(&owner_dependency_started);
22055 let exact = owner_executor.schedule_blocking(move || {
22056 exact_started.store(true, Ordering::SeqCst);
22057 let (ready, changed) = &*exact_signal;
22058 *ready
22059 .lock()
22060 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22061 changed.notify_all();
22062 41usize
22063 });
22064
22065 let mut output = None;
22066 {
22067 let mut future = std::pin::pin!(async {
22068 output = Some(exact.await.unwrap());
22069 unrelated.await.unwrap();
22070 });
22071 assert_eq!(
22072 owner_executor.block_on(future.as_mut()),
22073 BlockOnOutcome::Completed
22074 );
22075 }
22076 output.unwrap()
22077 });
22078
22079 let (owner_done_tx, owner_done_rx) = mpsc::channel();
22080 let owner_waiter = std::thread::spawn(move || {
22081 owner_done_tx
22082 .send(futures::executor::block_on(owner))
22083 .unwrap();
22084 });
22085 let (completed_while_gated, owner_result) =
22086 match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
22087 Ok(result) => (true, result),
22088 Err(_) => {
22089 let (released, changed) = &*released;
22092 *released
22093 .lock()
22094 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22095 changed.notify_all();
22096 (
22097 false,
22098 owner_done_rx
22099 .recv_timeout(Duration::from_secs(2))
22100 .expect("releasing the other workers must let the broken ordering recover"),
22101 )
22102 }
22103 };
22104 let unrelated_observed = unrelated_observed_rx
22105 .recv_timeout(Duration::from_secs(2))
22106 .expect("the unrelated FIFO job must eventually run");
22107
22108 {
22109 let (released, changed) = &*released;
22110 *released
22111 .lock()
22112 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22113 changed.notify_all();
22114 }
22115 owner_waiter.join().unwrap();
22116 for blocker in blockers {
22117 futures::executor::block_on(blocker);
22118 }
22119 executor.begin_shutdown();
22120 executor.wait_until_scheduler_idle();
22121
22122 assert!(
22123 completed_while_gated,
22124 "the exact dependency must complete while unrelated workers remain gated"
22125 );
22126 assert_eq!(owner_result.unwrap(), 41);
22127 assert!(
22128 unrelated_observed,
22129 "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
22130 );
22131 }
22132
22133 #[cfg(napi_runtime_os_threads)]
22134 #[test]
22135 fn blocking_saturation_preserves_a_runnable_execution_lane() {
22136 use std::{sync::mpsc, time::Duration};
22137
22138 let options = RuntimeOptions {
22139 flavor: RuntimeFlavor::MultiThread,
22140 worker_threads: 2,
22141 max_blocking_tasks: 2,
22142 thread_name_prefix: "blocking-reserve".to_string(),
22143 park_deadline: None,
22144 drain_linger: DEFAULT_DRAIN_LINGER,
22145 }
22146 .validate()
22147 .expect("production options must reserve a runnable lane");
22148 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
22149 let executor =
22150 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22151 let (release_tx, release_rx) = mpsc::channel();
22152 let (started_tx, started_rx) = mpsc::channel();
22153 let first = executor.schedule_blocking(move || {
22154 started_tx.send(()).unwrap();
22155 release_rx.recv().unwrap();
22156 });
22157 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
22158
22159 let (queued_tx, queued_rx) = mpsc::channel();
22160 let second = executor.schedule_blocking(move || {
22161 queued_tx.send(()).unwrap();
22162 });
22163 assert!(
22164 queued_rx.try_recv().is_err(),
22165 "the second blocking job must remain queued while the sole blocking slot is occupied"
22166 );
22167
22168 let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
22169 let scheduler = Arc::clone(&executor);
22170 let (runnable, task) = async_task::spawn(
22171 async move {
22172 runnable_tx.send(()).unwrap();
22173 },
22174 move |r| scheduler.schedule(r),
22175 );
22176 task.detach();
22177 executor.schedule(runnable);
22178 runnable_rx
22179 .recv_timeout(Duration::from_secs(1))
22180 .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
22181
22182 release_tx.send(()).unwrap();
22183 futures::executor::block_on(first).unwrap();
22184 futures::executor::block_on(second).unwrap();
22185 queued_rx
22186 .recv_timeout(Duration::from_secs(1))
22187 .expect("queued blocking work must run after the occupied slot is released");
22188 }
22189
22190 #[cfg(napi_runtime_os_threads)]
22191 #[test]
22192 fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
22193 use std::sync::mpsc;
22201 use std::time::Duration;
22202
22203 struct SpawnOnReady {
22204 executor: Arc<MultiThreadExecutor>,
22205 child_tx: Option<mpsc::Sender<u64>>,
22206 }
22207 impl Future for SpawnOnReady {
22208 type Output = ();
22209 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22210 let child_tx = self.child_tx.take().expect("polled once");
22211 let sched = Arc::clone(&self.executor);
22212 let (runnable, task) = async_task::spawn(
22213 async move {
22214 child_tx.send(21).unwrap();
22215 },
22216 move |r| sched.schedule(r),
22217 );
22218 self.executor.schedule(runnable);
22220 task.detach();
22221 Poll::Ready(())
22222 }
22223 }
22224
22225 let (done_tx, done_rx) = mpsc::channel();
22226 let runner = std::thread::spawn(move || {
22227 let options = RuntimeOptions {
22228 flavor: RuntimeFlavor::MultiThread,
22229 worker_threads: 2,
22230 max_blocking_tasks: 2,
22231 thread_name_prefix: "exit-flush".to_string(),
22232 park_deadline: None,
22233 drain_linger: DEFAULT_DRAIN_LINGER,
22234 };
22235 let executor =
22236 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22237
22238 let (child_tx, child_rx) = mpsc::channel::<u64>();
22239 {
22240 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
22243 let mut future = std::pin::pin!(SpawnOnReady {
22244 executor: Arc::clone(&executor),
22245 child_tx: Some(child_tx)
22246 });
22247 executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
22248 }
22249
22250 let value = child_rx
22254 .recv_timeout(Duration::from_secs(5))
22255 .expect("the child scheduled by the final poll was stranded in the LIFO slot");
22256 assert_eq!(value, 21);
22257 let _ = done_tx.send(());
22258 });
22259
22260 match done_rx.recv_timeout(Duration::from_secs(10)) {
22261 Ok(()) => runner.join().unwrap(),
22262 Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
22263 }
22264 }
22265
22266 #[cfg(napi_runtime_os_threads)]
22270 fn spawn_hot_chain(
22271 executor: &Arc<MultiThreadExecutor>,
22272 stop: Arc<AtomicBool>,
22273 hops: Arc<AtomicU64>,
22274 ) {
22275 if stop.load(Ordering::SeqCst) {
22276 return;
22277 }
22278 let link_exec = Arc::clone(executor);
22279 let body = async move {
22280 hops.fetch_add(1, Ordering::SeqCst);
22281 spawn_hot_chain(&link_exec, stop, hops);
22282 };
22283 let sched = Arc::clone(executor);
22284 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
22285 executor.schedule(runnable);
22286 task.detach();
22287 }
22288
22289 #[cfg(napi_runtime_os_threads)]
22290 #[test]
22291 fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
22292 use std::sync::mpsc;
22301 use std::time::Duration;
22302
22303 let (done_tx, done_rx) = mpsc::channel();
22304 let runner = std::thread::spawn(move || {
22305 let options = RuntimeOptions {
22306 flavor: RuntimeFlavor::MultiThread,
22307 worker_threads: 1,
22308 max_blocking_tasks: 0,
22311 thread_name_prefix: "coop-budget".to_string(),
22312 park_deadline: None,
22313 drain_linger: DEFAULT_DRAIN_LINGER,
22314 };
22315 let executor =
22316 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22317
22318 let stop = Arc::new(AtomicBool::new(false));
22319 let hops = Arc::new(AtomicU64::new(0));
22320
22321 let m_exec = Arc::clone(&executor);
22325 let m_stop = Arc::clone(&stop);
22326 let m_hops = Arc::clone(&hops);
22327 let m_body = async move {
22328 let (tx, rx) = oneshot::channel::<u64>();
22329 let sched_f = Arc::clone(&m_exec);
22331 let (f_runnable, f_task) = async_task::spawn(
22332 async move {
22333 let _ = tx.send(7);
22334 },
22335 move |r| sched_f.schedule(r),
22336 );
22337 m_exec.schedule(f_runnable);
22338 f_task.detach();
22339 spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
22342
22343 let mut inner = std::pin::pin!(async move {
22344 assert_eq!(
22345 rx.await.unwrap(),
22346 7,
22347 "F must run and complete the awaited future"
22348 );
22349 });
22350 m_exec.block_on(inner.as_mut());
22351 m_stop.store(true, Ordering::SeqCst);
22352 };
22353 let sched_m = Arc::clone(&executor);
22354 let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
22355 executor.schedule(m_runnable);
22356
22357 futures::executor::block_on(m_task);
22358 assert!(
22359 hops.load(Ordering::SeqCst) >= 64,
22360 "the chain must have been genuinely hot (>= one budget window) before F ran"
22361 );
22362 let _ = done_tx.send(());
22363 });
22364
22365 match done_rx.recv_timeout(Duration::from_secs(10)) {
22366 Ok(()) => runner.join().unwrap(),
22367 Err(error) => panic!(
22368 "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
22369 ),
22370 }
22371 }
22372
22373 #[cfg(napi_runtime_os_threads)]
22374 #[test]
22375 fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
22376 use std::sync::mpsc;
22377
22378 struct RefillLifo {
22379 executor: Arc<MultiThreadExecutor>,
22380 fifo_ran: Arc<AtomicBool>,
22381 }
22382
22383 impl Future for RefillLifo {
22384 type Output = ();
22385
22386 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22387 if self.fifo_ran.load(Ordering::SeqCst) {
22388 return Poll::Ready(());
22389 }
22390 let scheduler = Arc::clone(&self.executor);
22391 let (runnable, task) =
22392 async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
22393 self.executor.schedule(runnable);
22394 task.detach();
22395 Poll::Pending
22396 }
22397 }
22398
22399 let (done_tx, done_rx) = mpsc::channel();
22400 let runner = std::thread::spawn(move || {
22401 let options = RuntimeOptions {
22402 flavor: RuntimeFlavor::MultiThread,
22403 worker_threads: 1,
22404 max_blocking_tasks: 0,
22405 thread_name_prefix: "forced-fifo".to_string(),
22406 park_deadline: None,
22407 drain_linger: DEFAULT_DRAIN_LINGER,
22408 };
22409 let executor =
22410 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22411 executor
22412 .active_drainers
22413 .store(executor.max_drainers, Ordering::SeqCst);
22414
22415 let fifo_ran = Arc::new(AtomicBool::new(false));
22416 let fifo_flag = Arc::clone(&fifo_ran);
22417 let scheduler = Arc::clone(&executor);
22418 let (fifo, task) = async_task::spawn(
22419 async move {
22420 fifo_flag.store(true, Ordering::SeqCst);
22421 },
22422 move |runnable| scheduler.schedule(runnable),
22423 );
22424 task.detach();
22425 executor.metrics.runnable_scheduled();
22426 executor.push_fifo_raw(fifo);
22427
22428 {
22429 let _driver = OnPoolWorkerGuard::enter(executor.id);
22430 let mut future = std::pin::pin!(RefillLifo {
22431 executor: Arc::clone(&executor),
22432 fifo_ran: Arc::clone(&fifo_ran)
22433 });
22434 executor.block_on(future.as_mut());
22435 while executor.run_one() {}
22436 }
22437 executor.active_drainers.store(0, Ordering::SeqCst);
22438 assert!(fifo_ran.load(Ordering::SeqCst));
22439 done_tx.send(()).unwrap();
22440 });
22441 done_rx
22442 .recv_timeout(Duration::from_secs(3))
22443 .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
22444 runner.join().unwrap();
22445 }
22446
22447 #[cfg(napi_runtime_os_threads)]
22448 #[test]
22449 fn blocking_closure_spawn_is_not_stranded_in_slot() {
22450 use std::sync::mpsc;
22457 use std::time::Duration;
22458
22459 let (done_tx, done_rx) = mpsc::channel();
22460 let runner = std::thread::spawn(move || {
22461 let options = RuntimeOptions {
22462 flavor: RuntimeFlavor::MultiThread,
22463 worker_threads: 2,
22464 max_blocking_tasks: 2,
22465 thread_name_prefix: "blk-spawn-wait".to_string(),
22466 park_deadline: None,
22467 drain_linger: DEFAULT_DRAIN_LINGER,
22468 };
22469 let executor =
22470 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22471
22472 let blk_exec = Arc::clone(&executor);
22475 let handle = executor.schedule_blocking(move || {
22476 let (tx, rx) = mpsc::channel::<u64>();
22477 let sched = Arc::clone(&blk_exec);
22478 let (runnable, task) = async_task::spawn(
22479 async move {
22480 tx.send(21).unwrap();
22481 },
22482 move |r| sched.schedule(r),
22483 );
22484 blk_exec.schedule(runnable);
22485 task.detach();
22486 rx.recv_timeout(Duration::from_secs(5))
22487 .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
22488 });
22489 assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
22490
22491 let (v_tx, v_rx) = mpsc::channel::<u64>();
22494 let v_exec = Arc::clone(&executor);
22495 let v_handle = executor.schedule_blocking(move || {
22496 let sched = Arc::clone(&v_exec);
22497 let (runnable, task) = async_task::spawn(
22498 async move {
22499 v_tx.send(42).unwrap();
22500 },
22501 move |r| sched.schedule(r),
22502 );
22503 v_exec.schedule(runnable);
22504 task.detach();
22505 1u64
22506 });
22507 assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
22508 assert_eq!(
22509 v_rx
22510 .recv_timeout(Duration::from_secs(5))
22511 .expect("fire-and-forget child must complete"),
22512 42
22513 );
22514 let _ = done_tx.send(());
22515 });
22516
22517 match done_rx.recv_timeout(Duration::from_secs(15)) {
22518 Ok(()) => runner.join().unwrap(),
22519 Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
22520 }
22521 }
22522
22523 #[cfg(napi_runtime_os_threads)]
22524 #[test]
22525 fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
22526 use std::sync::{Barrier, mpsc};
22532 use std::time::Duration;
22533
22534 let (done_tx, done_rx) = mpsc::channel();
22535 let runner = std::thread::spawn(move || {
22536 let metrics = Arc::new(RuntimeMetrics::default());
22537 let options = RuntimeOptions {
22538 flavor: RuntimeFlavor::MultiThread,
22539 worker_threads: 2,
22540 max_blocking_tasks: 2,
22541 thread_name_prefix: "rd1-nested-blk".to_string(),
22542 park_deadline: None,
22543 drain_linger: DEFAULT_DRAIN_LINGER,
22544 };
22545 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
22546
22547 let outer_count = 2usize; let barrier = Arc::new(Barrier::new(outer_count));
22549
22550 let mut handles = Vec::new();
22551 for _ in 0..outer_count {
22552 let exec = Arc::clone(&executor);
22553 let barrier = Arc::clone(&barrier);
22554 let handle = executor.schedule_blocking(move || {
22555 barrier.wait();
22557 let inner_exec = Arc::clone(&exec);
22558 let mut value = 0usize;
22559 {
22560 let mut inner = std::pin::pin!(async {
22561 value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
22562 });
22563 exec.block_on(inner.as_mut());
22564 }
22565 value
22566 });
22567 handles.push(handle);
22568 }
22569
22570 for handle in handles {
22571 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
22572 }
22573 let _ = done_tx.send(());
22574 });
22575
22576 match done_rx.recv_timeout(Duration::from_secs(10)) {
22577 Ok(()) => runner.join().unwrap(),
22578 Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
22579 }
22580 }
22581
22582 #[cfg(napi_runtime_os_threads)]
22583 #[test]
22584 fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
22585 let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
22586 let owner = executor.fresh_owner_token();
22587 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22588 let job = BlockingJobId(u64::MAX - 5);
22589 let dependency = TaskDependency::default();
22590 dependency.set_owned(BlockingDependency { job, owner: None });
22591 let ran = Arc::new(AtomicBool::new(false));
22592 let job_ran = Arc::clone(&ran);
22593 executor
22594 .blocking_queue
22595 .lock()
22596 .unwrap()
22597 .push(QueuedBlocking {
22598 id: job,
22599 run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
22600 });
22601
22602 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22603 assert!(
22604 !executor.try_owned_blocking_over_cap(&dependency),
22605 "an untagged worker must not infer ancestry from a sole active owner"
22606 );
22607 assert!(!ran.load(Ordering::SeqCst));
22608 assert!(
22609 executor
22610 .blocking_queue
22611 .lock()
22612 .unwrap()
22613 .jobs
22614 .contains_key(&job),
22615 "rejected owner inference must leave the exact job queued"
22616 );
22617 }
22618
22619 #[cfg(napi_runtime_os_threads)]
22620 #[test]
22621 fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
22622 use std::sync::{Barrier, mpsc};
22623
22624 let options = RuntimeOptions {
22625 flavor: RuntimeFlavor::MultiThread,
22626 worker_threads: 2,
22627 max_blocking_tasks: 2,
22628 thread_name_prefix: "exact-owner-frame".to_string(),
22629 park_deadline: None,
22630 drain_linger: DEFAULT_DRAIN_LINGER,
22631 };
22632 let executor =
22633 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22634 let owners_saturated = Arc::new(Barrier::new(3));
22635 let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
22636 let begin_lending = Arc::new(Barrier::new(3));
22637 let release_dependencies = Arc::new(Barrier::new(3));
22638 let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
22639 let mut owners = Vec::new();
22640
22641 for owner_index in 0..2 {
22642 let owner_executor = Arc::clone(&executor);
22643 let owners_saturated = Arc::clone(&owners_saturated);
22644 let dependency_queued_tx = dependency_queued_tx.clone();
22645 let begin_lending = Arc::clone(&begin_lending);
22646 let release_dependencies = Arc::clone(&release_dependencies);
22647 let dependency_started_tx = dependency_started_tx.clone();
22648 owners.push(executor.schedule_blocking(move || {
22649 let owner_thread = std::thread::current().id();
22650 owners_saturated.wait();
22651 let child_started = dependency_started_tx.clone();
22652 let child_release = Arc::clone(&release_dependencies);
22653 let child = owner_executor.schedule_blocking(move || {
22654 assert_eq!(
22655 std::thread::current().id(),
22656 owner_thread,
22657 "an owner frame must execute only its own exact dependency on its lent lane"
22658 );
22659 child_started.send(owner_index).unwrap();
22660 child_release.wait();
22661 owner_index
22662 });
22663 dependency_queued_tx.send(()).unwrap();
22664 begin_lending.wait();
22665
22666 let mut result = None;
22667 {
22668 let mut future = std::pin::pin!(async {
22669 result = Some(child.await.unwrap());
22670 });
22671 assert_eq!(
22672 owner_executor.block_on(future.as_mut()),
22673 BlockOnOutcome::Completed
22674 );
22675 }
22676 result.unwrap()
22677 }));
22678 }
22679 drop(dependency_queued_tx);
22680 drop(dependency_started_tx);
22681 owners_saturated.wait();
22682
22683 for _ in 0..2 {
22684 dependency_queued_rx
22685 .recv_timeout(Duration::from_secs(2))
22686 .expect("both owner-scoped dependencies must be queued while the cap is saturated");
22687 }
22688 let (unrelated_tx, unrelated_rx) = mpsc::channel();
22689 let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
22690 begin_lending.wait();
22691
22692 let mut started = Vec::new();
22693 for _ in 0..2 {
22694 started.push(
22695 dependency_started_rx
22696 .recv_timeout(Duration::from_secs(2))
22697 .expect("each owner must service its own exact dependency"),
22698 );
22699 }
22700 started.sort_unstable();
22701 assert_eq!(started, vec![0, 1]);
22702 assert!(
22703 unrelated_rx.try_recv().is_err(),
22704 "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
22705 );
22706
22707 release_dependencies.wait();
22708 for (owner_index, owner) in owners.into_iter().enumerate() {
22709 assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
22710 }
22711 unrelated_rx
22712 .recv_timeout(Duration::from_secs(2))
22713 .expect("the unrelated job must run after an owner releases a counted lane");
22714 futures::executor::block_on(unrelated).unwrap();
22715 }
22716
22717 #[cfg(napi_runtime_os_threads)]
22718 #[test]
22719 fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
22720 use std::sync::{Mutex, mpsc};
22721
22722 const DEPENDENCY_COUNT: usize = 4;
22723
22724 let options = RuntimeOptions {
22725 flavor: RuntimeFlavor::MultiThread,
22726 worker_threads: 3,
22727 max_blocking_tasks: 1,
22728 thread_name_prefix: "parked-unrelated-worker".to_string(),
22729 park_deadline: None,
22730 drain_linger: DEFAULT_DRAIN_LINGER,
22731 };
22732 let executor =
22733 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22734 let (parked_tx, parked_rx) = mpsc::channel();
22735 let (release_parked_tx, release_parked_rx) = mpsc::channel();
22736 executor.pool.spawn(move || {
22737 parked_tx.send(()).unwrap();
22738 release_parked_rx.recv().unwrap();
22739 });
22740 parked_rx
22741 .recv_timeout(Duration::from_secs(2))
22742 .expect("the unrelated Rayon worker must remain occupied");
22743
22744 let owner_executor = Arc::clone(&executor);
22745 let owner = executor.schedule_blocking(move || {
22746 let owner = BLOCKING_OWNER
22747 .with(std::cell::Cell::get)
22748 .expect("the blocking closure must carry its exact owner frame");
22749 let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
22750 let results = Arc::new(Mutex::new(Vec::new()));
22751 let descendant_results = Arc::clone(&results);
22752 let descendant_executor = Arc::clone(&owner_executor);
22753 rayon::scope(move |scope| {
22754 scope.spawn(move |_| {
22755 assert_eq!(
22756 BLOCKING_OWNER.with(std::cell::Cell::get),
22757 None,
22758 "the parked owner must force its descendant onto the remaining worker"
22759 );
22760 let _owner = BlockingOwnerGuard::enter(Some(owner));
22761 descendant_started_tx.send(()).unwrap();
22762 for value in 0..DEPENDENCY_COUNT {
22763 let handle = descendant_executor.schedule_blocking(move || value);
22764 let mut result = None;
22765 {
22766 let mut future = std::pin::pin!(async {
22767 result = Some(handle.await.unwrap());
22768 });
22769 assert_eq!(
22770 descendant_executor.block_on(future.as_mut()),
22771 BlockOnOutcome::Completed
22772 );
22773 }
22774 descendant_results.lock().unwrap().push(result.unwrap());
22775 }
22776 });
22777 descendant_started_rx
22778 .recv_timeout(Duration::from_secs(2))
22779 .expect("the nested descendant must start on the only unoccupied worker");
22780 });
22781 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22782 });
22783
22784 assert_eq!(
22785 futures::executor::block_on(owner).unwrap(),
22786 (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
22787 );
22788
22789 let (idle_tx, idle_rx) = mpsc::channel();
22790 let idle_executor = Arc::clone(&executor);
22791 let idle_waiter = std::thread::spawn(move || {
22792 idle_executor.begin_shutdown();
22793 idle_executor.wait_until_scheduler_idle();
22794 idle_tx.send(()).unwrap();
22795 });
22796 idle_rx
22797 .recv_timeout(Duration::from_secs(2))
22798 .expect("an unrelated parked worker must not retain owner-probe idle accounting");
22799
22800 release_parked_tx.send(()).unwrap();
22801 idle_waiter.join().unwrap();
22802 }
22803
22804 #[cfg(napi_runtime_os_threads)]
22805 #[test]
22806 fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
22807 use std::sync::{Condvar, Mutex, mpsc};
22808
22809 let options = RuntimeOptions {
22810 flavor: RuntimeFlavor::MultiThread,
22811 worker_threads: 3,
22812 max_blocking_tasks: 1,
22813 thread_name_prefix: "same-owner-rearm".to_string(),
22814 park_deadline: None,
22815 drain_linger: DEFAULT_DRAIN_LINGER,
22816 };
22817 let executor =
22818 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22819 let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
22820 let (job_started_tx, job_started_rx) = mpsc::channel();
22821 let owner_executor = Arc::clone(&executor);
22822 let owner_releases = Arc::clone(&releases);
22823 let owner = executor.schedule_blocking(move || {
22824 let owner = BLOCKING_OWNER
22825 .with(std::cell::Cell::get)
22826 .expect("the blocking closure must carry its exact owner frame");
22827 let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
22828 let results = Arc::new(Mutex::new(Vec::new()));
22829 let scope_results = Arc::clone(&results);
22830 rayon::scope(move |scope| {
22831 for index in 0..2 {
22832 let descendant_ready_tx = descendant_ready_tx.clone();
22833 let descendant_executor = Arc::clone(&owner_executor);
22834 let descendant_results = Arc::clone(&scope_results);
22835 let job_started_tx = job_started_tx.clone();
22836 let releases = Arc::clone(&owner_releases);
22837 scope.spawn(move |_| {
22838 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22839 let _owner = BlockingOwnerGuard::enter(Some(owner));
22840 descendant_ready_tx.send(()).unwrap();
22841 let handle = descendant_executor.schedule_blocking(move || {
22842 job_started_tx.send(index).unwrap();
22843 let (released, changed) = &*releases;
22844 let mut state = released.lock().unwrap();
22845 while !state[index] {
22846 state = changed.wait(state).unwrap();
22847 }
22848 index
22849 });
22850 let mut result = None;
22851 {
22852 let mut future = std::pin::pin!(async {
22853 result = Some(handle.await.unwrap());
22854 });
22855 assert_eq!(
22856 descendant_executor.block_on(future.as_mut()),
22857 BlockOnOutcome::Completed
22858 );
22859 }
22860 descendant_results.lock().unwrap().push(result.unwrap());
22861 });
22862 }
22863 drop(descendant_ready_tx);
22864 for _ in 0..2 {
22865 descendant_ready_rx
22866 .recv_timeout(Duration::from_secs(2))
22867 .expect("both descendants must be stolen before the owner leaves its Rayon scope");
22868 }
22869 });
22870 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22871 });
22872
22873 let first = job_started_rx
22874 .recv_timeout(Duration::from_secs(2))
22875 .expect("one exact dependency must borrow the owner lane");
22876 assert!(
22877 job_started_rx
22878 .recv_timeout(Duration::from_millis(100))
22879 .is_err(),
22880 "one owner frame must not service two dependencies concurrently"
22881 );
22882 let unrelated_parker = Arc::new(DriverParker::default());
22883 executor
22884 .parked_drivers
22885 .register_with_role(&unrelated_parker, true, None);
22886 {
22887 let (released, changed) = &*releases;
22888 released.lock().unwrap()[first] = true;
22889 changed.notify_all();
22890 }
22891
22892 let second = job_started_rx
22893 .recv_timeout(Duration::from_secs(2))
22894 .expect("completion must rearm one parked same-owner dependency");
22895 assert_ne!(second, first);
22896 assert!(
22897 !unrelated_parker.consume_permit(),
22898 "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
22899 );
22900 executor.parked_drivers.deregister(&unrelated_parker);
22901 {
22902 let (released, changed) = &*releases;
22903 released.lock().unwrap()[second] = true;
22904 changed.notify_all();
22905 }
22906
22907 let mut results = futures::executor::block_on(owner).unwrap();
22908 results.sort_unstable();
22909 assert_eq!(results, vec![0, 1]);
22910 }
22911
22912 #[cfg(napi_runtime_os_threads)]
22913 #[test]
22914 fn failed_exact_claim_rearms_parked_same_owner_dependency() {
22915 let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
22916 let owner = executor.fresh_owner_token();
22917 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22918 let failed_dependency = Arc::new(TaskDependency::default());
22919 failed_dependency.set_owned(BlockingDependency {
22920 job: BlockingJobId(u64::MAX - 21),
22921 owner: Some(owner),
22922 });
22923 let parked_dependency = Arc::new(TaskDependency::default());
22924 parked_dependency.set_owned(BlockingDependency {
22925 job: BlockingJobId(u64::MAX - 22),
22926 owner: Some(owner),
22927 });
22928 let parker = Arc::new(DriverParker::default());
22929
22930 let queue = executor.blocking_queue.lock().unwrap();
22931 let claimant_executor = Arc::clone(&executor);
22932 let claimant_dependency = Arc::clone(&failed_dependency);
22933 let claimant = std::thread::spawn(move || {
22934 let _owner = BlockingOwnerGuard::enter(Some(owner));
22935 claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
22936 });
22937 wait_until("the failed claimant to reserve the owner lane", || {
22938 !executor.active_blocking_owners.is_available(owner)
22939 });
22940 executor
22941 .parked_drivers
22942 .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
22943 failed_dependency.clear();
22944 drop(queue);
22945
22946 assert!(
22947 !claimant.join().unwrap(),
22948 "the cancelled dependency must not execute"
22949 );
22950 assert!(
22951 parker.consume_permit(),
22952 "releasing a failed exact claim must hand the owner lane to another parked dependency"
22953 );
22954 }
22955
22956 #[cfg(napi_runtime_os_threads)]
22957 #[test]
22958 fn exact_owner_lending_does_not_start_blocking_after_stop() {
22959 use std::sync::mpsc;
22960
22961 let options = RuntimeOptions {
22962 flavor: RuntimeFlavor::MultiThread,
22963 worker_threads: 2,
22964 max_blocking_tasks: 1,
22965 thread_name_prefix: "stopped-exact-owner".to_string(),
22966 park_deadline: None,
22967 drain_linger: DEFAULT_DRAIN_LINGER,
22968 };
22969 let executor =
22970 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22971 let ran = Arc::new(AtomicBool::new(false));
22972 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
22973 let (release_claim_tx, release_claim_rx) = mpsc::channel();
22974 let owner_executor = Arc::clone(&executor);
22975 let child_ran = Arc::clone(&ran);
22976 let owner = executor.schedule_blocking(move || {
22977 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
22978 *slot.borrow_mut() = Some(Box::new(move || {
22979 claim_entered_tx.send(()).unwrap();
22980 release_claim_rx.recv().unwrap();
22981 }));
22982 });
22983 let child = owner_executor.schedule_blocking(move || {
22984 child_ran.store(true, Ordering::SeqCst);
22985 });
22986 let mut future = std::pin::pin!(async move {
22987 let _ = child.await;
22988 });
22989 owner_executor.block_on(future.as_mut())
22990 });
22991
22992 claim_entered_rx
22993 .recv_timeout(Duration::from_secs(2))
22994 .expect("the cooperative driver must pause before the exact-owner queue claim");
22995 assert_eq!(
22996 executor.blocking_queue.lock().unwrap().jobs.len(),
22997 1,
22998 "the exact dependency must remain queued before stop publication"
22999 );
23000
23001 executor.stop.begin_shutdown();
23004 release_claim_tx.send(()).unwrap();
23005
23006 assert_eq!(
23007 futures::executor::block_on(owner).unwrap(),
23008 BlockOnOutcome::Stopped,
23009 "the cooperative driver must stop instead of executing its queued dependency"
23010 );
23011 assert!(
23012 !ran.load(Ordering::SeqCst),
23013 "exact-owner lending started blocking work after stop"
23014 );
23015 assert_eq!(
23016 executor.blocking_queue.lock().unwrap().jobs.len(),
23017 1,
23018 "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
23019 );
23020
23021 executor.begin_shutdown();
23022 executor.wait_until_scheduler_idle();
23023 assert!(executor.blocking_queue.lock().unwrap().is_empty());
23024 assert!(!ran.load(Ordering::SeqCst));
23025 }
23026
23027 #[cfg(napi_runtime_os_threads)]
23028 #[test]
23029 fn exact_owner_lending_attempts_scale_linearly_with_topology() {
23030 const DEPENDENCY_COUNT: usize = 1024;
23031
23032 let options = RuntimeOptions {
23033 flavor: RuntimeFlavor::MultiThread,
23034 worker_threads: 2,
23035 max_blocking_tasks: 1,
23036 thread_name_prefix: "linear-owner-lending".to_string(),
23037 park_deadline: None,
23038 drain_linger: DEFAULT_DRAIN_LINGER,
23039 };
23040 let executor =
23041 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23042 let completed = Arc::new(AtomicUsize::new(0));
23043
23044 for index in 0..DEPENDENCY_COUNT {
23045 let owner = executor.fresh_owner_token();
23046 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23047 let dependency = TaskDependency::new(u64::MAX, Some(owner));
23048 let blocking = BlockingDependency {
23049 job: BlockingJobId(u64::MAX - index as u64),
23050 owner: Some(owner),
23051 };
23052 dependency.set_owned(blocking);
23053 let completed = Arc::clone(&completed);
23054 executor
23055 .blocking_queue
23056 .lock()
23057 .unwrap()
23058 .push(QueuedBlocking {
23059 id: blocking.job,
23060 run: Box::new(move || {
23061 completed.fetch_add(1, Ordering::SeqCst);
23062 }),
23063 });
23064
23065 let _owner = BlockingOwnerGuard::enter(Some(owner));
23066 assert!(executor.try_owned_blocking_over_cap(&dependency));
23067 }
23068
23069 assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
23070 assert_eq!(
23071 executor.owner_lending_attempts.load(Ordering::Relaxed),
23072 DEPENDENCY_COUNT,
23073 "targeted lending must perform one O(1) claim attempt per exact dependency"
23074 );
23075 let queue = executor.blocking_queue.lock().unwrap();
23076 assert!(queue.is_empty());
23077 assert_eq!(queue.head, None);
23078 assert_eq!(queue.tail, None);
23079 }
23080
23081 #[cfg(napi_runtime_os_threads)]
23082 #[test]
23083 fn one_dependency_interspersed_owner_publications_stays_linear() {
23084 const PUBLICATION_COUNT: usize = 1024;
23085
23086 let executor = multi_thread_executor(2, None, "linear-publication-lending");
23087 let owner = executor.fresh_owner_token();
23088 let wrong_owner = executor.fresh_owner_token();
23089 let dependency = TaskDependency::new(u64::MAX, Some(owner));
23090 let completed = Arc::new(AtomicUsize::new(0));
23091 for index in 0..PUBLICATION_COUNT {
23092 let cancelled_source = TaskDependency::default();
23093 cancelled_source.set_owned(BlockingDependency {
23094 job: BlockingJobId(u64::MAX - 100_000 - index as u64),
23095 owner: Some(owner),
23096 });
23097 dependency.set_borrowed(
23098 &cancelled_source
23099 .get()
23100 .expect("the source publication must initially be live"),
23101 );
23102 cancelled_source.clear();
23103
23104 dependency.set_owned(BlockingDependency {
23105 job: BlockingJobId(u64::MAX - 200_000 - index as u64),
23106 owner: Some(wrong_owner),
23107 });
23108 let blocking = BlockingDependency {
23109 job: BlockingJobId(u64::MAX - 1_000 - index as u64),
23110 owner: Some(owner),
23111 };
23112 dependency.set_owned(blocking);
23113 let completed = Arc::clone(&completed);
23114 executor
23115 .blocking_queue
23116 .lock()
23117 .unwrap()
23118 .push(QueuedBlocking {
23119 id: blocking.job,
23120 run: Box::new(move || {
23121 completed.fetch_add(1, Ordering::SeqCst);
23122 }),
23123 });
23124 }
23125 dependency.reset_entry_visits();
23126
23127 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23128 let _owner = BlockingOwnerGuard::enter(Some(owner));
23129 for _ in 0..PUBLICATION_COUNT {
23130 assert!(
23131 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23132 "the owner-handoff predicate must find the next exact publication"
23133 );
23134 assert!(
23135 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23136 "the park-availability predicate must find the next serviceable publication"
23137 );
23138 assert!(executor.try_owned_blocking_over_cap(&dependency));
23139 }
23140
23141 assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
23142 assert!(
23143 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23144 "wrong-owner publications must not satisfy an exact handoff"
23145 );
23146 assert!(
23147 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23148 "wrong-owner publications must not make the owner's lane appear serviceable"
23149 );
23150 assert!(
23151 dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
23152 "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
23153 dependency.entry_visits()
23154 );
23155 assert!(executor.blocking_queue.lock().unwrap().is_empty());
23156 }
23157
23158 #[cfg(napi_runtime_os_threads)]
23159 #[test]
23160 fn binding_unowned_dependency_updates_exact_owner_predicate() {
23161 let owner = BlockingOwnerToken {
23162 executor_id: u64::MAX - 200,
23163 frame: u64::MAX - 201,
23164 };
23165 let dependency = TaskDependency::default();
23166 let blocking = BlockingDependency {
23167 job: BlockingJobId(u64::MAX - 202),
23168 owner: None,
23169 };
23170 dependency.set_owned(blocking);
23171
23172 assert!(
23173 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23174 "an unowned publication must not satisfy an exact owner handoff"
23175 );
23176 assert!(
23177 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23178 "an unowned publication is available to the ambient owner"
23179 );
23180
23181 let selected = dependency
23182 .select_for_owner(owner)
23183 .expect("the owner must bind the unowned publication");
23184 assert_eq!(selected.publication.dependency.owner, Some(owner));
23185 assert!(
23186 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23187 "binding must immediately publish the entry to exact-owner predicates"
23188 );
23189 assert!(dependency.try_claim(&selected));
23190 assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23191 }
23192
23193 #[cfg(napi_runtime_os_threads)]
23194 #[test]
23195 fn owner_selection_preserves_unowned_and_exact_publication_order() {
23196 let owner = BlockingOwnerToken {
23197 executor_id: u64::MAX - 210,
23198 frame: u64::MAX - 211,
23199 };
23200 let dependency = TaskDependency::default();
23201 let first = BlockingDependency {
23202 job: BlockingJobId(u64::MAX - 212),
23203 owner: None,
23204 };
23205 let second = BlockingDependency {
23206 job: BlockingJobId(u64::MAX - 213),
23207 owner: Some(owner),
23208 };
23209 dependency.set_owned(first);
23210 dependency.set_owned(second);
23211
23212 let selected = dependency
23213 .select_for_owner(owner)
23214 .expect("the first publication must be found");
23215 assert_eq!(
23216 selected.publication.dependency,
23217 BlockingDependency {
23218 owner: Some(owner),
23219 ..first
23220 }
23221 );
23222 assert!(dependency.try_claim(&selected));
23223
23224 let selected = dependency
23225 .select_for_owner(owner)
23226 .expect("the second publication must remain eligible");
23227 assert_eq!(selected.publication.dependency, second);
23228 }
23229
23230 #[cfg(napi_runtime_os_threads)]
23231 #[test]
23232 fn blocking_queue_exact_removal_reclaims_fifo_links() {
23233 const JOB_COUNT: u64 = 4096;
23234
23235 let mut queue = BlockingQueue {
23236 closed: false,
23237 head: None,
23238 tail: None,
23239 jobs: FxHashMap::default(),
23240 };
23241 for id in 1..=JOB_COUNT {
23242 queue.push(QueuedBlocking {
23243 id: BlockingJobId(id),
23244 run: Box::new(|| {}),
23245 });
23246 }
23247 for id in (2..=JOB_COUNT).step_by(2) {
23248 drop(
23249 queue
23250 .remove(BlockingJobId(id))
23251 .expect("even job must remain indexed"),
23252 );
23253 }
23254 for id in (1..=JOB_COUNT).step_by(2) {
23255 drop(
23256 queue
23257 .remove(BlockingJobId(id))
23258 .expect("odd job must remain indexed"),
23259 );
23260 }
23261 assert!(queue.jobs.is_empty());
23262 assert_eq!(queue.head, None);
23263 assert_eq!(queue.tail, None);
23264
23265 let order = Arc::new(Mutex::new(Vec::new()));
23266 for id in 1..=3 {
23267 let order = Arc::clone(&order);
23268 queue.push(QueuedBlocking {
23269 id: BlockingJobId(id),
23270 run: Box::new(move || order.lock().unwrap().push(id)),
23271 });
23272 }
23273 drop(
23274 queue
23275 .remove(BlockingJobId(2))
23276 .expect("middle job must be removable"),
23277 );
23278 queue.pop_front().expect("FIFO head must remain linked")();
23279 queue.pop_front().expect("FIFO tail must remain linked")();
23280 assert!(queue.pop_front().is_none());
23281 assert_eq!(*order.lock().unwrap(), vec![1, 3]);
23282 assert_eq!(queue.head, None);
23283 assert_eq!(queue.tail, None);
23284 }
23285
23286 #[cfg(not(target_family = "wasm"))]
23287 #[test]
23288 fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
23289 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
23290
23291 if std::env::var_os(CHILD_ENV).is_some() {
23292 configure(RuntimeOptions {
23293 flavor: RuntimeFlavor::MultiThread,
23294 worker_threads: 2,
23295 max_blocking_tasks: 1,
23296 thread_name_prefix: "dependency-lending-metrics".to_string(),
23297 park_deadline: None,
23298 drain_linger: DEFAULT_DRAIN_LINGER,
23299 })
23300 .unwrap();
23301
23302 let outer = spawn_blocking(|| {
23303 let inner = spawn_blocking(|| 42usize);
23304 block_on(inner).unwrap()
23305 });
23306 assert_eq!(block_on(outer).unwrap(), 42);
23307
23308 shutdown().unwrap();
23313 let snapshot = metrics();
23314 assert_eq!(snapshot.blocking_tasks_started, 2);
23315 assert_eq!(snapshot.blocking_tasks_completed, 2);
23316 assert_eq!(snapshot.active_blocking_tasks, 0);
23317 assert_eq!(
23318 snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
23319 "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
23320 );
23321 return;
23322 }
23323
23324 let output = std::process::Command::new(std::env::current_exe().unwrap())
23325 .arg("--exact")
23326 .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
23327 .arg("--nocapture")
23328 .env(CHILD_ENV, "1")
23329 .output()
23330 .expect("the dependency-lending metrics subprocess must start");
23331 assert!(
23332 output.status.success(),
23333 "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
23334 output.status.code(),
23335 String::from_utf8_lossy(&output.stdout),
23336 String::from_utf8_lossy(&output.stderr)
23337 );
23338 }
23339
23340 #[cfg(napi_runtime_os_threads)]
23341 #[test]
23342 fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
23343 use std::sync::mpsc;
23344
23345 let (done_tx, done_rx) = mpsc::channel();
23346 let runner = std::thread::spawn(move || {
23347 let options = RuntimeOptions {
23348 flavor: RuntimeFlavor::MultiThread,
23349 worker_threads: 2,
23350 max_blocking_tasks: 1,
23351 thread_name_prefix: "exact-blocking-job".to_string(),
23352 park_deadline: None,
23353 drain_linger: DEFAULT_DRAIN_LINGER,
23354 };
23355 let executor =
23356 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23357 let outer_exec = Arc::clone(&executor);
23358 let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
23359 let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
23360 let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
23361 let outer = executor.schedule_blocking(move || {
23362 let sibling = outer_exec.schedule_blocking(move || {
23363 sibling_started_tx.send(()).unwrap();
23364 release_sibling_rx.recv().unwrap();
23365 });
23366 let awaited = outer_exec.schedule_blocking(move || {
23367 awaited_ran_tx.send(()).unwrap();
23368 17usize
23369 });
23370 let mut value = None;
23371 {
23372 let mut future = std::pin::pin!(async {
23373 value = Some(awaited.await.unwrap());
23374 });
23375 outer_exec.block_on(future.as_mut());
23376 }
23377 (sibling, value.unwrap())
23378 });
23379
23380 awaited_ran_rx
23381 .recv_timeout(Duration::from_secs(2))
23382 .expect("the exact awaited job must bypass the earlier detached sibling");
23383 let (sibling, value) = futures::executor::block_on(outer).unwrap();
23384 assert_eq!(value, 17);
23385 sibling_started_rx
23386 .recv_timeout(Duration::from_secs(2))
23387 .expect("the sibling must run after the owner releases its counted slot");
23388 release_sibling_tx.send(()).unwrap();
23389 futures::executor::block_on(sibling).unwrap();
23390 done_tx.send(()).unwrap();
23391 });
23392 done_rx
23393 .recv_timeout(Duration::from_secs(5))
23394 .expect("same-owner sibling selection deadlocked the exact awaited job");
23395 runner.join().unwrap();
23396 }
23397
23398 #[cfg(napi_runtime_os_threads)]
23399 #[test]
23400 fn blocking_dependency_propagates_through_async_join_lineage() {
23401 use std::sync::mpsc;
23402
23403 let (done_tx, done_rx) = mpsc::channel();
23404 let runner = std::thread::spawn(move || {
23405 let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
23406 let owner_controller = Arc::clone(&controller);
23407 let (child_tx, child_rx) = mpsc::channel();
23408 let outer = controller
23409 .try_spawn_blocking(move || {
23410 let async_controller = Arc::clone(&owner_controller);
23411 let async_child = owner_controller
23412 .try_spawn(async move {
23413 async_controller
23414 .try_spawn_blocking(move || {
23415 child_tx.send(()).unwrap();
23416 23usize
23417 })
23418 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
23419 .await
23420 .unwrap()
23421 })
23422 .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
23423 let mut value = None;
23424 {
23425 let mut future = std::pin::pin!(async {
23426 value = Some(async_child.await.unwrap());
23427 });
23428 owner_controller.block_on(future.as_mut());
23429 }
23430 value.unwrap()
23431 })
23432 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23433
23434 child_rx
23435 .recv_timeout(Duration::from_secs(2))
23436 .expect("the async descendant's exact blocking dependency must inherit the owner lane");
23437 assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
23438 controller.shutdown().unwrap();
23439 done_tx.send(()).unwrap();
23440 });
23441 done_rx
23442 .recv_timeout(Duration::from_secs(5))
23443 .expect("blocking dependency lineage deadlocked");
23444 runner.join().unwrap();
23445 }
23446
23447 #[cfg(napi_runtime_os_threads)]
23448 #[test]
23449 fn blocking_dependency_does_not_escape_a_serviced_runnable() {
23450 use std::sync::mpsc;
23451
23452 let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
23453 let outer_controller = Arc::clone(&controller);
23454 let (child_queued_tx, child_queued_rx) = mpsc::channel();
23455 let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
23456 let (release_outer_tx, release_outer_rx) = oneshot::channel();
23457 let outer = controller
23458 .try_spawn_blocking(move || {
23459 let child_controller = Arc::clone(&outer_controller);
23460 let child = outer_controller
23461 .try_spawn(async move {
23462 let blocking = child_controller
23463 .try_spawn_blocking(move || {
23464 unrelated_ran_tx.send(()).unwrap();
23465 31usize
23466 })
23467 .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
23468 child_queued_tx.send(()).unwrap();
23469 blocking.await.unwrap()
23470 })
23471 .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
23472 let mut released = false;
23473 {
23474 let mut future = std::pin::pin!(async {
23475 release_outer_rx.await.unwrap();
23476 released = true;
23477 });
23478 outer_controller.block_on(future.as_mut());
23479 }
23480 assert!(released);
23481 child
23482 })
23483 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23484
23485 child_queued_rx
23486 .recv_timeout(Duration::from_secs(2))
23487 .expect("the cooperative driver must service the unrelated async runnable");
23488 assert!(
23489 unrelated_ran_rx
23490 .recv_timeout(Duration::from_millis(200))
23491 .is_err(),
23492 "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
23493 );
23494
23495 release_outer_tx.send(()).unwrap();
23496 let child = futures::executor::block_on(outer).unwrap();
23497 unrelated_ran_rx
23498 .recv_timeout(Duration::from_secs(2))
23499 .expect("the unrelated blocking job must run after the owner releases its counted slot");
23500 assert_eq!(futures::executor::block_on(child).unwrap(), 31);
23501 controller.shutdown().unwrap();
23502 }
23503
23504 #[cfg(napi_runtime_os_threads)]
23505 #[test]
23506 fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
23507 let parent = Arc::new(TaskDependency::default());
23508 let _context = TaskDependencyGuard::enter(&parent);
23509 let dependency = BlockingDependency {
23510 job: BlockingJobId(u64::MAX),
23511 owner: None,
23512 };
23513 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23514 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23515 receiver,
23516 dependency,
23517 published: PublishedBlockingDependencies::default(),
23518 });
23519 let waker = futures::task::noop_waker();
23520 let mut cx = Context::from_waker(&waker);
23521
23522 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23523 assert_eq!(parent.get_dependency(), Some(dependency));
23524 drop(handle);
23525 assert_eq!(
23526 parent.get_dependency(),
23527 None,
23528 "an abandoned JoinHandle must clear stale dependency lineage"
23529 );
23530 drop(sender);
23531 }
23532
23533 #[cfg(napi_runtime_os_threads)]
23534 #[test]
23535 fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
23536 let owner = BlockingOwnerToken {
23537 executor_id: u64::MAX - 40,
23538 frame: u64::MAX - 41,
23539 };
23540 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23541 let _context = TaskDependencyGuard::enter(&parent);
23542 let dependency = BlockingDependency {
23543 job: BlockingJobId(u64::MAX - 42),
23544 owner: None,
23545 };
23546 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23547 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23548 receiver,
23549 dependency,
23550 published: PublishedBlockingDependencies::default(),
23551 });
23552 let waker = futures::task::noop_waker();
23553 let mut cx = Context::from_waker(&waker);
23554
23555 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23556 assert_eq!(
23557 parent.get_dependency(),
23558 Some(BlockingDependency {
23559 owner: Some(owner),
23560 ..dependency
23561 }),
23562 "the ambient task owner must enrich an untagged direct blocking dependency"
23563 );
23564 drop(handle);
23565 assert_eq!(
23566 parent.get_dependency(),
23567 None,
23568 "detach cleanup must match the stable job id after owner enrichment"
23569 );
23570 drop(sender);
23571 }
23572
23573 #[cfg(napi_runtime_os_threads)]
23574 #[test]
23575 fn one_poll_preserves_every_observed_blocking_dependency() {
23576 let parent = Arc::new(TaskDependency::default());
23577 let _context = TaskDependencyGuard::enter(&parent);
23578 let first = BlockingDependency {
23579 job: BlockingJobId(u64::MAX - 60),
23580 owner: None,
23581 };
23582 let second = BlockingDependency {
23583 job: BlockingJobId(u64::MAX - 61),
23584 owner: None,
23585 };
23586 let (first_sender, first_receiver) =
23587 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23588 let (second_sender, second_receiver) =
23589 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23590 let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
23591 receiver: first_receiver,
23592 dependency: first,
23593 published: PublishedBlockingDependencies::default(),
23594 });
23595 let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
23596 receiver: second_receiver,
23597 dependency: second,
23598 published: PublishedBlockingDependencies::default(),
23599 });
23600 let waker = futures::task::noop_waker();
23601 let mut cx = Context::from_waker(&waker);
23602
23603 assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
23604 assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
23605 assert_eq!(
23606 parent.get_dependencies(),
23607 vec![first, second],
23608 "publishing a later dependency in one user poll must not replace an earlier dependency"
23609 );
23610
23611 drop(first_handle);
23612 drop(second_handle);
23613 drop(first_sender);
23614 drop(second_sender);
23615 }
23616
23617 #[cfg(napi_runtime_os_threads)]
23618 #[test]
23619 fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
23620 let owner = BlockingOwnerToken {
23621 executor_id: u64::MAX - 900,
23622 frame: u64::MAX - 901,
23623 };
23624 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23625 let _context = TaskDependencyGuard::enter(&parent);
23626 let waker = futures::task::noop_waker();
23627 let mut cx = Context::from_waker(&waker);
23628
23629 let j1 = BlockingDependency {
23630 job: BlockingJobId(u64::MAX - 910),
23631 owner: Some(owner),
23632 };
23633 let j2 = BlockingDependency {
23634 job: BlockingJobId(u64::MAX - 911),
23635 owner: Some(owner),
23636 };
23637
23638 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23639 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23640
23641 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23642 receiver: r1,
23643 dependency: j1,
23644 published: PublishedBlockingDependencies::default(),
23645 });
23646 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23649 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23650 let entries_after_double_poll = parent
23651 .state
23652 .lock()
23653 .unwrap_or_else(std::sync::PoisonError::into_inner)
23654 .current
23655 .len();
23656 drop(h1);
23657
23658 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23659 receiver: r2,
23660 dependency: j2,
23661 published: PublishedBlockingDependencies::default(),
23662 });
23663 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23664
23665 assert_eq!(
23666 entries_after_double_poll, 1,
23667 "a repeat Pending poll must supersede the handle's own previous publication"
23668 );
23669 let selected = parent
23670 .select_for_owner(owner)
23671 .expect("a live dependency must be selectable");
23672 assert_eq!(
23673 selected.publication.dependency.job, j2.job,
23674 "a DETACHED job must not be selected for owner lending ahead of the live one"
23675 );
23676 drop((s1, s2));
23677 }
23678
23679 #[cfg(napi_runtime_os_threads)]
23680 #[test]
23681 fn handle_polled_under_a_nested_frame_retires_both_publications() {
23682 let owner = BlockingOwnerToken {
23683 executor_id: u64::MAX - 920,
23684 frame: u64::MAX - 921,
23685 };
23686 let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23687 let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23688 let waker = futures::task::noop_waker();
23689 let mut cx = Context::from_waker(&waker);
23690
23691 let j1 = BlockingDependency {
23692 job: BlockingJobId(u64::MAX - 930),
23693 owner: Some(owner),
23694 };
23695 let j2 = BlockingDependency {
23696 job: BlockingJobId(u64::MAX - 931),
23697 owner: Some(owner),
23698 };
23699 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23700 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23701 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23702 receiver: r1,
23703 dependency: j1,
23704 published: Default::default(),
23705 });
23706
23707 {
23708 let _outer_ctx = TaskDependencyGuard::enter(&outer);
23709 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23713 {
23714 let _inner_ctx = TaskDependencyGuard::enter(&inner);
23715 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23716 }
23717 drop(h1);
23718 assert!(
23719 outer.get_dependencies().is_empty(),
23720 "detaching must retire the publication made in the outer frame too"
23721 );
23722 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23723 receiver: r2,
23724 dependency: j2,
23725 published: Default::default(),
23726 });
23727 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23728 let selected = outer.select_for_owner(owner).expect("selectable");
23729 assert_eq!(
23730 selected.publication.dependency.job, j2.job,
23731 "a detached job published in an outer frame must not outrank a live one"
23732 );
23733 drop(h2);
23734 }
23735 drop((s1, s2));
23736 }
23737
23738 #[cfg(napi_runtime_os_threads)]
23739 #[test]
23740 fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
23741 const FRAME_COUNT: usize = 64;
23742 let waker = futures::task::noop_waker();
23743 let mut cx = Context::from_waker(&waker);
23744 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23745 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23746 receiver,
23747 dependency: BlockingDependency {
23748 job: BlockingJobId(u64::MAX - 940),
23749 owner: None,
23750 },
23751 published: PublishedBlockingDependencies::default(),
23752 });
23753 let mut frames = Vec::new();
23754 for _ in 0..FRAME_COUNT {
23755 let frame = Arc::new(TaskDependency::default());
23756 frames.push(Arc::downgrade(&frame));
23757 let _context = TaskDependencyGuard::enter(&frame);
23758 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23759 }
23760 let retained = frames
23765 .iter()
23766 .filter(|frame| frame.strong_count() > 0)
23767 .count();
23768 assert_eq!(
23769 retained, 0,
23770 "a completed frame must not be kept alive by a pending handle's publication history"
23771 );
23772 drop(handle);
23773 drop(sender);
23774 }
23775
23776 #[cfg(napi_runtime_os_threads)]
23777 #[test]
23778 fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
23779 let owner = BlockingOwnerToken {
23784 executor_id: u64::MAX - 950,
23785 frame: u64::MAX - 951,
23786 };
23787 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23788 let _context = TaskDependencyGuard::enter(&parent);
23789 let waker = futures::task::noop_waker();
23790 let mut cx = Context::from_waker(&waker);
23791
23792 let j1 = BlockingDependency {
23793 job: BlockingJobId(u64::MAX - 960),
23794 owner: Some(owner),
23795 };
23796 let j2 = BlockingDependency {
23797 job: BlockingJobId(u64::MAX - 961),
23798 owner: Some(owner),
23799 };
23800 let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23801 let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23802 let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23803 receiver: r1,
23804 dependency: j1,
23805 published: PublishedBlockingDependencies::default(),
23806 });
23807 let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23808 receiver: r2,
23809 dependency: j2,
23810 published: PublishedBlockingDependencies::default(),
23811 });
23812
23813 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23814 assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23815 assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23816
23817 let selected = parent
23818 .select_for_owner(owner)
23819 .expect("a live dependency must be selectable");
23820 assert_eq!(
23821 selected.publication.dependency.job, j1.job,
23822 "re-polling a handle must not push its dependency behind one published later"
23823 );
23824 drop((h1, h2, s1, s2));
23825 }
23826
23827 #[cfg(napi_runtime_os_threads)]
23828 #[test]
23829 fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
23830 const FRAME_COUNT: usize = 256;
23837 let waker = futures::task::noop_waker();
23838 let mut cx = Context::from_waker(&waker);
23839 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23840 let mut handle = JoinHandle(JoinHandleInner::Blocking {
23841 receiver,
23842 dependency: BlockingDependency {
23843 job: BlockingJobId(u64::MAX - 970),
23844 owner: None,
23845 },
23846 published: PublishedBlockingDependencies::default(),
23847 });
23848 let mut retained = Vec::new();
23849 for _ in 0..FRAME_COUNT {
23850 let frame = Arc::new(TaskDependency::default());
23851 retained.push(Arc::clone(&frame));
23852 let _context = TaskDependencyGuard::enter(&frame);
23853 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23854 frame.begin_poll();
23856 }
23857 let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
23858 panic!("the handle must still be a blocking handle");
23859 };
23860 let remembered = usize::from(published.first.is_some()) + published.rest.len();
23861 assert!(
23862 remembered <= 2,
23863 "a drained frame must not stay in the publication history, remembered={remembered}"
23864 );
23865 drop(handle);
23866 drop(sender);
23867 }
23868
23869 #[cfg(napi_runtime_os_threads)]
23870 #[test]
23871 fn mass_detach_of_pending_blocking_handles_stays_linear() {
23872 const HANDLE_COUNT: usize = 512;
23883
23884 let parent = Arc::new(TaskDependency::default());
23885 let _context = TaskDependencyGuard::enter(&parent);
23886 let waker = futures::task::noop_waker();
23887 let mut cx = Context::from_waker(&waker);
23888
23889 let mut senders = Vec::with_capacity(HANDLE_COUNT);
23890 let mut handles = Vec::with_capacity(HANDLE_COUNT);
23891 for index in 0..HANDLE_COUNT {
23892 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23893 senders.push(sender);
23894 handles.push(JoinHandle(JoinHandleInner::Blocking {
23895 receiver,
23896 dependency: BlockingDependency {
23897 job: BlockingJobId(u64::MAX - 300_000 - index as u64),
23898 owner: None,
23899 },
23900 published: PublishedBlockingDependencies::default(),
23901 }));
23902 }
23903 for handle in &mut handles {
23904 assert!(Pin::new(handle).poll(&mut cx).is_pending());
23905 }
23906 assert_eq!(
23907 parent
23908 .state
23909 .lock()
23910 .unwrap_or_else(std::sync::PoisonError::into_inner)
23911 .current
23912 .len(),
23913 HANDLE_COUNT,
23914 "every handle must be registered and still pending before the detach"
23915 );
23916
23917 parent.reset_entry_visits();
23918 drop(handles);
23919
23920 assert!(
23921 parent.entry_visits() <= HANDLE_COUNT * 4,
23922 "mass-detach must visit O(n) entries across n handles, visits={}",
23923 parent.entry_visits()
23924 );
23925 assert_eq!(
23926 parent.get_dependency(),
23927 None,
23928 "every detached handle must have retired its own publication"
23929 );
23930 drop(senders);
23931 }
23932
23933 #[cfg(napi_runtime_os_threads)]
23934 #[test]
23935 fn dependency_tls_borrow_is_released_before_reentrant_wake() {
23936 use std::sync::mpsc;
23937
23938 struct ReenterDependencyContext {
23939 dependency: Arc<TaskDependency>,
23940 woke: mpsc::Sender<()>,
23941 }
23942
23943 impl std::task::Wake for ReenterDependencyContext {
23944 fn wake(self: Arc<Self>) {
23945 let _context = TaskDependencyGuard::enter(&self.dependency);
23946 self.woke.send(()).unwrap();
23947 }
23948 }
23949
23950 let parent = Arc::new(TaskDependency::default());
23951 let nested = Arc::new(TaskDependency::default());
23952 let (woke_tx, woke_rx) = mpsc::channel();
23953 let waiter = Waker::from(Arc::new(ReenterDependencyContext {
23954 dependency: Arc::clone(&nested),
23955 woke: woke_tx,
23956 }));
23957 parent.register_waiter(&waiter);
23958 let blocking = BlockingDependency {
23959 job: BlockingJobId(u64::MAX - 62),
23960 owner: None,
23961 };
23962
23963 let _context = TaskDependencyGuard::enter(&parent);
23964 let mut published = PublishedBlockingDependencies::default();
23965 record_blocking_dependency(&mut published, blocking);
23966 woke_rx
23967 .recv_timeout(Duration::from_secs(1))
23968 .expect("dependency notification must permit reentrant TLS context access");
23969
23970 parent.register_waiter(&waiter);
23971 clear_blocking_dependency(published);
23972 woke_rx
23973 .recv_timeout(Duration::from_secs(1))
23974 .expect("dependency clearing must permit reentrant TLS context access");
23975 }
23976
23977 #[cfg(napi_runtime_os_threads)]
23978 #[test]
23979 fn dependency_free_begin_poll_does_not_lock_state() {
23980 use std::sync::mpsc;
23981
23982 let dependency = Arc::new(TaskDependency::default());
23983 let state = dependency.state.lock().unwrap();
23984 let (completed_tx, completed_rx) = mpsc::channel();
23985 let polling_dependency = Arc::clone(&dependency);
23986 let polling = std::thread::spawn(move || {
23987 polling_dependency.begin_poll();
23988 completed_tx.send(()).unwrap();
23989 });
23990
23991 let completed_without_lock = completed_rx
23992 .recv_timeout(Duration::from_millis(200))
23993 .is_ok();
23994 drop(state);
23995 polling.join().unwrap();
23996 assert!(
23997 completed_without_lock,
23998 "a task with no published dependency must skip the dependency-state mutex"
23999 );
24000 }
24001
24002 #[cfg(not(target_family = "wasm"))]
24003 #[test]
24004 fn deep_dependency_liveness_chain_is_iterative() {
24005 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
24006
24007 if std::env::var_os(CHILD_ENV).is_some() {
24008 const DEPTH: usize = 100_000;
24009
24010 let mut links = Vec::with_capacity(DEPTH + 1);
24011 links.push(DependencyLink::root());
24012 for _ in 0..DEPTH {
24013 links.push(DependencyLink::derived(links.last().unwrap()));
24014 }
24015 assert!(links.last().unwrap().is_live());
24016 links[0].cancel();
24017 assert!(!links.last().unwrap().is_live());
24018 return;
24019 }
24020
24021 let output = std::process::Command::new(std::env::current_exe().unwrap())
24022 .arg("--exact")
24023 .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
24024 .arg("--nocapture")
24025 .env(CHILD_ENV, "1")
24026 .output()
24027 .expect("the subprocess test must start");
24028 assert!(
24029 output.status.success(),
24030 "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
24031 output.status.code(),
24032 String::from_utf8_lossy(&output.stdout),
24033 String::from_utf8_lossy(&output.stderr)
24034 );
24035 }
24036
24037 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24038 #[test]
24039 fn task_dependency_set_contains_panicking_waiter() {
24040 struct PanicWake;
24041 impl std::task::Wake for PanicWake {
24042 fn wake(self: Arc<Self>) {
24043 panic!("intentional TaskDependency::set waiter panic");
24044 }
24045 }
24046
24047 let dependency = TaskDependency::default();
24048 let blocking = BlockingDependency {
24049 job: BlockingJobId(u64::MAX),
24050 owner: None,
24051 };
24052 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24053
24054 let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
24055 assert!(
24056 result.is_ok(),
24057 "set notification must not unwind its caller"
24058 );
24059 assert_eq!(
24060 dependency.get_dependency(),
24061 Some(blocking),
24062 "set must commit before notifying its waiter"
24063 );
24064 }
24065
24066 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24067 #[test]
24068 fn task_dependency_clear_contains_panicking_waiter() {
24069 struct PanicWake;
24070 impl std::task::Wake for PanicWake {
24071 fn wake(self: Arc<Self>) {
24072 panic!("intentional TaskDependency::clear waiter panic");
24073 }
24074 }
24075
24076 let dependency = TaskDependency::default();
24077 dependency.set_owned(BlockingDependency {
24078 job: BlockingJobId(u64::MAX),
24079 owner: None,
24080 });
24081 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24082
24083 let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
24084 assert!(
24085 result.is_ok(),
24086 "clear notification must not unwind its caller"
24087 );
24088 assert_eq!(
24089 dependency.get_dependency(),
24090 None,
24091 "clear must commit before notifying its waiter"
24092 );
24093 }
24094
24095 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24096 #[test]
24097 fn task_dependency_clear_if_contains_panicking_waiter() {
24098 struct PanicWake;
24099 impl std::task::Wake for PanicWake {
24100 fn wake(self: Arc<Self>) {
24101 panic!("intentional TaskDependency::clear_if waiter panic");
24102 }
24103 }
24104
24105 let dependency = TaskDependency::default();
24106 let blocking = BlockingDependency {
24107 job: BlockingJobId(u64::MAX),
24108 owner: None,
24109 };
24110 let sequence = dependency.set_owned(blocking);
24111 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24112
24113 let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
24114 assert!(
24115 result.is_ok(),
24116 "retire notification must not unwind its caller"
24117 );
24118 assert_eq!(
24119 dependency.get_dependency(),
24120 None,
24121 "retire must commit before notifying its waiter"
24122 );
24123 }
24124
24125 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24126 #[test]
24127 fn task_dependency_waiter_replacement_contains_panicking_drop() {
24128 use std::sync::mpsc;
24129
24130 const GENERATION: u64 = u64::MAX - 20;
24131
24132 struct PanicOnDropWake {
24133 dropped: mpsc::Sender<Option<u64>>,
24134 }
24135
24136 #[expect(
24137 clippy::manual_noop_waker,
24138 reason = "the test observes the waker payload's Drop"
24139 )]
24140 impl std::task::Wake for PanicOnDropWake {
24141 fn wake(self: Arc<Self>) {}
24142 }
24143
24144 impl Drop for PanicOnDropWake {
24145 fn drop(&mut self) {
24146 self
24147 .dropped
24148 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24149 .unwrap();
24150 panic!("intentional replaced dependency waiter destructor panic");
24151 }
24152 }
24153
24154 let dependency = TaskDependency::new(GENERATION, None);
24155 let (dropped_tx, dropped_rx) = mpsc::channel();
24156 let waiter = Waker::from(Arc::new(PanicOnDropWake {
24157 dropped: dropped_tx,
24158 }));
24159 dependency.register_waiter(&waiter);
24160 drop(waiter);
24161
24162 let replacement = futures::task::noop_waker();
24163 let result = catch_unwind(AssertUnwindSafe(|| {
24164 dependency.register_waiter(&replacement)
24165 }));
24166 assert!(
24167 result.is_ok(),
24168 "replacing a hostile waiter must not unwind registration"
24169 );
24170 assert_eq!(
24171 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24172 Some(GENERATION),
24173 "the replaced waiter must be destroyed under its runtime generation"
24174 );
24175 }
24176
24177 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24178 #[test]
24179 fn task_dependency_retirement_contains_stored_waiter_drop() {
24180 use std::sync::mpsc;
24181
24182 const GENERATION: u64 = u64::MAX - 21;
24183
24184 struct PanicOnDropWake {
24185 dropped: mpsc::Sender<Option<u64>>,
24186 }
24187
24188 #[expect(
24189 clippy::manual_noop_waker,
24190 reason = "the test observes the waker payload's Drop"
24191 )]
24192 impl std::task::Wake for PanicOnDropWake {
24193 fn wake(self: Arc<Self>) {}
24194 }
24195
24196 impl Drop for PanicOnDropWake {
24197 fn drop(&mut self) {
24198 self
24199 .dropped
24200 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24201 .unwrap();
24202 panic!("intentional stored dependency waiter destructor panic");
24203 }
24204 }
24205
24206 let dependency = TaskDependency::new(GENERATION, None);
24207 let (dropped_tx, dropped_rx) = mpsc::channel();
24208 let waiter = Waker::from(Arc::new(PanicOnDropWake {
24209 dropped: dropped_tx,
24210 }));
24211 dependency.register_waiter(&waiter);
24212 drop(waiter);
24213
24214 let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
24215 assert!(
24216 result.is_ok(),
24217 "retiring a hostile stored waiter must not unwind"
24218 );
24219 assert_eq!(
24220 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24221 Some(GENERATION),
24222 "the retained waiter must be destroyed under its runtime generation"
24223 );
24224 }
24225
24226 #[cfg(napi_runtime_os_threads)]
24227 #[test]
24228 fn detaching_task_clears_dependency_waiter_parent_retention() {
24229 struct RetainingWake {
24230 retained_parent: Arc<()>,
24231 }
24232
24233 impl std::task::Wake for RetainingWake {
24234 fn wake(self: Arc<Self>) {
24235 let _ = &self.retained_parent;
24236 }
24237 }
24238
24239 let dependency = Arc::new(TaskDependency::default());
24240 let retained_parent = Arc::new(());
24241 let retained_parent_weak = Arc::downgrade(&retained_parent);
24242 let waiter = Waker::from(Arc::new(RetainingWake {
24243 retained_parent: Arc::clone(&retained_parent),
24244 }));
24245 dependency.register_waiter(&waiter);
24246 drop(waiter);
24247 drop(retained_parent);
24248 assert!(
24249 retained_parent_weak.upgrade().is_some(),
24250 "the stored dependency waiter must retain its parent before detachment"
24251 );
24252
24253 let (runnable, task) = async_task::spawn(
24254 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24255 |_| {},
24256 );
24257 drop(JoinHandle(JoinHandleInner::Task {
24258 task: task.fallible(),
24259 awaiter: None,
24260 dependency: Arc::clone(&dependency),
24261 }));
24262 assert!(
24263 retained_parent_weak.upgrade().is_none(),
24264 "detachment must clear the dependency waiter instead of retaining its parent task"
24265 );
24266 drop(runnable);
24267 }
24268
24269 #[cfg(napi_runtime_os_threads)]
24270 #[test]
24271 fn detaching_task_clears_many_parent_publications_linearly() {
24272 const PUBLICATION_COUNT: usize = 1024;
24273
24274 struct CountWake(Arc<AtomicUsize>);
24275
24276 impl std::task::Wake for CountWake {
24277 fn wake(self: Arc<Self>) {
24278 self.0.fetch_add(1, Ordering::SeqCst);
24279 }
24280 }
24281
24282 fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
24283 dependency
24284 .state
24285 .lock()
24286 .unwrap_or_else(std::sync::PoisonError::into_inner)
24287 .current
24288 .back()
24289 .expect("the sentinel publication must be appended")
24290 .publication
24291 .clone()
24292 }
24293
24294 let owner = BlockingOwnerToken {
24295 executor_id: u64::MAX - 300,
24296 frame: u64::MAX - 301,
24297 };
24298 let other_owner = BlockingOwnerToken {
24299 executor_id: u64::MAX - 302,
24300 frame: u64::MAX - 303,
24301 };
24302 let child = Arc::new(TaskDependency::default());
24303 for index in 0..PUBLICATION_COUNT {
24304 child.set_owned(BlockingDependency {
24305 job: BlockingJobId(1_000_000 + index as u64),
24306 owner: (index % 2 == 0).then_some(owner),
24307 });
24308 }
24309 let child_publications = child.snapshot_all_live();
24310 let source = child_publications[0].clone();
24311
24312 let parent = Arc::new(TaskDependency::default());
24313 let _context = TaskDependencyGuard::enter(&parent);
24314 let (runnable, task) = async_task::spawn(
24315 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24316 |_| {},
24317 );
24318 let mut handle = JoinHandle(JoinHandleInner::Task {
24319 task: task.fallible(),
24320 awaiter: None,
24321 dependency: Arc::clone(&child),
24322 });
24323 let waker = futures::task::noop_waker();
24324 let mut cx = Context::from_waker(&waker);
24325 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24326 assert_eq!(
24327 parent.get_dependencies().len(),
24328 PUBLICATION_COUNT,
24329 "polling the child handle must propagate every current publication"
24330 );
24331
24332 let mut wrong_claim_source = source.clone();
24333 wrong_claim_source.claim = Arc::new(DependencyClaim::new());
24334 parent.set_borrowed(&wrong_claim_source);
24335 let wrong_claim_publication = latest_publication(&parent);
24336
24337 let mut wrong_job_source = source.clone();
24338 wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
24339 parent.set_borrowed(&wrong_job_source);
24340 let wrong_job_publication = latest_publication(&parent);
24341
24342 let wrong_immediate_source = source.derived(None);
24343 parent.set_borrowed(&wrong_immediate_source);
24344 let wrong_immediate_publication = latest_publication(&parent);
24345
24346 let wakes = Arc::new(AtomicUsize::new(0));
24347 parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
24348 parent.reset_entry_visits();
24349 drop(handle);
24350
24351 assert_eq!(
24352 wakes.load(Ordering::SeqCst),
24353 1,
24354 "one batched parent cleanup must emit one waiter notification"
24355 );
24356 assert!(
24357 parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
24358 "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
24359 parent.entry_visits()
24360 );
24361 assert!(
24362 wrong_claim_publication.is_live(),
24363 "claim identity must remain part of the match"
24364 );
24365 assert!(
24366 wrong_job_publication.is_live(),
24367 "job identity must remain part of the match"
24368 );
24369 assert!(
24370 wrong_immediate_publication.is_live(),
24371 "only an exact immediate-parent link may be retired"
24372 );
24373
24374 {
24375 let state = parent
24376 .state
24377 .lock()
24378 .unwrap_or_else(std::sync::PoisonError::into_inner);
24379 assert_eq!(state.current.len(), 3);
24380 assert!(
24387 state
24388 .current
24389 .iter()
24390 .all(|entry| entry.publication.is_live()),
24391 "only the three non-matching sentinels may remain, all still live"
24392 );
24393 assert!(state.unowned_sequences.is_empty());
24394 assert_eq!(state.owner_sequences.len(), 1);
24395 assert_eq!(
24396 state.owner_sequences.get(&owner).map(VecDeque::len),
24397 Some(3)
24398 );
24399 }
24400 assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
24401 assert!(
24402 !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
24403 "cancelled unowned publications must not survive in availability predicates"
24404 );
24405 drop(runnable);
24406 }
24407
24408 #[cfg(not(target_family = "wasm"))]
24409 #[test]
24410 fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
24411 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
24412
24413 if std::env::var_os(CHILD_ENV).is_some() {
24414 struct PanicWake;
24415 impl std::task::Wake for PanicWake {
24416 fn wake(self: Arc<Self>) {
24417 panic!("intentional dependency waker panic");
24418 }
24419 }
24420
24421 struct PanicOnDrop;
24422 impl Drop for PanicOnDrop {
24423 fn drop(&mut self) {
24424 panic!("intentional buffered blocking result destructor panic");
24425 }
24426 }
24427
24428 let parent = Arc::new(TaskDependency::default());
24429 let dependency = BlockingDependency {
24430 job: BlockingJobId(u64::MAX - 1),
24431 owner: None,
24432 };
24433 let sequence = parent.set_owned(dependency);
24437 parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
24438 let _context = TaskDependencyGuard::enter(&parent);
24439 let (sender, receiver) = oneshot::channel();
24440 let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
24441 ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
24442 ));
24443 drop(JoinHandle(JoinHandleInner::Blocking {
24444 receiver,
24445 dependency,
24446 published: PublishedBlockingDependencies {
24447 first: Some(PublishedBlockingDependency {
24448 state: Arc::downgrade(&parent),
24449 sequence,
24450 }),
24451 rest: Vec::new(),
24452 },
24453 }));
24454 assert_eq!(parent.get_dependency(), None);
24455 return;
24456 }
24457
24458 let output = std::process::Command::new(std::env::current_exe().unwrap())
24459 .arg("--exact")
24460 .arg(
24461 "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
24462 )
24463 .arg("--nocapture")
24464 .env(CHILD_ENV, "1")
24465 .output()
24466 .expect("the subprocess test must start");
24467 assert!(
24468 output.status.success(),
24469 "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
24470 output.status.code(),
24471 String::from_utf8_lossy(&output.stdout),
24472 String::from_utf8_lossy(&output.stderr)
24473 );
24474 }
24475
24476 #[cfg(napi_runtime_os_threads)]
24477 #[test]
24478 fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
24479 use std::sync::{Barrier, mpsc};
24488 use std::time::Duration;
24489
24490 let (done_tx, done_rx) = mpsc::channel();
24491 let peak_out = Arc::new(Mutex::new(None));
24492 let peak_slot = Arc::clone(&peak_out);
24493 let runner = std::thread::spawn(move || {
24494 let metrics = Arc::new(RuntimeMetrics::default());
24495 let options = RuntimeOptions {
24496 flavor: RuntimeFlavor::MultiThread,
24497 worker_threads: 4,
24498 max_blocking_tasks: 2,
24499 thread_name_prefix: "rd1-cap".to_string(),
24500 park_deadline: None,
24501 drain_linger: DEFAULT_DRAIN_LINGER,
24502 };
24503 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24504
24505 let holder_count = 2usize; let driver_count = 2usize;
24507
24508 let (held_tx, held_rx) = mpsc::channel();
24510 let release = Arc::new(Barrier::new(holder_count + 1));
24511
24512 let mut holder_handles = Vec::new();
24513 for _ in 0..holder_count {
24514 let held_tx = held_tx.clone();
24515 let release = Arc::clone(&release);
24516 let handle = executor.schedule_blocking(move || {
24517 held_tx.send(()).unwrap();
24518 release.wait();
24519 0usize
24520 });
24521 holder_handles.push(handle);
24522 }
24523 for _ in 0..holder_count {
24525 held_rx.recv().unwrap();
24526 }
24527
24528 let mut driver_tasks = Vec::new();
24531 for _ in 0..driver_count {
24532 let exec = Arc::clone(&executor);
24533 let body = async move {
24534 let inner_exec = Arc::clone(&exec);
24535 let mut inner = std::pin::pin!(async move {
24536 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24537 assert_eq!(v, 5);
24538 });
24539 exec.block_on(inner.as_mut());
24540 };
24541 let scheduler = Arc::clone(&executor);
24542 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
24543 executor.schedule(runnable);
24544 driver_tasks.push(task);
24545 }
24546
24547 std::thread::sleep(Duration::from_millis(300));
24550
24551 release.wait();
24553
24554 for task in driver_tasks {
24555 futures::executor::block_on(task);
24556 }
24557 for handle in holder_handles {
24558 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24559 }
24560
24561 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24562 let _ = done_tx.send(());
24563 });
24564
24565 match done_rx.recv_timeout(Duration::from_secs(10)) {
24566 Ok(()) => runner.join().unwrap(),
24567 Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
24568 }
24569 let peak = peak_out.lock().unwrap().expect("peak captured");
24570 assert!(
24571 peak <= 2,
24572 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24573 );
24574 }
24575
24576 #[cfg(napi_runtime_os_threads)]
24577 #[test]
24578 fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
24579 use std::sync::{Barrier, mpsc};
24595 use std::time::Duration;
24596
24597 let (done_tx, done_rx) = mpsc::channel();
24598 let peak_out = Arc::new(Mutex::new(None));
24599 let peak_slot = Arc::clone(&peak_out);
24600 let runner = std::thread::spawn(move || {
24601 let metrics = Arc::new(RuntimeMetrics::default());
24602 let options = RuntimeOptions {
24603 flavor: RuntimeFlavor::MultiThread,
24604 worker_threads: 2,
24605 max_blocking_tasks: 2,
24606 thread_name_prefix: "rd1-owner-runnable".to_string(),
24607 park_deadline: None,
24608 drain_linger: DEFAULT_DRAIN_LINGER,
24609 };
24610 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24611
24612 let saturated = Arc::new(Barrier::new(2));
24613 let release = Arc::new(Barrier::new(2));
24614
24615 let h2_saturated = Arc::clone(&saturated);
24617 let h2_release = Arc::clone(&release);
24618 let h2 = executor.schedule_blocking(move || {
24619 h2_saturated.wait();
24620 h2_release.wait();
24621 0usize
24622 });
24623
24624 let o_exec = Arc::clone(&executor);
24627 let o_saturated = Arc::clone(&saturated);
24628 let o = executor.schedule_blocking(move || {
24629 o_saturated.wait(); let r_exec = Arc::clone(&o_exec);
24632 let body = async move {
24633 let inner_exec = Arc::clone(&r_exec);
24634 let mut inner = std::pin::pin!(async move {
24635 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24636 assert_eq!(v, 5);
24637 });
24638 r_exec.block_on(inner.as_mut());
24639 };
24640 let sched = Arc::clone(&o_exec);
24641 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
24642 o_exec.schedule(runnable);
24643
24644 let mut wait_r = std::pin::pin!(async move {
24645 task.await;
24646 });
24647 o_exec.block_on(wait_r.as_mut());
24648 1usize
24649 });
24650
24651 std::thread::sleep(Duration::from_millis(300));
24654 release.wait();
24656
24657 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24658 assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
24659
24660 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24661 let _ = done_tx.send(());
24662 });
24663
24664 match done_rx.recv_timeout(Duration::from_secs(10)) {
24665 Ok(()) => runner.join().unwrap(),
24666 Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
24667 }
24668 let peak = peak_out.lock().unwrap().expect("peak captured");
24669 assert!(
24670 peak <= 2,
24671 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24672 );
24673 }
24674
24675 #[cfg(napi_runtime_os_threads)]
24676 #[test]
24677 fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
24678 use std::sync::{Barrier, mpsc};
24687
24688 let (done_tx, done_rx) = mpsc::channel();
24689 let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
24690 let order_read = Arc::clone(&order_out);
24691 let runner = std::thread::spawn(move || {
24692 let metrics = Arc::new(RuntimeMetrics::default());
24693 let options = RuntimeOptions {
24694 flavor: RuntimeFlavor::MultiThread,
24695 worker_threads: 2,
24696 max_blocking_tasks: 2,
24697 thread_name_prefix: "rd1-unrelated".to_string(),
24698 park_deadline: None,
24699 drain_linger: DEFAULT_DRAIN_LINGER,
24700 };
24701 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24702
24703 let (held_tx, held_rx) = mpsc::channel();
24706 let u_ready = Arc::new(Barrier::new(2)); let h_release = Arc::new(Barrier::new(2)); let h_held = held_tx.clone();
24711 let h_rel = Arc::clone(&h_release);
24712 let h = executor.schedule_blocking(move || {
24713 h_held.send(()).unwrap();
24714 h_rel.wait();
24715 0usize
24716 });
24717
24718 let o_exec = Arc::clone(&executor);
24721 let o_ready = Arc::clone(&u_ready);
24722 let o_order = Arc::clone(&order_out);
24723 let o = executor.schedule_blocking(move || {
24724 held_tx.send(()).unwrap();
24725 o_ready.wait(); let inner_exec = Arc::clone(&o_exec);
24727 let d_order = Arc::clone(&o_order);
24728 let mut inner = std::pin::pin!(async move {
24729 let v = inner_exec
24730 .schedule_blocking(move || {
24731 d_order.lock().unwrap().push("D");
24732 5usize
24733 })
24734 .await
24735 .unwrap();
24736 assert_eq!(v, 5);
24737 });
24738 o_exec.block_on(inner.as_mut());
24739 1usize
24740 });
24741
24742 held_rx.recv().unwrap();
24744 held_rx.recv().unwrap();
24745
24746 let u_order = Arc::clone(&order_out);
24748 let u = executor.schedule_blocking(move || {
24749 u_order.lock().unwrap().push("U");
24750 2usize
24751 });
24752
24753 u_ready.wait();
24755
24756 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24758 assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
24759
24760 h_release.wait();
24762 assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
24763
24764 let _ = done_tx.send(());
24765 });
24766
24767 match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
24768 Ok(()) => runner.join().unwrap(),
24769 Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
24770 }
24771 let order = order_read.lock().unwrap().clone();
24772 assert_eq!(
24773 order,
24774 vec!["D", "U"],
24775 "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
24776 );
24777 }
24778
24779 #[cfg(napi_runtime_os_threads)]
24780 #[test]
24781 fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
24782 use std::sync::{Barrier, mpsc};
24783
24784 let opts = RuntimeOptions {
24785 flavor: RuntimeFlavor::MultiThread,
24786 worker_threads: 2,
24787 max_blocking_tasks: 2,
24788 thread_name_prefix: "rd1-scope".to_string(),
24789 park_deadline: None,
24790 drain_linger: DEFAULT_DRAIN_LINGER,
24791 };
24792 let exec1 =
24793 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24794 let exec2 =
24795 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24796 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
24797
24798 let holder_count = opts.max_blocking_tasks;
24799 let (held_tx, held_rx) = mpsc::channel();
24800 let release = Arc::new(Barrier::new(holder_count + 1));
24801 let mut holder_handles = Vec::new();
24802 for _ in 0..holder_count {
24803 let held_tx = held_tx.clone();
24804 let release = Arc::clone(&release);
24805 holder_handles.push(exec2.schedule_blocking(move || {
24806 held_tx.send(()).unwrap();
24807 release.wait();
24808 0usize
24809 }));
24810 }
24811 for _ in 0..holder_count {
24812 held_rx.recv().unwrap();
24813 }
24814 assert_eq!(
24815 exec2.active_blocking.load(Ordering::Acquire),
24816 opts.max_blocking_tasks
24817 );
24818
24819 let exact = BlockingJobId(u64::MAX);
24820 let ran = Arc::new(AtomicBool::new(false));
24821 let ran_job = Arc::clone(&ran);
24822 exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
24823 id: exact,
24824 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24825 });
24826 let dependency = TaskDependency::default();
24827
24828 let token1 = exec1.fresh_owner_token();
24829 {
24830 let _owner = BlockingOwnerGuard::enter(Some(token1));
24831 dependency.set_owned(BlockingDependency {
24832 job: exact,
24833 owner: Some(token1),
24834 });
24835 assert!(
24836 !exec2.try_owned_blocking_over_cap(&dependency),
24837 "a foreign-executor ambient token must fail the executor gate"
24838 );
24839 }
24840
24841 let token2 = exec2.fresh_owner_token();
24842 {
24843 let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
24844 let _owner = BlockingOwnerGuard::enter(Some(token2));
24845 dependency.set_owned(BlockingDependency {
24846 job: BlockingJobId(u64::MAX - 1),
24847 owner: Some(token2),
24848 });
24849 assert!(
24850 !exec2.try_owned_blocking_over_cap(&dependency),
24851 "a foreign or unrelated dependency id must fail the exact-job gate"
24852 );
24853 dependency.set_owned(BlockingDependency {
24854 job: BlockingJobId(exact.0 - 2),
24855 owner: Some(token2),
24856 });
24857 assert!(
24858 !exec2.try_owned_blocking_over_cap(&dependency),
24859 "a different job id must not consume the queued dependency"
24860 );
24861 dependency.set_owned(BlockingDependency {
24862 job: exact,
24863 owner: Some(token2),
24864 });
24865 assert!(
24866 exec2.try_owned_blocking_over_cap(&dependency),
24867 "the exact awaited job must run over cap"
24868 );
24869 }
24870 assert!(
24871 ran.load(Ordering::SeqCst),
24872 "the exact dependency must have run"
24873 );
24874
24875 release.wait();
24876 for handle in holder_handles {
24877 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24878 }
24879 }
24880
24881 #[cfg(napi_runtime_os_threads)]
24882 #[test]
24883 fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
24884 let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
24885 let first_owner = executor.fresh_owner_token();
24886 let second_owner = executor.fresh_owner_token();
24887 let _first_owner =
24888 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
24889 let _second_owner =
24890 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
24891 let exact = BlockingJobId(u64::MAX - 30);
24892 let dependency = TaskDependency::default();
24893 dependency.set_owned(BlockingDependency {
24894 job: exact,
24895 owner: None,
24896 });
24897 let ran = Arc::new(AtomicBool::new(false));
24898 let ran_job = Arc::clone(&ran);
24899 executor
24900 .blocking_queue
24901 .lock()
24902 .unwrap()
24903 .push(QueuedBlocking {
24904 id: exact,
24905 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24906 });
24907
24908 assert!(
24909 !executor.try_owned_blocking_over_cap(&dependency),
24910 "an untagged driver must not choose between multiple active owner frames"
24911 );
24912 assert!(!ran.load(Ordering::SeqCst));
24913 let queued = executor.blocking_queue.lock().unwrap().remove(exact);
24914 assert!(
24915 queued.is_some(),
24916 "ambiguous ownership must leave the exact job queued"
24917 );
24918 }
24919
24920 #[cfg(napi_runtime_os_threads)]
24921 #[test]
24922 fn owner_lending_claims_live_dependency_not_stale_snapshot() {
24923 let executor = multi_thread_executor(2, None, "live-parked-dependency");
24924 let owner = executor.fresh_owner_token();
24925 let dependency = TaskDependency::new(u64::MAX, Some(owner));
24926 let stale = BlockingJobId(u64::MAX - 10);
24927 let live = BlockingJobId(u64::MAX - 9);
24928 dependency.set_owned(BlockingDependency {
24929 job: stale,
24930 owner: Some(owner),
24931 });
24932 dependency.begin_poll();
24933 dependency.set_owned(BlockingDependency {
24934 job: live,
24935 owner: Some(owner),
24936 });
24937
24938 let stale_ran = Arc::new(AtomicBool::new(false));
24939 let stale_job_ran = Arc::clone(&stale_ran);
24940 let live_ran = Arc::new(AtomicBool::new(false));
24941 let live_job_ran = Arc::clone(&live_ran);
24942 {
24943 let mut queue = executor
24944 .blocking_queue
24945 .lock()
24946 .unwrap_or_else(std::sync::PoisonError::into_inner);
24947 queue.push(QueuedBlocking {
24948 id: stale,
24949 run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
24950 });
24951 queue.push(QueuedBlocking {
24952 id: live,
24953 run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
24954 });
24955 }
24956
24957 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24958 let _owner = BlockingOwnerGuard::enter(Some(owner));
24959 assert!(
24960 executor.try_owned_blocking_over_cap(&dependency),
24961 "the owner lane must claim the currently published dependency"
24962 );
24963 assert!(
24964 live_ran.load(Ordering::SeqCst),
24965 "the live dependency must run"
24966 );
24967 assert!(
24968 !stale_ran.load(Ordering::SeqCst),
24969 "the stale registered id must remain queued"
24970 );
24971 let remaining = executor
24972 .blocking_queue
24973 .lock()
24974 .unwrap_or_else(std::sync::PoisonError::into_inner)
24975 .jobs
24976 .keys()
24977 .copied()
24978 .collect::<Vec<_>>();
24979 assert_eq!(remaining, vec![stale]);
24980 }
24981
24982 #[cfg(napi_runtime_os_threads)]
24983 #[test]
24984 fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
24985 let executor = multi_thread_executor(2, None, "cancelled-child-publication");
24986 let owner = executor.fresh_owner_token();
24987 let child = TaskDependency::new_task(u64::MAX);
24988 let parent = TaskDependency::new(u64::MAX, Some(owner));
24989 let exact = BlockingDependency {
24990 job: BlockingJobId(u64::MAX - 31),
24991 owner: Some(owner),
24992 };
24993
24994 child.set_owned(exact);
24995 let stale = child
24996 .get()
24997 .expect("the child must publish its direct dependency");
24998 child.begin_poll();
24999 parent.set_borrowed(&stale);
25000
25001 let ran = Arc::new(AtomicBool::new(false));
25002 let ran_job = Arc::clone(&ran);
25003 executor
25004 .blocking_queue
25005 .lock()
25006 .unwrap()
25007 .push(QueuedBlocking {
25008 id: exact.job,
25009 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25010 });
25011 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25012 let _owner = BlockingOwnerGuard::enter(Some(owner));
25013 assert!(
25014 !executor.try_owned_blocking_over_cap(&parent),
25015 "a publication copied before child cancellation must remain cancelled after parent install"
25016 );
25017 assert!(!ran.load(Ordering::SeqCst));
25018
25019 child.set_owned(exact);
25020 parent.set_borrowed(
25021 &child
25022 .get()
25023 .expect("the replacement publication must be live"),
25024 );
25025 assert!(
25026 executor.try_owned_blocking_over_cap(&parent),
25027 "a fresh child publication must still authorize its exact queued job"
25028 );
25029 assert!(ran.load(Ordering::SeqCst));
25030 }
25031
25032 #[cfg(napi_runtime_os_threads)]
25033 #[test]
25034 fn intermediate_repoll_does_not_cancel_child_source_claim() {
25035 let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
25036 let owner = executor.fresh_owner_token();
25037 let child = TaskDependency::new_task(u64::MAX);
25038 let intermediate = TaskDependency::new_task(u64::MAX);
25039 let parent = TaskDependency::new(u64::MAX, Some(owner));
25040 let exact = BlockingDependency {
25041 job: BlockingJobId(u64::MAX - 43),
25042 owner: Some(owner),
25043 };
25044
25045 child.set_owned(exact);
25046 intermediate.set_borrowed(&child.get().expect("the child source must be live"));
25047 parent.set_borrowed(
25048 &intermediate
25049 .get()
25050 .expect("the intermediate publication must be live"),
25051 );
25052
25053 intermediate.begin_poll();
25054 assert!(
25055 child.get().is_some(),
25056 "withdrawing an intermediate hop must not cancel the child's source claim"
25057 );
25058 assert!(
25059 parent.get().is_none(),
25060 "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
25061 );
25062
25063 intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
25064 parent.set_borrowed(
25065 &intermediate
25066 .get()
25067 .expect("the intermediate must republish the still-live source"),
25068 );
25069 let ran = Arc::new(AtomicBool::new(false));
25070 let ran_job = Arc::clone(&ran);
25071 executor
25072 .blocking_queue
25073 .lock()
25074 .unwrap()
25075 .push(QueuedBlocking {
25076 id: exact.job,
25077 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25078 });
25079
25080 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25081 let _owner = BlockingOwnerGuard::enter(Some(owner));
25082 assert!(
25083 executor.try_owned_blocking_over_cap(&parent),
25084 "a fresh chain through the repolled intermediate must retain the exact source claim"
25085 );
25086 assert!(ran.load(Ordering::SeqCst));
25087 }
25088
25089 #[cfg(napi_runtime_os_threads)]
25090 #[test]
25091 fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
25092 use std::sync::mpsc;
25093
25094 let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
25095 let owner = executor.fresh_owner_token();
25096 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25097 let child = Arc::new(TaskDependency::new_task(u64::MAX));
25098 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25099 let exact = BlockingDependency {
25100 job: BlockingJobId(u64::MAX - 44),
25101 owner: Some(owner),
25102 };
25103 child.set_owned(exact);
25104 parent.set_borrowed(&child.get().expect("the child source must be live"));
25105
25106 let ran = Arc::new(AtomicBool::new(false));
25107 let ran_job = Arc::clone(&ran);
25108 executor
25109 .blocking_queue
25110 .lock()
25111 .unwrap()
25112 .push(QueuedBlocking {
25113 id: exact.job,
25114 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25115 });
25116
25117 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
25118 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25119 let claimant_executor = Arc::clone(&executor);
25120 let claimant_parent = Arc::clone(&parent);
25121 let claimant = std::thread::spawn(move || {
25122 DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
25123 *slot.borrow_mut() = Some(Box::new(move || {
25124 claim_entered_tx.send(()).unwrap();
25125 release_claim_rx.recv().unwrap();
25126 }));
25127 });
25128 let _owner = BlockingOwnerGuard::enter(Some(owner));
25129 claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
25130 });
25131 claim_entered_rx
25132 .recv_timeout(Duration::from_secs(2))
25133 .expect("the exact claimant must hold the shared transition lock");
25134
25135 let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
25136 let withdrawing_child = Arc::clone(&child);
25137 let withdrawal = std::thread::spawn(move || {
25138 withdrawing_child.begin_poll();
25139 withdrawn_tx.send(()).unwrap();
25140 });
25141 let withdrew_before_claim = withdrawn_rx
25142 .recv_timeout(Duration::from_millis(200))
25143 .is_ok();
25144 release_claim_tx.send(()).unwrap();
25145
25146 assert!(
25147 claimant.join().unwrap(),
25148 "the claim that linearized first must execute the exact job"
25149 );
25150 withdrawal.join().unwrap();
25151 assert!(
25152 !withdrew_before_claim,
25153 "source withdrawal must not interleave after claim validation but before claim commit"
25154 );
25155 assert!(ran.load(Ordering::SeqCst));
25156 }
25157
25158 #[cfg(napi_runtime_os_threads)]
25159 #[test]
25160 fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
25161 struct PendThenReady {
25182 polls: usize,
25183 }
25184 impl Future for PendThenReady {
25185 type Output = ();
25186 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25187 self.polls += 1;
25188 if self.polls >= 2 {
25189 Poll::Ready(())
25190 } else {
25191 cx.waker().wake_by_ref();
25195 Poll::Pending
25196 }
25197 }
25198 }
25199
25200 fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
25203 let scheduler = Arc::clone(target);
25204 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
25205 target.push_fifo_raw(runnable);
25206 task.detach();
25207 }
25208
25209 let opts = RuntimeOptions {
25210 flavor: RuntimeFlavor::MultiThread,
25211 worker_threads: 2,
25212 max_blocking_tasks: 2,
25213 thread_name_prefix: "rd-onpool-scope".to_string(),
25214 park_deadline: None,
25215 drain_linger: DEFAULT_DRAIN_LINGER,
25216 };
25217 let exec1 =
25218 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25219 let exec2 =
25220 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25221 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25222
25223 let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
25225
25226 enqueue_runnable(&exec2);
25230 {
25231 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25232 exec2.block_on(probe.as_mut());
25233 }
25234 assert_eq!(
25235 exec2.metrics.runnable_polls.load(Ordering::Relaxed),
25236 0,
25237 "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
25238 );
25239 assert_eq!(
25240 exec2.injector.len(),
25241 1,
25242 "the foreign call must leave exec2's runnable untouched on its queue"
25243 );
25244
25245 enqueue_runnable(&exec1);
25249 {
25250 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25251 exec1.block_on(probe.as_mut());
25252 }
25253 assert_eq!(
25254 exec1.metrics.runnable_polls.load(Ordering::Relaxed),
25255 1,
25256 "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
25257 );
25258 assert!(
25259 exec1.injector.is_empty(),
25260 "the cooperative branch must have drained exec1's queued runnable"
25261 );
25262
25263 while exec2.steal_one().is_some() {}
25266 }
25267
25268 #[cfg(napi_runtime_os_threads)]
25269 #[test]
25270 fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
25271 use std::sync::mpsc;
25275 use std::time::Duration;
25276
25277 const PRODUCER_COUNT: usize = 8;
25278
25279 fn schedule_pool_workload(
25280 executor: &Arc<MultiThreadExecutor>,
25281 tx: &mpsc::Sender<Option<usize>>,
25282 count: usize,
25283 ) {
25284 for i in 0..count {
25285 let tx = tx.clone();
25286 let scheduler = Arc::clone(executor);
25287 let (runnable, task) =
25288 async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
25289 scheduler.schedule(r);
25290 });
25291 executor.schedule(runnable);
25292 task.detach();
25293 }
25294 let tx = tx.clone();
25295 let scheduler = Arc::clone(executor);
25296 let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
25297 scheduler.schedule(r)
25298 });
25299 executor.schedule(runnable);
25300 task.detach();
25301 }
25302
25303 let expected_sum: usize = (0..PRODUCER_COUNT).sum();
25304 let (done_tx, done_rx) = mpsc::sync_channel(1);
25305 let runner = std::thread::spawn(move || {
25306 let options = RuntimeOptions {
25307 flavor: RuntimeFlavor::MultiThread,
25308 worker_threads: 1,
25309 max_blocking_tasks: 1,
25310 thread_name_prefix: "rd2-pre-fix".to_string(),
25311 park_deadline: None,
25312 drain_linger: DEFAULT_DRAIN_LINGER,
25313 }
25314 .validate()
25315 .expect("production MultiThread options must provide a reserve lane");
25316 let executor =
25317 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25318
25319 let (tx, rx) = mpsc::channel::<Option<usize>>();
25320 let (started_tx, started_rx) = mpsc::sync_channel(1);
25321 let handle = executor.schedule_blocking(move || {
25322 started_tx.send(()).unwrap();
25323 let mut sum = 0usize;
25324 while let Ok(msg) = rx.recv() {
25325 match msg {
25326 Some(value) => sum += value,
25327 None => break,
25328 }
25329 }
25330 sum
25331 });
25332 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
25333 schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
25334 let sum = futures::executor::block_on(handle).unwrap();
25335 done_tx.send(sum).unwrap();
25336 });
25337
25338 match done_rx.recv_timeout(Duration::from_secs(10)) {
25339 Ok(sum) => {
25340 runner.join().unwrap();
25341 assert_eq!(sum, expected_sum);
25342 }
25343 Err(error) => {
25344 panic!("a saturated blocking lane starved the async producers that feed it ({error})")
25345 }
25346 }
25347 }
25348
25349 #[test]
25350 fn current_thread_blocking_work_completes_inline() {
25351 let metrics = Arc::new(RuntimeMetrics::default());
25352 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
25353 CurrentThreadExecutor::new(Arc::clone(&metrics)),
25354 )));
25355 let registration = backend.work.try_register_work().unwrap();
25356
25357 let value =
25358 futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
25359 .expect("blocking task should complete");
25360
25361 assert_eq!(value, 7);
25362 assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
25363 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25364 }
25365
25366 #[cfg(napi_runtime_os_threads)]
25367 #[test]
25368 fn current_thread_blocking_work_obeys_single_lane_across_callers() {
25369 use std::sync::mpsc;
25370
25371 let controller = Arc::new(current_thread_controller(
25372 "current-thread-blocking-admission",
25373 ));
25374 let backend = controller.backend();
25375 let (first_started_tx, first_started_rx) = mpsc::channel();
25376 let (release_first_tx, release_first_rx) = mpsc::channel();
25377 let first_controller = Arc::clone(&controller);
25378 let first = std::thread::spawn(move || {
25379 let handle = first_controller
25380 .try_spawn_blocking(move || {
25381 first_started_tx.send(()).unwrap();
25382 release_first_rx.recv().unwrap();
25383 1usize
25384 })
25385 .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
25386 futures::executor::block_on(handle).unwrap()
25387 });
25388 first_started_rx
25389 .recv_timeout(Duration::from_secs(1))
25390 .unwrap();
25391
25392 let (second_started_tx, second_started_rx) = mpsc::channel();
25393 let second_controller = Arc::clone(&controller);
25394 let second = std::thread::spawn(move || {
25395 let handle = second_controller
25396 .try_spawn_blocking(move || {
25397 second_started_tx.send(()).unwrap();
25398 2usize
25399 })
25400 .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
25401 futures::executor::block_on(handle).unwrap()
25402 });
25403
25404 wait_until("both CurrentThread blocking calls to be accepted", || {
25405 backend
25406 .work
25407 .state
25408 .lock()
25409 .unwrap_or_else(std::sync::PoisonError::into_inner)
25410 .active
25411 == 2
25412 });
25413 assert!(
25414 second_started_rx
25415 .recv_timeout(Duration::from_millis(100))
25416 .is_err(),
25417 "CurrentThread must not execute two blocking closures concurrently"
25418 );
25419
25420 release_first_tx.send(()).unwrap();
25421 second_started_rx
25422 .recv_timeout(Duration::from_secs(1))
25423 .expect("the waiting blocking call must acquire the released lane");
25424 assert_eq!(first.join().unwrap(), 1);
25425 assert_eq!(second.join().unwrap(), 2);
25426 assert_eq!(
25427 controller
25428 .metrics
25429 .blocking_tasks_started
25430 .load(Ordering::Relaxed),
25431 2
25432 );
25433 assert_eq!(
25434 controller
25435 .metrics
25436 .blocking_tasks_completed
25437 .load(Ordering::Relaxed),
25438 2
25439 );
25440 assert_eq!(
25441 controller
25442 .metrics
25443 .active_blocking_tasks
25444 .load(Ordering::Relaxed),
25445 0
25446 );
25447 assert_eq!(
25448 controller
25449 .metrics
25450 .max_active_blocking_tasks
25451 .load(Ordering::Relaxed),
25452 1,
25453 "the public active-lane metric must remain within CurrentThread's reported capacity"
25454 );
25455 controller.shutdown().unwrap();
25456 }
25457
25458 #[cfg(napi_runtime_os_threads)]
25459 #[test]
25460 fn current_thread_nested_blocking_reuses_the_active_lane() {
25461 use std::sync::mpsc;
25462
25463 let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
25464 let (done_tx, done_rx) = mpsc::channel();
25465 let runner_controller = Arc::clone(&controller);
25466 let runner = std::thread::spawn(move || {
25467 let nested_controller = Arc::clone(&runner_controller);
25468 let outer = runner_controller
25469 .try_spawn_blocking(move || {
25470 let inner = nested_controller
25471 .try_spawn_blocking(|| 41usize)
25472 .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
25473 futures::executor::block_on(inner).unwrap() + 1
25474 })
25475 .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
25476 done_tx
25477 .send(futures::executor::block_on(outer).unwrap())
25478 .unwrap();
25479 });
25480
25481 assert_eq!(
25482 done_rx
25483 .recv_timeout(Duration::from_secs(2))
25484 .expect("a nested CurrentThread blocking call must not wait on its own lane"),
25485 42
25486 );
25487 runner.join().unwrap();
25488 assert_eq!(
25489 controller
25490 .metrics
25491 .blocking_tasks_started
25492 .load(Ordering::Relaxed),
25493 2
25494 );
25495 assert_eq!(
25496 controller
25497 .metrics
25498 .blocking_tasks_completed
25499 .load(Ordering::Relaxed),
25500 2
25501 );
25502 assert_eq!(
25503 controller
25504 .metrics
25505 .active_blocking_tasks
25506 .load(Ordering::Relaxed),
25507 0
25508 );
25509 assert_eq!(
25510 controller
25511 .metrics
25512 .max_active_blocking_tasks
25513 .load(Ordering::Relaxed),
25514 1,
25515 "nested work borrows the outer lane instead of reporting a second physical lane"
25516 );
25517 controller.shutdown().unwrap();
25518 }
25519
25520 #[cfg(napi_runtime_os_threads)]
25521 #[test]
25522 fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
25523 use std::sync::mpsc;
25524
25525 struct HotFuture {
25526 stop: Arc<AtomicBool>,
25527 }
25528
25529 impl Future for HotFuture {
25530 type Output = ();
25531
25532 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25533 if self.stop.load(Ordering::SeqCst) {
25534 Poll::Ready(())
25535 } else {
25536 cx.waker().wake_by_ref();
25537 Poll::Pending
25538 }
25539 }
25540 }
25541
25542 let stop = Arc::new(AtomicBool::new(false));
25543 let (ready_tx, ready_rx) = mpsc::channel();
25544 let (exact_tx, exact_rx) = mpsc::channel();
25545 let (done_tx, done_rx) = mpsc::channel();
25546 let runner_stop = Arc::clone(&stop);
25547 let runner = std::thread::spawn(move || {
25548 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25549 RuntimeMetrics::default(),
25550 )));
25551 let owner = executor.fresh_owner_token();
25552 let job = BlockingJobId(u64::MAX - 70);
25553 let (result_tx, result_rx) = oneshot::channel();
25554 {
25555 let mut state = executor
25556 .blocking_admission
25557 .state
25558 .lock()
25559 .unwrap_or_else(std::sync::PoisonError::into_inner);
25560 state.owner = Some(owner);
25561 let exact_stop = Arc::clone(&runner_stop);
25562 state.queue.push(QueuedBlocking {
25563 id: job,
25564 run: Box::new(move || {
25565 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
25566 let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
25567 }),
25568 });
25569 }
25570 let exact = JoinHandle(JoinHandleInner::Blocking {
25571 receiver: result_rx,
25572 dependency: BlockingDependency {
25573 job,
25574 owner: Some(owner),
25575 },
25576 published: PublishedBlockingDependencies::default(),
25577 });
25578 let _owner = executor.enter_blocking_owner(owner, false);
25579
25580 let scheduler = Arc::clone(&executor);
25581 let hot_stop = Arc::clone(&runner_stop);
25582 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
25583 scheduler.schedule(runnable);
25584 });
25585 executor.schedule(runnable);
25586 ready_tx.send(()).unwrap();
25587
25588 let mut value = None;
25589 {
25590 let mut future = std::pin::pin!(async {
25591 value = Some(exact.await.unwrap());
25592 hot_task.await;
25593 });
25594 assert_eq!(
25595 executor.block_on(future.as_mut()),
25596 BlockOnOutcome::Completed
25597 );
25598 }
25599 executor
25600 .blocking_admission
25601 .state
25602 .lock()
25603 .unwrap_or_else(std::sync::PoisonError::into_inner)
25604 .owner = None;
25605 done_tx.send(value.unwrap()).unwrap();
25606 });
25607
25608 ready_rx
25609 .recv_timeout(Duration::from_secs(2))
25610 .expect("the fairness driver must start");
25611 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
25612 stop.store(true, Ordering::SeqCst);
25613 assert_eq!(
25614 done_rx
25615 .recv_timeout(Duration::from_secs(2))
25616 .expect("the CurrentThread driver must retire"),
25617 41
25618 );
25619 runner.join().unwrap();
25620 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
25621 assert_eq!(
25622 exact_before_stop,
25623 Some(true),
25624 "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
25625 );
25626 }
25627
25628 #[cfg(napi_runtime_os_threads)]
25629 #[test]
25630 fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
25631 use std::sync::mpsc;
25632
25633 let controller = Arc::new(current_thread_controller(
25634 "current-thread-cross-driver-blocking",
25635 ));
25636 let backend = controller.backend();
25637 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25638 panic!("configured CurrentThread backend must create a CurrentThread executor");
25639 };
25640 let executor = Arc::clone(executor);
25641 drop(backend);
25642
25643 let (release_driver_tx, release_driver_rx) = oneshot::channel();
25644 let (driver_started_tx, driver_started_rx) = mpsc::channel();
25645 let driver_controller = Arc::clone(&controller);
25646 let driver = std::thread::spawn(move || {
25647 driver_started_tx.send(std::thread::current().id()).unwrap();
25648 block_on_with_controller(&driver_controller, async {
25649 release_driver_rx
25650 .await
25651 .expect("driver release sender must remain alive");
25652 });
25653 });
25654 let driver_thread = driver_started_rx
25655 .recv_timeout(Duration::from_secs(1))
25656 .unwrap();
25657 wait_until("the secondary CurrentThread driver to park", || {
25658 executor
25659 .stop
25660 .parkers
25661 .lock()
25662 .unwrap_or_else(std::sync::PoisonError::into_inner)
25663 .iter()
25664 .filter_map(Weak::upgrade)
25665 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25666 });
25667
25668 let (child_polled_tx, child_polled_rx) = mpsc::channel();
25669 let (release_child_tx, release_child_rx) = mpsc::channel();
25670 let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
25671 let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
25672 let (owner_done_tx, owner_done_rx) = mpsc::channel();
25673 let owner_controller = Arc::clone(&controller);
25674 let owner = std::thread::spawn(move || {
25675 let owner_thread = std::thread::current().id();
25676 let await_controller = Arc::clone(&owner_controller);
25677 let child_controller = Arc::clone(&owner_controller);
25678 let outer = owner_controller
25679 .try_spawn_blocking(move || {
25680 let spawn_controller = Arc::clone(&child_controller);
25681 let child = spawn_controller
25682 .try_spawn(async move {
25683 child_polled_tx.send(std::thread::current().id()).unwrap();
25684 release_child_rx.recv().unwrap();
25685 child_controller
25686 .try_spawn_blocking(move || {
25687 blocking_ran_tx.send(std::thread::current().id()).unwrap();
25688 41usize
25689 })
25690 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
25691 .await
25692 .unwrap()
25693 + 1
25694 })
25695 .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
25696
25697 assert_eq!(
25698 child_polled_rx
25699 .recv_timeout(Duration::from_secs(1))
25700 .unwrap(),
25701 driver_thread,
25702 "the secondary driver must own the child poll that reaches blocking admission"
25703 );
25704 owner_awaiting_tx.send(owner_thread).unwrap();
25705 block_on_with_controller(&await_controller, child).unwrap()
25706 })
25707 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
25708 owner_done_tx
25709 .send(futures::executor::block_on(outer))
25710 .unwrap();
25711 });
25712
25713 let owner_thread = owner_awaiting_rx
25714 .recv_timeout(Duration::from_secs(1))
25715 .unwrap();
25716 wait_until(
25717 "the blocking owner to park while awaiting the cross-driver child",
25718 || {
25719 executor
25720 .stop
25721 .parkers
25722 .lock()
25723 .unwrap_or_else(std::sync::PoisonError::into_inner)
25724 .iter()
25725 .filter_map(Weak::upgrade)
25726 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25727 },
25728 );
25729 release_child_tx.send(()).unwrap();
25730
25731 assert_eq!(
25732 blocking_ran_rx
25733 .recv_timeout(Duration::from_secs(2))
25734 .unwrap(),
25735 owner_thread,
25736 "the exact descendant must be serviced by the lane-owning driver"
25737 );
25738 assert_eq!(
25739 owner_done_rx
25740 .recv_timeout(Duration::from_secs(2))
25741 .expect("cross-driver blocking dependency must not deadlock")
25742 .unwrap(),
25743 42
25744 );
25745 release_driver_tx.send(()).unwrap();
25746 owner.join().unwrap();
25747 driver.join().unwrap();
25748 controller.shutdown().unwrap();
25749 assert_eq!(
25750 controller
25751 .metrics
25752 .blocking_tasks_started
25753 .load(Ordering::Relaxed),
25754 2
25755 );
25756 assert_eq!(
25757 controller
25758 .metrics
25759 .blocking_tasks_completed
25760 .load(Ordering::Relaxed),
25761 2
25762 );
25763 assert_eq!(
25764 controller
25765 .metrics
25766 .active_blocking_tasks
25767 .load(Ordering::Relaxed),
25768 0
25769 );
25770 assert_eq!(
25771 controller
25772 .metrics
25773 .max_active_blocking_tasks
25774 .load(Ordering::Relaxed),
25775 1,
25776 "cross-driver dependency service must reuse the owner's physical lane"
25777 );
25778 }
25779
25780 #[cfg(napi_runtime_os_threads)]
25781 #[test]
25782 fn current_thread_blocking_admission_rechecks_stop_under_lock() {
25783 use std::sync::mpsc;
25784
25785 let controller = Arc::new(current_thread_controller(
25786 "current-thread-blocking-stop-race",
25787 ));
25788 let backend = controller.backend();
25789 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25790 panic!("configured CurrentThread backend must create a CurrentThread executor");
25791 };
25792 let executor = Arc::clone(executor);
25793 drop(backend);
25794
25795 let ran = Arc::new(AtomicBool::new(false));
25796 let blocking_ran = Arc::clone(&ran);
25797 let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
25798 let (release_admission_tx, release_admission_rx) = mpsc::channel();
25799 let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
25800 let submit_controller = Arc::clone(&controller);
25801 let submitter = std::thread::spawn(move || {
25802 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25803 *slot.borrow_mut() = Some(Box::new(move || {
25804 admission_locked_tx.send(()).unwrap();
25805 release_admission_rx.recv().unwrap();
25806 }));
25807 });
25808 let handle = submit_controller
25809 .try_spawn_blocking(move || {
25810 blocking_ran.store(true, Ordering::SeqCst);
25811 })
25812 .unwrap_or_else(|_| {
25813 panic!("the submission linearized before shutdown and must be accepted")
25814 });
25815 blocking_result_tx
25816 .send(futures::executor::block_on(handle))
25817 .unwrap();
25818 });
25819 admission_locked_rx
25820 .recv_timeout(Duration::from_secs(2))
25821 .expect("the blocking submitter must pause while holding the admission lock");
25822
25823 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
25824 let shutdown_controller = Arc::clone(&controller);
25825 let shutdown = std::thread::spawn(move || {
25826 shutdown_result_tx
25827 .send(shutdown_controller.shutdown())
25828 .unwrap();
25829 });
25830 wait_until("CurrentThread shutdown to publish generation stop", || {
25831 executor.stop.is_stopping()
25832 });
25833 release_admission_tx.send(()).unwrap();
25834
25835 let blocking_error = blocking_result_rx
25836 .recv_timeout(Duration::from_secs(2))
25837 .expect("the admitted blocking handle must resolve")
25838 .expect_err("stop publication must reject the blocking lane claim");
25839 assert_eq!(
25840 blocking_error.to_string(),
25841 "the async runtime stopped before the blocking task could start"
25842 );
25843 assert!(
25844 !ran.load(Ordering::SeqCst),
25845 "blocking work must not start after stop publication"
25846 );
25847 shutdown_result_rx
25848 .recv_timeout(Duration::from_secs(2))
25849 .expect("shutdown must finish after the rejected admission retires")
25850 .unwrap();
25851 submitter.join().unwrap();
25852 shutdown.join().unwrap();
25853 }
25854
25855 #[cfg(napi_runtime_os_threads)]
25856 #[test]
25857 fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
25858 use std::sync::mpsc;
25859
25860 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25861 RuntimeMetrics::default(),
25862 )));
25863 let ran = Arc::new(AtomicBool::new(false));
25864 let job_ran = Arc::clone(&ran);
25865 {
25866 let mut state = executor
25867 .blocking_admission
25868 .state
25869 .lock()
25870 .unwrap_or_else(std::sync::PoisonError::into_inner);
25871 state.queue.push(QueuedBlocking {
25872 id: BlockingJobId(u64::MAX - 71),
25873 run: Box::new(move || {
25874 job_ran.store(true, Ordering::SeqCst);
25875 }),
25876 });
25877 }
25878
25879 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25880 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25881 let (claim_result_tx, claim_result_rx) = mpsc::channel();
25882 let claim_executor = Arc::clone(&executor);
25883 let claimant = std::thread::spawn(move || {
25884 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25885 *slot.borrow_mut() = Some(Box::new(move || {
25886 claim_locked_tx.send(()).unwrap();
25887 release_claim_rx.recv().unwrap();
25888 }));
25889 });
25890 claim_result_tx
25891 .send(claim_executor.run_one_blocking())
25892 .unwrap();
25893 });
25894 claim_locked_rx
25895 .recv_timeout(Duration::from_secs(2))
25896 .expect("the FIFO claimant must pause while holding the admission lock");
25897
25898 executor.stop.begin_shutdown();
25899 release_claim_tx.send(()).unwrap();
25900 assert!(
25901 !claim_result_rx
25902 .recv_timeout(Duration::from_secs(2))
25903 .expect("the stopped FIFO claim must finish"),
25904 "stop publication must reject the queued FIFO claim"
25905 );
25906 claimant.join().unwrap();
25907 assert!(
25908 !ran.load(Ordering::SeqCst),
25909 "queued FIFO work must not start after stop publication"
25910 );
25911 assert!(
25912 !executor
25913 .blocking_admission
25914 .state
25915 .lock()
25916 .unwrap_or_else(std::sync::PoisonError::into_inner)
25917 .queue
25918 .is_empty(),
25919 "the rejected FIFO job must remain queued for shutdown cancellation"
25920 );
25921
25922 executor.close_blocking_admission();
25923 assert!(
25924 executor
25925 .blocking_admission
25926 .state
25927 .lock()
25928 .unwrap_or_else(std::sync::PoisonError::into_inner)
25929 .queue
25930 .is_empty()
25931 );
25932 }
25933
25934 #[cfg(napi_runtime_os_threads)]
25935 #[test]
25936 fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
25937 use std::sync::mpsc;
25938
25939 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25940 RuntimeMetrics::default(),
25941 )));
25942 let ran = Arc::new(AtomicBool::new(false));
25943 let job_ran = Arc::clone(&ran);
25944 {
25945 let mut state = executor
25946 .blocking_admission
25947 .state
25948 .lock()
25949 .unwrap_or_else(std::sync::PoisonError::into_inner);
25950 state.queue.push(QueuedBlocking {
25951 id: BlockingJobId(u64::MAX - 73),
25952 run: Box::new(move || {
25953 job_ran.store(true, Ordering::SeqCst);
25954 }),
25955 });
25956 }
25957
25958 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25959 let (release_claim_tx, release_claim_rx) = mpsc::channel();
25960 let (claim_result_tx, claim_result_rx) = mpsc::channel();
25961 let claim_executor = Arc::clone(&executor);
25962 let claimant = std::thread::spawn(move || {
25963 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25964 *slot.borrow_mut() = Some(Box::new(move || {
25965 claim_locked_tx.send(()).unwrap();
25966 release_claim_rx.recv().unwrap();
25967 }));
25968 });
25969 claim_result_tx
25970 .send(claim_executor.run_one_blocking())
25971 .unwrap();
25972 });
25973 claim_locked_rx
25974 .recv_timeout(Duration::from_secs(2))
25975 .expect("the FIFO claimant must pause while holding the admission lock");
25976
25977 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
25978 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
25979 let cancelling_executor = Arc::clone(&executor);
25980 let cancellation = std::thread::spawn(move || {
25981 let cancelling = cancelling_executor
25982 .begin_failed_dispatch_cancellation()
25983 .expect("the test must publish terminal dispatch cancellation");
25984 cancellation_published_tx.send(()).unwrap();
25985 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
25986 cancellation_done_tx.send(()).unwrap();
25987 });
25988 cancellation_published_rx
25989 .recv_timeout(Duration::from_secs(2))
25990 .expect("terminal dispatch cancellation must publish before queue cancellation");
25991 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25992 assert!(
25993 matches!(
25994 cancellation_done_rx.try_recv(),
25995 Err(mpsc::TryRecvError::Empty)
25996 ),
25997 "terminal cancellation must wait for the blocking-admission lock"
25998 );
25999
26000 release_claim_tx.send(()).unwrap();
26001 assert!(
26002 !claim_result_rx
26003 .recv_timeout(Duration::from_secs(2))
26004 .expect("the cancelled FIFO claim must finish"),
26005 "terminal cancellation publication must reject the queued FIFO claim"
26006 );
26007 cancellation_done_rx
26008 .recv_timeout(Duration::from_secs(2))
26009 .expect("terminal cancellation must drain the rejected FIFO job");
26010 claimant.join().unwrap();
26011 cancellation.join().unwrap();
26012
26013 assert!(
26014 !ran.load(Ordering::SeqCst),
26015 "terminally cancelled FIFO work must not start"
26016 );
26017 assert!(
26018 executor
26019 .blocking_admission
26020 .state
26021 .lock()
26022 .unwrap_or_else(std::sync::PoisonError::into_inner)
26023 .queue
26024 .is_empty(),
26025 "terminal cancellation must drain the rejected FIFO job"
26026 );
26027 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26028 }
26029
26030 #[cfg(napi_runtime_os_threads)]
26031 #[test]
26032 fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
26033 use std::sync::mpsc;
26034
26035 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26036 RuntimeMetrics::default(),
26037 )));
26038 let owner = executor.fresh_owner_token();
26039 let job = BlockingJobId(u64::MAX - 72);
26040 let ran = Arc::new(AtomicBool::new(false));
26041 let job_ran = Arc::clone(&ran);
26042 {
26043 let mut state = executor
26044 .blocking_admission
26045 .state
26046 .lock()
26047 .unwrap_or_else(std::sync::PoisonError::into_inner);
26048 state.owner = Some(owner);
26049 state.queue.push(QueuedBlocking {
26050 id: job,
26051 run: Box::new(move || {
26052 job_ran.store(true, Ordering::SeqCst);
26053 }),
26054 });
26055 }
26056 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26057 dependency.set_owned(BlockingDependency {
26058 job,
26059 owner: Some(owner),
26060 });
26061
26062 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26063 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26064 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26065 let claim_executor = Arc::clone(&executor);
26066 let claim_dependency = Arc::clone(&dependency);
26067 let claimant = std::thread::spawn(move || {
26068 let _owner = claim_executor.enter_blocking_owner(owner, false);
26069 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26070 *slot.borrow_mut() = Some(Box::new(move || {
26071 claim_locked_tx.send(()).unwrap();
26072 release_claim_rx.recv().unwrap();
26073 }));
26074 });
26075 claim_result_tx
26076 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26077 .unwrap();
26078 });
26079 claim_locked_rx
26080 .recv_timeout(Duration::from_secs(2))
26081 .expect("the owner claimant must pause while holding the admission lock");
26082
26083 executor.stop.begin_shutdown();
26084 release_claim_tx.send(()).unwrap();
26085 assert!(
26086 !claim_result_rx
26087 .recv_timeout(Duration::from_secs(2))
26088 .expect("the stopped owner claim must finish"),
26089 "stop publication must reject the exact-owner claim"
26090 );
26091 claimant.join().unwrap();
26092 assert!(
26093 !ran.load(Ordering::SeqCst),
26094 "queued exact-owner work must not start after stop publication"
26095 );
26096 assert!(
26097 !executor
26098 .blocking_admission
26099 .state
26100 .lock()
26101 .unwrap_or_else(std::sync::PoisonError::into_inner)
26102 .queue
26103 .is_empty(),
26104 "the rejected exact-owner job must remain queued for shutdown cancellation"
26105 );
26106
26107 executor
26108 .blocking_admission
26109 .state
26110 .lock()
26111 .unwrap_or_else(std::sync::PoisonError::into_inner)
26112 .owner = None;
26113 executor.close_blocking_admission();
26114 assert!(
26115 executor
26116 .blocking_admission
26117 .state
26118 .lock()
26119 .unwrap_or_else(std::sync::PoisonError::into_inner)
26120 .queue
26121 .is_empty()
26122 );
26123 }
26124
26125 #[cfg(napi_runtime_os_threads)]
26126 #[test]
26127 fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26128 use std::sync::mpsc;
26129
26130 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26131 RuntimeMetrics::default(),
26132 )));
26133 let owner = executor.fresh_owner_token();
26134 let job = BlockingJobId(u64::MAX - 74);
26135 let ran = Arc::new(AtomicBool::new(false));
26136 let job_ran = Arc::clone(&ran);
26137 {
26138 let mut state = executor
26139 .blocking_admission
26140 .state
26141 .lock()
26142 .unwrap_or_else(std::sync::PoisonError::into_inner);
26143 state.owner = Some(owner);
26144 state.queue.push(QueuedBlocking {
26145 id: job,
26146 run: Box::new(move || {
26147 job_ran.store(true, Ordering::SeqCst);
26148 }),
26149 });
26150 }
26151 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26152 dependency.set_owned(BlockingDependency {
26153 job,
26154 owner: Some(owner),
26155 });
26156
26157 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26158 let (release_claim_tx, release_claim_rx) = mpsc::channel();
26159 let (claim_result_tx, claim_result_rx) = mpsc::channel();
26160 let claim_executor = Arc::clone(&executor);
26161 let claim_dependency = Arc::clone(&dependency);
26162 let claimant = std::thread::spawn(move || {
26163 let _owner = claim_executor.enter_blocking_owner(owner, false);
26164 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26165 *slot.borrow_mut() = Some(Box::new(move || {
26166 claim_locked_tx.send(()).unwrap();
26167 release_claim_rx.recv().unwrap();
26168 }));
26169 });
26170 claim_result_tx
26171 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26172 .unwrap();
26173 });
26174 claim_locked_rx
26175 .recv_timeout(Duration::from_secs(2))
26176 .expect("the owner claimant must pause while holding the admission lock");
26177
26178 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26179 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26180 let cancelling_executor = Arc::clone(&executor);
26181 let cancellation = std::thread::spawn(move || {
26182 let cancelling = cancelling_executor
26183 .begin_failed_dispatch_cancellation()
26184 .expect("the test must publish terminal dispatch cancellation");
26185 cancellation_published_tx.send(()).unwrap();
26186 cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26187 cancellation_done_tx.send(()).unwrap();
26188 });
26189 cancellation_published_rx
26190 .recv_timeout(Duration::from_secs(2))
26191 .expect("terminal dispatch cancellation must publish before queue cancellation");
26192 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26193 assert!(
26194 matches!(
26195 cancellation_done_rx.try_recv(),
26196 Err(mpsc::TryRecvError::Empty)
26197 ),
26198 "terminal cancellation must wait for the blocking-admission lock"
26199 );
26200
26201 release_claim_tx.send(()).unwrap();
26202 assert!(
26203 !claim_result_rx
26204 .recv_timeout(Duration::from_secs(2))
26205 .expect("the cancelled owner claim must finish"),
26206 "terminal cancellation publication must reject the exact-owner claim"
26207 );
26208 cancellation_done_rx
26209 .recv_timeout(Duration::from_secs(2))
26210 .expect("terminal cancellation must drain the rejected exact-owner job");
26211 claimant.join().unwrap();
26212 cancellation.join().unwrap();
26213
26214 assert!(
26215 !ran.load(Ordering::SeqCst),
26216 "terminally cancelled exact-owner work must not start"
26217 );
26218 assert!(
26219 executor
26220 .blocking_admission
26221 .state
26222 .lock()
26223 .unwrap_or_else(std::sync::PoisonError::into_inner)
26224 .queue
26225 .is_empty(),
26226 "terminal cancellation must drain the rejected exact-owner job"
26227 );
26228 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26229 }
26230
26231 #[cfg(napi_runtime_os_threads)]
26232 #[test]
26233 fn current_thread_shutdown_cancels_waiting_blocking_work() {
26234 use std::sync::mpsc;
26235
26236 struct DropSignal(mpsc::Sender<()>);
26237
26238 impl Drop for DropSignal {
26239 fn drop(&mut self) {
26240 let _ = self.0.send(());
26241 }
26242 }
26243
26244 let controller = Arc::new(current_thread_controller(
26245 "current-thread-blocking-shutdown",
26246 ));
26247 let backend = controller.backend();
26248 let (first_started_tx, first_started_rx) = mpsc::channel();
26249 let (release_first_tx, release_first_rx) = mpsc::channel();
26250 let first_controller = Arc::clone(&controller);
26251 let first = std::thread::spawn(move || {
26252 let handle = first_controller
26253 .try_spawn_blocking(move || {
26254 first_started_tx.send(()).unwrap();
26255 release_first_rx.recv().unwrap();
26256 })
26257 .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
26258 futures::executor::block_on(handle)
26259 });
26260 first_started_rx
26261 .recv_timeout(Duration::from_secs(1))
26262 .unwrap();
26263
26264 let waiting_ran = Arc::new(AtomicBool::new(false));
26265 let waiting_ran_task = Arc::clone(&waiting_ran);
26266 let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
26267 let waiting_drop = DropSignal(waiting_dropped_tx);
26268 let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
26269 let waiting_controller = Arc::clone(&controller);
26270 let waiting = std::thread::spawn(move || {
26271 let handle = waiting_controller
26272 .try_spawn_blocking(move || {
26273 let _waiting_drop = waiting_drop;
26274 waiting_ran_task.store(true, Ordering::SeqCst);
26275 })
26276 .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
26277 waiting_result_tx
26278 .send(futures::executor::block_on(handle))
26279 .unwrap();
26280 });
26281 wait_until("both CurrentThread blocking calls to be accepted", || {
26282 backend
26283 .work
26284 .state
26285 .lock()
26286 .unwrap_or_else(std::sync::PoisonError::into_inner)
26287 .active
26288 == 2
26289 });
26290
26291 let (shutdown_tx, shutdown_rx) = mpsc::channel();
26292 let shutdown_controller = Arc::clone(&controller);
26293 let shutdown = std::thread::spawn(move || {
26294 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26295 });
26296 waiting_dropped_rx
26297 .recv_timeout(Duration::from_secs(1))
26298 .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
26299 assert!(
26300 !waiting_ran.load(Ordering::SeqCst),
26301 "waiting blocking work must not start"
26302 );
26303 assert!(
26304 waiting_result_rx
26305 .recv_timeout(Duration::from_secs(1))
26306 .unwrap()
26307 .is_err(),
26308 "the waiting blocking handle must resolve as cancelled"
26309 );
26310 assert!(
26311 shutdown_rx
26312 .recv_timeout(Duration::from_millis(100))
26313 .is_err(),
26314 "shutdown must still wait for the blocking closure that already owns the lane"
26315 );
26316
26317 release_first_tx.send(()).unwrap();
26318 first.join().unwrap().unwrap();
26319 waiting.join().unwrap();
26320 shutdown_rx
26321 .recv_timeout(Duration::from_secs(1))
26322 .unwrap()
26323 .unwrap();
26324 shutdown.join().unwrap();
26325 assert_eq!(
26326 controller
26327 .metrics
26328 .blocking_tasks_started
26329 .load(Ordering::Relaxed),
26330 1
26331 );
26332 assert_eq!(
26333 controller
26334 .metrics
26335 .blocking_tasks_completed
26336 .load(Ordering::Relaxed),
26337 1
26338 );
26339 assert_eq!(
26340 controller
26341 .metrics
26342 .active_blocking_tasks
26343 .load(Ordering::Relaxed),
26344 0
26345 );
26346 }
26347
26348 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
26349 #[test]
26350 fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
26351 use std::{panic::panic_any, sync::mpsc};
26352
26353 struct ReentrantPayload {
26354 controller: Arc<RuntimeController>,
26355 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26356 }
26357
26358 impl Drop for ReentrantPayload {
26359 fn drop(&mut self) {
26360 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26361 let shutdown = self.controller.shutdown();
26362 self.result.send((generation, shutdown)).unwrap();
26363 }
26364 }
26365
26366 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26367
26368 impl Drop for PanicPayloadOnDrop {
26369 fn drop(&mut self) {
26370 panic_any(self.0.take().expect("the result must panic only once"));
26371 }
26372 }
26373
26374 let controller = Arc::new(current_thread_controller(
26375 "current-thread-panic-payload-generation",
26376 ));
26377 let backend = controller.backend();
26378 let generation = backend.generation();
26379 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26380 panic!("configured CurrentThread backend must create a CurrentThread executor");
26381 };
26382 let executor = Arc::clone(executor);
26383 drop(backend);
26384
26385 let owner = executor.fresh_owner_token();
26388 executor
26389 .blocking_admission
26390 .state
26391 .lock()
26392 .unwrap_or_else(std::sync::PoisonError::into_inner)
26393 .owner = Some(owner);
26394 let (result_tx, result_rx) = mpsc::channel();
26395 let payload_controller = Arc::clone(&controller);
26396 let handle = controller
26397 .try_spawn_blocking(move || {
26398 PanicPayloadOnDrop(Some(ReentrantPayload {
26399 controller: payload_controller,
26400 result: result_tx,
26401 }))
26402 })
26403 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26404 drop(handle);
26405 executor
26406 .blocking_admission
26407 .state
26408 .lock()
26409 .unwrap_or_else(std::sync::PoisonError::into_inner)
26410 .owner = None;
26411 let dispatch = current_thread_dispatch(&executor);
26412 controller.drive_current_thread_dispatch(dispatch, false);
26413
26414 let (observed_generation, shutdown) = result_rx
26415 .recv_timeout(Duration::from_secs(1))
26416 .expect("the caught result panic payload must be destroyed in the scheduler turn");
26417 assert_eq!(observed_generation, Some(generation));
26418 let error = shutdown.expect_err("payload destruction is still work in the active generation");
26419 assert!(error.to_string().contains("work running on that runtime"));
26420 controller.shutdown().unwrap();
26421 }
26422
26423 #[cfg(panic = "unwind")]
26424 #[test]
26425 fn current_thread_detached_blocking_result_drop_is_contained() {
26426 use std::sync::mpsc;
26427
26428 struct PanicOnDrop(mpsc::Sender<()>);
26429
26430 impl Drop for PanicOnDrop {
26431 fn drop(&mut self) {
26432 self.0.send(()).unwrap();
26433 panic!("intentional CurrentThread blocking result destructor panic");
26434 }
26435 }
26436
26437 let controller = current_thread_controller("current-thread-blocking-result-drop");
26438 let (dropped_tx, dropped_rx) = mpsc::channel();
26439 let handle = controller
26440 .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
26441 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26442
26443 let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26444 dropped_rx
26445 .recv_timeout(Duration::from_secs(1))
26446 .expect("dropping the detached handle must reclaim its blocking result");
26447 assert!(
26448 result.is_ok(),
26449 "the blocking result destructor panic must be contained"
26450 );
26451 controller.shutdown().unwrap();
26452 }
26453
26454 #[test]
26459 fn validate_rejects_zero_worker_threads() {
26460 let error = RuntimeOptions {
26461 flavor: RuntimeFlavor::MultiThread,
26462 worker_threads: 0,
26463 max_blocking_tasks: 1,
26464 thread_name_prefix: "rd8".to_string(),
26465 park_deadline: None,
26466 drain_linger: DEFAULT_DRAIN_LINGER,
26467 }
26468 .validate()
26469 .expect_err("worker_threads == 0 must be rejected");
26470 assert_eq!(
26471 error.to_string(),
26472 "worker_threads must be greater than zero"
26473 );
26474 }
26475
26476 #[test]
26477 fn validate_rejects_zero_max_blocking_tasks() {
26478 let error = RuntimeOptions {
26479 flavor: RuntimeFlavor::MultiThread,
26480 worker_threads: 2,
26481 max_blocking_tasks: 0,
26482 thread_name_prefix: "rd8".to_string(),
26483 park_deadline: None,
26484 drain_linger: DEFAULT_DRAIN_LINGER,
26485 }
26486 .validate()
26487 .expect_err("max_blocking_tasks == 0 must be rejected");
26488 assert_eq!(
26489 error.to_string(),
26490 "max_blocking_tasks must be greater than zero"
26491 );
26492 }
26493
26494 #[test]
26495 fn validate_current_thread_coerces_worker_threads_to_one() {
26496 let validated = RuntimeOptions {
26499 flavor: RuntimeFlavor::CurrentThread,
26500 worker_threads: 8,
26501 max_blocking_tasks: 8,
26502 thread_name_prefix: "rd8".to_string(),
26503 park_deadline: None,
26504 drain_linger: DEFAULT_DRAIN_LINGER,
26505 }
26506 .validate()
26507 .expect("CurrentThread options must validate");
26508 assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
26509 assert_eq!(validated.worker_threads, 1);
26510 assert_eq!(validated.max_blocking_tasks, 1);
26511 }
26512
26513 #[cfg(napi_runtime_os_threads)]
26517 #[test]
26518 fn validate_reserves_one_worker_from_blocking_admission() {
26519 let validated = RuntimeOptions {
26520 flavor: RuntimeFlavor::MultiThread,
26521 worker_threads: 2,
26522 max_blocking_tasks: 8,
26523 thread_name_prefix: "rd8".to_string(),
26524 park_deadline: None,
26525 drain_linger: DEFAULT_DRAIN_LINGER,
26526 }
26527 .validate()
26528 .expect("MultiThread options must validate");
26529 assert_eq!(validated.worker_threads, 2);
26530 assert_eq!(validated.max_blocking_tasks, 1);
26531 }
26532
26533 #[cfg(napi_runtime_os_threads)]
26534 #[test]
26535 fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
26536 let validated = RuntimeOptions {
26537 flavor: RuntimeFlavor::MultiThread,
26538 worker_threads: 1,
26539 max_blocking_tasks: 8,
26540 thread_name_prefix: "rd8-minimum".to_string(),
26541 park_deadline: None,
26542 drain_linger: DEFAULT_DRAIN_LINGER,
26543 }
26544 .validate()
26545 .expect("MultiThread options must validate");
26546 assert_eq!(validated.worker_threads, 2);
26547 assert_eq!(validated.max_blocking_tasks, 1);
26548 }
26549
26550 #[cfg(napi_runtime_os_threads)]
26551 #[test]
26552 fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
26553 let simulated_32_bit = RuntimeOptions {
26554 flavor: RuntimeFlavor::MultiThread,
26555 worker_threads: 256,
26556 max_blocking_tasks: 256,
26557 thread_name_prefix: "rd8-rayon-cap-32".to_string(),
26558 park_deadline: None,
26559 drain_linger: DEFAULT_DRAIN_LINGER,
26560 }
26561 .validate_with_rayon_max_threads(Some(255))
26562 .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
26563 assert_eq!(simulated_32_bit.worker_threads, 255);
26564 assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
26565
26566 let rayon_max_threads = max_num_threads();
26567 let requested_threads = rayon_max_threads
26568 .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
26569 .checked_add(1)
26570 .expect("supported worker counts must leave room for an oversized request");
26571 let controller = RuntimeController::new();
26572 controller
26573 .configure(RuntimeOptions {
26574 flavor: RuntimeFlavor::MultiThread,
26575 worker_threads: requested_threads,
26576 max_blocking_tasks: requested_threads,
26577 thread_name_prefix: "rd8-rayon-cap".to_string(),
26578 park_deadline: None,
26579 drain_linger: DEFAULT_DRAIN_LINGER,
26580 })
26581 .expect("an oversized request must clamp to the production worker ceiling");
26582
26583 let reported = controller.options();
26584 let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
26585 assert_eq!(reported.worker_threads, expected_workers);
26586 assert_eq!(
26587 reported.max_blocking_tasks,
26588 expected_workers - 1,
26589 "the blocking cap must reserve a lane from the physically realizable worker count"
26590 );
26591 }
26592
26593 #[cfg(napi_runtime_os_threads)]
26594 #[test]
26595 fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
26596 let extreme = Duration::from_micros(u64::MAX);
26603 let validated = RuntimeOptions {
26604 flavor: RuntimeFlavor::MultiThread,
26605 worker_threads: 2,
26606 max_blocking_tasks: 1,
26607 thread_name_prefix: "linger-clamp".to_string(),
26608 park_deadline: None,
26609 drain_linger: extreme,
26610 }
26611 .validate_with_rayon_max_threads(Some(2))
26612 .expect("an extreme linger budget must clamp, not fail validation");
26613 assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
26614
26615 let controller = RuntimeController::new();
26616 controller
26617 .configure_partial(RuntimeOptionsPatch {
26618 drain_linger: Some(extreme),
26619 ..RuntimeOptionsPatch::default()
26620 })
26621 .expect("an extreme linger patch must clamp, not fail validation");
26622 assert_eq!(
26623 controller.options().drain_linger,
26624 MAX_DRAIN_LINGER,
26625 "introspection must report the clamped budget, not the requested one"
26626 );
26627 controller
26628 .configure_partial(RuntimeOptionsPatch {
26629 drain_linger: Some(Duration::ZERO),
26630 ..RuntimeOptionsPatch::default()
26631 })
26632 .expect("ZERO (lingering disabled) must remain a valid budget");
26633 assert_eq!(controller.options().drain_linger, Duration::ZERO);
26634
26635 let metrics = Arc::new(RuntimeMetrics::default());
26636 let unvalidated = RuntimeOptions {
26637 flavor: RuntimeFlavor::MultiThread,
26638 worker_threads: 2,
26639 max_blocking_tasks: 2,
26640 thread_name_prefix: "linger-clamp-exec".to_string(),
26641 park_deadline: None,
26642 drain_linger: extreme,
26643 };
26644 let executor = MultiThreadExecutor::new(&unvalidated, metrics)
26645 .expect("the executor must construct with an extreme unvalidated budget");
26646 assert_eq!(
26647 executor.drain_linger,
26648 Some(MAX_DRAIN_LINGER),
26649 "the constructor's defensive clamp must bound unvalidated budgets"
26650 );
26651
26652 let metrics = Arc::new(RuntimeMetrics::default());
26653 let disabled = RuntimeOptions {
26654 flavor: RuntimeFlavor::MultiThread,
26655 worker_threads: 2,
26656 max_blocking_tasks: 2,
26657 thread_name_prefix: "linger-clamp-off".to_string(),
26658 park_deadline: None,
26659 drain_linger: Duration::ZERO,
26660 };
26661 let executor = MultiThreadExecutor::new(&disabled, metrics)
26662 .expect("the executor must construct with lingering disabled");
26663 assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
26664 }
26665
26666 #[cfg(not(target_family = "wasm"))]
26667 #[test]
26668 fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
26669 let controller = RuntimeController::new();
26670 let options = controller.options();
26671 assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
26672 assert_eq!(
26673 options.max_blocking_tasks,
26674 options.worker_threads.saturating_sub(1).max(1),
26675 "the unconfigured Rust API must use the same validated reserve as the binding"
26676 );
26677 }
26678
26679 #[cfg(napi_runtime_os_threads)]
26680 #[test]
26681 fn concurrent_partial_configuration_merges_from_latest_committed_options() {
26682 use std::{sync::mpsc, time::Duration};
26683
26684 let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
26685 let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
26686 let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
26687 let first_controller = Arc::clone(&controller);
26688 let first = std::thread::spawn(move || {
26689 first_controller
26690 .configure_partial_inner(
26691 RuntimeOptionsPatch {
26692 max_blocking_tasks: Some(3),
26693 ..RuntimeOptionsPatch::default()
26694 },
26695 |candidate| {
26696 assert_eq!(
26697 (candidate.worker_threads, candidate.max_blocking_tasks),
26698 (8, 3)
26699 );
26700 first_merged_tx
26701 .send(())
26702 .expect("the race coordinator must still be listening");
26703 release_first_rx
26704 .recv_timeout(Duration::from_secs(5))
26705 .expect("the first partial configuration must be released");
26706 },
26707 )
26708 .expect("the first partial configuration must commit");
26709 });
26710 first_merged_rx
26711 .recv_timeout(Duration::from_secs(5))
26712 .expect("the first partial configuration must hold the controller lock after merging");
26713
26714 let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
26715 let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
26716 let second_controller = Arc::clone(&controller);
26717 let second = std::thread::spawn(move || {
26718 second_started_tx
26719 .send(())
26720 .expect("the race coordinator must still be listening");
26721 second_controller
26722 .configure_partial_inner(
26723 RuntimeOptionsPatch {
26724 worker_threads: Some(6),
26725 ..RuntimeOptionsPatch::default()
26726 },
26727 |candidate| {
26728 second_candidate_tx
26729 .send((candidate.worker_threads, candidate.max_blocking_tasks))
26730 .expect("the race coordinator must still be listening");
26731 },
26732 )
26733 .expect("the second partial configuration must commit");
26734 });
26735 second_started_rx
26736 .recv_timeout(Duration::from_secs(5))
26737 .expect("the second partial configuration thread must start");
26738 release_first_tx
26739 .send(())
26740 .expect("the first partial configuration must still be waiting");
26741
26742 first
26743 .join()
26744 .expect("the first configuration thread must not panic");
26745 second
26746 .join()
26747 .expect("the second configuration thread must not panic");
26748 assert_eq!(
26749 second_candidate_rx
26750 .recv_timeout(Duration::from_secs(5))
26751 .expect("the second partial configuration must expose its merged candidate"),
26752 (6, 3),
26753 "the second caller must merge against the first caller's committed options"
26754 );
26755
26756 let options = controller.options();
26757 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
26758 assert_eq!(options.thread_name_prefix, "partial-config-race");
26759 }
26760
26761 #[cfg(napi_runtime_os_threads)]
26762 #[test]
26763 fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
26764 use std::{sync::mpsc, time::Duration};
26765
26766 let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
26767 let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
26768 let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
26769 let invalid_controller = Arc::clone(&controller);
26770 let invalid = std::thread::spawn(move || {
26771 invalid_controller
26772 .configure_partial_inner(
26773 RuntimeOptionsPatch {
26774 worker_threads: Some(0),
26775 ..RuntimeOptionsPatch::default()
26776 },
26777 |candidate| {
26778 assert_eq!(
26779 (candidate.worker_threads, candidate.max_blocking_tasks),
26780 (0, 2)
26781 );
26782 invalid_merged_tx
26783 .send(())
26784 .expect("the race coordinator must still be listening");
26785 release_invalid_rx
26786 .recv_timeout(Duration::from_secs(5))
26787 .expect("the invalid partial configuration must be released");
26788 },
26789 )
26790 .expect_err("the invalid partial configuration must be rejected")
26791 .to_string()
26792 });
26793 invalid_merged_rx
26794 .recv_timeout(Duration::from_secs(5))
26795 .expect("the invalid partial configuration must hold the lock after merging");
26796
26797 let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
26798 let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
26799 let valid_controller = Arc::clone(&controller);
26800 let valid = std::thread::spawn(move || {
26801 valid_started_tx
26802 .send(())
26803 .expect("the race coordinator must still be listening");
26804 valid_controller
26805 .configure_partial_inner(
26806 RuntimeOptionsPatch {
26807 max_blocking_tasks: Some(4),
26808 ..RuntimeOptionsPatch::default()
26809 },
26810 |candidate| {
26811 valid_candidate_tx
26812 .send((candidate.worker_threads, candidate.max_blocking_tasks))
26813 .expect("the race coordinator must still be listening");
26814 },
26815 )
26816 .expect("the valid partial configuration must commit");
26817 });
26818 valid_started_rx
26819 .recv_timeout(Duration::from_secs(5))
26820 .expect("the valid partial configuration thread must start");
26821 release_invalid_tx
26822 .send(())
26823 .expect("the invalid partial configuration must still be waiting");
26824
26825 assert_eq!(
26826 invalid
26827 .join()
26828 .expect("the invalid configuration thread must not panic"),
26829 "worker_threads must be greater than zero"
26830 );
26831 valid
26832 .join()
26833 .expect("the valid configuration thread must not panic");
26834 assert_eq!(
26835 valid_candidate_rx
26836 .recv_timeout(Duration::from_secs(5))
26837 .expect("the valid partial configuration must expose its merged candidate"),
26838 (6, 4),
26839 "the valid caller must merge against the last committed options, not the rejected candidate"
26840 );
26841
26842 let options = controller.options();
26843 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
26844 assert_eq!(options.thread_name_prefix, "partial-config-rollback");
26845 }
26846
26847 #[test]
26848 fn configure_after_backend_started_is_rejected() {
26849 let controller = RuntimeController::new();
26852 controller
26853 .configure(RuntimeOptions {
26854 flavor: RuntimeFlavor::CurrentThread,
26855 worker_threads: 1,
26856 max_blocking_tasks: 1,
26857 thread_name_prefix: "rd8".to_string(),
26858 park_deadline: None,
26859 drain_linger: DEFAULT_DRAIN_LINGER,
26860 })
26861 .expect("first configure before the backend exists must succeed");
26862
26863 let _backend = controller.backend();
26865
26866 let error = controller
26867 .configure(RuntimeOptions::default())
26868 .expect_err("configure after the backend started must be rejected");
26869 assert_eq!(
26870 error.to_string(),
26871 "the async runtime configuration is frozen; configure it before the first async call"
26872 );
26873 }
26874
26875 #[test]
26876 fn partial_configuration_after_backend_started_is_rejected() {
26877 let controller = current_thread_controller("partial-config-frozen");
26878 let _backend = controller.backend();
26879
26880 let error = controller
26881 .configure_partial(RuntimeOptionsPatch {
26882 worker_threads: Some(2),
26883 ..RuntimeOptionsPatch::default()
26884 })
26885 .expect_err("partial configuration after the backend started must be rejected");
26886 assert_eq!(
26887 error.to_string(),
26888 "the async runtime configuration is frozen; configure it before the first async call"
26889 );
26890 }
26891
26892 fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
26893 let controller = RuntimeController::new();
26894 controller
26895 .configure(RuntimeOptions {
26896 flavor: RuntimeFlavor::CurrentThread,
26897 worker_threads: 1,
26898 max_blocking_tasks: 1,
26899 thread_name_prefix: thread_name_prefix.to_string(),
26900 park_deadline: None,
26901 drain_linger: DEFAULT_DRAIN_LINGER,
26902 })
26903 .expect("CurrentThread test configuration must be accepted");
26904 controller
26905 }
26906
26907 #[cfg(napi_runtime_os_threads)]
26908 fn multi_thread_controller(
26909 thread_name_prefix: &str,
26910 worker_threads: usize,
26911 max_blocking_tasks: usize,
26912 ) -> RuntimeController {
26913 let controller = RuntimeController::new();
26914 controller
26915 .configure(RuntimeOptions {
26916 flavor: RuntimeFlavor::MultiThread,
26917 worker_threads,
26918 max_blocking_tasks,
26919 thread_name_prefix: thread_name_prefix.to_string(),
26920 park_deadline: None,
26921 drain_linger: DEFAULT_DRAIN_LINGER,
26922 })
26923 .expect("MultiThread test configuration must be accepted");
26924 controller
26925 }
26926
26927 #[test]
26928 fn blocking_backend_creation_failure_preserves_diagnostic() {
26929 let controller = current_thread_controller("blocking-backend-creation-failure");
26930 let ran = Arc::new(AtomicBool::new(false));
26931 let work_ran = Arc::clone(&ran);
26932
26933 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26934 let Err((error, work)) = controller.try_spawn_blocking(move || {
26935 work_ran.store(true, Ordering::SeqCst);
26936 17usize
26937 }) else {
26938 panic!("the injected backend creation failure must reject blocking work");
26939 };
26940 assert_eq!(
26941 error.to_string(),
26942 "injected async runtime backend creation failure"
26943 );
26944 assert!(
26945 !ran.load(Ordering::SeqCst),
26946 "rejected blocking work must not run"
26947 );
26948 assert_eq!(
26949 work(),
26950 17,
26951 "the rejected blocking closure must remain usable"
26952 );
26953 assert!(ran.load(Ordering::SeqCst));
26954
26955 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26956 let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
26957 .expect_err("infallible blocking submission must return a failed join handle");
26958 assert_eq!(
26959 error.to_string(),
26960 "injected async runtime backend creation failure"
26961 );
26962
26963 let handle = controller
26964 .try_spawn_blocking(|| 31usize)
26965 .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
26966 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
26967 controller.shutdown().unwrap();
26968 }
26969
26970 #[cfg(not(target_family = "wasm"))]
26971 #[test]
26972 fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
26973 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
26974
26975 if std::env::var_os(CHILD_ENV).is_some() {
26976 struct PollProbe {
26977 polls: Arc<AtomicUsize>,
26978 }
26979
26980 impl Future for PollProbe {
26981 type Output = ();
26982
26983 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26984 self.polls.fetch_add(1, Ordering::SeqCst);
26985 Poll::Ready(())
26986 }
26987 }
26988
26989 shutdown().expect("the isolated runtime must stop before block_on admission");
26990 let polls = Arc::new(AtomicUsize::new(0));
26991 let mut future = std::pin::pin!(PollProbe {
26992 polls: Arc::clone(&polls)
26993 });
26994 let error = try_block_on_dyn(future.as_mut())
26995 .expect_err("the stopped runtime must reject the borrowed future");
26996 assert_eq!(
26997 error.to_string(),
26998 "the async runtime is stopped; call start before submitting work"
26999 );
27000 assert_eq!(
27001 polls.load(Ordering::SeqCst),
27002 0,
27003 "rejected block_on must not poll its future"
27004 );
27005
27006 start().expect("the isolated runtime must restart");
27007 try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
27008 assert_eq!(polls.load(Ordering::SeqCst), 1);
27009 shutdown().expect("the restarted isolated runtime must stop");
27010 return;
27011 }
27012
27013 let output = std::process::Command::new(std::env::current_exe().unwrap())
27014 .arg("--exact")
27015 .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
27016 .arg("--nocapture")
27017 .env(CHILD_ENV, "1")
27018 .output()
27019 .expect("the stopped block_on subprocess must start");
27020 assert!(
27021 output.status.success(),
27022 "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
27023 output.status.code(),
27024 String::from_utf8_lossy(&output.stdout),
27025 String::from_utf8_lossy(&output.stderr)
27026 );
27027 }
27028
27029 #[cfg(not(target_family = "wasm"))]
27030 #[test]
27031 fn try_block_on_dyn_reports_concurrent_shutdown() {
27032 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
27033
27034 if std::env::var_os(CHILD_ENV).is_some() {
27035 use std::sync::mpsc;
27036
27037 struct PendingProbe {
27038 entered: Option<mpsc::Sender<()>>,
27039 complete: Arc<AtomicBool>,
27040 }
27041
27042 impl Future for PendingProbe {
27043 type Output = ();
27044
27045 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27046 if let Some(entered) = self.entered.take() {
27047 entered.send(()).unwrap();
27048 }
27049 if self.complete.load(Ordering::SeqCst) {
27050 Poll::Ready(())
27051 } else {
27052 Poll::Pending
27053 }
27054 }
27055 }
27056
27057 configure(RuntimeOptions {
27058 flavor: RuntimeFlavor::CurrentThread,
27059 worker_threads: 1,
27060 max_blocking_tasks: 1,
27061 thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
27062 park_deadline: None,
27063 drain_linger: DEFAULT_DRAIN_LINGER,
27064 })
27065 .expect("the isolated runtime must accept CurrentThread configuration");
27066 start().expect("the isolated runtime must start");
27067
27068 let (entered_tx, entered_rx) = mpsc::channel();
27069 let (error_tx, error_rx) = mpsc::channel();
27070 let (retry_tx, retry_rx) = mpsc::channel();
27071 let complete = Arc::new(AtomicBool::new(false));
27072 let future_complete = Arc::clone(&complete);
27073 let runner = std::thread::spawn(move || {
27074 let mut future = std::pin::pin!(PendingProbe {
27075 entered: Some(entered_tx),
27076 complete: future_complete
27077 });
27078 let error = try_block_on_dyn(future.as_mut())
27079 .expect_err("concurrent shutdown must interrupt the borrowed future")
27080 .to_string();
27081 error_tx.send(error).unwrap();
27082 retry_rx.recv().unwrap();
27083 try_block_on_dyn(future.as_mut())
27084 .expect("restart must accept the same interrupted borrowed future");
27085 });
27086 entered_rx
27087 .recv_timeout(Duration::from_secs(2))
27088 .expect("the borrowed future must be polled before shutdown");
27089 shutdown().expect("shutdown must wait for the active block_on scope to retire");
27090 assert_eq!(
27091 error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27092 "the async runtime stopped before block_on completed its future"
27093 );
27094 start().expect("the isolated runtime must restart after the interrupted drive");
27095 complete.store(true, Ordering::SeqCst);
27096 retry_tx.send(()).unwrap();
27097 runner.join().unwrap();
27098 shutdown().expect("the retried isolated runtime must stop");
27099 return;
27100 }
27101
27102 let output = std::process::Command::new(std::env::current_exe().unwrap())
27103 .arg("--exact")
27104 .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
27105 .arg("--nocapture")
27106 .env(CHILD_ENV, "1")
27107 .output()
27108 .expect("the concurrent block_on shutdown subprocess must start");
27109 assert!(
27110 output.status.success(),
27111 "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
27112 output.status.code(),
27113 String::from_utf8_lossy(&output.stdout),
27114 String::from_utf8_lossy(&output.stderr)
27115 );
27116 }
27117
27118 #[test]
27119 fn initial_lifecycle_start_preserves_lazy_configuration() {
27120 let controller = RuntimeController::new();
27121 controller
27122 .start()
27123 .expect("initial lifecycle start must succeed");
27124 controller
27125 .configure(RuntimeOptions {
27126 flavor: RuntimeFlavor::CurrentThread,
27127 worker_threads: 1,
27128 max_blocking_tasks: 1,
27129 thread_name_prefix: "lifecycle-initial-start".to_string(),
27130 park_deadline: None,
27131 drain_linger: DEFAULT_DRAIN_LINGER,
27132 })
27133 .expect("module-registration start must not consume the configuration window");
27134
27135 let state = controller
27136 .state
27137 .lock()
27138 .unwrap_or_else(std::sync::PoisonError::into_inner);
27139 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27140 }
27141
27142 #[test]
27143 fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
27144 let controller = RuntimeController::new();
27145
27146 for _ in 0..3 {
27147 controller
27148 .start()
27149 .expect("zero-work lifecycle start must succeed");
27150 assert!(matches!(
27151 &controller
27152 .state
27153 .lock()
27154 .unwrap_or_else(std::sync::PoisonError::into_inner)
27155 .lifecycle,
27156 RuntimeLifecycle::Initial
27157 ));
27158
27159 controller
27160 .shutdown()
27161 .expect("zero-work lifecycle shutdown must succeed");
27162 assert!(matches!(
27163 &controller
27164 .state
27165 .lock()
27166 .unwrap_or_else(std::sync::PoisonError::into_inner)
27167 .lifecycle,
27168 RuntimeLifecycle::StoppedBeforeFirstUse
27169 ));
27170 }
27171
27172 controller
27173 .start()
27174 .expect("a later zero-work restart must remain lazy");
27175 controller
27176 .configure(RuntimeOptions {
27177 flavor: RuntimeFlavor::CurrentThread,
27178 worker_threads: 1,
27179 max_blocking_tasks: 1,
27180 thread_name_prefix: "lifecycle-zero-work-config".to_string(),
27181 park_deadline: None,
27182 drain_linger: DEFAULT_DRAIN_LINGER,
27183 })
27184 .expect("zero-work lifecycle cycles must not consume the configuration window");
27185
27186 let state = controller
27187 .state
27188 .lock()
27189 .unwrap_or_else(std::sync::PoisonError::into_inner);
27190 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27191 }
27192
27193 #[cfg(napi_runtime_os_threads)]
27194 #[test]
27195 fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
27196 use std::sync::mpsc;
27197
27198 struct BlockingDropFuture {
27199 entered: mpsc::Sender<()>,
27200 release: mpsc::Receiver<()>,
27201 }
27202
27203 impl Future for BlockingDropFuture {
27204 type Output = ();
27205
27206 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27207 Poll::Pending
27208 }
27209 }
27210
27211 impl Drop for BlockingDropFuture {
27212 fn drop(&mut self) {
27213 self.entered.send(()).unwrap();
27214 self.release.recv().unwrap();
27215 }
27216 }
27217
27218 let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
27219 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27220 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27221 let submit_controller = Arc::clone(&controller);
27222 let submitter = std::thread::spawn(move || {
27223 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27224 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27225 entered: drop_entered_tx,
27226 release: release_drop_rx,
27227 }));
27228 assert!(
27229 result.is_err(),
27230 "the injected backend creation failure must reject the submission"
27231 );
27232 });
27233 drop_entered_rx
27234 .recv_timeout(Duration::from_secs(2))
27235 .expect("the rejected future destructor must start");
27236
27237 let retry_ran = Arc::new(AtomicBool::new(false));
27238 let retry_ran_task = Arc::clone(&retry_ran);
27239 let Err(retry) = controller.try_spawn_detached(async move {
27240 retry_ran_task.store(true, Ordering::SeqCst);
27241 }) else {
27242 panic!("backend creation must remain closed during rejected destruction");
27243 };
27244 drop(retry);
27245 assert!(!retry_ran.load(Ordering::SeqCst));
27246
27247 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27248 let shutdown_controller = Arc::clone(&controller);
27249 let shutdown = std::thread::spawn(move || {
27250 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27251 });
27252 wait_until("initial shutdown to close runtime admission", || {
27253 matches!(
27254 controller
27255 .state
27256 .lock()
27257 .unwrap_or_else(std::sync::PoisonError::into_inner)
27258 .lifecycle,
27259 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
27260 )
27261 });
27262 assert!(
27263 shutdown_rx
27264 .recv_timeout(Duration::from_millis(200))
27265 .is_err(),
27266 "initial shutdown must wait for rejected user state destruction"
27267 );
27268 assert!(
27269 controller.try_spawn_detached(async {}).is_err(),
27270 "shutdown must keep first-generation admission closed while it waits"
27271 );
27272
27273 let (start_tx, start_rx) = mpsc::channel();
27274 let start_controller = Arc::clone(&controller);
27275 let starter = std::thread::spawn(move || {
27276 start_tx.send(start_controller.start()).unwrap();
27277 });
27278 assert!(
27279 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27280 "start must wait for the zero-backend shutdown transition to finish"
27281 );
27282
27283 release_drop_tx.send(()).unwrap();
27284 submitter.join().unwrap();
27285 shutdown_rx
27286 .recv_timeout(Duration::from_secs(2))
27287 .unwrap()
27288 .unwrap();
27289 shutdown.join().unwrap();
27290 start_rx
27291 .recv_timeout(Duration::from_secs(2))
27292 .unwrap()
27293 .unwrap();
27294 starter.join().unwrap();
27295
27296 let ran = Arc::new(AtomicBool::new(false));
27297 let ran_task = Arc::clone(&ran);
27298 controller
27299 .try_spawn_detached(async move {
27300 ran_task.store(true, Ordering::SeqCst);
27301 })
27302 .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
27303 let dispatch = controller_current_thread_dispatch(&controller);
27304 controller.drive_current_thread_dispatch(dispatch, false);
27305 assert!(ran.load(Ordering::SeqCst));
27306 controller.shutdown().unwrap();
27307 }
27308
27309 #[cfg(napi_runtime_os_threads)]
27310 #[test]
27311 fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
27312 use std::sync::mpsc;
27313
27314 struct BlockingDropFuture {
27315 entered: mpsc::Sender<()>,
27316 release: mpsc::Receiver<()>,
27317 }
27318
27319 impl Future for BlockingDropFuture {
27320 type Output = ();
27321
27322 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27323 Poll::Pending
27324 }
27325 }
27326
27327 impl Drop for BlockingDropFuture {
27328 fn drop(&mut self) {
27329 self.entered.send(()).unwrap();
27330 self.release.recv().unwrap();
27331 }
27332 }
27333
27334 let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
27335 let old_generation = controller.backend().generation();
27336 controller.shutdown().unwrap();
27337
27338 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27339 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27340 let submit_controller = Arc::clone(&controller);
27341 let submitter = std::thread::spawn(move || {
27342 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27343 entered: drop_entered_tx,
27344 release: release_drop_rx,
27345 }));
27346 assert!(
27347 result.is_err(),
27348 "the stopped runtime must reject the submission"
27349 );
27350 });
27351 drop_entered_rx
27352 .recv_timeout(Duration::from_secs(2))
27353 .unwrap();
27354
27355 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27356 let shutdown_controller = Arc::clone(&controller);
27357 let shutdown = std::thread::spawn(move || {
27358 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27359 });
27360 wait_until(
27361 "stopped shutdown to enter its zero-backend transition",
27362 || {
27363 matches!(
27364 controller
27365 .state
27366 .lock()
27367 .unwrap_or_else(std::sync::PoisonError::into_inner)
27368 .lifecycle,
27369 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
27370 )
27371 },
27372 );
27373
27374 let (start_tx, start_rx) = mpsc::channel();
27375 let start_controller = Arc::clone(&controller);
27376 let starter = std::thread::spawn(move || {
27377 start_tx.send(start_controller.start()).unwrap();
27378 });
27379 assert!(
27380 shutdown_rx
27381 .recv_timeout(Duration::from_millis(200))
27382 .is_err(),
27383 "stopped shutdown must wait for rejected destruction"
27384 );
27385 assert!(
27386 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27387 "restart must wait for the stopped zero-backend transition"
27388 );
27389
27390 release_drop_tx.send(()).unwrap();
27391 submitter.join().unwrap();
27392 shutdown_rx
27393 .recv_timeout(Duration::from_secs(2))
27394 .unwrap()
27395 .unwrap();
27396 shutdown.join().unwrap();
27397 start_rx
27398 .recv_timeout(Duration::from_secs(2))
27399 .unwrap()
27400 .unwrap();
27401 starter.join().unwrap();
27402
27403 let new_generation = controller.backend().generation();
27404 assert_ne!(new_generation, old_generation);
27405 controller.shutdown().unwrap();
27406 }
27407
27408 #[test]
27409 fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
27410 use std::sync::mpsc;
27411
27412 struct ShutdownOnDrop {
27413 controller: Arc<RuntimeController>,
27414 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27415 }
27416
27417 impl Future for ShutdownOnDrop {
27418 type Output = ();
27419
27420 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27421 Poll::Pending
27422 }
27423 }
27424
27425 impl Drop for ShutdownOnDrop {
27426 fn drop(&mut self) {
27427 self.result.send(self.controller.shutdown()).unwrap();
27428 }
27429 }
27430
27431 let controller = Arc::new(current_thread_controller(
27432 "failed-initial-reentrant-shutdown",
27433 ));
27434 let (result_tx, result_rx) = mpsc::channel();
27435 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27436 let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
27437 controller: Arc::clone(&controller),
27438 result: result_tx,
27439 }));
27440 assert!(rejected.is_err());
27441
27442 let error = result_rx
27443 .recv_timeout(Duration::from_secs(1))
27444 .expect("the rejected destructor must not self-deadlock")
27445 .expect_err("rejected destruction cannot wait for its own shutdown");
27446 assert!(error.to_string().contains("rejected submission"));
27447 {
27448 let state = controller
27449 .state
27450 .lock()
27451 .unwrap_or_else(std::sync::PoisonError::into_inner);
27452 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27453 assert_eq!(state.rejected_drops, 0);
27454 }
27455
27456 let handle = controller
27457 .try_spawn(async { 23usize })
27458 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27459 let dispatch = controller_current_thread_dispatch(&controller);
27460 controller.drive_current_thread_dispatch(dispatch, false);
27461 assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
27462 controller.shutdown().unwrap();
27463 }
27464
27465 #[cfg(napi_runtime_os_threads)]
27466 #[test]
27467 fn failed_initial_submission_destructor_cannot_reenter_start() {
27468 use std::sync::mpsc;
27469
27470 struct StartOnDrop {
27471 controller: Arc<RuntimeController>,
27472 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27473 release: mpsc::Receiver<()>,
27474 }
27475
27476 impl Future for StartOnDrop {
27477 type Output = ();
27478
27479 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27480 Poll::Pending
27481 }
27482 }
27483
27484 impl Drop for StartOnDrop {
27485 fn drop(&mut self) {
27486 self.result.send(self.controller.start()).unwrap();
27487 self.release.recv().unwrap();
27488 }
27489 }
27490
27491 let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
27492 let (result_tx, result_rx) = mpsc::channel();
27493 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27494 let (submission_tx, submission_rx) = mpsc::channel();
27495 let submit_controller = Arc::clone(&controller);
27496 let submitter = std::thread::spawn(move || {
27497 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27498 let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
27499 controller: Arc::clone(&submit_controller),
27500 result: result_tx,
27501 release: release_drop_rx,
27502 }));
27503 submission_tx.send(rejected).unwrap();
27504 });
27505
27506 let error = result_rx
27507 .recv_timeout(Duration::from_secs(1))
27508 .expect("the rejected destructor must not self-deadlock")
27509 .expect_err("rejected destruction must not report a successful runtime start");
27510 assert!(error.to_string().contains("rejected submission"));
27511 {
27512 let state = controller
27513 .state
27514 .lock()
27515 .unwrap_or_else(std::sync::PoisonError::into_inner);
27516 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27517 assert_eq!(state.rejected_drops, 1);
27518 }
27519
27520 let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
27521 let (external_start_tx, external_start_rx) = mpsc::channel();
27522 let start_controller = Arc::clone(&controller);
27523 let starter = std::thread::spawn(move || {
27524 BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
27525 *slot.borrow_mut() = Some(Box::new(move || {
27526 wait_entered_tx.send(()).unwrap();
27527 }));
27528 });
27529 external_start_tx.send(start_controller.start()).unwrap();
27530 });
27531 wait_entered_rx
27532 .recv_timeout(Duration::from_secs(1))
27533 .expect("external start must reach the Initial rejected-destruction wait");
27534 assert!(
27535 external_start_rx.try_recv().is_err(),
27536 "external start must not return while rejected destruction remains active"
27537 );
27538
27539 release_drop_tx.send(()).unwrap();
27540 assert!(
27541 submission_rx
27542 .recv_timeout(Duration::from_secs(2))
27543 .unwrap()
27544 .is_err()
27545 );
27546 submitter.join().unwrap();
27547 external_start_rx
27548 .recv_timeout(Duration::from_secs(2))
27549 .unwrap()
27550 .unwrap();
27551 starter.join().unwrap();
27552
27553 {
27554 let state = controller
27555 .state
27556 .lock()
27557 .unwrap_or_else(std::sync::PoisonError::into_inner);
27558 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27559 assert_eq!(state.rejected_drops, 0);
27560 }
27561
27562 let handle = controller
27563 .try_spawn(async { 31usize })
27564 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27565 let dispatch = controller_current_thread_dispatch(&controller);
27566 controller.drive_current_thread_dispatch(dispatch, false);
27567 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27568 controller.shutdown().unwrap();
27569 }
27570
27571 #[cfg(panic = "unwind")]
27572 #[test]
27573 fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
27574 use std::sync::mpsc;
27575
27576 struct BlockingDropFuture {
27577 controller: Arc<RuntimeController>,
27578 entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
27579 release: mpsc::Receiver<()>,
27580 }
27581
27582 impl Future for BlockingDropFuture {
27583 type Output = ();
27584
27585 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27586 Poll::Pending
27587 }
27588 }
27589
27590 impl Drop for BlockingDropFuture {
27591 fn drop(&mut self) {
27592 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
27593 self
27594 .entered
27595 .send((generation, self.controller.shutdown()))
27596 .unwrap();
27597 self.release.recv().unwrap();
27598 }
27599 }
27600
27601 let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
27602 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27603 let (release_drop_tx, release_drop_rx) = mpsc::channel();
27604 let (runner_done_tx, runner_done_rx) = mpsc::channel();
27605 let runner_controller = Arc::clone(&controller);
27606 let runner = std::thread::spawn(move || {
27607 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27608 let result = catch_unwind(AssertUnwindSafe(|| {
27609 block_on_with_controller(
27610 &runner_controller,
27611 BlockingDropFuture {
27612 controller: Arc::clone(&runner_controller),
27613 entered: drop_entered_tx,
27614 release: release_drop_rx,
27615 },
27616 );
27617 }));
27618 runner_done_tx.send(result.is_err()).unwrap();
27619 });
27620
27621 let (generation, reentrant_shutdown) = drop_entered_rx
27622 .recv_timeout(Duration::from_secs(2))
27623 .expect("failed block_on acquisition must destroy its owned future");
27624 assert_eq!(generation, None, "no runtime generation was created");
27625 let error = reentrant_shutdown
27626 .expect_err("rejected block_on destruction cannot wait for its own shutdown");
27627 assert!(error.to_string().contains("rejected submission"));
27628
27629 let Err(retry_error) = controller.try_backend() else {
27630 panic!("backend retry must remain closed during rejected block_on destruction");
27631 };
27632 assert!(retry_error.to_string().contains("rejected submission"));
27633
27634 let (shutdown_tx, shutdown_rx) = mpsc::channel();
27635 let shutdown_controller = Arc::clone(&controller);
27636 let shutdown = std::thread::spawn(move || {
27637 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27638 });
27639 assert!(
27640 shutdown_rx
27641 .recv_timeout(Duration::from_millis(200))
27642 .is_err(),
27643 "initial shutdown must wait for rejected block_on destruction"
27644 );
27645
27646 release_drop_tx.send(()).unwrap();
27647 assert!(
27648 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27649 "block_on must preserve its infallible panic contract after contained cleanup"
27650 );
27651 runner.join().unwrap();
27652 shutdown_rx
27653 .recv_timeout(Duration::from_secs(2))
27654 .unwrap()
27655 .unwrap();
27656 shutdown.join().unwrap();
27657
27658 controller
27659 .start()
27660 .expect("zero-generation shutdown must remain restartable");
27661 let ran = Arc::new(AtomicBool::new(false));
27662 let ran_task = Arc::clone(&ran);
27663 controller
27664 .try_spawn_detached(async move {
27665 ran_task.store(true, Ordering::SeqCst);
27666 })
27667 .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
27668 let dispatch = controller_current_thread_dispatch(&controller);
27669 controller.drive_current_thread_dispatch(dispatch, false);
27670 assert!(ran.load(Ordering::SeqCst));
27671 controller.shutdown().unwrap();
27672 }
27673
27674 #[cfg(not(target_family = "wasm"))]
27675 #[test]
27676 fn public_block_on_contains_poll_and_drop_double_panic() {
27677 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
27678
27679 if std::env::var_os(CHILD_ENV).is_some() {
27680 struct PanicInPollAndDrop;
27681
27682 impl Future for PanicInPollAndDrop {
27683 type Output = ();
27684
27685 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27686 panic!("intentional block_on poll panic");
27687 }
27688 }
27689
27690 impl Drop for PanicInPollAndDrop {
27691 fn drop(&mut self) {
27692 panic!("intentional block_on future destructor panic");
27693 }
27694 }
27695
27696 let controller = current_thread_controller("block-on-double-panic");
27697 assert!(
27698 catch_unwind(AssertUnwindSafe(|| {
27699 block_on_with_controller(&controller, PanicInPollAndDrop);
27700 }))
27701 .is_err(),
27702 "the original poll panic must still reach the caller"
27703 );
27704 controller
27705 .shutdown()
27706 .expect("contained future destruction must not strand the runtime");
27707 return;
27708 }
27709
27710 let output = std::process::Command::new(std::env::current_exe().unwrap())
27711 .arg("--exact")
27712 .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
27713 .arg("--nocapture")
27714 .env(CHILD_ENV, "1")
27715 .output()
27716 .expect("the subprocess test must start");
27717 assert!(
27718 output.status.success(),
27719 "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
27720 output.status.code(),
27721 String::from_utf8_lossy(&output.stdout),
27722 String::from_utf8_lossy(&output.stderr)
27723 );
27724 }
27725
27726 #[test]
27727 fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
27728 let controller = RuntimeController::new();
27729 controller
27730 .start()
27731 .expect("initial lifecycle start must succeed");
27732 controller
27733 .shutdown()
27734 .expect("zero-work lifecycle shutdown must succeed");
27735 controller
27736 .start()
27737 .expect("zero-work lifecycle restart must remain lazy");
27738 controller
27739 .configure(RuntimeOptions {
27740 flavor: RuntimeFlavor::CurrentThread,
27741 worker_threads: 1,
27742 max_blocking_tasks: 1,
27743 thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
27744 park_deadline: None,
27745 drain_linger: DEFAULT_DRAIN_LINGER,
27746 })
27747 .expect("configuration before first real async use must succeed");
27748
27749 let first_generation = controller.backend().generation();
27750 controller
27751 .shutdown()
27752 .expect("the first real generation must stop");
27753 assert!(matches!(
27754 &controller
27755 .state
27756 .lock()
27757 .unwrap_or_else(std::sync::PoisonError::into_inner)
27758 .lifecycle,
27759 RuntimeLifecycle::Stopped
27760 ));
27761 let error = controller
27762 .configure(RuntimeOptions::default())
27763 .expect_err("a real generation must permanently freeze configuration");
27764 assert_eq!(
27765 error.to_string(),
27766 "the async runtime configuration is frozen; configure it before the first async call"
27767 );
27768
27769 controller
27770 .start()
27771 .expect("the stopped real generation must restart");
27772 let restarted_generation = {
27773 let state = controller
27774 .state
27775 .lock()
27776 .unwrap_or_else(std::sync::PoisonError::into_inner);
27777 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
27778 panic!("a real generation restart must eagerly create its replacement backend");
27779 };
27780 backend.generation()
27781 };
27782 assert_ne!(
27783 restarted_generation, first_generation,
27784 "a real generation restart must not reuse the retired backend"
27785 );
27786 controller
27787 .shutdown()
27788 .expect("the replacement generation must stop");
27789 }
27790
27791 #[test]
27792 fn runtime_controller_rejects_submissions_after_shutdown() {
27793 let controller = current_thread_controller("lifecycle-reject");
27794 controller.start().expect("runtime must start");
27795 drop(controller.backend());
27796 controller.shutdown().expect("runtime must stop");
27797
27798 let async_ran = Arc::new(AtomicBool::new(false));
27799 let async_ran_task = Arc::clone(&async_ran);
27800 let Err(rejected_future) = controller.try_spawn_detached(async move {
27801 async_ran_task.store(true, Ordering::SeqCst);
27802 }) else {
27803 panic!("shutdown must reject async work");
27804 };
27805 assert!(
27806 !async_ran.load(Ordering::SeqCst),
27807 "a rejected future must not be polled"
27808 );
27809 futures::executor::block_on(rejected_future);
27810 assert!(
27811 async_ran.load(Ordering::SeqCst),
27812 "the original future must remain usable"
27813 );
27814
27815 let blocking_ran = Arc::new(AtomicBool::new(false));
27816 let blocking_ran_task = Arc::clone(&blocking_ran);
27817 let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
27818 blocking_ran_task.store(true, Ordering::SeqCst);
27819 7
27820 }) else {
27821 panic!("shutdown must reject blocking work");
27822 };
27823 assert_eq!(
27824 blocking_error.to_string(),
27825 "the async runtime is stopped; call start before submitting work"
27826 );
27827 assert!(
27828 !blocking_ran.load(Ordering::SeqCst),
27829 "rejected blocking work must not run"
27830 );
27831 assert_eq!(
27832 rejected_work(),
27833 7,
27834 "the original closure must remain usable"
27835 );
27836
27837 let config_error = controller
27838 .configure(RuntimeOptions::default())
27839 .expect_err("shutdown must not make runtime configuration mutable again");
27840 assert_eq!(
27841 config_error.to_string(),
27842 "the async runtime configuration is frozen; configure it before the first async call"
27843 );
27844
27845 let state = controller
27846 .state
27847 .lock()
27848 .unwrap_or_else(std::sync::PoisonError::into_inner);
27849 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
27850 }
27851
27852 #[test]
27853 fn rejected_future_can_be_resubmitted_after_restart() {
27854 let controller = current_thread_controller("lifecycle-retry-future");
27855 controller.start().expect("runtime must start");
27856 drop(controller.backend());
27857 controller.shutdown().expect("runtime must stop");
27858
27859 let runs = Arc::new(AtomicUsize::new(0));
27860 let runs_task = Arc::clone(&runs);
27861 let future = async move {
27862 runs_task.fetch_add(1, Ordering::SeqCst);
27863 23
27864 };
27865 let Err((error, future)) = controller.try_spawn(future) else {
27866 panic!("stopped runtime must reject the future");
27867 };
27868 assert!(error.to_string().contains("runtime is stopped"));
27869 assert_eq!(runs.load(Ordering::SeqCst), 0);
27870
27871 controller.start().expect("runtime must restart");
27872 let handle = controller
27873 .try_spawn(future)
27874 .unwrap_or_else(|_| panic!("restart must accept retained future"));
27875 let dispatch = controller_current_thread_dispatch(&controller);
27876 controller.drive_current_thread_dispatch(dispatch, false);
27877 assert_eq!(
27878 futures::executor::block_on(handle).expect("retained future must complete"),
27879 23
27880 );
27881 assert_eq!(runs.load(Ordering::SeqCst), 1);
27882 controller
27883 .shutdown()
27884 .expect("replacement runtime must stop");
27885 }
27886
27887 #[test]
27888 fn runtime_controller_start_after_shutdown_accepts_work() {
27889 let controller = current_thread_controller("lifecycle-restart");
27890 controller.start().expect("runtime must start");
27891 drop(controller.backend());
27892 controller.shutdown().expect("runtime must stop");
27893 controller.start().expect("runtime must restart");
27894
27895 let async_ran = Arc::new(AtomicBool::new(false));
27896 let async_ran_task = Arc::clone(&async_ran);
27897 controller
27898 .try_spawn_detached(async move {
27899 async_ran_task.store(true, Ordering::SeqCst);
27900 })
27901 .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
27902 assert!(
27903 !async_ran.load(Ordering::SeqCst),
27904 "hostless CurrentThread submission must remain enqueue-only"
27905 );
27906 let dispatch = controller_current_thread_dispatch(&controller);
27907 controller.drive_current_thread_dispatch(dispatch, false);
27908 assert!(
27909 async_ran.load(Ordering::SeqCst),
27910 "restarted runtime must poll submitted work"
27911 );
27912
27913 let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
27914 panic!("restarted runtime must accept blocking work");
27915 };
27916 assert_eq!(
27917 futures::executor::block_on(handle).expect("blocking work must complete after restart"),
27918 11
27919 );
27920 }
27921
27922 #[test]
27923 fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
27924 let controller = current_thread_controller("stale-host-generation");
27925 let old_generation = controller.backend().generation();
27926 let old_dispatch = controller_current_thread_dispatch(&controller);
27927 controller.shutdown().unwrap();
27928 controller.start().unwrap();
27929 let new_generation = controller.backend().generation();
27930 assert_ne!(new_generation, old_generation);
27931
27932 let ran = Arc::new(AtomicBool::new(false));
27933 let ran_task = Arc::clone(&ran);
27934 controller
27935 .try_spawn_detached(async move {
27936 ran_task.store(true, Ordering::SeqCst);
27937 })
27938 .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
27939 let new_dispatch = controller_current_thread_dispatch(&controller);
27940 assert_ne!(new_dispatch, old_dispatch);
27941
27942 controller.drive_current_thread_dispatch(old_dispatch, false);
27943 assert!(
27944 !ran.load(Ordering::SeqCst),
27945 "a callback from the retired generation must not touch the restarted executor"
27946 );
27947 controller.drive_current_thread_dispatch(new_dispatch, false);
27948 assert!(ran.load(Ordering::SeqCst));
27949 controller.shutdown().unwrap();
27950 }
27951
27952 #[test]
27953 fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
27954 let controller = current_thread_controller("stale-host-cancellation");
27955 let old_generation = controller.backend().generation();
27956 let old_dispatch = controller_current_thread_dispatch(&controller);
27957 controller.shutdown().unwrap();
27958 controller.start().unwrap();
27959 let backend = controller.backend();
27960 let new_generation = backend.generation();
27961 assert_ne!(new_generation, old_generation);
27962 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
27963 panic!("configured CurrentThread backend must create a CurrentThread executor");
27964 };
27965 let new_dispatch = current_thread_dispatch(executor);
27966 assert_ne!(new_dispatch, old_dispatch);
27967
27968 controller.cancel_current_thread_dispatch_for_test(old_dispatch);
27969 assert_eq!(
27970 executor.dispatch_pending.load(Ordering::Acquire),
27971 new_dispatch,
27972 "a delayed failure from the retired generation must not release the replacement latch"
27973 );
27974
27975 controller.cancel_current_thread_dispatch_for_test(new_dispatch);
27976 assert_eq!(
27977 executor.dispatch_pending.load(Ordering::Acquire),
27978 0,
27979 "the live generation must be able to cancel its accepted dispatch"
27980 );
27981 drop(backend);
27982 controller.shutdown().unwrap();
27983 }
27984
27985 #[cfg(napi_runtime_os_threads)]
27986 #[test]
27987 fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
27988 use std::sync::mpsc;
27989
27990 type ProbeResult = (
27991 &'static str,
27992 Option<u64>,
27993 Result<(), String>,
27994 Result<(), String>,
27995 Result<(), String>,
27996 );
27997
27998 struct StaleGenerationProbe {
27999 label: &'static str,
28000 controller: Arc<RuntimeController>,
28001 result: mpsc::Sender<ProbeResult>,
28002 }
28003
28004 impl Drop for StaleGenerationProbe {
28005 fn drop(&mut self) {
28006 let generation = active_runtime_generation();
28007 let submission = match self.controller.try_spawn(async {}) {
28008 Ok(handle) => {
28009 drop(handle);
28010 Ok(())
28011 }
28012 Err((error, future)) => {
28013 drop(future);
28014 Err(error.to_string())
28015 }
28016 };
28017 let start = self.controller.start().map_err(|error| error.to_string());
28018 let shutdown = self
28019 .controller
28020 .shutdown()
28021 .map_err(|error| error.to_string());
28022 self
28023 .result
28024 .send((self.label, generation, submission, start, shutdown))
28025 .unwrap();
28026 }
28027 }
28028
28029 let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
28030 let old_generation = controller.backend().generation();
28031 let (result_tx, result_rx) = mpsc::channel();
28032
28033 let async_controller = Arc::clone(&controller);
28034 let async_result = result_tx.clone();
28035 let async_handle = controller
28036 .try_spawn(async move {
28037 StaleGenerationProbe {
28038 label: "async",
28039 controller: async_controller,
28040 result: async_result,
28041 }
28042 })
28043 .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
28044 let old_dispatch = controller_current_thread_dispatch(&controller);
28045 controller.drive_current_thread_dispatch(old_dispatch, false);
28046
28047 let ready_controller = Arc::clone(&controller);
28048 let ready_handle = controller
28049 .try_spawn_blocking(move || StaleGenerationProbe {
28050 label: "ready",
28051 controller: ready_controller,
28052 result: result_tx,
28053 })
28054 .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
28055
28056 controller.shutdown().unwrap();
28057 controller.start().unwrap();
28058 let new_generation = controller.backend().generation();
28059 assert_ne!(new_generation, old_generation);
28060
28061 drop(async_handle);
28062 let async_result = result_rx
28063 .recv_timeout(Duration::from_secs(2))
28064 .expect("the retained async output must be destroyed without waiting on the replacement");
28065 drop(ready_handle);
28066 let ready_result = result_rx
28067 .recv_timeout(Duration::from_secs(2))
28068 .expect("the retained immediate output must be destroyed without touching the replacement");
28069
28070 for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
28071 let (label, generation, submission, start, shutdown) = result;
28072 assert_eq!(label, expected_label);
28073 assert_eq!(generation, Some(old_generation));
28074 for error in [submission, start, shutdown] {
28075 assert!(
28076 error
28077 .expect_err("retired-generation output destruction must be rejected")
28078 .contains("retired generation")
28079 );
28080 }
28081 }
28082
28083 let survivor = controller
28084 .try_spawn(async { 29usize })
28085 .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
28086 let new_dispatch = controller_current_thread_dispatch(&controller);
28087 controller.drive_current_thread_dispatch(new_dispatch, false);
28088 assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
28089 controller.shutdown().unwrap();
28090 }
28091
28092 #[cfg(napi_runtime_os_threads)]
28093 #[test]
28094 fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
28095 use std::sync::mpsc;
28096
28097 struct ShutdownOnDrop {
28098 controller: Arc<RuntimeController>,
28099 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28100 }
28101
28102 impl Drop for ShutdownOnDrop {
28103 fn drop(&mut self) {
28104 self
28105 .result
28106 .send((active_runtime_generation(), self.controller.shutdown()))
28107 .unwrap();
28108 }
28109 }
28110
28111 let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
28112 let old_generation = controller.backend().generation();
28113 let (result_tx, result_rx) = mpsc::channel();
28114 let old_handle = controller
28115 .try_spawn_blocking({
28116 let controller = Arc::clone(&controller);
28117 move || ShutdownOnDrop {
28118 controller,
28119 result: result_tx,
28120 }
28121 })
28122 .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
28123 wait_until(
28124 "the first-generation blocking result to be buffered",
28125 || {
28126 controller
28127 .metrics
28128 .blocking_tasks_completed
28129 .load(Ordering::Acquire)
28130 == 1
28131 },
28132 );
28133 controller.shutdown().unwrap();
28134 controller.start().unwrap();
28135 let new_generation = controller.backend().generation();
28136 assert_ne!(new_generation, old_generation);
28137
28138 let (holder_started_tx, holder_started_rx) = mpsc::channel();
28139 let (release_holder_tx, release_holder_rx) = mpsc::channel();
28140 let holder = controller
28141 .try_spawn_blocking(move || {
28142 holder_started_tx.send(()).unwrap();
28143 release_holder_rx.recv().unwrap();
28144 })
28145 .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
28146 holder_started_rx
28147 .recv_timeout(Duration::from_secs(2))
28148 .unwrap();
28149
28150 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28151 let shutdown_controller = Arc::clone(&controller);
28152 let shutdown = std::thread::spawn(move || {
28153 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28154 });
28155 wait_until("the replacement generation to enter Stopping", || {
28156 matches!(
28157 controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
28158 RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
28159 )
28160 });
28161
28162 let dropper = std::thread::spawn(move || drop(old_handle));
28163 let (observed_generation, reentrant_shutdown) =
28164 match result_rx.recv_timeout(Duration::from_secs(2)) {
28165 Ok(result) => result,
28166 Err(error) => {
28167 release_holder_tx.send(()).unwrap();
28168 let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
28169 let _ = dropper.join();
28170 let _ = shutdown.join();
28171 panic!("retained first-generation output waited on replacement shutdown: {error}");
28172 }
28173 };
28174 assert_eq!(observed_generation, Some(old_generation));
28175 let error = reentrant_shutdown
28176 .expect_err("a retained first-generation output must not control replacement shutdown");
28177 assert!(error.to_string().contains("retired generation"));
28178
28179 release_holder_tx.send(()).unwrap();
28180 shutdown_rx
28181 .recv_timeout(Duration::from_secs(2))
28182 .unwrap()
28183 .unwrap();
28184 dropper.join().unwrap();
28185 shutdown.join().unwrap();
28186 futures::executor::block_on(holder).unwrap();
28187 }
28188
28189 #[cfg(napi_runtime_os_threads)]
28190 #[test]
28191 fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
28192 use std::sync::mpsc;
28193
28194 let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
28195 let generation = controller.backend().generation();
28196 let dispatch = controller_current_thread_dispatch(&controller);
28197 let (admitted_tx, admitted_rx) = mpsc::channel();
28198 let (release_tx, release_rx) = mpsc::channel();
28199 let driver_controller = Arc::clone(&controller);
28200 let driver = std::thread::spawn(move || {
28201 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
28202 *slot.borrow_mut() = Some(Box::new(move || {
28203 admitted_tx.send(()).unwrap();
28204 release_rx.recv().unwrap();
28205 }));
28206 });
28207 driver_controller.drive_current_thread_dispatch(dispatch, false);
28208 });
28209 admitted_rx
28210 .recv_timeout(Duration::from_secs(2))
28211 .expect("the host turn must claim its scheduler role before execution");
28212
28213 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28214 let shutdown_controller = Arc::clone(&controller);
28215 let shutdown = std::thread::spawn(move || {
28216 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28217 });
28218 wait_until(
28219 "shutdown to close the admitted CurrentThread generation",
28220 || {
28221 let state = controller
28222 .state
28223 .lock()
28224 .unwrap_or_else(std::sync::PoisonError::into_inner);
28225 matches!(
28226 &state.lifecycle,
28227 RuntimeLifecycle::Stopping(identity) if identity.generation == generation
28228 )
28229 },
28230 );
28231 assert!(
28232 shutdown_rx
28233 .recv_timeout(Duration::from_millis(200))
28234 .is_err(),
28235 "shutdown must wait for an admitted host callback even before it starts draining"
28236 );
28237
28238 release_tx.send(()).unwrap();
28239 driver.join().unwrap();
28240 shutdown_rx
28241 .recv_timeout(Duration::from_secs(2))
28242 .unwrap()
28243 .unwrap();
28244 shutdown.join().unwrap();
28245 }
28246
28247 #[cfg(napi_runtime_os_threads)]
28248 #[test]
28249 fn spawn_racing_shutdown_cannot_recreate_backend() {
28250 use std::sync::mpsc;
28251
28252 let controller = Arc::new(current_thread_controller("lifecycle-race"));
28253 controller.start().expect("runtime must start");
28254 drop(controller.backend());
28255
28256 let (at_hook_tx, at_hook_rx) = mpsc::channel();
28257 let (release_tx, release_rx) = mpsc::channel();
28258 let ran = Arc::new(AtomicBool::new(false));
28259 let ran_task = Arc::clone(&ran);
28260 let worker_controller = Arc::clone(&controller);
28261 let worker = std::thread::spawn(move || {
28262 BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
28263 *slot.borrow_mut() = Some(Box::new(move || {
28264 at_hook_tx
28265 .send(())
28266 .expect("test coordinator must still be listening");
28267 release_rx
28268 .recv()
28269 .expect("test coordinator must release the submission");
28270 }));
28271 });
28272
28273 let future = async move {
28274 ran_task.store(true, Ordering::SeqCst);
28275 };
28276 match worker_controller.try_spawn_detached(future) {
28277 Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
28278 Err(future) => drop(future),
28279 }
28280 });
28281
28282 at_hook_rx
28283 .recv_timeout(Duration::from_secs(5))
28284 .expect("submitting thread must stop before taking the controller lock");
28285 controller.shutdown().expect("runtime must stop");
28286 release_tx
28287 .send(())
28288 .expect("submitting thread must still be waiting");
28289 worker.join().expect("submitting thread must not panic");
28290
28291 assert!(
28292 !ran.load(Ordering::SeqCst),
28293 "the rejected task must never run"
28294 );
28295 let state = controller
28296 .state
28297 .lock()
28298 .unwrap_or_else(std::sync::PoisonError::into_inner);
28299 assert!(
28300 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
28301 "the rejected submission must not lazily recreate a backend"
28302 );
28303 }
28304
28305 #[cfg(napi_runtime_os_threads)]
28306 #[test]
28307 fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
28308 use std::sync::mpsc;
28309
28310 struct BlockingDropFuture {
28311 work: Arc<GenerationWork>,
28312 entered: mpsc::Sender<(Option<u64>, usize)>,
28313 release: mpsc::Receiver<()>,
28314 }
28315
28316 impl Future for BlockingDropFuture {
28317 type Output = ();
28318
28319 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28320 Poll::Pending
28321 }
28322 }
28323
28324 impl Drop for BlockingDropFuture {
28325 fn drop(&mut self) {
28326 let active = self
28327 .work
28328 .state
28329 .lock()
28330 .unwrap_or_else(std::sync::PoisonError::into_inner)
28331 .active;
28332 self
28333 .entered
28334 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28335 .unwrap();
28336 self.release.recv().unwrap();
28337 }
28338 }
28339
28340 let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
28341 let backend = controller.backend();
28342 let old_generation = backend.generation();
28343 let work = Arc::clone(&backend.work);
28344 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28345 panic!("configured CurrentThread backend must create a CurrentThread executor");
28346 };
28347 let executor = Arc::clone(executor);
28348 drop(backend);
28349
28350 let (registered_tx, registered_rx) = mpsc::channel();
28351 let (release_submission_tx, release_submission_rx) = mpsc::channel();
28352 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28353 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28354 let submit_controller = Arc::clone(&controller);
28355 let submit_work = Arc::clone(&work);
28356 let submitter = std::thread::spawn(move || {
28357 AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28358 *slot.borrow_mut() = Some(Box::new(move || {
28359 registered_tx.send(()).unwrap();
28360 release_submission_rx.recv().unwrap();
28361 }));
28362 });
28363 submit_controller
28364 .try_spawn_detached(BlockingDropFuture {
28365 work: submit_work,
28366 entered: drop_entered_tx,
28367 release: release_drop_rx,
28368 })
28369 .unwrap_or_else(|_| {
28370 panic!("the submission linearized before shutdown and must be accepted")
28371 });
28372 });
28373 registered_rx
28374 .recv_timeout(Duration::from_secs(2))
28375 .expect("the submitting thread must register generation work before shutdown");
28376
28377 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28378 let shutdown_controller = Arc::clone(&controller);
28379 let shutdown = std::thread::spawn(move || {
28380 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28381 });
28382 wait_until("CurrentThread shutdown to close the runnable queue", || {
28383 executor
28384 .queue
28385 .lock()
28386 .unwrap_or_else(std::sync::PoisonError::into_inner)
28387 .closed
28388 });
28389 release_submission_tx.send(()).unwrap();
28390
28391 let (drop_generation, active_during_drop) = drop_entered_rx
28392 .recv_timeout(Duration::from_secs(2))
28393 .expect("the closed queue must destroy the accepted unpolled future");
28394 assert_eq!(drop_generation, Some(old_generation));
28395 assert_eq!(
28396 active_during_drop, 1,
28397 "accepted generation work must remain registered through user future destruction"
28398 );
28399
28400 let (start_tx, start_rx) = mpsc::channel();
28401 let start_controller = Arc::clone(&controller);
28402 let starter = std::thread::spawn(move || {
28403 start_tx.send(start_controller.start()).unwrap();
28404 });
28405 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28406 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28407 let shutdown_overlapped = early_shutdown.is_some();
28408 let restart_overlapped = early_start.is_some();
28409
28410 release_drop_tx.send(()).unwrap();
28411 submitter.join().unwrap();
28412 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28413 shutdown_rx
28414 .recv_timeout(Duration::from_secs(2))
28415 .expect("shutdown must finish after drop")
28416 });
28417 shutdown_result.unwrap();
28418 shutdown.join().unwrap();
28419 let start_result = early_start.unwrap_or_else(|| {
28420 start_rx
28421 .recv_timeout(Duration::from_secs(2))
28422 .expect("restart must follow shutdown")
28423 });
28424 start_result.unwrap();
28425 starter.join().unwrap();
28426
28427 assert!(
28428 !shutdown_overlapped,
28429 "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
28430 );
28431 assert!(
28432 !restart_overlapped,
28433 "restart must not create a new generation while an accepted old-generation future is being destroyed"
28434 );
28435 assert_ne!(controller.backend().generation(), old_generation);
28436 controller.shutdown().unwrap();
28437 }
28438
28439 #[cfg(napi_runtime_os_threads)]
28440 #[test]
28441 fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
28442 use std::sync::mpsc;
28443
28444 struct BlockingDrop {
28445 work: Arc<GenerationWork>,
28446 entered: mpsc::Sender<(Option<u64>, usize)>,
28447 release: mpsc::Receiver<()>,
28448 }
28449
28450 impl Drop for BlockingDrop {
28451 fn drop(&mut self) {
28452 let active = self
28453 .work
28454 .state
28455 .lock()
28456 .unwrap_or_else(std::sync::PoisonError::into_inner)
28457 .active;
28458 self
28459 .entered
28460 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28461 .unwrap();
28462 self.release.recv().unwrap();
28463 }
28464 }
28465
28466 let controller = Arc::new(multi_thread_controller(
28467 "accepted-blocking-submission-drop",
28468 2,
28469 1,
28470 ));
28471 let backend = controller.backend();
28472 let old_generation = backend.generation();
28473 let work = Arc::clone(&backend.work);
28474 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28475 panic!("configured MultiThread backend must create a MultiThread executor");
28476 };
28477 let executor = Arc::downgrade(executor);
28478 drop(backend);
28479
28480 let (registered_tx, registered_rx) = mpsc::channel();
28481 let (release_submission_tx, release_submission_rx) = mpsc::channel();
28482 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28483 let (release_drop_tx, release_drop_rx) = mpsc::channel();
28484 let submit_controller = Arc::clone(&controller);
28485 let submitter = std::thread::spawn(move || {
28486 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28487 *slot.borrow_mut() = Some(Box::new(move || {
28488 registered_tx.send(()).unwrap();
28489 release_submission_rx.recv().unwrap();
28490 }));
28491 });
28492 let drop_signal = BlockingDrop {
28493 work,
28494 entered: drop_entered_tx,
28495 release: release_drop_rx,
28496 };
28497 let handle = submit_controller
28498 .try_spawn_blocking(move || {
28499 let _drop_signal = drop_signal;
28500 })
28501 .unwrap_or_else(|_| {
28502 panic!("the submission linearized before shutdown and must be accepted")
28503 });
28504 assert!(
28505 futures::executor::block_on(handle).is_err(),
28506 "the executor must cancel work that reaches its queue after shutdown closed it"
28507 );
28508 });
28509 registered_rx
28510 .recv_timeout(Duration::from_secs(2))
28511 .expect("the submitting thread must register generation work before shutdown");
28512
28513 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28514 let shutdown_controller = Arc::clone(&controller);
28515 let shutdown = std::thread::spawn(move || {
28516 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28517 });
28518 wait_until("MultiThread shutdown to close the blocking queue", || {
28519 executor
28520 .upgrade()
28521 .expect("the retiring executor must remain alive until shutdown completes")
28522 .blocking_queue
28523 .lock()
28524 .unwrap_or_else(std::sync::PoisonError::into_inner)
28525 .closed
28526 });
28527 release_submission_tx.send(()).unwrap();
28528
28529 let (drop_generation, active_during_drop) = drop_entered_rx
28530 .recv_timeout(Duration::from_secs(2))
28531 .expect("the closed queue must destroy the accepted blocking closure");
28532 assert_eq!(drop_generation, Some(old_generation));
28533 assert_eq!(
28534 active_during_drop, 1,
28535 "accepted generation work must remain registered through blocking closure destruction"
28536 );
28537
28538 let (start_tx, start_rx) = mpsc::channel();
28539 let start_controller = Arc::clone(&controller);
28540 let starter = std::thread::spawn(move || {
28541 start_tx.send(start_controller.start()).unwrap();
28542 });
28543 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28544 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28545 let shutdown_overlapped = early_shutdown.is_some();
28546 let restart_overlapped = early_start.is_some();
28547
28548 release_drop_tx.send(()).unwrap();
28549 submitter.join().unwrap();
28550 let shutdown_result = early_shutdown.unwrap_or_else(|| {
28551 shutdown_rx
28552 .recv_timeout(Duration::from_secs(2))
28553 .expect("shutdown must finish after drop")
28554 });
28555 shutdown_result.unwrap();
28556 shutdown.join().unwrap();
28557 let start_result = early_start.unwrap_or_else(|| {
28558 start_rx
28559 .recv_timeout(Duration::from_secs(2))
28560 .expect("restart must follow shutdown")
28561 });
28562 start_result.unwrap();
28563 starter.join().unwrap();
28564
28565 assert!(
28566 !shutdown_overlapped,
28567 "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
28568 );
28569 assert!(
28570 !restart_overlapped,
28571 "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
28572 );
28573 assert_ne!(controller.backend().generation(), old_generation);
28574 controller.shutdown().unwrap();
28575 }
28576
28577 #[cfg(napi_runtime_os_threads)]
28578 #[test]
28579 fn multi_thread_controller_reports_its_exact_physical_worker_count() {
28580 let controller = multi_thread_controller("lifecycle-physical", 1, 8);
28581 let options = controller.options();
28582 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
28583
28584 let backend = controller.backend();
28585 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28586 panic!("configured MultiThread backend must create a Rayon executor");
28587 };
28588 assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
28589 let lifecycle = Arc::clone(&executor.worker_lifecycle);
28590 drop(backend);
28591
28592 controller.shutdown().expect("runtime must stop");
28593 assert_eq!(
28594 lifecycle.remaining(),
28595 0,
28596 "shutdown must observe every physical worker exit"
28597 );
28598 }
28599
28600 #[cfg(napi_runtime_os_threads)]
28601 #[test]
28602 fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
28603 use std::sync::mpsc;
28620
28621 let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
28622 let backend = controller.backend();
28623 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28624 panic!("configured MultiThread backend must create a Rayon executor");
28625 };
28626 let worker_count = executor.pool.current_num_threads();
28627 let completed = Arc::new(AtomicUsize::new(0));
28628 let probe_completed = Arc::clone(&completed);
28629 executor.pool.broadcast(move |_| {
28630 WORKER_TLS_DROP_PROBE.with(|slot| {
28631 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28632 action: Some(WorkerTlsDropAction::Record {
28633 completed: Arc::clone(&probe_completed),
28634 }),
28635 });
28636 assert!(
28637 previous.is_none(),
28638 "each worker must install exactly one TLS drop probe"
28639 );
28640 });
28641 });
28642 drop(backend);
28643
28644 assert_eq!(
28645 completed.load(Ordering::SeqCst),
28646 0,
28647 "no worker TLS destructor may run before shutdown retires the workers"
28648 );
28649
28650 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28651 let shutdown_controller = Arc::clone(&controller);
28652 let sample_completed = Arc::clone(&completed);
28653 let shutdown = std::thread::spawn(move || {
28654 let result = shutdown_controller.shutdown();
28655 let seen_at_return = sample_completed.load(Ordering::SeqCst);
28659 shutdown_tx.send((result, seen_at_return)).unwrap();
28660 });
28661 let (shutdown_result, seen_at_return) = shutdown_rx
28662 .recv_timeout(Duration::from_secs(10))
28663 .expect("shutdown must finish once worker TLS destructors have run");
28664 shutdown_result.unwrap();
28665 assert_eq!(
28666 seen_at_return, worker_count,
28667 "every worker TLS destructor must have completed before shutdown() returned: \
28668 the worker-join barrier must include TLS-destructor completion"
28669 );
28670 join_within(
28671 "the shutdown thread exits after teardown",
28672 shutdown,
28673 Duration::from_secs(30),
28674 );
28675 }
28676
28677 #[cfg(not(target_family = "wasm"))]
28678 #[test]
28679 fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
28680 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
28681
28682 if std::env::var_os(CHILD_ENV).is_some() {
28683 use std::sync::mpsc;
28684
28685 let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
28686 let backend = controller.backend();
28687 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28688 panic!("configured MultiThread backend must create a Rayon executor");
28689 };
28690 let installed = Arc::new(AtomicBool::new(false));
28691 let (completed_tx, completed_rx) = mpsc::channel();
28692 let probe_controller = Arc::clone(&controller);
28693 let probe_installed = Arc::clone(&installed);
28694 executor.pool.broadcast(move |_| {
28695 if probe_installed.swap(true, Ordering::SeqCst) {
28696 return;
28697 }
28698 WORKER_TLS_DROP_PROBE.with(|slot| {
28699 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28700 action: Some(WorkerTlsDropAction::ReenterLifecycle {
28701 controller: Arc::clone(&probe_controller),
28702 completed: completed_tx.clone(),
28703 }),
28704 });
28705 assert!(
28706 previous.is_none(),
28707 "the selected worker must have no prior TLS probe"
28708 );
28709 });
28710 });
28711 assert!(
28712 installed.load(Ordering::SeqCst),
28713 "one worker must install the lifecycle probe"
28714 );
28715 drop(backend);
28716
28717 controller
28718 .shutdown()
28719 .expect("external shutdown must join the retiring workers");
28720 let (start, shutdown) = completed_rx
28721 .recv_timeout(Duration::from_secs(2))
28722 .expect("the TLS destructor must complete both lifecycle reentry attempts");
28723 assert_eq!(
28724 start.unwrap_err(),
28725 "cannot restart the async runtime from work in the generation being stopped"
28726 );
28727 assert_eq!(
28728 shutdown.unwrap_err(),
28729 "cannot wait for async runtime shutdown from work in the generation being stopped"
28730 );
28731 return;
28732 }
28733
28734 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28735 .arg("--exact")
28736 .arg(
28737 "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
28738 )
28739 .arg("--nocapture")
28740 .env(CHILD_ENV, "1")
28741 .stdout(std::process::Stdio::piped())
28742 .stderr(std::process::Stdio::piped())
28743 .spawn()
28744 .expect("the TLS lifecycle-reentry subprocess must start");
28745 let deadline = Instant::now() + Duration::from_secs(5);
28746 let status = loop {
28747 if let Some(status) = child
28748 .try_wait()
28749 .expect("the subprocess status must be readable")
28750 {
28751 break status;
28752 }
28753 if Instant::now() >= deadline {
28754 let _ = child.kill();
28755 let output = child
28756 .wait_with_output()
28757 .expect("the timed-out subprocess must be reaped");
28758 panic!(
28759 "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
28760 String::from_utf8_lossy(&output.stdout),
28761 String::from_utf8_lossy(&output.stderr)
28762 );
28763 }
28764 std::thread::sleep(Duration::from_millis(10));
28765 };
28766 let output = child
28767 .wait_with_output()
28768 .expect("the completed subprocess output must be read");
28769 assert!(
28770 status.success(),
28771 "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
28772 status.code(),
28773 String::from_utf8_lossy(&output.stdout),
28774 String::from_utf8_lossy(&output.stderr)
28775 );
28776 }
28777
28778 #[cfg(napi_runtime_os_threads)]
28779 #[test]
28780 fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
28781 use std::sync::mpsc;
28782
28783 struct DropSignal(mpsc::Sender<()>);
28784 impl Drop for DropSignal {
28785 fn drop(&mut self) {
28786 let _ = self.0.send(());
28787 }
28788 }
28789
28790 let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
28791 let (polled_tx, polled_rx) = mpsc::channel();
28792 let (dropped_tx, dropped_rx) = mpsc::channel();
28793 controller
28794 .try_spawn_detached(async move {
28795 let _drop_signal = DropSignal(dropped_tx);
28796 polled_tx.send(()).unwrap();
28797 std::future::pending::<()>().await;
28798 })
28799 .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
28800 polled_rx
28801 .recv_timeout(Duration::from_secs(5))
28802 .expect("the pending task must be polled before shutdown");
28803
28804 controller
28805 .shutdown()
28806 .expect("shutdown must cancel accepted async work");
28807 dropped_rx
28808 .recv_timeout(Duration::from_secs(5))
28809 .expect("shutdown must drop the accepted task future before returning");
28810 }
28811
28812 #[cfg(napi_runtime_os_threads)]
28813 #[test]
28814 fn current_thread_shutdown_outranks_perpetual_self_wake() {
28815 use std::sync::mpsc;
28816
28817 struct SelfWakingPending {
28818 entered: Option<mpsc::Sender<()>>,
28819 }
28820
28821 impl Future for SelfWakingPending {
28822 type Output = ();
28823
28824 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
28825 if let Some(entered) = self.entered.take() {
28826 entered.send(()).unwrap();
28827 }
28828 cx.waker().wake_by_ref();
28829 Poll::Pending
28830 }
28831 }
28832
28833 let controller = Arc::new(current_thread_controller(
28834 "current-thread-self-wake-shutdown",
28835 ));
28836 let (entered_tx, entered_rx) = mpsc::channel();
28837 let (outcome_tx, outcome_rx) = mpsc::channel();
28838 let block_on_controller = Arc::clone(&controller);
28839 let runner = std::thread::spawn(move || {
28840 let mut future = std::pin::pin!(SelfWakingPending {
28841 entered: Some(entered_tx)
28842 });
28843 outcome_tx
28844 .send(block_on_controller.block_on(future.as_mut()))
28845 .unwrap();
28846 });
28847 entered_rx
28848 .recv_timeout(Duration::from_secs(2))
28849 .expect("the self-waking future must be polled before shutdown");
28850
28851 let (shutdown_tx, shutdown_rx) = mpsc::channel();
28852 let shutdown_controller = Arc::clone(&controller);
28853 let shutdown = std::thread::spawn(move || {
28854 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28855 });
28856 shutdown_rx
28857 .recv_timeout(Duration::from_secs(2))
28858 .expect("shutdown must outrank a stored self-wake permit")
28859 .unwrap();
28860 assert_eq!(
28861 outcome_rx
28862 .recv_timeout(Duration::from_secs(2))
28863 .expect("the raw block_on driver must report shutdown"),
28864 BlockOnOutcome::Stopped
28865 );
28866 runner.join().unwrap();
28867 shutdown.join().unwrap();
28868 }
28869
28870 #[cfg(napi_runtime_os_threads)]
28871 #[test]
28872 fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
28873 use std::sync::mpsc;
28874
28875 let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
28876 let backend = controller.backend();
28877 let executor = match &backend.executor {
28878 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28879 RuntimeExecutor::CurrentThread(_) => unreachable!(),
28880 };
28881 let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
28882 let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
28883 for _ in 0..2 {
28884 let worker_ready_tx = worker_ready_tx.clone();
28885 let release_workers = Arc::clone(&release_workers);
28886 executor.pool.spawn(move || {
28887 worker_ready_tx.send(()).unwrap();
28888 let (released, changed) = &*release_workers;
28889 let mut released = released
28890 .lock()
28891 .unwrap_or_else(std::sync::PoisonError::into_inner);
28892 while !*released {
28893 released = changed
28894 .wait(released)
28895 .unwrap_or_else(std::sync::PoisonError::into_inner);
28896 }
28897 });
28898 }
28899 drop(worker_ready_tx);
28900 for _ in 0..2 {
28901 worker_ready_rx
28902 .recv_timeout(Duration::from_secs(5))
28903 .expect("both workers must be occupied before the FIFO runnable is submitted");
28904 }
28905 drop(executor);
28906 drop(backend);
28907
28908 let polls = Arc::new(AtomicUsize::new(0));
28909 let handle = controller.spawn(StopPollProbe {
28910 polls: Arc::clone(&polls),
28911 first_poll: None,
28912 });
28913 wait_until("the FIFO runnable to be queued", || {
28914 controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
28915 });
28916
28917 let observed_queue = Arc::new(Mutex::new(None));
28918 let hook_observed_queue = Arc::clone(&observed_queue);
28919 let hook_metrics = Arc::clone(&controller.metrics);
28920 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
28921 *slot.borrow_mut() = Some(Box::new(move || {
28922 let (released, changed) = &*release_workers;
28923 *released
28924 .lock()
28925 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
28926 changed.notify_all();
28927 wait_until("the stopped FIFO runnable claim", || {
28928 hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
28929 });
28930 *hook_observed_queue
28931 .lock()
28932 .unwrap_or_else(std::sync::PoisonError::into_inner) =
28933 Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
28934 }));
28935 });
28936
28937 controller
28938 .shutdown()
28939 .expect("shutdown must cancel the stopped FIFO claim");
28940 assert_eq!(polls.load(Ordering::SeqCst), 0);
28941 assert_eq!(
28942 *observed_queue
28943 .lock()
28944 .unwrap_or_else(std::sync::PoisonError::into_inner),
28945 Some(0)
28946 );
28947 assert_eq!(
28948 controller.metrics.queued_runnables.load(Ordering::SeqCst),
28949 0
28950 );
28951 drop(handle);
28952 }
28953
28954 #[cfg(napi_runtime_os_threads)]
28955 #[test]
28956 fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
28957 use std::sync::mpsc;
28958
28959 let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
28960 let backend = controller.backend();
28961 let executor = match &backend.executor {
28962 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28963 RuntimeExecutor::CurrentThread(_) => unreachable!(),
28964 };
28965 let polls = Arc::new(AtomicUsize::new(0));
28966 let scheduler = Arc::downgrade(&executor);
28967 let (runnable, task) = async_task::spawn(
28968 StopPollProbe {
28969 polls: Arc::clone(&polls),
28970 first_poll: None,
28971 },
28972 move |runnable| {
28973 if let Some(executor) = scheduler.upgrade() {
28974 executor.schedule(runnable);
28975 }
28976 },
28977 );
28978 let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
28979 let (release_slot_tx, release_slot_rx) = mpsc::channel();
28980 let (claim_done_tx, claim_done_rx) = mpsc::channel();
28981 let worker_executor = Arc::clone(&executor);
28982 let worker_metrics = Arc::clone(&controller.metrics);
28983 executor.pool.spawn(move || {
28984 let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
28985 worker_executor.schedule(runnable);
28986 slot_ready_tx.send(()).unwrap();
28987 release_slot_rx.recv().unwrap();
28988 let claimed = worker_executor.run_one_runnable();
28989 claim_done_tx
28990 .send((
28991 claimed,
28992 worker_metrics.queued_runnables.load(Ordering::SeqCst),
28993 ))
28994 .unwrap();
28995 });
28996 slot_ready_rx
28997 .recv_timeout(Duration::from_secs(5))
28998 .expect("the worker must publish the LIFO runnable before shutdown");
28999 assert_eq!(
29000 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29001 1
29002 );
29003 drop(executor);
29004 drop(backend);
29005
29006 let observed_claim = Arc::new(Mutex::new(None));
29007 let hook_observed_claim = Arc::clone(&observed_claim);
29008 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29009 *slot.borrow_mut() = Some(Box::new(move || {
29010 release_slot_tx.send(()).unwrap();
29011 let result = claim_done_rx
29012 .recv_timeout(Duration::from_secs(5))
29013 .expect("the stopped LIFO runnable claim must complete before abort");
29014 *hook_observed_claim
29015 .lock()
29016 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
29017 }));
29018 });
29019
29020 controller
29021 .shutdown()
29022 .expect("shutdown must cancel the stopped LIFO claim");
29023 assert_eq!(polls.load(Ordering::SeqCst), 0);
29024 assert_eq!(
29025 *observed_claim
29026 .lock()
29027 .unwrap_or_else(std::sync::PoisonError::into_inner),
29028 Some((true, 0))
29029 );
29030 assert_eq!(
29031 controller.metrics.queued_runnables.load(Ordering::SeqCst),
29032 0
29033 );
29034 drop(task);
29035 }
29036
29037 #[cfg(napi_runtime_os_threads)]
29038 #[test]
29039 fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
29040 use std::sync::mpsc;
29041
29042 struct BlockingDrop {
29043 entered: mpsc::Sender<()>,
29044 release: mpsc::Receiver<()>,
29045 }
29046
29047 impl Drop for BlockingDrop {
29048 fn drop(&mut self) {
29049 self.entered.send(()).unwrap();
29050 self.release.recv().unwrap();
29051 }
29052 }
29053
29054 struct TwoPollProbe {
29055 first_poll: Option<mpsc::Sender<()>>,
29056 release_first_poll: mpsc::Receiver<()>,
29057 second_poll: mpsc::Sender<()>,
29058 }
29059
29060 impl Future for TwoPollProbe {
29061 type Output = ();
29062
29063 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29064 if let Some(first_poll) = self.first_poll.take() {
29065 first_poll.send(()).unwrap();
29066 self.release_first_poll.recv().unwrap();
29067 } else {
29068 self.second_poll.send(()).unwrap();
29069 }
29070 cx.waker().wake_by_ref();
29071 Poll::Pending
29072 }
29073 }
29074
29075 let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
29076 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29077 let (release_drop_tx, release_drop_rx) = mpsc::channel();
29078 let blocking_drop = BlockingDrop {
29079 entered: drop_entered_tx,
29080 release: release_drop_rx,
29081 };
29082 controller
29083 .try_spawn_detached(async move {
29084 let _drop = blocking_drop;
29085 std::future::pending::<()>().await;
29086 })
29087 .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
29088
29089 let (first_poll_tx, first_poll_rx) = mpsc::channel();
29090 let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
29091 let (second_poll_tx, second_poll_rx) = mpsc::channel();
29092 let (outcome_tx, outcome_rx) = mpsc::channel();
29093 let runner_controller = Arc::clone(&controller);
29094 let runner = std::thread::spawn(move || {
29095 let mut future = std::pin::pin!(TwoPollProbe {
29096 first_poll: Some(first_poll_tx),
29097 release_first_poll: release_first_poll_rx,
29098 second_poll: second_poll_tx,
29099 });
29100 outcome_tx
29101 .send(runner_controller.block_on(future.as_mut()))
29102 .unwrap();
29103 });
29104 first_poll_rx
29105 .recv_timeout(Duration::from_secs(2))
29106 .expect("the active block_on poll must enter before shutdown");
29107
29108 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29109 let shutdown_controller = Arc::clone(&controller);
29110 let shutdown = std::thread::spawn(move || {
29111 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29112 });
29113 drop_entered_rx
29114 .recv_timeout(Duration::from_secs(2))
29115 .expect("shutdown must begin destroying queued work");
29116
29117 release_first_poll_tx.send(()).unwrap();
29118 let polled_again = second_poll_rx
29119 .recv_timeout(Duration::from_millis(200))
29120 .is_ok();
29121 release_drop_tx.send(()).unwrap();
29122
29123 assert!(
29124 !polled_again,
29125 "an active block_on driver must observe generation stop before queued user cleanup blocks"
29126 );
29127 assert_eq!(
29128 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29129 BlockOnOutcome::Stopped
29130 );
29131 shutdown_rx
29132 .recv_timeout(Duration::from_secs(2))
29133 .unwrap()
29134 .unwrap();
29135 runner.join().unwrap();
29136 shutdown.join().unwrap();
29137 }
29138
29139 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29140 #[test]
29141 fn shutdown_cancels_nested_block_on_within_deadline() {
29142 use std::sync::mpsc;
29143
29144 let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
29145 let task_controller = Arc::clone(&controller);
29146 let (entered_tx, entered_rx) = mpsc::channel();
29147 let task = controller
29148 .try_spawn(async move {
29149 entered_tx.send(()).unwrap();
29150 block_on_with_controller(&task_controller, std::future::pending::<()>());
29151 })
29152 .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
29153 entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29154
29155 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29156 let shutdown_controller = Arc::clone(&controller);
29157 let shutdown = std::thread::spawn(move || {
29158 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29159 });
29160 shutdown_rx
29161 .recv_timeout(Duration::from_secs(2))
29162 .expect("shutdown must wake and cancel an active nested block_on")
29163 .unwrap();
29164 shutdown.join().unwrap();
29165 assert!(
29166 futures::executor::block_on(task).is_err(),
29167 "the cancelled nested block_on task must resolve as stopped"
29168 );
29169 }
29170
29171 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29172 #[test]
29173 fn stopped_public_block_on_drops_future_before_generation_retirement() {
29174 use std::sync::mpsc;
29175
29176 struct PendingDrop {
29177 entered: Option<mpsc::Sender<()>>,
29178 dropped: mpsc::Sender<Option<u64>>,
29179 release: mpsc::Receiver<()>,
29180 }
29181
29182 impl Future for PendingDrop {
29183 type Output = ();
29184
29185 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29186 if let Some(entered) = self.entered.take() {
29187 entered.send(()).unwrap();
29188 }
29189 Poll::Pending
29190 }
29191 }
29192
29193 impl Drop for PendingDrop {
29194 fn drop(&mut self) {
29195 self
29196 .dropped
29197 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29198 .unwrap();
29199 self.release.recv().unwrap();
29200 }
29201 }
29202
29203 let controller = Arc::new(current_thread_controller(
29204 "public-block-on-generation-destruction",
29205 ));
29206 let generation = controller.backend().generation();
29207 let (entered_tx, entered_rx) = mpsc::channel();
29208 let (dropped_tx, dropped_rx) = mpsc::channel();
29209 let (release_tx, release_rx) = mpsc::channel();
29210 let (runner_done_tx, runner_done_rx) = mpsc::channel();
29211 let block_controller = Arc::clone(&controller);
29212 let runner = std::thread::spawn(move || {
29213 let result = catch_unwind(AssertUnwindSafe(|| {
29214 block_on_with_controller(
29215 &block_controller,
29216 PendingDrop {
29217 entered: Some(entered_tx),
29218 dropped: dropped_tx,
29219 release: release_rx,
29220 },
29221 );
29222 }));
29223 runner_done_tx.send(result.is_err()).unwrap();
29224 });
29225 entered_rx
29226 .recv_timeout(Duration::from_secs(2))
29227 .expect("the public block_on future must be polled before shutdown");
29228
29229 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29230 let shutdown_controller = Arc::clone(&controller);
29231 let shutdown = std::thread::spawn(move || {
29232 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29233 });
29234
29235 assert_eq!(
29236 dropped_rx
29237 .recv_timeout(Duration::from_secs(2))
29238 .expect("shutdown must wake block_on and begin destroying its future"),
29239 Some(generation),
29240 "the stopped future must be destroyed under its retiring generation identity"
29241 );
29242 assert!(
29243 shutdown_rx
29244 .recv_timeout(Duration::from_millis(200))
29245 .is_err(),
29246 "shutdown must not publish Stopped while the public block_on future destructor is running"
29247 );
29248
29249 release_tx.send(()).unwrap();
29250 assert!(
29251 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29252 "the public helper must retain its existing early-stop panic behavior"
29253 );
29254 shutdown_rx
29255 .recv_timeout(Duration::from_secs(2))
29256 .unwrap()
29257 .unwrap();
29258 runner.join().unwrap();
29259 shutdown.join().unwrap();
29260 }
29261
29262 #[cfg(not(target_family = "wasm"))]
29263 #[test]
29264 fn shutdown_contains_panicking_nested_block_on_future_drop() {
29265 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
29266
29267 if std::env::var_os(CHILD_ENV).is_some() {
29268 use std::sync::mpsc;
29269
29270 struct PendingPanicOnDrop {
29271 entered: Option<mpsc::Sender<()>>,
29272 }
29273
29274 impl Future for PendingPanicOnDrop {
29275 type Output = ();
29276
29277 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29278 if let Some(entered) = self.entered.take() {
29279 entered.send(()).unwrap();
29280 }
29281 Poll::Pending
29282 }
29283 }
29284
29285 impl Drop for PendingPanicOnDrop {
29286 fn drop(&mut self) {
29287 panic!("intentional nested block_on future destructor panic");
29288 }
29289 }
29290
29291 let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
29292 let task_controller = Arc::clone(&controller);
29293 let (entered_tx, entered_rx) = mpsc::channel();
29294 let task = controller
29295 .try_spawn(async move {
29296 block_on_with_controller(
29297 &task_controller,
29298 PendingPanicOnDrop {
29299 entered: Some(entered_tx),
29300 },
29301 );
29302 })
29303 .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
29304 entered_rx
29305 .recv_timeout(Duration::from_secs(2))
29306 .expect("the nested block_on future must be polled before shutdown");
29307
29308 controller
29309 .shutdown()
29310 .expect("shutdown must contain nested future destruction");
29311 assert!(
29312 futures::executor::block_on(task).is_err(),
29313 "shutdown cancellation must still surface as a task error"
29314 );
29315 return;
29316 }
29317
29318 let output = std::process::Command::new(std::env::current_exe().unwrap())
29319 .arg("--exact")
29320 .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
29321 .arg("--nocapture")
29322 .env(CHILD_ENV, "1")
29323 .output()
29324 .expect("the subprocess test must start");
29325 assert!(
29326 output.status.success(),
29327 "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
29328 output.status.code(),
29329 String::from_utf8_lossy(&output.stdout),
29330 String::from_utf8_lossy(&output.stderr)
29331 );
29332 }
29333
29334 #[cfg(not(target_family = "wasm"))]
29335 #[test]
29336 fn panicking_future_drop_is_contained_before_async_task_drop() {
29337 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
29338
29339 if std::env::var_os(CHILD_ENV).is_some() {
29340 struct PanicOnDrop;
29341 impl Drop for PanicOnDrop {
29342 fn drop(&mut self) {
29343 panic!("intentional future destructor panic");
29344 }
29345 }
29346
29347 let controller = current_thread_controller("future-drop-subprocess");
29348 let bomb = PanicOnDrop;
29349 controller
29350 .try_spawn_detached(async move {
29351 let _bomb = bomb;
29352 std::future::pending::<()>().await;
29353 })
29354 .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
29355 controller
29356 .shutdown()
29357 .expect("shutdown must contain the future destructor panic");
29358 return;
29359 }
29360
29361 let output = std::process::Command::new(std::env::current_exe().unwrap())
29362 .arg("--exact")
29363 .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
29364 .arg("--nocapture")
29365 .env(CHILD_ENV, "1")
29366 .output()
29367 .expect("the subprocess test must start");
29368 assert!(
29369 output.status.success(),
29370 "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29371 output.status.code(),
29372 String::from_utf8_lossy(&output.stdout),
29373 String::from_utf8_lossy(&output.stderr)
29374 );
29375 }
29376
29377 #[cfg(not(target_family = "wasm"))]
29378 #[test]
29379 fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
29380 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
29381
29382 if std::env::var_os(CHILD_ENV).is_some() {
29383 struct PanicOnDrop;
29384 impl Drop for PanicOnDrop {
29385 fn drop(&mut self) {
29386 panic!("intentional detached output destructor panic");
29387 }
29388 }
29389
29390 let controller = current_thread_controller("output-drop-subprocess");
29391 let handle = controller
29392 .try_spawn(async { PanicOnDrop })
29393 .unwrap_or_else(|_| panic!("runtime must accept the task"));
29394 drop(handle);
29395 let dispatch = controller_current_thread_dispatch(&controller);
29396 controller.drive_current_thread_dispatch(dispatch, false);
29397 controller.shutdown().unwrap();
29398 return;
29399 }
29400
29401 let output = std::process::Command::new(std::env::current_exe().unwrap())
29402 .arg("--exact")
29403 .arg(
29404 "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
29405 )
29406 .arg("--nocapture")
29407 .env(CHILD_ENV, "1")
29408 .output()
29409 .expect("the subprocess test must start");
29410 assert!(
29411 output.status.success(),
29412 "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29413 output.status.code(),
29414 String::from_utf8_lossy(&output.stdout),
29415 String::from_utf8_lossy(&output.stderr)
29416 );
29417 }
29418
29419 #[cfg(napi_runtime_os_threads)]
29420 #[test]
29421 fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
29422 use std::sync::mpsc;
29423
29424 struct BlockingDrop {
29425 controller: Arc<RuntimeController>,
29426 entered: mpsc::Sender<Option<u64>>,
29427 release: mpsc::Receiver<()>,
29428 reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
29429 }
29430
29431 impl Drop for BlockingDrop {
29432 fn drop(&mut self) {
29433 self
29434 .entered
29435 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29436 .unwrap();
29437 self.release.recv().unwrap();
29438 self
29439 .reentrant_shutdown
29440 .send(self.controller.shutdown())
29441 .unwrap();
29442 }
29443 }
29444
29445 let controller = Arc::new(current_thread_controller(
29446 "current-thread-host-drain-output-retirement",
29447 ));
29448 let old_generation = controller.backend().generation();
29449 let (entered_tx, entered_rx) = mpsc::channel();
29450 let (release_tx, release_rx) = mpsc::channel();
29451 let (reentrant_tx, reentrant_rx) = mpsc::channel();
29452 let output_controller = Arc::clone(&controller);
29453 let handle = controller
29454 .try_spawn(async move {
29455 BlockingDrop {
29456 controller: output_controller,
29457 entered: entered_tx,
29458 release: release_rx,
29459 reentrant_shutdown: reentrant_tx,
29460 }
29461 })
29462 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
29463 drop(handle);
29464 let dispatch = controller_current_thread_dispatch(&controller);
29465
29466 let driver_controller = Arc::clone(&controller);
29467 let driver =
29468 std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
29469 assert_eq!(
29470 entered_rx
29471 .recv_timeout(Duration::from_secs(2))
29472 .expect("the host turn must begin destroying the detached output"),
29473 Some(old_generation),
29474 "detached output destruction must retain the generation that produced it"
29475 );
29476
29477 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29478 let shutdown_controller = Arc::clone(&controller);
29479 let shutdown = std::thread::spawn(move || {
29480 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29481 });
29482 wait_until(
29483 "CurrentThread shutdown to enter the old generation's stopping state",
29484 || {
29485 let state = controller
29486 .state
29487 .lock()
29488 .unwrap_or_else(std::sync::PoisonError::into_inner);
29489 matches!(
29490 &state.lifecycle,
29491 RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
29492 )
29493 },
29494 );
29495
29496 let (restart_tx, restart_rx) = mpsc::channel();
29497 let restart_controller = Arc::clone(&controller);
29498 let restart = std::thread::spawn(move || {
29499 restart_controller.start().unwrap();
29500 restart_tx
29501 .send(restart_controller.backend().generation())
29502 .unwrap();
29503 });
29504
29505 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29506 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
29507 let shutdown_overlapped = early_shutdown.is_some();
29508 let restart_overlapped = early_restart.is_some();
29509 release_tx.send(()).unwrap();
29510
29511 let reentrant_error = reentrant_rx
29512 .recv_timeout(Duration::from_secs(2))
29513 .expect("the retiring output destructor must finish its lifecycle check")
29514 .expect_err("old-generation output destruction must not shut down a replacement runtime");
29515 assert!(
29516 reentrant_error
29517 .to_string()
29518 .contains("generation being stopped"),
29519 "the destructor must still observe its old generation as Stopping: {reentrant_error}"
29520 );
29521
29522 let shutdown_result = early_shutdown.unwrap_or_else(|| {
29523 shutdown_rx
29524 .recv_timeout(Duration::from_secs(2))
29525 .expect("shutdown must finish after detached output destruction retires")
29526 });
29527 shutdown_result.unwrap();
29528 let new_generation = early_restart.unwrap_or_else(|| {
29529 restart_rx
29530 .recv_timeout(Duration::from_secs(2))
29531 .expect("restart must finish after the old host turn retires")
29532 });
29533
29534 driver.join().unwrap();
29535 shutdown.join().unwrap();
29536 restart.join().unwrap();
29537 assert!(
29538 !shutdown_overlapped,
29539 "shutdown must not publish Stopped while a host turn is destroying detached output"
29540 );
29541 assert!(
29542 !restart_overlapped,
29543 "restart must not overlap a host turn from the retiring generation"
29544 );
29545 assert_ne!(new_generation, old_generation);
29546 controller.shutdown().unwrap();
29547 }
29548
29549 #[cfg(napi_runtime_os_threads)]
29550 #[test]
29551 fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
29552 use std::sync::mpsc;
29553
29554 struct ShutdownOnDrop {
29555 controller: Arc<RuntimeController>,
29556 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29557 }
29558
29559 impl Drop for ShutdownOnDrop {
29560 fn drop(&mut self) {
29561 let _ = self.result.send(self.controller.shutdown());
29562 }
29563 }
29564
29565 let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
29566 let (running_tx, running_rx) = mpsc::channel();
29567 let (release_tx, release_rx) = mpsc::channel();
29568 let running = controller
29569 .try_spawn_blocking(move || {
29570 running_tx.send(()).unwrap();
29571 release_rx.recv().unwrap();
29572 })
29573 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29574 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29575
29576 let (reentrant_tx, reentrant_rx) = mpsc::channel();
29577 let bomb = ShutdownOnDrop {
29578 controller: Arc::clone(&controller),
29579 result: reentrant_tx,
29580 };
29581 let queued = controller
29582 .try_spawn_blocking(move || {
29583 let _bomb = &bomb;
29584 })
29585 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29586
29587 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29588 let shutdown_controller = Arc::clone(&controller);
29589 let shutdown = std::thread::spawn(move || {
29590 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29591 });
29592 let error = reentrant_rx
29593 .recv_timeout(Duration::from_secs(2))
29594 .expect("queued destruction must not self-deadlock")
29595 .expect_err("the retiring generation must reject reentrant shutdown");
29596 assert!(error.to_string().contains("generation being stopped"));
29597 release_tx.send(()).unwrap();
29598 shutdown_rx
29599 .recv_timeout(Duration::from_secs(2))
29600 .unwrap()
29601 .unwrap();
29602 shutdown.join().unwrap();
29603 futures::executor::block_on(running).unwrap();
29604 assert!(futures::executor::block_on(queued).is_err());
29605 }
29606
29607 #[cfg(napi_runtime_os_threads)]
29608 #[test]
29609 fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
29610 use std::sync::mpsc;
29611
29612 struct ShutdownAfterRelease {
29613 controller: Arc<RuntimeController>,
29614 entered: mpsc::Sender<()>,
29615 release: mpsc::Receiver<()>,
29616 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29617 }
29618
29619 impl Drop for ShutdownAfterRelease {
29620 fn drop(&mut self) {
29621 self.entered.send(()).unwrap();
29622 self.release.recv().unwrap();
29623 self.result.send(self.controller.shutdown()).unwrap();
29624 }
29625 }
29626
29627 let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
29628 let (holder_started_tx, holder_started_rx) = mpsc::channel();
29629 let (release_holder_tx, release_holder_rx) = mpsc::channel();
29630 let holder = controller
29631 .try_spawn_blocking(move || {
29632 holder_started_tx.send(()).unwrap();
29633 release_holder_rx.recv().unwrap();
29634 })
29635 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29636 holder_started_rx
29637 .recv_timeout(Duration::from_secs(2))
29638 .unwrap();
29639
29640 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29641 let shutdown_controller = Arc::clone(&controller);
29642 let shutdown = std::thread::spawn(move || {
29643 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29644 });
29645 wait_until("the runtime entered Stopping", || {
29646 matches!(
29647 controller
29648 .state
29649 .lock()
29650 .unwrap_or_else(std::sync::PoisonError::into_inner)
29651 .lifecycle,
29652 RuntimeLifecycle::Stopping(_)
29653 )
29654 });
29655
29656 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29657 let (release_drop_tx, release_drop_rx) = mpsc::channel();
29658 let (reentrant_tx, reentrant_rx) = mpsc::channel();
29659 let rejected_controller = Arc::clone(&controller);
29660 let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
29661 let rejected = std::thread::spawn(move || {
29662 let bomb = ShutdownAfterRelease {
29663 controller: Arc::clone(&rejected_controller),
29664 entered: drop_entered_tx,
29665 release: release_drop_rx,
29666 result: reentrant_tx,
29667 };
29668 let handle = rejected_controller.spawn(async move {
29669 let _bomb = bomb;
29670 std::future::pending::<()>().await;
29671 });
29672 rejected_done_tx
29673 .send(futures::executor::block_on(handle))
29674 .unwrap();
29675 });
29676 drop_entered_rx
29677 .recv_timeout(Duration::from_secs(2))
29678 .unwrap();
29679
29680 release_holder_tx.send(()).unwrap();
29681 assert!(
29682 shutdown_rx
29683 .recv_timeout(Duration::from_millis(200))
29684 .is_err(),
29685 "shutdown crossed into Stopped before rejected destruction finished"
29686 );
29687
29688 let (start_tx, start_rx) = mpsc::channel();
29689 let start_controller = Arc::clone(&controller);
29690 let starter = std::thread::spawn(move || {
29691 start_tx.send(start_controller.start()).unwrap();
29692 });
29693 assert!(
29694 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
29695 "restart crossed into a new generation before rejected destruction finished"
29696 );
29697
29698 release_drop_tx.send(()).unwrap();
29699 let error = reentrant_rx
29700 .recv_timeout(Duration::from_secs(2))
29701 .expect("the rejected destructor must complete")
29702 .expect_err("the retiring generation must reject the destructor's shutdown");
29703 assert!(error.to_string().contains("generation being stopped"));
29704 assert!(
29705 rejected_done_rx
29706 .recv_timeout(Duration::from_secs(2))
29707 .unwrap()
29708 .is_err()
29709 );
29710 rejected.join().unwrap();
29711
29712 shutdown_rx
29713 .recv_timeout(Duration::from_secs(2))
29714 .unwrap()
29715 .unwrap();
29716 shutdown.join().unwrap();
29717 start_rx
29718 .recv_timeout(Duration::from_secs(2))
29719 .unwrap()
29720 .unwrap();
29721 starter.join().unwrap();
29722 futures::executor::block_on(holder).unwrap();
29723 controller.shutdown().unwrap();
29724 }
29725
29726 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29727 #[test]
29728 fn shutdown_contains_hostile_caught_panic_payload() {
29729 use std::{panic::panic_any, sync::mpsc};
29730
29731 struct PanicAgain;
29732 impl Drop for PanicAgain {
29733 fn drop(&mut self) {
29734 panic!("caught panic payload destructor escaped");
29735 }
29736 }
29737
29738 struct PanicPayloadOnDrop(mpsc::Sender<()>);
29739 impl Drop for PanicPayloadOnDrop {
29740 fn drop(&mut self) {
29741 let _ = self.0.send(());
29742 panic_any(PanicAgain);
29743 }
29744 }
29745
29746 let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
29747 let (running_tx, running_rx) = mpsc::channel();
29748 let (release_tx, release_rx) = mpsc::channel();
29749 let running = controller
29750 .try_spawn_blocking(move || {
29751 running_tx.send(()).unwrap();
29752 release_rx.recv().unwrap();
29753 })
29754 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29755 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29756
29757 let (drop_tx, drop_rx) = mpsc::channel();
29758 let payload = PanicPayloadOnDrop(drop_tx);
29759 let queued = controller
29760 .try_spawn_blocking(move || {
29761 let _payload = &payload;
29762 })
29763 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29764
29765 let (shutdown_tx, shutdown_rx) = mpsc::channel();
29766 let shutdown_controller = Arc::clone(&controller);
29767 let shutdown = std::thread::spawn(move || {
29768 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29769 });
29770 drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29771 release_tx.send(()).unwrap();
29772 shutdown_rx
29773 .recv_timeout(Duration::from_secs(2))
29774 .expect("hostile panic payload destruction must remain contained")
29775 .unwrap();
29776 shutdown
29777 .join()
29778 .expect("the caught payload must not panic during destruction");
29779 futures::executor::block_on(running).unwrap();
29780 assert!(futures::executor::block_on(queued).is_err());
29781 }
29782
29783 #[cfg(napi_runtime_os_threads)]
29784 #[test]
29785 fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
29786 use std::sync::mpsc;
29787
29788 struct DropSignal(mpsc::Sender<()>);
29789 impl Drop for DropSignal {
29790 fn drop(&mut self) {
29791 let _ = self.0.send(());
29792 }
29793 }
29794
29795 let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
29796 let (running_tx, running_rx) = mpsc::channel();
29797 let (release_tx, release_rx) = mpsc::channel();
29798 let running = controller
29799 .try_spawn_blocking(move || {
29800 running_tx.send(()).unwrap();
29801 release_rx.recv().unwrap();
29802 7usize
29803 })
29804 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29805 running_rx
29806 .recv_timeout(Duration::from_secs(5))
29807 .expect("the first blocking job must hold the only blocking slot");
29808
29809 let queued_ran = Arc::new(AtomicBool::new(false));
29810 let queued_ran_work = Arc::clone(&queued_ran);
29811 let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
29812 let queued_drop_signal = DropSignal(queued_dropped_tx);
29813 let queued = controller
29814 .try_spawn_blocking(move || {
29815 let _drop_signal = queued_drop_signal;
29816 queued_ran_work.store(true, Ordering::SeqCst);
29817 11usize
29818 })
29819 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29820
29821 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29822 let shutdown_controller = Arc::clone(&controller);
29823 let shutdown = std::thread::spawn(move || {
29824 shutdown_controller.shutdown().unwrap();
29825 shutdown_done_tx.send(()).unwrap();
29826 });
29827
29828 queued_dropped_rx
29829 .recv_timeout(Duration::from_secs(5))
29830 .expect("shutdown must drop queued blocking work");
29831 assert!(
29832 !queued_ran.load(Ordering::SeqCst),
29833 "queued blocking work must not execute"
29834 );
29835 assert!(
29836 shutdown_done_rx.try_recv().is_err(),
29837 "shutdown must wait while an accepted blocking closure is still running"
29838 );
29839
29840 release_tx.send(()).unwrap();
29841 shutdown_done_rx
29842 .recv_timeout(Duration::from_secs(5))
29843 .expect("shutdown must finish after running blocking work retires");
29844 shutdown.join().unwrap();
29845 assert_eq!(futures::executor::block_on(running).unwrap(), 7);
29846 assert!(
29847 futures::executor::block_on(queued).is_err(),
29848 "the queued blocking handle must resolve as cancelled"
29849 );
29850 }
29851
29852 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29853 #[test]
29854 fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
29855 use std::sync::mpsc;
29856
29857 struct PanicOnDrop(mpsc::Sender<()>);
29858 impl Drop for PanicOnDrop {
29859 fn drop(&mut self) {
29860 let _ = self.0.send(());
29861 panic!("intentional queued blocking destructor panic");
29862 }
29863 }
29864
29865 let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
29866 let (running_tx, running_rx) = mpsc::channel();
29867 let (release_tx, release_rx) = mpsc::channel();
29868 let running = controller
29869 .try_spawn_blocking(move || {
29870 running_tx.send(()).unwrap();
29871 release_rx.recv().unwrap();
29872 })
29873 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29874 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29875
29876 let (drop_tx, drop_rx) = mpsc::channel();
29877 let panic_on_drop = PanicOnDrop(drop_tx);
29878 let queued = controller
29879 .try_spawn_blocking(move || {
29880 let _keep_captured_until_run = &panic_on_drop;
29881 })
29882 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29883
29884 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29885 let shutdown_controller = Arc::clone(&controller);
29886 let shutdown = std::thread::spawn(move || {
29887 shutdown_controller.shutdown().unwrap();
29888 shutdown_done_tx.send(()).unwrap();
29889 });
29890
29891 drop_rx
29892 .recv_timeout(Duration::from_secs(5))
29893 .expect("shutdown must attempt to drop the queued closure");
29894 release_tx.send(()).unwrap();
29895 shutdown_done_rx
29896 .recv_timeout(Duration::from_secs(5))
29897 .expect("a panicking queued destructor must not strand shutdown");
29898 shutdown
29899 .join()
29900 .expect("shutdown must contain the destructor panic");
29901 futures::executor::block_on(running).unwrap();
29902 assert!(futures::executor::block_on(queued).is_err());
29903
29904 controller
29905 .start()
29906 .expect("the stopped controller must restart");
29907 let restarted = controller
29908 .try_spawn_blocking(|| 17usize)
29909 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29910 assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
29911 controller.shutdown().unwrap();
29912 }
29913
29914 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29915 #[test]
29916 fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
29917 use std::{panic::panic_any, sync::mpsc};
29918
29919 struct ReentrantPayload {
29920 controller: Arc<RuntimeController>,
29921 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
29922 }
29923
29924 impl Drop for ReentrantPayload {
29925 fn drop(&mut self) {
29926 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
29927 let shutdown = self.controller.shutdown();
29928 self.result.send((generation, shutdown)).unwrap();
29929 }
29930 }
29931
29932 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
29933
29934 impl Drop for PanicPayloadOnDrop {
29935 fn drop(&mut self) {
29936 panic_any(self.0.take().expect("the result must panic only once"));
29937 }
29938 }
29939
29940 let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
29941 let generation = controller.backend().generation();
29942 let (started_tx, started_rx) = mpsc::channel();
29943 let (release_tx, release_rx) = mpsc::channel();
29944 let (result_tx, result_rx) = mpsc::channel();
29945 let payload_controller = Arc::clone(&controller);
29946 let handle = controller
29947 .try_spawn_blocking(move || {
29948 started_tx.send(()).unwrap();
29949 release_rx.recv().unwrap();
29950 PanicPayloadOnDrop(Some(ReentrantPayload {
29951 controller: payload_controller,
29952 result: result_tx,
29953 }))
29954 })
29955 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29956 started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29957
29958 drop(handle);
29961 release_tx.send(()).unwrap();
29962 let (observed_generation, reentrant_shutdown) = result_rx
29963 .recv_timeout(Duration::from_secs(5))
29964 .expect("the dropped result panic payload must not self-deadlock");
29965 assert_eq!(observed_generation, Some(generation));
29966 let error =
29967 reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
29968 assert!(error.to_string().contains("work running on that runtime"));
29969
29970 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29971 let shutdown_controller = Arc::clone(&controller);
29972 let shutdown = std::thread::spawn(move || {
29973 shutdown_controller.shutdown().unwrap();
29974 shutdown_done_tx.send(()).unwrap();
29975 });
29976 shutdown_done_rx
29977 .recv_timeout(Duration::from_secs(5))
29978 .expect("a dropped-result destructor panic must not strand shutdown");
29979 shutdown
29980 .join()
29981 .expect("shutdown must contain the destructor panic");
29982
29983 controller
29984 .start()
29985 .expect("the stopped controller must restart");
29986 let restarted = controller
29987 .try_spawn_blocking(|| 19usize)
29988 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29989 assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
29990 controller.shutdown().unwrap();
29991 }
29992
29993 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29994 #[test]
29995 fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
29996 use std::sync::mpsc;
29997
29998 struct PanicOnDrop(mpsc::Sender<()>);
29999
30000 impl Drop for PanicOnDrop {
30001 fn drop(&mut self) {
30002 self.0.send(()).unwrap();
30003 panic!("intentional buffered blocking result destructor panic");
30004 }
30005 }
30006
30007 let options = RuntimeOptions {
30008 flavor: RuntimeFlavor::MultiThread,
30009 worker_threads: 2,
30010 max_blocking_tasks: 1,
30011 thread_name_prefix: "buffered-blocking-result-drop".to_string(),
30012 park_deadline: None,
30013 drain_linger: DEFAULT_DRAIN_LINGER,
30014 };
30015 let executor =
30016 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30017 let (started_tx, started_rx) = mpsc::channel();
30018 let (dropped_tx, dropped_rx) = mpsc::channel();
30019 let handle = executor.schedule_blocking(move || {
30020 started_tx.send(()).unwrap();
30021 PanicOnDrop(dropped_tx)
30022 });
30023 started_rx
30024 .recv_timeout(Duration::from_secs(2))
30025 .expect("the blocking closure must run");
30026 wait_until("the completed blocking result to be buffered", || {
30027 executor.active_blocking.load(Ordering::Acquire) == 0
30028 });
30029 assert!(
30030 dropped_rx.try_recv().is_err(),
30031 "the buffered result must remain owned by the live receiver"
30032 );
30033
30034 let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
30035 assert!(
30036 dropped.is_ok(),
30037 "dropping the buffered result must contain its destructor panic"
30038 );
30039 dropped_rx
30040 .recv_timeout(Duration::from_secs(2))
30041 .expect("dropping the receiver must reclaim the buffered result");
30042 }
30043
30044 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30045 #[test]
30046 fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
30047 use std::sync::mpsc;
30048
30049 struct PanicOnDrop(mpsc::Sender<()>);
30050 impl Drop for PanicOnDrop {
30051 fn drop(&mut self) {
30052 let _ = self.0.send(());
30053 panic!("intentional late blocking destructor panic");
30054 }
30055 }
30056
30057 let executor = multi_thread_executor(2, None, "closed-queue-drop");
30058 executor.begin_shutdown();
30059
30060 let (drop_tx, drop_rx) = mpsc::channel();
30061 let panic_on_drop = PanicOnDrop(drop_tx);
30062 let handle = executor.schedule_blocking(move || {
30063 let _keep_captured_until_run = &panic_on_drop;
30064 });
30065
30066 drop_rx
30067 .recv_timeout(Duration::from_secs(5))
30068 .expect("the closed queue must retire the rejected closure");
30069 assert!(
30070 futures::executor::block_on(handle).is_err(),
30071 "the rejected blocking handle must resolve as cancelled"
30072 );
30073 }
30074
30075 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30076 #[test]
30077 fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
30078 struct PanicWake;
30079 impl std::task::Wake for PanicWake {
30080 fn wake(self: Arc<Self>) {
30081 panic!("intentional timer waker panic");
30082 }
30083 }
30084
30085 let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
30086 let backend = controller.backend();
30087 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30088 panic!("configured MultiThread backend must create a Rayon executor");
30089 };
30090 let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
30091 let waker = Waker::from(Arc::new(PanicWake));
30092 let mut cx = Context::from_waker(&waker);
30093 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30094 drop(backend);
30095
30096 controller
30097 .shutdown()
30098 .expect("shutdown must contain the timer waker panic");
30099 controller
30100 .start()
30101 .expect("the stopped controller must restart");
30102 let restarted = controller
30103 .try_spawn_blocking(|| 23usize)
30104 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30105 assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
30106 controller.shutdown().unwrap();
30107 }
30108
30109 #[cfg(napi_runtime_os_threads)]
30110 #[test]
30111 fn multi_thread_restart_waits_for_old_generation_worker_exit() {
30112 use std::sync::mpsc;
30113
30114 let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
30115 let backend = controller.backend();
30116 let old_generation = backend.generation();
30117 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30118 panic!("configured MultiThread backend must create a Rayon executor");
30119 };
30120 let old_workers = Arc::clone(&executor.worker_lifecycle);
30121 drop(backend);
30122
30123 let (running_tx, running_rx) = mpsc::channel();
30124 let (release_tx, release_rx) = mpsc::channel();
30125 let running = controller
30126 .try_spawn_blocking(move || {
30127 running_tx.send(()).unwrap();
30128 release_rx.recv().unwrap();
30129 })
30130 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30131 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30132
30133 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30134 let shutdown_controller = Arc::clone(&controller);
30135 let shutdown = std::thread::spawn(move || {
30136 shutdown_controller.shutdown().unwrap();
30137 shutdown_done_tx.send(()).unwrap();
30138 });
30139 wait_until("controller entered Stopping", || {
30140 let state = controller
30141 .state
30142 .lock()
30143 .unwrap_or_else(std::sync::PoisonError::into_inner);
30144 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
30145 });
30146
30147 let (start_done_tx, start_done_rx) = mpsc::channel();
30148 let start_controller = Arc::clone(&controller);
30149 let starter = std::thread::spawn(move || {
30150 start_controller.start().unwrap();
30151 start_done_tx.send(()).unwrap();
30152 });
30153 assert!(
30154 start_done_rx.try_recv().is_err(),
30155 "start must wait while shutdown is quiescing"
30156 );
30157 assert!(
30158 old_workers.remaining() > 0,
30159 "the held old generation must still own workers"
30160 );
30161
30162 release_tx.send(()).unwrap();
30163 futures::executor::block_on(running).unwrap();
30164 shutdown_done_rx
30165 .recv_timeout(Duration::from_secs(5))
30166 .unwrap();
30167 start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30168 shutdown.join().unwrap();
30169 starter.join().unwrap();
30170 assert_eq!(
30171 old_workers.remaining(),
30172 0,
30173 "restart may begin only after every old worker exits"
30174 );
30175
30176 let backend = controller.backend();
30177 assert_ne!(backend.generation(), old_generation);
30178 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30179 panic!("restarted backend must remain MultiThread");
30180 };
30181 assert_eq!(
30182 executor.pool.current_num_threads(),
30183 controller.options().worker_threads
30184 );
30185 drop(backend);
30186 controller.shutdown().unwrap();
30187 }
30188
30189 #[cfg(napi_runtime_os_threads)]
30190 #[test]
30191 fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
30192 let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
30193 let task_controller = Arc::clone(&controller);
30194 let handle = controller
30195 .try_spawn(async move {
30196 task_controller
30197 .shutdown()
30198 .expect_err("runtime work must not wait for its own generation")
30199 .to_string()
30200 })
30201 .unwrap_or_else(|_| panic!("running runtime must accept the task"));
30202 let message = futures::executor::block_on(handle).unwrap();
30203 assert!(message.contains("cannot shut down"));
30204 controller.shutdown().unwrap();
30205 }
30206
30207 #[cfg(napi_runtime_os_threads)]
30218 #[test]
30219 fn two_phase_shutdown_matches_single_call() {
30220 let controller = multi_thread_controller("two-phase-shutdown", 2, 1);
30224 let entered = Arc::new(AtomicBool::new(false));
30225 let release = Arc::new(AtomicBool::new(false));
30226 let job_entered = Arc::clone(&entered);
30227 let job_release = Arc::clone(&release);
30228 let handle = controller
30229 .try_spawn_blocking(move || {
30230 job_entered.store(true, Ordering::SeqCst);
30231 while !job_release.load(Ordering::SeqCst) {
30232 std::hint::spin_loop();
30233 }
30234 7usize
30235 })
30236 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30237 while !entered.load(Ordering::SeqCst) {
30238 std::thread::yield_now();
30239 }
30240
30241 assert!(
30242 controller
30243 .begin_shutdown()
30244 .expect("phase 1 on a running generation must be accepted"),
30245 "phase 1 must report the still-running blocking closure instead of waiting for it"
30246 );
30247 assert!(
30248 controller.runtime_work_pending(),
30249 "the parked closure holds its generation guard"
30250 );
30251 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30254 panic!("a submission between the phases must be rejected");
30255 };
30256 assert_eq!(
30257 error.to_string(),
30258 "the async runtime is stopped; call start before submitting work"
30259 );
30260
30261 release.store(true, Ordering::SeqCst);
30262 let deadline = Instant::now() + Duration::from_secs(5);
30263 while controller.runtime_work_pending() {
30264 assert!(
30265 Instant::now() < deadline,
30266 "the released closure must retire and the poll must answer false"
30267 );
30268 std::thread::yield_now();
30269 }
30270 assert_eq!(
30271 futures::executor::block_on(handle).unwrap(),
30272 7usize,
30273 "the closure that phase 1 refused to wait for still delivers its result"
30274 );
30275
30276 controller
30277 .finish_shutdown()
30278 .expect("phase 2 must complete the outstanding phase 1");
30279 {
30280 let state = controller
30281 .state
30282 .lock()
30283 .unwrap_or_else(std::sync::PoisonError::into_inner);
30284 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30285 assert!(
30286 state.draining.is_none(),
30287 "phase 2 must consume the phase-1 handoff"
30288 );
30289 }
30290
30291 controller.start().expect("a stopped runtime must restart");
30293 drop(controller.backend());
30294 controller
30295 .shutdown()
30296 .expect("the single call must still shut the restarted runtime down");
30297 }
30298
30299 #[cfg(napi_runtime_os_threads)]
30300 #[test]
30301 fn concurrent_shutdown_during_two_phase_still_reports_success() {
30302 let controller = Arc::new(multi_thread_controller("two-phase-concurrent", 2, 1));
30313 let entered = Arc::new(AtomicBool::new(false));
30314 let committed = Arc::new(AtomicBool::new(false));
30315 let job_entered = Arc::clone(&entered);
30316 let job_committed = Arc::clone(&committed);
30317 let handle = controller
30321 .try_spawn_blocking(move || {
30322 job_entered.store(true, Ordering::SeqCst);
30323 while !job_committed.load(Ordering::SeqCst) {
30324 std::hint::spin_loop();
30325 }
30326 3usize
30327 })
30328 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30329 wait_until("the blocking closure to park", || {
30330 entered.load(Ordering::SeqCst)
30331 });
30332 assert!(
30333 controller
30334 .begin_shutdown()
30335 .expect("phase 1 must be accepted")
30336 );
30337
30338 let (concurrent_tx, concurrent_rx) = std::sync::mpsc::channel();
30339 let hook_controller = Arc::clone(&controller);
30340 let hook_committed = Arc::clone(&committed);
30341 let owner_took_the_drain = Arc::new(AtomicBool::new(false));
30342 let hook_took_the_drain = Arc::clone(&owner_took_the_drain);
30343 AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30344 *slot.borrow_mut() = Some(Box::new(move || {
30345 hook_took_the_drain.store(true, Ordering::SeqCst);
30346 let concurrent = std::thread::spawn(move || {
30347 hook_committed.store(true, Ordering::SeqCst);
30348 hook_controller.shutdown()
30349 });
30350 concurrent_tx
30351 .send(concurrent)
30352 .expect("the test thread must still be waiting for the concurrent handle");
30353 }));
30354 });
30355
30356 controller
30357 .finish_shutdown()
30358 .expect("phase 2 must complete the handshake");
30359 assert!(
30360 owner_took_the_drain.load(Ordering::SeqCst),
30361 "the phase-1 owner must be the thread that took the handoff"
30362 );
30363 concurrent_rx
30364 .recv()
30365 .expect("the hook must have started the concurrent shutdown")
30366 .join()
30367 .expect("the concurrent shutdown thread must not panic")
30368 .expect("a concurrent single-call shutdown must still succeed");
30369
30370 assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30371 let state = controller
30372 .state
30373 .lock()
30374 .unwrap_or_else(std::sync::PoisonError::into_inner);
30375 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30376 assert!(state.draining.is_none());
30377 }
30378
30379 #[cfg(napi_runtime_os_threads)]
30380 #[test]
30381 fn a_phase_one_during_the_in_flight_drain_answers_without_waiting() {
30382 let controller = Arc::new(multi_thread_controller("two-phase-inflight-drain", 2, 1));
30400 let entered = Arc::new(AtomicBool::new(false));
30401 let release = Arc::new(AtomicBool::new(false));
30402 let job_entered = Arc::clone(&entered);
30403 let job_release = Arc::clone(&release);
30404 let handle = controller
30405 .try_spawn_blocking(move || {
30406 job_entered.store(true, Ordering::SeqCst);
30407 while !job_release.load(Ordering::SeqCst) {
30408 std::hint::spin_loop();
30409 }
30410 11usize
30411 })
30412 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30413 wait_until("the blocking closure to park", || {
30414 entered.load(Ordering::SeqCst)
30415 });
30416 assert!(
30417 controller
30418 .begin_shutdown()
30419 .expect("phase 1 must be accepted"),
30420 "the parked closure must be reported as live"
30421 );
30422
30423 let (probe_tx, probe_rx) = std::sync::mpsc::channel();
30424 let hook_controller = Arc::clone(&controller);
30425 let hook_release = Arc::clone(&release);
30426 AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30427 *slot.borrow_mut() = Some(Box::new(move || {
30428 let probe_controller = Arc::clone(&hook_controller);
30429 let probe = std::thread::spawn(move || {
30430 let pending = probe_controller.runtime_work_pending();
30431 (pending, probe_controller.begin_shutdown())
30432 });
30433 let watchdog = std::thread::spawn(move || {
30434 let deadline = Instant::now() + Duration::from_secs(5);
30435 while !probe.is_finished() && Instant::now() < deadline {
30436 std::thread::yield_now();
30437 }
30438 let answered_during_the_drain = probe.is_finished();
30442 hook_release.store(true, Ordering::SeqCst);
30443 (answered_during_the_drain, probe.join())
30444 });
30445 probe_tx
30446 .send(watchdog)
30447 .expect("the test thread must still be waiting for the probe handle");
30448 }));
30449 });
30450
30451 controller
30452 .finish_shutdown()
30453 .expect("phase 2 must complete the outstanding phase 1");
30454 let (answered_during_the_drain, probe) = probe_rx
30455 .recv()
30456 .expect("the hook must have started the probe")
30457 .join()
30458 .expect("the watchdog thread must not panic");
30459 let (pending, verdict) = probe.expect("the probe thread must not panic");
30460 assert!(
30461 answered_during_the_drain,
30462 "a phase 1 re-entered during the in-flight drain must answer without waiting for the join"
30463 );
30464 assert!(
30465 pending,
30466 "the poll must still see the parked closure while the drain that is waiting for it runs"
30467 );
30468 assert!(
30469 verdict.expect("a phase 1 during the in-flight drain must be accepted"),
30470 "the re-entering phase 1 must report the parked closure as live"
30471 );
30472
30473 assert_eq!(futures::executor::block_on(handle).unwrap(), 11usize);
30474 {
30475 let state = controller
30476 .state
30477 .lock()
30478 .unwrap_or_else(std::sync::PoisonError::into_inner);
30479 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30480 assert!(
30481 state.draining.is_none(),
30482 "publishing `Stopped` must retire the in-flight drain marker too"
30483 );
30484 }
30485 assert!(
30486 !controller.runtime_work_pending(),
30487 "a stopped runtime has no pending work"
30488 );
30489
30490 controller.start().expect("a stopped runtime must restart");
30491 drop(controller.backend());
30492 controller
30493 .shutdown()
30494 .expect("the single call must still shut the restarted runtime down");
30495 }
30496
30497 #[cfg(napi_runtime_os_threads)]
30498 #[test]
30499 fn phase_one_owner_finishes_after_a_concurrent_shutdown_drains() {
30500 let controller = Arc::new(multi_thread_controller("two-phase-owner-finish", 2, 1));
30507 let entered = Arc::new(AtomicBool::new(false));
30508 let release = Arc::new(AtomicBool::new(false));
30509 let job_entered = Arc::clone(&entered);
30510 let job_release = Arc::clone(&release);
30511 let handle = controller
30512 .try_spawn_blocking(move || {
30513 job_entered.store(true, Ordering::SeqCst);
30514 while !job_release.load(Ordering::SeqCst) {
30515 std::hint::spin_loop();
30516 }
30517 3usize
30518 })
30519 .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30520 wait_until("the blocking closure to park", || {
30521 entered.load(Ordering::SeqCst)
30522 });
30523 assert!(
30524 controller
30525 .begin_shutdown()
30526 .expect("phase 1 must be accepted"),
30527 "the parked closure must be reported as live"
30528 );
30529
30530 let single_call = Arc::clone(&controller);
30531 let concurrent = std::thread::spawn(move || single_call.shutdown());
30532 release.store(true, Ordering::SeqCst);
30533 concurrent
30534 .join()
30535 .expect("the concurrent shutdown thread must not panic")
30536 .expect("a concurrent single-call shutdown must still succeed");
30537 {
30538 let state = controller
30539 .state
30540 .lock()
30541 .unwrap_or_else(std::sync::PoisonError::into_inner);
30542 assert!(
30543 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30544 "the concurrent call must have published the stop"
30545 );
30546 assert!(
30547 state.draining.is_none(),
30548 "the concurrent call must have consumed the phase-1 handoff"
30549 );
30550 }
30551
30552 controller
30553 .finish_shutdown()
30554 .expect("the thread that began phase 1 must still be told its shutdown completed");
30555 assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30556
30557 controller.start().expect("a stopped runtime must restart");
30562 drop(controller.backend());
30563 controller
30564 .finish_shutdown()
30565 .expect("phase 2 after a restart is a no-op, not an error");
30566 {
30567 let state = controller
30568 .state
30569 .lock()
30570 .unwrap_or_else(std::sync::PoisonError::into_inner);
30571 assert!(
30572 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30573 "the no-op phase 2 must not stop the restarted generation"
30574 );
30575 }
30576 controller
30577 .shutdown()
30578 .expect("the single call must still shut the restarted runtime down");
30579 }
30580
30581 #[test]
30582 fn begin_shutdown_twice_does_not_block() {
30583 let controller = current_thread_controller("two-phase-reentrant-begin");
30591 drop(controller.backend());
30592
30593 assert!(
30594 !controller
30595 .begin_shutdown()
30596 .expect("phase 1 on an idle generation must be accepted"),
30597 "an idle generation has nothing for the host to wait for"
30598 );
30599 {
30600 let state = controller
30601 .state
30602 .lock()
30603 .unwrap_or_else(std::sync::PoisonError::into_inner);
30604 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_)));
30605 }
30606 assert!(
30607 !controller
30608 .begin_shutdown()
30609 .expect("a second phase 1 must answer immediately"),
30610 "the second phase 1 must report the same verdict"
30611 );
30612
30613 controller
30614 .finish_shutdown()
30615 .expect("one phase 2 completes the single outstanding phase 1");
30616 {
30617 let state = controller
30618 .state
30619 .lock()
30620 .unwrap_or_else(std::sync::PoisonError::into_inner);
30621 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30622 assert!(state.draining.is_none());
30623 }
30624
30625 controller
30626 .finish_shutdown()
30627 .expect("a second phase 2 repeats the completed stop instead of erroring");
30628 {
30629 let state = controller
30630 .state
30631 .lock()
30632 .unwrap_or_else(std::sync::PoisonError::into_inner);
30633 assert!(
30634 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30635 "the repeated phase 2 must change nothing"
30636 );
30637 }
30638 }
30639
30640 #[test]
30641 fn current_thread_two_phase_is_idle_immediately() {
30642 let controller = current_thread_controller("two-phase-current-thread");
30645 drop(controller.backend());
30646 assert!(
30647 !controller.runtime_work_pending(),
30648 "an idle running generation has no pending work"
30649 );
30650
30651 assert!(
30652 !controller
30653 .begin_shutdown()
30654 .expect("phase 1 must be accepted"),
30655 "phase 1 on an idle generation must report nothing to wait for"
30656 );
30657 assert!(
30658 !controller.runtime_work_pending(),
30659 "the poll must stay false across the window"
30660 );
30661 controller.finish_shutdown().expect("phase 2 must complete");
30662 {
30663 let state = controller
30664 .state
30665 .lock()
30666 .unwrap_or_else(std::sync::PoisonError::into_inner);
30667 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30668 }
30669
30670 controller.start().expect("a stopped runtime must restart");
30671 drop(controller.backend());
30672 controller
30673 .shutdown()
30674 .expect("the single call must still work");
30675 }
30676
30677 #[test]
30678 fn finish_shutdown_without_begin_is_an_error() {
30679 let controller = current_thread_controller("two-phase-orphan-finish");
30680 drop(controller.backend());
30681 let error = controller
30682 .finish_shutdown()
30683 .expect_err("phase 2 without an outstanding phase 1 must be rejected");
30684 assert_eq!(
30685 error.to_string(),
30686 "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown"
30687 );
30688 controller
30689 .shutdown()
30690 .expect("the rejected phase 2 must leave the runtime shuttable");
30691 }
30692
30693 #[test]
30694 fn a_restart_does_not_inherit_a_settled_shutdown_handoff() {
30695 let controller = current_thread_controller("two-phase-stale-settled");
30703 drop(controller.backend());
30704 controller
30705 .shutdown()
30706 .expect("the first shutdown must complete");
30707
30708 assert!(
30709 !controller
30710 .begin_shutdown()
30711 .expect("phase 1 on a stopped runtime must be accepted"),
30712 "a stopped runtime has no work for the host to wait for"
30713 );
30714 controller.start().expect("a stopped runtime must restart");
30715 {
30716 let state = controller
30717 .state
30718 .lock()
30719 .unwrap_or_else(std::sync::PoisonError::into_inner);
30720 assert!(
30721 state.draining.is_none(),
30722 "a fresh generation must not inherit the settled handoff"
30723 );
30724 }
30725 drop(controller.backend());
30726
30727 controller
30728 .shutdown()
30729 .expect("the second shutdown must stop the restarted generation");
30730 {
30731 let state = controller
30732 .state
30733 .lock()
30734 .unwrap_or_else(std::sync::PoisonError::into_inner);
30735 assert!(
30736 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30737 "the restarted generation must really be stopped, not reported stopped"
30738 );
30739 assert!(state.draining.is_none());
30740 }
30741 assert!(
30742 controller.current_backend.load().is_none(),
30743 "the stopped generation's backend must be gone"
30744 );
30745 assert!(
30746 !controller.runtime_work_pending(),
30747 "a stopped runtime has no pending work"
30748 );
30749 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30750 panic!("a submission after the second shutdown must be rejected");
30751 };
30752 assert_eq!(
30753 error.to_string(),
30754 "the async runtime is stopped; call start before submitting work"
30755 );
30756 }
30757
30758 #[test]
30759 fn a_settled_phase_one_is_reported_by_every_finish_across_a_restart() {
30760 let controller = current_thread_controller("two-phase-settled-idempotent");
30768 drop(controller.backend());
30769 controller
30770 .shutdown()
30771 .expect("the first shutdown must complete");
30772 assert!(
30773 !controller
30774 .begin_shutdown()
30775 .expect("phase 1 on a stopped runtime must be accepted")
30776 );
30777 controller.start().expect("a stopped runtime must restart");
30778 {
30779 let state = controller
30780 .state
30781 .lock()
30782 .unwrap_or_else(std::sync::PoisonError::into_inner);
30783 assert!(
30784 state.draining.is_none(),
30785 "the restart must drop the settled handoff"
30786 );
30787 assert!(
30788 state.phase_ever_opened,
30789 "the restart must not erase the fact that a stop was announced"
30790 );
30791 }
30792
30793 controller
30794 .finish_shutdown()
30795 .expect("the announced stop must be reported to the caller that opened it");
30796 controller
30797 .finish_shutdown()
30798 .expect("and to every phase 2 after it -- the call is idempotent");
30799 {
30800 let state = controller
30801 .state
30802 .lock()
30803 .unwrap_or_else(std::sync::PoisonError::into_inner);
30804 assert!(
30805 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30806 "neither phase 2 may stop the generation the restart published"
30807 );
30808 }
30809 drop(controller.backend());
30810 controller
30811 .shutdown()
30812 .expect("the no-op phase 2s must leave the runtime shuttable");
30813 }
30814
30815 #[test]
30816 fn shutdown_racing_a_start_still_reports_ok() {
30817 let controller = Arc::new(current_thread_controller("shutdown-racing-start"));
30829 drop(controller.backend());
30830 controller
30831 .shutdown()
30832 .expect("the first shutdown must complete");
30833
30834 let start_controller = Arc::clone(&controller);
30835 let started = Arc::new(AtomicBool::new(false));
30836 let hook_started = Arc::clone(&started);
30837 BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
30838 *slot.borrow_mut() = Some(Box::new(move || {
30839 start_controller
30840 .start()
30841 .expect("a stopped runtime must restart");
30842 hook_started.store(true, Ordering::SeqCst);
30843 }));
30844 });
30845 controller
30846 .shutdown()
30847 .expect("a shutdown must report the phase it opened, even across a racing start");
30848 assert!(
30849 started.load(Ordering::SeqCst),
30850 "the hook must have run the start inside the window"
30851 );
30852
30853 {
30854 let state = controller
30855 .state
30856 .lock()
30857 .unwrap_or_else(std::sync::PoisonError::into_inner);
30858 assert!(
30859 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30860 "the racing start wins the window: its generation is live, not stopped"
30861 );
30862 assert!(
30863 state.draining.is_none(),
30864 "the start must have dropped the settled handoff"
30865 );
30866 }
30867
30868 let ran = Arc::new(AtomicBool::new(false));
30871 let ran_task = Arc::clone(&ran);
30872 controller
30873 .try_spawn_detached(async move {
30874 ran_task.store(true, Ordering::SeqCst);
30875 })
30876 .unwrap_or_else(|_| panic!("the generation the start published must accept work"));
30877 let dispatch = controller_current_thread_dispatch(&controller);
30878 controller.drive_current_thread_dispatch(dispatch, false);
30879 assert!(ran.load(Ordering::SeqCst));
30880 controller
30881 .shutdown()
30882 .expect("the restarted generation must still shut down");
30883 let state = controller
30884 .state
30885 .lock()
30886 .unwrap_or_else(std::sync::PoisonError::into_inner);
30887 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30888 assert!(state.draining.is_none());
30889 }
30890
30891 #[cfg(napi_runtime_os_threads)]
30895 #[test]
30896 fn concurrent_split_finish_racing_a_single_call_shutdown() {
30897 let controller = Arc::new(current_thread_controller("split-finish-vs-single"));
30907 drop(controller.backend());
30908
30909 let split_controller = Arc::clone(&controller);
30910 let (tx, rx) = std::sync::mpsc::channel();
30911 BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
30912 *slot.borrow_mut() = Some(Box::new(move || {
30913 let split = std::thread::spawn(move || {
30914 let pending = split_controller
30915 .begin_shutdown()
30916 .expect("a second phase 1 must re-report the outstanding handoff");
30917 split_controller
30918 .finish_shutdown()
30919 .expect("an in-protocol phase 2 must complete the outstanding stop");
30920 pending
30921 });
30922 tx.send(
30923 split
30924 .join()
30925 .expect("the split shutdown thread must not panic"),
30926 )
30927 .expect("the test thread must still be waiting for the split result");
30928 }));
30929 });
30930
30931 controller
30932 .shutdown()
30933 .expect("the single call must report the stop it announced, whoever joined it");
30934 assert!(
30935 !rx
30936 .try_recv()
30937 .expect("the hook must have run the split pair inside the window"),
30938 "an idle generation has nothing for the host to wait for"
30939 );
30940
30941 let state = controller
30942 .state
30943 .lock()
30944 .unwrap_or_else(std::sync::PoisonError::into_inner);
30945 assert!(
30946 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30947 "the generation must really be stopped, not reported stopped"
30948 );
30949 assert!(state.draining.is_none());
30950 }
30951
30952 #[test]
30953 fn repeated_begin_start_pairs_leave_no_residue() {
30954 const PAIRS: usize = 50;
30959 let controller = current_thread_controller("begin-start-residue");
30960 drop(controller.backend());
30961 for _ in 0..PAIRS {
30962 controller
30963 .shutdown()
30964 .expect("each shutdown must stop the live generation");
30965 assert!(
30966 !controller
30967 .begin_shutdown()
30968 .expect("phase 1 on a stopped runtime must be accepted"),
30969 "a stopped runtime has no work for the host to wait for"
30970 );
30971 controller
30972 .start()
30973 .expect("a zero-backend window must let a restart through");
30974 let state = controller
30975 .state
30976 .lock()
30977 .unwrap_or_else(std::sync::PoisonError::into_inner);
30978 assert!(
30979 state.draining.is_none(),
30980 "the restart must drop the settled handoff"
30981 );
30982 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Running(_)));
30983 }
30984
30985 controller
30986 .finish_shutdown()
30987 .expect("a phase 2 with nothing outstanding is a no-op Ok");
30988 {
30989 let state = controller
30990 .state
30991 .lock()
30992 .unwrap_or_else(std::sync::PoisonError::into_inner);
30993 assert!(
30994 matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30995 "the no-op phase 2 must not stop the generation the last restart published"
30996 );
30997 }
30998
30999 controller
31000 .shutdown()
31001 .expect("a full shutdown after the begin/start pairs must still work");
31002 let state = controller
31003 .state
31004 .lock()
31005 .unwrap_or_else(std::sync::PoisonError::into_inner);
31006 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31007 assert!(state.draining.is_none());
31008 }
31009
31010 #[cfg(napi_runtime_os_threads)]
31011 #[test]
31012 fn a_restart_after_a_settled_phase_one_still_joins_its_workers() {
31013 let controller = multi_thread_controller("two-phase-stale-settled-mt", 2, 1);
31017 drop(controller.backend());
31018 controller
31019 .shutdown()
31020 .expect("the first shutdown must complete");
31021 assert!(
31022 !controller
31023 .begin_shutdown()
31024 .expect("phase 1 on a stopped runtime must be accepted")
31025 );
31026 controller.start().expect("a stopped runtime must restart");
31027
31028 let backend = controller.backend();
31029 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31030 panic!("configured MultiThread backend must create a Rayon executor");
31031 };
31032 let workers = Arc::clone(&executor.worker_lifecycle);
31033 drop(backend);
31034 let handle = controller
31035 .try_spawn_blocking(|| 5usize)
31036 .unwrap_or_else(|_| panic!("the restarted generation must accept work"));
31037 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
31038
31039 controller
31040 .shutdown()
31041 .expect("the second shutdown must stop the restarted generation");
31042 assert_eq!(
31043 workers.remaining(),
31044 0,
31045 "phase 2 must join the restarted generation's workers"
31046 );
31047 {
31048 let state = controller
31049 .state
31050 .lock()
31051 .unwrap_or_else(std::sync::PoisonError::into_inner);
31052 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31053 assert!(state.draining.is_none());
31054 }
31055 let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31056 panic!("a submission after the second shutdown must be rejected");
31057 };
31058 assert_eq!(
31059 error.to_string(),
31060 "the async runtime is stopped; call start before submitting work"
31061 );
31062 }
31063
31064 #[cfg(napi_runtime_os_threads)]
31068 struct ParkedRejectedDropFuture {
31069 entered: std::sync::mpsc::Sender<()>,
31070 release: std::sync::mpsc::Receiver<()>,
31071 }
31072
31073 #[cfg(napi_runtime_os_threads)]
31074 impl Future for ParkedRejectedDropFuture {
31075 type Output = ();
31076
31077 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31078 Poll::Pending
31079 }
31080 }
31081
31082 #[cfg(napi_runtime_os_threads)]
31083 impl Drop for ParkedRejectedDropFuture {
31084 fn drop(&mut self) {
31085 self.entered.send(()).unwrap();
31086 self.release.recv().unwrap();
31087 }
31088 }
31089
31090 #[cfg(napi_runtime_os_threads)]
31094 fn park_a_rejected_destructor(
31095 controller: &Arc<RuntimeController>,
31096 fail_backend_creation: bool,
31097 ) -> (std::thread::JoinHandle<()>, std::sync::mpsc::Sender<()>) {
31098 use std::sync::mpsc;
31099
31100 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
31101 let (release_drop_tx, release_drop_rx) = mpsc::channel();
31102 let submit_controller = Arc::clone(controller);
31103 let submitter = std::thread::spawn(move || {
31104 if fail_backend_creation {
31105 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
31106 }
31107 let result = futures::executor::block_on(submit_controller.spawn(ParkedRejectedDropFuture {
31108 entered: drop_entered_tx,
31109 release: release_drop_rx,
31110 }));
31111 assert!(
31112 result.is_err(),
31113 "the submission must be rejected, not accepted"
31114 );
31115 });
31116 drop_entered_rx
31117 .recv_timeout(Duration::from_secs(2))
31118 .expect("the rejected future destructor must start");
31119 (submitter, release_drop_tx)
31120 }
31121
31122 #[cfg(napi_runtime_os_threads)]
31128 fn begin_shutdown_within(
31129 controller: &Arc<RuntimeController>,
31130 what: &str,
31131 timeout: Duration,
31132 ) -> Result<bool, RuntimeConfigError> {
31133 use std::sync::mpsc;
31134
31135 let (tx, rx) = mpsc::channel();
31136 let begin_controller = Arc::clone(controller);
31137 let beginner = std::thread::spawn(move || {
31138 tx.send(begin_controller.begin_shutdown()).unwrap();
31139 });
31140 let answer = rx
31141 .recv_timeout(timeout)
31142 .unwrap_or_else(|_| panic!("phase 1 must not wait: {what}"));
31143 join_within(what, beginner, Duration::from_secs(2));
31144 answer
31145 }
31146
31147 #[cfg(napi_runtime_os_threads)]
31148 #[test]
31149 fn begin_shutdown_does_not_wait_for_rejected_destruction_on_a_stopped_runtime() {
31150 use std::sync::mpsc;
31158
31159 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-stopped"));
31160 drop(controller.backend());
31161 controller
31162 .shutdown()
31163 .expect("the first shutdown must complete");
31164
31165 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31166
31167 assert!(
31168 begin_shutdown_within(
31169 &controller,
31170 "phase 1 on a stopped runtime with a rejected destructor outstanding",
31171 Duration::from_secs(2),
31172 )
31173 .expect("phase 1 on a stopped runtime must be accepted"),
31174 "the outstanding rejected destruction is a wait phase 2 still owes"
31175 );
31176 assert!(
31177 controller.runtime_work_pending(),
31178 "the poll must report the destruction, or the host stops turning the loop that frees it"
31179 );
31180 {
31181 let state = controller
31182 .state
31183 .lock()
31184 .unwrap_or_else(std::sync::PoisonError::into_inner);
31185 assert!(matches!(
31186 &state.lifecycle,
31187 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
31188 ));
31189 assert!(matches!(
31190 &state.draining,
31191 Some(ShutdownDrain::ZeroBackend(
31192 ZeroBackendShutdownTarget::Stopped
31193 ))
31194 ));
31195 }
31196
31197 assert!(
31198 begin_shutdown_within(
31199 &controller,
31200 "a second phase 1 inside the zero-backend window",
31201 Duration::from_secs(2),
31202 )
31203 .expect("a second phase 1 must answer from the outstanding handoff"),
31204 "the second phase 1 must repeat the same verdict"
31205 );
31206
31207 let (finish_tx, finish_rx) = mpsc::channel();
31208 let finish_controller = Arc::clone(&controller);
31209 let finisher = std::thread::spawn(move || {
31210 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31211 });
31212 assert!(
31213 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31214 "phase 2 owns the wait phase 1 refused"
31215 );
31216
31217 release_drop_tx.send(()).unwrap();
31218 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31219 finish_rx
31220 .recv_timeout(Duration::from_secs(2))
31221 .expect("phase 2 must return once the destructor retires")
31222 .expect("phase 2 must complete the announced stop");
31223 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31224
31225 {
31226 let state = controller
31227 .state
31228 .lock()
31229 .unwrap_or_else(std::sync::PoisonError::into_inner);
31230 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31231 assert!(state.draining.is_none());
31232 }
31233 }
31234
31235 #[cfg(napi_runtime_os_threads)]
31236 #[test]
31237 fn begin_shutdown_does_not_wait_for_rejected_destruction_on_an_initial_runtime() {
31238 use std::sync::mpsc;
31244
31245 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-initial"));
31246 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, true);
31247
31248 assert!(
31249 begin_shutdown_within(
31250 &controller,
31251 "phase 1 on an initial runtime with a rejected destructor outstanding",
31252 Duration::from_secs(2),
31253 )
31254 .expect("phase 1 on an initial runtime must be accepted"),
31255 "the outstanding rejected destruction is a wait phase 2 still owes"
31256 );
31257 assert!(controller.runtime_work_pending());
31258 {
31259 let state = controller
31260 .state
31261 .lock()
31262 .unwrap_or_else(std::sync::PoisonError::into_inner);
31263 assert!(matches!(
31264 &state.lifecycle,
31265 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
31266 ));
31267 assert!(matches!(
31268 &state.draining,
31269 Some(ShutdownDrain::ZeroBackend(
31270 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
31271 ))
31272 ));
31273 }
31274 assert!(
31275 controller.try_spawn_detached(async {}).is_err(),
31276 "the announced stop must keep first-generation admission closed"
31277 );
31278
31279 let (finish_tx, finish_rx) = mpsc::channel();
31280 let finish_controller = Arc::clone(&controller);
31281 let finisher = std::thread::spawn(move || {
31282 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31283 });
31284 assert!(
31285 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31286 "phase 2 owns the wait phase 1 refused"
31287 );
31288
31289 release_drop_tx.send(()).unwrap();
31290 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31291 finish_rx
31292 .recv_timeout(Duration::from_secs(2))
31293 .expect("phase 2 must return once the destructor retires")
31294 .expect("phase 2 must complete the announced stop");
31295 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31296
31297 {
31298 let state = controller
31299 .state
31300 .lock()
31301 .unwrap_or_else(std::sync::PoisonError::into_inner);
31302 assert!(
31303 matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse),
31304 "a generation that never had a backend must stop before first use"
31305 );
31306 assert!(state.draining.is_none());
31307 }
31308 }
31309
31310 #[cfg(napi_runtime_os_threads)]
31311 #[test]
31312 fn start_waits_out_a_zero_backend_phase_one() {
31313 use std::sync::mpsc;
31321
31322 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-start"));
31323 let old_generation = controller.backend().generation();
31324 controller
31325 .shutdown()
31326 .expect("the first shutdown must complete");
31327
31328 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31329 assert!(
31330 begin_shutdown_within(
31331 &controller,
31332 "phase 1 before the restart",
31333 Duration::from_secs(2),
31334 )
31335 .expect("phase 1 on a stopped runtime must be accepted")
31336 );
31337
31338 let (start_tx, start_rx) = mpsc::channel();
31339 let start_controller = Arc::clone(&controller);
31340 let starter = std::thread::spawn(move || {
31341 start_tx.send(start_controller.start()).unwrap();
31342 });
31343 assert!(
31344 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31345 "a restart must wait for the announced zero-backend stop to be published"
31346 );
31347
31348 let (finish_tx, finish_rx) = mpsc::channel();
31349 let finish_controller = Arc::clone(&controller);
31350 let finisher = std::thread::spawn(move || {
31351 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31352 });
31353 assert!(
31354 finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31355 "phase 2 must still be waiting on the destructor"
31356 );
31357
31358 release_drop_tx.send(()).unwrap();
31359 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31360 finish_rx
31361 .recv_timeout(Duration::from_secs(2))
31362 .expect("phase 2 must return once the destructor retires")
31363 .expect("phase 2 must complete the announced stop");
31364 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31365 start_rx
31366 .recv_timeout(Duration::from_secs(2))
31367 .expect("the restart must be admitted once phase 2 has published")
31368 .expect("a stopped runtime must restart after phase 2");
31369 join_within("the restart thread", starter, Duration::from_secs(2));
31370
31371 let new_generation = controller.backend().generation();
31372 assert_ne!(
31373 new_generation, old_generation,
31374 "the restart must publish a fresh generation"
31375 );
31376 controller
31377 .shutdown()
31378 .expect("the restarted generation must still shut down");
31379 }
31380
31381 #[cfg(napi_runtime_os_threads)]
31382 #[test]
31383 fn zero_backend_phase_one_settles_through_the_poll_before_finish() {
31384 use std::sync::mpsc;
31394
31395 let controller = Arc::new(current_thread_controller("two-phase-zero-backend-poll"));
31396 drop(controller.backend());
31397 controller
31398 .shutdown()
31399 .expect("the first shutdown must complete");
31400
31401 let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31402 assert!(
31403 begin_shutdown_within(
31404 &controller,
31405 "phase 1 before the loader's poll loop",
31406 Duration::from_secs(2),
31407 )
31408 .expect("phase 1 on a stopped runtime must be accepted"),
31409 "the outstanding rejected destruction is a wait phase 2 still owes"
31410 );
31411 assert!(
31412 controller.runtime_work_pending(),
31413 "the loader gates its poll loop on this, so it must report the destruction first"
31414 );
31415
31416 release_drop_tx.send(()).unwrap();
31417 join_within("the rejected submitter", submitter, Duration::from_secs(2));
31418
31419 let deadline = std::time::Instant::now() + Duration::from_secs(5);
31422 while controller.runtime_work_pending() {
31423 assert!(
31424 std::time::Instant::now() < deadline,
31425 "the poll must stop reporting work once the rejected destructor retires"
31426 );
31427 std::thread::sleep(Duration::from_millis(1));
31428 }
31429
31430 let (finish_tx, finish_rx) = mpsc::channel();
31431 let finish_controller = Arc::clone(&controller);
31432 let finisher = std::thread::spawn(move || {
31433 finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31434 });
31435 finish_rx
31436 .recv_timeout(Duration::from_millis(200))
31437 .expect("phase 2 entered after the poll cleared must not block")
31438 .expect("phase 2 must complete the announced stop");
31439 join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31440
31441 {
31442 let state = controller
31443 .state
31444 .lock()
31445 .unwrap_or_else(std::sync::PoisonError::into_inner);
31446 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31447 assert!(state.draining.is_none());
31448 }
31449 }
31450
31451 #[cfg(napi_runtime_os_threads)]
31458 #[test]
31459 fn multi_thread_blocking_active_never_exceeds_max_blocking() {
31460 use std::sync::{Barrier, mpsc};
31482 use std::time::Duration;
31483
31484 let (done_tx, done_rx) = mpsc::channel();
31485 let peak_out = Arc::new(Mutex::new(None));
31486 let peak_slot = Arc::clone(&peak_out);
31487 let runner = std::thread::spawn(move || {
31488 let metrics = Arc::new(RuntimeMetrics::default());
31489 let options = RuntimeOptions {
31490 flavor: RuntimeFlavor::MultiThread,
31491 worker_threads: 4,
31492 max_blocking_tasks: 2,
31493 thread_name_prefix: "rd9-blk-cap".to_string(),
31494 park_deadline: None,
31495 drain_linger: DEFAULT_DRAIN_LINGER,
31496 };
31497 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
31498
31499 let holder_count = options.max_blocking_tasks; let extra_count = 4usize; let (held_tx, held_rx) = mpsc::channel();
31504 let release = Arc::new(Barrier::new(holder_count + 1));
31505 let mut holder_handles = Vec::new();
31506 for _ in 0..holder_count {
31507 let held_tx = held_tx.clone();
31508 let release = Arc::clone(&release);
31509 holder_handles.push(executor.schedule_blocking(move || {
31510 held_tx.send(()).unwrap();
31511 release.wait();
31512 0usize
31513 }));
31514 }
31515 for _ in 0..holder_count {
31517 held_rx.recv().unwrap();
31518 }
31519
31520 let (extra_started_tx, extra_started_rx) = mpsc::channel();
31522 let mut extra_handles = Vec::new();
31523 for i in 0..extra_count {
31524 let extra_started_tx = extra_started_tx.clone();
31525 extra_handles.push(executor.schedule_blocking(move || {
31526 extra_started_tx.send(i).unwrap();
31527 i * 2
31528 }));
31529 }
31530
31531 assert!(
31534 matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
31535 "a beyond-cap blocking job ran before the cap was released (cap check broken)"
31536 );
31537
31538 release.wait();
31540
31541 for handle in holder_handles {
31542 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
31543 }
31544 for (i, handle) in extra_handles.into_iter().enumerate() {
31547 assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
31548 }
31549
31550 assert_eq!(
31553 metrics.blocking_tasks_started.load(Ordering::Relaxed),
31554 (holder_count + extra_count) as u64
31555 );
31556
31557 *peak_slot
31558 .lock()
31559 .unwrap_or_else(std::sync::PoisonError::into_inner) =
31560 Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
31561 let _ = done_tx.send(());
31562 });
31563
31564 match done_rx.recv_timeout(Duration::from_secs(10)) {
31565 Ok(()) => runner.join().unwrap(),
31566 Err(error) => panic!("blocking-cap test did not complete ({error})"),
31567 }
31568 let peak = peak_out
31569 .lock()
31570 .unwrap_or_else(std::sync::PoisonError::into_inner)
31571 .expect("peak captured");
31572 assert!(
31573 peak >= 1,
31574 "at least one blocking job must have run (peak {peak})"
31575 );
31576 assert!(
31577 peak <= 2,
31578 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
31579 );
31580 }
31581
31582 #[cfg(napi_runtime_os_threads)]
31583 #[test]
31584 fn multi_thread_drain_budget_completes_large_runnable_batch() {
31585 use std::sync::mpsc;
31590 use std::time::{Duration, Instant};
31591
31592 let (done_tx, done_rx) = mpsc::channel();
31593 let runner = std::thread::spawn(move || {
31594 let metrics = Arc::new(RuntimeMetrics::default());
31595 let options = RuntimeOptions {
31596 flavor: RuntimeFlavor::MultiThread,
31597 worker_threads: 2,
31598 max_blocking_tasks: 2,
31599 thread_name_prefix: "rd9-budget".to_string(),
31600 park_deadline: None,
31601 drain_linger: DEFAULT_DRAIN_LINGER,
31602 };
31603 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
31604
31605 let task_count = 200usize; let completed = Arc::new(AtomicU64::new(0));
31607 let mut tasks = Vec::new();
31608 for _ in 0..task_count {
31609 let completed = Arc::clone(&completed);
31610 let scheduler = Arc::clone(&executor);
31611 let (runnable, task) = async_task::spawn(
31612 async move {
31613 completed.fetch_add(1, Ordering::Relaxed);
31614 },
31615 move |r| scheduler.schedule(r),
31616 );
31617 executor.schedule(runnable);
31618 tasks.push(task);
31619 }
31620 for task in tasks {
31621 futures::executor::block_on(task);
31622 }
31623
31624 assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
31627 assert_eq!(
31628 metrics.runnable_polls.load(Ordering::Relaxed),
31629 task_count as u64
31630 );
31631
31632 let deadline = Instant::now() + Duration::from_secs(5);
31636 loop {
31637 let active = metrics.active_runnables.load(Ordering::Relaxed);
31638 let queued = metrics.queued_runnables.load(Ordering::Relaxed);
31639 if active == 0 && queued == 0 {
31640 break;
31641 }
31642 assert!(
31643 Instant::now() < deadline,
31644 "runnable counters did not settle to 0: active={active} queued={queued}"
31645 );
31646 std::thread::yield_now();
31647 }
31648 let _ = done_tx.send(());
31649 });
31650
31651 match done_rx.recv_timeout(Duration::from_secs(15)) {
31652 Ok(()) => runner.join().unwrap(),
31653 Err(error) => panic!("drain-budget test did not complete ({error})"),
31654 }
31655 }
31656
31657 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31658 #[test]
31659 fn multi_thread_spawned_panic_surfaces_as_join_error() {
31660 use std::sync::mpsc;
31667 use std::time::Duration;
31668
31669 let (done_tx, done_rx) = mpsc::channel();
31670 let runner = std::thread::spawn(move || {
31671 let metrics = Arc::new(RuntimeMetrics::default());
31672 let options = RuntimeOptions {
31673 flavor: RuntimeFlavor::MultiThread,
31674 worker_threads: 2,
31675 max_blocking_tasks: 2,
31676 thread_name_prefix: "rd9-panic".to_string(),
31677 park_deadline: None,
31678 drain_linger: DEFAULT_DRAIN_LINGER,
31679 };
31680 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
31681
31682 let scheduler = Arc::clone(&executor);
31683 let wrapped = async {
31684 AssertUnwindSafe(async { panic!("x") })
31685 .catch_unwind()
31686 .await
31687 .map(|()| ContainedTaskOutput::new((), u64::MAX))
31688 .map_err(panic_payload_to_join_error)
31689 };
31690 let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
31691 executor.schedule(runnable);
31692
31693 let result: Result<(), JoinError> =
31694 futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
31695 task: task.fallible(),
31696 awaiter: None,
31697 dependency: Arc::new(TaskDependency::default()),
31698 }));
31699 let _ = done_tx.send(result);
31700 });
31701
31702 let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
31703 Ok(result) => {
31704 runner.join().unwrap();
31705 result
31706 }
31707 Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
31708 };
31709 let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
31710 assert_eq!(error.to_string(), "x");
31711 }
31712
31713 #[cfg(napi_runtime_os_threads)]
31714 #[test]
31715 fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
31716 use std::sync::mpsc;
31717
31718 let metrics = Arc::new(RuntimeMetrics::default());
31719 metrics.tasks_spawned.store(1, Ordering::Relaxed);
31720 metrics.tasks_completed.store(2, Ordering::Relaxed);
31721 metrics.tasks_panicked.store(3, Ordering::Relaxed);
31722 metrics.runnable_schedules.store(4, Ordering::Relaxed);
31723 metrics.runnable_polls.store(5, Ordering::Relaxed);
31724 metrics
31725 .blocking_tasks_scheduled
31726 .store(14, Ordering::Relaxed);
31727 metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
31728 metrics
31729 .blocking_tasks_completed
31730 .store(11, Ordering::Relaxed);
31731 metrics.runnable_scheduled();
31732 let (guards_live_tx, guards_live_rx) = mpsc::channel();
31733 let (release_tx, release_rx) = mpsc::channel();
31734 let guard_metrics = Arc::clone(&metrics);
31735 let guard_thread = std::thread::spawn(move || {
31736 let runnable_guard = guard_metrics.runnable_started();
31737 let blocking_guard = guard_metrics.blocking_started(true);
31738 guards_live_tx.send(()).unwrap();
31739 release_rx.recv().unwrap();
31740 drop(runnable_guard);
31741 drop(blocking_guard);
31742 });
31743 guards_live_rx.recv().unwrap();
31744 let fingerprint_before_reset = metrics.progress_fingerprint();
31745
31746 metrics.reset();
31747
31748 assert!(
31749 metrics.progress_fingerprint() != fingerprint_before_reset,
31750 "a reset must advance the deadlock detector generation even if counters later repeat"
31751 );
31752 assert_eq!(
31753 metrics.tasks_spawned.load(Ordering::Relaxed),
31754 0,
31755 "tasks_spawned"
31756 );
31757 assert_eq!(
31758 metrics.tasks_completed.load(Ordering::Relaxed),
31759 0,
31760 "tasks_completed"
31761 );
31762 assert_eq!(
31763 metrics.tasks_panicked.load(Ordering::Relaxed),
31764 0,
31765 "tasks_panicked"
31766 );
31767 assert_eq!(
31768 metrics.runnable_schedules.load(Ordering::Relaxed),
31769 0,
31770 "runnable_schedules"
31771 );
31772 assert_eq!(
31773 metrics.runnable_polls.load(Ordering::Relaxed),
31774 0,
31775 "runnable_polls"
31776 );
31777 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
31778 assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
31779 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
31780 assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
31781 assert_eq!(
31782 metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
31783 0,
31784 "blocking_tasks_scheduled"
31785 );
31786 assert_eq!(
31787 metrics.blocking_tasks_started.load(Ordering::Relaxed),
31788 0,
31789 "blocking_tasks_started"
31790 );
31791 assert_eq!(
31792 metrics.blocking_tasks_completed.load(Ordering::Relaxed),
31793 0,
31794 "blocking_tasks_completed"
31795 );
31796 assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
31797 assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
31798
31799 release_tx.send(()).unwrap();
31800 guard_thread.join().unwrap();
31801 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
31802 assert_eq!(
31803 metrics.active_blocking_tasks.load(Ordering::Relaxed),
31804 0,
31805 "a live blocking guard completed after reset without unsigned underflow"
31806 );
31807 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
31808 }
31809
31810 #[cfg(napi_runtime_os_threads)]
31811 #[test]
31812 fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
31813 use std::sync::mpsc;
31814
31815 fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
31816 RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
31817 *slot.borrow_mut() = Some(Box::new(move || {
31818 entered.send(()).unwrap();
31819 release.recv().unwrap();
31820 }));
31821 });
31822 }
31823
31824 let options = RuntimeOptions {
31825 flavor: RuntimeFlavor::CurrentThread,
31826 worker_threads: 1,
31827 max_blocking_tasks: 1,
31828 thread_name_prefix: "metrics-snapshot".to_string(),
31829 park_deadline: None,
31830 drain_linger: DEFAULT_DRAIN_LINGER,
31831 };
31832
31833 let queued_metrics = Arc::new(RuntimeMetrics::default());
31834 let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
31835 let (queued_release_tx, queued_release_rx) = mpsc::channel();
31836 let queued_writer_metrics = Arc::clone(&queued_metrics);
31837 let queued_writer = std::thread::spawn(move || {
31838 pause_next_high_water_update(queued_entered_tx, queued_release_rx);
31839 queued_writer_metrics.runnable_scheduled();
31840 });
31841 queued_entered_rx.recv().unwrap();
31842 assert_eq!(
31843 queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
31844 0
31845 );
31846 let snapshot = queued_metrics.snapshot(&options);
31847 assert_eq!(snapshot.queued_runnables, 1);
31848 assert_eq!(snapshot.max_queued_runnables, 1);
31849 queued_release_tx.send(()).unwrap();
31850 queued_writer.join().unwrap();
31851 queued_metrics.runnable_cancelled();
31852
31853 let active_metrics = Arc::new(RuntimeMetrics::default());
31854 active_metrics.queued_runnables.store(1, Ordering::Relaxed);
31855 let (active_entered_tx, active_entered_rx) = mpsc::channel();
31856 let (active_release_tx, active_release_rx) = mpsc::channel();
31857 let active_writer_metrics = Arc::clone(&active_metrics);
31858 let active_writer = std::thread::spawn(move || {
31859 pause_next_high_water_update(active_entered_tx, active_release_rx);
31860 let _active = active_writer_metrics.runnable_started();
31861 });
31862 active_entered_rx.recv().unwrap();
31863 assert_eq!(
31864 active_metrics.max_active_runnables.load(Ordering::Relaxed),
31865 0
31866 );
31867 let snapshot = active_metrics.snapshot(&options);
31868 assert_eq!(snapshot.active_runnables, 1);
31869 assert_eq!(snapshot.max_active_runnables, 1);
31870 active_release_tx.send(()).unwrap();
31871 active_writer.join().unwrap();
31872
31873 let blocking_metrics = Arc::new(RuntimeMetrics::default());
31874 let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
31875 let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
31876 let blocking_writer_metrics = Arc::clone(&blocking_metrics);
31877 let blocking_writer = std::thread::spawn(move || {
31878 pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
31879 let _active = blocking_writer_metrics.blocking_started(true);
31880 });
31881 blocking_entered_rx.recv().unwrap();
31882 assert_eq!(
31883 blocking_metrics
31884 .max_active_blocking_tasks
31885 .load(Ordering::Relaxed),
31886 0
31887 );
31888 let snapshot = blocking_metrics.snapshot(&options);
31889 assert_eq!(snapshot.active_blocking_tasks, 1);
31890 assert_eq!(snapshot.max_active_blocking_tasks, 1);
31891 blocking_release_tx.send(()).unwrap();
31892 blocking_writer.join().unwrap();
31893 }
31894
31895 #[cfg(panic = "unwind")]
31896 #[test]
31897 fn runtime_metrics_reset_fails_before_generation_wraparound() {
31898 let metrics = RuntimeMetrics::default();
31899 metrics
31900 .reset_generation
31901 .store(u64::MAX - 1, Ordering::SeqCst);
31902 metrics.tasks_spawned.store(7, Ordering::Relaxed);
31903
31904 let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
31905 assert!(exhausted.is_err());
31906 assert!(!metrics.reset_lock.is_poisoned());
31907 assert_eq!(
31908 metrics.reset_generation.load(Ordering::SeqCst),
31909 u64::MAX - 1
31910 );
31911 assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
31912 }
31913
31914 struct NeverReady;
31926 impl Future for NeverReady {
31927 type Output = ();
31928 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31929 Poll::Pending
31930 }
31931 }
31932
31933 #[cfg(napi_runtime_os_threads)]
31934 struct ReadyAfterOwnerLanePublication {
31935 dependency: BlockingDependency,
31936 ready: Arc<AtomicBool>,
31937 }
31938
31939 #[cfg(napi_runtime_os_threads)]
31940 impl Future for ReadyAfterOwnerLanePublication {
31941 type Output = ();
31942
31943 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31944 if self.ready.load(Ordering::SeqCst) {
31945 Poll::Ready(())
31946 } else {
31947 let mut published = PublishedBlockingDependencies::default();
31950 record_blocking_dependency(&mut published, self.dependency);
31951 Poll::Pending
31952 }
31953 }
31954 }
31955
31956 #[cfg(panic = "unwind")]
31957 #[test]
31958 fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
31959 use std::sync::mpsc;
31966
31967 let (done_tx, done_rx) = mpsc::channel();
31968 let runner = std::thread::spawn(move || {
31969 let metrics = Arc::new(RuntimeMetrics::default());
31970 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31973
31974 let payload = catch_unwind(AssertUnwindSafe(|| {
31975 let mut future = std::pin::pin!(NeverReady);
31976 executor.block_on(future.as_mut());
31977 }))
31978 .expect_err("a threadless park with an empty queue and no pending wake must panic");
31979 let diagnostic = payload
31980 .downcast_ref::<BlockOnDeadlock>()
31981 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31982 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
31983 assert_eq!(
31984 diagnostic.park_deadline, None,
31985 "the certain case carries no deadline"
31986 );
31987 let message = diagnostic.to_string();
31988 assert!(
31989 message.contains("block_on") && message.contains("JS"),
31990 "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
31991 );
31992 let _ = done_tx.send(());
31993 });
31994
31995 match done_rx.recv_timeout(Duration::from_secs(10)) {
31996 Ok(()) => runner.join().unwrap(),
31997 Err(error) => panic!(
31998 "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
31999 ),
32000 }
32001 }
32002
32003 #[cfg(napi_runtime_os_threads)]
32004 #[test]
32005 fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
32006 use std::sync::mpsc;
32012
32013 struct PendThenReady {
32014 polls: usize,
32015 }
32016 impl Future for PendThenReady {
32017 type Output = ();
32018 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
32019 self.polls += 1;
32020 if self.polls >= 2 {
32021 Poll::Ready(())
32022 } else {
32023 cx.waker().wake_by_ref();
32024 Poll::Pending
32025 }
32026 }
32027 }
32028
32029 let (done_tx, done_rx) = mpsc::channel();
32030 let runner = std::thread::spawn(move || {
32031 let metrics = Arc::new(RuntimeMetrics::default());
32032 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32033 let mut future = std::pin::pin!(PendThenReady { polls: 0 });
32034 executor.block_on(future.as_mut());
32035 assert_eq!(
32036 future.polls, 2,
32037 "the self-woken future must have been re-polled to completion"
32038 );
32039 let _ = done_tx.send(());
32040 });
32041
32042 match done_rx.recv_timeout(Duration::from_secs(10)) {
32043 Ok(()) => runner.join().unwrap(),
32044 Err(error) => panic!(
32045 "a self-waking Pending future must complete without panicking on a threadless build ({error})"
32046 ),
32047 }
32048 }
32049
32050 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32051 #[test]
32052 fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
32053 use std::sync::mpsc;
32058
32059 let (done_tx, done_rx) = mpsc::channel();
32060 let runner = std::thread::spawn(move || {
32061 let metrics = Arc::new(RuntimeMetrics::default());
32062 let executor = Arc::new(CurrentThreadExecutor::with_detection(
32063 metrics,
32064 false,
32065 Some(Duration::from_millis(50)),
32066 ));
32067
32068 let payload = catch_unwind(AssertUnwindSafe(|| {
32069 let mut future = std::pin::pin!(NeverReady);
32070 executor.block_on(future.as_mut());
32071 }))
32072 .expect_err("a dead native CT park must fire the armed deadline");
32073 let diagnostic = payload
32074 .downcast_ref::<BlockOnDeadlock>()
32075 .expect("typed payload");
32076 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
32077 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32078 drop(executor);
32081 let _ = done_tx.send(());
32082 });
32083
32084 match done_rx.recv_timeout(Duration::from_secs(10)) {
32085 Ok(()) => join_within(
32086 "the CT deadline-fire runner exits after teardown",
32087 runner,
32088 Duration::from_secs(30),
32089 ),
32090 Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
32091 }
32092 }
32093
32094 #[cfg(napi_runtime_os_threads)]
32095 #[test]
32096 fn current_thread_native_park_deadline_resets_on_runtime_progress() {
32097 use std::sync::mpsc;
32114
32115 let deadline = Duration::from_millis(1000);
32116 let metrics = Arc::new(RuntimeMetrics::default());
32117 let executor = Arc::new(CurrentThreadExecutor::with_detection(
32118 Arc::clone(&metrics),
32119 false,
32120 Some(deadline),
32121 ));
32122
32123 let (value_tx, value_rx) = oneshot::channel::<usize>();
32124 let (done_tx, done_rx) = mpsc::channel();
32125 let runner = std::thread::spawn(move || {
32126 let mut output = None;
32127 {
32128 let mut future = std::pin::pin!(async {
32129 output = Some(value_rx.await.unwrap());
32130 });
32131 executor.block_on(future.as_mut());
32132 }
32133 assert_eq!(output, Some(9));
32134 drop(executor);
32136 let _ = done_tx.send(());
32137 });
32138
32139 let hold_until = Instant::now() + deadline * 5 / 2;
32145 while Instant::now() < hold_until {
32146 std::thread::sleep(Duration::from_millis(25));
32147 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32148 }
32149 value_tx.send(9).unwrap();
32150
32151 match done_rx.recv_timeout(Duration::from_secs(30)) {
32152 Ok(()) => join_within(
32153 "the CT deadline-reset runner exits after teardown",
32154 runner,
32155 Duration::from_secs(30),
32156 ),
32157 Err(error) => panic!(
32158 "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
32159 ),
32160 }
32161 }
32162
32163 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32164 #[test]
32165 fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
32166 use std::sync::mpsc;
32171
32172 let (done_tx, done_rx) = mpsc::channel();
32173 let runner = std::thread::spawn(move || {
32174 let options = RuntimeOptions {
32175 flavor: RuntimeFlavor::MultiThread,
32176 worker_threads: 1,
32177 max_blocking_tasks: 1,
32178 thread_name_prefix: "deadline-fire".to_string(),
32179 park_deadline: Some(Duration::from_millis(50)),
32180 drain_linger: DEFAULT_DRAIN_LINGER,
32181 };
32182 let executor =
32183 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32184
32185 let payload = {
32186 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32187 catch_unwind(AssertUnwindSafe(|| {
32188 let mut future = std::pin::pin!(NeverReady);
32189 executor.block_on(future.as_mut());
32190 }))
32191 .expect_err("a dead cooperative park must fire the armed deadline")
32192 };
32193 let diagnostic = payload
32194 .downcast_ref::<BlockOnDeadlock>()
32195 .expect("typed payload");
32196 assert_eq!(
32197 diagnostic.kind,
32198 BlockOnDeadlockKind::MultiThreadCooperativeDeadline
32199 );
32200 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32201 let message = diagnostic.to_string();
32202 assert!(
32203 message.contains("block_on"),
32204 "diagnostic must name the hazard, got: {message}"
32205 );
32206 assert_eq!(
32207 executor.parked_drivers.count.load(Ordering::SeqCst),
32208 0,
32209 "the firing driver must deregister itself before panicking"
32210 );
32211 drop(executor);
32213 let _ = done_tx.send(());
32214 });
32215
32216 match done_rx.recv_timeout(Duration::from_secs(10)) {
32217 Ok(()) => join_within(
32218 "the MT deadline-fire runner exits after executor teardown",
32219 runner,
32220 Duration::from_secs(30),
32221 ),
32222 Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
32223 }
32224 }
32225
32226 #[cfg(napi_runtime_os_threads)]
32227 #[test]
32228 fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
32229 use std::sync::mpsc;
32245
32246 let (done_tx, done_rx) = mpsc::channel();
32247 let runner = std::thread::spawn(move || {
32248 let deadline = Duration::from_millis(1000);
32249 let metrics = Arc::new(RuntimeMetrics::default());
32250 let options = RuntimeOptions {
32251 flavor: RuntimeFlavor::MultiThread,
32252 worker_threads: 1,
32253 max_blocking_tasks: 1,
32254 thread_name_prefix: "deadline-reset".to_string(),
32255 park_deadline: Some(deadline),
32256 drain_linger: DEFAULT_DRAIN_LINGER,
32257 };
32258 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32259
32260 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32263 let td_exec = Arc::clone(&executor);
32264 let completed = Arc::new(AtomicBool::new(false));
32265 let td_flag = Arc::clone(&completed);
32266 let td_body = async move {
32267 let mut inner = std::pin::pin!(async move {
32268 assert_eq!(gate_rx.await.unwrap(), 9);
32269 });
32270 td_exec.block_on(inner.as_mut());
32271 td_flag.store(true, Ordering::SeqCst);
32272 };
32273 let sched = Arc::clone(&executor);
32274 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
32275 executor.schedule(td_runnable);
32276 wait_until("the sole driver parked", || {
32277 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
32278 });
32279
32280 let hold_until = Instant::now() + deadline * 5 / 2;
32286 while Instant::now() < hold_until {
32287 std::thread::sleep(Duration::from_millis(25));
32288 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32289 }
32290 assert!(
32291 !completed.load(Ordering::SeqCst),
32292 "the driver must still be parked awaiting its late wake"
32293 );
32294 gate_tx.send(9).unwrap();
32295 futures::executor::block_on(td_task);
32296 assert!(
32297 completed.load(Ordering::SeqCst),
32298 "the late wake must complete the parked driver"
32299 );
32300 drop(executor);
32302 let _ = done_tx.send(());
32303 });
32304
32305 match done_rx.recv_timeout(Duration::from_secs(30)) {
32306 Ok(()) => join_within(
32307 "the MT deadline-reset runner exits after executor teardown",
32308 runner,
32309 Duration::from_secs(30),
32310 ),
32311 Err(error) => panic!(
32312 "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
32313 ),
32314 }
32315 }
32316
32317 #[cfg(napi_runtime_os_threads)]
32318 #[test]
32319 fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
32320 use std::sync::mpsc;
32325
32326 let (done_tx, done_rx) = mpsc::channel();
32327 let runner = std::thread::spawn(move || {
32328 let options = RuntimeOptions {
32329 flavor: RuntimeFlavor::MultiThread,
32330 worker_threads: 2,
32331 max_blocking_tasks: 2,
32332 thread_name_prefix: "foreign-exempt".to_string(),
32333 park_deadline: Some(Duration::from_millis(40)),
32334 drain_linger: DEFAULT_DRAIN_LINGER,
32335 };
32336 let executor =
32337 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32338
32339 let (value_tx, value_rx) = oneshot::channel::<usize>();
32340 let waker_thread = std::thread::spawn(move || {
32341 std::thread::sleep(Duration::from_millis(300));
32343 let _ = value_tx.send(7);
32344 });
32345
32346 let started = Instant::now();
32347 let mut output = None;
32348 {
32349 let mut future = std::pin::pin!(async {
32350 output = Some(value_rx.await.unwrap());
32351 });
32352 executor.block_on(future.as_mut());
32354 }
32355 assert_eq!(output, Some(7));
32356 assert!(
32357 started.elapsed() >= Duration::from_millis(250),
32358 "the park must genuinely have outlived the 40ms deadline many times over"
32359 );
32360 waker_thread.join().unwrap();
32361 drop(executor);
32366 let _ = done_tx.send(());
32367 });
32368
32369 match done_rx.recv_timeout(Duration::from_secs(10)) {
32370 Ok(()) => join_within(
32371 "the exempt foreign-park runner exits after executor teardown",
32372 runner,
32373 Duration::from_secs(30),
32374 ),
32375 Err(error) => {
32376 panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
32377 }
32378 }
32379 }
32380
32381 #[cfg(napi_runtime_os_threads)]
32382 #[test]
32383 fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
32384 use std::sync::mpsc;
32399
32400 let (done_tx, done_rx) = mpsc::channel();
32401 let runner = std::thread::spawn(move || {
32402 let options = RuntimeOptions {
32403 flavor: RuntimeFlavor::MultiThread,
32404 worker_threads: 1,
32405 max_blocking_tasks: 1,
32406 thread_name_prefix: "edge-wake".to_string(),
32407 park_deadline: Some(Duration::from_millis(40)),
32408 drain_linger: DEFAULT_DRAIN_LINGER,
32409 };
32410 let executor =
32411 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32412
32413 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32415 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
32417
32418 let hook_exec = Arc::clone(&executor);
32419 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
32420 *slot.borrow_mut() = Some(Box::new(move || {
32421 let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32422 let handle = hook_exec.schedule_blocking(move || {
32426 let _ = gate_tx.send(5usize);
32427 0usize
32428 });
32429 drop(handle); let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32431 hook_saw_tx.send((before, after)).unwrap();
32432 }));
32433 });
32434
32435 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32438 let mut output = None;
32439 {
32440 let mut future = std::pin::pin!(async {
32441 output = Some(gate_rx.await.unwrap());
32442 });
32443 executor.block_on(future.as_mut());
32444 }
32445 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
32450 assert_eq!(
32451 output,
32452 Some(5),
32453 "the edge-delivered wake's job must have run and completed the future"
32454 );
32455
32456 let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
32457 assert_eq!(
32458 before, 1,
32459 "the hook must observe the parker STILL registered (race window)"
32460 );
32461 assert_eq!(
32462 after, 0,
32463 "the racing wake_one must have popped the parker (wake DELIVERED)"
32464 );
32465 drop(executor);
32467 let _ = done_tx.send(());
32468 });
32469
32470 match done_rx.recv_timeout(Duration::from_secs(10)) {
32471 Ok(()) => join_within(
32472 "the expiry-edge-wake runner exits after executor teardown",
32473 runner,
32474 Duration::from_secs(30),
32475 ),
32476 Err(error) => panic!(
32477 "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
32478 ),
32479 }
32480 }
32481
32482 #[cfg(napi_runtime_os_threads)]
32483 #[test]
32484 fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
32485 use std::sync::mpsc;
32497
32498 let (done_tx, done_rx) = mpsc::channel();
32499 let runner = std::thread::spawn(move || {
32500 let options = RuntimeOptions {
32501 flavor: RuntimeFlavor::MultiThread,
32502 worker_threads: 1,
32503 max_blocking_tasks: 1,
32504 thread_name_prefix: "verdict-blocking".to_string(),
32505 park_deadline: Some(Duration::from_millis(40)),
32506 drain_linger: DEFAULT_DRAIN_LINGER,
32507 };
32508 let executor =
32509 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32510 executor
32511 .active_drainers
32512 .store(executor.max_drainers, Ordering::SeqCst);
32513
32514 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32516 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
32519
32520 let hook_exec = Arc::clone(&executor);
32521 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
32522 *slot.borrow_mut() = Some(Box::new(move || {
32523 let handle = hook_exec.schedule_blocking(move || {
32524 let _ = gate_tx.send(11usize);
32525 0usize
32526 });
32527 drop(handle); hook_saw_tx
32529 .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
32530 .unwrap();
32531 }));
32532 });
32533
32534 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32537 let mut output = None;
32538 {
32539 let mut future = std::pin::pin!(async {
32540 output = Some(gate_rx.await.unwrap());
32541 });
32542 executor.block_on(future.as_mut());
32543 }
32544 assert_eq!(
32545 output,
32546 Some(11),
32547 "the enqueued job must have been run by the surviving driver via run_one"
32548 );
32549
32550 let registered_at_hook = hook_saw_rx
32551 .recv()
32552 .expect("the verdict hook must have fired");
32553 assert_eq!(
32554 registered_at_hook, 0,
32555 "the hook fires post-deregister: no permit can be delivered, only the fingerprint's enqueue counter can catch this submission"
32556 );
32557 executor.active_drainers.store(0, Ordering::SeqCst);
32558 let _ = done_tx.send(());
32559 });
32560
32561 match done_rx.recv_timeout(Duration::from_secs(10)) {
32562 Ok(()) => runner.join().unwrap(),
32563 Err(error) => panic!(
32564 "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
32565 ),
32566 }
32567 }
32568
32569 #[cfg(napi_runtime_os_threads)]
32570 #[test]
32571 fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
32572 use std::sync::mpsc;
32578
32579 struct ReadyAfterPublication(Arc<AtomicBool>);
32580
32581 impl Future for ReadyAfterPublication {
32582 type Output = ();
32583
32584 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32585 if self.0.load(Ordering::SeqCst) {
32586 Poll::Ready(())
32587 } else {
32588 Poll::Pending
32589 }
32590 }
32591 }
32592
32593 let (done_tx, done_rx) = mpsc::channel();
32594 let runner = std::thread::spawn(move || {
32595 let options = RuntimeOptions {
32596 flavor: RuntimeFlavor::MultiThread,
32597 worker_threads: 1,
32598 max_blocking_tasks: 1,
32599 thread_name_prefix: "final-verdict-publication".to_string(),
32600 park_deadline: Some(Duration::from_millis(40)),
32601 drain_linger: DEFAULT_DRAIN_LINGER,
32602 };
32603 let executor =
32604 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32605
32606 let ready = Arc::new(AtomicBool::new(false));
32607 let baseline_fingerprint = executor.metrics.progress_fingerprint();
32608 let baseline_publications = executor.deadlock_state.publications();
32609 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
32610 let hook_exec = Arc::clone(&executor);
32611 let hook_ready = Arc::clone(&ready);
32612 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
32613 *slot.borrow_mut() = Some(Box::new(move || {
32614 let publication_executor = Arc::clone(&hook_exec);
32615 std::thread::spawn(move || {
32616 let mut admission = publication_executor
32617 .begin_deadlock_admission()
32618 .expect("the deadline detector must enable admission accounting");
32619 hook_ready.store(true, Ordering::SeqCst);
32620 admission.mark_published();
32621 })
32622 .join()
32623 .unwrap();
32624 hook_saw_tx
32625 .send((
32626 hook_exec.deadlock_state.publications(),
32627 !hook_exec.has_queued_runnable(),
32628 hook_exec
32629 .blocking_queue
32630 .lock()
32631 .unwrap_or_else(std::sync::PoisonError::into_inner)
32632 .is_empty(),
32633 hook_exec.next_timer_deadline().is_none(),
32634 hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
32635 hook_exec.parked_drivers.count.load(Ordering::SeqCst),
32636 ))
32637 .unwrap();
32638 }));
32639 });
32640
32641 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32642 let mut future = std::pin::pin!(ReadyAfterPublication(ready));
32643 assert_eq!(
32644 executor.block_on(future.as_mut()),
32645 BlockOnOutcome::Completed,
32646 "the publication epoch must veto the first verdict and permit the next poll"
32647 );
32648 let (
32649 publications,
32650 runnable_queue_empty,
32651 blocking_queue_empty,
32652 timer_heap_empty,
32653 fingerprint_unchanged,
32654 parked_drivers,
32655 ) = hook_saw_rx
32656 .recv()
32657 .expect("the final-verdict hook must have fired");
32658 assert_eq!(
32659 publications,
32660 baseline_publications
32661 .checked_add(1)
32662 .expect("test publication epoch must not overflow"),
32663 "the cross-thread admission must publish exactly one epoch"
32664 );
32665 assert!(
32666 runnable_queue_empty,
32667 "the publication test must not queue a runnable"
32668 );
32669 assert!(
32670 blocking_queue_empty,
32671 "the publication test must not queue blocking work"
32672 );
32673 assert!(
32674 timer_heap_empty,
32675 "the publication test must not register a timer"
32676 );
32677 assert!(
32678 fingerprint_unchanged,
32679 "ordinary metrics progress must not veto the publication-only verdict"
32680 );
32681 assert_eq!(
32682 parked_drivers, 0,
32683 "the publication must land after the cooperative parker was deregistered"
32684 );
32685 let _ = done_tx.send(());
32686 });
32687
32688 match done_rx.recv_timeout(Duration::from_secs(10)) {
32689 Ok(()) => runner.join().unwrap(),
32690 Err(error) => panic!(
32691 "a publication-only cross-thread admission was not observed by the final verdict ({error})"
32692 ),
32693 }
32694 }
32695
32696 #[cfg(napi_runtime_os_threads)]
32697 #[test]
32698 fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
32699 use std::sync::mpsc;
32707
32708 let executor = multi_thread_executor(
32709 1,
32710 Some(Duration::from_millis(40)),
32711 "deadline-selected-handoff",
32712 );
32713 let owner = executor.fresh_owner_token();
32714 executor.active_blocking_owners.register(owner);
32715 executor
32716 .active_blocking
32717 .store(executor.max_blocking, Ordering::SeqCst);
32718
32719 let ready = Arc::new(AtomicBool::new(false));
32720 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
32721 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
32722 let (selected_tx, selected_rx) = mpsc::channel();
32723 let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
32724 let (released_tx, released_rx) = mpsc::channel();
32725 let (renewed_tx, renewed_rx) = mpsc::channel();
32726 let release_executor = Arc::clone(&executor);
32727 let renewal_executor = Arc::clone(&executor);
32728 let release_ready = Arc::clone(&ready);
32729 let releaser = std::thread::spawn(move || {
32730 let reservation =
32731 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
32732 .expect("the synthetic exact-owner lane must start reserved");
32733 reservation_ready_tx.send(()).unwrap();
32734 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
32735 *slot.borrow_mut() = Some(Box::new(move || {
32736 selected_tx.send(()).unwrap();
32737 allow_unpark_rx.recv().unwrap();
32738 let renewed = renewal_executor
32739 .active_blocking_owners
32740 .reserve(owner)
32741 .expect("the released exact-owner lane must be reservable before unpark");
32742 release_ready.store(true, Ordering::SeqCst);
32743 renewed_tx.send(renewed).unwrap();
32744 }));
32745 });
32746 release_reservation_rx.recv().unwrap();
32747 released_tx
32748 .send(release_executor.release_owner_lane_and_wake(reservation))
32749 .unwrap();
32750 });
32751 reservation_ready_rx
32752 .recv_timeout(Duration::from_secs(2))
32753 .expect("the exact-owner reservation must be established before the park");
32754
32755 let baseline_fingerprint = executor.metrics.progress_fingerprint();
32756 let baseline_publications = executor.deadlock_state.publications();
32757 let expiry_executor = Arc::clone(&executor);
32758 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
32759 *slot.borrow_mut() = Some(Box::new(move || {
32760 release_reservation_tx.send(()).unwrap();
32761 selected_rx
32762 .recv_timeout(Duration::from_secs(2))
32763 .expect("reservation release must select the still-registered exact-owner driver");
32764 assert_eq!(
32765 expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
32766 0,
32767 "handoff selection must remove the driver before pausing ahead of unpark"
32768 );
32769 }));
32770 });
32771
32772 let verdict_executor = Arc::clone(&executor);
32773 let verdict_ready = Arc::clone(&ready);
32774 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
32775 *slot.borrow_mut() = Some(Box::new(move || {
32776 assert!(
32777 verdict_executor.active_blocking_owners.is_available(owner),
32778 "reservation release must publish lane availability before handoff selection"
32779 );
32780 assert!(!verdict_executor.has_queued_runnable());
32781 assert!(
32782 verdict_executor
32783 .blocking_queue
32784 .lock()
32785 .unwrap_or_else(std::sync::PoisonError::into_inner)
32786 .is_empty()
32787 );
32788 assert!(verdict_executor.next_timer_deadline().is_none());
32789 assert!(
32790 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
32791 "ordinary metrics progress must not save the selected-handoff verdict"
32792 );
32793 assert_eq!(
32794 verdict_executor.deadlock_state.publications(),
32795 baseline_publications
32796 );
32797 assert!(
32798 !verdict_ready.load(Ordering::SeqCst),
32799 "the selected handoff must still be paused before unpark at the gated verdict"
32800 );
32801 allow_unpark_tx.send(()).unwrap();
32802 }));
32803 });
32804 let exact_owner_executor = Arc::clone(&executor);
32805 DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
32806 *slot.borrow_mut() = Some(Box::new(move |parker| {
32807 assert!(
32808 parker.consume_permit(),
32809 "the selected handoff must unpark before exact-owner final predicates run"
32810 );
32811 assert!(
32812 parker.has_owner_handoff(),
32813 "consuming the ordinary permit must leave the exact-owner handoff pending"
32814 );
32815 assert!(
32816 !exact_owner_executor
32817 .active_blocking_owners
32818 .is_available(owner),
32819 "the renewed reservation must prevent lane availability from saving the verdict"
32820 );
32821 }));
32822 });
32823
32824 {
32825 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32826 let _owner = BlockingOwnerGuard::enter(Some(owner));
32827 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
32828 dependency: BlockingDependency {
32829 job: BlockingJobId(u64::MAX - 80),
32830 owner: Some(owner)
32831 },
32832 ready: Arc::clone(&ready),
32833 });
32834 assert_eq!(
32835 executor.block_on(future.as_mut()),
32836 BlockOnOutcome::Completed
32837 );
32838 }
32839 assert!(
32840 released_rx
32841 .recv_timeout(Duration::from_secs(2))
32842 .expect("the selected handoff publication must finish"),
32843 "reservation release must have selected the cooperative driver"
32844 );
32845 let renewed = renewed_rx
32846 .recv_timeout(Duration::from_secs(2))
32847 .expect("the selected handoff must renew the owner reservation before unpark");
32848 releaser.join().unwrap();
32849
32850 executor.active_blocking_owners.release(owner, renewed);
32851 executor.active_blocking.store(0, Ordering::SeqCst);
32852 executor.active_blocking_owners.unregister(owner);
32853 executor.begin_shutdown();
32854 executor.wait_until_scheduler_idle();
32855 }
32856
32857 #[cfg(napi_runtime_os_threads)]
32858 #[test]
32859 fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
32860 use std::sync::mpsc;
32865
32866 let executor = multi_thread_executor(
32867 1,
32868 Some(Duration::from_millis(40)),
32869 "deadline-owner-no-parker",
32870 );
32871 let owner = executor.fresh_owner_token();
32872 executor.active_blocking_owners.register(owner);
32873 executor
32874 .active_blocking
32875 .store(executor.max_blocking, Ordering::SeqCst);
32876
32877 let ready = Arc::new(AtomicBool::new(false));
32878 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
32879 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
32880 let (released_tx, released_rx) = mpsc::channel();
32881 let release_executor = Arc::clone(&executor);
32882 let release_ready = Arc::clone(&ready);
32883 let releaser = std::thread::spawn(move || {
32884 let reservation =
32885 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
32886 .expect("the synthetic exact-owner lane must start reserved");
32887 reservation_ready_tx.send(()).unwrap();
32888 release_reservation_rx.recv().unwrap();
32889 let woke = release_executor.release_owner_lane_and_wake(reservation);
32890 release_ready.store(true, Ordering::SeqCst);
32891 released_tx.send(woke).unwrap();
32892 });
32893 reservation_ready_rx
32894 .recv_timeout(Duration::from_secs(2))
32895 .expect("the exact-owner reservation must be established before the park");
32896
32897 let baseline_fingerprint = executor.metrics.progress_fingerprint();
32898 let baseline_publications = executor.deadlock_state.publications();
32899 let verdict_executor = Arc::clone(&executor);
32900 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
32901 *slot.borrow_mut() = Some(Box::new(move || {
32902 assert_eq!(
32903 verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
32904 0,
32905 "the release must begin only after deadline deregistration"
32906 );
32907 release_reservation_tx.send(()).unwrap();
32908 assert!(
32909 !released_rx
32910 .recv_timeout(Duration::from_secs(2))
32911 .expect("the no-parker owner release must finish"),
32912 "no registered parker may receive the released owner lane"
32913 );
32914 assert!(verdict_executor.active_blocking_owners.is_available(owner));
32915 assert!(!verdict_executor.has_queued_runnable());
32916 assert!(
32917 verdict_executor
32918 .blocking_queue
32919 .lock()
32920 .unwrap_or_else(std::sync::PoisonError::into_inner)
32921 .is_empty()
32922 );
32923 assert!(verdict_executor.next_timer_deadline().is_none());
32924 assert!(
32925 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
32926 "ordinary metrics progress must not save the no-parker release verdict"
32927 );
32928 assert_eq!(
32929 verdict_executor.deadlock_state.publications(),
32930 baseline_publications
32931 );
32932 }));
32933 });
32934
32935 {
32936 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32937 let _owner = BlockingOwnerGuard::enter(Some(owner));
32938 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
32939 dependency: BlockingDependency {
32940 job: BlockingJobId(u64::MAX - 81),
32941 owner: Some(owner)
32942 },
32943 ready: Arc::clone(&ready),
32944 });
32945 assert_eq!(
32946 executor.block_on(future.as_mut()),
32947 BlockOnOutcome::Completed
32948 );
32949 }
32950 releaser.join().unwrap();
32951
32952 executor.active_blocking.store(0, Ordering::SeqCst);
32953 executor.active_blocking_owners.unregister(owner);
32954 executor.begin_shutdown();
32955 executor.wait_until_scheduler_idle();
32956 }
32957
32958 #[cfg(napi_runtime_os_threads)]
32959 #[test]
32960 fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
32961 use std::sync::mpsc;
32975
32976 let controller = Arc::new(RuntimeController::new());
32977 controller
32978 .configure(RuntimeOptions {
32979 flavor: RuntimeFlavor::MultiThread,
32980 worker_threads: 2,
32981 max_blocking_tasks: 1,
32982 thread_name_prefix: "shutdown-gated-verdict".to_string(),
32983 park_deadline: Some(Duration::from_millis(40)),
32984 drain_linger: DEFAULT_DRAIN_LINGER,
32985 })
32986 .expect("the shutdown race test configuration must be accepted");
32987
32988 let backend = controller.backend();
32989 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
32990 panic!("the configured backend must be MultiThread");
32991 };
32992 let executor_id = executor.id;
32993 let weak_executor = Arc::downgrade(executor);
32994 drop(backend);
32995
32996 let (task_polled_tx, task_polled_rx) = mpsc::channel();
32997 let task = controller
32998 .try_spawn(async move {
32999 task_polled_tx.send(()).unwrap();
33000 std::future::pending::<()>().await;
33001 })
33002 .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
33003 task_polled_rx
33004 .recv_timeout(Duration::from_secs(2))
33005 .expect("the accepted task must publish an abortable pending waker");
33006
33007 let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
33008 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33009 let (driver_result_tx, driver_result_rx) = mpsc::channel();
33010 let driver_controller = Arc::clone(&controller);
33011 let driver = std::thread::spawn(move || {
33012 DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
33013 *slot.borrow_mut() = Some(Box::new(move || {
33014 gate_closed_tx.send(()).unwrap();
33015 release_verdict_rx.recv().unwrap();
33016 }));
33017 });
33018
33019 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33020 let mut pending = std::pin::pin!(NeverReady);
33021 let result = catch_unwind(AssertUnwindSafe(|| {
33022 driver_controller.block_on(pending.as_mut())
33023 }));
33024 let result = match result {
33025 Ok(outcome) => Ok(outcome),
33026 Err(payload) => {
33027 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33028 discard_panic_payload(payload);
33029 Err(was_deadlock)
33030 }
33031 };
33032 driver_result_tx.send(result).unwrap();
33033 });
33034
33035 gate_closed_rx
33036 .recv_timeout(Duration::from_secs(2))
33037 .expect("the cooperative driver must pause with the verdict gate closed");
33038
33039 let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
33040 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33041 let shutdown_controller = Arc::clone(&controller);
33042 let shutdown = std::thread::spawn(move || {
33043 DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
33044 *slot.borrow_mut() = Some(Box::new(move || {
33045 abort_blocked_tx.send(()).unwrap();
33046 }));
33047 });
33048 shutdown_result_tx
33049 .send(shutdown_controller.shutdown())
33050 .unwrap();
33051 });
33052
33053 abort_blocked_rx
33054 .recv_timeout(Duration::from_secs(2))
33055 .expect("the accepted task's abort wake must reach the closed verdict gate");
33056 let lifecycle_is_stopping = {
33057 let state = controller
33058 .state
33059 .lock()
33060 .unwrap_or_else(std::sync::PoisonError::into_inner);
33061 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
33062 };
33063 let stop_was_published = weak_executor
33064 .upgrade()
33065 .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
33066
33067 release_verdict_tx.send(()).unwrap();
33068
33069 let driver_result = driver_result_rx
33070 .recv_timeout(Duration::from_secs(2))
33071 .expect("the gated driver must resolve after observing shutdown");
33072 let shutdown_result = shutdown_result_rx
33073 .recv_timeout(Duration::from_secs(5))
33074 .expect("shutdown must finish after the verdict gate reopens");
33075 join_within(
33080 "the gated cooperative driver exits after teardown",
33081 driver,
33082 Duration::from_secs(30),
33083 );
33084 join_within(
33085 "the shutdown thread exits after teardown",
33086 shutdown,
33087 Duration::from_secs(30),
33088 );
33089
33090 assert!(
33091 lifecycle_is_stopping,
33092 "the abort wake must occur after Running -> Stopping"
33093 );
33094 assert!(
33095 stop_was_published,
33096 "generation stop must be visible before shutdown invokes accepted-task aborts"
33097 );
33098 assert_eq!(
33099 driver_result,
33100 Ok(BlockOnOutcome::Stopped),
33101 "shutdown must not be reported as a gated BlockOnDeadlock"
33102 );
33103 shutdown_result.unwrap();
33104 assert!(
33105 futures::executor::block_on(task).is_err(),
33106 "the accepted pending task must resolve as cancelled"
33107 );
33108 }
33109
33110 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
33111 #[test]
33112 fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
33113 use std::sync::mpsc;
33118
33119 let controller = Arc::new(RuntimeController::new());
33120 controller
33121 .configure(RuntimeOptions {
33122 flavor: RuntimeFlavor::MultiThread,
33123 worker_threads: 2,
33124 max_blocking_tasks: 1,
33125 thread_name_prefix: "verdict-before-shutdown".to_string(),
33126 park_deadline: Some(Duration::from_millis(40)),
33127 drain_linger: DEFAULT_DRAIN_LINGER,
33128 })
33129 .expect("the verdict-first test configuration must be accepted");
33130
33131 let backend = controller.backend();
33132 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33133 panic!("the configured backend must be MultiThread");
33134 };
33135 let executor_id = executor.id;
33136 drop(backend);
33137
33138 let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
33139 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33140 let (driver_result_tx, driver_result_rx) = mpsc::channel();
33141 let driver_controller = Arc::clone(&controller);
33142 let driver = std::thread::spawn(move || {
33143 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33144 *slot.borrow_mut() = Some(Box::new(move || {
33145 verdict_locked_tx.send(()).unwrap();
33146 release_verdict_rx.recv().unwrap();
33147 }));
33148 });
33149
33150 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33151 let mut pending = std::pin::pin!(NeverReady);
33152 let result = catch_unwind(AssertUnwindSafe(|| {
33153 driver_controller.block_on(pending.as_mut())
33154 }));
33155 let result = match result {
33156 Ok(outcome) => Ok(outcome),
33157 Err(payload) => {
33158 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33159 discard_panic_payload(payload);
33160 Err(was_deadlock)
33161 }
33162 };
33163 driver_result_tx.send(result).unwrap();
33164 });
33165
33166 verdict_locked_rx
33167 .recv_timeout(Duration::from_secs(2))
33168 .expect("the verdict must hold the synchronized stop-publication guard");
33169
33170 let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
33171 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33172 let shutdown_controller = Arc::clone(&controller);
33173 let shutdown = std::thread::spawn(move || {
33174 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
33175 *slot.borrow_mut() = Some(Box::new(move || {
33176 shutdown_waiting_tx.send(()).unwrap();
33177 }));
33178 });
33179 shutdown_result_tx
33180 .send(shutdown_controller.shutdown())
33181 .unwrap();
33182 });
33183
33184 shutdown_waiting_rx
33185 .recv_timeout(Duration::from_secs(2))
33186 .expect("shutdown must reach the lifecycle-to-publication lock edge");
33187 let early_shutdown = shutdown_result_rx
33188 .recv_timeout(Duration::from_millis(100))
33189 .ok();
33190 let shutdown_was_blocked = early_shutdown.is_none();
33191 release_verdict_tx.send(()).unwrap();
33192
33193 let driver_result = driver_result_rx
33194 .recv_timeout(Duration::from_secs(2))
33195 .expect("the verdict-first driver must finish after release");
33196 let shutdown_result = early_shutdown.unwrap_or_else(|| {
33197 shutdown_result_rx
33198 .recv_timeout(Duration::from_secs(5))
33199 .expect("shutdown must finish after the verdict releases publication")
33200 });
33201 driver.join().unwrap();
33202 shutdown.join().unwrap();
33203
33204 assert!(
33205 shutdown_was_blocked,
33206 "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
33207 );
33208 assert_eq!(
33209 driver_result,
33210 Err(true),
33211 "a verdict that linearizes before shutdown must retain the typed deadlock"
33212 );
33213 shutdown_result.unwrap();
33214 }
33215
33216 #[cfg(not(target_family = "wasm"))]
33217 #[test]
33218 fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
33219 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
33220
33221 if std::env::var_os(CHILD_ENV).is_some() {
33222 let executor =
33223 multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
33224 executor
33227 .active_drainers
33228 .store(executor.max_drainers, Ordering::SeqCst);
33229
33230 let hook_returned = Arc::new(AtomicBool::new(false));
33231 let hook_flag = Arc::clone(&hook_returned);
33232 let hook_executor = Arc::clone(&executor);
33233 let previous_hook = std::panic::take_hook();
33234 std::panic::set_hook(Box::new(move |_| {
33235 let handle = hook_executor.schedule_blocking(|| ());
33236 drop(handle);
33237 hook_flag.store(true, Ordering::SeqCst);
33238 }));
33239
33240 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33241 let result = catch_unwind(AssertUnwindSafe(|| {
33242 let mut pending = std::pin::pin!(std::future::pending::<()>());
33243 executor.block_on(pending.as_mut());
33244 }));
33245 std::panic::set_hook(previous_hook);
33246
33247 let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
33248 assert!(
33249 payload.downcast_ref::<BlockOnDeadlock>().is_some(),
33250 "the deadline must retain its structured diagnostic"
33251 );
33252 assert!(
33253 hook_returned.load(Ordering::SeqCst),
33254 "the panic hook's scheduler submission must return before unwind"
33255 );
33256
33257 executor.active_drainers.store(0, Ordering::SeqCst);
33258 executor.begin_shutdown();
33259 executor.wait_until_scheduler_idle();
33260 return;
33261 }
33262
33263 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
33264 .arg("--exact")
33265 .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
33266 .arg("--nocapture")
33267 .env(CHILD_ENV, "1")
33268 .stdout(std::process::Stdio::piped())
33269 .stderr(std::process::Stdio::piped())
33270 .spawn()
33271 .expect("the panic-hook subprocess must start");
33272 let deadline = Instant::now() + Duration::from_secs(5);
33273 let status = loop {
33274 if let Some(status) = child
33275 .try_wait()
33276 .expect("the subprocess status must be readable")
33277 {
33278 break status;
33279 }
33280 if Instant::now() >= deadline {
33281 let _ = child.kill();
33282 let output = child
33283 .wait_with_output()
33284 .expect("the timed-out subprocess must be reaped");
33285 panic!(
33286 "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
33287 String::from_utf8_lossy(&output.stdout),
33288 String::from_utf8_lossy(&output.stderr)
33289 );
33290 }
33291 std::thread::sleep(Duration::from_millis(10));
33292 };
33293 let output = child
33294 .wait_with_output()
33295 .expect("the completed subprocess output must be read");
33296 assert!(
33297 status.success(),
33298 "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
33299 status.code(),
33300 String::from_utf8_lossy(&output.stdout),
33301 String::from_utf8_lossy(&output.stderr)
33302 );
33303 }
33304
33305 #[cfg(panic = "unwind")]
33316 #[test]
33317 fn contained_waker_separates_wake_and_drop_panics() {
33318 struct PanicWakeAndDrop;
33319
33320 impl std::task::Wake for PanicWakeAndDrop {
33321 fn wake(self: Arc<Self>) {
33322 panic!("intentional consuming wake panic");
33323 }
33324
33325 fn wake_by_ref(self: &Arc<Self>) {
33326 panic!("intentional borrowed wake panic");
33327 }
33328 }
33329
33330 impl Drop for PanicWakeAndDrop {
33331 fn drop(&mut self) {
33332 panic!("intentional RawWaker destructor panic");
33333 }
33334 }
33335
33336 wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
33337 }
33338
33339 struct TestHostileRawWakerState {
33340 clones: AtomicUsize,
33341 drop_panics: AtomicUsize,
33342 panic_on_clone: Option<usize>,
33343 }
33344
33345 struct TestHostileRawWakerToken {
33346 state: Arc<TestHostileRawWakerState>,
33347 panic_on_drop: bool,
33348 }
33349
33350 unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
33351 let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
33352 let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
33353 assert_ne!(
33354 token.state.panic_on_clone,
33355 Some(clone),
33356 "intentional RawWaker clone panic"
33357 );
33358 let clone = Box::new(TestHostileRawWakerToken {
33359 state: Arc::clone(&token.state),
33360 panic_on_drop: true,
33361 });
33362 std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
33363 }
33364
33365 unsafe fn wake_test_hostile_waker(data: *const ()) {
33366 unsafe { drop_test_hostile_waker(data) };
33367 }
33368
33369 unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
33370
33371 unsafe fn drop_test_hostile_waker(data: *const ()) {
33372 let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
33373 if token.panic_on_drop {
33374 token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
33375 panic!("intentional cloned RawWaker destructor panic");
33376 }
33377 }
33378
33379 static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
33380 clone_test_hostile_waker,
33381 wake_test_hostile_waker,
33382 wake_test_hostile_waker_by_ref,
33383 drop_test_hostile_waker,
33384 );
33385
33386 fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
33387 let token = Box::new(TestHostileRawWakerToken {
33388 state,
33389 panic_on_drop: false,
33390 });
33391 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
33392 unsafe { Waker::from_raw(raw) }
33393 }
33394
33395 struct CountingWake(AtomicUsize);
33397 impl std::task::Wake for CountingWake {
33398 fn wake(self: Arc<Self>) {
33399 self.0.fetch_add(1, Ordering::SeqCst);
33400 }
33401 }
33402
33403 type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
33405
33406 struct StubHostTimerDriver {
33410 timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
33411 }
33412
33413 impl StubHostTimerDriver {
33414 fn new() -> Self {
33415 Self {
33416 timers: Mutex::new(FxHashMap::default()),
33417 }
33418 }
33419 }
33420
33421 impl TimerDriver for StubHostTimerDriver {
33422 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33423 let mut timers = self.timers.lock().unwrap();
33424 if let Some(cell) = timers.get(&id) {
33425 *cell.0.lock().unwrap() = waker;
33426 return;
33427 }
33428 let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
33429 timers.insert(id, Arc::clone(&cell));
33430 std::thread::spawn(move || {
33431 let now = Instant::now();
33432 if deadline > now {
33433 std::thread::sleep(deadline - now);
33434 }
33435 if !cell.1.load(Ordering::SeqCst) {
33436 cell.0.lock().unwrap().wake_by_ref();
33437 }
33438 });
33439 }
33440
33441 fn cancel(&self, id: TimerId) {
33442 let removed = self.timers.lock().unwrap().remove(&id);
33443 if let Some(cell) = removed {
33444 cell.1.store(true, Ordering::SeqCst);
33445 }
33446 }
33447 }
33448
33449 struct ManualStubDriver {
33455 live: AtomicBool,
33456 registers: Mutex<Vec<(TimerId, Instant)>>,
33457 cancels: Mutex<Vec<TimerId>>,
33458 pending: Mutex<FxHashMap<TimerId, Waker>>,
33459 swept: AtomicBool,
33460 }
33461
33462 impl ManualStubDriver {
33463 fn new() -> Arc<Self> {
33464 Arc::new(Self {
33465 live: AtomicBool::new(true),
33466 registers: Mutex::new(Vec::new()),
33467 cancels: Mutex::new(Vec::new()),
33468 pending: Mutex::new(FxHashMap::default()),
33469 swept: AtomicBool::new(false),
33470 })
33471 }
33472
33473 fn kill(&self) {
33474 self.live.store(false, Ordering::SeqCst);
33475 }
33476 }
33477
33478 impl TimerDriver for ManualStubDriver {
33479 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33480 self.registers.lock().unwrap().push((id, deadline));
33481 self.pending.lock().unwrap().insert(id, waker);
33482 }
33483
33484 fn cancel(&self, id: TimerId) {
33485 self.cancels.lock().unwrap().push(id);
33486 self.pending.lock().unwrap().remove(&id);
33487 }
33488
33489 fn is_live(&self) -> bool {
33490 self.live.load(Ordering::SeqCst)
33491 }
33492
33493 fn on_swept(&self) {
33494 self.swept.store(true, Ordering::SeqCst);
33497 let wakers: Vec<Waker> = self
33498 .pending
33499 .lock()
33500 .unwrap()
33501 .drain()
33502 .map(|(_, w)| w)
33503 .collect();
33504 for waker in wakers {
33505 waker.wake();
33506 }
33507 }
33508 }
33509
33510 struct PanickingTimerDriver {
33511 swept: AtomicBool,
33512 }
33513
33514 impl TimerDriver for PanickingTimerDriver {
33515 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33516
33517 fn cancel(&self, _id: TimerId) {}
33518
33519 fn is_live(&self) -> bool {
33520 panic!("intentional timer-driver liveness panic");
33521 }
33522
33523 fn on_swept(&self) {
33524 self.swept.store(true, Ordering::SeqCst);
33525 panic!("intentional timer-driver sweep panic");
33526 }
33527 }
33528
33529 struct PanickingRegisterTimerDriver {
33530 registers: AtomicUsize,
33531 cancels: Mutex<Vec<TimerId>>,
33532 pending: Mutex<FxHashMap<TimerId, Waker>>,
33533 }
33534
33535 impl PanickingRegisterTimerDriver {
33536 fn new() -> Arc<Self> {
33537 Arc::new(Self {
33538 registers: AtomicUsize::new(0),
33539 cancels: Mutex::new(Vec::new()),
33540 pending: Mutex::new(FxHashMap::default()),
33541 })
33542 }
33543 }
33544
33545 impl TimerDriver for PanickingRegisterTimerDriver {
33546 fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
33547 self.registers.fetch_add(1, Ordering::SeqCst);
33548 self.pending.lock().unwrap().insert(id, waker);
33549 panic!("intentional timer-driver register panic after arming");
33550 }
33551
33552 fn cancel(&self, id: TimerId) {
33553 self.cancels.lock().unwrap().push(id);
33554 self.pending.lock().unwrap().remove(&id);
33555 }
33556 }
33557
33558 struct PanicBeforeRetainingTimerDriver {
33559 registers: AtomicUsize,
33560 }
33561
33562 impl TimerDriver for PanicBeforeRetainingTimerDriver {
33563 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
33564 self.registers.fetch_add(1, Ordering::SeqCst);
33565 panic!("intentional timer-driver register panic before retaining its waker");
33566 }
33567
33568 fn cancel(&self, _id: TimerId) {}
33569 }
33570
33571 struct PanickingCancelDropTimerDriver {
33572 cancels: Arc<AtomicUsize>,
33573 drops: Arc<AtomicUsize>,
33574 }
33575
33576 impl TimerDriver for PanickingCancelDropTimerDriver {
33577 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33578
33579 fn cancel(&self, _id: TimerId) {
33580 self.cancels.fetch_add(1, Ordering::SeqCst);
33581 panic!("intentional timer-driver cancel panic");
33582 }
33583 }
33584
33585 impl Drop for PanickingCancelDropTimerDriver {
33586 fn drop(&mut self) {
33587 self.drops.fetch_add(1, Ordering::SeqCst);
33588 panic!("intentional timer-driver destructor panic");
33589 }
33590 }
33591
33592 struct UnregisterLaterThenPanicTimerDriver {
33593 registry: Weak<TimerDriverRegistry>,
33594 later: Mutex<Option<TimerDriverId>>,
33595 registers: AtomicUsize,
33596 }
33597
33598 impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
33599 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
33600 self.registers.fetch_add(1, Ordering::SeqCst);
33601 let later = *self.later.lock().unwrap();
33602 if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
33603 registry.unregister(later);
33604 }
33605 panic!("intentional earlier timer-driver panic after unregistering a later driver");
33606 }
33607
33608 fn cancel(&self, _id: TimerId) {}
33609 }
33610
33611 struct ReentrantLivenessDriver {
33612 registry: std::sync::Weak<TimerDriverRegistry>,
33613 registration: Mutex<Option<TimerDriverId>>,
33614 }
33615
33616 impl TimerDriver for ReentrantLivenessDriver {
33617 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33618
33619 fn cancel(&self, _id: TimerId) {}
33620
33621 fn is_live(&self) -> bool {
33622 let registration = *self.registration.lock().unwrap();
33623 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
33624 registry.unregister(id);
33625 }
33626 false
33627 }
33628 }
33629
33630 struct ReentrantMultiUnregisterTimerDriver {
33631 registry: std::sync::Weak<TimerDriverRegistry>,
33632 registrations: Mutex<Vec<TimerDriverId>>,
33633 }
33634
33635 impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
33636 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33637
33638 fn cancel(&self, _id: TimerId) {}
33639
33640 fn is_live(&self) -> bool {
33641 if let Some(registry) = self.registry.upgrade() {
33642 for registration in self.registrations.lock().unwrap().iter().copied() {
33643 registry.unregister(registration);
33644 }
33645 }
33646 false
33647 }
33648 }
33649
33650 struct ReentrantSweepTimerDriver {
33651 registry: std::sync::Weak<TimerDriverRegistry>,
33652 registration: Mutex<Option<TimerDriverId>>,
33653 sweeps: AtomicUsize,
33654 removed_before_sweep: AtomicBool,
33655 }
33656
33657 impl TimerDriver for ReentrantSweepTimerDriver {
33658 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33659
33660 fn cancel(&self, _id: TimerId) {}
33661
33662 fn is_live(&self) -> bool {
33663 false
33664 }
33665
33666 fn on_swept(&self) {
33667 self.sweeps.fetch_add(1, Ordering::SeqCst);
33668 let registration = self.registration.lock().unwrap().take();
33669 if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
33670 let state = registry
33671 .state
33672 .try_lock()
33673 .expect("the sweep callback must run outside the timer-driver registry lock");
33674 assert!(
33675 !state.entries.contains_key(®istration),
33676 "the dead driver must be unlinked before its sweep callback runs"
33677 );
33678 self.removed_before_sweep.store(true, Ordering::SeqCst);
33679 drop(state);
33680 registry.unregister(registration);
33683 }
33684 }
33685 }
33686
33687 struct ReentrantDropDriver {
33688 registry: std::sync::Weak<TimerDriverRegistry>,
33689 fallback: TimerDriverId,
33690 }
33691
33692 impl TimerDriver for ReentrantDropDriver {
33693 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33694
33695 fn cancel(&self, _id: TimerId) {}
33696 }
33697
33698 impl Drop for ReentrantDropDriver {
33699 fn drop(&mut self) {
33700 if let Some(registry) = self.registry.upgrade() {
33701 registry.unregister(self.fallback);
33702 }
33703 }
33704 }
33705
33706 #[derive(Default)]
33709 struct WakeFlag(AtomicBool);
33710
33711 impl std::task::Wake for WakeFlag {
33712 fn wake(self: Arc<Self>) {
33713 self.0.store(true, Ordering::SeqCst);
33714 }
33715 }
33716
33717 #[cfg(napi_runtime_os_threads)]
33718 fn multi_thread_executor(
33719 workers: usize,
33720 park_deadline: Option<Duration>,
33721 prefix: &str,
33722 ) -> Arc<MultiThreadExecutor> {
33723 let options = RuntimeOptions {
33724 flavor: RuntimeFlavor::MultiThread,
33725 worker_threads: workers,
33726 max_blocking_tasks: workers,
33727 thread_name_prefix: prefix.to_string(),
33728 park_deadline,
33729 drain_linger: DEFAULT_DRAIN_LINGER,
33730 };
33731 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
33732 }
33733
33734 fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
33737 let registry = Arc::new(TimerDriverRegistry::default());
33738 registry.register(driver);
33739 registry
33740 }
33741
33742 #[cfg(napi_runtime_os_threads)]
33743 fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
33744 make_sleep(
33746 &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
33747 &Arc::new(TimerDriverRegistry::default()),
33748 deadline,
33749 )
33750 }
33751
33752 #[cfg(napi_runtime_os_threads)]
33753 #[test]
33754 fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
33755 use std::sync::mpsc;
33756
33757 let executor = multi_thread_executor(
33758 1,
33759 Some(Duration::from_secs(1)),
33760 "timer-registration-admission",
33761 );
33762 let (entered_tx, entered_rx) = mpsc::channel();
33763 let (release_tx, release_rx) = mpsc::channel();
33764 let registration_executor = Arc::clone(&executor);
33765 let registration = std::thread::spawn(move || {
33766 TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
33767 *slot.borrow_mut() = Some(Box::new(move || {
33768 entered_tx.send(()).unwrap();
33769 release_rx.recv().unwrap();
33770 }));
33771 });
33772 registration_executor.register_timer(
33773 1,
33774 Instant::now() + Duration::from_secs(60),
33775 futures::task::noop_waker(),
33776 &Arc::new(AtomicBool::new(false)),
33777 );
33778 });
33779
33780 entered_rx
33781 .recv_timeout(Duration::from_secs(2))
33782 .expect("timer registration must pause after entering verdict admission");
33783 let verdict = executor.try_begin_deadlock_verdict();
33784 let admission_blocked_verdict = verdict.is_none();
33785 drop(verdict);
33786 release_tx.send(()).unwrap();
33787 registration.join().unwrap();
33788
33789 assert!(
33790 admission_blocked_verdict,
33791 "a timer registration must remain announced until its heap entry is visible"
33792 );
33793 assert!(
33794 executor.next_timer_deadline().is_some(),
33795 "the admitted registration must publish its timer after release"
33796 );
33797 executor.begin_shutdown();
33798 executor.wait_until_scheduler_idle();
33799 }
33800
33801 #[cfg(napi_runtime_os_threads)]
33802 #[derive(Clone, Copy, Debug)]
33803 enum TimerFireAdmissionPath {
33804 Timekeeper,
33805 Cooperative,
33806 Shutdown,
33807 }
33808
33809 #[cfg(napi_runtime_os_threads)]
33810 struct ScheduleAfterTimerWakeRelease {
33811 executor: Arc<MultiThreadExecutor>,
33812 runnable: Mutex<Option<Runnable>>,
33813 entered: std::sync::mpsc::Sender<()>,
33814 release: Mutex<std::sync::mpsc::Receiver<()>>,
33815 scheduled: std::sync::mpsc::Sender<()>,
33816 }
33817
33818 #[cfg(napi_runtime_os_threads)]
33819 impl ScheduleAfterTimerWakeRelease {
33820 fn publish(&self) {
33821 self.entered.send(()).unwrap();
33822 self
33823 .release
33824 .lock()
33825 .unwrap_or_else(std::sync::PoisonError::into_inner)
33826 .recv()
33827 .unwrap();
33828 let runnable = self
33829 .runnable
33830 .lock()
33831 .unwrap_or_else(std::sync::PoisonError::into_inner)
33832 .take();
33833 if let Some(runnable) = runnable {
33834 self.executor.schedule(runnable);
33835 }
33836 self.scheduled.send(()).unwrap();
33837 }
33838 }
33839
33840 #[cfg(napi_runtime_os_threads)]
33841 impl std::task::Wake for ScheduleAfterTimerWakeRelease {
33842 fn wake(self: Arc<Self>) {
33843 self.publish();
33844 }
33845
33846 fn wake_by_ref(self: &Arc<Self>) {
33847 self.publish();
33848 }
33849 }
33850
33851 #[cfg(napi_runtime_os_threads)]
33852 fn insert_timer_for_fire_admission_test(
33853 executor: &MultiThreadExecutor,
33854 id: TimerId,
33855 deadline: Instant,
33856 waker: Waker,
33857 fired: Arc<AtomicBool>,
33858 ) {
33859 let mut inner = executor
33860 .timers
33861 .inner
33862 .lock()
33863 .unwrap_or_else(std::sync::PoisonError::into_inner);
33864 inner.entries.insert(id, HeapTimerEntry { waker, fired });
33865 inner.queue.push(deadline, id);
33866 }
33867
33868 #[cfg(napi_runtime_os_threads)]
33869 fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
33870 use std::sync::mpsc;
33871
33872 let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
33873 let (runnable, task) = async_task::spawn(async {}, |_| {});
33874 task.detach();
33875 let (entered_tx, entered_rx) = mpsc::channel();
33876 let (release_tx, release_rx) = mpsc::channel();
33877 let (scheduled_tx, scheduled_rx) = mpsc::channel();
33878 let fired = Arc::new(AtomicBool::new(false));
33879 let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
33880 executor: Arc::clone(&executor),
33881 runnable: Mutex::new(Some(runnable)),
33882 entered: entered_tx,
33883 release: Mutex::new(release_rx),
33884 scheduled: scheduled_tx,
33885 }));
33886
33887 let fire_thread = match path {
33888 TimerFireAdmissionPath::Timekeeper => {
33889 executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
33890 None
33891 }
33892 TimerFireAdmissionPath::Cooperative => {
33893 insert_timer_for_fire_admission_test(
33894 &executor,
33895 1,
33896 Instant::now(),
33897 waker,
33898 Arc::clone(&fired),
33899 );
33900 let fire_executor = Arc::clone(&executor);
33901 Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
33902 }
33903 TimerFireAdmissionPath::Shutdown => {
33904 insert_timer_for_fire_admission_test(
33905 &executor,
33906 1,
33907 Instant::now() + Duration::from_secs(60),
33908 waker,
33909 Arc::clone(&fired),
33910 );
33911 let fire_executor = Arc::clone(&executor);
33912 Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
33913 }
33914 };
33915
33916 entered_rx
33920 .recv_timeout(Duration::from_secs(10))
33921 .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
33922 assert!(
33923 executor.next_timer_deadline().is_none(),
33924 "{path:?} must exercise the remove-before-wake window"
33925 );
33926 assert!(
33927 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
33928 "{path:?} must retain admission while its waker is publishing"
33929 );
33930 let verdict = executor.try_begin_deadlock_verdict();
33931 let admission_blocked_verdict = verdict.is_none();
33932 drop(verdict);
33933
33934 release_tx.send(()).unwrap();
33935 scheduled_rx
33936 .recv_timeout(Duration::from_secs(10))
33937 .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
33938 if let Some(fire_thread) = fire_thread {
33939 fire_thread.join().unwrap();
33940 }
33941 assert!(
33942 admission_blocked_verdict,
33943 "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
33944 );
33945
33946 executor.begin_shutdown();
33947 executor.wait_until_scheduler_idle();
33948 }
33949
33950 #[cfg(napi_runtime_os_threads)]
33951 #[test]
33952 fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
33953 for path in [
33954 TimerFireAdmissionPath::Timekeeper,
33955 TimerFireAdmissionPath::Cooperative,
33956 TimerFireAdmissionPath::Shutdown,
33957 ] {
33958 assert_timer_fire_path_holds_deadlock_admission(path);
33959 }
33960 }
33961
33962 #[cfg(napi_runtime_os_threads)]
33963 #[test]
33964 fn multi_thread_sleep_fires_from_foreign_block_on() {
33965 use std::sync::mpsc;
33970
33971 let (done_tx, done_rx) = mpsc::channel();
33972 let runner = std::thread::spawn(move || {
33973 let executor = multi_thread_executor(2, None, "timer-foreign");
33974 let started = Instant::now();
33975 let mut future = std::pin::pin!(async {
33976 heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
33977 });
33978 executor.block_on(future.as_mut());
33979 assert!(
33980 started.elapsed() >= Duration::from_millis(80),
33981 "the sleep must not resolve before its deadline"
33982 );
33983 let _ = done_tx.send(());
33984 });
33985
33986 match done_rx.recv_timeout(Duration::from_secs(10)) {
33987 Ok(()) => runner.join().unwrap(),
33988 Err(error) => {
33989 panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
33990 }
33991 }
33992 }
33993
33994 #[cfg(napi_runtime_os_threads)]
33995 #[test]
33996 fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
33997 use std::sync::mpsc;
34000
34001 let (done_tx, done_rx) = mpsc::channel();
34002 let runner = std::thread::spawn(move || {
34003 let executor = multi_thread_executor(1, None, "timer-drainer");
34004
34005 let sleep_exec = Arc::clone(&executor);
34008 let sleep_done = Arc::new(AtomicBool::new(false));
34009 let sleep_flag = Arc::clone(&sleep_done);
34010 let sched = Arc::clone(&executor);
34011 let (sleep_runnable, sleep_task) = async_task::spawn(
34012 async move {
34013 heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
34014 sleep_flag.store(true, Ordering::SeqCst);
34015 },
34016 move |r| sched.schedule(r),
34017 );
34018 executor.schedule(sleep_runnable);
34019 wait_until("the timekeeper parked on the 60s deadline", || {
34020 executor
34021 .timers
34022 .inner
34023 .lock()
34024 .unwrap()
34025 .timekeeper_parker
34026 .is_some()
34027 });
34028
34029 let counter = Arc::new(AtomicUsize::new(0));
34031 let mut tasks = Vec::new();
34032 for _ in 0..32 {
34033 let counter = Arc::clone(&counter);
34034 let sched = Arc::clone(&executor);
34035 let (runnable, task) = async_task::spawn(
34036 async move {
34037 counter.fetch_add(1, Ordering::SeqCst);
34038 },
34039 move |r| sched.schedule(r),
34040 );
34041 executor.schedule(runnable);
34042 tasks.push(task);
34043 }
34044 for task in tasks {
34045 futures::executor::block_on(task);
34046 }
34047 assert_eq!(counter.load(Ordering::SeqCst), 32);
34048 assert!(
34049 !sleep_done.load(Ordering::SeqCst),
34050 "the 60s timer must still be pending: the work ran DURING the timer wait"
34051 );
34052 executor.shutdown_timers();
34054 futures::executor::block_on(sleep_task);
34055 let _ = done_tx.send(());
34056 });
34057
34058 match done_rx.recv_timeout(Duration::from_secs(15)) {
34059 Ok(()) => runner.join().unwrap(),
34060 Err(error) => {
34061 panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
34062 }
34063 }
34064 }
34065
34066 #[cfg(napi_runtime_os_threads)]
34067 #[test]
34068 fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
34069 let executor = multi_thread_executor(2, None, "timer-lazy-start");
34070 assert!(
34071 executor.timers.thread.lock().unwrap().is_none(),
34072 "constructing a runtime without timers must not create an extra OS thread"
34073 );
34074
34075 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34076 assert!(
34077 executor.timers.thread.lock().unwrap().is_none(),
34078 "an unpolled sleep has not registered a deadline"
34079 );
34080 let waker = futures::task::noop_waker();
34081 let mut cx = Context::from_waker(&waker);
34082 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34083 assert!(
34084 executor.timers.thread.lock().unwrap().is_some(),
34085 "the first timer registration must start the dedicated service"
34086 );
34087 }
34088
34089 #[cfg(napi_runtime_os_threads)]
34090 #[test]
34091 fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
34092 use std::sync::mpsc;
34093
34094 let executor = multi_thread_executor(1, None, "timer-closed-start");
34095 executor.shutdown_timers();
34096 let handle_guard = executor.timers.thread.lock().unwrap();
34097 let ensure_executor = Arc::clone(&executor);
34098 let (completed_tx, completed_rx) = mpsc::channel();
34099 let ensure = std::thread::spawn(move || {
34100 ensure_executor.timers.ensure_started().unwrap();
34101 completed_tx.send(()).unwrap();
34102 });
34103
34104 completed_rx.recv_timeout(Duration::from_secs(10)).expect(
34107 "ensure_started consulted or joined the timer handle before observing the closed heap",
34108 );
34109 drop(handle_guard);
34110 ensure.join().unwrap();
34111 }
34112
34113 #[cfg(napi_runtime_os_threads)]
34114 #[test]
34115 fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
34116 const TIMERS: u64 = 4096;
34117
34118 let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
34119 let base = Instant::now() + Duration::from_secs(3600);
34120 for id in 0..TIMERS {
34121 executor.register_timer(
34122 id,
34123 base + Duration::from_secs(id),
34124 futures::task::noop_waker(),
34125 &Arc::new(AtomicBool::new(false)),
34126 );
34127 }
34128 {
34129 let inner = executor.timers.inner.lock().unwrap();
34130 assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
34131 assert_eq!(inner.queue.len(), inner.entries.len());
34132 }
34133
34134 for id in 1..TIMERS {
34137 executor.cancel_timer(id);
34138 }
34139 {
34140 let inner = executor.timers.inner.lock().unwrap();
34141 assert_eq!(inner.entries.len(), 1);
34142 assert_eq!(inner.queue.len(), 1);
34143 assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
34144 }
34145
34146 executor.cancel_timer(0);
34147 {
34148 let inner = executor.timers.inner.lock().unwrap();
34149 assert!(inner.entries.is_empty());
34150 assert!(inner.queue.is_empty());
34151 }
34152 executor.shutdown_timers();
34153 executor.timers.join();
34154 }
34155
34156 #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
34157 #[test]
34158 fn timer_registration_is_transactional_when_timekeeper_start_fails() {
34159 let executor = multi_thread_executor(2, None, "timer-start-failure");
34160 executor
34161 .timers
34162 .fail_next_start
34163 .store(true, Ordering::SeqCst);
34164
34165 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34166 let waker = Waker::from(Arc::clone(&wake_count));
34167 let baseline_waker_refs = Arc::strong_count(&wake_count);
34168 let mut cx = Context::from_waker(&waker);
34169 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34170
34171 let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
34172 assert!(
34173 failure.is_err(),
34174 "the injected timer-thread start failure must panic the poll"
34175 );
34176 {
34177 let inner = executor.timers.inner.lock().unwrap();
34178 assert!(
34179 inner.entries.is_empty(),
34180 "a failed start must not retain a timer entry"
34181 );
34182 assert!(
34183 inner.queue.is_empty(),
34184 "a failed start must not retain a stale heap node"
34185 );
34186 assert!(
34187 inner.timekeeper_parker.is_none(),
34188 "a failed start must not publish a timekeeper parker"
34189 );
34190 }
34191 assert!(
34192 executor.timers.thread.lock().unwrap().is_none(),
34193 "a failed start must not retain a thread handle"
34194 );
34195 assert_eq!(
34196 Arc::strong_count(&wake_count),
34197 baseline_waker_refs,
34198 "a failed start must release the cloned timer waker"
34199 );
34200
34201 assert!(
34202 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34203 "the same Sleep must be safely retryable after a transient start failure"
34204 );
34205 {
34206 let inner = executor.timers.inner.lock().unwrap();
34207 assert_eq!(
34208 inner.entries.len(),
34209 1,
34210 "the retry must publish exactly one timer entry"
34211 );
34212 assert_eq!(
34213 inner.queue.len(),
34214 1,
34215 "the retry must publish exactly one heap node"
34216 );
34217 }
34218 assert!(
34219 executor.timers.thread.lock().unwrap().is_some(),
34220 "the retry must start the dedicated timekeeper"
34221 );
34222
34223 drop(sleep);
34224 assert!(
34225 executor.timers.inner.lock().unwrap().entries.is_empty(),
34226 "dropping the retried Sleep must cancel its registration"
34227 );
34228 executor.shutdown_timers();
34229 executor.wait_until_scheduler_idle();
34230 assert!(
34231 executor.timers.thread.lock().unwrap().is_none(),
34232 "shutdown after a recovered start failure must join the timekeeper"
34233 );
34234 }
34235
34236 #[cfg(napi_runtime_os_threads)]
34237 #[test]
34238 fn pending_timer_preserves_every_configured_rayon_worker() {
34239 use std::sync::mpsc;
34240
34241 let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
34242 let waker = futures::task::noop_waker();
34243 let mut cx = Context::from_waker(&waker);
34244 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34245 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34246 wait_until("the dedicated timer thread to park", || {
34247 executor
34248 .timers
34249 .inner
34250 .lock()
34251 .unwrap()
34252 .timekeeper_parker
34253 .is_some()
34254 });
34255
34256 let released = Arc::new((Mutex::new(false), Condvar::new()));
34257 let (started_tx, started_rx) = mpsc::channel();
34258 let (completed_tx, completed_rx) = mpsc::channel();
34259 for _ in 0..2 {
34260 let released = Arc::clone(&released);
34261 let started_tx = started_tx.clone();
34262 let completed_tx = completed_tx.clone();
34263 executor.pool.spawn(move || {
34264 started_tx.send(()).unwrap();
34265 let (lock, changed) = &*released;
34266 let mut released = lock
34267 .lock()
34268 .unwrap_or_else(std::sync::PoisonError::into_inner);
34269 while !*released {
34270 released = changed
34271 .wait(released)
34272 .unwrap_or_else(std::sync::PoisonError::into_inner);
34273 }
34274 completed_tx.send(()).unwrap();
34275 });
34276 }
34277 drop(started_tx);
34278 drop(completed_tx);
34279
34280 let first = started_rx.recv_timeout(Duration::from_secs(2));
34281 let second = started_rx.recv_timeout(Duration::from_secs(2));
34282 {
34283 let (lock, changed) = &*released;
34284 *lock
34285 .lock()
34286 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
34287 changed.notify_all();
34288 }
34289 drop(sleep);
34290 for _ in 0..2 {
34291 completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34292 }
34293
34294 assert!(
34295 first.is_ok(),
34296 "the first configured Rayon worker did not start"
34297 );
34298 assert!(
34299 second.is_ok(),
34300 "a pending timer consumed one of the two configured Rayon workers"
34301 );
34302 }
34303
34304 #[cfg(napi_runtime_os_threads)]
34305 #[test]
34306 fn timekeeper_never_enters_a_blocking_closure() {
34307 use std::{
34308 sync::{Barrier, mpsc},
34309 time::Duration,
34310 };
34311
34312 let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
34313 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34314 let waker = Waker::from(Arc::clone(&wake_count));
34315 let mut cx = Context::from_waker(&waker);
34316 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
34317 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34318 wait_until(
34319 "the timekeeper parked before blocking work was submitted",
34320 || {
34321 executor
34322 .timers
34323 .inner
34324 .lock()
34325 .unwrap()
34326 .timekeeper_parker
34327 .is_some()
34328 },
34329 );
34330
34331 let release = Arc::new(Barrier::new(2));
34332 let blocker_release = Arc::clone(&release);
34333 let (started_tx, started_rx) = mpsc::sync_channel(1);
34334 let blocking = executor.schedule_blocking(move || {
34335 started_tx.send(()).unwrap();
34336 blocker_release.wait();
34337 });
34338 started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
34341
34342 wait_until(
34343 "the timer waker to fire while the Rayon worker is blocked",
34344 || wake_count.0.load(Ordering::SeqCst) != 0,
34345 );
34346 release.wait();
34347 futures::executor::block_on(blocking).unwrap();
34348 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
34349 }
34350
34351 #[cfg(napi_runtime_os_threads)]
34352 #[test]
34353 fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
34354 use std::sync::mpsc;
34355
34356 struct PendingEntered(Option<mpsc::Sender<()>>);
34357
34358 impl Future for PendingEntered {
34359 type Output = ();
34360
34361 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
34362 if let Some(entered) = self.0.take() {
34363 entered.send(()).unwrap();
34364 }
34365 Poll::Pending
34366 }
34367 }
34368
34369 let options = RuntimeOptions {
34370 flavor: RuntimeFlavor::MultiThread,
34371 worker_threads: 2,
34372 max_blocking_tasks: 1,
34373 thread_name_prefix: "nested-timer-wake".to_string(),
34374 park_deadline: None,
34375 drain_linger: DEFAULT_DRAIN_LINGER,
34376 };
34377 let executor =
34378 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
34379
34380 let timer_exec = Arc::clone(&executor);
34381 let (timer_tx, timer_rx) = mpsc::channel();
34382 let timer_scheduler = Arc::clone(&executor);
34383 let (timer_runnable, timer_task) = async_task::spawn(
34384 async move {
34385 heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
34386 timer_tx.send(()).unwrap();
34387 },
34388 move |runnable| timer_scheduler.schedule(runnable),
34389 );
34390 executor.schedule(timer_runnable);
34391 wait_until(
34392 "the dedicated timekeeper parked before nested block_on",
34393 || {
34394 executor
34395 .timers
34396 .inner
34397 .lock()
34398 .unwrap()
34399 .timekeeper_parker
34400 .is_some()
34401 },
34402 );
34403
34404 let (entered_tx, entered_rx) = mpsc::channel();
34405 let nested_exec = Arc::clone(&executor);
34406 let nested_scheduler = Arc::clone(&executor);
34407 let (nested_runnable, nested_task) = async_task::spawn(
34408 async move {
34409 let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
34410 let outcome = nested_exec.block_on(pending.as_mut());
34411 drop(pending);
34412 assert_eq!(
34413 outcome,
34414 BlockOnOutcome::Stopped,
34415 "shutdown must stop the raw nested block_on driver"
34416 );
34417 },
34418 move |runnable| nested_scheduler.schedule(runnable),
34419 );
34420 executor.schedule(nested_runnable);
34421 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34422
34423 let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
34424 let (release_tx, release_rx) = mpsc::channel();
34425 let blocking = executor.schedule_blocking(move || {
34426 blocking_started_tx.send(()).unwrap();
34427 release_rx.recv().unwrap();
34428 });
34429 blocking_started_rx
34430 .recv_timeout(Duration::from_secs(2))
34431 .expect("a general worker must service blocking work");
34432 timer_rx
34433 .recv_timeout(Duration::from_secs(2))
34434 .expect("the timer wake must reach the nested cooperative driver");
34435
34436 release_tx.send(()).unwrap();
34437 futures::executor::block_on(blocking).unwrap();
34438 executor.begin_shutdown();
34439 assert_eq!(
34440 futures::executor::block_on(nested_task.fallible()),
34441 Some(())
34442 );
34443 futures::executor::block_on(timer_task);
34444 }
34445
34446 #[cfg(napi_runtime_os_threads)]
34447 #[test]
34448 fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
34449 use std::sync::mpsc;
34453
34454 let (done_tx, done_rx) = mpsc::channel();
34455 let runner = std::thread::spawn(move || {
34456 let executor = multi_thread_executor(2, None, "timer-rearm");
34457
34458 let long_exec = Arc::clone(&executor);
34459 let long_done = Arc::new(AtomicBool::new(false));
34460 let long_flag = Arc::clone(&long_done);
34461 let sched = Arc::clone(&executor);
34462 let (long_runnable, long_task) = async_task::spawn(
34463 async move {
34464 heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
34465 long_flag.store(true, Ordering::SeqCst);
34466 },
34467 move |r| sched.schedule(r),
34468 );
34469 executor.schedule(long_runnable);
34470 wait_until("the timekeeper parked on the 60s sentinel", || {
34471 executor
34472 .timers
34473 .inner
34474 .lock()
34475 .unwrap()
34476 .timekeeper_parker
34477 .is_some()
34478 });
34479
34480 let started = Instant::now();
34481 let mut future = std::pin::pin!(async {
34482 heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
34483 });
34484 executor.block_on(future.as_mut());
34485 let elapsed = started.elapsed();
34486 assert!(
34487 elapsed >= Duration::from_millis(60),
34488 "fired before its deadline: {elapsed:?}"
34489 );
34490 assert!(
34491 !long_done.load(Ordering::SeqCst),
34492 "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
34493 timekeeper, not by the sentinel expiring"
34494 );
34495 executor.shutdown_timers();
34496 futures::executor::block_on(long_task);
34497 let _ = done_tx.send(());
34498 });
34499
34500 match done_rx.recv_timeout(Duration::from_secs(15)) {
34501 Ok(()) => runner.join().unwrap(),
34502 Err(error) => panic!(
34503 "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
34504 ),
34505 }
34506 }
34507
34508 #[cfg(napi_runtime_os_threads)]
34509 #[test]
34510 fn multi_thread_sleep_cancels_registration_on_drop() {
34511 let executor = multi_thread_executor(1, None, "timer-cancel");
34514 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34515 let waker = Waker::from(Arc::clone(&wake_count));
34516 let mut cx = Context::from_waker(&waker);
34517
34518 let deadline = Instant::now() + Duration::from_millis(250);
34522 let mut sleep = heap_sleep(&executor, deadline);
34523 assert!(
34524 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34525 "must register, not resolve"
34526 );
34527 assert!(
34528 executor.next_timer_deadline().is_some(),
34529 "the registration must be in the heap"
34530 );
34531 drop(sleep);
34532 assert!(
34533 executor.next_timer_deadline().is_none(),
34534 "cancel-on-drop must retire the heap registration"
34535 );
34536 while Instant::now() < deadline + Duration::from_millis(25) {
34541 std::thread::sleep(Duration::from_millis(10));
34542 }
34543 executor.fire_due_timers();
34544 assert_eq!(
34545 wake_count.0.load(Ordering::SeqCst),
34546 0,
34547 "a dropped Sleep's waker must never fire"
34548 );
34549 }
34550
34551 #[cfg(napi_runtime_os_threads)]
34552 #[test]
34553 fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
34554 let executor = multi_thread_executor(2, None, "timer-cancel-retire");
34555 let waker = futures::task::noop_waker();
34556 let mut cx = Context::from_waker(&waker);
34557 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34558
34559 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34560 wait_until(
34561 "the timekeeper parked on the timer that will be cancelled",
34562 || {
34563 executor
34564 .timers
34565 .inner
34566 .lock()
34567 .unwrap()
34568 .timekeeper_parker
34569 .is_some()
34570 },
34571 );
34572
34573 drop(sleep);
34574 wait_until(
34575 "the cancelled timer's timekeeper returned to its idle wait",
34576 || {
34577 executor.next_timer_deadline().is_none()
34578 && executor
34579 .timers
34580 .inner
34581 .lock()
34582 .unwrap()
34583 .timekeeper_parker
34584 .is_none()
34585 },
34586 );
34587 }
34588
34589 #[cfg(napi_runtime_os_threads)]
34590 #[test]
34591 fn timer_waker_replacement_drops_outside_heap_lock() {
34592 use std::sync::mpsc;
34593
34594 struct DropOtherTimer(Mutex<Option<Sleep>>);
34595
34596 #[expect(
34597 clippy::manual_noop_waker,
34598 reason = "the no-op wake type has a reentrant Drop used by this regression"
34599 )]
34600 impl std::task::Wake for DropOtherTimer {
34601 fn wake(self: Arc<Self>) {}
34602 }
34603
34604 impl Drop for DropOtherTimer {
34605 fn drop(&mut self) {
34606 drop(self.0.lock().unwrap().take());
34607 }
34608 }
34609
34610 let (done_tx, done_rx) = mpsc::channel();
34611 let runner = std::thread::spawn(move || {
34612 let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
34613 let deadline = Instant::now() + Duration::from_secs(120);
34614 let mut other = heap_sleep(&executor, deadline);
34615 let noop = futures::task::noop_waker();
34616 let mut noop_cx = Context::from_waker(&noop);
34617 assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
34618
34619 let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
34620 let mut primary = heap_sleep(&executor, deadline);
34621 let mut old_cx = Context::from_waker(&old);
34622 assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
34623 drop(old);
34624
34625 let replacement = futures::task::noop_waker();
34626 let mut replacement_cx = Context::from_waker(&replacement);
34627 assert!(
34628 Pin::new(&mut primary)
34629 .poll(&mut replacement_cx)
34630 .is_pending()
34631 );
34632 drop(primary);
34633 executor.shutdown_timers();
34634 done_tx.send(()).unwrap();
34635 });
34636 done_rx
34639 .recv_timeout(Duration::from_secs(10))
34640 .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
34641 runner.join().unwrap();
34642 }
34643
34644 #[cfg(napi_runtime_os_threads)]
34645 #[test]
34646 fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
34647 use std::sync::mpsc;
34648
34649 struct DropFinalExecutorOnWake {
34650 executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
34651 completed: mpsc::Sender<()>,
34652 }
34653
34654 impl std::task::Wake for DropFinalExecutorOnWake {
34655 fn wake(self: Arc<Self>) {
34656 let executor = self
34657 .executor
34658 .lock()
34659 .unwrap_or_else(std::sync::PoisonError::into_inner)
34660 .take()
34661 .expect("the timer waker must own the final executor reference");
34662 assert_eq!(
34663 ON_TIMER_THREAD.with(std::cell::Cell::get),
34664 Some(executor.id),
34665 "the dedicated timer thread must deliver the timer wake"
34666 );
34667 drop(executor);
34668 self
34669 .completed
34670 .send(())
34671 .expect("the test receiver must outlive the timer-thread callback");
34672 }
34673 }
34674
34675 let executor = multi_thread_executor(1, None, "timer-final-arc");
34676 let (completed_tx, completed_rx) = mpsc::channel();
34677 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
34678 {
34679 let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
34680 executor: Mutex::new(Some(Arc::clone(&executor))),
34681 completed: completed_tx,
34682 }));
34683 let mut cx = Context::from_waker(&waker);
34684 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34685 }
34686 wait_until(
34687 "the timer thread parked before owning the final executor Arc",
34688 || {
34689 executor
34690 .timers
34691 .inner
34692 .lock()
34693 .unwrap()
34694 .timekeeper_parker
34695 .is_some()
34696 },
34697 );
34698
34699 drop(executor);
34703 completed_rx
34706 .recv_timeout(Duration::from_secs(10))
34707 .expect("dropping the final executor Arc on the timer thread attempted to join itself");
34708 }
34709
34710 #[cfg(not(target_family = "wasm"))]
34711 #[test]
34712 fn multi_thread_shutdown_with_pending_timers_drain_fires() {
34713 use std::sync::mpsc;
34718
34719 let (done_tx, done_rx) = mpsc::channel();
34720 let runner = std::thread::spawn(move || {
34721 let controller = Arc::new(RuntimeController::new());
34722 let backend = controller.backend();
34724 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
34725 panic!("default native backend must be MultiThread");
34726 };
34727 let executor = Arc::downgrade(executor);
34728 let mut late = heap_sleep(
34729 &executor
34730 .upgrade()
34731 .expect("the initial executor must be live"),
34732 Instant::now() + Duration::from_secs(120),
34733 );
34734 drop(backend);
34735
34736 let (parked_tx, parked_rx) = mpsc::channel();
34737 let block_controller = Arc::clone(&controller);
34738 let block_exec = Weak::clone(&executor);
34739 let blocker = std::thread::spawn(move || {
34740 let mut future = std::pin::pin!(async {
34741 let executor = block_exec
34742 .upgrade()
34743 .expect("the runtime must remain live while blocking");
34744 let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
34745 drop(executor);
34746 parked_tx.send(()).unwrap();
34747 sleep.await;
34748 });
34749 block_controller.block_on(future.as_mut());
34750 });
34751 parked_rx.recv().unwrap();
34752 wait_until("the 120s sleep registered in the heap", || {
34753 executor
34754 .upgrade()
34755 .is_some_and(|executor| executor.next_timer_deadline().is_some())
34756 });
34757
34758 controller
34760 .shutdown()
34761 .expect("runtime shutdown must succeed");
34762 blocker
34763 .join()
34764 .expect("the blocked sleep must be drain-fired by shutdown, not hang");
34765
34766 let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
34769 let mut cx = Context::from_waker(&waker);
34770 assert!(
34771 Pin::new(&mut late).poll(&mut cx).is_ready(),
34772 "a Sleep polled after shutdown must fire early, never park a closing runtime"
34773 );
34774 let _ = done_tx.send(());
34775 });
34776
34777 match done_rx.recv_timeout(Duration::from_secs(10)) {
34778 Ok(()) => runner.join().unwrap(),
34779 Err(error) => {
34780 panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
34781 }
34782 }
34783 }
34784
34785 #[cfg(napi_runtime_os_threads)]
34786 #[test]
34787 fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
34788 use std::sync::mpsc;
34797
34798 let (done_tx, done_rx) = mpsc::channel();
34799 let runner = std::thread::spawn(move || {
34800 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
34801
34802 let task_exec = Arc::clone(&executor);
34803 let body = async move {
34804 let started = Instant::now();
34805 let mut inner = std::pin::pin!(async {
34806 heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
34807 });
34808 task_exec.block_on(inner.as_mut());
34810 assert!(started.elapsed() >= Duration::from_millis(250));
34811 };
34812 let sched = Arc::clone(&executor);
34813 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
34814 executor.schedule(runnable);
34815
34816 futures::executor::block_on(task);
34820 drop(executor);
34826 let _ = done_tx.send(());
34827 });
34828
34829 match done_rx.recv_timeout(Duration::from_secs(10)) {
34830 Ok(()) => join_within(
34831 "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
34832 runner,
34833 Duration::from_secs(30),
34834 ),
34835 Err(error) => {
34836 panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
34837 }
34838 }
34839 }
34840
34841 #[cfg(napi_runtime_os_threads)]
34842 #[test]
34843 fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
34844 use std::sync::mpsc;
34851
34852 struct GateWake {
34855 gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
34856 }
34857 impl std::task::Wake for GateWake {
34858 fn wake(self: Arc<Self>) {
34859 let gate = self.gate.lock().unwrap().take();
34860 if let Some(gate) = gate {
34861 let _ = gate.send(23);
34862 }
34863 }
34864 }
34865
34866 let (done_tx, done_rx) = mpsc::channel();
34867 let runner = std::thread::spawn(move || {
34868 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
34869 executor
34873 .active_drainers
34874 .store(executor.max_drainers, Ordering::SeqCst);
34875
34876 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
34879 let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
34880
34881 let hook_exec = Arc::clone(&executor);
34882 let hook_gate = Arc::clone(&gate_tx);
34883 let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
34886 let hook_sleep_slot = Arc::clone(&parked_sleep);
34887 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
34888 *slot.borrow_mut() = Some(Box::new(move || {
34889 let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
34890 let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
34891 let mut cx = Context::from_waker(&waker);
34892 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34893 *hook_sleep_slot.lock().unwrap() = Some(sleep);
34894 }));
34895 });
34896
34897 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
34900 let mut output = None;
34901 {
34902 let mut future = std::pin::pin!(async {
34903 output = Some(gate_rx.await.unwrap());
34904 });
34905 executor.block_on(future.as_mut());
34906 }
34907 DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
34913 assert_eq!(
34914 output,
34915 Some(23),
34916 "the edge-registered timer must have fired and released the gate"
34917 );
34918 executor.active_drainers.store(0, Ordering::SeqCst);
34919 drop(executor);
34922 let _ = done_tx.send(());
34923 });
34924
34925 match done_rx.recv_timeout(Duration::from_secs(10)) {
34926 Ok(()) => join_within(
34927 "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
34928 runner,
34929 Duration::from_secs(30),
34930 ),
34931 Err(error) => panic!(
34932 "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
34933 ),
34934 }
34935 }
34936
34937 #[cfg(panic = "unwind")]
34938 #[test]
34939 fn current_thread_sleep_without_host_driver_fails_loud() {
34940 let metrics = Arc::new(RuntimeMetrics::default());
34944 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34945 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
34946
34947 let payload = catch_unwind(AssertUnwindSafe(|| {
34948 let _sleep = make_sleep(
34949 &backend,
34950 &Arc::new(TimerDriverRegistry::default()),
34951 Instant::now() + Duration::from_millis(10),
34952 );
34953 }))
34954 .expect_err("sleep_until on CurrentThread without a driver must panic");
34955 let message = payload
34956 .downcast_ref::<String>()
34957 .cloned()
34958 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
34959 .expect("panic payload must be a message");
34960 assert!(
34961 message.contains("CurrentThread runtime has no live timer driver registered"),
34962 "the diagnostic must name the missing driver, got: {message}"
34963 );
34964 }
34965
34966 #[test]
34967 fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
34968 let registry = TimerDriverRegistry::default();
34972 let a = ManualStubDriver::new();
34973 let b = ManualStubDriver::new();
34974 let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
34975 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
34976 assert_ne!(a_id, b_id, "registration handles must be distinct");
34977 assert_eq!(
34978 registry.current().map(|(id, _)| id),
34979 Some(b_id),
34980 "newest live registrant serves"
34981 );
34982 assert!(registry.has_live_driver());
34983
34984 b.kill();
34987 assert_eq!(
34988 registry.current().map(|(id, _)| id),
34989 Some(a_id),
34990 "dead newest must be skipped"
34991 );
34992 b.live.store(true, Ordering::SeqCst);
34995 assert_eq!(
34996 registry.current().map(|(id, _)| id),
34997 Some(a_id),
34998 "swept entries stay gone"
34999 );
35000
35001 registry.unregister(a_id);
35004 assert!(registry.current().is_none());
35005 assert!(
35006 !registry.has_live_driver(),
35007 "an emptied registry must not report a live driver"
35008 );
35009 }
35010
35011 #[cfg(panic = "unwind")]
35012 #[test]
35013 fn timer_driver_registry_contains_callback_panics_and_falls_back() {
35014 let registry = TimerDriverRegistry::default();
35015 let fallback = ManualStubDriver::new();
35016 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35017 let panicking = Arc::new(PanickingTimerDriver {
35018 swept: AtomicBool::new(false),
35019 });
35020 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35021
35022 assert_eq!(
35023 registry.current().map(|(id, _)| id),
35024 Some(fallback_id),
35025 "a panicking liveness callback must be treated as a dead driver"
35026 );
35027 assert!(
35028 panicking.swept.load(Ordering::SeqCst),
35029 "a panicking liveness callback must still run its contained sweep hook"
35030 );
35031 assert!(
35032 registry.has_live_driver(),
35033 "the healthy fallback must remain selectable"
35034 );
35035 }
35036
35037 #[cfg(napi_runtime_os_threads)]
35038 #[test]
35039 fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
35040 use std::{sync::mpsc, time::Duration};
35041
35042 let registry = Arc::new(TimerDriverRegistry::default());
35043 let fallback = ManualStubDriver::new();
35044 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35045 let reentrant = Arc::new(ReentrantLivenessDriver {
35046 registry: Arc::downgrade(®istry),
35047 registration: Mutex::new(None),
35048 });
35049 let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35050 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
35051
35052 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
35053 let selection_registry = Arc::clone(®istry);
35054 let selection_thread = std::thread::spawn(move || {
35055 selection_tx
35056 .send(selection_registry.current().map(|(id, _)| id))
35057 .unwrap();
35058 });
35059 assert_eq!(
35060 selection_rx
35061 .recv_timeout(Duration::from_secs(10))
35062 .expect("is_live re-entry deadlocked"),
35063 Some(fallback_id)
35064 );
35065 selection_thread.join().unwrap();
35066
35067 let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
35068 registry: Arc::downgrade(®istry),
35069 fallback: fallback_id,
35070 });
35071 let drop_id = registry.register(Arc::clone(&drop_driver));
35072 drop(drop_driver);
35073
35074 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
35075 let drop_registry = Arc::clone(®istry);
35076 let drop_thread = std::thread::spawn(move || {
35077 drop_registry.unregister(drop_id);
35078 drop_tx.send(()).unwrap();
35079 });
35080 drop_rx
35081 .recv_timeout(Duration::from_secs(10))
35082 .expect("driver destructor re-entry deadlocked");
35083 drop_thread.join().unwrap();
35084 assert!(
35085 registry.current().is_none(),
35086 "the destructor must be able to unregister the fallback"
35087 );
35088 }
35089
35090 #[test]
35091 fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
35092 const DRIVER_PAIRS: usize = 2_048;
35093
35094 let registry = Arc::new(TimerDriverRegistry::default());
35095 let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
35096 let mut live = Vec::with_capacity(DRIVER_PAIRS);
35097 let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
35098 for _ in 0..DRIVER_PAIRS {
35099 let driver = Arc::new(ReentrantSweepTimerDriver {
35100 registry: Arc::downgrade(®istry),
35101 registration: Mutex::new(None),
35102 sweeps: AtomicUsize::new(0),
35103 removed_before_sweep: AtomicBool::new(false),
35104 });
35105 let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35106 *driver.registration.lock().unwrap() = Some(id);
35107 dead.push(driver);
35108
35109 let driver = ManualStubDriver::new();
35110 live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
35111 live.push(driver);
35112 }
35113 let dead_tail = Arc::new(ReentrantSweepTimerDriver {
35114 registry: Arc::downgrade(®istry),
35115 registration: Mutex::new(None),
35116 sweeps: AtomicUsize::new(0),
35117 removed_before_sweep: AtomicBool::new(false),
35118 });
35119 let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
35120 *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
35121 dead.push(dead_tail);
35122
35123 let selection = registry.live();
35124 assert_eq!(
35125 selection
35126 .as_slice()
35127 .iter()
35128 .map(|(id, _)| *id)
35129 .collect::<Vec<_>>(),
35130 live_ids,
35131 "sweeping every interleaved dead host must retain live registration order"
35132 );
35133 drop(selection);
35134 assert!(
35135 dead.iter().all(|driver| {
35136 driver.sweeps.load(Ordering::SeqCst) == 1
35137 && driver.removed_before_sweep.load(Ordering::SeqCst)
35138 && driver.registration.lock().unwrap().is_none()
35139 }),
35140 "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
35141 );
35142
35143 {
35144 let state = registry
35145 .state
35146 .lock()
35147 .unwrap_or_else(std::sync::PoisonError::into_inner);
35148 assert_eq!(state.entries.len(), DRIVER_PAIRS);
35149 assert_eq!(state.oldest, live_ids.first().copied());
35150 assert_eq!(state.newest, live_ids.last().copied());
35151 assert_eq!(
35152 state
35153 .ordered_snapshot()
35154 .iter()
35155 .map(|(id, _)| *id)
35156 .collect::<Vec<_>>(),
35157 live_ids,
35158 "keyed removals must preserve the linked order of every live host"
35159 );
35160 }
35161 let second_selection = registry.live();
35162 assert_eq!(
35163 second_selection
35164 .as_slice()
35165 .iter()
35166 .map(|(id, _)| *id)
35167 .collect::<Vec<_>>(),
35168 live_ids,
35169 "a second selection must retain the same live order"
35170 );
35171 drop(second_selection);
35172 assert!(
35173 dead
35174 .iter()
35175 .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
35176 "permanently swept hosts must not receive repeated eviction callbacks"
35177 );
35178 assert_eq!(
35179 registry.current().map(|(id, _)| id),
35180 live_ids.last().copied(),
35181 "newest-live compatibility selection must survive the mass sweep"
35182 );
35183
35184 drop(live);
35185 drop(dead);
35186 }
35187
35188 #[cfg(not(target_family = "wasm"))]
35189 #[test]
35190 fn timer_driver_registry_snapshot_contains_each_driver_drop() {
35191 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
35192
35193 if std::env::var_os(CHILD_ENV).is_some() {
35194 let registry = Arc::new(TimerDriverRegistry::default());
35195 let fallback = ManualStubDriver::new();
35196 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35197 let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
35198 registry: Arc::downgrade(®istry),
35199 registrations: Mutex::new(Vec::new()),
35200 });
35201 registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35202
35203 let first_cancels = Arc::new(AtomicUsize::new(0));
35204 let first_drops = Arc::new(AtomicUsize::new(0));
35205 let first = Arc::new(PanickingCancelDropTimerDriver {
35206 cancels: Arc::clone(&first_cancels),
35207 drops: Arc::clone(&first_drops),
35208 });
35209 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35210 drop(first);
35211
35212 let second_cancels = Arc::new(AtomicUsize::new(0));
35213 let second_drops = Arc::new(AtomicUsize::new(0));
35214 let second = Arc::new(PanickingCancelDropTimerDriver {
35215 cancels: Arc::clone(&second_cancels),
35216 drops: Arc::clone(&second_drops),
35217 });
35218 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35219 drop(second);
35220 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
35221
35222 assert_eq!(
35223 registry.current().map(|(id, _)| id),
35224 Some(fallback_id),
35225 "selection must retry after reentrant removals and preserve the healthy fallback"
35226 );
35227 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35228 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35229 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35230 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35231 assert_eq!(
35232 registry
35233 .state
35234 .lock()
35235 .unwrap_or_else(std::sync::PoisonError::into_inner)
35236 .entries
35237 .len(),
35238 1
35239 );
35240 return;
35241 }
35242
35243 let output = std::process::Command::new(std::env::current_exe().unwrap())
35244 .arg("--exact")
35245 .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
35246 .arg("--nocapture")
35247 .env(CHILD_ENV, "1")
35248 .output()
35249 .expect("the hostile timer-driver snapshot subprocess test must start");
35250 assert!(
35251 output.status.success(),
35252 "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35253 output.status.code(),
35254 String::from_utf8_lossy(&output.stdout),
35255 String::from_utf8_lossy(&output.stderr)
35256 );
35257 }
35258
35259 #[cfg(not(target_family = "wasm"))]
35260 #[test]
35261 fn timer_driver_registry_live_result_contains_each_driver_drop() {
35262 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
35263
35264 if std::env::var_os(CHILD_ENV).is_some() {
35265 let registry = TimerDriverRegistry::default();
35266
35267 let first_cancels = Arc::new(AtomicUsize::new(0));
35268 let first_drops = Arc::new(AtomicUsize::new(0));
35269 let first = Arc::new(PanickingCancelDropTimerDriver {
35270 cancels: Arc::clone(&first_cancels),
35271 drops: Arc::clone(&first_drops),
35272 });
35273 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35274 drop(first);
35275
35276 let second_cancels = Arc::new(AtomicUsize::new(0));
35277 let second_drops = Arc::new(AtomicUsize::new(0));
35278 let second = Arc::new(PanickingCancelDropTimerDriver {
35279 cancels: Arc::clone(&second_cancels),
35280 drops: Arc::clone(&second_drops),
35281 });
35282 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35283 drop(second);
35284
35285 let live = registry.live();
35286 assert_eq!(live.len(), 2);
35287 registry.unregister(first_id);
35288 registry.unregister(second_id);
35289 drop(live);
35290
35291 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35292 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35293 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35294 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35295 assert!(
35296 registry
35297 .state
35298 .lock()
35299 .unwrap_or_else(std::sync::PoisonError::into_inner)
35300 .entries
35301 .is_empty()
35302 );
35303 return;
35304 }
35305
35306 let output = std::process::Command::new(std::env::current_exe().unwrap())
35307 .arg("--exact")
35308 .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
35309 .arg("--nocapture")
35310 .env(CHILD_ENV, "1")
35311 .output()
35312 .expect("the hostile live timer-driver snapshot subprocess test must start");
35313 assert!(
35314 output.status.success(),
35315 "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35316 output.status.code(),
35317 String::from_utf8_lossy(&output.stdout),
35318 String::from_utf8_lossy(&output.stderr)
35319 );
35320 }
35321
35322 #[cfg(not(target_family = "wasm"))]
35323 #[test]
35324 fn timer_driver_registry_drop_contains_each_driver_destructor() {
35325 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
35326
35327 if std::env::var_os(CHILD_ENV).is_some() {
35328 let first_drops = Arc::new(AtomicUsize::new(0));
35329 let second_drops = Arc::new(AtomicUsize::new(0));
35330 let registry = TimerDriverRegistry::default();
35331 let first = Arc::new(PanickingCancelDropTimerDriver {
35332 cancels: Arc::new(AtomicUsize::new(0)),
35333 drops: Arc::clone(&first_drops),
35334 });
35335 let second = Arc::new(PanickingCancelDropTimerDriver {
35336 cancels: Arc::new(AtomicUsize::new(0)),
35337 drops: Arc::clone(&second_drops),
35338 });
35339 registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35340 registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35341 drop(first);
35342 drop(second);
35343
35344 drop(registry);
35345
35346 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35347 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35348 return;
35349 }
35350
35351 let output = std::process::Command::new(std::env::current_exe().unwrap())
35352 .arg("--exact")
35353 .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
35354 .arg("--nocapture")
35355 .env(CHILD_ENV, "1")
35356 .output()
35357 .expect("the hostile timer-driver registry drop subprocess test must start");
35358 assert!(
35359 output.status.success(),
35360 "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
35361 output.status.code(),
35362 String::from_utf8_lossy(&output.stdout),
35363 String::from_utf8_lossy(&output.stderr)
35364 );
35365 }
35366
35367 #[test]
35368 fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
35369 const DRIVERS: usize = 512;
35370
35371 let metrics = Arc::new(RuntimeMetrics::default());
35372 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35373 let backend =
35374 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35375 let registry = Arc::new(TimerDriverRegistry::default());
35376 let drivers = (0..DRIVERS)
35377 .map(|_| {
35378 let driver = ManualStubDriver::new();
35379 registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35380 driver
35381 })
35382 .collect::<Vec<_>>();
35383 let mut sleep = make_sleep(
35384 &backend,
35385 ®istry,
35386 Instant::now() + Duration::from_secs(60),
35387 );
35388 let mut cx = Context::from_waker(Waker::noop());
35389
35390 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35391 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35392 let timer_id = drivers[0].registers.lock().unwrap()[0].0;
35393 match &sleep.inner {
35394 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
35395 #[cfg(napi_runtime_os_threads)]
35396 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35397 }
35398 for driver in &drivers {
35399 assert_eq!(
35400 driver.registers.lock().unwrap().len(),
35401 2,
35402 "a re-poll must refresh each live driver exactly once"
35403 );
35404 }
35405
35406 for driver in drivers.iter().step_by(2) {
35407 driver.kill();
35408 }
35409 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35410 match &sleep.inner {
35411 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
35412 #[cfg(napi_runtime_os_threads)]
35413 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35414 }
35415 for (index, driver) in drivers.iter().enumerate() {
35416 let registers = driver.registers.lock().unwrap().len();
35417 let cancels = driver.cancels.lock().unwrap().clone();
35418 if index % 2 == 0 {
35419 assert_eq!(registers, 2);
35420 assert_eq!(cancels, vec![timer_id]);
35421 } else {
35422 assert_eq!(registers, 3);
35423 assert!(cancels.is_empty());
35424 }
35425 }
35426
35427 drop(sleep);
35428 for driver in drivers {
35429 assert_eq!(
35430 driver.cancels.lock().unwrap().as_slice(),
35431 &[timer_id],
35432 "every physical arm must be cancelled exactly once"
35433 );
35434 }
35435 assert_eq!(registry.pending_len(), 0);
35436 assert!(!executor.host_timers.has_pending());
35437 }
35438
35439 #[cfg(panic = "unwind")]
35440 #[test]
35441 fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
35442 let metrics = Arc::new(RuntimeMetrics::default());
35443 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35444 let backend =
35445 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35446 let healthy = ManualStubDriver::new();
35447 let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
35448 let deadline = Instant::now() + Duration::from_secs(60);
35449 let mut sleep = make_sleep(&backend, ®istry, deadline);
35450 let mut cx = Context::from_waker(Waker::noop());
35451
35452 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35453 let timer_id = healthy.registers.lock().unwrap()[0].0;
35454
35455 let panicking = PanickingRegisterTimerDriver::new();
35456 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35457 let result = catch_unwind(AssertUnwindSafe(|| {
35458 let _ = Pin::new(&mut sleep).poll(&mut cx);
35459 }));
35460 assert!(
35461 result.is_err(),
35462 "the hostile register panic must retain its normal propagation"
35463 );
35464 assert_eq!(
35465 healthy.registers.lock().unwrap().len(),
35466 2,
35467 "the previously armed driver must have completed its refresh before the later panic"
35468 );
35469 assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
35470 assert!(
35471 panicking.pending.lock().unwrap().contains_key(&timer_id),
35472 "the hostile driver must model an arm committed before it panics"
35473 );
35474
35475 drop(sleep);
35476 assert_eq!(
35477 healthy.cancels.lock().unwrap().as_slice(),
35478 &[timer_id],
35479 "unwind must preserve cancellation ownership for the previously armed driver"
35480 );
35481 assert_eq!(
35482 panicking.cancels.lock().unwrap().as_slice(),
35483 &[timer_id],
35484 "unwind must preserve cancellation ownership for the driver that armed and then panicked"
35485 );
35486 assert!(panicking.pending.lock().unwrap().is_empty());
35487 assert_eq!(registry.pending_len(), 0);
35488 assert!(!executor.host_timers.has_pending());
35489 }
35490
35491 #[cfg(not(target_family = "wasm"))]
35492 #[test]
35493 fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
35494 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
35495
35496 if std::env::var_os(CHILD_ENV).is_some() {
35497 let metrics = Arc::new(RuntimeMetrics::default());
35498 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35499 let backend =
35500 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35501 let registry = Arc::new(TimerDriverRegistry::default());
35502 let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
35503 registry: Arc::downgrade(®istry),
35504 later: Mutex::new(None),
35505 registers: AtomicUsize::new(0),
35506 });
35507 registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
35508
35509 let later_cancels = Arc::new(AtomicUsize::new(0));
35510 let later_drops = Arc::new(AtomicUsize::new(0));
35511 let later = Arc::new(PanickingCancelDropTimerDriver {
35512 cancels: Arc::clone(&later_cancels),
35513 drops: Arc::clone(&later_drops),
35514 });
35515 let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
35516 *earlier.later.lock().unwrap() = Some(later_id);
35517 drop(later);
35518
35519 let mut sleep = make_sleep(
35520 &backend,
35521 ®istry,
35522 Instant::now() + Duration::from_secs(60),
35523 );
35524 let mut cx = Context::from_waker(Waker::noop());
35525 let result = catch_unwind(AssertUnwindSafe(|| {
35526 let _ = Pin::new(&mut sleep).poll(&mut cx);
35527 }));
35528
35529 assert!(
35530 result.is_err(),
35531 "the earlier driver's register panic must still propagate"
35532 );
35533 assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
35534 assert_eq!(
35535 later_drops.load(Ordering::SeqCst),
35536 0,
35537 "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
35538 );
35539
35540 drop(sleep);
35541 assert_eq!(
35542 later_cancels.load(Ordering::SeqCst),
35543 1,
35544 "the later driver must remain owned for cancellation despite its registry removal"
35545 );
35546 assert_eq!(
35547 later_drops.load(Ordering::SeqCst),
35548 1,
35549 "the later driver's final destructor must run once behind containment"
35550 );
35551 assert_eq!(registry.pending_len(), 0);
35552 assert!(!executor.host_timers.has_pending());
35553 return;
35554 }
35555
35556 let output = std::process::Command::new(std::env::current_exe().unwrap())
35557 .arg("--exact")
35558 .arg(
35559 "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
35560 )
35561 .arg("--nocapture")
35562 .env(CHILD_ENV, "1")
35563 .output()
35564 .expect("the hostile host timer snapshot subprocess test must start");
35565 assert!(
35566 output.status.success(),
35567 "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
35568 output.status.code(),
35569 String::from_utf8_lossy(&output.stdout),
35570 String::from_utf8_lossy(&output.stderr)
35571 );
35572 }
35573
35574 #[cfg(not(target_family = "wasm"))]
35575 #[test]
35576 fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
35577 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
35578
35579 if std::env::var_os(CHILD_ENV).is_some() {
35580 let metrics = Arc::new(RuntimeMetrics::default());
35581 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35582 let backend =
35583 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35584 let cancels = Arc::new(AtomicUsize::new(0));
35585 let drops = Arc::new(AtomicUsize::new(0));
35586 let hostile = Arc::new(PanickingCancelDropTimerDriver {
35587 cancels: Arc::clone(&cancels),
35588 drops: Arc::clone(&drops),
35589 });
35590 let healthy = ManualStubDriver::new();
35591 let registry = Arc::new(TimerDriverRegistry::default());
35592 let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
35593 registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
35594 let mut sleep = make_sleep(
35595 &backend,
35596 ®istry,
35597 Instant::now() + Duration::from_secs(60),
35598 );
35599 let mut cx = Context::from_waker(Waker::noop());
35600
35601 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35602 let timer_id = healthy.registers.lock().unwrap()[0].0;
35603
35604 registry.unregister(hostile_id);
35607 drop(hostile);
35608 drop(sleep);
35609 assert_eq!(cancels.load(Ordering::SeqCst), 1);
35610 assert_eq!(drops.load(Ordering::SeqCst), 1);
35611 assert_eq!(
35612 healthy.cancels.lock().unwrap().as_slice(),
35613 &[timer_id],
35614 "one hostile cancellation must not skip later armed drivers"
35615 );
35616 assert_eq!(registry.pending_len(), 0);
35617 assert!(!executor.host_timers.has_pending());
35618 return;
35619 }
35620
35621 let output = std::process::Command::new(std::env::current_exe().unwrap())
35622 .arg("--exact")
35623 .arg(
35624 "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
35625 )
35626 .arg("--nocapture")
35627 .env(CHILD_ENV, "1")
35628 .output()
35629 .expect("the hostile timer-driver cancellation subprocess test must start");
35630 assert!(
35631 output.status.success(),
35632 "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
35633 output.status.code(),
35634 String::from_utf8_lossy(&output.stdout),
35635 String::from_utf8_lossy(&output.stderr)
35636 );
35637 }
35638
35639 #[cfg(not(target_family = "wasm"))]
35640 #[test]
35641 fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
35642 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
35643
35644 if std::env::var_os(CHILD_ENV).is_some() {
35645 let metrics = Arc::new(RuntimeMetrics::default());
35646 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35647 let backend =
35648 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35649 let driver = ManualStubDriver::new();
35650 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35651 let state = Arc::new(TestHostileRawWakerState {
35652 clones: AtomicUsize::new(0),
35653 drop_panics: AtomicUsize::new(0),
35654 panic_on_clone: Some(2),
35655 });
35656
35657 let result = catch_unwind(AssertUnwindSafe(|| {
35658 let mut sleep = make_sleep(
35659 &backend,
35660 ®istry,
35661 Instant::now() + Duration::from_secs(60),
35662 );
35663 let waker = test_hostile_waker(Arc::clone(&state));
35664 let mut cx = Context::from_waker(&waker);
35665 let _ = Pin::new(&mut sleep).poll(&mut cx);
35666 }));
35667 assert!(
35668 result.is_err(),
35669 "the original RawWaker clone panic must still propagate"
35670 );
35671 assert_eq!(state.clones.load(Ordering::SeqCst), 2);
35672 assert_eq!(
35673 state.drop_panics.load(Ordering::SeqCst),
35674 1,
35675 "the first clone must be destroyed once behind a contained boundary"
35676 );
35677 assert!(driver.registers.lock().unwrap().is_empty());
35678 assert_eq!(registry.pending_len(), 0);
35679 assert!(!executor.host_timers.has_pending());
35680 return;
35681 }
35682
35683 let output = std::process::Command::new(std::env::current_exe().unwrap())
35684 .arg("--exact")
35685 .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
35686 .arg("--nocapture")
35687 .env(CHILD_ENV, "1")
35688 .output()
35689 .expect("the hostile RawWaker subprocess test must start");
35690 assert!(
35691 output.status.success(),
35692 "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
35693 output.status.code(),
35694 String::from_utf8_lossy(&output.stdout),
35695 String::from_utf8_lossy(&output.stderr)
35696 );
35697 }
35698
35699 #[cfg(not(target_family = "wasm"))]
35700 #[test]
35701 fn host_sleep_register_unwind_contains_unretained_waker_drop() {
35702 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
35703
35704 if std::env::var_os(CHILD_ENV).is_some() {
35705 let metrics = Arc::new(RuntimeMetrics::default());
35706 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35707 let backend =
35708 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35709 let driver = Arc::new(PanicBeforeRetainingTimerDriver {
35710 registers: AtomicUsize::new(0),
35711 });
35712 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35713 let state = Arc::new(TestHostileRawWakerState {
35714 clones: AtomicUsize::new(0),
35715 drop_panics: AtomicUsize::new(0),
35716 panic_on_clone: None,
35717 });
35718 let mut sleep = make_sleep(
35719 &backend,
35720 ®istry,
35721 Instant::now() + Duration::from_secs(60),
35722 );
35723 let waker = test_hostile_waker(Arc::clone(&state));
35724 let mut cx = Context::from_waker(&waker);
35725
35726 let result = catch_unwind(AssertUnwindSafe(|| {
35727 let _ = Pin::new(&mut sleep).poll(&mut cx);
35728 }));
35729 assert!(
35730 result.is_err(),
35731 "the timer-driver register panic must still propagate"
35732 );
35733 assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
35734 assert_eq!(state.clones.load(Ordering::SeqCst), 3);
35735 assert_eq!(
35736 state.drop_panics.load(Ordering::SeqCst),
35737 1,
35738 "the unretained register argument must be destroyed once behind the proxy boundary"
35739 );
35740 assert_eq!(registry.pending_len(), 1);
35741 assert!(executor.host_timers.has_pending());
35742
35743 drop(sleep);
35744 assert_eq!(
35745 state.drop_panics.load(Ordering::SeqCst),
35746 3,
35747 "repoll and registry wakers must retain their existing contained cleanup"
35748 );
35749 assert_eq!(registry.pending_len(), 0);
35750 assert!(!executor.host_timers.has_pending());
35751 return;
35752 }
35753
35754 let output = std::process::Command::new(std::env::current_exe().unwrap())
35755 .arg("--exact")
35756 .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
35757 .arg("--nocapture")
35758 .env(CHILD_ENV, "1")
35759 .output()
35760 .expect("the hostile register-waker subprocess test must start");
35761 assert!(
35762 output.status.success(),
35763 "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
35764 output.status.code(),
35765 String::from_utf8_lossy(&output.stdout),
35766 String::from_utf8_lossy(&output.stderr)
35767 );
35768 }
35769
35770 #[test]
35771 fn host_sleep_rearms_on_next_live_driver_after_eviction() {
35772 let metrics = Arc::new(RuntimeMetrics::default());
35778 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35779 let backend =
35780 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35781
35782 let a = ManualStubDriver::new();
35783 let b = ManualStubDriver::new();
35784 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
35785 let deadline = Instant::now() + Duration::from_secs(60);
35786
35787 let (timer_id, b_id) = {
35788 let mut sleep = std::pin::pin!(make_sleep(&backend, ®istry, deadline));
35789 let mut cx = Context::from_waker(Waker::noop());
35790
35791 assert!(sleep.as_mut().poll(&mut cx).is_pending());
35793 let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
35794 assert_eq!(armed_deadline, deadline);
35795 assert!(
35796 executor.host_timers.has_pending(),
35797 "first poll must arm the wake-token"
35798 );
35799
35800 a.kill();
35804 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
35805 assert!(sleep.as_mut().poll(&mut cx).is_pending());
35806 assert_eq!(
35807 b.registers.lock().unwrap().as_slice(),
35808 &[(timer_id, deadline)],
35809 "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
35810 );
35811 assert_eq!(
35812 a.cancels.lock().unwrap().as_slice(),
35813 &[timer_id],
35814 "the dead driver gets a best-effort cancel for the old arm"
35815 );
35816 assert_eq!(
35817 registry.current().map(|(id, _)| id),
35818 Some(b_id),
35819 "the dead driver was evicted"
35820 );
35821 assert!(
35822 executor.host_timers.has_pending(),
35823 "the wake-token survives the re-arm (still exactly this sleep's entry)"
35824 );
35825 (timer_id, b_id)
35826 };
35827
35828 let _ = b_id;
35831 assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
35832 assert!(
35833 !executor.host_timers.has_pending(),
35834 "drop must retire the wake-token"
35835 );
35836 }
35837
35838 #[cfg(napi_runtime_os_threads)]
35839 #[test]
35840 fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
35841 let metrics = Arc::new(RuntimeMetrics::default());
35842 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35843 let backend =
35844 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35845 let responsive = Arc::new(StubHostTimerDriver::new());
35846 let starved = ManualStubDriver::new();
35847 let registry = Arc::new(TimerDriverRegistry::default());
35848 registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
35849 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
35852
35853 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
35854 let waker = Waker::from(Arc::clone(&wake_count));
35855 let mut cx = Context::from_waker(&waker);
35856 let deadline = Instant::now() + Duration::from_millis(500);
35861 let mut sleep = make_sleep(&backend, ®istry, deadline);
35862 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35863
35864 let responsive_timer = *responsive
35865 .timers
35866 .lock()
35867 .unwrap()
35868 .keys()
35869 .next()
35870 .expect("responsive timer must be armed");
35871 assert_eq!(
35872 starved.registers.lock().unwrap().as_slice(),
35873 &[(responsive_timer, deadline)],
35874 "every live host must receive the same timer identity and absolute deadline"
35875 );
35876
35877 let fire_bound = Instant::now() + Duration::from_secs(30);
35884 while wake_count.0.load(Ordering::SeqCst) == 0 {
35885 assert!(
35886 Instant::now() < fire_bound,
35887 "the responsive host must wake a sleep whose newest host is live but starved"
35888 );
35889 std::thread::sleep(Duration::from_millis(10));
35890 }
35891 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
35894 assert_eq!(
35895 starved.cancels.lock().unwrap().as_slice(),
35896 &[responsive_timer],
35897 "completion through the winning host must cancel the starved losing arm"
35898 );
35899 }
35900
35901 #[cfg(napi_runtime_os_threads)]
35902 #[test]
35903 fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
35904 use std::sync::mpsc;
35905
35906 struct ReentrantTimerRegistrationWake {
35907 registry: Weak<TimerDriverRegistry>,
35908 wakes: AtomicUsize,
35909 reentered: AtomicBool,
35910 }
35911
35912 impl std::task::Wake for ReentrantTimerRegistrationWake {
35913 fn wake(self: Arc<Self>) {
35914 self.wakes.fetch_add(1, Ordering::SeqCst);
35915 if let Some(registry) = self.registry.upgrade() {
35916 let _ = registry.has_live_driver();
35917 self.reentered.store(true, Ordering::SeqCst);
35918 }
35919 }
35920 }
35921
35922 let metrics = Arc::new(RuntimeMetrics::default());
35923 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35924 let backend =
35925 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35926 let registry = Arc::new(TimerDriverRegistry::default());
35927 let starved = ManualStubDriver::new();
35928 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
35929
35930 let deadline = Instant::now() + Duration::from_secs(2);
35937 let mut first = make_sleep(&backend, ®istry, deadline);
35938 let mut second = make_sleep(&backend, ®istry, deadline);
35939 let first_wake = Arc::new(ReentrantTimerRegistrationWake {
35940 registry: Arc::downgrade(®istry),
35941 wakes: AtomicUsize::new(0),
35942 reentered: AtomicBool::new(false),
35943 });
35944 let second_wake = Arc::new(ReentrantTimerRegistrationWake {
35945 registry: Arc::downgrade(®istry),
35946 wakes: AtomicUsize::new(0),
35947 reentered: AtomicBool::new(false),
35948 });
35949 let first_waker = Waker::from(Arc::clone(&first_wake));
35950 let second_waker = Waker::from(Arc::clone(&second_wake));
35951 let mut first_cx = Context::from_waker(&first_waker);
35952 let mut second_cx = Context::from_waker(&second_waker);
35953
35954 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
35955 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
35956 let initial = starved.registers.lock().unwrap().clone();
35957 let first_id = initial[0].0;
35958 let second_id = initial[1].0;
35959 assert_ne!(first_id, second_id);
35960 assert_eq!(
35961 registry.pending_len(),
35962 2,
35963 "one stable repoll entry is retained per sleep"
35964 );
35965
35966 let responsive = Arc::new(StubHostTimerDriver::new());
35967 let (registered_tx, registered_rx) = mpsc::sync_channel(1);
35968 let registration_registry = Arc::clone(®istry);
35969 let registration_driver = Arc::clone(&responsive);
35970 let registration = std::thread::spawn(move || {
35971 let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
35972 registered_tx.send(id).unwrap();
35973 });
35974 registered_rx
35978 .recv_timeout(Duration::from_secs(10))
35979 .expect("late driver registration or repoll callback re-entry deadlocked");
35980 registration.join().unwrap();
35981
35982 assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
35983 assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
35984 assert!(first_wake.reentered.load(Ordering::SeqCst));
35985 assert!(second_wake.reentered.load(Ordering::SeqCst));
35986 assert!(
35987 responsive.timers.lock().unwrap().is_empty(),
35988 "registration requests bounded re-polls instead of invoking driver callbacks inline"
35989 );
35990
35991 assert!(
35996 Instant::now() < deadline,
35997 "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
35998 this runner is starved beyond the margin this test budgets for"
35999 );
36000 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36001 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36002 let responsive_ids = responsive
36003 .timers
36004 .lock()
36005 .unwrap()
36006 .keys()
36007 .copied()
36008 .collect::<FxHashSet<_>>();
36009 assert_eq!(
36010 responsive_ids,
36011 FxHashSet::from_iter([first_id, second_id]),
36012 "every pending sleep must re-arm its existing identity on the new driver"
36013 );
36014 assert_eq!(
36015 starved
36016 .registers
36017 .lock()
36018 .unwrap()
36019 .iter()
36020 .filter(|(id, _)| *id == first_id)
36021 .count(),
36022 2,
36023 "the old host receives only the normal waker refresh on the one re-poll"
36024 );
36025 assert_eq!(
36026 starved
36027 .registers
36028 .lock()
36029 .unwrap()
36030 .iter()
36031 .filter(|(id, _)| *id == second_id)
36032 .count(),
36033 2,
36034 "the old host receives only the normal waker refresh on the one re-poll"
36035 );
36036 assert_eq!(
36037 registry.pending_len(),
36038 2,
36039 "late registration must not duplicate pending state"
36040 );
36041
36042 let fire_bound = Instant::now() + Duration::from_secs(30);
36049 while first_wake.wakes.load(Ordering::SeqCst) < 2
36050 || second_wake.wakes.load(Ordering::SeqCst) < 2
36051 {
36052 assert!(
36053 Instant::now() < fire_bound,
36054 "the late responsive host must fire both timers while the original host remains starved"
36055 );
36056 std::thread::sleep(Duration::from_millis(10));
36057 }
36058 assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
36059 assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
36060
36061 let mut cancelled = starved.cancels.lock().unwrap().clone();
36062 cancelled.sort_unstable();
36063 let mut expected = vec![first_id, second_id];
36064 expected.sort_unstable();
36065 assert_eq!(
36066 cancelled, expected,
36067 "completion must cancel both losing starved-host arms"
36068 );
36069 assert_eq!(
36070 registry.pending_len(),
36071 0,
36072 "completion retires every repoll entry"
36073 );
36074 assert!(!executor.host_timers.has_pending());
36075 }
36076
36077 #[cfg(panic = "unwind")]
36078 #[test]
36079 fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
36080 let metrics = Arc::new(RuntimeMetrics::default());
36084 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36085 let backend =
36086 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36087
36088 let a = ManualStubDriver::new();
36089 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36090 let mut sleep = std::pin::pin!(make_sleep(
36091 &backend,
36092 ®istry,
36093 Instant::now() + Duration::from_secs(60)
36094 ));
36095 let mut cx = Context::from_waker(Waker::noop());
36096 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36097
36098 a.kill();
36099 let payload = catch_unwind(AssertUnwindSafe(|| {
36100 let _ = sleep.as_mut().poll(&mut cx);
36101 }))
36102 .expect_err("a sleep with every driver dead must panic, not pend silently");
36103 let message = payload
36104 .downcast_ref::<String>()
36105 .cloned()
36106 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
36107 .expect("panic payload must be a message");
36108 assert!(
36109 message.contains("lost every live timer driver mid-sleep"),
36110 "the diagnostic must name the mid-flight driver loss, got: {message}"
36111 );
36112 }
36113
36114 #[test]
36115 fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
36116 let metrics = Arc::new(RuntimeMetrics::default());
36123 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36124 let backend =
36125 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36126
36127 let a = ManualStubDriver::new();
36128 let b = ManualStubDriver::new();
36129 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36130 let mut sleep = std::pin::pin!(make_sleep(
36131 &backend,
36132 ®istry,
36133 Instant::now() + Duration::from_secs(60)
36134 ));
36135
36136 let flag = Arc::new(WakeFlag::default());
36138 let waker = Waker::from(Arc::clone(&flag));
36139 let mut cx = Context::from_waker(&waker);
36140 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36141 assert!(!flag.0.load(Ordering::SeqCst));
36142
36143 a.kill();
36146 registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36147 let _ = registry.has_live_driver();
36148
36149 assert!(
36150 a.swept.load(Ordering::SeqCst),
36151 "the sweep must notify the swept driver"
36152 );
36153 assert!(
36154 flag.0.load(Ordering::SeqCst),
36155 "a sleep armed on the swept driver must be woken into re-selection, not stranded"
36156 );
36157
36158 assert!(sleep.as_mut().poll(&mut cx).is_pending());
36160 assert_eq!(
36161 b.registers.lock().unwrap().len(),
36162 1,
36163 "the re-poll must re-arm on the live driver"
36164 );
36165 }
36166
36167 #[cfg(napi_runtime_os_threads)]
36168 #[test]
36169 fn current_thread_sleep_fires_through_stub_host_driver() {
36170 use std::sync::mpsc;
36173
36174 let (done_tx, done_rx) = mpsc::channel();
36175 let runner = std::thread::spawn(move || {
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<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36181
36182 let started = Instant::now();
36183 let mut future = std::pin::pin!(async {
36184 make_sleep(
36185 &backend,
36186 ®istry_with(driver),
36187 Instant::now() + Duration::from_millis(60),
36188 )
36189 .await;
36190 });
36191 executor.block_on(future.as_mut());
36192 assert!(started.elapsed() >= Duration::from_millis(60));
36193 assert!(
36194 !executor.host_timers.has_pending(),
36195 "a completed sleep must retire its pending-registry entry"
36196 );
36197 let _ = done_tx.send(());
36198 });
36199
36200 match done_rx.recv_timeout(Duration::from_secs(10)) {
36201 Ok(()) => runner.join().unwrap(),
36202 Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
36203 }
36204 }
36205
36206 #[test]
36207 fn current_thread_timer_shutdown_retires_pending_repoll_state() {
36208 let metrics = Arc::new(RuntimeMetrics::default());
36209 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36210 let backend =
36211 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36212 let drivers = Arc::new(TimerDriverRegistry::default());
36213 let driver = ManualStubDriver::new();
36214 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36215 let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
36216 let mut cx = Context::from_waker(Waker::noop());
36217
36218 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36219 let timer_id = driver.registers.lock().unwrap()[0].0;
36220 assert_eq!(drivers.pending_len(), 1);
36221
36222 executor.host_timers.shutdown();
36223 assert_eq!(
36224 drivers.pending_len(),
36225 0,
36226 "shutdown must retire the registration-repoll index before the Sleep is dropped"
36227 );
36228 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36229 assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
36230 }
36231
36232 #[cfg(napi_runtime_os_threads)]
36233 #[test]
36234 fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
36235 use std::sync::mpsc;
36236
36237 let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
36238 let backend = controller.backend();
36239 let drivers = Arc::new(TimerDriverRegistry::default());
36240 let driver = ManualStubDriver::new();
36241 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36242 let sleep = make_sleep(
36243 &backend,
36244 &drivers,
36245 Instant::now() + Duration::from_secs(120),
36246 );
36247 drop(backend);
36248
36249 let (polled_tx, polled_rx) = mpsc::channel();
36250 let block_controller = Arc::clone(&controller);
36251 let blocker = std::thread::spawn(move || {
36252 let mut announced = false;
36253 let mut sleep = std::pin::pin!(sleep);
36254 let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
36255 let poll = sleep.as_mut().poll(cx);
36256 if !announced {
36257 announced = true;
36258 polled_tx.send(()).unwrap();
36259 }
36260 poll
36261 }));
36262 block_controller.block_on(future.as_mut());
36263 });
36264 polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
36265
36266 let (shutdown_tx, shutdown_rx) = mpsc::channel();
36267 let shutdown_controller = Arc::clone(&controller);
36268 let shutdown = std::thread::spawn(move || {
36269 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
36270 });
36271 shutdown_rx
36272 .recv_timeout(Duration::from_secs(2))
36273 .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
36274 .unwrap();
36275 shutdown.join().unwrap();
36276 blocker.join().unwrap();
36277 assert_eq!(driver.cancels.lock().unwrap().len(), 1);
36278 }
36279
36280 #[cfg(panic = "unwind")]
36281 #[test]
36282 fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
36283 use std::sync::mpsc;
36294
36295 let (done_tx, done_rx) = mpsc::channel();
36296 let runner = std::thread::spawn(move || {
36297 let metrics = Arc::new(RuntimeMetrics::default());
36298 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36302 let backend =
36303 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36304 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36305
36306 let payload = catch_unwind(AssertUnwindSafe(|| {
36307 let mut future = std::pin::pin!(async {
36308 make_sleep(
36309 &backend,
36310 ®istry_with(driver),
36311 Instant::now() + Duration::from_millis(60),
36312 )
36313 .await;
36314 });
36315 executor.block_on(future.as_mut());
36316 }))
36317 .expect_err("a threadless park must self-detect even while a host timer is pending");
36318 let diagnostic = payload
36319 .downcast_ref::<BlockOnDeadlock>()
36320 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36321 assert_eq!(
36322 diagnostic.kind,
36323 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
36324 );
36325 assert_eq!(
36326 diagnostic.park_deadline, None,
36327 "the certain case carries no deadline"
36328 );
36329 let message = diagnostic.to_string();
36330 assert!(
36331 message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
36332 "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
36333 );
36334 let _ = done_tx.send(());
36335 });
36336
36337 match done_rx.recv_timeout(Duration::from_secs(10)) {
36338 Ok(()) => runner.join().unwrap(),
36339 Err(error) => panic!(
36340 "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})"
36341 ),
36342 }
36343 }
36344
36345 #[cfg(panic = "unwind")]
36346 #[test]
36347 fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
36348 use std::sync::mpsc;
36357
36358 let (done_tx, done_rx) = mpsc::channel();
36359 let runner = std::thread::spawn(move || {
36360 let metrics = Arc::new(RuntimeMetrics::default());
36361 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36362 let backend =
36363 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36364 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36365
36366 let _unpolled = make_sleep(
36369 &backend,
36370 ®istry_with(driver),
36371 Instant::now() + Duration::from_secs(60),
36372 );
36373
36374 let payload = catch_unwind(AssertUnwindSafe(|| {
36375 let mut future = std::pin::pin!(NeverReady);
36376 executor.block_on(future.as_mut());
36377 }))
36378 .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
36379 let diagnostic = payload
36380 .downcast_ref::<BlockOnDeadlock>()
36381 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36382 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
36383 let _ = done_tx.send(());
36384 });
36385
36386 match done_rx.recv_timeout(Duration::from_secs(10)) {
36387 Ok(()) => runner.join().unwrap(),
36388 Err(error) => panic!(
36389 "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
36390 ),
36391 }
36392 }
36393
36394 #[cfg(napi_runtime_os_threads)]
36395 #[test]
36396 fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
36397 use std::sync::mpsc;
36402
36403 let (done_tx, done_rx) = mpsc::channel();
36404 let runner = std::thread::spawn(move || {
36405 let metrics = Arc::new(RuntimeMetrics::default());
36406 let executor = Arc::new(CurrentThreadExecutor::with_detection(
36418 metrics,
36419 false,
36420 Some(Duration::from_secs(2)),
36421 ));
36422 let backend =
36423 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36424 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36425
36426 let started = Instant::now();
36427 let mut future = std::pin::pin!(async {
36428 make_sleep(
36429 &backend,
36430 ®istry_with(driver),
36431 Instant::now() + Duration::from_millis(200),
36432 )
36433 .await;
36434 });
36435 executor.block_on(future.as_mut());
36436 assert!(started.elapsed() >= Duration::from_millis(200));
36437 let _ = done_tx.send(());
36438 });
36439
36440 match done_rx.recv_timeout(Duration::from_secs(30)) {
36441 Ok(()) => runner.join().unwrap(),
36442 Err(error) => {
36443 panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
36444 }
36445 }
36446 }
36447
36448 #[test]
36449 fn host_timer_unrepresentable_grace_remains_live() {
36450 let registry = HostTimerRegistry::default();
36451 let fired = Arc::new(AtomicBool::new(false));
36452 let drivers = Arc::new(TimerDriverRegistry::default());
36453 let repoll = Arc::new(HostTimerRepoll::default());
36454 assert!(
36455 Instant::now().checked_add(Duration::MAX).is_none(),
36456 "Duration::MAX must exercise the unrepresentable-grace path"
36457 );
36458 assert!(
36459 registry.register(
36460 1,
36461 Instant::now(),
36462 futures::task::noop_waker(),
36463 &fired,
36464 &drivers,
36465 &repoll,
36466 ),
36467 "the open registry must accept the timer"
36468 );
36469
36470 assert!(
36471 registry.has_live_wait(Duration::MAX),
36472 "an unrepresentable grace deadline is effectively unbounded"
36473 );
36474 registry.remove(1);
36475 }
36476
36477 #[cfg(napi_runtime_os_threads)]
36480 #[test]
36481 fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
36482 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
36487 let expected = diagnostic.to_string();
36488 let payload: Box<dyn Any + Send> = Box::new(diagnostic);
36489 assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
36490 }
36491 #[cfg(napi_runtime_wasi_threads)]
36499 #[test]
36500 fn wasi_threads_reports_a_thread_capable_worker_ceiling() {
36501 let ceiling = crate::max_async_runtime_worker_threads();
36505 assert_eq!(
36506 ceiling, 255,
36507 "the threaded WASI target must report Rayon's realizable ceiling"
36508 );
36509 assert!(
36510 ceiling >= 2,
36511 "a ceiling below two would make MultiThread unconfigurable"
36512 );
36513 }
36514
36515 #[cfg(napi_runtime_wasi_threads)]
36516 #[test]
36517 fn wasi_threads_default_flavor_is_still_current_thread() {
36518 assert_eq!(
36522 RuntimeOptions::default().flavor,
36523 RuntimeFlavor::CurrentThread,
36524 "the threaded WASI target must not silently default to MultiThread"
36525 );
36526 }
36527
36528 #[cfg(napi_runtime_wasi_threads)]
36529 #[test]
36530 fn wasi_threads_multi_thread_validates_to_the_two_worker_minimum() {
36531 let validated = RuntimeOptions {
36537 flavor: RuntimeFlavor::MultiThread,
36538 ..RuntimeOptions::default()
36539 }
36540 .validate()
36541 .expect("MultiThread must validate on the threaded WASI target");
36542 assert_eq!(validated.worker_threads, 2);
36543 assert_eq!(validated.max_blocking_tasks, 1);
36544 }
36545
36546 #[cfg(napi_runtime_wasi_threads)]
36547 #[test]
36548 fn threadless_build_is_false_on_the_threads_target() {
36549 assert!(
36554 !THREADLESS_BUILD,
36555 "the threaded WASI target has real OS threads"
36556 );
36557 assert!(
36558 cfg!(napi_runtime_os_threads),
36559 "the threaded WASI target must build the OS-thread machinery"
36560 );
36561 }
36562
36563 #[cfg(not(napi_runtime_os_threads))]
36567 #[test]
36568 fn threadless_wasm_still_rejects_multi_thread() {
36569 let error = RuntimeOptions {
36570 flavor: RuntimeFlavor::MultiThread,
36571 ..RuntimeOptions::default()
36572 }
36573 .validate()
36574 .expect_err("threadless WebAssembly cannot run the multi-thread runtime");
36575 let message = error.to_string();
36576 assert!(
36577 message.contains("threadless"),
36578 "the rejection must name the property that causes it, not the family: {message}"
36579 );
36580 assert!(
36581 message.contains("wasm32-wasip1-threads"),
36582 "the rejection must name the target that does work: {message}"
36583 );
36584 }
36585
36586 #[cfg(napi_runtime_os_threads)]
36587 #[test]
36588 fn multi_thread_pool_starts_and_joins_every_worker() {
36589 let options = RuntimeOptions {
36593 flavor: RuntimeFlavor::MultiThread,
36594 worker_threads: 3,
36595 max_blocking_tasks: 2,
36596 thread_name_prefix: "rd-os-threads-join".to_string(),
36597 park_deadline: None,
36598 drain_linger: DEFAULT_DRAIN_LINGER,
36599 };
36600 let executor = Arc::new(
36601 MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default()))
36602 .expect("a three-worker pool must build"),
36603 );
36604 assert_eq!(executor.pool.current_num_threads(), 3);
36605
36606 let ran = Arc::new(AtomicBool::new(false));
36607 let task_ran = Arc::clone(&ran);
36608 let (runnable, task) = async_task::spawn(
36609 async move {
36610 task_ran.store(true, Ordering::SeqCst);
36611 },
36612 |_| {},
36613 );
36614 executor.schedule(runnable);
36615 futures::executor::block_on(task);
36616 assert!(
36617 ran.load(Ordering::SeqCst),
36618 "the pool must run scheduled work"
36619 );
36620
36621 let lifecycle = Arc::clone(&executor.worker_lifecycle);
36622 executor.begin_shutdown();
36623 executor.wait_until_scheduler_idle();
36624 let deadline = Instant::now() + Duration::from_secs(10);
36630 while Arc::strong_count(&executor) > 1 && Instant::now() < deadline {
36631 std::thread::yield_now();
36632 }
36633 let executor = Arc::into_inner(executor)
36634 .expect("no drainer may outlive `wait_until_scheduler_idle` for long");
36635 drop(executor);
36636 lifecycle.wait_for_all_workers();
36637 assert_eq!(
36638 lifecycle.remaining(),
36639 0,
36640 "every physical worker must have exited before the join returns"
36641 );
36642 }
36643
36644 #[cfg(napi_runtime_os_threads)]
36645 #[test]
36646 fn current_thread_queues_concurrent_blocking_admission() {
36647 use std::sync::mpsc;
36654
36655 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
36656 RuntimeMetrics::default(),
36657 )));
36658 let work = GenerationWork::new();
36659 let (outcome_tx, outcome_rx) = mpsc::channel();
36660
36661 let owner_registration = work
36662 .try_register_work()
36663 .expect("the lane owner's blocking job must be accepted");
36664 let concurrent_work = Arc::clone(&work);
36665 let concurrent_executor = Arc::clone(&executor);
36666 let task_executor = Arc::clone(&executor);
36667 let wake_executor = Arc::clone(&executor);
36668 let (runnable, task) = async_task::spawn(
36669 async move {
36670 let concurrent = task_executor
36671 .schedule_blocking(
36672 move || {
36673 std::thread::spawn(move || {
36674 let registration = concurrent_work
36675 .try_register_work()
36676 .expect("the concurrent blocking job must be accepted");
36677 concurrent_executor.schedule_blocking(|| 7usize, registration)
36678 })
36679 .join()
36680 .expect("the concurrent submitter thread must finish")
36681 },
36682 owner_registration,
36683 )
36684 .await
36685 .expect("the lane owner's blocking job must finish");
36686 let _ = outcome_tx.send(concurrent.await);
36687 },
36688 move |runnable| wake_executor.schedule(runnable),
36689 );
36690
36691 executor.schedule(runnable);
36692 let mut turns = 0usize;
36693 let outcome = loop {
36694 executor.drive_host_turn();
36695 if let Ok(outcome) = outcome_rx.try_recv() {
36696 break outcome;
36697 }
36698 turns += 1;
36699 assert!(
36700 turns <= 8,
36701 "queued blocking admission must make bounded host-turn progress"
36702 );
36703 };
36704 futures::executor::block_on(task);
36705 assert_eq!(
36706 outcome.expect("a concurrent blocking submission must be queued, not rejected"),
36707 7
36708 );
36709 }
36710}