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 rustc_hash::{FxHashMap, FxHashSet};
21
22use crate::MAX_ASYNC_RUNTIME_WORKER_THREADS;
23#[cfg(test)]
24use async_task::Task;
25use async_task::{FallibleTask, Runnable};
26use futures::{
27 FutureExt,
28 future::{AbortHandle, AbortRegistration, Abortable},
29};
30
31#[cfg(not(target_family = "wasm"))]
32use futures::channel::oneshot;
33#[cfg(all(test, not(target_family = "wasm")))]
34use rayon::max_num_threads;
35#[cfg(not(target_family = "wasm"))]
36use rayon::{ThreadPool, ThreadPoolBuilder};
37
38fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
41 counter
42 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
43 .expect(exhausted)
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum RuntimeFlavor {
48 CurrentThread,
49 MultiThread,
50}
51
52#[derive(Debug, Clone)]
53pub struct RuntimeOptions {
54 pub flavor: RuntimeFlavor,
55 pub worker_threads: usize,
56 pub max_blocking_tasks: usize,
57 pub thread_name_prefix: String,
58 pub park_deadline: Option<Duration>,
71}
72
73#[derive(Debug, Clone, Copy, Default)]
80pub struct RuntimeOptionsPatch {
81 pub flavor: Option<RuntimeFlavor>,
82 pub worker_threads: Option<usize>,
83 pub max_blocking_tasks: Option<usize>,
84}
85
86impl RuntimeOptionsPatch {
87 fn apply_to(self, options: &mut RuntimeOptions) {
88 if let Some(flavor) = self.flavor {
89 options.flavor = flavor;
90 }
91 if let Some(worker_threads) = self.worker_threads {
92 options.worker_threads = worker_threads;
93 }
94 if let Some(max_blocking_tasks) = self.max_blocking_tasks {
95 options.max_blocking_tasks = max_blocking_tasks;
96 }
97 }
98}
99
100impl Default for RuntimeOptions {
101 fn default() -> Self {
102 let worker_threads = std::thread::available_parallelism()
103 .map_or(1, usize::from)
104 .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
105 Self {
106 flavor: if cfg!(target_family = "wasm") {
107 RuntimeFlavor::CurrentThread
108 } else {
109 RuntimeFlavor::MultiThread
110 },
111 worker_threads,
112 max_blocking_tasks: worker_threads,
113 thread_name_prefix: "napi-async-runtime".to_string(),
114 park_deadline: None,
115 }
116 }
117}
118
119impl RuntimeOptions {
120 fn validate(self) -> Result<Self, RuntimeConfigError> {
121 #[cfg(target_family = "wasm")]
122 let rayon_max_threads = None;
123 #[cfg(not(target_family = "wasm"))]
124 let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
125 self.validate_with_rayon_max_threads(rayon_max_threads)
126 }
127
128 fn validate_with_rayon_max_threads(
129 mut self,
130 rayon_max_threads: Option<usize>,
131 ) -> Result<Self, RuntimeConfigError> {
132 if self.worker_threads == 0 {
133 return Err(RuntimeConfigError(
134 "worker_threads must be greater than zero".to_string(),
135 ));
136 }
137 if self.max_blocking_tasks == 0 {
138 return Err(RuntimeConfigError(
139 "max_blocking_tasks must be greater than zero".to_string(),
140 ));
141 }
142 if self.flavor == RuntimeFlavor::CurrentThread {
143 self.worker_threads = 1;
144 self.max_blocking_tasks = 1;
145 } else {
146 let Some(rayon_max_threads) = rayon_max_threads else {
147 return Err(RuntimeConfigError(
148 "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
149 ));
150 };
151 if rayon_max_threads < 2 {
152 return Err(RuntimeConfigError(
153 "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
154 ));
155 }
156 self.worker_threads = self
164 .worker_threads
165 .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
166 .min(rayon_max_threads);
167 let blocking_capacity = self.worker_threads - 1;
168 self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
169 }
170 Ok(self)
171 }
172}
173
174pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
183
184const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
200
201#[derive(Debug, Clone)]
202pub struct RuntimeConfigError(String);
203
204impl fmt::Display for RuntimeConfigError {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 f.write_str(&self.0)
207 }
208}
209
210impl std::error::Error for RuntimeConfigError {}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum BlockOnDeadlockKind {
215 CurrentThreadCertain,
220 CurrentThreadCertainTimerBacked,
227 CurrentThreadDeadline,
230 MultiThreadCooperativeDeadline,
234}
235
236#[derive(Debug, Clone)]
244pub struct BlockOnDeadlock {
245 pub kind: BlockOnDeadlockKind,
246 pub park_deadline: Option<Duration>,
249}
250
251impl BlockOnDeadlock {
252 fn current_thread_certain() -> Self {
253 Self {
254 kind: BlockOnDeadlockKind::CurrentThreadCertain,
255 park_deadline: None,
256 }
257 }
258
259 fn current_thread_certain_timer_backed() -> Self {
260 Self {
261 kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
262 park_deadline: None,
263 }
264 }
265
266 fn current_thread_deadline(deadline: Duration) -> Self {
267 Self {
268 kind: BlockOnDeadlockKind::CurrentThreadDeadline,
269 park_deadline: Some(deadline),
270 }
271 }
272
273 #[cfg(not(target_family = "wasm"))]
276 fn multi_thread_cooperative(deadline: Duration) -> Self {
277 Self {
278 kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
279 park_deadline: Some(deadline),
280 }
281 }
282}
283
284impl fmt::Display for BlockOnDeadlock {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 let deadline_ms = self
287 .park_deadline
288 .map_or(0, |deadline| deadline.as_millis());
289 match self.kind {
290 BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
291 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
292 park with an empty task queue and no pending wake on a threadless build -- no other \
293 thread exists that could ever deliver the wake, so this park would never end. This is \
294 the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
295 (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
296 dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
297 continuation is the one now parking.",
298 ),
299 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
300 "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
301 park with an empty task queue on a threadless build while a host timer is pending. \
302 The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
303 only run on the very thread that is now parking, so it can never fire, and no other \
304 thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
305 hazard: native code called `block_on` on a future that (transitively) awaits a JS \
306 continuation, but the only thread that could run that continuation is the one now \
307 parking.",
308 ),
309 BlockOnDeadlockKind::CurrentThreadDeadline => write!(
310 f,
311 "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
312 {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
313 task queue, no pending wake and ZERO runtime progress across the whole window. This is \
314 the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
315 awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
316 once this thread returns to the event loop. If this park is legitimate, raise or unset \
317 the deadline.",
318 ),
319 BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
320 f,
321 "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
322 parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
323 no runnable or blocking work available and ZERO executor progress across the whole \
324 window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
325 The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
326 If this park is legitimate, raise or unset the deadline.",
327 ),
328 }
329 }
330}
331
332impl std::error::Error for BlockOnDeadlock {}
333
334#[derive(Debug)]
335pub struct JoinError {
336 message: String,
337}
338
339impl JoinError {
340 fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
341 Self {
342 message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
346 deadlock.to_string()
347 } else if let Some(message) = panic.downcast_ref::<&str>() {
348 (*message).to_string()
349 } else if let Some(message) = panic.downcast_ref::<String>() {
350 message.clone()
351 } else {
352 "async runtime task panicked".to_string()
353 },
354 }
355 }
356}
357
358impl fmt::Display for JoinError {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 f.write_str(&self.message)
361 }
362}
363
364impl std::error::Error for JoinError {}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367enum BlockOnOutcome {
368 Completed,
369 Stopped,
370}
371
372fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
373 if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
378 std::mem::forget(nested_payload);
379 }
380}
381
382#[derive(Default)]
383struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
384
385impl RetainedPanicPayload {
386 fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
387 if self.0.is_some() {
388 discard_panic_payload(payload);
389 } else {
390 self.0 = Some(payload);
391 }
392 }
393
394 fn is_some(&self) -> bool {
395 self.0.is_some()
396 }
397
398 fn resume(mut self) -> ! {
399 resume_unwind(
400 self
401 .0
402 .take()
403 .expect("a retained panic payload must remain owned"),
404 )
405 }
406}
407
408impl Drop for RetainedPanicPayload {
409 fn drop(&mut self) {
410 if let Some(payload) = self.0.take() {
411 discard_panic_payload(payload);
412 }
413 }
414}
415
416fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
417 match catch_unwind(AssertUnwindSafe(function)) {
418 Ok(value) => Some(value),
419 Err(payload) => {
420 discard_panic_payload(payload);
421 None
422 }
423 }
424}
425
426struct ContainedArc<T: ?Sized> {
427 value: Option<Arc<T>>,
428}
429
430impl<T: ?Sized> ContainedArc<T> {
431 fn new(value: Arc<T>) -> Self {
432 Self { value: Some(value) }
433 }
434
435 fn get(&self) -> &T {
436 self
437 .value
438 .as_deref()
439 .expect("the contained Arc must remain owned")
440 }
441
442 #[cfg(test)]
443 fn into_inner(mut self) -> Arc<T> {
444 self
445 .value
446 .take()
447 .expect("the contained Arc must remain owned")
448 }
449}
450
451impl<T: ?Sized> Drop for ContainedArc<T> {
452 fn drop(&mut self) {
453 if let Some(value) = self.value.take() {
454 let _ = catch_unwind_contained(|| drop(value));
455 }
456 }
457}
458
459struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
460 values: Vec<(I, Arc<T>)>,
461}
462
463impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
464 fn new(values: Vec<(I, Arc<T>)>) -> Self {
465 Self { values }
466 }
467
468 fn as_slice(&self) -> &[(I, Arc<T>)] {
469 &self.values
470 }
471
472 fn len(&self) -> usize {
473 self.values.len()
474 }
475
476 fn is_empty(&self) -> bool {
477 self.values.is_empty()
478 }
479
480 fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
481 self
482 .values
483 .pop()
484 .map(|(id, value)| (id, ContainedArc::new(value)))
485 }
486}
487
488impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
489 fn drop(&mut self) {
490 while let Some((_, value)) = self.values.pop() {
491 let _ = catch_unwind_contained(|| drop(value));
492 }
493 }
494}
495
496struct PanicContainedWaker {
497 waker: ManuallyDrop<Waker>,
498 generation: Option<u64>,
499}
500
501impl std::task::Wake for PanicContainedWaker {
502 fn wake(self: Arc<Self>) {
503 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
504 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
505 }
506
507 fn wake_by_ref(self: &Arc<Self>) {
508 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
509 let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
510 }
511}
512
513impl Drop for PanicContainedWaker {
514 fn drop(&mut self) {
515 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
516 let _ = catch_unwind_contained(|| {
517 unsafe { ManuallyDrop::drop(&mut self.waker) };
519 });
520 }
521}
522
523fn panic_contained_waker(waker: Waker) -> Waker {
524 Waker::from(Arc::new(PanicContainedWaker {
525 waker: ManuallyDrop::new(waker),
526 generation: active_runtime_generation(),
527 }))
528}
529
530struct PanicContainedWakerSource {
531 waker: ManuallyDrop<Waker>,
532 generation: Option<u64>,
533}
534
535impl PanicContainedWakerSource {
536 fn new(waker: Waker) -> Self {
537 Self {
538 waker: ManuallyDrop::new(waker),
539 generation: active_runtime_generation(),
540 }
541 }
542
543 fn as_waker(&self) -> &Waker {
544 &self.waker
545 }
546}
547
548impl Drop for PanicContainedWakerSource {
549 fn drop(&mut self) {
550 let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
551 let _ = catch_unwind_contained(|| {
552 unsafe { ManuallyDrop::drop(&mut self.waker) };
554 });
555 }
556}
557
558struct PanicContainedWakerCache {
559 source: PanicContainedWakerSource,
560 contained: Waker,
561}
562
563impl PanicContainedWakerCache {
564 fn new(waker: &Waker) -> Self {
565 let generation = active_runtime_generation();
566 let _generation = RuntimeGenerationGuard::enter_optional(generation);
567 let source = PanicContainedWakerSource::new(waker.clone());
568 let contained = panic_contained_waker(source.as_waker().clone());
569 Self { source, contained }
570 }
571
572 fn update(&mut self, waker: &Waker) {
573 let will_wake = {
574 let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
575 self.source.as_waker().will_wake(waker)
576 };
577 if !will_wake {
578 *self = Self::new(waker);
579 }
580 }
581
582 fn contained(&self) -> &Waker {
583 &self.contained
584 }
585}
586
587fn wake_waker_contained(waker: Waker) {
588 let _ = catch_unwind_contained(|| waker.wake_by_ref());
593 let _ = catch_unwind_contained(|| drop(waker));
594}
595
596fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
597 match catch_unwind(AssertUnwindSafe(function)) {
598 Ok(value) => Ok(value),
599 Err(payload) => {
600 let error = JoinError::from_panic(&*payload);
601 discard_panic_payload(payload);
602 Err(error)
603 }
604 }
605}
606
607fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
608 let error = JoinError::from_panic(&*payload);
609 discard_panic_payload(payload);
610 error
611}
612
613struct ContainedTaskOutput<T> {
614 output: ManuallyDrop<T>,
615 generation: u64,
616}
617
618impl<T> ContainedTaskOutput<T> {
619 fn new(output: T, generation: u64) -> Self {
620 Self {
621 output: ManuallyDrop::new(output),
622 generation,
623 }
624 }
625
626 fn into_inner(mut self) -> T {
627 let output = unsafe { ManuallyDrop::take(&mut self.output) };
630 std::mem::forget(self);
631 output
632 }
633}
634
635impl<T> Drop for ContainedTaskOutput<T> {
636 fn drop(&mut self) {
637 let _generation = RuntimeGenerationGuard::enter(self.generation);
638 let _ = catch_unwind_contained(|| {
639 unsafe { ManuallyDrop::drop(&mut self.output) };
642 });
643 }
644}
645
646#[cfg(not(target_family = "wasm"))]
647#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
648struct BlockingJobId(u64);
649
650#[cfg(not(target_family = "wasm"))]
651static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
652
653#[cfg(all(test, not(target_family = "wasm")))]
654thread_local! {
655 static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
656 const { std::cell::RefCell::new(None) };
657 static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
658 const { std::cell::RefCell::new(None) };
659 static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
660 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
661 const { std::cell::RefCell::new(None) };
662 static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
663 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
664 const { std::cell::RefCell::new(None) };
665 static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
666 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
667 const { std::cell::RefCell::new(None) };
668 static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
669 const { std::cell::RefCell::new(None) };
670 static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
671 const { std::cell::RefCell::new(None) };
672 static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
673 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
674 const { std::cell::RefCell::new(None) };
675 static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
676 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
677 const { std::cell::RefCell::new(None) };
678 static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
679 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
680 const { std::cell::RefCell::new(None) };
681 static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
682 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
683 const { std::cell::RefCell::new(None) };
684 static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
685 const { std::cell::RefCell::new(None) };
686 static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
687 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
688 const { std::cell::RefCell::new(None) };
689 static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
690 const { std::cell::RefCell::new(None) };
691}
692
693#[cfg(all(test, not(target_family = "wasm")))]
694struct WorkerTlsDropProbe {
695 action: Option<WorkerTlsDropAction>,
696}
697
698#[cfg(all(test, not(target_family = "wasm")))]
712enum WorkerTlsDropAction {
713 Record { completed: Arc<AtomicUsize> },
717 ReenterLifecycle {
718 controller: Arc<RuntimeController>,
719 completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
720 },
721}
722
723#[cfg(all(test, not(target_family = "wasm")))]
724impl Drop for WorkerTlsDropProbe {
725 fn drop(&mut self) {
726 match self
727 .action
728 .take()
729 .expect("worker TLS drop probe action already consumed")
730 {
731 WorkerTlsDropAction::Record { completed } => {
732 completed.fetch_add(1, Ordering::SeqCst);
733 }
734 WorkerTlsDropAction::ReenterLifecycle {
739 controller,
740 completed,
741 } => {
742 let start = controller.start().map_err(|error| error.to_string());
743 let shutdown = controller.shutdown().map_err(|error| error.to_string());
744 let _ = completed.send((start, shutdown));
745 }
746 }
747 }
748}
749
750#[cfg(all(test, not(target_family = "wasm")))]
751fn run_dependency_claim_test_hook() {
752 if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
753 hook();
754 }
755}
756
757#[cfg(all(test, not(target_family = "wasm")))]
758fn run_current_thread_dispatch_claim_test_hook() {
759 if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
760 {
761 hook();
762 }
763}
764
765#[cfg(all(test, not(target_family = "wasm")))]
766fn run_current_thread_dispatch_publication_blocked_test_hook() {
767 if let Some(hook) =
768 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
769 {
770 hook();
771 }
772}
773
774#[cfg(all(test, not(target_family = "wasm")))]
775fn run_current_thread_dispatch_recovery_test_hook() {
776 if let Some(hook) =
777 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
778 {
779 hook();
780 }
781}
782
783#[cfg(all(test, not(target_family = "wasm")))]
784fn run_current_thread_failed_publication_retired_test_hook() {
785 if let Some(hook) =
786 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
787 {
788 hook();
789 }
790}
791
792#[cfg(all(test, not(target_family = "wasm")))]
793fn run_current_thread_dispatch_call_test_hook() {
794 if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
795 hook();
796 }
797}
798
799#[cfg(all(test, not(target_family = "wasm")))]
800fn run_current_thread_dispatch_returned_test_hook() {
801 if let Some(hook) =
802 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
803 {
804 hook();
805 }
806}
807
808#[cfg(all(test, not(target_family = "wasm")))]
809fn run_current_thread_blocking_claim_test_hook() {
810 if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
811 {
812 hook();
813 }
814}
815
816#[cfg(all(test, not(target_family = "wasm")))]
817fn run_current_thread_runnable_claim_test_hook() {
818 if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
819 {
820 hook();
821 }
822}
823
824#[cfg(all(test, not(target_family = "wasm")))]
825fn run_multi_thread_work_claimed_test_hook() {
826 if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
827 hook();
828 }
829}
830
831#[cfg(all(test, not(target_family = "wasm")))]
832fn run_multi_thread_work_started_test_hook() {
833 if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
834 hook();
835 }
836}
837
838#[cfg(all(test, not(target_family = "wasm")))]
839fn run_dependency_predicate_test_hook() {
840 if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
841 hook();
842 }
843}
844
845#[cfg(all(test, not(target_family = "wasm")))]
846fn run_before_owned_blocking_queue_claim_test_hook() {
847 if let Some(hook) =
848 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
849 {
850 hook();
851 }
852}
853
854#[cfg(not(target_family = "wasm"))]
855#[derive(Clone, Copy, Debug, PartialEq, Eq)]
856struct BlockingDependency {
857 job: BlockingJobId,
858 owner: Option<BlockingOwnerToken>,
859}
860
861#[cfg(not(target_family = "wasm"))]
862#[derive(Debug)]
863struct DependencyClaim {
864 claimed: AtomicBool,
865 transition: Mutex<()>,
866}
867
868#[cfg(not(target_family = "wasm"))]
869impl DependencyClaim {
870 fn new() -> Self {
871 Self {
872 claimed: AtomicBool::new(false),
873 transition: Mutex::new(()),
874 }
875 }
876
877 fn is_live(&self) -> bool {
878 !self.claimed.load(Ordering::Acquire)
879 }
880
881 fn cancel_link(&self, link: &DependencyLink) {
882 let _transition = self
883 .transition
884 .lock()
885 .unwrap_or_else(std::sync::PoisonError::into_inner);
886 link.cancel();
887 }
888
889 fn try_claim(&self, link: &DependencyLink) -> bool {
890 let _transition = self
891 .transition
892 .lock()
893 .unwrap_or_else(std::sync::PoisonError::into_inner);
894 #[cfg(test)]
895 run_dependency_claim_test_hook();
896 if self.claimed.load(Ordering::Acquire) || !link.is_live() {
897 return false;
898 }
899 self.claimed.store(true, Ordering::Release);
900 true
901 }
902}
903
904#[cfg(not(target_family = "wasm"))]
905#[derive(Debug)]
906struct DependencyLink {
907 live: AtomicBool,
908 parent: Option<Weak<DependencyLink>>,
909}
910
911#[cfg(not(target_family = "wasm"))]
912impl DependencyLink {
913 fn root() -> Arc<Self> {
914 Arc::new(Self {
915 live: AtomicBool::new(true),
916 parent: None,
917 })
918 }
919
920 fn derived(parent: &Arc<Self>) -> Arc<Self> {
921 Arc::new(Self {
922 live: AtomicBool::new(true),
923 parent: Some(Arc::downgrade(parent)),
924 })
925 }
926
927 fn is_live(&self) -> bool {
928 if !self.live.load(Ordering::Acquire) {
929 return false;
930 }
931 let Some(parent) = &self.parent else {
932 return true;
933 };
934 let Some(mut current) = parent.upgrade() else {
935 return false;
936 };
937 loop {
938 if !current.live.load(Ordering::Acquire) {
939 return false;
940 }
941 let Some(parent) = ¤t.parent else {
942 return true;
943 };
944 let Some(next) = parent.upgrade() else {
945 return false;
946 };
947 current = next;
948 }
949 }
950
951 fn cancel(&self) {
952 self.live.store(false, Ordering::Release);
953 }
954}
955
956#[cfg(not(target_family = "wasm"))]
957#[derive(Clone, Copy, PartialEq, Eq, Hash)]
958struct DependencyPublicationIdentity {
959 job: BlockingJobId,
960 claim: *const DependencyClaim,
961 link: *const DependencyLink,
962}
963
964#[cfg(not(target_family = "wasm"))]
965#[derive(Clone, Debug)]
966struct DependencyPublication {
967 dependency: BlockingDependency,
968 claim: Arc<DependencyClaim>,
969 link: Arc<DependencyLink>,
970}
971
972#[cfg(not(target_family = "wasm"))]
973impl DependencyPublication {
974 fn owned(dependency: BlockingDependency) -> Self {
975 Self {
976 dependency,
977 claim: Arc::new(DependencyClaim::new()),
978 link: DependencyLink::root(),
979 }
980 }
981
982 fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
983 Self {
984 dependency: BlockingDependency {
985 owner: self.dependency.owner.or(owner),
986 ..self.dependency
987 },
988 claim: Arc::clone(&self.claim),
989 link: DependencyLink::derived(&self.link),
990 }
991 }
992
993 fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
994 self.dependency.owner = self.dependency.owner.or(owner);
995 self
996 }
997
998 fn same_as(&self, other: &Self) -> bool {
999 self.dependency == other.dependency
1000 && Arc::ptr_eq(&self.claim, &other.claim)
1001 && Arc::ptr_eq(&self.link, &other.link)
1002 }
1003
1004 fn identity(&self) -> DependencyPublicationIdentity {
1005 DependencyPublicationIdentity {
1006 job: self.dependency.job,
1007 claim: Arc::as_ptr(&self.claim),
1008 link: Arc::as_ptr(&self.link),
1009 }
1010 }
1011
1012 fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1013 Some(DependencyPublicationIdentity {
1014 job: self.dependency.job,
1015 claim: Arc::as_ptr(&self.claim),
1016 link: self.link.parent.as_ref()?.as_ptr(),
1017 })
1018 }
1019
1020 fn is_derived_from(&self, source: &Self) -> bool {
1021 self.immediate_source_identity() == Some(source.identity())
1022 }
1023
1024 fn is_live(&self) -> bool {
1025 self.claim.is_live() && self.link.is_live()
1026 }
1027
1028 fn cancel(&self) {
1029 self.claim.cancel_link(&self.link);
1030 }
1031
1032 fn try_claim(&self) -> bool {
1033 self.claim.try_claim(&self.link)
1034 }
1035}
1036
1037#[cfg(not(target_family = "wasm"))]
1038struct TaskDependencyEntry {
1039 sequence: u64,
1040 publication: DependencyPublication,
1041}
1042
1043#[cfg(not(target_family = "wasm"))]
1044struct SelectedDependencyPublication {
1045 sequence: u64,
1046 publication: DependencyPublication,
1047}
1048
1049#[cfg(not(target_family = "wasm"))]
1050#[derive(Clone, Copy)]
1051enum OwnerDependencyMode {
1052 Available,
1053 Exact,
1054}
1055
1056#[cfg(not(target_family = "wasm"))]
1057#[derive(Default)]
1058struct TaskDependencyState {
1059 current: VecDeque<TaskDependencyEntry>,
1060 next_sequence: u64,
1061 unowned_sequences: VecDeque<u64>,
1062 owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1063 job_sequences: FxHashMap<BlockingJobId, FxHashSet<u64>>,
1064 waiter: Option<Waker>,
1065}
1066
1067#[cfg(not(target_family = "wasm"))]
1068impl TaskDependencyState {
1069 fn append(&mut self, publication: DependencyPublication) {
1070 let sequence = self.next_sequence;
1071 self.next_sequence = self
1072 .next_sequence
1073 .checked_add(1)
1074 .expect("task dependency sequence space exhausted");
1075 match publication.dependency.owner {
1076 Some(owner) => self
1077 .owner_sequences
1078 .entry(owner)
1079 .or_default()
1080 .push_back(sequence),
1081 None => self.unowned_sequences.push_back(sequence),
1082 }
1083 self
1084 .job_sequences
1085 .entry(publication.dependency.job)
1086 .or_default()
1087 .insert(sequence);
1088 self.current.push_back(TaskDependencyEntry {
1089 sequence,
1090 publication,
1091 });
1092 }
1093
1094 fn entry_in(
1095 current: &VecDeque<TaskDependencyEntry>,
1096 sequence: u64,
1097 ) -> Option<&TaskDependencyEntry> {
1098 let front = current.front()?.sequence;
1099 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1100 let entry = current.get(index)?;
1101 (entry.sequence == sequence).then_some(entry)
1102 }
1103
1104 fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1105 Self::entry_in(&self.current, sequence)
1106 }
1107
1108 fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1109 let front = self.current.front()?.sequence;
1110 let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1111 let entry = self.current.get_mut(index)?;
1112 (entry.sequence == sequence).then_some(entry)
1113 }
1114
1115 fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1116 match dependency.owner {
1117 Some(owner) => {
1118 let remove_owner = self
1119 .owner_sequences
1120 .get_mut(&owner)
1121 .is_some_and(|sequences| {
1122 if sequences.front() == Some(&sequence) {
1123 sequences.pop_front();
1124 }
1125 sequences.is_empty()
1126 });
1127 if remove_owner {
1128 self.owner_sequences.remove(&owner);
1129 }
1130 }
1131 None => {
1132 if self.unowned_sequences.front() == Some(&sequence) {
1133 self.unowned_sequences.pop_front();
1134 }
1135 }
1136 }
1137 if let Some(sequences) = self.job_sequences.get_mut(&dependency.job) {
1138 sequences.remove(&sequence);
1139 if sequences.is_empty() {
1140 self.job_sequences.remove(&dependency.job);
1141 }
1142 }
1143 }
1144
1145 fn clear_indexes(&mut self) {
1146 self.unowned_sequences.clear();
1147 self.owner_sequences.clear();
1148 self.job_sequences.clear();
1149 }
1150}
1151
1152#[cfg(not(target_family = "wasm"))]
1153struct TaskDependency {
1154 state: Mutex<TaskDependencyState>,
1155 owner_hint: Option<BlockingOwnerToken>,
1156 has_current: AtomicBool,
1157 generation: u64,
1158 #[cfg(test)]
1159 entry_visits: AtomicUsize,
1160}
1161
1162#[cfg(not(target_family = "wasm"))]
1163impl Default for TaskDependency {
1164 fn default() -> Self {
1165 Self::new(u64::MAX, None)
1166 }
1167}
1168
1169#[cfg(not(target_family = "wasm"))]
1170impl TaskDependency {
1171 fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1172 Self {
1173 state: Mutex::new(TaskDependencyState::default()),
1174 owner_hint,
1175 has_current: AtomicBool::new(false),
1176 generation,
1177 #[cfg(test)]
1178 entry_visits: AtomicUsize::new(0),
1179 }
1180 }
1181
1182 fn new_task(generation: u64) -> Self {
1183 Self::new(generation, None)
1184 }
1185
1186 fn notify_waiter(&self, waiter: Option<Waker>) {
1187 if let Some(waiter) = waiter {
1188 let _generation = RuntimeGenerationGuard::enter(self.generation);
1189 wake_waker_contained(waiter);
1190 }
1191 }
1192
1193 fn clear_waiter(&self) {
1194 let waiter = self
1195 .state
1196 .lock()
1197 .unwrap_or_else(std::sync::PoisonError::into_inner)
1198 .waiter
1199 .take();
1200 if let Some(waiter) = waiter {
1201 let _generation = RuntimeGenerationGuard::enter(self.generation);
1202 let _ = catch_unwind_contained(|| drop(waiter));
1203 }
1204 }
1205
1206 fn clear(&self) {
1207 let waiter = {
1208 let mut state = self
1209 .state
1210 .lock()
1211 .unwrap_or_else(std::sync::PoisonError::into_inner);
1212 if state.current.is_empty() {
1213 None
1214 } else {
1215 for current in state.current.drain(..) {
1216 current.publication.cancel();
1217 }
1218 state.clear_indexes();
1219 self.has_current.store(false, Ordering::Release);
1220 state.waiter.take()
1221 }
1222 };
1223 self.notify_waiter(waiter);
1224 }
1225
1226 fn clear_if_dependency(&self, dependency: BlockingDependency) {
1227 let waiter = {
1228 let mut state = self
1229 .state
1230 .lock()
1231 .unwrap_or_else(std::sync::PoisonError::into_inner);
1232 let mut removed = false;
1233 if let Some(sequences) = state.job_sequences.remove(&dependency.job) {
1234 for sequence in sequences {
1235 if let Some(current) = state.entry_mut(sequence) {
1236 current.publication.cancel();
1237 removed = true;
1238 }
1239 }
1240 }
1241 if removed {
1242 self.prune_stale_prefix(&mut state);
1243 state.waiter.take()
1244 } else {
1245 None
1246 }
1247 };
1248 self.notify_waiter(waiter);
1249 }
1250
1251 fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1252 let Some(first_publication) = publications.first() else {
1253 return;
1254 };
1255 let publication_identities = (publications.len() > 1).then(|| {
1256 publications
1257 .iter()
1258 .map(DependencyPublication::identity)
1259 .collect::<FxHashSet<_>>()
1260 });
1261 let waiter = {
1262 let mut state = self
1263 .state
1264 .lock()
1265 .unwrap_or_else(std::sync::PoisonError::into_inner);
1266 let removed = state
1267 .current
1268 .iter_mut()
1269 .filter_map(|current| {
1270 self.note_entry_visit();
1271 let is_derived = publication_identities.as_ref().map_or_else(
1272 || current.publication.is_derived_from(first_publication),
1273 |identities| {
1274 current
1275 .publication
1276 .immediate_source_identity()
1277 .is_some_and(|identity| identities.contains(&identity))
1278 },
1279 );
1280 if !is_derived {
1281 return None;
1282 }
1283 current.publication.cancel();
1284 Some((current.sequence, current.publication.dependency.job))
1285 })
1286 .collect::<Vec<_>>();
1287 for (sequence, job) in &removed {
1288 if let Some(sequences) = state.job_sequences.get_mut(job) {
1289 sequences.remove(sequence);
1290 if sequences.is_empty() {
1291 state.job_sequences.remove(job);
1292 }
1293 }
1294 }
1295 if removed.is_empty() {
1296 None
1297 } else {
1298 self.prune_stale_prefix(&mut state);
1299 state.waiter.take()
1300 }
1301 };
1302 self.notify_waiter(waiter);
1303 }
1304
1305 fn set_owned(&self, dependency: BlockingDependency) {
1306 let publication = DependencyPublication::owned(BlockingDependency {
1307 owner: dependency.owner.or(self.owner_hint),
1308 ..dependency
1309 });
1310 self.set_entry(publication);
1311 }
1312
1313 fn set_borrowed(&self, publication: &DependencyPublication) {
1314 self.set_entry(publication.derived(self.owner_hint));
1315 }
1316
1317 fn set_entry(&self, publication: DependencyPublication) {
1318 let waiter = {
1319 let mut state = self
1320 .state
1321 .lock()
1322 .unwrap_or_else(std::sync::PoisonError::into_inner);
1323 state.append(publication);
1326 self.has_current.store(true, Ordering::Release);
1327 state.waiter.take()
1328 };
1329 self.notify_waiter(waiter);
1330 }
1331
1332 fn note_entry_visit(&self) {
1333 #[cfg(test)]
1334 self.entry_visits.fetch_add(1, Ordering::Relaxed);
1335 }
1336
1337 fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1338 while state.current.front().is_some_and(|current| {
1339 self.note_entry_visit();
1340 !current.publication.is_live()
1341 }) {
1342 let current = state
1343 .current
1344 .pop_front()
1345 .expect("the stale prefix entry must exist");
1346 state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1347 }
1348 self
1349 .has_current
1350 .store(!state.current.is_empty(), Ordering::Release);
1351 }
1352
1353 fn first_live_sequence(
1354 &self,
1355 current: &VecDeque<TaskDependencyEntry>,
1356 sequences: &mut VecDeque<u64>,
1357 ) -> Option<u64> {
1358 loop {
1359 let sequence = *sequences.front()?;
1360 let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1361 sequences.pop_front();
1362 continue;
1363 };
1364 self.note_entry_visit();
1365 if entry.publication.is_live() {
1366 return Some(sequence);
1367 }
1368 sequences.pop_front();
1369 }
1370 }
1371
1372 fn first_live_owner_sequence(
1373 &self,
1374 state: &mut TaskDependencyState,
1375 owner: BlockingOwnerToken,
1376 ) -> Option<u64> {
1377 let sequence = {
1378 let current = &state.current;
1379 state
1380 .owner_sequences
1381 .get_mut(&owner)
1382 .and_then(|sequences| self.first_live_sequence(current, sequences))
1383 };
1384 if sequence.is_none() {
1385 state.owner_sequences.remove(&owner);
1386 }
1387 sequence
1388 }
1389
1390 fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1391 self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1392 }
1393
1394 #[cfg(test)]
1395 fn get(&self) -> Option<DependencyPublication> {
1396 self
1397 .state
1398 .lock()
1399 .unwrap_or_else(std::sync::PoisonError::into_inner)
1400 .current
1401 .iter()
1402 .find(|current| current.publication.is_live())
1403 .map(|current| current.publication.clone())
1404 }
1405
1406 fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1407 self
1408 .state
1409 .lock()
1410 .unwrap_or_else(std::sync::PoisonError::into_inner)
1411 .current
1412 .iter()
1413 .filter(|current| {
1414 self.note_entry_visit();
1415 current.publication.is_live()
1416 })
1417 .map(|current| current.publication.clone())
1418 .collect()
1419 }
1420
1421 fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1422 let mut state = self
1423 .state
1424 .lock()
1425 .unwrap_or_else(std::sync::PoisonError::into_inner);
1426 self.prune_stale_prefix(&mut state);
1427 let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1428 let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1429 let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1430 (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1431 (Some(_) | None, Some(unowned)) => (unowned, true),
1432 (Some(exact), None) => (exact, false),
1433 (None, None) => return None,
1434 };
1435 if bind_owner {
1436 let removed = state.unowned_sequences.pop_front();
1437 debug_assert_eq!(removed, Some(sequence));
1438 let current = state
1439 .entry_mut(sequence)
1440 .expect("the selected dependency must remain indexed");
1441 debug_assert_eq!(current.publication.dependency.owner, None);
1442 current.publication.dependency.owner = Some(owner);
1443 state
1447 .owner_sequences
1448 .entry(owner)
1449 .or_default()
1450 .push_front(sequence);
1451 }
1452 let publication = state
1453 .entry(sequence)
1454 .expect("the selected dependency must remain indexed")
1455 .publication
1456 .clone();
1457 Some(SelectedDependencyPublication {
1458 sequence,
1459 publication,
1460 })
1461 }
1462
1463 fn has_live_owner_dependency(
1464 &self,
1465 owner: BlockingOwnerToken,
1466 mode: OwnerDependencyMode,
1467 ) -> bool {
1468 #[cfg(all(test, not(target_family = "wasm")))]
1469 run_dependency_predicate_test_hook();
1470 let mut state = self
1471 .state
1472 .lock()
1473 .unwrap_or_else(std::sync::PoisonError::into_inner);
1474 self.prune_stale_prefix(&mut state);
1475 if self.first_live_owner_sequence(&mut state, owner).is_some() {
1476 return true;
1477 }
1478 matches!(mode, OwnerDependencyMode::Available)
1479 && self.first_live_unowned_sequence(&mut state).is_some()
1480 }
1481
1482 #[cfg(test)]
1483 fn get_dependency(&self) -> Option<BlockingDependency> {
1484 self.get().map(|publication| publication.dependency)
1485 }
1486
1487 #[cfg(test)]
1488 fn get_dependencies(&self) -> Vec<BlockingDependency> {
1489 self
1490 .snapshot_all_live()
1491 .into_iter()
1492 .map(|publication| publication.dependency)
1493 .collect()
1494 }
1495
1496 #[cfg(test)]
1497 fn reset_entry_visits(&self) {
1498 self.entry_visits.store(0, Ordering::Relaxed);
1499 }
1500
1501 #[cfg(test)]
1502 fn entry_visits(&self) -> usize {
1503 self.entry_visits.load(Ordering::Relaxed)
1504 }
1505
1506 fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1510 let mut state = self
1511 .state
1512 .lock()
1513 .unwrap_or_else(std::sync::PoisonError::into_inner);
1514 let Some(current) = state.entry(selected.sequence) else {
1515 return false;
1516 };
1517 self.note_entry_visit();
1518 if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1519 return false;
1520 }
1521 let dependency = current.publication.dependency;
1522 state
1523 .entry_mut(selected.sequence)
1524 .expect("the claimed dependency must remain indexed")
1525 .publication
1526 .cancel();
1527 state.remove_sequence_indexes(selected.sequence, dependency);
1528 self.prune_stale_prefix(&mut state);
1529 true
1530 }
1531
1532 fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1533 let _generation = RuntimeGenerationGuard::enter(self.generation);
1534 let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1535 return Vec::new();
1536 };
1537 let (previous, unused, current) = {
1538 let mut state = self
1539 .state
1540 .lock()
1541 .unwrap_or_else(std::sync::PoisonError::into_inner);
1542 let current = state
1543 .current
1544 .iter()
1545 .filter(|current| current.publication.is_live())
1546 .map(|current| current.publication.clone())
1547 .collect();
1548 if state
1549 .waiter
1550 .as_ref()
1551 .is_some_and(|previous| previous.will_wake(&waker))
1552 {
1553 (None, Some(waker), current)
1554 } else {
1555 (state.waiter.replace(waker), None, current)
1556 }
1557 };
1558 if let Some(previous) = previous {
1559 let _ = catch_unwind_contained(|| drop(previous));
1560 }
1561 if let Some(unused) = unused {
1562 let _ = catch_unwind_contained(|| drop(unused));
1563 }
1564 current
1565 }
1566
1567 fn begin_poll(&self) {
1568 if !self.has_current.load(Ordering::Acquire) {
1569 return;
1570 }
1571 let waiter = {
1572 let mut state = self
1573 .state
1574 .lock()
1575 .unwrap_or_else(std::sync::PoisonError::into_inner);
1576 if state.current.is_empty() {
1577 self.has_current.store(false, Ordering::Release);
1578 None
1579 } else {
1580 for previous in state.current.drain(..) {
1581 previous.publication.cancel();
1582 }
1583 state.clear_indexes();
1584 self.has_current.store(false, Ordering::Release);
1585 state.waiter.take()
1586 }
1587 };
1588 self.notify_waiter(waiter);
1589 }
1590
1591 #[cfg(test)]
1592 fn register_waiter(&self, waker: &Waker) {
1593 let _ = self.register_waiter_and_get(waker);
1594 }
1595}
1596
1597#[cfg(not(target_family = "wasm"))]
1598impl Drop for TaskDependency {
1599 fn drop(&mut self) {
1600 let state = self
1601 .state
1602 .get_mut()
1603 .unwrap_or_else(std::sync::PoisonError::into_inner);
1604 for current in state.current.drain(..) {
1605 current.publication.cancel();
1606 }
1607 if let Some(waiter) = state.waiter.take() {
1608 let _generation = RuntimeGenerationGuard::enter(self.generation);
1609 let _ = catch_unwind_contained(|| drop(waiter));
1610 }
1611 }
1612}
1613
1614#[cfg(not(target_family = "wasm"))]
1615thread_local! {
1616 static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1617 const { std::cell::RefCell::new(Vec::new()) };
1618}
1619
1620#[cfg(not(target_family = "wasm"))]
1621struct TaskDependencyGuard(Arc<TaskDependency>);
1622
1623#[cfg(not(target_family = "wasm"))]
1624impl TaskDependencyGuard {
1625 fn enter(dependency: &Arc<TaskDependency>) -> Self {
1626 let dependency = Arc::clone(dependency);
1627 CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1628 Self(dependency)
1629 }
1630}
1631
1632#[cfg(not(target_family = "wasm"))]
1633impl Drop for TaskDependencyGuard {
1634 fn drop(&mut self) {
1635 CURRENT_DEPENDENCY_STACK.with(|stack| {
1636 let current = stack
1637 .borrow_mut()
1638 .pop()
1639 .expect("blocking dependency context underflow");
1640 debug_assert!(Arc::ptr_eq(¤t, &self.0));
1641 });
1642 }
1643}
1644
1645#[cfg(not(target_family = "wasm"))]
1646struct BlockOnDependencyGuard {
1647 dependency: Arc<TaskDependency>,
1648 _context: TaskDependencyGuard,
1649}
1650
1651#[cfg(not(target_family = "wasm"))]
1652impl BlockOnDependencyGuard {
1653 fn enter(generation: u64) -> Self {
1654 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1655 let dependency = Arc::new(TaskDependency::new(generation, owner));
1656 let context = TaskDependencyGuard::enter(&dependency);
1657 Self {
1658 dependency,
1659 _context: context,
1660 }
1661 }
1662
1663 fn clear(&self) {
1664 self.dependency.clear();
1665 }
1666}
1667
1668#[cfg(not(target_family = "wasm"))]
1669fn record_blocking_dependency(dependency: BlockingDependency) {
1670 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1671 if let Some(current) = current {
1672 let dependency = BlockingDependency {
1673 owner: dependency
1674 .owner
1675 .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
1676 ..dependency
1677 };
1678 current.set_owned(dependency);
1679 }
1680}
1681
1682#[cfg(not(target_family = "wasm"))]
1683fn propagate_blocking_dependency(publication: DependencyPublication) {
1684 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1685 if let Some(current) = current {
1686 let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1687 let publication = publication.with_owner_if_none(owner);
1688 current.set_borrowed(&publication);
1689 }
1690}
1691
1692#[cfg(not(target_family = "wasm"))]
1693fn clear_blocking_dependency(dependency: BlockingDependency) {
1694 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1695 if let Some(current) = current {
1696 current.clear_if_dependency(dependency);
1697 }
1698}
1699
1700#[cfg(not(target_family = "wasm"))]
1701fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
1702 let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1703 if let Some(current) = current {
1704 current.clear_if_publications(publications);
1705 }
1706}
1707
1708enum JoinHandleInner<T> {
1709 Task {
1710 task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
1711 awaiter: Option<PanicContainedWakerCache>,
1712 #[cfg(not(target_family = "wasm"))]
1713 dependency: Arc<TaskDependency>,
1714 },
1715 #[cfg(not(target_family = "wasm"))]
1716 Blocking {
1717 receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
1718 dependency: BlockingDependency,
1719 },
1720 Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
1721}
1722
1723pub struct JoinHandle<T>(JoinHandleInner<T>);
1724
1725impl<T> Unpin for JoinHandle<T> {}
1726
1727impl<T> JoinHandle<T> {
1728 fn detach_task(&mut self) {
1729 let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
1730 match inner {
1731 JoinHandleInner::Task {
1732 task,
1733 awaiter: _,
1734 #[cfg(not(target_family = "wasm"))]
1735 dependency,
1736 } => {
1737 #[cfg(not(target_family = "wasm"))]
1738 let current_dependencies = dependency.snapshot_all_live();
1739 #[cfg(not(target_family = "wasm"))]
1740 dependency.clear_waiter();
1741 task.detach();
1742 #[cfg(not(target_family = "wasm"))]
1743 let _ = catch_unwind_contained(|| {
1747 clear_propagated_blocking_dependencies(¤t_dependencies);
1748 });
1749 }
1750 #[cfg(not(target_family = "wasm"))]
1751 JoinHandleInner::Blocking {
1752 receiver,
1753 dependency,
1754 } => {
1755 let _ = catch_unwind_contained(|| drop(receiver));
1759 let _ = catch_unwind_contained(|| clear_blocking_dependency(dependency));
1762 }
1763 JoinHandleInner::Ready(result) => {
1764 let _ = catch_unwind_contained(|| drop(result));
1765 }
1766 }
1767 }
1768
1769 pub fn detach(mut self) {
1770 self.detach_task();
1771 }
1772}
1773
1774impl<T> Drop for JoinHandle<T> {
1775 fn drop(&mut self) {
1776 self.detach_task();
1780 }
1781}
1782
1783impl<T> Future for JoinHandle<T> {
1784 type Output = Result<T, JoinError>;
1785
1786 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1787 match &mut self.get_mut().0 {
1788 JoinHandleInner::Task {
1789 task,
1790 awaiter: awaiter_slot,
1791 #[cfg(not(target_family = "wasm"))]
1792 dependency,
1793 } => {
1794 let awaiter = match awaiter_slot {
1795 Some(awaiter) => {
1796 awaiter.update(cx.waker());
1797 awaiter
1798 }
1799 None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
1800 };
1801 let contained_waker = awaiter.contained();
1802 let mut contained_cx = Context::from_waker(contained_waker);
1803 let poll = Pin::new(task).poll(&mut contained_cx);
1804 #[cfg(not(target_family = "wasm"))]
1805 if poll.is_pending() {
1806 for current_dependency in dependency.register_waiter_and_get(contained_waker) {
1807 propagate_blocking_dependency(current_dependency);
1808 }
1809 } else {
1810 dependency.clear_waiter();
1811 }
1812 if poll.is_ready() {
1813 *awaiter_slot = None;
1814 }
1815 match poll {
1816 Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
1817 Poll::Ready(None) => Poll::Ready(Err(JoinError {
1818 message: "async runtime stopped before the task completed".to_string(),
1819 })),
1820 Poll::Pending => Poll::Pending,
1821 }
1822 }
1823 #[cfg(not(target_family = "wasm"))]
1824 JoinHandleInner::Blocking {
1825 receiver,
1826 dependency,
1827 } => {
1828 let poll = Pin::new(receiver).poll(cx);
1829 if poll.is_pending() {
1830 record_blocking_dependency(*dependency);
1831 }
1832 match poll {
1833 Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
1834 Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
1835 message: "async runtime stopped before the blocking task completed".to_string(),
1836 })),
1837 Poll::Pending => Poll::Pending,
1838 }
1839 }
1840 JoinHandleInner::Ready(result) => Poll::Ready(
1841 result
1842 .take()
1843 .expect("JoinHandle polled after completion")
1844 .map(ContainedTaskOutput::into_inner),
1845 ),
1846 }
1847 }
1848}
1849
1850#[derive(Debug, Clone, Copy)]
1851pub struct RuntimeMetricsSnapshot {
1852 pub flavor: RuntimeFlavor,
1853 pub worker_threads: usize,
1854 pub max_blocking_tasks: usize,
1855 pub tasks_spawned: u64,
1856 pub tasks_completed: u64,
1857 pub tasks_panicked: u64,
1858 pub runnable_schedules: u64,
1859 pub runnable_polls: u64,
1860 pub queued_runnables: u64,
1861 pub max_queued_runnables: u64,
1862 pub active_runnables: u64,
1863 pub max_active_runnables: u64,
1864 pub blocking_tasks_started: u64,
1865 pub blocking_tasks_completed: u64,
1866 pub active_blocking_tasks: u64,
1867 pub max_active_blocking_tasks: u64,
1868}
1869
1870#[derive(Default)]
1871struct RuntimeMetrics {
1872 tasks_spawned: AtomicU64,
1873 tasks_completed: AtomicU64,
1874 tasks_panicked: AtomicU64,
1875 runnable_schedules: AtomicU64,
1876 runnable_polls: AtomicU64,
1877 queued_runnables: AtomicU64,
1878 max_queued_runnables: AtomicU64,
1879 active_runnables: AtomicU64,
1880 max_active_runnables: AtomicU64,
1881 blocking_tasks_scheduled: AtomicU64,
1887 blocking_tasks_started: AtomicU64,
1888 blocking_tasks_completed: AtomicU64,
1889 active_blocking_tasks: AtomicU64,
1890 max_active_blocking_tasks: AtomicU64,
1891 reset_generation: AtomicU64,
1895 reset_lock: Mutex<()>,
1896}
1897
1898#[cfg(test)]
1899thread_local! {
1900 static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
1901 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
1902 const { std::cell::RefCell::new(None) };
1903}
1904
1905#[cfg(test)]
1906fn run_runtime_metrics_high_water_test_hook() {
1907 if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1908 hook();
1909 }
1910}
1911
1912impl RuntimeMetrics {
1913 fn runnable_scheduled(&self) {
1914 self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
1915 let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
1916 #[cfg(test)]
1917 run_runtime_metrics_high_water_test_hook();
1918 self
1919 .max_queued_runnables
1920 .fetch_max(queued, Ordering::Relaxed);
1921 }
1922
1923 fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
1924 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
1925 self.runnable_polls.fetch_add(1, Ordering::Relaxed);
1926 let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
1927 #[cfg(test)]
1928 run_runtime_metrics_high_water_test_hook();
1929 self
1930 .max_active_runnables
1931 .fetch_max(active, Ordering::Relaxed);
1932 ActiveRunnableGuard { metrics: self }
1933 }
1934
1935 #[cfg(any(not(target_family = "wasm"), test))]
1936 fn runnable_cancelled(&self) {
1937 self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
1938 }
1939
1940 fn blocking_scheduled(&self) {
1944 self
1945 .blocking_tasks_scheduled
1946 .fetch_add(1, Ordering::Relaxed);
1947 }
1948
1949 fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
1950 self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
1951 if count_active_lane {
1952 let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
1953 #[cfg(test)]
1954 run_runtime_metrics_high_water_test_hook();
1955 self
1956 .max_active_blocking_tasks
1957 .fetch_max(active, Ordering::Relaxed);
1958 }
1959 BlockingTaskGuard {
1960 metrics: Arc::clone(self),
1961 count_active_lane,
1962 }
1963 }
1964
1965 fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
1966 let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
1967 let active_runnables = self.active_runnables.load(Ordering::Relaxed);
1968 let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
1969 RuntimeMetricsSnapshot {
1970 flavor: options.flavor,
1971 worker_threads: options.worker_threads,
1972 max_blocking_tasks: options.max_blocking_tasks,
1973 tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
1974 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
1975 tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
1976 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
1977 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
1978 queued_runnables,
1979 max_queued_runnables: self
1980 .max_queued_runnables
1981 .load(Ordering::Relaxed)
1982 .max(queued_runnables),
1983 active_runnables,
1984 max_active_runnables: self
1985 .max_active_runnables
1986 .load(Ordering::Relaxed)
1987 .max(active_runnables),
1988 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
1989 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
1990 active_blocking_tasks,
1991 max_active_blocking_tasks: self
1992 .max_active_blocking_tasks
1993 .load(Ordering::Relaxed)
1994 .max(active_blocking_tasks),
1995 }
1996 }
1997
1998 fn progress_fingerprint(&self) -> ProgressFingerprint {
2007 loop {
2008 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2009 if !reset_generation.is_multiple_of(2) {
2010 std::hint::spin_loop();
2011 continue;
2012 }
2013 let fingerprint = ProgressFingerprint {
2014 reset_generation,
2015 runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2016 runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2017 tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2018 blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2019 blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2020 blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2021 };
2022 if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2023 return fingerprint;
2024 }
2025 }
2026 }
2027
2028 fn reset(&self) {
2029 let reset_guard = self
2030 .reset_lock
2031 .lock()
2032 .unwrap_or_else(std::sync::PoisonError::into_inner);
2033 let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2041 let Some(resetting_generation) = reset_generation.checked_add(1) else {
2042 drop(reset_guard);
2043 panic!("runtime metrics reset generation space exhausted");
2044 };
2045 let Some(settled_generation) = resetting_generation.checked_add(1) else {
2046 drop(reset_guard);
2047 panic!("runtime metrics reset generation space exhausted");
2048 };
2049 self
2050 .reset_generation
2051 .store(resetting_generation, Ordering::SeqCst);
2052 self.tasks_spawned.store(0, Ordering::Relaxed);
2053 self.tasks_completed.store(0, Ordering::Relaxed);
2054 self.tasks_panicked.store(0, Ordering::Relaxed);
2055 self.runnable_schedules.store(0, Ordering::Relaxed);
2056 self.runnable_polls.store(0, Ordering::Relaxed);
2057 self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2058 self.blocking_tasks_started.store(0, Ordering::Relaxed);
2059 self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2060 self
2061 .reset_generation
2062 .store(settled_generation, Ordering::SeqCst);
2063 }
2064}
2065
2066#[derive(Clone, Copy, PartialEq, Eq)]
2068struct ProgressFingerprint {
2069 reset_generation: u64,
2070 runnable_schedules: u64,
2071 runnable_polls: u64,
2072 tasks_completed: u64,
2073 blocking_tasks_scheduled: u64,
2074 blocking_tasks_started: u64,
2075 blocking_tasks_completed: u64,
2076}
2077
2078struct ActiveRunnableGuard<'a> {
2079 metrics: &'a RuntimeMetrics,
2080}
2081
2082impl Drop for ActiveRunnableGuard<'_> {
2083 fn drop(&mut self) {
2084 self
2085 .metrics
2086 .active_runnables
2087 .fetch_sub(1, Ordering::Relaxed);
2088 }
2089}
2090
2091struct BlockingTaskGuard {
2092 metrics: Arc<RuntimeMetrics>,
2093 count_active_lane: bool,
2094}
2095
2096impl Drop for BlockingTaskGuard {
2097 fn drop(&mut self) {
2098 if self.count_active_lane {
2099 self
2100 .metrics
2101 .active_blocking_tasks
2102 .fetch_sub(1, Ordering::Relaxed);
2103 }
2104 self
2105 .metrics
2106 .blocking_tasks_completed
2107 .fetch_add(1, Ordering::Relaxed);
2108 }
2109}
2110
2111static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2112
2113fn next_runtime_generation() -> u64 {
2114 next_unique_id(
2115 &NEXT_RUNTIME_GENERATION,
2116 "async runtime generation id space exhausted",
2117 )
2118}
2119
2120thread_local! {
2121 static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2122 const { std::cell::Cell::new(None) };
2123}
2124
2125fn active_runtime_generation() -> Option<u64> {
2126 ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2127}
2128
2129struct RuntimeGenerationGuard {
2130 previous: Option<u64>,
2131}
2132
2133impl RuntimeGenerationGuard {
2134 fn enter(generation: u64) -> Self {
2135 Self::enter_optional(Some(generation))
2136 }
2137
2138 fn enter_optional(generation: Option<u64>) -> Self {
2139 Self {
2140 previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2141 }
2142 }
2143}
2144
2145impl Drop for RuntimeGenerationGuard {
2146 fn drop(&mut self) {
2147 ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2148 }
2149}
2150
2151struct ContainedFuture<F> {
2152 future: ManuallyDrop<F>,
2153 generation: u64,
2154}
2155
2156impl<F> ContainedFuture<F> {
2157 fn new(future: F, generation: u64) -> Self {
2158 Self {
2159 future: ManuallyDrop::new(future),
2160 generation,
2161 }
2162 }
2163}
2164
2165impl<F: Future> Future for ContainedFuture<F> {
2166 type Output = F::Output;
2167
2168 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2169 let this = unsafe { self.get_unchecked_mut() };
2171 let _generation = RuntimeGenerationGuard::enter(this.generation);
2172 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2175 }
2176}
2177
2178impl<F> Drop for ContainedFuture<F> {
2179 fn drop(&mut self) {
2180 let _generation = RuntimeGenerationGuard::enter(self.generation);
2181 let _ = catch_unwind_contained(|| {
2182 unsafe { ManuallyDrop::drop(&mut self.future) };
2184 });
2185 }
2186}
2187
2188#[derive(Default)]
2189struct GenerationStop {
2190 stopping: AtomicBool,
2191 publication: Mutex<()>,
2192 parkers: Mutex<Vec<Weak<DriverParker>>>,
2193}
2194
2195impl GenerationStop {
2196 fn is_stopping(&self) -> bool {
2197 self.stopping.load(Ordering::Acquire)
2198 }
2199
2200 fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2201 #[cfg(all(test, not(target_family = "wasm")))]
2202 run_before_generation_stop_publication_lock_test_hook();
2203 self.runnable_claim_guard()
2204 }
2205
2206 fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2207 GenerationStopPublicationGuard {
2208 stop: self,
2209 _publication: self
2210 .publication
2211 .lock()
2212 .unwrap_or_else(std::sync::PoisonError::into_inner),
2213 }
2214 }
2215
2216 fn publish_stopping(&self) {
2217 self.publication_guard().publish_stopping();
2218 }
2219
2220 fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2221 {
2222 let mut parkers = self
2223 .parkers
2224 .lock()
2225 .unwrap_or_else(std::sync::PoisonError::into_inner);
2226 parkers.retain(|candidate| candidate.strong_count() != 0);
2227 parkers.push(Arc::downgrade(parker));
2228 }
2229 if self.is_stopping() {
2230 parker.unpark();
2231 }
2232 GenerationStopGuard {
2233 stop: Arc::clone(self),
2234 parker: Arc::downgrade(parker),
2235 }
2236 }
2237
2238 fn wake_all(&self) -> usize {
2243 let mut count = 0;
2244 let mut registered = self
2245 .parkers
2246 .lock()
2247 .unwrap_or_else(std::sync::PoisonError::into_inner);
2248 registered.retain(|candidate| {
2249 let Some(parker) = candidate.upgrade() else {
2250 return false;
2251 };
2252 count += 1;
2253 parker.unpark();
2256 true
2257 });
2258 count
2259 }
2260
2261 fn begin_shutdown(&self) {
2262 self.publish_stopping();
2263 let parkers = {
2264 let mut registered = self
2265 .parkers
2266 .lock()
2267 .unwrap_or_else(std::sync::PoisonError::into_inner);
2268 let parkers = registered
2269 .iter()
2270 .filter_map(Weak::upgrade)
2271 .collect::<Vec<_>>();
2272 registered.clear();
2273 parkers
2274 };
2275 for parker in parkers {
2276 parker.unpark();
2277 }
2278 }
2279}
2280
2281struct GenerationStopPublicationGuard<'a> {
2282 stop: &'a GenerationStop,
2283 _publication: std::sync::MutexGuard<'a, ()>,
2284}
2285
2286impl GenerationStopPublicationGuard<'_> {
2287 fn is_stopping(&self) -> bool {
2288 self.stop.is_stopping()
2289 }
2290
2291 fn publish_stopping(&self) {
2292 self.stop.stopping.store(true, Ordering::Release);
2293 }
2294}
2295
2296struct GenerationStopGuard {
2297 stop: Arc<GenerationStop>,
2298 parker: Weak<DriverParker>,
2299}
2300
2301impl Drop for GenerationStopGuard {
2302 fn drop(&mut self) {
2303 let Some(parker) = self.parker.upgrade() else {
2304 return;
2305 };
2306 let mut registered = self
2307 .stop
2308 .parkers
2309 .lock()
2310 .unwrap_or_else(std::sync::PoisonError::into_inner);
2311 if let Some(index) = registered.iter().position(|candidate| {
2312 candidate
2313 .upgrade()
2314 .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2315 }) {
2316 registered.swap_remove(index);
2317 }
2318 }
2319}
2320
2321struct GenerationWorkState {
2322 closed: bool,
2323 next_task_id: u64,
2324 active: usize,
2325 abort_handles: FxHashMap<u64, AbortHandle>,
2326}
2327
2328struct GenerationWork {
2329 id: u64,
2330 state: Mutex<GenerationWorkState>,
2331 idle: Condvar,
2332}
2333
2334impl GenerationWork {
2335 fn new() -> Arc<Self> {
2336 Arc::new(Self {
2337 id: next_runtime_generation(),
2338 state: Mutex::new(GenerationWorkState {
2339 closed: false,
2340 next_task_id: 0,
2341 active: 0,
2342 abort_handles: FxHashMap::default(),
2343 }),
2344 idle: Condvar::new(),
2345 })
2346 }
2347
2348 fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2349 let (abort_handle, abort_registration) = AbortHandle::new_pair();
2350 let mut state = self
2351 .state
2352 .lock()
2353 .unwrap_or_else(std::sync::PoisonError::into_inner);
2354 if state.closed {
2355 return None;
2356 }
2357 let task_id = state.next_task_id;
2358 let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2359 drop(state);
2360 panic!("async runtime generation task id space exhausted");
2361 };
2362 state.next_task_id = next_task_id;
2363 state.active += 1;
2364 state.abort_handles.insert(task_id, abort_handle);
2365 Some((
2366 abort_registration,
2367 GenerationWorkGuard {
2368 work: Arc::clone(self),
2369 task_id: Some(task_id),
2370 },
2371 ))
2372 }
2373
2374 fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2375 let mut state = self
2376 .state
2377 .lock()
2378 .unwrap_or_else(std::sync::PoisonError::into_inner);
2379 if state.closed {
2380 return None;
2381 }
2382 state.active += 1;
2383 Some(GenerationWorkGuard {
2384 work: Arc::clone(self),
2385 task_id: None,
2386 })
2387 }
2388
2389 fn close_and_abort(&self) {
2390 let abort_handles = {
2391 let mut state = self
2392 .state
2393 .lock()
2394 .unwrap_or_else(std::sync::PoisonError::into_inner);
2395 state.closed = true;
2396 state.abort_handles.values().cloned().collect::<Vec<_>>()
2397 };
2398 for abort_handle in abort_handles {
2399 abort_handle.abort();
2400 }
2401 }
2402
2403 fn wait_until_idle(&self) {
2404 let mut state = self
2405 .state
2406 .lock()
2407 .unwrap_or_else(std::sync::PoisonError::into_inner);
2408 while state.active != 0 {
2409 state = self
2410 .idle
2411 .wait(state)
2412 .unwrap_or_else(std::sync::PoisonError::into_inner);
2413 }
2414 }
2415}
2416
2417struct GenerationWorkGuard {
2418 work: Arc<GenerationWork>,
2419 task_id: Option<u64>,
2420}
2421
2422impl Drop for GenerationWorkGuard {
2423 fn drop(&mut self) {
2424 let mut state = self
2425 .work
2426 .state
2427 .lock()
2428 .unwrap_or_else(std::sync::PoisonError::into_inner);
2429 if let Some(task_id) = self.task_id {
2430 state.abort_handles.remove(&task_id);
2431 }
2432 state.active = state
2433 .active
2434 .checked_sub(1)
2435 .expect("generation work count underflow");
2436 if state.active == 0 {
2437 self.work.idle.notify_all();
2438 }
2439 }
2440}
2441
2442struct RegisteredTaskFuture<F> {
2448 future: ManuallyDrop<F>,
2449 registration: Option<GenerationWorkGuard>,
2450 generation: u64,
2451}
2452
2453impl<F> RegisteredTaskFuture<F> {
2454 fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2455 Self {
2456 future: ManuallyDrop::new(future),
2457 registration: Some(registration),
2458 generation,
2459 }
2460 }
2461}
2462
2463impl<F: Future> Future for RegisteredTaskFuture<F> {
2464 type Output = F::Output;
2465
2466 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2467 let this = unsafe { self.get_unchecked_mut() };
2469 let _generation = RuntimeGenerationGuard::enter(this.generation);
2470 unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2473 }
2474}
2475
2476impl<F> Drop for RegisteredTaskFuture<F> {
2477 fn drop(&mut self) {
2478 let _generation = RuntimeGenerationGuard::enter(self.generation);
2479 let _ = catch_unwind_contained(|| {
2480 unsafe { ManuallyDrop::drop(&mut self.future) };
2482 });
2483 drop(self.registration.take());
2486 }
2487}
2488
2489#[cfg(not(target_family = "wasm"))]
2490struct RegisteredBlockingFunction<F> {
2491 function: Option<F>,
2492 registration: Option<GenerationWorkGuard>,
2493 generation: u64,
2494}
2495
2496#[cfg(not(target_family = "wasm"))]
2497impl<F> RegisteredBlockingFunction<F> {
2498 fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2499 Self {
2500 function: Some(function),
2501 registration: Some(registration),
2502 generation,
2503 }
2504 }
2505
2506 fn run<T>(mut self) -> Result<T, JoinError>
2507 where
2508 F: FnOnce() -> T,
2509 {
2510 let _generation = RuntimeGenerationGuard::enter(self.generation);
2511 let result = catch_unwind_join_error(
2512 self
2513 .function
2514 .take()
2515 .expect("blocking function must be available"),
2516 );
2517 drop(self.registration.take());
2518 result
2519 }
2520}
2521
2522#[cfg(not(target_family = "wasm"))]
2523impl<F> Drop for RegisteredBlockingFunction<F> {
2524 fn drop(&mut self) {
2525 let _generation = RuntimeGenerationGuard::enter(self.generation);
2526 if let Some(function) = self.function.take() {
2527 let _ = catch_unwind_contained(|| drop(function));
2528 }
2529 drop(self.registration.take());
2533 }
2534}
2535
2536fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2537 let _active = metrics.runnable_started();
2538 let _ = catch_unwind_contained(|| runnable.run());
2539}
2540
2541#[cfg(not(target_family = "wasm"))]
2542enum RunnableClaim {
2543 Empty,
2544 Run(Runnable),
2545 Cancel(Runnable),
2546}
2547
2548static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2549static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2550
2551fn next_current_thread_dispatch_id() -> u64 {
2552 next_unique_id(
2553 &NEXT_CURRENT_THREAD_DISPATCH_ID,
2554 "CurrentThread dispatch id space exhausted",
2555 )
2556 .checked_add(1)
2557 .expect("CurrentThread dispatch id space exhausted")
2558}
2559
2560struct CurrentThreadBlockingAdmission {
2561 state: Mutex<CurrentThreadBlockingAdmissionState>,
2562}
2563
2564struct CurrentThreadBlockingAdmissionState {
2565 owner: Option<BlockingOwnerToken>,
2566 closed: bool,
2567 #[cfg(not(target_family = "wasm"))]
2568 queue: BlockingQueue,
2569}
2570
2571struct CurrentThreadExecutor {
2572 id: u64,
2573 generation: u64,
2574 stop: Arc<GenerationStop>,
2575 queue: Mutex<CurrentThreadQueue>,
2576 blocking_admission: CurrentThreadBlockingAdmission,
2577 draining: AtomicBool,
2578 scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2579 scheduler_idle: Condvar,
2580 dispatch_pending: AtomicU64,
2584 recovery_dispatch: AtomicU64,
2588 cancelling_failed_dispatch: AtomicBool,
2591 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2592 task_drivers: &'static CurrentThreadTaskDriverRegistry,
2593 metrics: Arc<RuntimeMetrics>,
2594 threadless: bool,
2602 park_deadline: Option<Duration>,
2603 host_timers: Arc<HostTimerRegistry>,
2613}
2614
2615struct CurrentThreadQueue {
2616 closed: bool,
2617 runnables: VecDeque<Runnable>,
2618}
2619
2620#[derive(Default)]
2621struct CurrentThreadSchedulerState {
2622 active_host_turns: usize,
2623 active_dispatch_calls: usize,
2624 dispatch_publications: Vec<CurrentThreadDispatchPublication>,
2625}
2626
2627struct CurrentThreadDispatchPublication {
2628 dispatch: u64,
2629 republish_requested: bool,
2630}
2631
2632#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2633enum CurrentThreadDispatchRequest {
2634 Accepted,
2635 Coalesced,
2636 Unavailable,
2637}
2638
2639#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2640struct CurrentThreadTaskDispatchFailure {
2641 dispatch: u64,
2642 epoch: u64,
2643}
2644
2645#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2646enum CurrentThreadHostDispatchResult {
2647 Accepted,
2648 Unavailable,
2649 Failed(CurrentThreadTaskDispatchFailure),
2650}
2651
2652enum CurrentThreadDispatchPublicationStep {
2653 Republish,
2654 Complete(Option<CurrentThreadHostDispatchResult>),
2655}
2656
2657struct CurrentThreadHostTurn {
2658 executor: Arc<CurrentThreadExecutor>,
2659 draining: bool,
2660 _generation: RuntimeGenerationGuard,
2661}
2662
2663impl CurrentThreadHostTurn {
2664 fn drive(&mut self) {
2665 let executor = Arc::clone(&self.executor);
2666 executor.drive_admitted_host_turn(self);
2667 }
2668
2669 fn release_draining(&mut self) {
2670 if !self.draining {
2671 return;
2672 }
2673 let _scheduler = self
2674 .executor
2675 .scheduler_idle_lock
2676 .lock()
2677 .unwrap_or_else(std::sync::PoisonError::into_inner);
2678 self.executor.draining.store(false, Ordering::Release);
2679 self.draining = false;
2680 }
2681}
2682
2683impl Drop for CurrentThreadHostTurn {
2684 fn drop(&mut self) {
2685 let mut scheduler = self
2686 .executor
2687 .scheduler_idle_lock
2688 .lock()
2689 .unwrap_or_else(std::sync::PoisonError::into_inner);
2690 if self.draining {
2691 self.executor.draining.store(false, Ordering::Release);
2692 self.draining = false;
2693 }
2694 scheduler.active_host_turns = scheduler
2695 .active_host_turns
2696 .checked_sub(1)
2697 .expect("CurrentThread active host-turn count underflow");
2698 self.executor.scheduler_idle.notify_all();
2699 }
2700}
2701
2702#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
2709pub struct CurrentThreadTaskCallbackLease {
2710 _host_turn: CurrentThreadHostTurn,
2711 _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
2712}
2713
2714impl CurrentThreadTaskCallbackLease {
2715 fn new(host_turn: CurrentThreadHostTurn) -> Self {
2716 Self {
2717 _host_turn: host_turn,
2718 _not_send: std::marker::PhantomData,
2719 }
2720 }
2721}
2722
2723struct CurrentThreadDispatchCall<'a> {
2724 executor: &'a CurrentThreadExecutor,
2725 publication: Option<u64>,
2726 _generation: RuntimeGenerationGuard,
2727}
2728
2729impl CurrentThreadDispatchCall<'_> {
2730 fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
2731 let dispatch = self
2732 .publication
2733 .expect("the CurrentThread dispatch call must own a publication");
2734 let index = scheduler
2735 .dispatch_publications
2736 .iter()
2737 .position(|publication| publication.dispatch == dispatch)
2738 .expect("the CurrentThread dispatch publication must remain registered");
2739 scheduler.dispatch_publications.swap_remove(index);
2740 self.publication = None;
2741 }
2742}
2743
2744impl Drop for CurrentThreadDispatchCall<'_> {
2745 fn drop(&mut self) {
2746 let mut scheduler = self
2747 .executor
2748 .scheduler_idle_lock
2749 .lock()
2750 .unwrap_or_else(std::sync::PoisonError::into_inner);
2751 if let Some(dispatch) = self.publication.take()
2752 && let Some(index) = scheduler
2753 .dispatch_publications
2754 .iter()
2755 .position(|publication| publication.dispatch == dispatch)
2756 {
2757 scheduler.dispatch_publications.swap_remove(index);
2758 }
2759 scheduler.active_dispatch_calls = scheduler
2760 .active_dispatch_calls
2761 .checked_sub(1)
2762 .expect("CurrentThread active dispatch-call count underflow");
2763 self.executor.scheduler_idle.notify_all();
2764 }
2765}
2766
2767struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
2768
2769impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
2770 fn drop(&mut self) {
2771 self
2772 .0
2773 .cancelling_failed_dispatch
2774 .store(false, Ordering::Release);
2775 }
2776}
2777
2778enum CurrentThreadDispatchFailureAction<'a> {
2779 None,
2780 Cancel(CurrentThreadDispatchCancellationGuard<'a>),
2781 Publish {
2782 dispatch: u64,
2783 call: CurrentThreadDispatchCall<'a>,
2784 },
2785}
2786
2787struct CurrentThreadDispatchFailureRecovery<'a> {
2788 action: CurrentThreadDispatchFailureAction<'a>,
2789 _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
2790}
2791
2792struct CurrentThreadRegistryCancellationGuard<'a> {
2793 registry: &'a CurrentThreadTaskDriverRegistry,
2794 failure: Option<CurrentThreadTaskDispatchFailure>,
2795}
2796
2797impl CurrentThreadRegistryCancellationGuard<'_> {
2798 fn new(
2799 registry: &CurrentThreadTaskDriverRegistry,
2800 failure: CurrentThreadTaskDispatchFailure,
2801 ) -> CurrentThreadRegistryCancellationGuard<'_> {
2802 CurrentThreadRegistryCancellationGuard {
2803 registry,
2804 failure: Some(failure),
2805 }
2806 }
2807}
2808
2809impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
2810 fn drop(&mut self) {
2811 if let Some(failure) = self.failure.take() {
2812 let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
2813 }
2814 }
2815}
2816
2817struct CurrentThreadBlockingGuard<'a> {
2818 executor: &'a CurrentThreadExecutor,
2819 owner: BlockingOwnerToken,
2820 previous_owner: Option<BlockingOwnerToken>,
2821 owns_lane: bool,
2822}
2823
2824impl CurrentThreadBlockingGuard<'_> {
2825 fn counts_active_lane(&self) -> bool {
2826 self.owns_lane
2827 }
2828}
2829
2830impl Drop for CurrentThreadBlockingGuard<'_> {
2831 fn drop(&mut self) {
2832 BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
2833 if !self.owns_lane {
2834 return;
2835 }
2836 let mut state = self
2837 .executor
2838 .blocking_admission
2839 .state
2840 .lock()
2841 .unwrap_or_else(std::sync::PoisonError::into_inner);
2842 debug_assert_eq!(state.owner, Some(self.owner));
2843 state.owner = None;
2844 drop(state);
2845 self.executor.stop.wake_all();
2846 }
2847}
2848
2849impl CurrentThreadExecutor {
2850 const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
2853 #[cfg(not(target_family = "wasm"))]
2855 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
2856
2857 #[cfg(test)]
2861 fn new(metrics: Arc<RuntimeMetrics>) -> Self {
2862 Self::with_detection(metrics, THREADLESS_BUILD, None)
2863 }
2864
2865 #[cfg(test)]
2871 fn with_detection(
2872 metrics: Arc<RuntimeMetrics>,
2873 threadless: bool,
2874 park_deadline: Option<Duration>,
2875 ) -> Self {
2876 Self::with_generation(
2877 metrics,
2878 threadless,
2879 park_deadline,
2880 u64::MAX,
2881 Arc::new(GenerationStop::default()),
2882 )
2883 }
2884
2885 fn with_generation(
2886 metrics: Arc<RuntimeMetrics>,
2887 threadless: bool,
2888 park_deadline: Option<Duration>,
2889 generation: u64,
2890 stop: Arc<GenerationStop>,
2891 ) -> Self {
2892 Self {
2893 id: next_unique_id(
2894 &NEXT_RUNTIME_EXECUTOR_ID,
2895 "async runtime executor id space exhausted",
2896 ),
2897 generation,
2898 stop,
2899 queue: Mutex::new(CurrentThreadQueue {
2900 closed: false,
2901 runnables: VecDeque::new(),
2902 }),
2903 blocking_admission: CurrentThreadBlockingAdmission {
2904 state: Mutex::new(CurrentThreadBlockingAdmissionState {
2905 owner: None,
2906 closed: false,
2907 #[cfg(not(target_family = "wasm"))]
2908 queue: BlockingQueue {
2909 closed: false,
2910 head: None,
2911 tail: None,
2912 jobs: FxHashMap::default(),
2913 },
2914 }),
2915 },
2916 draining: AtomicBool::new(false),
2917 scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
2918 scheduler_idle: Condvar::new(),
2919 dispatch_pending: AtomicU64::new(0),
2920 recovery_dispatch: AtomicU64::new(0),
2921 cancelling_failed_dispatch: AtomicBool::new(false),
2922 task_dispatch: dispatch_current_thread_tasks,
2923 task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
2924 metrics,
2925 threadless,
2926 park_deadline,
2927 host_timers: Arc::new(HostTimerRegistry::default()),
2928 }
2929 }
2930
2931 fn fresh_owner_token(&self) -> BlockingOwnerToken {
2932 BlockingOwnerToken {
2933 executor_id: self.id,
2934 frame: next_unique_id(
2935 &NEXT_BLOCKING_FRAME,
2936 "blocking owner frame id space exhausted",
2937 ),
2938 }
2939 }
2940
2941 fn enter_blocking_owner(
2942 &self,
2943 owner: BlockingOwnerToken,
2944 owns_lane: bool,
2945 ) -> CurrentThreadBlockingGuard<'_> {
2946 let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
2947 CurrentThreadBlockingGuard {
2948 executor: self,
2949 owner,
2950 previous_owner,
2951 owns_lane,
2952 }
2953 }
2954
2955 fn schedule_blocking<F, T>(
2956 self: &Arc<Self>,
2957 function: F,
2958 registration: GenerationWorkGuard,
2959 ) -> JoinHandle<T>
2960 where
2961 F: FnOnce() -> T + Send + 'static,
2962 T: Send + 'static,
2963 {
2964 self.metrics.blocking_scheduled();
2966 let mut function = Some(function);
2967 let mut registration = Some(registration);
2968 let ambient_owner = BLOCKING_OWNER
2969 .with(std::cell::Cell::get)
2970 .filter(|owner| owner.executor_id == self.id);
2971 let mut run_owner = None;
2972 let mut owns_lane = false;
2973 let mut rejected = false;
2974
2975 #[cfg(not(target_family = "wasm"))]
2976 let (sender, receiver) = oneshot::channel();
2977 #[cfg(not(target_family = "wasm"))]
2978 let dependency = BlockingDependency {
2979 job: BlockingJobId(next_unique_id(
2980 &NEXT_BLOCKING_JOB_ID,
2981 "blocking job id space exhausted",
2982 )),
2983 owner: ambient_owner,
2984 };
2985 #[cfg(not(target_family = "wasm"))]
2986 let mut queued = false;
2987
2988 {
2989 let mut state = self
2990 .blocking_admission
2991 .state
2992 .lock()
2993 .unwrap_or_else(std::sync::PoisonError::into_inner);
2994 #[cfg(all(test, not(target_family = "wasm")))]
2995 run_current_thread_blocking_claim_test_hook();
2996 if self.stop.is_stopping()
3000 || state.closed
3001 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3002 {
3003 rejected = true;
3004 } else if ambient_owner.is_some() && state.owner == ambient_owner {
3005 run_owner = ambient_owner;
3006 } else {
3007 #[cfg(not(target_family = "wasm"))]
3008 let queue_empty = state.queue.is_empty();
3009 #[cfg(target_family = "wasm")]
3010 let queue_empty = true;
3011
3012 if state.owner.is_none() && queue_empty {
3013 let owner = self.fresh_owner_token();
3014 state.owner = Some(owner);
3015 run_owner = Some(owner);
3016 owns_lane = true;
3017 } else {
3018 #[cfg(not(target_family = "wasm"))]
3019 {
3020 let function = function
3021 .take()
3022 .expect("blocking function must be available");
3023 let registration = registration
3024 .take()
3025 .expect("blocking registration must be available");
3026 let generation = self.generation;
3027 let function = RegisteredBlockingFunction::new(function, registration, generation);
3028 state.queue.push(QueuedBlocking {
3029 id: dependency.job,
3030 run: Box::new(move || {
3031 let _generation = RuntimeGenerationGuard::enter(generation);
3032 let result = function
3033 .run()
3034 .map(|output| ContainedTaskOutput::new(output, generation));
3035 let _ = sender.send(result);
3036 }),
3037 });
3038 queued = true;
3039 }
3040 #[cfg(target_family = "wasm")]
3041 {
3042 rejected = true;
3046 }
3047 }
3048 }
3049 }
3050
3051 #[cfg(not(target_family = "wasm"))]
3052 if queued {
3053 self.stop.wake_all();
3054 if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3055 self.request_drain();
3056 }
3057 return JoinHandle(JoinHandleInner::Blocking {
3058 receiver,
3059 dependency,
3060 });
3061 }
3062
3063 if rejected {
3064 let _generation = RuntimeGenerationGuard::enter(self.generation);
3065 let _ = catch_unwind_contained(|| drop(function.take()));
3066 drop(registration.take());
3067 return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3068 message: "the async runtime stopped before the blocking task could start".to_string(),
3069 }))));
3070 }
3071
3072 let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3073 let blocking = self.enter_blocking_owner(owner, owns_lane);
3074 let _registration = registration
3075 .take()
3076 .expect("blocking registration must be available");
3077 let _generation = RuntimeGenerationGuard::enter(self.generation);
3078 let result = {
3079 let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3080 catch_unwind_join_error(
3081 function
3082 .take()
3083 .expect("blocking function must be available"),
3084 )
3085 }
3086 .map(|output| ContainedTaskOutput::new(output, self.generation));
3087 drop(blocking);
3088 #[cfg(not(target_family = "wasm"))]
3089 if owns_lane && !self.draining.load(Ordering::Acquire) {
3090 self.service_queued_blocking_work();
3094 }
3095 JoinHandle(JoinHandleInner::Ready(Some(result)))
3096 }
3097
3098 #[cfg(not(target_family = "wasm"))]
3099 fn take_queued_blocking(
3100 &self,
3101 ) -> Option<(
3102 Box<dyn FnOnce() + Send + 'static>,
3103 CurrentThreadBlockingGuard<'_>,
3104 )> {
3105 let (job, owner) = {
3106 let mut state = self
3107 .blocking_admission
3108 .state
3109 .lock()
3110 .unwrap_or_else(std::sync::PoisonError::into_inner);
3111 #[cfg(all(test, not(target_family = "wasm")))]
3112 run_current_thread_blocking_claim_test_hook();
3113 if self.stop.is_stopping()
3114 || state.closed
3115 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3116 || state.owner.is_some()
3117 {
3118 return None;
3119 }
3120 let job = state.queue.pop_front()?;
3121 let owner = self.fresh_owner_token();
3122 state.owner = Some(owner);
3123 (job, owner)
3124 };
3125 Some((job, self.enter_blocking_owner(owner, true)))
3126 }
3127
3128 #[cfg(not(target_family = "wasm"))]
3129 fn run_one_blocking(&self) -> bool {
3130 let Some((job, blocking)) = self.take_queued_blocking() else {
3131 return false;
3132 };
3133 let _generation = RuntimeGenerationGuard::enter(self.generation);
3134 {
3135 let _task = self.metrics.blocking_started(true);
3136 let _ = catch_unwind_contained(job);
3137 }
3138 drop(blocking);
3139 true
3140 }
3141
3142 #[cfg(not(target_family = "wasm"))]
3143 fn service_queued_blocking_work(&self) {
3144 while self.run_one_blocking() {}
3145 }
3146
3147 #[cfg(not(target_family = "wasm"))]
3148 fn has_serviceable_blocking_work(&self) -> bool {
3149 let state = self
3150 .blocking_admission
3151 .state
3152 .lock()
3153 .unwrap_or_else(std::sync::PoisonError::into_inner);
3154 !state.closed && state.owner.is_none() && !state.queue.is_empty()
3155 }
3156
3157 #[cfg(not(target_family = "wasm"))]
3158 fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3159 let Some(ambient_owner) = BLOCKING_OWNER
3160 .with(std::cell::Cell::get)
3161 .filter(|owner| owner.executor_id == self.id)
3162 else {
3163 return false;
3164 };
3165 let Some(current) = dependency.select_for_owner(ambient_owner) else {
3166 return false;
3167 };
3168 let job = {
3169 let mut state = self
3170 .blocking_admission
3171 .state
3172 .lock()
3173 .unwrap_or_else(std::sync::PoisonError::into_inner);
3174 #[cfg(all(test, not(target_family = "wasm")))]
3175 run_current_thread_blocking_claim_test_hook();
3176 if self.stop.is_stopping()
3177 || state.closed
3178 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3179 || state.owner != Some(ambient_owner)
3180 {
3181 return false;
3182 }
3183 if dependency.try_claim(¤t) {
3184 state.queue.remove(current.publication.dependency.job)
3185 } else {
3186 None
3187 }
3188 };
3189 let Some(job) = job else {
3190 return false;
3191 };
3192 let _generation = RuntimeGenerationGuard::enter(self.generation);
3193 let _task = self.metrics.blocking_started(false);
3194 let _ = catch_unwind_contained(job);
3195 true
3196 }
3197
3198 fn close_blocking_admission(&self) {
3199 #[cfg(not(target_family = "wasm"))]
3200 let queued = {
3201 let mut state = self
3202 .blocking_admission
3203 .state
3204 .lock()
3205 .unwrap_or_else(std::sync::PoisonError::into_inner);
3206 state.closed = true;
3207 state.queue.closed = true;
3208 state.queue.take_all()
3209 };
3210 #[cfg(target_family = "wasm")]
3211 {
3212 let mut state = self
3213 .blocking_admission
3214 .state
3215 .lock()
3216 .unwrap_or_else(std::sync::PoisonError::into_inner);
3217 state.closed = true;
3218 }
3219 #[cfg(not(target_family = "wasm"))]
3220 for job in queued {
3221 let _ = catch_unwind_contained(|| drop(job));
3222 }
3223 }
3224
3225 #[cfg(test)]
3226 fn with_task_dispatch(
3227 metrics: Arc<RuntimeMetrics>,
3228 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3229 ) -> Self {
3230 Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
3231 }
3232
3233 #[cfg(test)]
3234 fn with_task_dispatch_registry(
3235 metrics: Arc<RuntimeMetrics>,
3236 task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3237 task_drivers: &'static CurrentThreadTaskDriverRegistry,
3238 ) -> Self {
3239 let mut executor = Self::new(metrics);
3240 executor.task_dispatch = task_dispatch;
3241 executor.task_drivers = task_drivers;
3242 executor
3243 }
3244
3245 fn schedule(self: &Arc<Self>, runnable: Runnable) {
3246 self.metrics.runnable_scheduled();
3247 let rejected = {
3248 let mut queue = self
3249 .queue
3250 .lock()
3251 .unwrap_or_else(std::sync::PoisonError::into_inner);
3252 if queue.closed || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3253 Some(runnable)
3254 } else {
3255 queue.runnables.push_back(runnable);
3256 None
3257 }
3258 };
3259 if let Some(runnable) = rejected {
3260 self
3261 .metrics
3262 .queued_runnables
3263 .fetch_sub(1, Ordering::Relaxed);
3264 let _generation = RuntimeGenerationGuard::enter(self.generation);
3265 let _ = catch_unwind_contained(|| drop(runnable));
3266 return;
3267 }
3268 self.stop.wake_all();
3272 if !self.draining.load(Ordering::Acquire) {
3273 self.request_drain();
3274 }
3275 }
3276
3277 fn request_drain(self: &Arc<Self>) {
3286 let _ = self.request_drain_with(next_current_thread_dispatch_id);
3287 }
3288
3289 fn lock_scheduler_for_publication(
3290 &self,
3291 ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
3292 #[cfg(all(test, not(target_family = "wasm")))]
3293 match self.scheduler_idle_lock.try_lock() {
3294 Ok(scheduler) => scheduler,
3295 Err(std::sync::TryLockError::WouldBlock) => {
3296 run_current_thread_dispatch_publication_blocked_test_hook();
3297 self
3298 .scheduler_idle_lock
3299 .lock()
3300 .unwrap_or_else(std::sync::PoisonError::into_inner)
3301 }
3302 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
3303 }
3304 #[cfg(any(not(test), target_family = "wasm"))]
3305 self
3306 .scheduler_idle_lock
3307 .lock()
3308 .unwrap_or_else(std::sync::PoisonError::into_inner)
3309 }
3310
3311 fn request_drain_with(
3312 self: &Arc<Self>,
3313 next_dispatch: impl FnOnce() -> u64,
3314 ) -> CurrentThreadDispatchRequest {
3315 let mut scheduler = self.lock_scheduler_for_publication();
3316 if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3317 return CurrentThreadDispatchRequest::Unavailable;
3318 }
3319 if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
3320 return CurrentThreadDispatchRequest::Coalesced;
3321 }
3322 let dispatch = next_dispatch();
3323 if self
3324 .dispatch_pending
3325 .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
3326 .is_err()
3327 {
3328 return CurrentThreadDispatchRequest::Coalesced;
3329 }
3330 let dispatch_call = self
3331 .begin_host_dispatch_publication(&mut scheduler, dispatch)
3332 .expect("a fresh CurrentThread dispatch must own its publication");
3333 drop(scheduler);
3334 match self.publish_host_dispatch(dispatch, dispatch_call) {
3335 CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
3336 CurrentThreadDispatchRequest::Accepted
3337 }
3338 CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
3339 }
3340 }
3341
3342 fn claim_dispatch_call<'a>(
3343 &'a self,
3344 scheduler: &mut CurrentThreadSchedulerState,
3345 ) -> CurrentThreadDispatchCall<'a> {
3346 let generation = RuntimeGenerationGuard::enter(self.generation);
3347 let active_dispatch_calls = scheduler
3348 .active_dispatch_calls
3349 .checked_add(1)
3350 .expect("CurrentThread active dispatch-call count exhausted");
3351 scheduler.active_dispatch_calls = active_dispatch_calls;
3352 CurrentThreadDispatchCall {
3353 executor: self,
3354 publication: None,
3355 _generation: generation,
3356 }
3357 }
3358
3359 fn begin_host_dispatch_publication<'a>(
3360 &'a self,
3361 scheduler: &mut CurrentThreadSchedulerState,
3362 dispatch: u64,
3363 ) -> Option<CurrentThreadDispatchCall<'a>> {
3364 if let Some(publication) = scheduler
3366 .dispatch_publications
3367 .iter_mut()
3368 .find(|publication| publication.dispatch == dispatch)
3369 {
3370 publication.republish_requested = true;
3371 return None;
3372 }
3373 let mut dispatch_call = self.claim_dispatch_call(scheduler);
3374 scheduler
3375 .dispatch_publications
3376 .push(CurrentThreadDispatchPublication {
3377 dispatch,
3378 republish_requested: false,
3379 });
3380 dispatch_call.publication = Some(dispatch);
3381 Some(dispatch_call)
3382 }
3383
3384 fn publish_host_dispatch(
3385 self: &Arc<Self>,
3386 dispatch: u64,
3387 mut dispatch_call: CurrentThreadDispatchCall<'_>,
3388 ) -> CurrentThreadHostDispatchResult {
3389 let mut panic_payload = RetainedPanicPayload::default();
3390 loop {
3391 let result = match catch_unwind(AssertUnwindSafe(|| {
3392 #[cfg(all(test, not(target_family = "wasm")))]
3393 run_current_thread_dispatch_call_test_hook();
3394 let result = (self.task_dispatch)(dispatch);
3395 #[cfg(all(test, not(target_family = "wasm")))]
3396 run_current_thread_dispatch_returned_test_hook();
3397 result
3398 })) {
3399 Ok(result) => Some(result),
3400 Err(payload) => {
3401 panic_payload.capture(payload);
3402 None
3403 }
3404 };
3405
3406 let step = catch_unwind(AssertUnwindSafe(|| {
3407 let (republish, cancelling, failure, result) = {
3408 let mut scheduler = self
3409 .scheduler_idle_lock
3410 .lock()
3411 .unwrap_or_else(std::sync::PoisonError::into_inner);
3412 let index = scheduler
3413 .dispatch_publications
3414 .iter()
3415 .position(|publication| publication.dispatch == dispatch)
3416 .expect("the CurrentThread dispatch publication must remain registered");
3417 let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
3418 let republish = dispatch_remains_pending
3419 && !self.stop.is_stopping()
3420 && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
3421 && scheduler.dispatch_publications[index].republish_requested;
3422 if republish {
3423 if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3424 let _ = self.task_drivers.rearm_failed_publication(failure);
3425 }
3426 scheduler.dispatch_publications[index].republish_requested = false;
3427 (true, None, None, result)
3428 } else {
3429 dispatch_call.finish_publication(&mut scheduler);
3430 let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3431 #[cfg(all(test, not(target_family = "wasm")))]
3432 run_current_thread_failed_publication_retired_test_hook();
3433 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3434 } else {
3435 None
3436 };
3437 let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
3438 && dispatch_remains_pending)
3439 .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
3440 .flatten();
3441 let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
3442 && !dispatch_remains_pending
3443 {
3444 Some(CurrentThreadHostDispatchResult::Accepted)
3447 } else {
3448 result
3449 };
3450 (false, cancelling, failure, result)
3451 }
3452 };
3453
3454 if republish {
3455 return CurrentThreadDispatchPublicationStep::Republish;
3456 }
3457 if let Some(cancelling) = cancelling {
3458 self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
3459 }
3460 if let Some(failure) = failure {
3461 self.run_host_dispatch_failure(failure.action);
3462 }
3463 CurrentThreadDispatchPublicationStep::Complete(result)
3464 }));
3465
3466 match step {
3467 Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
3468 Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
3469 if panic_payload.is_some() {
3470 panic_payload.resume();
3471 }
3472 return result
3473 .expect("a CurrentThread host dispatch without a result must resume its panic");
3474 }
3475 Err(payload) => {
3476 panic_payload.capture(payload);
3477 panic_payload.resume();
3478 }
3479 }
3480 }
3481 }
3482
3483 fn claim_host_turn(
3484 self: &Arc<Self>,
3485 scheduler: &mut CurrentThreadSchedulerState,
3486 ) -> CurrentThreadHostTurn {
3487 scheduler.active_host_turns = scheduler
3488 .active_host_turns
3489 .checked_add(1)
3490 .expect("CurrentThread active host-turn count exhausted");
3491 self.draining.store(true, Ordering::Release);
3492 CurrentThreadHostTurn {
3493 executor: Arc::clone(self),
3494 draining: true,
3495 _generation: RuntimeGenerationGuard::enter(self.generation),
3496 }
3497 }
3498
3499 fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
3500 let mut scheduler = self
3501 .scheduler_idle_lock
3502 .lock()
3503 .unwrap_or_else(std::sync::PoisonError::into_inner);
3504 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3505 return None;
3506 }
3507 if dispatch == 0
3508 || self
3509 .dispatch_pending
3510 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3511 .is_err()
3512 {
3513 return None;
3514 }
3515 #[cfg(all(test, not(target_family = "wasm")))]
3516 run_current_thread_dispatch_claim_test_hook();
3517 let _ =
3518 self
3519 .recovery_dispatch
3520 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
3521 Some(self.claim_host_turn(&mut scheduler))
3522 }
3523
3524 #[cfg(test)]
3525 fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
3526 let mut scheduler = self
3527 .scheduler_idle_lock
3528 .lock()
3529 .unwrap_or_else(std::sync::PoisonError::into_inner);
3530 if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3531 return None;
3532 }
3533 Some(self.claim_host_turn(&mut scheduler))
3534 }
3535
3536 #[cfg(test)]
3537 fn drive_host_turn(self: &Arc<Self>) {
3538 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3539 let host_turn = if dispatch == 0 {
3540 self.try_admit_host_turn_without_dispatch()
3541 } else {
3542 self.try_admit_host_turn(dispatch)
3543 };
3544 if let Some(mut host_turn) = host_turn {
3545 host_turn.drive();
3546 }
3547 }
3548
3549 #[cfg(test)]
3550 fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
3551 if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
3552 host_turn.drive();
3553 }
3554 }
3555
3556 fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
3560 #[cfg(not(target_family = "wasm"))]
3561 let mut runnable_streak = 0usize;
3562 for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
3563 #[cfg(not(target_family = "wasm"))]
3564 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
3565 runnable_streak = 0;
3566 if self.run_one_blocking() {
3567 continue;
3568 }
3569 }
3570 if self.drain_one() {
3571 #[cfg(not(target_family = "wasm"))]
3572 {
3573 runnable_streak += 1;
3574 }
3575 continue;
3576 }
3577 #[cfg(not(target_family = "wasm"))]
3578 if self.run_one_blocking() {
3579 runnable_streak = 0;
3580 continue;
3581 }
3582 break;
3583 }
3584 host_turn.release_draining();
3589 self.request_drain_if_work_remains();
3590 }
3591
3592 fn request_drain_if_queued(self: &Arc<Self>) {
3593 if !self.has_serviceable_work() {
3594 return;
3595 }
3596 let mut scheduler = self.lock_scheduler_for_publication();
3597 if self.stop.is_stopping()
3598 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3599 || self.draining.load(Ordering::Acquire)
3600 {
3601 return;
3602 }
3603 let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3604 if dispatch == 0 {
3605 drop(scheduler);
3606 self.request_drain_if_work_remains();
3607 return;
3608 }
3609
3610 let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
3614 return;
3615 };
3616 drop(scheduler);
3617 let _ = self.publish_host_dispatch(dispatch, dispatch_call);
3618 }
3619
3620 fn retire_unavailable_host_dispatch_locked(
3621 &self,
3622 dispatch: u64,
3623 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
3624 if self
3625 .dispatch_pending
3626 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3627 .is_err()
3628 {
3629 return None;
3630 }
3631 if self
3632 .recovery_dispatch
3633 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3634 .is_ok()
3635 {
3636 self.begin_failed_dispatch_cancellation()
3637 } else {
3638 None
3639 }
3640 }
3641
3642 fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
3643 let failure = {
3645 let mut scheduler = self
3646 .scheduler_idle_lock
3647 .lock()
3648 .unwrap_or_else(std::sync::PoisonError::into_inner);
3649 self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3650 };
3651 if let Some(failure) = failure {
3652 self.run_host_dispatch_failure(failure.action);
3653 }
3654 }
3655
3656 fn begin_host_dispatch_failure_locked<'a>(
3657 &'a self,
3658 scheduler: &mut CurrentThreadSchedulerState,
3659 failure: CurrentThreadTaskDispatchFailure,
3660 ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
3661 if !self.task_drivers.is_current_failure(failure) {
3662 return None;
3663 }
3664 let registry_cancellation =
3665 CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
3666 let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
3667 Some(CurrentThreadDispatchFailureRecovery {
3668 action,
3669 _registry_cancellation: registry_cancellation,
3670 })
3671 }
3672
3673 fn begin_host_dispatch_failure_transition_locked<'a>(
3674 &'a self,
3675 scheduler: &mut CurrentThreadSchedulerState,
3676 dispatch: u64,
3677 ) -> CurrentThreadDispatchFailureAction<'a> {
3678 if dispatch == 0
3679 || self
3680 .dispatch_pending
3681 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3682 .is_err()
3683 {
3684 return CurrentThreadDispatchFailureAction::None;
3685 }
3686 #[cfg(all(test, not(target_family = "wasm")))]
3687 run_current_thread_dispatch_recovery_test_hook();
3688
3689 if self
3690 .recovery_dispatch
3691 .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3692 .is_ok()
3693 {
3694 return self.begin_failed_dispatch_cancellation().map_or(
3698 CurrentThreadDispatchFailureAction::None,
3699 CurrentThreadDispatchFailureAction::Cancel,
3700 );
3701 }
3702
3703 if self.stop.is_stopping()
3704 || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3705 || self.draining.load(Ordering::Acquire)
3706 || !self.has_serviceable_work()
3707 {
3708 return CurrentThreadDispatchFailureAction::None;
3709 }
3710
3711 let replacement = next_current_thread_dispatch_id();
3715 self.recovery_dispatch.store(replacement, Ordering::Release);
3716 self.dispatch_pending.store(replacement, Ordering::Release);
3717 let dispatch_call = self
3718 .begin_host_dispatch_publication(scheduler, replacement)
3719 .expect("a reserved CurrentThread replacement must own its publication");
3720 CurrentThreadDispatchFailureAction::Publish {
3721 dispatch: replacement,
3722 call: dispatch_call,
3723 }
3724 }
3725
3726 #[cfg(test)]
3727 fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
3728 let failure = {
3729 let mut scheduler = self
3730 .scheduler_idle_lock
3731 .lock()
3732 .unwrap_or_else(std::sync::PoisonError::into_inner);
3733 self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
3734 };
3735 self.run_host_dispatch_failure(failure);
3736 }
3737
3738 fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
3739 match failure {
3740 CurrentThreadDispatchFailureAction::None => {}
3741 CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
3742 self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
3743 }
3744 CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
3745 let _ = self.publish_host_dispatch(dispatch, call);
3746 }
3747 }
3748 }
3749
3750 fn has_serviceable_work(&self) -> bool {
3751 let has_queued_runnable = {
3752 let queue = self
3753 .queue
3754 .lock()
3755 .unwrap_or_else(std::sync::PoisonError::into_inner);
3756 !queue.closed && !queue.runnables.is_empty()
3757 };
3758 #[cfg(not(target_family = "wasm"))]
3759 let has_queued_blocking = self.has_serviceable_blocking_work();
3760 #[cfg(target_family = "wasm")]
3761 let has_queued_blocking = false;
3762 has_queued_runnable || has_queued_blocking
3763 }
3764
3765 fn request_drain_if_work_remains(self: &Arc<Self>) {
3766 if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
3767 self.request_drain();
3768 }
3769 }
3770
3771 fn begin_failed_dispatch_cancellation(
3772 &self,
3773 ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
3774 if self
3775 .cancelling_failed_dispatch
3776 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
3777 .is_err()
3778 {
3779 return None;
3780 }
3781 self.dispatch_pending.store(0, Ordering::Release);
3782 self.recovery_dispatch.store(0, Ordering::Release);
3783 Some(CurrentThreadDispatchCancellationGuard(self))
3784 }
3785
3786 fn cancel_work_after_host_dispatch_failure_claimed(
3787 &self,
3788 _cancelling: CurrentThreadDispatchCancellationGuard<'_>,
3789 ) {
3790 let queued = {
3791 let mut queue = self
3792 .queue
3793 .lock()
3794 .unwrap_or_else(std::sync::PoisonError::into_inner);
3795 std::mem::take(&mut queue.runnables)
3796 };
3797 #[cfg(not(target_family = "wasm"))]
3798 let queued_blocking = {
3799 let mut state = self
3800 .blocking_admission
3801 .state
3802 .lock()
3803 .unwrap_or_else(std::sync::PoisonError::into_inner);
3804 state.queue.take_all()
3805 };
3806
3807 let _generation = RuntimeGenerationGuard::enter(self.generation);
3808 for runnable in queued {
3809 self
3810 .metrics
3811 .queued_runnables
3812 .fetch_sub(1, Ordering::Relaxed);
3813 let _ = catch_unwind_contained(|| drop(runnable));
3814 }
3815 #[cfg(not(target_family = "wasm"))]
3816 for job in queued_blocking {
3817 let _ = catch_unwind_contained(|| drop(job));
3818 }
3819 }
3820
3821 fn take_runnable(&self) -> Option<Runnable> {
3822 let scheduler = self
3827 .scheduler_idle_lock
3828 .lock()
3829 .unwrap_or_else(std::sync::PoisonError::into_inner);
3830 #[cfg(all(test, not(target_family = "wasm")))]
3831 run_current_thread_runnable_claim_test_hook();
3832 let stop_publication = self.stop.publication_guard();
3833 if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3834 return None;
3835 }
3836 let runnable = {
3837 let mut queue = self
3838 .queue
3839 .lock()
3840 .unwrap_or_else(std::sync::PoisonError::into_inner);
3841 if queue.closed {
3842 None
3843 } else {
3844 queue.runnables.pop_front()
3845 }
3846 };
3847 drop(stop_publication);
3848 drop(scheduler);
3849 runnable
3850 }
3851
3852 fn drain_one(&self) -> bool {
3853 let runnable = self.take_runnable();
3854 if let Some(runnable) = runnable {
3855 #[cfg(not(target_family = "wasm"))]
3856 let _non_owner = BlockingOwnerGuard::enter(None);
3857 run_runnable(&self.metrics, runnable);
3858 true
3859 } else {
3860 false
3861 }
3862 }
3863
3864 fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
3886 let parker = Arc::new(DriverParker::default());
3887 let _stop_registration = self.stop.register(&parker);
3888 #[cfg(not(target_family = "wasm"))]
3889 let dependency = BlockOnDependencyGuard::enter(self.generation);
3890 let waker = std::task::Waker::from(Arc::clone(&parker));
3891 let mut cx = Context::from_waker(&waker);
3892 #[cfg(not(target_family = "wasm"))]
3893 let mut runnable_streak = 0usize;
3894 loop {
3895 if self.stop.is_stopping() {
3896 return BlockOnOutcome::Stopped;
3897 }
3898 #[cfg(not(target_family = "wasm"))]
3899 dependency.clear();
3900 if future.as_mut().poll(&mut cx).is_ready() {
3901 return BlockOnOutcome::Completed;
3902 }
3903 if self.stop.is_stopping() {
3907 return BlockOnOutcome::Stopped;
3908 }
3909 #[cfg(not(target_family = "wasm"))]
3910 if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
3911 runnable_streak = 0;
3912 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
3913 continue;
3914 }
3915 }
3916 if self.drain_one() {
3917 #[cfg(not(target_family = "wasm"))]
3918 {
3919 runnable_streak += 1;
3920 }
3921 continue;
3922 }
3923 #[cfg(not(target_family = "wasm"))]
3924 if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
3925 runnable_streak = 0;
3926 continue;
3927 }
3928 #[cfg(not(target_family = "wasm"))]
3929 {
3930 runnable_streak = 0;
3931 }
3932 if parker.consume_permit() {
3936 continue;
3937 }
3938 if self.threadless {
3950 if self.host_timers.has_pending() {
3951 std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
3952 }
3953 std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
3954 }
3955 match self.park_deadline {
3956 None => parker.park(),
3960 Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
3961 DeadlineParkOutcome::Woken => {}
3962 DeadlineParkOutcome::Expired(armed) => {
3963 if parker.consume_permit()
3978 || self.metrics.progress_fingerprint() != armed
3979 || self.host_timers.has_live_wait(deadline)
3980 {
3981 continue;
3982 }
3983 std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
3984 }
3985 },
3986 }
3987 }
3988 }
3989
3990 fn begin_shutdown(&self) {
3991 let _generation = RuntimeGenerationGuard::enter(self.generation);
3992 self.stop.begin_shutdown();
3993 self.close_blocking_admission();
3994 let queued = {
3995 let mut queue = self
3996 .queue
3997 .lock()
3998 .unwrap_or_else(std::sync::PoisonError::into_inner);
3999 queue.closed = true;
4000 std::mem::take(&mut queue.runnables)
4001 };
4002 self.dispatch_pending.store(0, Ordering::Release);
4003 self.recovery_dispatch.store(0, Ordering::Release);
4004 self.task_drivers.retire_for_shutdown();
4005 for runnable in queued {
4006 self
4007 .metrics
4008 .queued_runnables
4009 .fetch_sub(1, Ordering::Relaxed);
4010 let _ = catch_unwind_contained(|| drop(runnable));
4013 }
4014 self.host_timers.shutdown();
4015 }
4016
4017 fn wait_until_scheduler_idle(&self) {
4018 let mut idle = self
4019 .scheduler_idle_lock
4020 .lock()
4021 .unwrap_or_else(std::sync::PoisonError::into_inner);
4022 while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
4023 idle = self
4024 .scheduler_idle
4025 .wait(idle)
4026 .unwrap_or_else(std::sync::PoisonError::into_inner);
4027 }
4028 debug_assert!(
4029 idle.dispatch_publications.is_empty(),
4030 "an idle CurrentThread scheduler must not retain dispatch publications"
4031 );
4032 }
4033}
4034
4035#[cfg(not(target_family = "wasm"))]
4048thread_local! {
4049 static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4050 static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4054}
4055
4056#[cfg(not(target_family = "wasm"))]
4062#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4063struct SchedulerDriverRole {
4064 executor_id: u64,
4065 can_run_blocking: bool,
4066}
4067
4068#[cfg(not(target_family = "wasm"))]
4069thread_local! {
4070 static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
4071 const { std::cell::Cell::new(None) };
4072}
4073
4074#[cfg(not(target_family = "wasm"))]
4091thread_local! {
4092 static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
4093 const { std::cell::Cell::new(None) };
4094}
4095
4096#[cfg(not(target_family = "wasm"))]
4097struct OnPoolWorkerGuard {
4098 previous_worker: Option<u64>,
4099 previous_driver: Option<SchedulerDriverRole>,
4100}
4101
4102#[cfg(not(target_family = "wasm"))]
4103impl OnPoolWorkerGuard {
4104 fn enter(id: u64) -> Self {
4108 Self::enter_with_role(id, true)
4109 }
4110
4111 fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
4112 Self {
4113 previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
4114 previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
4115 flag.replace(Some(SchedulerDriverRole {
4116 executor_id: id,
4117 can_run_blocking,
4118 }))
4119 }),
4120 }
4121 }
4122}
4123
4124#[cfg(not(target_family = "wasm"))]
4125impl Drop for OnPoolWorkerGuard {
4126 fn drop(&mut self) {
4127 IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
4128 ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
4129 }
4130}
4131
4132#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
4145struct BlockingOwnerToken {
4146 executor_id: u64,
4147 frame: u64,
4148}
4149
4150#[cfg(not(target_family = "wasm"))]
4151enum OwnedBlockingOutcome {
4152 Ineligible,
4153 OwnerBusy(BlockingOwnerToken),
4154 ReservationReleased {
4155 owner: BlockingOwnerToken,
4156 ran: bool,
4157 },
4158}
4159
4160static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
4163#[cfg(not(target_family = "wasm"))]
4164static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
4165
4166thread_local! {
4169 static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
4170 const { std::cell::Cell::new(None) };
4171}
4172
4173#[cfg(not(target_family = "wasm"))]
4180struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
4181
4182#[cfg(not(target_family = "wasm"))]
4183impl BlockingOwnerGuard {
4184 fn enter(token: Option<BlockingOwnerToken>) -> Self {
4185 Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
4186 }
4187}
4188
4189#[cfg(not(target_family = "wasm"))]
4190impl Drop for BlockingOwnerGuard {
4191 fn drop(&mut self) {
4192 BLOCKING_OWNER.with(|cell| cell.set(self.0));
4193 }
4194}
4195
4196#[cfg(not(target_family = "wasm"))]
4197#[derive(Default)]
4198struct ActiveBlockingOwners {
4199 owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
4203}
4204
4205#[cfg(not(target_family = "wasm"))]
4206impl ActiveBlockingOwners {
4207 fn register(&self, owner: BlockingOwnerToken) {
4208 let previous = self
4209 .owners
4210 .lock()
4211 .unwrap_or_else(std::sync::PoisonError::into_inner)
4212 .insert(owner, None);
4213 debug_assert!(previous.is_none());
4214 }
4215
4216 fn unregister(&self, owner: BlockingOwnerToken) {
4217 let removed = self
4218 .owners
4219 .lock()
4220 .unwrap_or_else(std::sync::PoisonError::into_inner)
4221 .remove(&owner);
4222 debug_assert!(removed.is_some());
4223 }
4224
4225 fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
4226 let mut owners = self
4227 .owners
4228 .lock()
4229 .unwrap_or_else(std::sync::PoisonError::into_inner);
4230 let reservation = owners.get_mut(&owner)?;
4231 if reservation.is_some() {
4232 return None;
4233 }
4234 let id = next_unique_id(
4235 &NEXT_OWNER_RESERVATION,
4236 "blocking owner reservation id space exhausted",
4237 );
4238 *reservation = Some(id);
4239 Some(id)
4240 }
4241
4242 fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
4243 let mut owners = self
4244 .owners
4245 .lock()
4246 .unwrap_or_else(std::sync::PoisonError::into_inner);
4247 if let Some(current) = owners.get_mut(&owner)
4248 && *current == Some(reservation)
4249 {
4250 *current = None;
4251 }
4252 }
4253
4254 fn is_available(&self, owner: BlockingOwnerToken) -> bool {
4255 self
4256 .owners
4257 .lock()
4258 .unwrap_or_else(std::sync::PoisonError::into_inner)
4259 .get(&owner)
4260 .is_some_and(Option::is_none)
4261 }
4262}
4263
4264#[cfg(not(target_family = "wasm"))]
4265struct ActiveBlockingOwnerGuard<'a> {
4266 owners: &'a ActiveBlockingOwners,
4267 owner: BlockingOwnerToken,
4268}
4269
4270#[cfg(not(target_family = "wasm"))]
4271impl<'a> ActiveBlockingOwnerGuard<'a> {
4272 fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
4273 owners.register(owner);
4274 Self { owners, owner }
4275 }
4276}
4277
4278#[cfg(not(target_family = "wasm"))]
4279impl Drop for ActiveBlockingOwnerGuard<'_> {
4280 fn drop(&mut self) {
4281 self.owners.unregister(self.owner);
4282 }
4283}
4284
4285#[cfg(not(target_family = "wasm"))]
4286struct OwnerLaneReservation<'a> {
4287 owners: &'a ActiveBlockingOwners,
4288 owner: BlockingOwnerToken,
4289 reservation: u64,
4290}
4291
4292#[cfg(not(target_family = "wasm"))]
4293impl<'a> OwnerLaneReservation<'a> {
4294 fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
4295 let reservation = owners.reserve(owner)?;
4296 Some(Self {
4297 owners,
4298 owner,
4299 reservation,
4300 })
4301 }
4302}
4303
4304#[cfg(not(target_family = "wasm"))]
4305impl Drop for OwnerLaneReservation<'_> {
4306 fn drop(&mut self) {
4307 self.owners.release(self.owner, self.reservation);
4308 }
4309}
4310
4311#[cfg(not(target_family = "wasm"))]
4315type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
4316
4317#[cfg(not(target_family = "wasm"))]
4318struct QueuedBlocking {
4319 id: BlockingJobId,
4320 run: BlockingClosure,
4321}
4322
4323#[cfg(not(target_family = "wasm"))]
4324struct BlockingQueueNode {
4325 previous: Option<BlockingJobId>,
4326 next: Option<BlockingJobId>,
4327 run: Box<dyn FnOnce() + Send + 'static>,
4328}
4329
4330#[cfg(not(target_family = "wasm"))]
4331struct BlockingQueue {
4332 closed: bool,
4333 head: Option<BlockingJobId>,
4334 tail: Option<BlockingJobId>,
4335 jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
4336}
4337
4338#[cfg(not(target_family = "wasm"))]
4339impl BlockingQueue {
4340 fn push(&mut self, job: QueuedBlocking) {
4341 let previous = self.tail;
4342 let replaced = self.jobs.insert(
4343 job.id,
4344 BlockingQueueNode {
4345 previous,
4346 next: None,
4347 run: job.run,
4348 },
4349 );
4350 debug_assert!(replaced.is_none());
4351 if let Some(previous) = previous {
4352 self
4353 .jobs
4354 .get_mut(&previous)
4355 .expect("blocking queue tail must be indexed")
4356 .next = Some(job.id);
4357 } else {
4358 self.head = Some(job.id);
4359 }
4360 self.tail = Some(job.id);
4361 }
4362
4363 fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4364 self.head.and_then(|id| self.remove(id))
4365 }
4366
4367 fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4368 let node = self.jobs.remove(&id)?;
4369 if let Some(previous) = node.previous {
4370 self
4371 .jobs
4372 .get_mut(&previous)
4373 .expect("blocking queue predecessor must be indexed")
4374 .next = node.next;
4375 } else {
4376 self.head = node.next;
4377 }
4378 if let Some(next) = node.next {
4379 self
4380 .jobs
4381 .get_mut(&next)
4382 .expect("blocking queue successor must be indexed")
4383 .previous = node.previous;
4384 } else {
4385 self.tail = node.previous;
4386 }
4387 Some(node.run)
4388 }
4389
4390 fn is_empty(&self) -> bool {
4391 debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
4392 debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
4393 self.jobs.is_empty()
4394 }
4395
4396 fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
4397 self.head = None;
4398 self.tail = None;
4399 std::mem::take(&mut self.jobs)
4400 .into_values()
4401 .map(|node| node.run)
4402 .collect()
4403 }
4404}
4405
4406#[cfg(not(target_family = "wasm"))]
4407struct WorkerLifecycle {
4408 remaining: Mutex<usize>,
4409 handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
4410}
4411
4412#[cfg(not(target_family = "wasm"))]
4413impl WorkerLifecycle {
4414 fn new(worker_threads: usize) -> Arc<Self> {
4415 Arc::new(Self {
4416 remaining: Mutex::new(worker_threads),
4417 handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
4418 })
4419 }
4420
4421 fn retain(&self, handle: std::thread::JoinHandle<()>) {
4422 self
4423 .handles
4424 .lock()
4425 .unwrap_or_else(std::sync::PoisonError::into_inner)
4426 .as_mut()
4427 .expect("runtime worker handles already joined")
4428 .push(handle);
4429 }
4430
4431 fn worker_exited(&self) {
4432 let mut remaining = self
4433 .remaining
4434 .lock()
4435 .unwrap_or_else(std::sync::PoisonError::into_inner);
4436 *remaining = remaining
4437 .checked_sub(1)
4438 .expect("runtime worker exit count underflow");
4439 }
4440
4441 fn wait_for_all_workers(&self) {
4442 let handles = self
4443 .handles
4444 .lock()
4445 .unwrap_or_else(std::sync::PoisonError::into_inner)
4446 .take()
4447 .expect("runtime worker handles already joined");
4448 for handle in handles {
4449 if let Err(payload) = handle.join() {
4450 discard_panic_payload(payload);
4451 }
4452 }
4453 }
4454
4455 #[cfg(test)]
4456 fn remaining(&self) -> usize {
4457 *self
4458 .remaining
4459 .lock()
4460 .unwrap_or_else(std::sync::PoisonError::into_inner)
4461 }
4462}
4463
4464#[cfg(not(target_family = "wasm"))]
4465struct MultiThreadExecutor {
4466 generation: u64,
4467 stop: Arc<GenerationStop>,
4468 id: u64,
4471 pool: ThreadPool,
4472 queue: Mutex<VecDeque<Runnable>>,
4473 blocking_queue: Mutex<BlockingQueue>,
4474 active_drainers: AtomicUsize,
4475 active_blocking: AtomicUsize,
4476 scheduler_idle_lock: Mutex<()>,
4477 scheduler_idle: Condvar,
4478 worker_lifecycle: Arc<WorkerLifecycle>,
4479 parked_drivers: ParkedDrivers,
4487 active_blocking_owners: ActiveBlockingOwners,
4488 #[cfg(test)]
4489 owner_lending_attempts: AtomicUsize,
4490 max_drainers: usize,
4491 max_blocking: usize,
4492 park_deadline: Option<Duration>,
4499 deadlock_state: Arc<MultiThreadDeadlockState>,
4503 timers: TimerHeap,
4506 metrics: Arc<RuntimeMetrics>,
4507}
4508
4509#[cfg(not(target_family = "wasm"))]
4510impl MultiThreadExecutor {
4511 const RUNNABLE_BUDGET: usize = 64;
4514 const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
4517
4518 #[cfg(test)]
4519 fn new(
4520 options: &RuntimeOptions,
4521 metrics: Arc<RuntimeMetrics>,
4522 ) -> Result<Self, RuntimeConfigError> {
4523 Self::new_for_generation(
4524 options,
4525 metrics,
4526 u64::MAX,
4527 Arc::new(GenerationStop::default()),
4528 )
4529 }
4530
4531 fn new_for_generation(
4532 options: &RuntimeOptions,
4533 metrics: Arc<RuntimeMetrics>,
4534 generation: u64,
4535 stop: Arc<GenerationStop>,
4536 ) -> Result<Self, RuntimeConfigError> {
4537 let id = next_unique_id(
4538 &NEXT_RUNTIME_EXECUTOR_ID,
4539 "async runtime executor id space exhausted",
4540 );
4541 let thread_name_prefix = options.thread_name_prefix.clone();
4542 let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
4543 options.park_deadline.is_some(),
4544 ));
4545 let timers = TimerHeap::new(
4546 generation,
4547 id,
4548 format!("{thread_name_prefix}-timer"),
4549 Arc::clone(&deadlock_state),
4550 );
4551 let pool_threads = options.worker_threads;
4555 let worker_lifecycle = WorkerLifecycle::new(pool_threads);
4556 let exit_lifecycle = Arc::clone(&worker_lifecycle);
4557 let spawn_lifecycle = Arc::clone(&worker_lifecycle);
4558 let pool = ThreadPoolBuilder::new()
4559 .num_threads(pool_threads)
4560 .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
4561 .start_handler(move |_| {
4562 ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
4563 })
4564 .exit_handler(move |_| {
4565 IN_SCHEDULER_DRIVER.with(|driver| {
4566 if driver.get().is_some_and(|role| role.executor_id == id) {
4567 driver.set(None);
4568 }
4569 });
4570 exit_lifecycle.worker_exited();
4576 })
4577 .spawn_handler(move |thread| {
4578 let mut builder = std::thread::Builder::new();
4579 if let Some(name) = thread.name() {
4580 builder = builder.name(name.to_string());
4581 }
4582 if let Some(stack_size) = thread.stack_size() {
4583 builder = builder.stack_size(stack_size);
4584 }
4585 let handle = builder.spawn(move || thread.run())?;
4586 spawn_lifecycle.retain(handle);
4587 Ok(())
4588 })
4589 .build()
4590 .map_err(|error| {
4591 worker_lifecycle.wait_for_all_workers();
4592 RuntimeConfigError(format!("failed to create runtime workers: {error}"))
4593 })?;
4594 Ok(Self {
4595 generation,
4596 stop,
4597 id,
4598 pool,
4599 queue: Mutex::new(VecDeque::new()),
4600 blocking_queue: Mutex::new(BlockingQueue {
4601 closed: false,
4602 head: None,
4603 tail: None,
4604 jobs: FxHashMap::default(),
4605 }),
4606 active_drainers: AtomicUsize::new(0),
4607 active_blocking: AtomicUsize::new(0),
4608 scheduler_idle_lock: Mutex::new(()),
4609 scheduler_idle: Condvar::new(),
4610 worker_lifecycle,
4611 parked_drivers: ParkedDrivers::default(),
4612 active_blocking_owners: ActiveBlockingOwners::default(),
4613 #[cfg(test)]
4614 owner_lending_attempts: AtomicUsize::new(0),
4615 max_drainers: pool_threads,
4616 max_blocking: options.max_blocking_tasks,
4617 park_deadline: options.park_deadline,
4618 deadlock_state,
4619 timers,
4620 metrics,
4621 })
4622 }
4623
4624 fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
4625 self.deadlock_state.begin_admission()
4626 }
4627
4628 fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
4629 self.deadlock_state.try_begin_verdict()
4630 }
4631
4632 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
4633 let _publication = self.deadlock_state.owner_handoff_publication_guard();
4635 self.parked_drivers.wake_one_owner_dependency(owner)
4636 }
4637
4638 fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
4639 let owner = reservation.owner;
4640 let _publication = self.deadlock_state.owner_handoff_publication_guard();
4644 drop(reservation);
4645 self.parked_drivers.wake_one_owner_dependency(owner)
4646 }
4647
4648 fn schedule(self: &Arc<Self>, runnable: Runnable) {
4649 let mut admission = self.begin_deadlock_admission();
4650 self.metrics.runnable_scheduled();
4653 if IN_SCHEDULER_DRIVER
4673 .with(std::cell::Cell::get)
4674 .is_some_and(|role| role.executor_id == self.id)
4675 && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
4676 {
4677 match LIFO_SLOT.with(std::cell::Cell::take) {
4678 None => {
4679 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
4680 if let Some(admission) = admission.as_mut() {
4681 admission.mark_published();
4682 }
4683 return;
4684 }
4685 Some((id, displaced)) if id == self.id => {
4689 LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
4690 self.push_to_queue_and_wake(displaced);
4691 if let Some(admission) = admission.as_mut() {
4692 admission.mark_published();
4693 }
4694 return;
4695 }
4696 Some(foreign) => {
4703 LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
4704 }
4705 }
4706 }
4707 self.push_to_queue_and_wake(runnable);
4708 if let Some(admission) = admission.as_mut() {
4709 admission.mark_published();
4710 }
4711 }
4712
4713 fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
4717 self
4718 .queue
4719 .lock()
4720 .unwrap_or_else(std::sync::PoisonError::into_inner)
4721 .push_back(runnable);
4722 self.wake_for_new_work();
4723 }
4724
4725 fn wake_for_new_work(self: &Arc<Self>) {
4734 if !self.parked_drivers.wake_one() {
4735 self.ensure_drainer();
4736 }
4737 }
4738
4739 fn wake_for_blocking_work(self: &Arc<Self>) {
4740 if !self.parked_drivers.wake_one_blocking() {
4743 self.ensure_drainer();
4744 }
4745 }
4746
4747 #[cfg(test)]
4748 fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
4749 where
4750 F: FnOnce() -> T + Send + 'static,
4751 T: Send + 'static,
4752 {
4753 self.schedule_blocking_result(move || catch_unwind_join_error(function))
4754 }
4755
4756 fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
4757 where
4758 F: FnOnce() -> Result<T, JoinError> + Send + 'static,
4759 T: Send + 'static,
4760 {
4761 let mut admission = self.begin_deadlock_admission();
4762 let (sender, receiver) = oneshot::channel();
4763 let generation = self.generation;
4764 let dependency = BlockingDependency {
4765 job: BlockingJobId(next_unique_id(
4766 &NEXT_BLOCKING_JOB_ID,
4767 "blocking job id space exhausted",
4768 )),
4769 owner: BLOCKING_OWNER
4770 .with(std::cell::Cell::get)
4771 .filter(|owner| owner.executor_id == self.id),
4772 };
4773 self.metrics.blocking_scheduled();
4778 let queued = QueuedBlocking {
4779 id: dependency.job,
4780 run: Box::new(move || {
4781 let result = function().map(|output| ContainedTaskOutput::new(output, generation));
4782 let _ = sender.send(result);
4783 }),
4784 };
4785 let rejected = {
4786 let mut queue = self
4787 .blocking_queue
4788 .lock()
4789 .unwrap_or_else(std::sync::PoisonError::into_inner);
4790 if queue.closed {
4791 Some(queued)
4792 } else {
4793 queue.push(queued);
4794 None
4795 }
4796 };
4797 if let Some(rejected) = rejected {
4798 let _generation = RuntimeGenerationGuard::enter(self.generation);
4803 let _ = catch_unwind_contained(|| drop(rejected));
4804 } else {
4805 self.wake_for_blocking_work();
4807 if let Some(admission) = admission.as_mut() {
4808 admission.mark_published();
4809 }
4810 }
4811 JoinHandle(JoinHandleInner::Blocking {
4812 receiver,
4813 dependency,
4814 })
4815 }
4816
4817 fn ensure_drainer(self: &Arc<Self>) {
4818 loop {
4819 let active = self.active_drainers.load(Ordering::Acquire);
4820 if active >= self.max_drainers {
4821 return;
4822 }
4823 if self
4824 .active_drainers
4825 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
4826 .is_ok()
4827 {
4828 let executor = Arc::clone(self);
4829 self.pool.spawn_fifo(move || executor.drain());
4830 return;
4831 }
4832 }
4833 }
4834
4835 fn drain(self: Arc<Self>) {
4836 let _on_pool = OnPoolWorkerGuard::enter(self.id);
4839 let _slot_backstop = LifoSlotFlushGuard(&self);
4845
4846 let mut runnable_streak = 0usize;
4847 for _ in 0..Self::RUNNABLE_BUDGET {
4848 if self.run_one_fair(&mut runnable_streak) {
4849 continue;
4850 }
4851 self.flush_lifo_slot();
4856 self.finish_draining();
4857 return;
4858 }
4859
4860 self.flush_lifo_slot();
4864 self.finish_draining();
4865 }
4866
4867 fn pop_lifo_slot(&self) -> Option<Runnable> {
4871 LIFO_SLOT.with(|slot| match slot.take() {
4872 Some((id, runnable)) if id == self.id => Some(runnable),
4873 other => {
4874 slot.set(other);
4875 None
4876 }
4877 })
4878 }
4879
4880 fn flush_lifo_slot(self: &Arc<Self>) {
4887 let mut admission = self.begin_deadlock_admission();
4888 if let Some(runnable) = self.pop_lifo_slot() {
4889 self.push_to_queue_and_wake(runnable);
4890 if let Some(admission) = admission.as_mut() {
4891 admission.mark_published();
4892 }
4893 }
4894 }
4895
4896 fn fresh_owner_token(&self) -> BlockingOwnerToken {
4900 BlockingOwnerToken {
4901 executor_id: self.id,
4902 frame: next_unique_id(
4903 &NEXT_BLOCKING_FRAME,
4904 "blocking owner frame id space exhausted",
4905 ),
4906 }
4907 }
4908
4909 fn finish_draining(self: &Arc<Self>) {
4910 self.active_drainers.fetch_sub(1, Ordering::AcqRel);
4920 let has_runnable = !self
4921 .queue
4922 .lock()
4923 .unwrap_or_else(std::sync::PoisonError::into_inner)
4924 .is_empty();
4925 let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
4926 && !self
4927 .blocking_queue
4928 .lock()
4929 .unwrap_or_else(std::sync::PoisonError::into_inner)
4930 .is_empty();
4931 if has_runnable || has_blocking {
4932 self.ensure_drainer();
4933 }
4934 let _idle = self
4935 .scheduler_idle_lock
4936 .lock()
4937 .unwrap_or_else(std::sync::PoisonError::into_inner);
4938 self.scheduler_idle.notify_all();
4939 }
4940
4941 fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4942 if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
4943 self.cooperative_block_on(future)
4951 } else {
4952 self.parking_block_on(future)
4956 }
4957 }
4958
4959 fn parking_block_on(
4960 self: &Arc<Self>,
4961 mut future: Pin<&mut dyn Future<Output = ()>>,
4962 ) -> BlockOnOutcome {
4963 let parker = Arc::new(DriverParker::default());
4964 let _stop_registration = self.stop.register(&parker);
4965 let waker = Waker::from(Arc::clone(&parker));
4966 let mut cx = Context::from_waker(&waker);
4967 loop {
4968 if self.stop.is_stopping() {
4969 return BlockOnOutcome::Stopped;
4970 }
4971 if future.as_mut().poll(&mut cx).is_ready() {
4972 return BlockOnOutcome::Completed;
4973 }
4974 if self.stop.is_stopping() {
4975 return BlockOnOutcome::Stopped;
4976 }
4977 parker.park();
4978 }
4979 }
4980
4981 fn run_runnable(
4982 self: &Arc<Self>,
4983 runnable: Runnable,
4984 admission: Option<MultiThreadDeadlockAdmissionGuard>,
4985 ) {
4986 let _non_owner = BlockingOwnerGuard::enter(None);
4989 let _active = self.metrics.runnable_started();
4990 #[cfg(test)]
4991 run_multi_thread_work_started_test_hook();
4992 drop(admission);
4996 let _ = catch_unwind_contained(|| runnable.run());
4997 }
4998
4999 fn cancel_runnable(
5000 &self,
5001 runnable: Runnable,
5002 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5003 ) {
5004 self.metrics.runnable_cancelled();
5005 drop(admission);
5008 let _generation = RuntimeGenerationGuard::enter(self.generation);
5009 let _ = catch_unwind_contained(|| drop(runnable));
5010 }
5011
5012 fn finish_runnable_claim(
5013 self: &Arc<Self>,
5014 claim: RunnableClaim,
5015 admission: Option<MultiThreadDeadlockAdmissionGuard>,
5016 ) -> bool {
5017 match claim {
5018 RunnableClaim::Empty => {
5019 drop(admission);
5020 false
5021 }
5022 RunnableClaim::Run(runnable) => {
5023 #[cfg(test)]
5024 run_multi_thread_work_claimed_test_hook();
5025 self.run_runnable(runnable, admission);
5026 true
5027 }
5028 RunnableClaim::Cancel(runnable) => {
5029 #[cfg(test)]
5030 run_multi_thread_work_claimed_test_hook();
5031 self.cancel_runnable(runnable, admission);
5032 true
5033 }
5034 }
5035 }
5036
5037 fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
5038 match runnable {
5039 Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
5040 Some(runnable) => RunnableClaim::Run(runnable),
5041 None => RunnableClaim::Empty,
5042 }
5043 }
5044
5045 fn claim_fifo_runnable(&self) -> RunnableClaim {
5046 let stop_publication = self.stop.runnable_claim_guard();
5047 let runnable = self
5048 .queue
5049 .lock()
5050 .unwrap_or_else(std::sync::PoisonError::into_inner)
5051 .pop_front();
5052 let stopping = stop_publication.is_stopping();
5053 drop(stop_publication);
5054 Self::runnable_claim(stopping, runnable)
5055 }
5056
5057 fn claim_runnable(&self) -> RunnableClaim {
5058 let stop_publication = self.stop.runnable_claim_guard();
5059 let runnable = self.pop_lifo_slot().or_else(|| {
5060 self
5061 .queue
5062 .lock()
5063 .unwrap_or_else(std::sync::PoisonError::into_inner)
5064 .pop_front()
5065 });
5066 let stopping = stop_publication.is_stopping();
5067 drop(stop_publication);
5068 Self::runnable_claim(stopping, runnable)
5069 }
5070
5071 fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
5072 let admission = self.begin_deadlock_admission();
5073 let claim = self.claim_fifo_runnable();
5074 self.finish_runnable_claim(claim, admission)
5075 }
5076
5077 fn run_one_runnable(self: &Arc<Self>) -> bool {
5079 let admission = self.begin_deadlock_admission();
5080 let claim = self.claim_runnable();
5083 self.finish_runnable_claim(claim, admission)
5084 }
5085
5086 fn run_one_blocking(self: &Arc<Self>) -> bool {
5087 if let Some((blocking, admission)) = self.take_blocking() {
5088 let _generation = RuntimeGenerationGuard::enter(self.generation);
5089 {
5090 let _task = self.metrics.blocking_started(true);
5091 #[cfg(test)]
5092 run_multi_thread_work_started_test_hook();
5093 drop(admission);
5096 let owner = self.fresh_owner_token();
5097 let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
5098 let _owner = BlockingOwnerGuard::enter(Some(owner));
5099 let _ = catch_unwind_contained(blocking);
5104 }
5105 self.active_blocking.fetch_sub(1, Ordering::Release);
5106 return true;
5107 }
5108 false
5109 }
5110
5111 fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
5115 self.run_one_fair_with_exact(runnable_streak, None)
5116 }
5117
5118 fn run_one_fair_with_exact(
5122 self: &Arc<Self>,
5123 runnable_streak: &mut usize,
5124 exact_dependency: Option<&TaskDependency>,
5125 ) -> bool {
5126 let mut exact_attempted = false;
5127 if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
5128 *runnable_streak = 0;
5129 if let Some(dependency) = exact_dependency {
5130 exact_attempted = true;
5131 if self.try_owned_blocking_over_cap(dependency) {
5132 return true;
5133 }
5134 }
5135 if self.run_one_blocking() {
5136 return true;
5137 }
5138 }
5139 if self.run_one_runnable() {
5140 *runnable_streak += 1;
5141 return true;
5142 }
5143 if !exact_attempted
5144 && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
5145 {
5146 *runnable_streak = 0;
5147 return true;
5148 }
5149 if self.run_one_blocking() {
5150 *runnable_streak = 0;
5151 return true;
5152 }
5153 false
5154 }
5155
5156 #[cfg(test)]
5157 fn run_one(self: &Arc<Self>) -> bool {
5158 let mut runnable_streak = 0;
5159 self.run_one_fair(&mut runnable_streak)
5160 }
5161
5162 fn run_owned_blocking_over_cap(
5172 self: &Arc<Self>,
5173 dependency: &TaskDependency,
5174 ) -> OwnedBlockingOutcome {
5175 #[cfg(test)]
5176 self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
5177
5178 let Some(ambient_owner) = BLOCKING_OWNER
5179 .with(std::cell::Cell::get)
5180 .filter(|owner| owner.executor_id == self.id)
5181 else {
5182 return OwnedBlockingOutcome::Ineligible;
5183 };
5184 let Some(current) = dependency.select_for_owner(ambient_owner) else {
5185 return OwnedBlockingOutcome::Ineligible;
5186 };
5187 let admission = self.begin_deadlock_admission();
5188 let owner = current
5189 .publication
5190 .dependency
5191 .owner
5192 .expect("selected dependency must carry its owner");
5193 let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
5194 else {
5195 return OwnedBlockingOutcome::OwnerBusy(owner);
5196 };
5197 #[cfg(test)]
5198 run_before_owned_blocking_queue_claim_test_hook();
5199 let job = {
5200 let mut queue = self
5201 .blocking_queue
5202 .lock()
5203 .unwrap_or_else(std::sync::PoisonError::into_inner);
5204 if queue.closed || self.stop.is_stopping() {
5208 None
5209 } else if dependency.try_claim(¤t) {
5213 queue.remove(current.publication.dependency.job)
5214 } else {
5215 None
5216 }
5217 };
5218 let ran = if let Some(job) = job {
5219 #[cfg(test)]
5220 run_multi_thread_work_claimed_test_hook();
5221 let _generation = RuntimeGenerationGuard::enter(self.generation);
5222 {
5223 let _task = self.metrics.blocking_started(false);
5224 #[cfg(test)]
5225 run_multi_thread_work_started_test_hook();
5226 drop(admission);
5229 let nested_owner = self.fresh_owner_token();
5230 let _active_owner =
5231 ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
5232 let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
5233 let _ = catch_unwind_contained(job);
5234 }
5235 true
5236 } else {
5237 drop(admission);
5238 false
5239 };
5240 self.release_owner_lane_and_wake(reservation);
5243 OwnedBlockingOutcome::ReservationReleased { owner, ran }
5244 }
5245
5246 fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
5247 matches!(
5248 self.run_owned_blocking_over_cap(dependency),
5249 OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
5250 )
5251 }
5252
5253 fn service_owner_handoff(
5254 self: &Arc<Self>,
5255 parker: &DriverParker,
5256 dependency: &TaskDependency,
5257 can_run_blocking: bool,
5258 ) -> Option<bool> {
5259 let handoff_owner = parker.take_owner_handoff()?;
5260 if !can_run_blocking {
5261 self.wake_one_owner_dependency(handoff_owner);
5262 return Some(false);
5263 }
5264 match self.run_owned_blocking_over_cap(dependency) {
5265 OwnedBlockingOutcome::Ineligible => {
5266 self.wake_one_owner_dependency(handoff_owner);
5267 Some(false)
5268 }
5269 OwnedBlockingOutcome::OwnerBusy(owner) => {
5270 if owner != handoff_owner {
5271 self.wake_one_owner_dependency(handoff_owner);
5272 }
5273 Some(false)
5274 }
5275 OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
5276 if owner != handoff_owner {
5277 self.wake_one_owner_dependency(handoff_owner);
5278 }
5279 Some(ran)
5280 }
5281 }
5282 }
5283
5284 fn forward_owner_handoff(&self, parker: &DriverParker) {
5285 if let Some(owner) = parker.take_owner_handoff() {
5286 self.wake_one_owner_dependency(owner);
5287 }
5288 }
5289
5290 fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
5291 let Some(ambient_owner) = BLOCKING_OWNER
5292 .with(std::cell::Cell::get)
5293 .filter(|owner| owner.executor_id == self.id)
5294 else {
5295 return false;
5296 };
5297 self.active_blocking_owners.is_available(ambient_owner)
5298 && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
5299 }
5300
5301 fn has_exact_owner_work_for_role(
5302 &self,
5303 parker: &DriverParker,
5304 dependency: &TaskDependency,
5305 can_run_blocking: bool,
5306 ) -> bool {
5307 parker.has_owner_handoff()
5308 || (can_run_blocking && self.owner_dependency_lane_available(dependency))
5309 }
5310
5311 fn cooperative_block_on(
5320 self: &Arc<Self>,
5321 mut future: Pin<&mut dyn Future<Output = ()>>,
5322 ) -> BlockOnOutcome {
5323 let parker = Arc::new(DriverParker::default());
5328 let _stop_registration = self.stop.register(&parker);
5329 let dependency = BlockOnDependencyGuard::enter(self.generation);
5330 let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
5331 let can_run_blocking =
5332 driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
5333 let _exit = CooperativeDriverExitGuard {
5334 executor: self,
5335 parker: &parker,
5336 can_run_blocking,
5337 };
5338 let waker = std::task::Waker::from(Arc::clone(&parker));
5343 let mut cx = Context::from_waker(&waker);
5344 let mut runnable_streak = 0usize;
5352 let mut work_streak = 0usize;
5353 let mut force_fifo = false;
5354 loop {
5355 if self.stop.is_stopping() {
5356 return BlockOnOutcome::Stopped;
5357 }
5358 dependency.clear();
5359 if future.as_mut().poll(&mut cx).is_ready() {
5360 return BlockOnOutcome::Completed;
5361 }
5362 if self.stop.is_stopping() {
5363 return BlockOnOutcome::Stopped;
5364 }
5365 if self
5366 .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
5367 .is_some_and(|ran| ran)
5368 {
5369 continue;
5370 }
5371 if force_fifo {
5372 force_fifo = false;
5373 if self.run_one_fifo_runnable() {
5374 runnable_streak += 1;
5375 work_streak += 1;
5376 continue;
5377 }
5378 }
5379 let ran_work = if can_run_blocking {
5380 self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
5381 } else if self.run_one_runnable() {
5382 runnable_streak += 1;
5383 true
5384 } else {
5385 false
5386 };
5387 if ran_work {
5388 work_streak += 1;
5389 if work_streak >= Self::RUNNABLE_BUDGET {
5390 self.flush_lifo_slot();
5395 work_streak = 0;
5396 force_fifo = true;
5397 }
5398 continue;
5399 }
5400 runnable_streak = 0;
5401 work_streak = 0;
5402 self.flush_lifo_slot();
5407 self.parked_drivers.register_with_role(
5411 &parker,
5412 can_run_blocking,
5413 Some(Arc::clone(&dependency.dependency)),
5414 );
5415 if self.has_queued_work_for_role(can_run_blocking)
5416 || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
5417 {
5418 self.parked_drivers.deregister(&parker);
5419 continue;
5420 }
5421 if self.stop.is_stopping() {
5422 return BlockOnOutcome::Stopped;
5423 }
5424 if let Some(next) = self.next_timer_deadline() {
5439 let now = Instant::now();
5440 if next > now {
5441 parker.park_timeout(next - now);
5442 }
5443 self.parked_drivers.deregister(&parker);
5444 self.fire_due_timers();
5445 continue;
5446 }
5447 match self.park_deadline {
5448 None => parker.park(),
5449 Some(deadline) => {
5450 let deadlock_publications = self.deadlock_state.publications();
5456 match park_with_deadline(&parker, deadline, &self.metrics) {
5457 DeadlineParkOutcome::Woken => {}
5458 DeadlineParkOutcome::Expired(armed) => {
5459 #[cfg(test)]
5460 run_deadline_expiry_test_hook();
5461 self.parked_drivers.deregister(&parker);
5498 if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
5499 continue;
5500 }
5501 #[cfg(test)]
5504 run_deadline_verdict_test_hook();
5505 if self.metrics.progress_fingerprint() != armed {
5506 continue;
5507 }
5508 if self.next_timer_deadline().is_some() {
5517 continue;
5518 }
5519 #[cfg(test)]
5522 run_deadline_final_verdict_test_hook();
5523 let Some(verdict) = self.try_begin_deadlock_verdict() else {
5524 continue;
5525 };
5526 #[cfg(test)]
5527 run_deadline_gated_verdict_test_hook();
5528 let stop_publication = self.stop.publication_guard();
5533 #[cfg(test)]
5534 run_deadline_synchronized_stop_test_hook();
5535 if stop_publication.is_stopping() {
5536 return BlockOnOutcome::Stopped;
5537 }
5538 let owner_handoff_publication = self
5539 .deadlock_state
5540 .owner_handoff_publication_guard()
5541 .expect("a deadline verdict requires exact-owner publication");
5542 #[cfg(test)]
5543 run_deadline_exact_owner_verdict_test_hook(&parker);
5544 if self.has_exact_owner_work_for_role(
5552 &parker,
5553 &dependency.dependency,
5554 can_run_blocking,
5555 ) || parker.consume_permit()
5556 || self.has_queued_work_for_role(can_run_blocking)
5557 || self.deadlock_state.publications() != deadlock_publications
5558 || self.metrics.progress_fingerprint() != armed
5559 || self.next_timer_deadline().is_some()
5560 {
5561 continue;
5562 }
5563 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
5564 drop(owner_handoff_publication);
5569 drop(stop_publication);
5570 drop(verdict);
5571 std::panic::panic_any(diagnostic);
5572 }
5573 }
5574 }
5575 }
5576 self.parked_drivers.deregister(&parker);
5579 }
5580 }
5581
5582 fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
5588 if self.has_queued_runnable() {
5589 return true;
5590 }
5591 can_run_blocking
5592 && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5593 && !self
5594 .blocking_queue
5595 .lock()
5596 .unwrap_or_else(std::sync::PoisonError::into_inner)
5597 .is_empty()
5598 }
5599
5600 fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
5601 if self.has_queued_runnable() {
5602 self.wake_for_new_work();
5603 }
5604 if self.stop.is_stopping() {
5608 return;
5609 }
5610 if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5611 && !self
5612 .blocking_queue
5613 .lock()
5614 .unwrap_or_else(std::sync::PoisonError::into_inner)
5615 .is_empty()
5616 {
5617 let consumed = can_run_blocking && self.run_one_blocking();
5625 let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5626 && !self
5627 .blocking_queue
5628 .lock()
5629 .unwrap_or_else(std::sync::PoisonError::into_inner)
5630 .is_empty();
5631 if !self.stop.is_stopping() && (!consumed || has_residue) {
5632 self.wake_for_blocking_work();
5633 }
5634 }
5635 }
5636
5637 fn has_queued_runnable(&self) -> bool {
5638 !self
5639 .queue
5640 .lock()
5641 .unwrap_or_else(std::sync::PoisonError::into_inner)
5642 .is_empty()
5643 }
5644
5645 fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
5649 let admission = self.begin_deadlock_admission();
5650 loop {
5651 let active = self.active_blocking.load(Ordering::Acquire);
5652 if active >= self.max_blocking {
5653 return None;
5654 }
5655 let mut queue = self
5656 .blocking_queue
5657 .lock()
5658 .unwrap_or_else(std::sync::PoisonError::into_inner);
5659 if queue.closed || self.stop.is_stopping() || queue.is_empty() {
5660 return None;
5661 }
5662 if self
5663 .active_blocking
5664 .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5665 .is_ok()
5666 {
5667 let blocking = queue
5668 .pop_front()
5669 .expect("a non-empty blocking queue must have a FIFO head");
5670 drop(queue);
5671 #[cfg(test)]
5672 run_multi_thread_work_claimed_test_hook();
5673 return Some((blocking, admission));
5674 }
5675 }
5676 }
5677}
5678
5679#[cfg(not(target_family = "wasm"))]
5680struct MultiThreadDeadlockState {
5681 enabled: bool,
5682 verdict_closed: AtomicBool,
5683 admissions: AtomicUsize,
5684 publications: AtomicU64,
5685 owner_handoff_publication: Mutex<()>,
5686}
5687
5688#[cfg(not(target_family = "wasm"))]
5689impl MultiThreadDeadlockState {
5690 fn new(enabled: bool) -> Self {
5691 Self {
5692 enabled,
5693 verdict_closed: AtomicBool::new(false),
5694 admissions: AtomicUsize::new(0),
5695 publications: AtomicU64::new(0),
5696 owner_handoff_publication: Mutex::new(()),
5697 }
5698 }
5699
5700 fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
5701 if !self.enabled {
5702 return None;
5703 }
5704 loop {
5705 while self.verdict_closed.load(Ordering::SeqCst) {
5706 #[cfg(test)]
5707 run_deadlock_closed_gate_admission_test_hook();
5708 std::hint::spin_loop();
5709 }
5710 self.admissions.fetch_add(1, Ordering::SeqCst);
5711 if !self.verdict_closed.load(Ordering::SeqCst) {
5712 return Some(MultiThreadDeadlockAdmissionGuard {
5713 state: Arc::clone(self),
5714 published: false,
5715 });
5716 }
5717 self.admissions.fetch_sub(1, Ordering::SeqCst);
5718 }
5719 }
5720
5721 fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
5722 debug_assert!(
5723 self.enabled,
5724 "a deadline verdict requires an enabled admission state"
5725 );
5726 if self
5727 .verdict_closed
5728 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
5729 .is_err()
5730 {
5731 return None;
5732 }
5733 if self.admissions.load(Ordering::SeqCst) != 0 {
5734 self.verdict_closed.store(false, Ordering::SeqCst);
5735 return None;
5736 }
5737 Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
5738 }
5739
5740 fn publications(&self) -> u64 {
5741 self.publications.load(Ordering::SeqCst)
5742 }
5743
5744 fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
5745 self.enabled.then(|| {
5746 self
5747 .owner_handoff_publication
5748 .lock()
5749 .unwrap_or_else(std::sync::PoisonError::into_inner)
5750 })
5751 }
5752}
5753
5754#[cfg(not(target_family = "wasm"))]
5755struct MultiThreadDeadlockAdmissionGuard {
5756 state: Arc<MultiThreadDeadlockState>,
5757 published: bool,
5758}
5759
5760#[cfg(not(target_family = "wasm"))]
5761impl MultiThreadDeadlockAdmissionGuard {
5762 fn mark_published(&mut self) {
5763 self.published = true;
5764 }
5765}
5766
5767#[cfg(not(target_family = "wasm"))]
5768impl Drop for MultiThreadDeadlockAdmissionGuard {
5769 fn drop(&mut self) {
5770 if self.published {
5771 self.state.publications.fetch_add(1, Ordering::SeqCst);
5772 }
5773 self.state.admissions.fetch_sub(1, Ordering::SeqCst);
5776 }
5777}
5778
5779#[cfg(not(target_family = "wasm"))]
5780struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
5781
5782#[cfg(not(target_family = "wasm"))]
5783impl Drop for MultiThreadDeadlockVerdictGuard {
5784 fn drop(&mut self) {
5785 self.0.verdict_closed.store(false, Ordering::SeqCst);
5786 }
5787}
5788
5789#[cfg(not(target_family = "wasm"))]
5795struct CooperativeDriverExitGuard<'a> {
5796 executor: &'a Arc<MultiThreadExecutor>,
5797 parker: &'a Arc<DriverParker>,
5798 can_run_blocking: bool,
5799}
5800
5801#[cfg(not(target_family = "wasm"))]
5802impl Drop for CooperativeDriverExitGuard<'_> {
5803 fn drop(&mut self) {
5804 let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
5805 let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
5806 let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
5807 let _ = catch_unwind_contained(|| {
5808 self
5809 .executor
5810 .compensate_queued_work_wake(self.can_run_blocking);
5811 });
5812 }
5813}
5814
5815#[cfg(not(target_family = "wasm"))]
5821struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
5822
5823#[cfg(not(target_family = "wasm"))]
5824impl Drop for LifoSlotFlushGuard<'_> {
5825 fn drop(&mut self) {
5826 self.0.flush_lifo_slot();
5827 }
5828}
5829
5830const PARKER_EMPTY: usize = 0;
5852const PARKER_NOTIFIED: usize = 1;
5854const PARKER_SLEEPING: usize = 2;
5856
5857#[derive(Default)]
5861struct DriverParker {
5862 state: AtomicUsize,
5863 lock: Mutex<()>,
5864 condvar: std::sync::Condvar,
5865 #[cfg(not(target_family = "wasm"))]
5866 owner_handoff_pending: AtomicBool,
5867 #[cfg(not(target_family = "wasm"))]
5868 owner_handoff: Mutex<Option<BlockingOwnerToken>>,
5869}
5870
5871impl DriverParker {
5872 #[cfg(not(target_family = "wasm"))]
5873 fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
5874 let mut pending = self
5875 .owner_handoff
5876 .lock()
5877 .unwrap_or_else(std::sync::PoisonError::into_inner);
5878 debug_assert!(pending.is_none());
5879 *pending = Some(owner);
5880 self.owner_handoff_pending.store(true, Ordering::Release);
5881 }
5882
5883 #[cfg(not(target_family = "wasm"))]
5884 fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
5885 if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
5886 return None;
5887 }
5888 self
5889 .owner_handoff
5890 .lock()
5891 .unwrap_or_else(std::sync::PoisonError::into_inner)
5892 .take()
5893 }
5894
5895 #[cfg(not(target_family = "wasm"))]
5896 fn has_owner_handoff(&self) -> bool {
5897 self.owner_handoff_pending.load(Ordering::Acquire)
5898 }
5899
5900 fn unpark(&self) {
5907 if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
5908 drop(
5913 self
5914 .lock
5915 .lock()
5916 .unwrap_or_else(std::sync::PoisonError::into_inner),
5917 );
5918 self.condvar.notify_one();
5919 }
5920 }
5921
5922 fn consume_permit(&self) -> bool {
5927 self
5928 .state
5929 .compare_exchange(
5930 PARKER_NOTIFIED,
5931 PARKER_EMPTY,
5932 Ordering::SeqCst,
5933 Ordering::SeqCst,
5934 )
5935 .is_ok()
5936 }
5937
5938 fn park(&self) {
5942 if self.consume_permit() {
5944 return;
5945 }
5946 let mut guard = self
5947 .lock
5948 .lock()
5949 .unwrap_or_else(std::sync::PoisonError::into_inner);
5950 match self.state.compare_exchange(
5951 PARKER_EMPTY,
5952 PARKER_SLEEPING,
5953 Ordering::SeqCst,
5954 Ordering::SeqCst,
5955 ) {
5956 Ok(_) => {}
5957 Err(_) => {
5961 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
5962 return;
5963 }
5964 }
5965 loop {
5966 guard = self
5967 .condvar
5968 .wait(guard)
5969 .unwrap_or_else(std::sync::PoisonError::into_inner);
5970 if self
5971 .state
5972 .compare_exchange(
5973 PARKER_NOTIFIED,
5974 PARKER_EMPTY,
5975 Ordering::SeqCst,
5976 Ordering::SeqCst,
5977 )
5978 .is_ok()
5979 {
5980 return;
5981 }
5982 }
5984 }
5985
5986 fn park_timeout(&self, timeout: Duration) -> bool {
5992 if self.consume_permit() {
5993 return true;
5994 }
5995 let mut guard = self
5996 .lock
5997 .lock()
5998 .unwrap_or_else(std::sync::PoisonError::into_inner);
5999 match self.state.compare_exchange(
6000 PARKER_EMPTY,
6001 PARKER_SLEEPING,
6002 Ordering::SeqCst,
6003 Ordering::SeqCst,
6004 ) {
6005 Ok(_) => {}
6006 Err(_) => {
6010 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6011 return true;
6012 }
6013 }
6014 let Some(deadline) = Instant::now().checked_add(timeout) else {
6015 loop {
6019 guard = self
6020 .condvar
6021 .wait(guard)
6022 .unwrap_or_else(std::sync::PoisonError::into_inner);
6023 if self
6024 .state
6025 .compare_exchange(
6026 PARKER_NOTIFIED,
6027 PARKER_EMPTY,
6028 Ordering::SeqCst,
6029 Ordering::SeqCst,
6030 )
6031 .is_ok()
6032 {
6033 return true;
6034 }
6035 }
6036 };
6037 loop {
6038 if self
6039 .state
6040 .compare_exchange(
6041 PARKER_NOTIFIED,
6042 PARKER_EMPTY,
6043 Ordering::SeqCst,
6044 Ordering::SeqCst,
6045 )
6046 .is_ok()
6047 {
6048 return true;
6049 }
6050 let now = Instant::now();
6051 if now >= deadline {
6052 return match self.state.compare_exchange(
6057 PARKER_SLEEPING,
6058 PARKER_EMPTY,
6059 Ordering::SeqCst,
6060 Ordering::SeqCst,
6061 ) {
6062 Ok(_) => false,
6063 Err(_) => {
6064 self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6065 true
6066 }
6067 };
6068 }
6069 let (returned_guard, _timed_out) = self
6070 .condvar
6071 .wait_timeout(guard, deadline - now)
6072 .unwrap_or_else(std::sync::PoisonError::into_inner);
6073 guard = returned_guard;
6074 }
6077 }
6078}
6079
6080impl std::task::Wake for DriverParker {
6081 fn wake(self: Arc<Self>) {
6082 self.unpark();
6083 }
6084
6085 fn wake_by_ref(self: &Arc<Self>) {
6086 self.unpark();
6087 }
6088}
6089
6090enum DeadlineParkOutcome {
6092 Woken,
6094 Expired(ProgressFingerprint),
6103}
6104
6105fn park_with_deadline(
6114 parker: &DriverParker,
6115 deadline: Duration,
6116 metrics: &RuntimeMetrics,
6117) -> DeadlineParkOutcome {
6118 let mut armed = metrics.progress_fingerprint();
6119 loop {
6120 if parker.park_timeout(deadline) {
6121 return DeadlineParkOutcome::Woken;
6122 }
6123 let current = metrics.progress_fingerprint();
6124 if current != armed {
6125 armed = current;
6126 continue;
6127 }
6128 return DeadlineParkOutcome::Expired(armed);
6129 }
6130}
6131
6132#[cfg(all(test, not(target_family = "wasm")))]
6140thread_local! {
6141 static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6142 const { std::cell::RefCell::new(None) };
6143}
6144
6145#[cfg(all(test, not(target_family = "wasm")))]
6146fn run_deadline_expiry_test_hook() {
6147 if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6148 hook();
6149 }
6150}
6151
6152#[cfg(all(test, not(target_family = "wasm")))]
6159thread_local! {
6160 static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6161 const { std::cell::RefCell::new(None) };
6162}
6163
6164#[cfg(all(test, not(target_family = "wasm")))]
6165fn run_deadline_verdict_test_hook() {
6166 if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6167 hook();
6168 }
6169}
6170
6171#[cfg(all(test, not(target_family = "wasm")))]
6176thread_local! {
6177 static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6178 const { std::cell::RefCell::new(None) };
6179}
6180
6181#[cfg(all(test, not(target_family = "wasm")))]
6182fn run_deadline_final_verdict_test_hook() {
6183 if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6184 hook();
6185 }
6186}
6187
6188#[cfg(all(test, not(target_family = "wasm")))]
6193thread_local! {
6194 static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6195 const { std::cell::RefCell::new(None) };
6196}
6197
6198#[cfg(all(test, not(target_family = "wasm")))]
6199fn run_deadline_gated_verdict_test_hook() {
6200 if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6201 hook();
6202 }
6203}
6204
6205#[cfg(all(test, not(target_family = "wasm")))]
6209thread_local! {
6210 static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6211 const { std::cell::RefCell::new(None) };
6212}
6213
6214#[cfg(all(test, not(target_family = "wasm")))]
6215fn run_deadline_synchronized_stop_test_hook() {
6216 if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6217 hook();
6218 }
6219}
6220
6221#[cfg(all(test, not(target_family = "wasm")))]
6225type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
6226
6227#[cfg(all(test, not(target_family = "wasm")))]
6228thread_local! {
6229 static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
6230 std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
6231 const { std::cell::RefCell::new(None) };
6232}
6233
6234#[cfg(all(test, not(target_family = "wasm")))]
6235fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
6236 if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6237 hook(parker);
6238 }
6239}
6240
6241#[cfg(all(test, not(target_family = "wasm")))]
6245thread_local! {
6246 static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
6247 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6248 const { std::cell::RefCell::new(None) };
6249}
6250
6251#[cfg(all(test, not(target_family = "wasm")))]
6252fn run_before_generation_stop_publication_lock_test_hook() {
6253 if let Some(hook) =
6254 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6255 {
6256 hook();
6257 }
6258}
6259
6260#[cfg(all(test, not(target_family = "wasm")))]
6264thread_local! {
6265 static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
6266 std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6267 const { std::cell::RefCell::new(None) };
6268}
6269
6270#[cfg(all(test, not(target_family = "wasm")))]
6271fn run_deadlock_closed_gate_admission_test_hook() {
6272 if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6273 {
6274 hook();
6275 }
6276}
6277
6278#[cfg(all(test, not(target_family = "wasm")))]
6282thread_local! {
6283 static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6284 const { std::cell::RefCell::new(None) };
6285}
6286
6287#[cfg(all(test, not(target_family = "wasm")))]
6288fn run_timer_registration_admission_test_hook() {
6289 if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6290 hook();
6291 }
6292}
6293
6294#[cfg(all(test, not(target_family = "wasm")))]
6295thread_local! {
6296 static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6297 const { std::cell::RefCell::new(None) };
6298}
6299
6300#[cfg(all(test, not(target_family = "wasm")))]
6301fn run_owner_handoff_selected_test_hook() {
6302 if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6303 hook();
6304 }
6305}
6306
6307#[cfg(not(target_family = "wasm"))]
6310#[derive(Default)]
6311struct ParkedDrivers {
6312 parked: Mutex<Vec<ParkedDriver>>,
6313 count: AtomicUsize,
6316}
6317
6318#[cfg(not(target_family = "wasm"))]
6319struct ParkedDriver {
6320 parker: Arc<DriverParker>,
6321 can_run_blocking: bool,
6322 dependency: Option<Arc<TaskDependency>>,
6323}
6324
6325#[cfg(not(target_family = "wasm"))]
6326impl ParkedDrivers {
6327 #[cfg(test)]
6330 fn register(&self, parker: &Arc<DriverParker>) {
6331 self.register_with_role(parker, true, None);
6332 }
6333
6334 fn register_with_role(
6335 &self,
6336 parker: &Arc<DriverParker>,
6337 can_run_blocking: bool,
6338 dependency: Option<Arc<TaskDependency>>,
6339 ) {
6340 let mut parked = self
6341 .parked
6342 .lock()
6343 .unwrap_or_else(std::sync::PoisonError::into_inner);
6344 parked.push(ParkedDriver {
6345 parker: Arc::clone(parker),
6346 can_run_blocking,
6347 dependency,
6348 });
6349 self.count.store(parked.len(), Ordering::SeqCst);
6350 }
6351
6352 fn deregister(&self, parker: &Arc<DriverParker>) {
6356 let removed = {
6357 let mut parked = self
6358 .parked
6359 .lock()
6360 .unwrap_or_else(std::sync::PoisonError::into_inner);
6361 let removed = parked
6362 .iter()
6363 .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
6364 .map(|index| parked.swap_remove(index));
6365 self.count.store(parked.len(), Ordering::SeqCst);
6366 removed
6367 };
6368 drop(removed);
6369 }
6370
6371 fn wake_one(&self) -> bool {
6394 if self.count.load(Ordering::SeqCst) == 0 {
6395 return false;
6399 }
6400 let driver = {
6401 let mut parked = self
6402 .parked
6403 .lock()
6404 .unwrap_or_else(std::sync::PoisonError::into_inner);
6405 let driver = parked.pop();
6406 self.count.store(parked.len(), Ordering::SeqCst);
6407 driver
6408 };
6409 match driver {
6410 Some(driver) => {
6411 driver.parker.unpark();
6412 true
6413 }
6414 None => false,
6415 }
6416 }
6417
6418 fn wake_one_blocking(&self) -> bool {
6421 if self.count.load(Ordering::SeqCst) == 0 {
6422 return false;
6423 }
6424 let driver = {
6425 let mut parked = self
6426 .parked
6427 .lock()
6428 .unwrap_or_else(std::sync::PoisonError::into_inner);
6429 let driver = parked
6430 .iter()
6431 .rposition(|driver| driver.can_run_blocking)
6432 .map(|index| parked.swap_remove(index));
6433 self.count.store(parked.len(), Ordering::SeqCst);
6434 driver
6435 };
6436 match driver {
6437 Some(driver) => {
6438 driver.parker.unpark();
6439 true
6440 }
6441 None => false,
6442 }
6443 }
6444
6445 fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
6450 loop {
6451 if self.count.load(Ordering::SeqCst) == 0 {
6452 return false;
6453 }
6454 let candidates = {
6455 let parked = self
6456 .parked
6457 .lock()
6458 .unwrap_or_else(std::sync::PoisonError::into_inner);
6459 parked
6460 .iter()
6461 .rev()
6462 .filter_map(|driver| {
6463 if !driver.can_run_blocking {
6464 return None;
6465 }
6466 driver
6467 .dependency
6468 .as_ref()
6469 .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
6470 })
6471 .collect::<Vec<_>>()
6472 };
6473 let selected = candidates.into_iter().find(|(_, dependency)| {
6474 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
6475 });
6476 let Some((selected_parker, selected_dependency)) = selected else {
6477 return false;
6478 };
6479 let driver = {
6480 let mut parked = self
6481 .parked
6482 .lock()
6483 .unwrap_or_else(std::sync::PoisonError::into_inner);
6484 let driver = parked
6485 .iter()
6486 .rposition(|driver| {
6487 Arc::ptr_eq(&driver.parker, &selected_parker)
6488 && driver
6489 .dependency
6490 .as_ref()
6491 .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
6492 })
6493 .map(|index| parked.swap_remove(index));
6494 if let Some(driver) = &driver {
6499 driver.parker.grant_owner_handoff(owner);
6500 }
6501 self.count.store(parked.len(), Ordering::SeqCst);
6502 driver
6503 };
6504 if let Some(driver) = driver {
6505 #[cfg(test)]
6506 run_owner_handoff_selected_test_hook();
6507 driver.parker.unpark();
6508 return true;
6509 }
6510 }
6511 }
6512
6513 fn wake_all(&self) {
6516 if self.count.load(Ordering::SeqCst) == 0 {
6517 return;
6518 }
6519 let drivers = {
6520 let mut parked = self
6521 .parked
6522 .lock()
6523 .unwrap_or_else(std::sync::PoisonError::into_inner);
6524 let drivers = parked.drain(..).collect::<Vec<_>>();
6525 self.count.store(0, Ordering::SeqCst);
6526 drivers
6527 };
6528 for driver in drivers {
6529 driver.parker.unpark();
6530 }
6531 }
6532}
6533
6534pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
6579 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
6592
6593 fn is_live(&self) -> bool {
6594 true
6595 }
6596
6597 fn on_swept(&self) {}
6600}
6601
6602#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6603pub struct CurrentThreadTaskDriverId(u64);
6604
6605type CurrentThreadTaskDriverSnapshot =
6606 ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
6607
6608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6609pub struct CurrentThreadTaskDelivery {
6610 driver: CurrentThreadTaskDriverId,
6611 capability: u64,
6612 dispatch: u64,
6613}
6614
6615impl CurrentThreadTaskDelivery {
6616 pub fn capability(self) -> u64 {
6618 self.capability
6619 }
6620}
6621
6622struct CurrentThreadTaskDeliveryState {
6623 delivery: CurrentThreadTaskDelivery,
6624 claimed: bool,
6625 accepted: bool,
6626}
6627
6628struct CurrentThreadTaskDriverEntry {
6629 driver: Arc<dyn CurrentThreadTaskDriver>,
6630 delivery: Option<CurrentThreadTaskDeliveryState>,
6631 pending: Option<u64>,
6632 previous: Option<CurrentThreadTaskDriverId>,
6633 next: Option<CurrentThreadTaskDriverId>,
6634}
6635
6636#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
6637enum CurrentThreadDispatchPhase {
6638 #[default]
6639 Pending,
6640 Serviced,
6641 Cancelling,
6642}
6643
6644#[derive(Default)]
6645struct CurrentThreadDispatchState {
6646 phase: CurrentThreadDispatchPhase,
6647 epoch: u64,
6648 publications: usize,
6649 references: usize,
6650 accepted: bool,
6651 accepted_failure: bool,
6652}
6653
6654impl CurrentThreadDispatchState {
6655 fn is_serviced(&self) -> bool {
6656 self.phase == CurrentThreadDispatchPhase::Serviced
6657 }
6658
6659 fn is_cancelling(&self) -> bool {
6660 self.phase == CurrentThreadDispatchPhase::Cancelling
6661 }
6662
6663 fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
6664 CurrentThreadTaskDispatchFailure {
6665 dispatch,
6666 epoch: self.epoch,
6667 }
6668 }
6669}
6670
6671#[derive(Default)]
6672struct CurrentThreadTaskDriverRegistryState {
6673 entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
6674 oldest: Option<CurrentThreadTaskDriverId>,
6675 newest: Option<CurrentThreadTaskDriverId>,
6676 delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
6677 dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
6678}
6679
6680impl CurrentThreadTaskDriverRegistryState {
6681 fn insert_driver(
6682 &mut self,
6683 id: CurrentThreadTaskDriverId,
6684 driver: Arc<dyn CurrentThreadTaskDriver>,
6685 ) {
6686 let previous = self.newest;
6687 let replaced = self.entries.insert(
6688 id,
6689 CurrentThreadTaskDriverEntry {
6690 driver,
6691 delivery: None,
6692 pending: None,
6693 previous,
6694 next: None,
6695 },
6696 );
6697 assert!(
6698 replaced.is_none(),
6699 "CurrentThread task driver id must be unique"
6700 );
6701 if let Some(previous) = previous {
6702 self
6703 .entries
6704 .get_mut(&previous)
6705 .expect("the newest CurrentThread task driver must remain registered")
6706 .next = Some(id);
6707 } else {
6708 self.oldest = Some(id);
6709 }
6710 self.newest = Some(id);
6711 }
6712
6713 fn remove_driver(
6714 &mut self,
6715 id: CurrentThreadTaskDriverId,
6716 ) -> Option<CurrentThreadTaskDriverEntry> {
6717 let entry = self.entries.remove(&id)?;
6718 if let Some(previous) = entry.previous {
6719 self
6720 .entries
6721 .get_mut(&previous)
6722 .expect("the previous CurrentThread task driver must remain registered")
6723 .next = entry.next;
6724 } else {
6725 self.oldest = entry.next;
6726 }
6727 if let Some(next) = entry.next {
6728 self
6729 .entries
6730 .get_mut(&next)
6731 .expect("the next CurrentThread task driver must remain registered")
6732 .previous = entry.previous;
6733 } else {
6734 self.newest = entry.previous;
6735 }
6736 if let Some(delivery) = &entry.delivery {
6737 let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
6738 debug_assert_eq!(
6739 indexed,
6740 Some(id),
6741 "delivery capability index must match its driver"
6742 );
6743 }
6744 Some(entry)
6745 }
6746
6747 fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
6748 let mut snapshot = Vec::with_capacity(self.entries.len());
6749 let mut current = self.oldest;
6750 while let Some(id) = current {
6751 let entry = self
6752 .entries
6753 .get(&id)
6754 .expect("CurrentThread task driver order must reference a live entry");
6755 snapshot.push((id, Arc::clone(&entry.driver)));
6756 current = entry.next;
6757 }
6758 debug_assert_eq!(
6759 snapshot.len(),
6760 self.entries.len(),
6761 "CurrentThread task driver order must contain every entry exactly once"
6762 );
6763 snapshot
6764 }
6765
6766 fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
6767 if self.entries.len() != expected.len() {
6768 return false;
6769 }
6770 let mut current = self.oldest;
6771 for expected in expected {
6772 if current != Some(*expected) {
6773 return false;
6774 }
6775 current = self
6776 .entries
6777 .get(expected)
6778 .expect("CurrentThread task driver order must reference a live entry")
6779 .next;
6780 }
6781 current.is_none()
6782 }
6783}
6784
6785struct CurrentThreadTaskDriverSelection {
6786 live: CurrentThreadTaskDriverSnapshot,
6787 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
6788}
6789
6790#[derive(Default)]
6791struct CurrentThreadTaskDeliveryCompletion {
6792 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
6793 redispatch: bool,
6794}
6795
6796struct CurrentThreadTaskDispatchCompletion {
6797 result: CurrentThreadHostDispatchResult,
6798 failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
6799}
6800
6801struct CurrentThreadTaskDispatchPublicationGuard<'a> {
6802 registry: &'a CurrentThreadTaskDriverRegistry,
6803 dispatch: Option<u64>,
6804}
6805
6806impl CurrentThreadTaskDispatchPublicationGuard<'_> {
6807 fn finish(mut self) -> CurrentThreadHostDispatchResult {
6808 let dispatch = self
6809 .dispatch
6810 .expect("the CurrentThread task dispatch publication must remain armed");
6811 let result = self.registry.finish_publication(dispatch);
6812 self.dispatch = None;
6813 result
6814 }
6815}
6816
6817impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
6818 fn drop(&mut self) {
6819 if let Some(dispatch) = self.dispatch.take() {
6820 self.registry.abort_publication(dispatch);
6821 }
6822 }
6823}
6824
6825#[derive(Default)]
6826struct CurrentThreadTaskDriverRegistry {
6827 state: Mutex<CurrentThreadTaskDriverRegistryState>,
6828 next_id: AtomicU64,
6829 next_delivery: AtomicU64,
6830}
6831
6832impl CurrentThreadTaskDriverRegistry {
6833 fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
6834 let id = next_unique_id(
6835 &self.next_id,
6836 "CurrentThread task driver id space exhausted",
6837 );
6838 let id = CurrentThreadTaskDriverId(id);
6839 self
6840 .state
6841 .lock()
6842 .unwrap_or_else(std::sync::PoisonError::into_inner)
6843 .insert_driver(id, driver);
6844 id
6845 }
6846
6847 fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
6848 let (removed, failed_dispatches) = {
6849 let mut state = self
6850 .state
6851 .lock()
6852 .unwrap_or_else(std::sync::PoisonError::into_inner);
6853 let removed = state.remove_driver(id);
6854 let mut failed_dispatches = Vec::new();
6855 if let Some(entry) = &removed {
6856 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
6857 }
6858 (removed, failed_dispatches)
6859 };
6860 let _ = catch_unwind_contained(|| drop(removed));
6861 CurrentThreadTaskDeliveryCompletion {
6862 failed_dispatches,
6863 redispatch: false,
6864 }
6865 }
6866
6867 fn live(&self) -> CurrentThreadTaskDriverSelection {
6868 let mut failed_dispatches = Vec::new();
6869 loop {
6870 let snapshot = CurrentThreadTaskDriverSnapshot::new({
6871 let state = self
6872 .state
6873 .lock()
6874 .unwrap_or_else(std::sync::PoisonError::into_inner);
6875 state.ordered_snapshot()
6876 });
6877 let mut dead_ids = FxHashSet::default();
6878 let mut live_ids = Vec::new();
6879 for (id, driver) in snapshot.as_slice() {
6880 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
6881 live_ids.push(*id);
6882 } else {
6883 dead_ids.insert(*id);
6884 }
6885 }
6886
6887 let (live, swept, retry) = {
6888 let mut state = self
6889 .state
6890 .lock()
6891 .unwrap_or_else(std::sync::PoisonError::into_inner);
6892 let mut swept = Vec::new();
6893 let mut current = state.oldest;
6894 while let Some(id) = current {
6895 let next = state
6896 .entries
6897 .get(&id)
6898 .expect("CurrentThread task driver order must reference a live entry")
6899 .next;
6900 if dead_ids.contains(&id)
6901 && let Some(entry) = state.remove_driver(id)
6902 {
6903 swept.push(entry);
6904 }
6905 current = next;
6906 }
6907 for entry in &swept {
6908 Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
6909 }
6910 let retry = !state.ordered_ids_match(&live_ids);
6911 let live = if retry {
6912 Vec::new()
6913 } else {
6914 state.ordered_snapshot()
6915 };
6916 (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
6917 };
6918
6919 drop(snapshot);
6922 for entry in swept {
6923 let _ = catch_unwind_contained(|| entry.driver.on_swept());
6924 let _ = catch_unwind_contained(|| drop(entry));
6925 }
6926 if !retry {
6927 return CurrentThreadTaskDriverSelection {
6928 live,
6929 failed_dispatches,
6930 };
6931 }
6932 }
6933 }
6934
6935 #[cfg(test)]
6936 fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
6937 let mut live = self.live().live;
6938 live.pop().map(|(id, driver)| (id, driver.into_inner()))
6939 }
6940
6941 fn next_delivery(
6942 &self,
6943 driver: CurrentThreadTaskDriverId,
6944 dispatch: u64,
6945 ) -> CurrentThreadTaskDelivery {
6946 let capability = next_unique_id(
6947 &self.next_delivery,
6948 "CurrentThread task delivery id space exhausted",
6949 )
6950 .checked_add(1)
6951 .expect("CurrentThread task delivery id space exhausted");
6952 CurrentThreadTaskDelivery {
6953 driver,
6954 capability,
6955 dispatch,
6956 }
6957 }
6958
6959 fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
6960 let dispatch_state = state
6961 .dispatches
6962 .get_mut(&dispatch)
6963 .expect("a referenced CurrentThread dispatch must remain registered");
6964 dispatch_state.references = dispatch_state
6965 .references
6966 .checked_add(1)
6967 .expect("CurrentThread dispatch reference count exhausted");
6968 }
6969
6970 fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
6971 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
6972 return;
6973 };
6974 dispatch_state.references = dispatch_state
6975 .references
6976 .checked_sub(1)
6977 .expect("CurrentThread dispatch reference count underflow");
6978 }
6979
6980 fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
6981 let publication = {
6982 let mut state = self
6983 .state
6984 .lock()
6985 .unwrap_or_else(std::sync::PoisonError::into_inner);
6986 let dispatch_state = state.dispatches.entry(dispatch).or_default();
6987 if dispatch_state.is_serviced() {
6988 return CurrentThreadTaskDispatchCompletion {
6989 result: CurrentThreadHostDispatchResult::Accepted,
6990 failed_dispatches: Vec::new(),
6991 };
6992 }
6993 if dispatch_state.is_cancelling() {
6994 return CurrentThreadTaskDispatchCompletion {
6995 result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
6996 failed_dispatches: Vec::new(),
6997 };
6998 }
6999 dispatch_state.publications = dispatch_state
7000 .publications
7001 .checked_add(1)
7002 .expect("CurrentThread dispatch publication count exhausted");
7003 CurrentThreadTaskDispatchPublicationGuard {
7004 registry: self,
7005 dispatch: Some(dispatch),
7006 }
7007 };
7008
7009 let CurrentThreadTaskDriverSelection {
7010 mut live,
7011 mut failed_dispatches,
7012 } = self.live();
7013 let planned_deliveries = {
7014 let mut state = self
7015 .state
7016 .lock()
7017 .unwrap_or_else(std::sync::PoisonError::into_inner);
7018 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
7019 return CurrentThreadTaskDispatchCompletion {
7020 result: CurrentThreadHostDispatchResult::Unavailable,
7021 failed_dispatches,
7022 };
7023 };
7024 if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
7025 Vec::new()
7026 } else {
7027 let mut reserved_deliveries = Vec::with_capacity(live.len());
7031 for (id, _) in live.as_slice().iter().rev() {
7032 let Some(entry) = state.entries.get(id) else {
7033 continue;
7034 };
7035 if entry.delivery.is_none() {
7036 reserved_deliveries.push(self.next_delivery(*id, dispatch));
7037 }
7038 }
7039 state.delivery_drivers.reserve(reserved_deliveries.len());
7040 let mut reserved_deliveries = reserved_deliveries.into_iter();
7041 let mut planned_deliveries = Vec::with_capacity(live.len());
7042 let mut retired = Vec::with_capacity(live.len());
7043 let mut accepted_reservation = false;
7044 for (id, _) in live.as_slice().iter().rev() {
7045 let Some(entry) = state.entries.get(id) else {
7046 continue;
7047 };
7048 if let Some(delivery) = &entry.delivery
7049 && delivery.delivery.dispatch == dispatch
7050 {
7051 accepted_reservation |= delivery.accepted;
7052 continue;
7053 }
7054 if entry.delivery.is_some() {
7055 let previous = state
7056 .entries
7057 .get_mut(id)
7058 .expect("the CurrentThread task driver must remain registered")
7059 .pending
7060 .replace(dispatch);
7061 if previous != Some(dispatch) {
7062 if let Some(previous) = previous {
7063 Self::remove_dispatch_reference(&mut state, previous);
7064 }
7065 Self::add_dispatch_reference(&mut state, dispatch);
7066 }
7067 retired.push(previous);
7068 accepted_reservation = true;
7069 continue;
7070 }
7071 let delivery = reserved_deliveries
7072 .next()
7073 .expect("every available CurrentThread host must have a reserved delivery");
7074 let pending = state
7075 .entries
7076 .get_mut(id)
7077 .expect("the CurrentThread task driver must remain registered")
7078 .pending
7079 .take();
7080 let accepted = pending == Some(dispatch);
7081 if !accepted && pending.is_some() {
7082 retired.push(pending);
7083 }
7084 match pending {
7085 Some(pending) if pending == dispatch => {}
7086 Some(pending) => {
7087 Self::remove_dispatch_reference(&mut state, pending);
7088 Self::add_dispatch_reference(&mut state, dispatch);
7089 }
7090 None => Self::add_dispatch_reference(&mut state, dispatch),
7091 }
7092 let indexed = state.delivery_drivers.insert(delivery.capability, *id);
7093 assert!(
7094 indexed.is_none(),
7095 "CurrentThread delivery capability must be unique"
7096 );
7097 state
7098 .entries
7099 .get_mut(id)
7100 .expect("the CurrentThread task driver must remain registered")
7101 .delivery = Some(CurrentThreadTaskDeliveryState {
7102 delivery,
7103 claimed: false,
7104 accepted,
7105 });
7106 accepted_reservation |= accepted;
7107 planned_deliveries.push((*id, delivery));
7108 }
7109 let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
7110 debug_assert!(
7111 all_reserved_deliveries_installed,
7112 "every reserved CurrentThread delivery must be installed"
7113 );
7114 if accepted_reservation {
7115 state
7116 .dispatches
7117 .get_mut(&dispatch)
7118 .expect("the publishing dispatch state must remain registered")
7119 .accepted = true;
7120 }
7121 for dispatch in retired.into_iter().flatten() {
7122 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7123 }
7124 planned_deliveries
7125 }
7126 };
7127
7128 let mut deliveries = Vec::with_capacity(planned_deliveries.len());
7133 let mut planned_deliveries = planned_deliveries.into_iter();
7134 let mut next_delivery = planned_deliveries.next();
7135 while let Some((id, driver)) = live.pop() {
7136 let Some((planned_id, delivery)) = next_delivery else {
7137 break;
7138 };
7139 if planned_id == id {
7140 deliveries.push((id, driver, delivery));
7141 next_delivery = planned_deliveries.next();
7142 } else {
7143 next_delivery = Some((planned_id, delivery));
7144 }
7145 }
7146 debug_assert!(
7147 next_delivery.is_none() && planned_deliveries.len() == 0,
7148 "every planned CurrentThread delivery must retain its selected driver"
7149 );
7150
7151 for (id, driver, delivery) in deliveries {
7152 if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
7153 self.accept_delivery(id, delivery);
7154 } else {
7155 let (entry, failures) = self.reject_delivery(id, delivery);
7156 failed_dispatches.extend(failures);
7157 if let Some(entry) = entry {
7158 let _ = catch_unwind_contained(|| entry.driver.on_swept());
7159 let _ = catch_unwind_contained(|| drop(entry));
7160 }
7161 }
7162 }
7163
7164 let result = publication.finish();
7165 debug_assert!(
7166 !failed_dispatches
7167 .iter()
7168 .any(|failure| failure.dispatch == dispatch),
7169 "the active publication must report its own terminal outcome directly"
7170 );
7171 CurrentThreadTaskDispatchCompletion {
7172 result,
7173 failed_dispatches,
7174 }
7175 }
7176
7177 #[cfg(test)]
7178 fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7179 self.dispatch_with_completion(dispatch).result
7180 }
7181
7182 fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
7183 let mut state = self
7184 .state
7185 .lock()
7186 .unwrap_or_else(std::sync::PoisonError::into_inner);
7187 let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
7188 entry
7189 .delivery
7190 .as_ref()
7191 .is_some_and(|current| current.delivery == delivery)
7192 }) {
7193 state
7194 .entries
7195 .get_mut(&id)
7196 .expect("the matching CurrentThread task driver must remain registered")
7197 .delivery
7198 .as_mut()
7199 .expect("the matching delivery must remain installed")
7200 .accepted = true;
7201 true
7202 } else {
7203 false
7208 };
7209 if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
7210 dispatch_state.accepted = true;
7211 dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
7212 }
7213 }
7214
7215 fn reject_delivery(
7216 &self,
7217 id: CurrentThreadTaskDriverId,
7218 delivery: CurrentThreadTaskDelivery,
7219 ) -> (
7220 Option<CurrentThreadTaskDriverEntry>,
7221 Vec<CurrentThreadTaskDispatchFailure>,
7222 ) {
7223 let mut state = self
7224 .state
7225 .lock()
7226 .unwrap_or_else(std::sync::PoisonError::into_inner);
7227 if !state.entries.get(&id).is_some_and(|entry| {
7228 entry
7229 .delivery
7230 .as_ref()
7231 .is_some_and(|current| current.delivery == delivery)
7232 }) {
7233 return (None, Vec::new());
7234 }
7235 let entry = state
7236 .remove_driver(id)
7237 .expect("the matching CurrentThread task driver must remain registered");
7238 let mut failures = Vec::new();
7239 Self::finish_entry_references(&mut state, &entry, &mut failures);
7240 (Some(entry), failures)
7241 }
7242
7243 fn finish_delivery(
7244 &self,
7245 delivery: CurrentThreadTaskDelivery,
7246 failed: bool,
7247 ) -> CurrentThreadTaskDeliveryCompletion {
7248 let mut state = self
7249 .state
7250 .lock()
7251 .unwrap_or_else(std::sync::PoisonError::into_inner);
7252 if !state.entries.get(&delivery.driver).is_some_and(|entry| {
7253 entry
7254 .delivery
7255 .as_ref()
7256 .is_some_and(|current| current.delivery == delivery)
7257 }) {
7258 return CurrentThreadTaskDeliveryCompletion::default();
7259 }
7260 let current = state
7261 .entries
7262 .get_mut(&delivery.driver)
7263 .expect("the matching CurrentThread task driver must remain registered")
7264 .delivery
7265 .take()
7266 .expect("the matching delivery must remain installed");
7267 let indexed = state.delivery_drivers.remove(&delivery.capability);
7268 debug_assert_eq!(
7269 indexed,
7270 Some(delivery.driver),
7271 "delivery capability index must match its driver"
7272 );
7273 let redispatch = state
7274 .entries
7275 .get(&delivery.driver)
7276 .expect("the matching CurrentThread task driver must remain registered")
7277 .pending
7278 .is_some_and(|dispatch| {
7279 state
7280 .dispatches
7281 .get(&dispatch)
7282 .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
7283 });
7284 let mut failed_dispatches = Vec::new();
7285 Self::finish_dispatch_reference(
7290 &mut state,
7291 delivery.dispatch,
7292 true,
7293 failed || !current.claimed,
7294 &mut failed_dispatches,
7295 );
7296 CurrentThreadTaskDeliveryCompletion {
7297 failed_dispatches,
7298 redispatch,
7299 }
7300 }
7301
7302 fn claim_delivery(&self, capability: u64) -> Option<u64> {
7303 let mut state = self
7304 .state
7305 .lock()
7306 .unwrap_or_else(std::sync::PoisonError::into_inner);
7307 let driver = *state.delivery_drivers.get(&capability)?;
7308 let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
7309 if delivery.capability != capability {
7310 return None;
7311 }
7312 let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
7313 if dispatch_state.is_serviced()
7314 || dispatch_state.is_cancelling()
7315 || state.entries.get(&driver)?.delivery.as_ref()?.claimed
7316 {
7317 return None;
7318 }
7319 let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
7320 current.claimed = true;
7321 current.accepted = true;
7322 state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
7323 Some(delivery.dispatch)
7324 }
7325
7326 fn mark_serviced(&self, dispatch: u64) {
7327 let mut state = self
7328 .state
7329 .lock()
7330 .unwrap_or_else(std::sync::PoisonError::into_inner);
7331 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7332 return;
7333 };
7334 dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
7335 let mut removed_references = 0usize;
7336 for entry in state.entries.values_mut() {
7337 if entry.pending == Some(dispatch) {
7338 entry.pending = None;
7339 removed_references = removed_references
7340 .checked_add(1)
7341 .expect("CurrentThread dispatch reference count exhausted");
7342 }
7343 }
7344 let dispatch_state = state
7345 .dispatches
7346 .get_mut(&dispatch)
7347 .expect("the serviced CurrentThread dispatch must remain registered");
7348 dispatch_state.references = dispatch_state
7349 .references
7350 .checked_sub(removed_references)
7351 .expect("CurrentThread dispatch reference count underflow");
7352 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7353 }
7354
7355 fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
7356 let delivery = {
7357 let state = self
7358 .state
7359 .lock()
7360 .unwrap_or_else(std::sync::PoisonError::into_inner);
7361 state
7362 .delivery_drivers
7363 .get(&capability)
7364 .and_then(|driver| state.entries.get(driver))
7365 .and_then(|entry| entry.delivery.as_ref())
7366 .map(|delivery| delivery.delivery)
7367 .filter(|delivery| delivery.capability == capability)
7368 };
7369 delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
7370 self.finish_delivery(delivery, true)
7371 })
7372 }
7373
7374 fn finish_dispatch_reference(
7375 state: &mut CurrentThreadTaskDriverRegistryState,
7376 dispatch: u64,
7377 accepted: bool,
7378 failed: bool,
7379 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7380 ) {
7381 let (publications, references) = {
7382 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7383 return;
7384 };
7385 dispatch_state.references = dispatch_state
7386 .references
7387 .checked_sub(1)
7388 .expect("CurrentThread dispatch reference count underflow");
7389 dispatch_state.accepted |= accepted;
7390 dispatch_state.accepted_failure |= accepted && failed;
7391 (dispatch_state.publications, dispatch_state.references)
7392 };
7393 if references != 0 || publications != 0 {
7394 return;
7395 }
7396 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7397 return;
7398 };
7399 if dispatch_state.accepted_failure
7400 && !dispatch_state.is_serviced()
7401 && !dispatch_state.is_cancelling()
7402 {
7403 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7404 failures.push(dispatch_state.failure(dispatch));
7405 } else if !dispatch_state.is_cancelling() {
7406 state.dispatches.remove(&dispatch);
7407 }
7408 }
7409
7410 fn finish_entry_references(
7411 state: &mut CurrentThreadTaskDriverRegistryState,
7412 entry: &CurrentThreadTaskDriverEntry,
7413 failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7414 ) {
7415 if let Some(delivery) = &entry.delivery {
7416 Self::finish_dispatch_reference(
7417 state,
7418 delivery.delivery.dispatch,
7419 delivery.accepted,
7420 delivery.accepted,
7421 failures,
7422 );
7423 }
7424 if let Some(pending) = entry.pending {
7425 Self::finish_dispatch_reference(state, pending, true, true, failures);
7426 }
7427 }
7428
7429 fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7430 let mut state = self
7431 .state
7432 .lock()
7433 .unwrap_or_else(std::sync::PoisonError::into_inner);
7434 let publications = {
7435 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7436 return CurrentThreadHostDispatchResult::Unavailable;
7437 };
7438 dispatch_state.publications = dispatch_state
7439 .publications
7440 .checked_sub(1)
7441 .expect("CurrentThread dispatch publication count underflow");
7442 dispatch_state.publications
7443 };
7444 if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
7445 return CurrentThreadHostDispatchResult::Accepted;
7446 }
7447 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7448 return CurrentThreadHostDispatchResult::Unavailable;
7449 };
7450 if dispatch_state.is_cancelling() {
7451 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7452 }
7453 if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
7454 dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7455 return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7456 }
7457 let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
7458 state.dispatches.remove(&dispatch);
7459 if accepted {
7460 CurrentThreadHostDispatchResult::Accepted
7461 } else {
7462 CurrentThreadHostDispatchResult::Unavailable
7463 }
7464 }
7465
7466 fn abort_publication(&self, dispatch: u64) {
7467 let mut state = self
7468 .state
7469 .lock()
7470 .unwrap_or_else(std::sync::PoisonError::into_inner);
7471 let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7472 return;
7473 };
7474 let Some(publications) = dispatch_state.publications.checked_sub(1) else {
7475 return;
7476 };
7477 dispatch_state.publications = publications;
7478 Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7479 }
7480
7481 fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
7482 let mut state = self
7483 .state
7484 .lock()
7485 .unwrap_or_else(std::sync::PoisonError::into_inner);
7486 let can_rearm = state
7487 .dispatches
7488 .get(&failure.dispatch)
7489 .is_some_and(|dispatch_state| {
7490 dispatch_state.is_cancelling()
7491 && dispatch_state.epoch == failure.epoch
7492 && dispatch_state.publications == 0
7493 })
7494 && !Self::has_dispatch_reference(&state, failure.dispatch);
7495 if !can_rearm {
7496 return false;
7497 }
7498 let dispatch_state = state
7499 .dispatches
7500 .get_mut(&failure.dispatch)
7501 .expect("the exact failed CurrentThread dispatch must remain registered");
7502 let epoch = dispatch_state
7503 .epoch
7504 .checked_add(1)
7505 .expect("CurrentThread dispatch failure epoch space exhausted");
7506 *dispatch_state = CurrentThreadDispatchState {
7507 epoch,
7508 ..CurrentThreadDispatchState::default()
7509 };
7510 true
7511 }
7512
7513 fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
7514 self
7515 .state
7516 .lock()
7517 .unwrap_or_else(std::sync::PoisonError::into_inner)
7518 .dispatches
7519 .get(&failure.dispatch)
7520 .is_some_and(|dispatch_state| {
7521 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
7522 })
7523 }
7524
7525 fn retire_unreferenced_dispatch(
7526 state: &mut CurrentThreadTaskDriverRegistryState,
7527 dispatch: Option<u64>,
7528 ) {
7529 let Some(dispatch) = dispatch else {
7530 return;
7531 };
7532 if Self::has_dispatch_reference(state, dispatch) {
7533 return;
7534 }
7535 let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
7536 return;
7537 };
7538 if dispatch_state.publications == 0
7539 && !dispatch_state.is_cancelling()
7540 && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
7541 {
7542 state.dispatches.remove(&dispatch);
7543 }
7544 }
7545
7546 fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
7547 state
7548 .dispatches
7549 .get(&dispatch)
7550 .is_some_and(|dispatch_state| dispatch_state.references != 0)
7551 }
7552
7553 fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
7554 let mut state = self
7555 .state
7556 .lock()
7557 .unwrap_or_else(std::sync::PoisonError::into_inner);
7558 if state
7559 .dispatches
7560 .get(&failure.dispatch)
7561 .is_some_and(|dispatch_state| {
7562 dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
7563 })
7564 {
7565 state.dispatches.remove(&failure.dispatch);
7566 }
7567 }
7568
7569 fn retire_for_shutdown(&self) {
7570 let mut state = self
7571 .state
7572 .lock()
7573 .unwrap_or_else(std::sync::PoisonError::into_inner);
7574 state.dispatches.clear();
7575 for entry in state.entries.values_mut() {
7576 entry.pending = None;
7577 }
7578 }
7579}
7580
7581static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
7582 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
7583
7584pub fn register_current_thread_task_driver(
7585 driver: Arc<dyn CurrentThreadTaskDriver>,
7586) -> CurrentThreadTaskDriverId {
7587 CURRENT_THREAD_TASK_DRIVERS.register(driver)
7588}
7589
7590pub fn request_current_thread_task_drain() {
7595 RUNTIME.request_current_thread_drain();
7596}
7597
7598pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
7599 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
7600}
7601
7602fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
7603 let CurrentThreadTaskDispatchCompletion {
7604 result,
7605 failed_dispatches,
7606 } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
7607 complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
7608 failed_dispatches,
7609 redispatch: false,
7610 });
7611 result
7612}
7613
7614pub type TimerId = u64;
7615
7616static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
7618
7619pub trait TimerDriver: Send + Sync + 'static {
7625 fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
7629 fn cancel(&self, id: TimerId);
7632 fn is_live(&self) -> bool {
7637 true
7638 }
7639 fn on_swept(&self) {}
7649}
7650
7651#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7655pub struct TimerDriverId(u64);
7656
7657type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
7658
7659struct TimerDriverEntry {
7660 driver: Arc<dyn TimerDriver>,
7661 previous: Option<TimerDriverId>,
7662 next: Option<TimerDriverId>,
7663}
7664
7665#[derive(Default)]
7666struct TimerDriverRegistryState {
7667 entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
7668 oldest: Option<TimerDriverId>,
7669 newest: Option<TimerDriverId>,
7670}
7671
7672impl TimerDriverRegistryState {
7673 fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
7674 let previous = self.newest;
7675 let replaced = self.entries.insert(
7676 id,
7677 TimerDriverEntry {
7678 driver,
7679 previous,
7680 next: None,
7681 },
7682 );
7683 assert!(replaced.is_none(), "timer driver id must be unique");
7684 if let Some(previous) = previous {
7685 self
7686 .entries
7687 .get_mut(&previous)
7688 .expect("the newest timer driver must remain registered")
7689 .next = Some(id);
7690 } else {
7691 self.oldest = Some(id);
7692 }
7693 self.newest = Some(id);
7694 }
7695
7696 fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
7697 let entry = self.entries.remove(&id)?;
7698 if let Some(previous) = entry.previous {
7699 self
7700 .entries
7701 .get_mut(&previous)
7702 .expect("the previous timer driver must remain registered")
7703 .next = entry.next;
7704 } else {
7705 self.oldest = entry.next;
7706 }
7707 if let Some(next) = entry.next {
7708 self
7709 .entries
7710 .get_mut(&next)
7711 .expect("the next timer driver must remain registered")
7712 .previous = entry.previous;
7713 } else {
7714 self.newest = entry.previous;
7715 }
7716 Some(entry)
7717 }
7718
7719 fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
7720 let mut snapshot = Vec::with_capacity(self.entries.len());
7721 let mut current = self.oldest;
7722 while let Some(id) = current {
7723 let entry = self
7724 .entries
7725 .get(&id)
7726 .expect("timer driver order must reference a live entry");
7727 snapshot.push((id, Arc::clone(&entry.driver)));
7728 current = entry.next;
7729 }
7730 debug_assert_eq!(
7731 snapshot.len(),
7732 self.entries.len(),
7733 "timer driver order must contain every entry exactly once"
7734 );
7735 snapshot
7736 }
7737
7738 fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
7739 if self.entries.len() != expected.len() {
7740 return false;
7741 }
7742 let mut current = self.oldest;
7743 for expected in expected {
7744 if current != Some(*expected) {
7745 return false;
7746 }
7747 current = self
7748 .entries
7749 .get(expected)
7750 .expect("timer driver order must reference a live entry")
7751 .next;
7752 }
7753 current.is_none()
7754 }
7755}
7756
7757#[derive(Default)]
7775pub struct TimerDriverRegistry {
7776 state: Mutex<TimerDriverRegistryState>,
7780 pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
7785 next_id: AtomicU64,
7786}
7787
7788impl Drop for TimerDriverRegistry {
7789 fn drop(&mut self) {
7790 let state = self
7791 .state
7792 .get_mut()
7793 .unwrap_or_else(std::sync::PoisonError::into_inner);
7794 state.oldest = None;
7795 state.newest = None;
7796 let entries = std::mem::take(&mut state.entries);
7797 for (_, entry) in entries {
7798 let _ = catch_unwind_contained(|| drop(entry));
7799 }
7800 }
7801}
7802
7803impl TimerDriverRegistry {
7804 pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
7806 let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
7807 let id = TimerDriverId(id);
7808 self
7809 .state
7810 .lock()
7811 .unwrap_or_else(std::sync::PoisonError::into_inner)
7812 .insert_driver(id, driver);
7813 let pending = {
7818 let mut entries = self
7819 .pending
7820 .lock()
7821 .unwrap_or_else(std::sync::PoisonError::into_inner);
7822 let mut pending = Vec::with_capacity(entries.len());
7823 entries.retain(|_, repoll| {
7824 if let Some(repoll) = repoll.upgrade() {
7825 pending.push(repoll);
7826 true
7827 } else {
7828 false
7829 }
7830 });
7831 pending
7832 };
7833 for repoll in pending {
7834 repoll.wake();
7835 }
7836 id
7837 }
7838
7839 pub fn unregister(&self, id: TimerDriverId) {
7841 let removed = self
7842 .state
7843 .lock()
7844 .unwrap_or_else(std::sync::PoisonError::into_inner)
7845 .remove_driver(id);
7846 let _ = catch_unwind_contained(|| drop(removed));
7849 }
7850
7851 fn live(&self) -> HostTimerDriverSnapshot {
7862 loop {
7863 let snapshot = HostTimerDriverSnapshot::new({
7864 let state = self
7865 .state
7866 .lock()
7867 .unwrap_or_else(std::sync::PoisonError::into_inner);
7868 state.ordered_snapshot()
7869 });
7870 let mut dead_ids = FxHashSet::default();
7871 let mut live_ids = Vec::new();
7872 for (id, driver) in snapshot.as_slice() {
7873 if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
7874 live_ids.push(*id);
7875 } else {
7876 dead_ids.insert(*id);
7877 }
7878 }
7879
7880 let (live, swept, retry) = {
7881 let mut state = self
7882 .state
7883 .lock()
7884 .unwrap_or_else(std::sync::PoisonError::into_inner);
7885 let mut swept = Vec::new();
7886 let mut current = state.oldest;
7887 while let Some(id) = current {
7888 let next = state
7889 .entries
7890 .get(&id)
7891 .expect("timer driver order must reference a live entry")
7892 .next;
7893 if dead_ids.contains(&id)
7894 && let Some(entry) = state.remove_driver(id)
7895 {
7896 swept.push(entry);
7897 }
7898 current = next;
7899 }
7900 let retry = !state.ordered_ids_match(&live_ids);
7901 let live = if retry {
7902 Vec::new()
7903 } else {
7904 state.ordered_snapshot()
7905 };
7906 (HostTimerDriverSnapshot::new(live), swept, retry)
7907 };
7908
7909 drop(snapshot);
7912 for entry in swept {
7913 let _ = catch_unwind_contained(|| entry.driver.on_swept());
7914 let _ = catch_unwind_contained(|| drop(entry));
7915 }
7916 if !retry {
7917 return live;
7918 }
7919 }
7920 }
7921
7922 #[cfg(test)]
7923 fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
7924 let mut live = self.live();
7925 live.pop().map(|(id, driver)| (id, driver.into_inner()))
7926 }
7927
7928 pub fn has_live_driver(&self) -> bool {
7930 !self.live().is_empty()
7931 }
7932
7933 fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
7934 let replaced = self
7935 .pending
7936 .lock()
7937 .unwrap_or_else(std::sync::PoisonError::into_inner)
7938 .insert(id, Arc::downgrade(repoll));
7939 assert!(
7940 replaced.is_none(),
7941 "host timer {id} registered its repoll signal twice"
7942 );
7943 if repoll.is_closed() {
7946 self.remove_pending(id);
7947 }
7948 }
7949
7950 fn remove_pending(&self, id: TimerId) {
7951 let removed = self
7952 .pending
7953 .lock()
7954 .unwrap_or_else(std::sync::PoisonError::into_inner)
7955 .remove(&id);
7956 let _ = catch_unwind_contained(|| drop(removed));
7957 }
7958
7959 #[cfg(test)]
7960 fn pending_len(&self) -> usize {
7961 self
7962 .pending
7963 .lock()
7964 .unwrap_or_else(std::sync::PoisonError::into_inner)
7965 .len()
7966 }
7967}
7968
7969static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
7972 LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
7973
7974pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
7980 TIMER_DRIVERS.register(driver)
7981}
7982
7983pub fn unregister_timer_driver(id: TimerDriverId) {
7987 TIMER_DRIVERS.unregister(id);
7988}
7989
7990pub fn has_live_timer_driver() -> bool {
7997 TIMER_DRIVERS.has_live_driver()
7998}
7999
8000#[derive(Default)]
8004struct HostTimerRepoll {
8005 state: Mutex<HostTimerRepollState>,
8006}
8007
8008#[derive(Default)]
8009struct HostTimerRepollState {
8010 closed: bool,
8011 notified: bool,
8012 waker: Option<Waker>,
8013}
8014
8015impl HostTimerRepoll {
8016 fn register(&self, waker: Waker) {
8017 let (replaced, wake) = {
8018 let mut state = self
8019 .state
8020 .lock()
8021 .unwrap_or_else(std::sync::PoisonError::into_inner);
8022 if state.closed {
8023 (Some(waker), None)
8024 } else if state.notified {
8025 state.notified = false;
8026 (state.waker.take(), Some(waker))
8027 } else {
8028 (state.waker.replace(waker), None)
8029 }
8030 };
8031 let _ = catch_unwind_contained(|| drop(replaced));
8032 if let Some(waker) = wake {
8033 wake_waker_contained(waker);
8034 }
8035 }
8036
8037 fn wake(&self) {
8038 let waker = {
8039 let mut state = self
8040 .state
8041 .lock()
8042 .unwrap_or_else(std::sync::PoisonError::into_inner);
8043 if state.closed {
8044 return;
8045 }
8046 if let Some(waker) = state.waker.take() {
8047 Some(waker)
8048 } else {
8049 state.notified = true;
8050 None
8051 }
8052 };
8053 if let Some(waker) = waker {
8054 wake_waker_contained(waker);
8055 }
8056 }
8057
8058 fn close(&self) {
8059 let waker = {
8060 let mut state = self
8061 .state
8062 .lock()
8063 .unwrap_or_else(std::sync::PoisonError::into_inner);
8064 state.closed = true;
8065 state.notified = false;
8066 state.waker.take()
8067 };
8068 let _ = catch_unwind_contained(|| drop(waker));
8069 }
8070
8071 fn is_closed(&self) -> bool {
8072 self
8073 .state
8074 .lock()
8075 .unwrap_or_else(std::sync::PoisonError::into_inner)
8076 .closed
8077 }
8078}
8079
8080#[derive(Default)]
8087struct HostTimerRegistry {
8088 state: Mutex<HostTimerRegistryState>,
8089}
8090
8091#[derive(Default)]
8092struct HostTimerRegistryState {
8093 closed: bool,
8094 timers: FxHashMap<TimerId, HostTimerEntry>,
8095}
8096
8097struct HostTimerEntry {
8098 deadline: Instant,
8099 waker: Waker,
8100 fired: Arc<AtomicBool>,
8101 drivers: Weak<TimerDriverRegistry>,
8102 repoll: Arc<HostTimerRepoll>,
8103}
8104
8105impl HostTimerRegistry {
8106 fn register(
8107 &self,
8108 id: TimerId,
8109 deadline: Instant,
8110 waker: Waker,
8111 fired: &Arc<AtomicBool>,
8112 drivers: &Arc<TimerDriverRegistry>,
8113 repoll: &Arc<HostTimerRepoll>,
8114 ) -> bool {
8115 let replaced = {
8116 let mut state = self
8117 .state
8118 .lock()
8119 .unwrap_or_else(std::sync::PoisonError::into_inner);
8120 if state.closed || fired.load(Ordering::SeqCst) {
8121 fired.store(true, Ordering::SeqCst);
8122 Err(waker)
8123 } else {
8124 Ok(state.timers.insert(
8125 id,
8126 HostTimerEntry {
8127 deadline,
8128 waker,
8129 fired: Arc::clone(fired),
8130 drivers: Arc::downgrade(drivers),
8131 repoll: Arc::clone(repoll),
8132 },
8133 ))
8134 }
8135 };
8136 match replaced {
8137 Ok(Some(entry)) => {
8138 let _ = catch_unwind_contained(|| drop(entry));
8139 true
8140 }
8141 Ok(None) => true,
8142 Err(waker) => {
8143 let _ = catch_unwind_contained(|| drop(waker));
8144 false
8145 }
8146 }
8147 }
8148
8149 fn remove(&self, id: TimerId) {
8150 let removed = self
8151 .state
8152 .lock()
8153 .unwrap_or_else(std::sync::PoisonError::into_inner)
8154 .timers
8155 .remove(&id);
8156 if let Some(entry) = removed {
8157 let _ = catch_unwind_contained(|| drop(entry));
8158 }
8159 }
8160
8161 fn shutdown(&self) {
8162 let timers = {
8163 let mut state = self
8164 .state
8165 .lock()
8166 .unwrap_or_else(std::sync::PoisonError::into_inner);
8167 state.closed = true;
8168 state
8169 .timers
8170 .drain()
8171 .map(|(id, entry)| {
8172 entry.fired.store(true, Ordering::SeqCst);
8173 (id, entry)
8174 })
8175 .collect::<Vec<_>>()
8176 };
8177 for (id, entry) in timers {
8178 entry.repoll.close();
8179 if let Some(drivers) = entry.drivers.upgrade() {
8180 drivers.remove_pending(id);
8181 }
8182 wake_waker_contained(entry.waker);
8183 }
8184 }
8185
8186 fn has_pending(&self) -> bool {
8191 !self
8192 .state
8193 .lock()
8194 .unwrap_or_else(std::sync::PoisonError::into_inner)
8195 .timers
8196 .is_empty()
8197 }
8198
8199 fn has_live_wait(&self, grace: Duration) -> bool {
8208 let now = Instant::now();
8209 self
8210 .state
8211 .lock()
8212 .unwrap_or_else(std::sync::PoisonError::into_inner)
8213 .timers
8214 .values()
8215 .any(|entry| {
8216 entry
8217 .deadline
8218 .checked_add(grace)
8219 .is_none_or(|deadline| deadline > now)
8220 })
8221 }
8222}
8223
8224struct HostTimerPendingGuard {
8228 registry: Arc<HostTimerRegistry>,
8229 drivers: Arc<TimerDriverRegistry>,
8230 repoll: Arc<HostTimerRepoll>,
8231 id: TimerId,
8232}
8233
8234impl Drop for HostTimerPendingGuard {
8235 fn drop(&mut self) {
8236 self.repoll.close();
8237 self.registry.remove(self.id);
8238 self.drivers.remove_pending(self.id);
8239 }
8240}
8241
8242#[cfg(not(target_family = "wasm"))]
8248struct TimerHeap {
8249 inner: Arc<Mutex<TimerHeapInner>>,
8250 parker: Arc<DriverParker>,
8251 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
8252 deadlock_state: Arc<MultiThreadDeadlockState>,
8253 #[cfg(test)]
8254 fail_next_start: AtomicBool,
8255 generation: u64,
8256 executor_id: u64,
8257 thread_name: String,
8258}
8259
8260#[cfg(not(target_family = "wasm"))]
8261#[derive(Default)]
8262struct TimerHeapInner {
8263 queue: TimerDeadlineQueue,
8267 entries: FxHashMap<TimerId, HeapTimerEntry>,
8268 timekeeper_parker: Option<Arc<DriverParker>>,
8272 closed: bool,
8276}
8277
8278#[cfg(not(target_family = "wasm"))]
8279struct HeapTimerEntry {
8280 waker: Waker,
8281 fired: Arc<AtomicBool>,
8286}
8287
8288#[cfg(not(target_family = "wasm"))]
8289#[derive(Default)]
8290struct TimerDeadlineQueue {
8291 nodes: Vec<(Instant, TimerId)>,
8292 positions: FxHashMap<TimerId, usize>,
8293}
8294
8295#[cfg(not(target_family = "wasm"))]
8296impl TimerDeadlineQueue {
8297 #[cfg(test)]
8298 fn len(&self) -> usize {
8299 self.nodes.len()
8300 }
8301
8302 #[cfg(test)]
8303 fn is_empty(&self) -> bool {
8304 self.nodes.is_empty()
8305 }
8306
8307 fn clear(&mut self) {
8308 self.nodes.clear();
8309 self.positions.clear();
8310 }
8311
8312 fn peek(&self) -> Option<(Instant, TimerId)> {
8313 self.nodes.first().copied()
8314 }
8315
8316 fn push(&mut self, deadline: Instant, id: TimerId) {
8317 let index = self.nodes.len();
8318 assert!(
8319 self.positions.insert(id, index).is_none(),
8320 "timer deadline queue received duplicate id {id}"
8321 );
8322 self.nodes.push((deadline, id));
8323 self.sift_up(index);
8324 }
8325
8326 fn pop(&mut self) -> Option<(Instant, TimerId)> {
8327 let first = self.nodes.first().copied()?;
8328 self.positions.remove(&first.1);
8329 let last = self
8330 .nodes
8331 .pop()
8332 .expect("the non-empty timer heap must have a final node");
8333 if !self.nodes.is_empty() {
8334 self.nodes[0] = last;
8335 self.positions.insert(last.1, 0);
8336 self.sift_down(0);
8337 }
8338 Some(first)
8339 }
8340
8341 fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
8342 let index = self.positions.remove(&id)?;
8343 let removed = self.nodes[index];
8344 let last = self.nodes.pop().expect("an indexed timer node must exist");
8345 if index < self.nodes.len() {
8346 self.nodes[index] = last;
8347 self.positions.insert(last.1, index);
8348 if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
8349 self.sift_up(index);
8350 } else {
8351 self.sift_down(index);
8352 }
8353 }
8354 Some(removed)
8355 }
8356
8357 fn sift_up(&mut self, mut index: usize) {
8358 while index > 0 {
8359 let parent = (index - 1) / 2;
8360 if self.nodes[parent] <= self.nodes[index] {
8361 break;
8362 }
8363 self.swap(parent, index);
8364 index = parent;
8365 }
8366 }
8367
8368 fn sift_down(&mut self, mut index: usize) {
8369 loop {
8370 let left = index * 2 + 1;
8371 if left >= self.nodes.len() {
8372 return;
8373 }
8374 let right = left + 1;
8375 let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
8376 right
8377 } else {
8378 left
8379 };
8380 if self.nodes[index] <= self.nodes[child] {
8381 return;
8382 }
8383 self.swap(index, child);
8384 index = child;
8385 }
8386 }
8387
8388 fn swap(&mut self, left: usize, right: usize) {
8389 self.nodes.swap(left, right);
8390 self.positions.insert(self.nodes[left].1, left);
8391 self.positions.insert(self.nodes[right].1, right);
8392 }
8393}
8394
8395#[cfg(not(target_family = "wasm"))]
8396impl TimerHeapInner {
8397 fn next_deadline(&self) -> Option<Instant> {
8398 self.queue.peek().map(|(deadline, _)| deadline)
8399 }
8400}
8401
8402#[cfg(not(target_family = "wasm"))]
8403impl TimerHeap {
8404 fn new(
8405 generation: u64,
8406 executor_id: u64,
8407 thread_name: String,
8408 deadlock_state: Arc<MultiThreadDeadlockState>,
8409 ) -> Self {
8410 Self {
8411 inner: Arc::new(Mutex::new(TimerHeapInner::default())),
8412 parker: Arc::new(DriverParker::default()),
8413 thread: Mutex::new(None),
8414 deadlock_state,
8415 #[cfg(test)]
8416 fail_next_start: AtomicBool::new(false),
8417 generation,
8418 executor_id,
8419 thread_name,
8420 }
8421 }
8422
8423 fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
8424 if self
8425 .inner
8426 .lock()
8427 .unwrap_or_else(std::sync::PoisonError::into_inner)
8428 .closed
8429 {
8430 return Ok(());
8431 }
8432 let current_thread = std::thread::current().id();
8433 loop {
8434 let finished = {
8435 let mut thread = self
8436 .thread
8437 .lock()
8438 .unwrap_or_else(std::sync::PoisonError::into_inner);
8439 match thread.as_ref() {
8440 Some(handle) if handle.thread().id() == current_thread => return Ok(()),
8441 Some(handle) if !handle.is_finished() => return Ok(()),
8442 Some(_) => thread.take(),
8443 None => {
8444 if self
8447 .inner
8448 .lock()
8449 .unwrap_or_else(std::sync::PoisonError::into_inner)
8450 .closed
8451 {
8452 return Ok(());
8453 }
8454 #[cfg(test)]
8455 if self.fail_next_start.swap(false, Ordering::SeqCst) {
8456 return Err(RuntimeConfigError(
8457 "injected runtime timer thread creation failure".to_string(),
8458 ));
8459 }
8460
8461 let inner = Arc::clone(&self.inner);
8462 let parker = Arc::clone(&self.parker);
8463 let deadlock_state = Arc::clone(&self.deadlock_state);
8464 let generation = self.generation;
8465 let executor_id = self.executor_id;
8466 let handle = std::thread::Builder::new()
8467 .name(self.thread_name.clone())
8468 .spawn(move || {
8469 ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
8472 let _generation = RuntimeGenerationGuard::enter(generation);
8473 timer_timekeeper_main(&inner, &parker, &deadlock_state);
8474 })
8475 .map_err(|error| {
8476 RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
8477 })?;
8478 *thread = Some(handle);
8479 return Ok(());
8480 }
8481 }
8482 };
8483
8484 if let Some(finished) = finished
8488 && finished.thread().id() != current_thread
8489 && let Err(payload) = finished.join()
8490 {
8491 discard_panic_payload(payload);
8492 }
8493 if self
8494 .inner
8495 .lock()
8496 .unwrap_or_else(std::sync::PoisonError::into_inner)
8497 .closed
8498 {
8499 return Ok(());
8500 }
8501 }
8502 }
8503
8504 fn close(&self) -> Vec<Waker> {
8505 let mut inner = self
8506 .inner
8507 .lock()
8508 .unwrap_or_else(std::sync::PoisonError::into_inner);
8509 inner.closed = true;
8510 inner.queue.clear();
8511 inner.timekeeper_parker = None;
8512 inner
8513 .entries
8514 .drain()
8515 .map(|(_, entry)| {
8516 entry.fired.store(true, Ordering::SeqCst);
8517 entry.waker
8518 })
8519 .collect()
8520 }
8521
8522 fn join(&self) {
8523 let handle = self
8524 .thread
8525 .lock()
8526 .unwrap_or_else(std::sync::PoisonError::into_inner)
8527 .take();
8528 if let Some(handle) = handle {
8529 if handle.thread().id() == std::thread::current().id() {
8530 drop(handle);
8534 return;
8535 }
8536 if let Err(payload) = handle.join() {
8537 discard_panic_payload(payload);
8538 }
8539 }
8540 }
8541}
8542
8543#[cfg(not(target_family = "wasm"))]
8544impl Drop for TimerHeap {
8545 fn drop(&mut self) {
8546 let mut admission = self.deadlock_state.begin_admission();
8547 let wakers = self.close();
8548 if !wakers.is_empty()
8549 && let Some(admission) = admission.as_mut()
8550 {
8551 admission.mark_published();
8552 }
8553 self.parker.unpark();
8554 for waker in wakers {
8555 wake_waker_contained(waker);
8556 }
8557 drop(admission);
8558 self.join();
8559 }
8560}
8561
8562#[cfg(not(target_family = "wasm"))]
8563fn timer_timekeeper_main(
8564 inner: &Mutex<TimerHeapInner>,
8565 parker: &Arc<DriverParker>,
8566 deadlock_state: &Arc<MultiThreadDeadlockState>,
8567) {
8568 loop {
8569 let mut admission = deadlock_state.begin_admission();
8573 let (due, deadline) = {
8574 let mut inner = inner
8575 .lock()
8576 .unwrap_or_else(std::sync::PoisonError::into_inner);
8577 if inner.closed {
8578 inner.timekeeper_parker = None;
8579 return;
8580 }
8581 let now = Instant::now();
8582 let mut due = Vec::new();
8583 while let Some((deadline, id)) = inner.queue.peek() {
8584 if deadline > now {
8585 break;
8586 }
8587 inner.queue.pop();
8588 if let Some(entry) = inner.entries.remove(&id) {
8589 entry.fired.store(true, Ordering::SeqCst);
8590 due.push(entry.waker);
8591 }
8592 }
8593 let deadline = inner.next_deadline();
8594 inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
8595 (due, deadline)
8596 };
8597
8598 if !due.is_empty() {
8599 if let Some(admission) = admission.as_mut() {
8600 admission.mark_published();
8601 }
8602 for waker in due {
8603 wake_waker_contained(waker);
8604 }
8605 continue;
8606 }
8607 drop(admission);
8610
8611 match deadline {
8612 Some(deadline) => {
8613 let now = Instant::now();
8614 if deadline > now {
8615 parker.park_timeout(deadline - now);
8616 }
8617 }
8618 None => parker.park(),
8619 }
8620 }
8621}
8622
8623#[cfg(not(target_family = "wasm"))]
8624impl MultiThreadExecutor {
8625 fn register_timer(
8631 self: &Arc<Self>,
8632 id: TimerId,
8633 deadline: Instant,
8634 waker: Waker,
8635 fired: &Arc<AtomicBool>,
8636 ) {
8637 let mut admission = self.begin_deadlock_admission();
8638 #[cfg(test)]
8639 run_timer_registration_admission_test_hook();
8640 if let Err(error) = self.timers.ensure_started() {
8644 let _ = catch_unwind_contained(|| drop(waker));
8645 panic!("{error}");
8646 }
8647 let (became_earliest, published, discarded_waker) = {
8648 let mut inner = self
8649 .timers
8650 .inner
8651 .lock()
8652 .unwrap_or_else(std::sync::PoisonError::into_inner);
8653 if inner.closed || fired.load(Ordering::SeqCst) {
8654 fired.store(true, Ordering::SeqCst);
8655 (false, false, Some(waker))
8656 } else if let Some(entry) = inner.entries.get_mut(&id) {
8657 (
8659 false,
8660 false,
8661 Some(std::mem::replace(&mut entry.waker, waker)),
8662 )
8663 } else {
8664 let previous = inner.next_deadline();
8665 inner.entries.insert(
8666 id,
8667 HeapTimerEntry {
8668 waker,
8669 fired: Arc::clone(fired),
8670 },
8671 );
8672 inner.queue.push(deadline, id);
8673 (
8674 previous.is_none_or(|previous| deadline < previous),
8675 true,
8676 None,
8677 )
8678 }
8679 };
8680 if published && let Some(admission) = admission.as_mut() {
8681 admission.mark_published();
8682 }
8683 if let Some(waker) = discarded_waker {
8684 let _ = catch_unwind_contained(|| drop(waker));
8687 }
8688 if became_earliest {
8689 self.note_earlier_timer();
8691 }
8692 }
8693
8694 fn cancel_timer(&self, id: TimerId) {
8698 let (removed, earliest_changed) = {
8699 let mut inner = self
8700 .timers
8701 .inner
8702 .lock()
8703 .unwrap_or_else(std::sync::PoisonError::into_inner);
8704 let previous = inner.next_deadline();
8705 let removed = inner.entries.remove(&id);
8706 if removed.is_some() {
8707 let queued = inner.queue.remove(id);
8708 debug_assert!(
8709 queued.is_some(),
8710 "a live timer entry must have one indexed deadline node"
8711 );
8712 }
8713 let current = inner.next_deadline();
8714 (removed, previous != current)
8715 };
8716 if let Some(entry) = removed {
8717 let _ = catch_unwind_contained(|| drop(entry));
8720 }
8721 if earliest_changed {
8722 self.timers.parker.unpark();
8723 self.parked_drivers.wake_all();
8724 }
8725 }
8726
8727 fn next_timer_deadline(&self) -> Option<Instant> {
8729 self
8730 .timers
8731 .inner
8732 .lock()
8733 .unwrap_or_else(std::sync::PoisonError::into_inner)
8734 .next_deadline()
8735 }
8736
8737 fn fire_due_timers(&self) {
8742 let mut admission = self.begin_deadlock_admission();
8743 let due: Vec<Waker> = {
8744 let mut inner = self
8745 .timers
8746 .inner
8747 .lock()
8748 .unwrap_or_else(std::sync::PoisonError::into_inner);
8749 let now = Instant::now();
8750 let mut due = Vec::new();
8751 while let Some((deadline, id)) = inner.queue.peek() {
8752 if deadline > now {
8753 break;
8754 }
8755 inner.queue.pop();
8756 if let Some(entry) = inner.entries.remove(&id) {
8757 entry.fired.store(true, Ordering::SeqCst);
8758 due.push(entry.waker);
8759 }
8760 }
8761 due
8762 };
8763 if !due.is_empty()
8764 && let Some(admission) = admission.as_mut()
8765 {
8766 admission.mark_published();
8767 }
8768 for waker in due {
8769 wake_waker_contained(waker);
8772 }
8773 }
8774
8775 fn note_earlier_timer(self: &Arc<Self>) {
8779 self.timers.parker.unpark();
8780 self.parked_drivers.wake_one();
8781 }
8782
8783 fn shutdown_timers(&self) {
8789 let mut admission = self.begin_deadlock_admission();
8790 let wakers = self.timers.close();
8791 if !wakers.is_empty()
8792 && let Some(admission) = admission.as_mut()
8793 {
8794 admission.mark_published();
8795 }
8796 for waker in wakers {
8797 wake_waker_contained(waker);
8801 }
8802 self.timers.parker.unpark();
8803 }
8804
8805 fn begin_shutdown(&self) {
8806 let _generation = RuntimeGenerationGuard::enter(self.generation);
8807 self.stop.begin_shutdown();
8812 let queued = {
8813 let mut queue = self
8814 .blocking_queue
8815 .lock()
8816 .unwrap_or_else(std::sync::PoisonError::into_inner);
8817 queue.closed = true;
8820 queue.take_all()
8821 };
8822 for queued in queued {
8827 let _ = catch_unwind_contained(|| drop(queued));
8828 }
8829 self.shutdown_timers();
8830 }
8831
8832 fn wait_until_scheduler_idle(&self) {
8833 self.timers.join();
8834 let mut idle = self
8835 .scheduler_idle_lock
8836 .lock()
8837 .unwrap_or_else(std::sync::PoisonError::into_inner);
8838 while self.active_drainers.load(Ordering::Acquire) != 0 {
8839 idle = self
8840 .scheduler_idle
8841 .wait(idle)
8842 .unwrap_or_else(std::sync::PoisonError::into_inner);
8843 }
8844 }
8845}
8846
8847pub struct Sleep {
8852 deadline: Instant,
8853 inner: SleepInner,
8854}
8855
8856enum SleepInner {
8857 #[cfg(not(target_family = "wasm"))]
8858 Heap(HeapSleep),
8859 Host(HostSleep),
8860}
8861
8862#[cfg(not(target_family = "wasm"))]
8863struct HeapSleep {
8864 id: TimerId,
8865 executor: Weak<MultiThreadExecutor>,
8866 fired: Arc<AtomicBool>,
8867 registered: bool,
8868}
8869
8870struct HostSleep {
8871 id: TimerId,
8872 drivers: Arc<TimerDriverRegistry>,
8876 armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
8880 registry: Arc<HostTimerRegistry>,
8883 fired: Arc<AtomicBool>,
8884 repoll: Arc<HostTimerRepoll>,
8888 pending: Option<HostTimerPendingGuard>,
8893}
8894
8895fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
8896 let _ = catch_unwind_contained(|| driver.cancel(id));
8897 let _ = catch_unwind_contained(|| drop(driver));
8898}
8899
8900fn cancel_host_timer_drivers(
8901 id: TimerId,
8902 armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
8903) {
8904 for (_, driver) in armed.drain() {
8905 cancel_host_timer_driver(id, driver);
8906 }
8907}
8908
8909impl Future for Sleep {
8910 type Output = ();
8911
8912 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
8913 let deadline = self.deadline;
8914 let this = self.get_mut();
8915 match &mut this.inner {
8916 #[cfg(not(target_family = "wasm"))]
8917 SleepInner::Heap(heap) => {
8918 if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
8919 if heap.registered {
8920 if let Some(executor) = heap.executor.upgrade() {
8923 executor.cancel_timer(heap.id);
8924 }
8925 heap.registered = false;
8926 }
8927 return Poll::Ready(());
8928 }
8929 let Some(executor) = heap.executor.upgrade() else {
8930 heap.fired.store(true, Ordering::SeqCst);
8931 heap.registered = false;
8932 return Poll::Ready(());
8933 };
8934 executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
8935 heap.registered = true;
8936 if heap.fired.load(Ordering::SeqCst) {
8940 heap.registered = false;
8941 return Poll::Ready(());
8942 }
8943 Poll::Pending
8944 }
8945 SleepInner::Host(host) => {
8946 if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
8947 cancel_host_timer_drivers(host.id, &mut host.armed);
8948 host.pending.take();
8949 return Poll::Ready(());
8950 }
8951 let repoll_waker = cx.waker().clone();
8956 let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
8957 Ok(waker) => waker,
8958 Err(payload) => {
8959 let _ = catch_unwind_contained(|| drop(repoll_waker));
8960 resume_unwind(payload);
8961 }
8962 };
8963 host.repoll.register(repoll_waker);
8964 if !host.registry.register(
8965 host.id,
8966 deadline,
8967 registry_waker,
8968 &host.fired,
8969 &host.drivers,
8970 &host.repoll,
8971 ) {
8972 cancel_host_timer_drivers(host.id, &mut host.armed);
8973 if host.pending.take().is_none() {
8974 host.repoll.close();
8975 }
8976 return Poll::Ready(());
8977 }
8978 if host.pending.is_none() {
8979 host.drivers.register_pending(host.id, &host.repoll);
8987 host.pending = Some(HostTimerPendingGuard {
8988 registry: Arc::clone(&host.registry),
8989 drivers: Arc::clone(&host.drivers),
8990 repoll: Arc::clone(&host.repoll),
8991 id: host.id,
8992 });
8993 }
8994 let current = host.drivers.live();
8998 assert!(
8999 !current.as_slice().is_empty(),
9000 "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
9001 that registered timer drivers (via \
9002 `napi_async_runtime::register_timer_driver`; the napi adapter registers \
9003 one per importing env through `registerTimerHost`) have all been torn down, so this \
9004 `sleep_until` can never be woken."
9005 );
9006 let missing = current
9012 .as_slice()
9013 .iter()
9014 .filter(|(current_id, _)| !host.armed.contains_key(current_id))
9015 .count();
9016 host.armed.reserve(missing);
9017 for (current_id, current_driver) in current.as_slice() {
9018 host
9019 .armed
9020 .entry(*current_id)
9021 .or_insert_with(|| Arc::clone(current_driver));
9022 }
9023 for (current_id, _) in current.as_slice() {
9024 let armed_driver = host
9025 .armed
9026 .get(current_id)
9027 .expect("every live timer driver must have cancellation ownership before callbacks");
9028 armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
9029 }
9030 let current_ids = current
9031 .as_slice()
9032 .iter()
9033 .map(|(current_id, _)| *current_id)
9034 .collect::<FxHashSet<_>>();
9035 let retired = host
9036 .armed
9037 .keys()
9038 .filter(|armed_id| !current_ids.contains(armed_id))
9039 .copied()
9040 .collect::<Vec<_>>();
9041 for retired in retired {
9042 let old_driver = host
9043 .armed
9044 .remove(&retired)
9045 .expect("the retired timer driver must remain armed");
9046 cancel_host_timer_driver(host.id, old_driver);
9047 }
9048 Poll::Pending
9049 }
9050 }
9051 }
9052}
9053
9054impl Drop for Sleep {
9055 fn drop(&mut self) {
9056 match &mut self.inner {
9057 #[cfg(not(target_family = "wasm"))]
9058 SleepInner::Heap(heap) => {
9059 if heap.registered
9060 && let Some(executor) = heap.executor.upgrade()
9061 {
9062 executor.cancel_timer(heap.id);
9063 }
9064 }
9065 SleepInner::Host(host) => {
9066 host.repoll.close();
9069 cancel_host_timer_drivers(host.id, &mut host.armed);
9070 }
9072 }
9073 }
9074}
9075
9076pub fn sleep_until(deadline: Instant) -> Sleep {
9081 make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
9082}
9083
9084fn make_sleep(
9088 backend: &RuntimeBackend,
9089 host_drivers: &Arc<TimerDriverRegistry>,
9090 deadline: Instant,
9091) -> Sleep {
9092 let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
9093 match &backend.executor {
9094 #[cfg(not(target_family = "wasm"))]
9095 RuntimeExecutor::MultiThread(executor) => Sleep {
9096 deadline,
9097 inner: SleepInner::Heap(HeapSleep {
9098 id,
9099 executor: Arc::downgrade(executor),
9100 fired: Arc::new(AtomicBool::new(false)),
9101 registered: false,
9102 }),
9103 },
9104 RuntimeExecutor::CurrentThread(executor) => {
9105 assert!(
9113 host_drivers.has_live_driver(),
9114 "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
9115 single-thread flavor delegates timers to the host event loop. Install one via \
9116 `napi_async_runtime::register_timer_driver` (the napi adapter registers \
9117 a setTimeout-based driver per importing env through `registerTimerHost` at import)."
9118 );
9119 Sleep {
9126 deadline,
9127 inner: SleepInner::Host(HostSleep {
9128 id,
9129 drivers: Arc::clone(host_drivers),
9130 armed: FxHashMap::default(),
9131 registry: Arc::clone(&executor.host_timers),
9132 fired: Arc::new(AtomicBool::new(false)),
9133 repoll: Arc::new(HostTimerRepoll::default()),
9134 pending: None,
9135 }),
9136 }
9137 }
9138 }
9139}
9140
9141#[derive(Clone)]
9142enum RuntimeExecutor {
9143 CurrentThread(Arc<CurrentThreadExecutor>),
9144 #[cfg(not(target_family = "wasm"))]
9145 MultiThread(Arc<MultiThreadExecutor>),
9146}
9147
9148enum WeakRuntimeExecutor {
9149 CurrentThread(Weak<CurrentThreadExecutor>),
9150 #[cfg(not(target_family = "wasm"))]
9151 MultiThread(Weak<MultiThreadExecutor>),
9152}
9153
9154impl WeakRuntimeExecutor {
9155 fn schedule(&self, runnable: Runnable) {
9156 match self {
9157 Self::CurrentThread(executor) => {
9158 if let Some(executor) = executor.upgrade() {
9159 executor.schedule(runnable);
9160 }
9161 }
9162 #[cfg(not(target_family = "wasm"))]
9163 Self::MultiThread(executor) => {
9164 if let Some(executor) = executor.upgrade() {
9165 executor.schedule(runnable);
9166 }
9167 }
9168 }
9169 }
9170}
9171
9172#[derive(Clone)]
9173struct RuntimeBackend {
9174 work: Arc<GenerationWork>,
9175 executor: RuntimeExecutor,
9176}
9177
9178impl RuntimeBackend {
9179 fn new(
9180 options: &RuntimeOptions,
9181 metrics: Arc<RuntimeMetrics>,
9182 ) -> Result<Self, RuntimeConfigError> {
9183 #[cfg(test)]
9184 if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
9185 return Err(RuntimeConfigError(
9186 "injected async runtime backend creation failure".to_string(),
9187 ));
9188 }
9189
9190 let work = GenerationWork::new();
9191 let stop = Arc::new(GenerationStop::default());
9192 let executor = match options.flavor {
9193 RuntimeFlavor::CurrentThread => {
9194 RuntimeExecutor::CurrentThread(Arc::new(CurrentThreadExecutor::with_generation(
9195 metrics,
9196 THREADLESS_BUILD,
9197 options.park_deadline,
9198 work.id,
9199 Arc::clone(&stop),
9200 )))
9201 }
9202 RuntimeFlavor::MultiThread => {
9203 #[cfg(not(target_family = "wasm"))]
9204 {
9205 RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
9206 options, metrics, work.id, stop,
9207 )?))
9208 }
9209 #[cfg(target_family = "wasm")]
9210 {
9211 let _ = metrics;
9212 return Err(RuntimeConfigError(
9213 "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
9214 ));
9215 }
9216 }
9217 };
9218 Ok(Self { work, executor })
9219 }
9220
9221 #[cfg(test)]
9222 fn from_executor(executor: RuntimeExecutor) -> Self {
9223 Self {
9224 work: GenerationWork::new(),
9225 executor,
9226 }
9227 }
9228
9229 fn generation(&self) -> u64 {
9230 self.work.id
9231 }
9232
9233 fn downgrade_executor(&self) -> WeakRuntimeExecutor {
9234 match &self.executor {
9235 RuntimeExecutor::CurrentThread(executor) => {
9236 WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
9237 }
9238 #[cfg(not(target_family = "wasm"))]
9239 RuntimeExecutor::MultiThread(executor) => {
9240 WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
9241 }
9242 }
9243 }
9244
9245 fn schedule(&self, runnable: Runnable) {
9246 match &self.executor {
9247 RuntimeExecutor::CurrentThread(executor) => executor.schedule(runnable),
9248 #[cfg(not(target_family = "wasm"))]
9249 RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
9250 }
9251 }
9252
9253 fn spawn_registered_blocking<F, T>(
9254 &self,
9255 function: F,
9256 registration: GenerationWorkGuard,
9257 metrics: &Arc<RuntimeMetrics>,
9258 ) -> JoinHandle<T>
9259 where
9260 F: FnOnce() -> T + Send + 'static,
9261 T: Send + 'static,
9262 {
9263 match &self.executor {
9264 RuntimeExecutor::CurrentThread(executor) => {
9265 let _ = metrics;
9266 executor.schedule_blocking(function, registration)
9267 }
9268 #[cfg(not(target_family = "wasm"))]
9269 RuntimeExecutor::MultiThread(executor) => {
9270 let generation = self.generation();
9271 let _ = metrics;
9272 let function = RegisteredBlockingFunction::new(function, registration, generation);
9273 executor.schedule_blocking_result(move || function.run())
9274 }
9275 }
9276 }
9277
9278 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
9279 match &self.executor {
9280 RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
9281 #[cfg(not(target_family = "wasm"))]
9282 RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
9283 }
9284 }
9285
9286 fn generation_stop(&self) -> Arc<GenerationStop> {
9287 match &self.executor {
9288 RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
9289 #[cfg(not(target_family = "wasm"))]
9290 RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
9291 }
9292 }
9293
9294 fn begin_shutdown(&self) {
9295 let _generation = RuntimeGenerationGuard::enter(self.generation());
9296 self.work.close_and_abort();
9297 match &self.executor {
9298 RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
9299 #[cfg(not(target_family = "wasm"))]
9300 RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
9301 }
9302 }
9303
9304 fn wait_until_idle(&self) {
9305 self.work.wait_until_idle();
9306 match &self.executor {
9307 RuntimeExecutor::CurrentThread(executor) => executor.wait_until_scheduler_idle(),
9308 #[cfg(not(target_family = "wasm"))]
9309 RuntimeExecutor::MultiThread(executor) => executor.wait_until_scheduler_idle(),
9310 }
9311 }
9312
9313 #[cfg(not(target_family = "wasm"))]
9314 fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
9315 match &self.executor {
9316 RuntimeExecutor::CurrentThread(_) => None,
9317 RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
9318 }
9319 }
9320
9321 fn stop_identity(&self) -> RuntimeStopIdentity {
9322 RuntimeStopIdentity {
9323 generation: self.generation(),
9324 #[cfg(not(target_family = "wasm"))]
9325 executor_id: match &self.executor {
9326 RuntimeExecutor::CurrentThread(_) => None,
9327 RuntimeExecutor::MultiThread(executor) => Some(executor.id),
9328 },
9329 }
9330 }
9331
9332 fn is_current(&self) -> bool {
9333 self.stop_identity().is_current()
9334 }
9335}
9336
9337#[derive(Clone, Copy)]
9338struct RuntimeStopIdentity {
9339 generation: u64,
9340 #[cfg(not(target_family = "wasm"))]
9341 executor_id: Option<u64>,
9342}
9343
9344impl RuntimeStopIdentity {
9345 fn is_current(self) -> bool {
9346 if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
9347 return true;
9348 }
9349 #[cfg(not(target_family = "wasm"))]
9350 if let Some(executor_id) = self.executor_id
9351 && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
9352 || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
9353 {
9354 return true;
9355 }
9356 false
9357 }
9358}
9359
9360enum RuntimeLifecycle {
9361 Initial,
9362 StoppedBeforeFirstUse,
9363 Running(RuntimeBackend),
9364 Stopping(RuntimeStopIdentity),
9365 StoppingWithoutBackend(ZeroBackendShutdownTarget),
9366 Stopped,
9367}
9368
9369#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9370enum ZeroBackendShutdownTarget {
9371 StoppedBeforeFirstUse,
9372 Stopped,
9373}
9374
9375impl ZeroBackendShutdownTarget {
9376 fn into_lifecycle(self) -> RuntimeLifecycle {
9377 match self {
9378 Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
9379 Self::Stopped => RuntimeLifecycle::Stopped,
9380 }
9381 }
9382}
9383
9384struct RuntimeState {
9385 options: RuntimeOptions,
9386 lifecycle: RuntimeLifecycle,
9387 rejected_drops: usize,
9388}
9389
9390#[cfg(test)]
9391thread_local! {
9392 static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
9393 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9394 static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
9395 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9396 static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
9397 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9398 static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
9399 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9400 static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
9401 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9402 static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
9403 std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9404 static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
9405 std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
9406}
9407
9408#[cfg(test)]
9409fn run_before_runtime_submission_lock_test_hook() {
9410 if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9411 {
9412 hook();
9413 }
9414}
9415
9416#[cfg(test)]
9417fn run_after_async_work_registration_test_hook() {
9418 if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9419 {
9420 hook();
9421 }
9422}
9423
9424#[cfg(test)]
9425fn run_after_blocking_work_registration_test_hook() {
9426 if let Some(hook) =
9427 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9428 {
9429 hook();
9430 }
9431}
9432
9433#[cfg(test)]
9434fn run_after_current_thread_host_admission_test_hook() {
9435 if let Some(hook) =
9436 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9437 {
9438 hook();
9439 }
9440}
9441
9442#[cfg(test)]
9443fn run_before_initial_start_wait_test_hook() {
9444 if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
9445 hook();
9446 }
9447}
9448
9449#[cfg(test)]
9450fn run_after_generation_stop_publication_test_hook() {
9451 if let Some(hook) =
9452 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9453 {
9454 hook();
9455 }
9456}
9457
9458thread_local! {
9459 static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
9460 const { std::cell::Cell::new(0) };
9461}
9462
9463struct RejectedSubmissionDropContext;
9464
9465impl RejectedSubmissionDropContext {
9466 fn enter() -> Self {
9467 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
9468 Self
9469 }
9470
9471 fn is_current() -> bool {
9472 IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
9473 }
9474}
9475
9476impl Drop for RejectedSubmissionDropContext {
9477 fn drop(&mut self) {
9478 IN_REJECTED_SUBMISSION_DROP.with(|depth| {
9479 depth.set(
9480 depth
9481 .get()
9482 .checked_sub(1)
9483 .expect("rejected submission drop depth underflow"),
9484 );
9485 });
9486 }
9487}
9488
9489struct RuntimeController {
9490 state: Mutex<RuntimeState>,
9491 lifecycle_changed: Condvar,
9492 metrics: Arc<RuntimeMetrics>,
9493}
9494
9495struct RejectedSubmissionGuard<'a> {
9496 controller: &'a RuntimeController,
9497}
9498
9499impl Drop for RejectedSubmissionGuard<'_> {
9500 fn drop(&mut self) {
9501 let mut state = self
9502 .controller
9503 .state
9504 .lock()
9505 .unwrap_or_else(std::sync::PoisonError::into_inner);
9506 state.rejected_drops = state
9507 .rejected_drops
9508 .checked_sub(1)
9509 .expect("rejected submission drop count underflow");
9510 self.controller.lifecycle_changed.notify_all();
9511 }
9512}
9513
9514impl RuntimeController {
9515 fn new() -> Self {
9516 let options = RuntimeOptions::default()
9517 .validate()
9518 .expect("default async runtime options must be valid");
9519 Self {
9520 state: Mutex::new(RuntimeState {
9521 options,
9522 lifecycle: RuntimeLifecycle::Initial,
9523 rejected_drops: 0,
9524 }),
9525 lifecycle_changed: Condvar::new(),
9526 metrics: Arc::new(RuntimeMetrics::default()),
9527 }
9528 }
9529
9530 fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
9531 let options = options.validate()?;
9532 let mut state = self
9533 .state
9534 .lock()
9535 .unwrap_or_else(std::sync::PoisonError::into_inner);
9536 Self::ensure_configuration_mutable(&state)?;
9537 state.options = options;
9538 Ok(())
9539 }
9540
9541 fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
9542 self.configure_partial_inner(patch, |_| {})
9543 }
9544
9545 fn configure_partial_inner(
9546 &self,
9547 patch: RuntimeOptionsPatch,
9548 after_merge: impl FnOnce(&RuntimeOptions),
9549 ) -> Result<(), RuntimeConfigError> {
9550 let mut state = self
9551 .state
9552 .lock()
9553 .unwrap_or_else(std::sync::PoisonError::into_inner);
9554 Self::ensure_configuration_mutable(&state)?;
9555
9556 let mut options = state.options.clone();
9557 patch.apply_to(&mut options);
9558 after_merge(&options);
9559 let options = options.validate()?;
9560 state.options = options;
9561 Ok(())
9562 }
9563
9564 fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
9565 if matches!(
9566 &state.lifecycle,
9567 RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
9568 ) {
9569 return Ok(());
9570 }
9571 Err(RuntimeConfigError(
9572 "the async runtime configuration is frozen; configure it before the first async call"
9573 .to_string(),
9574 ))
9575 }
9576
9577 fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
9578 let Some(active_generation) = active_runtime_generation() else {
9579 return Ok(());
9580 };
9581 let lifecycle_generation = match &state.lifecycle {
9582 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
9583 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
9584 RuntimeLifecycle::Initial
9585 | RuntimeLifecycle::StoppedBeforeFirstUse
9586 | RuntimeLifecycle::StoppingWithoutBackend(_)
9587 | RuntimeLifecycle::Stopped => None,
9588 };
9589 if lifecycle_generation == Some(active_generation) {
9590 return Ok(());
9591 }
9592 Err(RuntimeConfigError(
9593 "cannot access or control the async runtime from work owned by a retired generation"
9594 .to_string(),
9595 ))
9596 }
9597
9598 fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
9599 Self::ensure_active_generation_current(state)?;
9600 match &state.lifecycle {
9601 RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
9602 RuntimeLifecycle::StoppedBeforeFirstUse
9603 | RuntimeLifecycle::Stopping(_)
9604 | RuntimeLifecycle::StoppingWithoutBackend(_)
9605 | RuntimeLifecycle::Stopped => {
9606 return Err(RuntimeConfigError(
9607 "the async runtime is stopped; call start before submitting work".to_string(),
9608 ));
9609 }
9610 RuntimeLifecycle::Initial => {}
9611 }
9612 if state.rejected_drops != 0 {
9613 return Err(RuntimeConfigError(
9614 "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
9615 ));
9616 }
9617 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
9618 state.lifecycle = RuntimeLifecycle::Running(backend.clone());
9619 Ok(backend)
9620 }
9621
9622 fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
9623 let mut state = self
9624 .state
9625 .lock()
9626 .unwrap_or_else(std::sync::PoisonError::into_inner);
9627 self.backend_locked(&mut state)
9628 }
9629
9630 fn backend(&self) -> RuntimeBackend {
9631 self.try_backend().unwrap_or_else(|error| panic!("{error}"))
9632 }
9633
9634 fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
9635 let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
9636 RuntimeLifecycle::Running(backend) => Some(backend.generation()),
9637 RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
9638 RuntimeLifecycle::Initial
9639 | RuntimeLifecycle::StoppedBeforeFirstUse
9640 | RuntimeLifecycle::StoppingWithoutBackend(_)
9641 | RuntimeLifecycle::Stopped => None,
9642 });
9643 state.rejected_drops += 1;
9644 generation
9645 }
9646
9647 fn try_spawn_tracked<F, T>(
9648 &self,
9649 future: F,
9650 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
9651 where
9652 F: Future<Output = T> + Send + 'static,
9653 T: Send + 'static,
9654 {
9655 #[cfg(test)]
9656 run_before_runtime_submission_lock_test_hook();
9657
9658 let (backend, registration) = {
9659 let mut state = self
9660 .state
9661 .lock()
9662 .unwrap_or_else(std::sync::PoisonError::into_inner);
9663 let backend = match self.backend_locked(&mut state) {
9664 Ok(backend) => backend,
9665 Err(error) => {
9666 let generation = Self::begin_rejected_drop(&mut state);
9667 return Err((error, future, generation));
9668 }
9669 };
9670 let Some(registration) = backend.work.try_register_async() else {
9671 let generation = Self::begin_rejected_drop(&mut state);
9672 return Err((
9673 RuntimeConfigError(
9674 "the async runtime is stopped; call start before submitting work".to_string(),
9675 ),
9676 future,
9677 generation,
9678 ));
9679 };
9680 (backend, registration)
9681 };
9682 #[cfg(test)]
9683 run_after_async_work_registration_test_hook();
9684 Ok(spawn_registered(
9685 &backend,
9686 Arc::clone(&self.metrics),
9687 future,
9688 registration,
9689 ))
9690 }
9691
9692 fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
9693 where
9694 F: Future<Output = T> + Send + 'static,
9695 T: Send + 'static,
9696 {
9697 match self.try_spawn_tracked(future) {
9698 Ok(handle) => Ok(handle),
9699 Err((error, future, _generation)) => {
9700 drop(RejectedSubmissionGuard { controller: self });
9701 Err((error, future))
9702 }
9703 }
9704 }
9705
9706 fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
9707 where
9708 F: Future<Output = T> + Send + 'static,
9709 T: Send + 'static,
9710 {
9711 match self.try_spawn_tracked(future) {
9712 Ok(handle) => handle,
9713 Err((error, future, generation)) => {
9714 let _rejection = RejectedSubmissionGuard { controller: self };
9715 let _drop_context = RejectedSubmissionDropContext::enter();
9716 let _generation = generation.map(RuntimeGenerationGuard::enter);
9717 let _ = catch_unwind_contained(|| drop(future));
9718 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
9719 message: error.to_string(),
9720 }))))
9721 }
9722 }
9723 }
9724
9725 fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
9726 where
9727 F: Future<Output = ()> + Send + 'static,
9728 {
9729 match self.try_spawn(future) {
9730 Ok(handle) => {
9731 handle.detach();
9732 Ok(())
9733 }
9734 Err((_, future)) => Err(future),
9735 }
9736 }
9737
9738 fn try_spawn_blocking_tracked<F, T>(
9739 &self,
9740 function: F,
9741 ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
9742 where
9743 F: FnOnce() -> T + Send + 'static,
9744 T: Send + 'static,
9745 {
9746 #[cfg(test)]
9747 run_before_runtime_submission_lock_test_hook();
9748
9749 let (backend, registration) = {
9750 let mut state = self
9751 .state
9752 .lock()
9753 .unwrap_or_else(std::sync::PoisonError::into_inner);
9754 let backend = match self.backend_locked(&mut state) {
9755 Ok(backend) => backend,
9756 Err(error) => {
9757 let generation = Self::begin_rejected_drop(&mut state);
9758 return Err((error, function, generation));
9759 }
9760 };
9761 let Some(registration) = backend.work.try_register_work() else {
9762 let generation = Self::begin_rejected_drop(&mut state);
9763 return Err((
9764 RuntimeConfigError(
9765 "the async runtime is stopped; call start before submitting work".to_string(),
9766 ),
9767 function,
9768 generation,
9769 ));
9770 };
9771 (backend, registration)
9772 };
9773 #[cfg(test)]
9774 run_after_blocking_work_registration_test_hook();
9775 Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
9776 }
9777
9778 fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
9779 where
9780 F: FnOnce() -> T + Send + 'static,
9781 T: Send + 'static,
9782 {
9783 match self.try_spawn_blocking_tracked(function) {
9784 Ok(handle) => Ok(handle),
9785 Err((error, function, _generation)) => {
9786 drop(RejectedSubmissionGuard { controller: self });
9787 Err((error, function))
9788 }
9789 }
9790 }
9791
9792 fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
9793 where
9794 F: FnOnce() -> T + Send + 'static,
9795 T: Send + 'static,
9796 {
9797 match self.try_spawn_blocking_tracked(function) {
9798 Ok(handle) => handle,
9799 Err((error, function, generation)) => {
9800 let _rejection = RejectedSubmissionGuard { controller: self };
9801 let _drop_context = RejectedSubmissionDropContext::enter();
9802 let _generation = generation.map(RuntimeGenerationGuard::enter);
9803 let _ = catch_unwind_contained(|| drop(function));
9804 JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
9805 message: error.to_string(),
9806 }))))
9807 }
9808 }
9809 }
9810
9811 #[cfg(test)]
9812 fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
9813 self
9814 .try_block_on(future)
9815 .unwrap_or_else(|error| panic!("{error}"))
9816 }
9817
9818 fn try_block_on_scope(
9819 &self,
9820 ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
9821 let mut state = self
9822 .state
9823 .lock()
9824 .unwrap_or_else(std::sync::PoisonError::into_inner);
9825 let backend = match self.backend_locked(&mut state) {
9826 Ok(backend) => backend,
9827 Err(error) => {
9828 let generation = Self::begin_rejected_drop(&mut state);
9829 return Err((error, generation));
9830 }
9831 };
9832 let Some(registration) = backend.work.try_register_work() else {
9833 let generation = Self::begin_rejected_drop(&mut state);
9834 return Err((
9835 RuntimeConfigError(
9836 "the async runtime is stopped; call start before submitting work".to_string(),
9837 ),
9838 generation,
9839 ));
9840 };
9841 Ok((backend, registration))
9842 }
9843
9844 fn try_block_on(
9845 &self,
9846 future: Pin<&mut dyn Future<Output = ()>>,
9847 ) -> Result<BlockOnOutcome, RuntimeConfigError> {
9848 let (backend, registration) = match self.try_block_on_scope() {
9849 Ok(scope) => scope,
9850 Err((error, _generation)) => {
9851 drop(RejectedSubmissionGuard { controller: self });
9852 return Err(error);
9853 }
9854 };
9855 let _registration = registration;
9856 let _generation = RuntimeGenerationGuard::enter(backend.generation());
9857 Ok(backend.block_on(future))
9858 }
9859
9860 fn start(&self) -> Result<(), RuntimeConfigError> {
9861 let mut state = self
9862 .state
9863 .lock()
9864 .unwrap_or_else(std::sync::PoisonError::into_inner);
9865 loop {
9866 Self::ensure_active_generation_current(&state)?;
9867 match &state.lifecycle {
9868 RuntimeLifecycle::Initial => {
9872 if state.rejected_drops == 0 {
9873 return Ok(());
9874 }
9875 if RejectedSubmissionDropContext::is_current() {
9876 return Err(RuntimeConfigError(
9877 "cannot start the async runtime while a rejected submission is being destroyed"
9878 .to_string(),
9879 ));
9880 }
9881 #[cfg(test)]
9882 run_before_initial_start_wait_test_hook();
9883 state = self
9884 .lifecycle_changed
9885 .wait(state)
9886 .unwrap_or_else(std::sync::PoisonError::into_inner);
9887 }
9888 RuntimeLifecycle::Running(_) => return Ok(()),
9889 RuntimeLifecycle::Stopping(identity) => {
9890 if identity.is_current() {
9891 return Err(RuntimeConfigError(
9892 "cannot restart the async runtime from work in the generation being stopped"
9893 .to_string(),
9894 ));
9895 }
9896 state = self
9897 .lifecycle_changed
9898 .wait(state)
9899 .unwrap_or_else(std::sync::PoisonError::into_inner);
9900 }
9901 RuntimeLifecycle::StoppingWithoutBackend(_) => {
9902 if RejectedSubmissionDropContext::is_current() {
9903 return Err(RuntimeConfigError(
9904 "cannot restart the async runtime while a rejected submission is being destroyed"
9905 .to_string(),
9906 ));
9907 }
9908 state = self
9909 .lifecycle_changed
9910 .wait(state)
9911 .unwrap_or_else(std::sync::PoisonError::into_inner);
9912 }
9913 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
9914 if state.rejected_drops == 0 {
9915 if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
9916 state.lifecycle = RuntimeLifecycle::Initial;
9917 return Ok(());
9918 }
9919 break;
9920 }
9921 if RejectedSubmissionDropContext::is_current() {
9922 return Err(RuntimeConfigError(
9923 "cannot restart the async runtime while a rejected submission is being destroyed"
9924 .to_string(),
9925 ));
9926 }
9927 state = self
9928 .lifecycle_changed
9929 .wait(state)
9930 .unwrap_or_else(std::sync::PoisonError::into_inner);
9931 }
9932 }
9933 }
9934 let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
9935 state.lifecycle = RuntimeLifecycle::Running(backend);
9936 Ok(())
9937 }
9938
9939 fn options(&self) -> RuntimeOptions {
9940 self
9941 .state
9942 .lock()
9943 .unwrap_or_else(std::sync::PoisonError::into_inner)
9944 .options
9945 .clone()
9946 }
9947
9948 fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
9949 let state = self
9950 .state
9951 .lock()
9952 .unwrap_or_else(std::sync::PoisonError::into_inner);
9953 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
9954 return None;
9955 };
9956 match &backend.executor {
9957 RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
9958 #[cfg(not(target_family = "wasm"))]
9959 RuntimeExecutor::MultiThread(_) => None,
9960 }
9961 }
9962
9963 fn request_current_thread_drain(&self) {
9964 if let Some(executor) = self.running_current_thread_executor() {
9965 executor.request_drain_if_queued();
9966 }
9967 }
9968
9969 fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
9970 let executor = {
9971 let state = self
9972 .state
9973 .lock()
9974 .unwrap_or_else(std::sync::PoisonError::into_inner);
9975 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
9976 return;
9977 };
9978 match &backend.executor {
9979 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
9980 #[cfg(not(target_family = "wasm"))]
9981 RuntimeExecutor::MultiThread(_) => return,
9982 }
9983 };
9984 executor.cancel_host_dispatch(failure);
9985 }
9986
9987 #[cfg(test)]
9988 fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
9989 let executor = {
9990 let state = self
9991 .state
9992 .lock()
9993 .unwrap_or_else(std::sync::PoisonError::into_inner);
9994 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
9995 return;
9996 };
9997 match &backend.executor {
9998 RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
9999 #[cfg(not(target_family = "wasm"))]
10000 RuntimeExecutor::MultiThread(_) => return,
10001 }
10002 };
10003 executor.cancel_host_dispatch_for_test(dispatch);
10004 }
10005
10006 fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
10007 let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
10008 self.drive_current_thread_dispatch_with_lease(dispatch, true)
10009 }
10010
10011 fn drive_current_thread_dispatch_with_lease(
10012 &self,
10013 dispatch: u64,
10014 mark_serviced: bool,
10015 ) -> Option<CurrentThreadTaskCallbackLease> {
10016 let mut host_turn = {
10017 let state = self
10018 .state
10019 .lock()
10020 .unwrap_or_else(std::sync::PoisonError::into_inner);
10021 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10022 return None;
10023 };
10024 let executor = match &backend.executor {
10025 RuntimeExecutor::CurrentThread(executor) => executor,
10026 #[cfg(not(target_family = "wasm"))]
10027 RuntimeExecutor::MultiThread(_) => return None,
10028 };
10029 let host_turn = executor.try_admit_host_turn(dispatch)?;
10030 if mark_serviced {
10031 CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
10032 }
10033 host_turn
10034 };
10035 #[cfg(test)]
10036 run_after_current_thread_host_admission_test_hook();
10037 host_turn.drive();
10038 Some(CurrentThreadTaskCallbackLease::new(host_turn))
10039 }
10040
10041 #[cfg(test)]
10042 fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
10043 self
10044 .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
10045 .is_some()
10046 }
10047
10048 fn shutdown(&self) -> Result<(), RuntimeConfigError> {
10049 let backend = {
10050 let mut state = self
10051 .state
10052 .lock()
10053 .unwrap_or_else(std::sync::PoisonError::into_inner);
10054 loop {
10055 Self::ensure_active_generation_current(&state)?;
10056 match &state.lifecycle {
10057 RuntimeLifecycle::Initial => {
10058 if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
10059 return Err(RuntimeConfigError(
10060 "cannot shut down the async runtime while a rejected submission is being destroyed"
10061 .to_string(),
10062 ));
10063 }
10064 let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
10065 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10066 self.lifecycle_changed.notify_all();
10067 while state.rejected_drops != 0 {
10068 state = self
10069 .lifecycle_changed
10070 .wait(state)
10071 .unwrap_or_else(std::sync::PoisonError::into_inner);
10072 }
10073 debug_assert!(matches!(
10074 state.lifecycle,
10075 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10076 ));
10077 state.lifecycle = target.into_lifecycle();
10078 self.lifecycle_changed.notify_all();
10079 return Ok(());
10080 }
10081 RuntimeLifecycle::Running(backend) => {
10082 if backend.is_current() {
10083 return Err(RuntimeConfigError(
10084 "cannot shut down the async runtime from work running on that runtime".to_string(),
10085 ));
10086 }
10087 let identity = backend.stop_identity();
10088 let stop = backend.generation_stop();
10089 let stop_publication = stop.publication_guard();
10094 stop_publication.publish_stopping();
10095 let RuntimeLifecycle::Running(backend) =
10096 std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
10097 else {
10098 unreachable!();
10099 };
10100 drop(stop_publication);
10104 break backend;
10105 }
10106 RuntimeLifecycle::Stopping(identity) => {
10107 if identity.is_current() {
10108 return Err(RuntimeConfigError(
10109 "cannot wait for async runtime shutdown from work in the generation being stopped"
10110 .to_string(),
10111 ));
10112 }
10113 state = self
10114 .lifecycle_changed
10115 .wait(state)
10116 .unwrap_or_else(std::sync::PoisonError::into_inner);
10117 }
10118 RuntimeLifecycle::StoppingWithoutBackend(_) => {
10119 if RejectedSubmissionDropContext::is_current() {
10120 return Err(RuntimeConfigError(
10121 "cannot shut down the async runtime while a rejected submission is being destroyed"
10122 .to_string(),
10123 ));
10124 }
10125 state = self
10126 .lifecycle_changed
10127 .wait(state)
10128 .unwrap_or_else(std::sync::PoisonError::into_inner);
10129 }
10130 RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10131 if state.rejected_drops == 0 {
10132 return Ok(());
10133 }
10134 if RejectedSubmissionDropContext::is_current() {
10135 return Err(RuntimeConfigError(
10136 "cannot shut down the async runtime while a rejected submission is being destroyed"
10137 .to_string(),
10138 ));
10139 }
10140 let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10141 ZeroBackendShutdownTarget::StoppedBeforeFirstUse
10142 } else {
10143 ZeroBackendShutdownTarget::Stopped
10144 };
10145 state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10146 self.lifecycle_changed.notify_all();
10147 while state.rejected_drops != 0 {
10148 state = self
10149 .lifecycle_changed
10150 .wait(state)
10151 .unwrap_or_else(std::sync::PoisonError::into_inner);
10152 }
10153 debug_assert!(matches!(
10154 state.lifecycle,
10155 RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10156 ));
10157 state.lifecycle = target.into_lifecycle();
10158 self.lifecycle_changed.notify_all();
10159 return Ok(());
10160 }
10161 }
10162 }
10163 };
10164
10165 #[cfg(test)]
10166 run_after_generation_stop_publication_test_hook();
10167 backend.begin_shutdown();
10168 backend.wait_until_idle();
10169
10170 #[cfg(not(target_family = "wasm"))]
10171 let worker_lifecycle = backend.worker_lifecycle();
10172 drop(backend);
10173
10174 #[cfg(not(target_family = "wasm"))]
10175 if let Some(worker_lifecycle) = worker_lifecycle {
10176 worker_lifecycle.wait_for_all_workers();
10177 }
10178
10179 let mut state = self
10180 .state
10181 .lock()
10182 .unwrap_or_else(std::sync::PoisonError::into_inner);
10183 while state.rejected_drops != 0 {
10184 state = self
10185 .lifecycle_changed
10186 .wait(state)
10187 .unwrap_or_else(std::sync::PoisonError::into_inner);
10188 }
10189 debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
10190 state.lifecycle = RuntimeLifecycle::Stopped;
10191 self.lifecycle_changed.notify_all();
10192 Ok(())
10193 }
10194}
10195
10196static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
10197
10198pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10199 RUNTIME.configure(options)
10200}
10201
10202pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10203 RUNTIME.configure_partial(patch)
10204}
10205
10206pub fn configured_options() -> RuntimeOptions {
10207 RUNTIME.options()
10208}
10209
10210pub fn is_multi_threaded() -> bool {
10211 RUNTIME.options().flavor == RuntimeFlavor::MultiThread
10212}
10213
10214fn spawn_registered<F, T>(
10215 backend: &RuntimeBackend,
10216 metrics: Arc<RuntimeMetrics>,
10217 future: F,
10218 registration: (AbortRegistration, GenerationWorkGuard),
10219) -> JoinHandle<T>
10220where
10221 F: Future<Output = T> + Send + 'static,
10222 T: Send + 'static,
10223{
10224 metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
10225 let generation = backend.generation();
10226 let (abort_registration, work_registration) = registration;
10227 #[cfg(not(target_family = "wasm"))]
10228 let dependency = Arc::new(TaskDependency::new_task(generation));
10229 #[cfg(not(target_family = "wasm"))]
10230 let task_dependency = Arc::clone(&dependency);
10231 let wrapped = async move {
10232 let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
10233 let mut abortable = std::pin::pin!(abortable);
10234 let polled = futures::future::poll_fn(|cx| {
10235 #[cfg(not(target_family = "wasm"))]
10236 task_dependency.begin_poll();
10237 #[cfg(not(target_family = "wasm"))]
10238 let _dependency = TaskDependencyGuard::enter(&task_dependency);
10239 abortable.as_mut().poll(cx)
10240 });
10241 match AssertUnwindSafe(polled).catch_unwind().await {
10242 Ok(Ok(output)) => {
10243 metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
10244 Ok(ContainedTaskOutput::new(output, generation))
10245 }
10246 Ok(Err(_)) => Err(JoinError {
10247 message: "async runtime stopped before the task completed".to_string(),
10248 }),
10249 Err(panic) => {
10250 metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
10251 Err(panic_payload_to_join_error(panic))
10252 }
10253 }
10254 };
10255 let scheduler = backend.downgrade_executor();
10256 let (runnable, task) = async_task::spawn(
10260 RegisteredTaskFuture::new(wrapped, work_registration, generation),
10261 move |runnable| {
10262 scheduler.schedule(runnable);
10263 },
10264 );
10265 backend.schedule(runnable);
10266 JoinHandle(JoinHandleInner::Task {
10267 task: task.fallible(),
10268 awaiter: None,
10269 #[cfg(not(target_family = "wasm"))]
10270 dependency,
10271 })
10272}
10273
10274pub fn spawn<F, T>(future: F) -> JoinHandle<T>
10275where
10276 F: Future<Output = T> + Send + 'static,
10277 T: Send + 'static,
10278{
10279 RUNTIME.spawn(future)
10280}
10281
10282pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10287where
10288 F: Future<Output = T> + Send + 'static,
10289 T: Send + 'static,
10290{
10291 RUNTIME.try_spawn(future)
10292}
10293
10294pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
10295where
10296 F: Future<Output = ()> + Send + 'static,
10297{
10298 RUNTIME.try_spawn_detached(future)
10299}
10300
10301pub fn spawn_detached<F>(future: F)
10302where
10303 F: Future<Output = ()> + Send + 'static,
10304{
10305 spawn(future).detach();
10306}
10307
10308pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
10309where
10310 F: FnOnce() -> T + Send + 'static,
10311 T: Send + 'static,
10312{
10313 RUNTIME.spawn_blocking(function)
10314}
10315
10316pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10321where
10322 F: FnOnce() -> T + Send + 'static,
10323 T: Send + 'static,
10324{
10325 RUNTIME.try_spawn_blocking(function)
10326}
10327
10328fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
10329 let (backend, registration) = match controller.try_block_on_scope() {
10330 Ok(scope) => scope,
10331 Err((error, generation)) => {
10332 let _rejection = RejectedSubmissionGuard { controller };
10333 let _drop_context = RejectedSubmissionDropContext::enter();
10334 let _generation = generation.map(RuntimeGenerationGuard::enter);
10335 let _ = catch_unwind_contained(|| drop(future));
10336 panic!("{error}");
10337 }
10338 };
10339 let generation = backend.generation();
10340 let _registration = registration;
10341 let _generation = RuntimeGenerationGuard::enter(generation);
10342 let mut output = None;
10343 {
10344 let future = ContainedFuture::new(future, generation);
10345 let mut erased = std::pin::pin!(async {
10346 output = Some(future.await);
10347 });
10348 let _ = backend.block_on(erased.as_mut());
10349 }
10354 output.expect("async runtime returned before the future completed")
10355}
10356
10357pub fn block_on<F: Future>(future: F) -> F::Output {
10358 block_on_with_controller(&RUNTIME, future)
10359}
10360
10361pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
10362 try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
10363}
10364
10365pub fn try_block_on_dyn(
10367 future: Pin<&mut dyn Future<Output = ()>>,
10368) -> Result<(), RuntimeConfigError> {
10369 match RUNTIME.try_block_on(future)? {
10370 BlockOnOutcome::Completed => Ok(()),
10371 BlockOnOutcome::Stopped => Err(RuntimeConfigError(
10372 "the async runtime stopped before block_on completed its future".to_string(),
10373 )),
10374 }
10375}
10376
10377pub fn start() -> Result<(), RuntimeConfigError> {
10378 RUNTIME.start()
10379}
10380
10381pub fn shutdown() -> Result<(), RuntimeConfigError> {
10382 RUNTIME.shutdown()
10383}
10384
10385fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
10386 for failure in completion.failed_dispatches {
10387 RUNTIME.cancel_current_thread_dispatch(failure);
10388 }
10389 if completion.redispatch {
10390 RUNTIME.request_current_thread_drain();
10391 }
10392}
10393
10394pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10403 complete_current_thread_task_delivery(
10404 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
10405 );
10406}
10407
10408pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10414 complete_current_thread_task_delivery(
10415 CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
10416 );
10417}
10418
10419pub fn cancel_current_thread_task_dispatch(delivery: u64) {
10426 complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
10427}
10428
10429pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
10438 RUNTIME.drive_current_thread_tasks(delivery)
10439}
10440
10441pub fn reset_metrics() {
10442 RUNTIME.metrics.reset();
10443}
10444
10445pub fn metrics() -> RuntimeMetricsSnapshot {
10446 let options = RUNTIME.options();
10447 RUNTIME.metrics.snapshot(&options)
10448}
10449
10450#[cfg(test)]
10451mod tests {
10452 use super::*;
10453
10454 static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10455 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10456 LazyLock::new(Mutex::default);
10457 static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
10458 static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10459 static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10460 static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10461 LazyLock::new(Mutex::default);
10462 static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10463 static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
10464 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10465 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10466 LazyLock::new(Mutex::default);
10467 static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
10468 static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
10469 CurrentThreadTaskDriverRegistry,
10470 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10471 static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
10472 static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
10473 static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10474 LazyLock::new(Mutex::default);
10475 static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
10476 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10477 static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10478 LazyLock::new(Mutex::default);
10479 static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
10480 static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10481 LazyLock::new(Mutex::default);
10482 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10483 LazyLock::new(Mutex::default);
10484 static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
10485 CurrentThreadTaskDriverRegistry,
10486 > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10487 static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
10488 LazyLock::new(Mutex::default);
10489 static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10490 static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
10491 LazyLock::new(CurrentThreadTaskDriverRegistry::default);
10492 static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
10493
10494 thread_local! {
10495 static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
10496 const { std::cell::Cell::new(0) };
10497 static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
10498 const { std::cell::Cell::new(0) };
10499 }
10500
10501 #[cfg(not(target_family = "wasm"))]
10502 struct StopPollProbe {
10503 polls: Arc<AtomicUsize>,
10504 first_poll: Option<std::sync::mpsc::Sender<()>>,
10505 }
10506
10507 #[cfg(not(target_family = "wasm"))]
10508 impl Future for StopPollProbe {
10509 type Output = ();
10510
10511 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
10512 self.polls.fetch_add(1, Ordering::SeqCst);
10513 if let Some(first_poll) = self.first_poll.take() {
10514 first_poll.send(()).unwrap();
10515 }
10516 Poll::Pending
10517 }
10518 }
10519
10520 #[cfg(not(target_family = "wasm"))]
10521 struct PanickingJoinAwaiter {
10522 wakes: Arc<AtomicUsize>,
10523 drops: Arc<AtomicUsize>,
10524 }
10525
10526 #[cfg(not(target_family = "wasm"))]
10527 impl std::task::Wake for PanickingJoinAwaiter {
10528 fn wake(self: Arc<Self>) {
10529 self.wakes.fetch_add(1, Ordering::SeqCst);
10530 panic!("intentional JoinHandle awaiter wake panic");
10531 }
10532
10533 fn wake_by_ref(self: &Arc<Self>) {
10534 self.wakes.fetch_add(1, Ordering::SeqCst);
10535 panic!("intentional JoinHandle awaiter wake-by-ref panic");
10536 }
10537 }
10538
10539 #[cfg(not(target_family = "wasm"))]
10540 impl Drop for PanickingJoinAwaiter {
10541 fn drop(&mut self) {
10542 self.drops.fetch_add(1, Ordering::SeqCst);
10543 panic!("intentional JoinHandle awaiter destructor panic");
10544 }
10545 }
10546
10547 #[cfg(not(target_family = "wasm"))]
10548 fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
10549 Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
10550 }
10551
10552 #[cfg(not(target_family = "wasm"))]
10553 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
10554 enum JoinAwaiterWakerEventKind {
10555 Clone,
10556 Wake,
10557 WakeByRef,
10558 Drop,
10559 }
10560
10561 #[cfg(not(target_family = "wasm"))]
10562 struct JoinAwaiterWakerState {
10563 events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
10564 }
10565
10566 #[cfg(not(target_family = "wasm"))]
10567 impl JoinAwaiterWakerState {
10568 fn record(&self, kind: JoinAwaiterWakerEventKind) {
10569 self
10570 .events
10571 .lock()
10572 .unwrap_or_else(std::sync::PoisonError::into_inner)
10573 .push((kind, active_runtime_generation()));
10574 }
10575
10576 fn clear(&self) {
10577 self
10578 .events
10579 .lock()
10580 .unwrap_or_else(std::sync::PoisonError::into_inner)
10581 .clear();
10582 }
10583
10584 fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
10585 self
10586 .events
10587 .lock()
10588 .unwrap_or_else(std::sync::PoisonError::into_inner)
10589 .clone()
10590 }
10591 }
10592
10593 #[cfg(not(target_family = "wasm"))]
10594 struct JoinAwaiterWakerToken {
10595 state: Arc<JoinAwaiterWakerState>,
10596 }
10597
10598 #[cfg(not(target_family = "wasm"))]
10599 unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
10600 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
10601 token.state.record(JoinAwaiterWakerEventKind::Clone);
10602 let clone = Box::new(JoinAwaiterWakerToken {
10603 state: Arc::clone(&token.state),
10604 });
10605 std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
10606 }
10607
10608 #[cfg(not(target_family = "wasm"))]
10609 unsafe fn wake_join_awaiter_waker(data: *const ()) {
10610 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
10611 token.state.record(JoinAwaiterWakerEventKind::Wake);
10612 }
10613
10614 #[cfg(not(target_family = "wasm"))]
10615 unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
10616 let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
10617 token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
10618 }
10619
10620 #[cfg(not(target_family = "wasm"))]
10621 unsafe fn drop_join_awaiter_waker(data: *const ()) {
10622 let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
10623 token.state.record(JoinAwaiterWakerEventKind::Drop);
10624 }
10625
10626 #[cfg(not(target_family = "wasm"))]
10627 static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
10628 clone_join_awaiter_waker,
10629 wake_join_awaiter_waker,
10630 wake_join_awaiter_waker_by_ref,
10631 drop_join_awaiter_waker,
10632 );
10633
10634 #[cfg(not(target_family = "wasm"))]
10635 fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
10636 let token = Box::new(JoinAwaiterWakerToken { state });
10637 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
10638 unsafe { Waker::from_raw(raw) }
10639 }
10640
10641 #[cfg(not(target_family = "wasm"))]
10642 fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
10643 wait_until("the block_on driver to sleep", || {
10644 stop
10645 .parkers
10646 .lock()
10647 .unwrap_or_else(std::sync::PoisonError::into_inner)
10648 .iter()
10649 .filter_map(Weak::upgrade)
10650 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
10651 });
10652 }
10653
10654 #[test]
10655 fn unique_ids_fail_before_wraparound() {
10656 let counter = AtomicU64::new(u64::MAX - 1);
10657 assert_eq!(
10658 next_unique_id(&counter, "test id space exhausted"),
10659 u64::MAX - 1
10660 );
10661
10662 let exhausted = catch_unwind(AssertUnwindSafe(|| {
10663 next_unique_id(&counter, "test id space exhausted");
10664 }));
10665 assert!(exhausted.is_err());
10666 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
10667 }
10668
10669 #[test]
10670 fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
10671 let work = GenerationWork::new();
10672 {
10673 let mut state = work.state.lock().unwrap();
10674 state.next_task_id = u64::MAX;
10675 }
10676
10677 let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
10678 assert!(exhausted.is_err());
10679
10680 let state = work.state.lock().unwrap();
10681 assert_eq!(state.next_task_id, u64::MAX);
10682 assert_eq!(state.active, 0);
10683 assert!(state.abort_handles.is_empty());
10684 }
10685
10686 #[test]
10687 fn caught_panic_payload_destructor_runs() {
10688 struct DropSignal(Arc<AtomicBool>);
10689
10690 impl Drop for DropSignal {
10691 fn drop(&mut self) {
10692 self.0.store(true, Ordering::SeqCst);
10693 }
10694 }
10695
10696 let dropped = Arc::new(AtomicBool::new(false));
10697 let payload = DropSignal(Arc::clone(&dropped));
10698
10699 assert!(
10700 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
10701 "the original panic must remain contained"
10702 );
10703 assert!(
10704 dropped.load(Ordering::SeqCst),
10705 "an ordinary caught panic payload must be destroyed"
10706 );
10707 }
10708
10709 #[test]
10710 fn hostile_caught_panic_payload_destructor_remains_contained() {
10711 struct PanicOnDrop(Arc<AtomicBool>);
10712
10713 impl Drop for PanicOnDrop {
10714 fn drop(&mut self) {
10715 self.0.store(true, Ordering::SeqCst);
10716 panic!("caught panic payload destructor escaped");
10717 }
10718 }
10719
10720 let dropped = Arc::new(AtomicBool::new(false));
10721 let payload = PanicOnDrop(Arc::clone(&dropped));
10722 let result = catch_unwind(AssertUnwindSafe(|| {
10723 catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
10724 }));
10725
10726 assert!(
10727 result.is_ok(),
10728 "the payload destructor's nested panic must remain contained"
10729 );
10730 assert!(
10731 result.unwrap().is_none(),
10732 "the original panic must remain contained"
10733 );
10734 assert!(
10735 dropped.load(Ordering::SeqCst),
10736 "the hostile payload destructor must be attempted"
10737 );
10738 }
10739
10740 fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10741 CurrentThreadHostDispatchResult::Accepted
10742 }
10743
10744 fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10745 CurrentThreadHostDispatchResult::Unavailable
10746 }
10747
10748 fn count_current_thread_recovery_host_dispatch(
10749 _dispatch: u64,
10750 ) -> CurrentThreadHostDispatchResult {
10751 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
10752 CurrentThreadHostDispatchResult::Accepted
10753 }
10754
10755 fn reset_current_thread_recovery_host_dispatches() {
10756 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
10757 }
10758
10759 fn current_thread_recovery_host_dispatches() -> usize {
10760 CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
10761 }
10762
10763 fn accept_only_first_current_thread_host_dispatch(
10764 _dispatch: u64,
10765 ) -> CurrentThreadHostDispatchResult {
10766 if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
10767 let previous = dispatches.get();
10768 dispatches.set(previous + 1);
10769 previous == 0
10770 }) {
10771 CurrentThreadHostDispatchResult::Accepted
10772 } else {
10773 CurrentThreadHostDispatchResult::Unavailable
10774 }
10775 }
10776
10777 fn reset_current_thread_one_shot_host_dispatches() {
10778 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
10779 }
10780
10781 fn current_thread_one_shot_host_dispatches() -> usize {
10782 CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
10783 }
10784
10785 fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10786 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10787 CurrentThreadHostDispatchResult::Accepted
10788 }
10789
10790 fn count_current_thread_blocking_budget_host_dispatch(
10791 _dispatch: u64,
10792 ) -> CurrentThreadHostDispatchResult {
10793 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10794 CurrentThreadHostDispatchResult::Accepted
10795 }
10796
10797 fn count_current_thread_admission_host_dispatch(
10798 _dispatch: u64,
10799 ) -> CurrentThreadHostDispatchResult {
10800 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10801 CurrentThreadHostDispatchResult::Accepted
10802 }
10803
10804 fn count_current_thread_interleaved_recovery_host_dispatch(
10805 _dispatch: u64,
10806 ) -> CurrentThreadHostDispatchResult {
10807 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10808 CurrentThreadHostDispatchResult::Accepted
10809 }
10810
10811 fn dispatch_late_registration_current_thread_tasks(
10812 dispatch: u64,
10813 ) -> CurrentThreadHostDispatchResult {
10814 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
10815 }
10816
10817 fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
10818 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
10819 .lock()
10820 .unwrap()
10821 .push(dispatch);
10822 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
10823 }
10824
10825 fn dispatch_authoritative_panic_current_thread_tasks(
10826 dispatch: u64,
10827 ) -> CurrentThreadHostDispatchResult {
10828 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
10829 .lock()
10830 .unwrap()
10831 .push(dispatch);
10832 match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
10833 0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
10834 2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
10835 _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
10836 }
10837 }
10838
10839 fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
10840 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
10841 }
10842
10843 fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
10844 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
10845 .lock()
10846 .unwrap()
10847 .push(dispatch);
10848 assert!(
10849 !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
10850 "injected CurrentThread host dispatch panic"
10851 );
10852 CurrentThreadHostDispatchResult::Accepted
10853 }
10854
10855 fn panic_once_current_thread_armed_republish_host_dispatch(
10856 dispatch: u64,
10857 ) -> CurrentThreadHostDispatchResult {
10858 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
10859 .lock()
10860 .unwrap()
10861 .push(dispatch);
10862 assert!(
10863 !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
10864 "injected armed CurrentThread host dispatch panic"
10865 );
10866 CurrentThreadHostDispatchResult::Accepted
10867 }
10868
10869 fn record_current_thread_republish_rearm_dispatch(
10870 dispatch: u64,
10871 ) -> CurrentThreadHostDispatchResult {
10872 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
10873 .lock()
10874 .unwrap()
10875 .push(dispatch);
10876 CurrentThreadHostDispatchResult::Accepted
10877 }
10878
10879 fn reject_initial_current_thread_publication_once(
10880 dispatch: u64,
10881 ) -> CurrentThreadHostDispatchResult {
10882 let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
10883 .lock()
10884 .unwrap();
10885 dispatches.push(dispatch);
10886 if dispatches.len() == 1 {
10887 CurrentThreadHostDispatchResult::Unavailable
10888 } else {
10889 CurrentThreadHostDispatchResult::Accepted
10890 }
10891 }
10892
10893 fn reject_current_thread_replacement_publication_once(
10894 dispatch: u64,
10895 ) -> CurrentThreadHostDispatchResult {
10896 let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
10897 .lock()
10898 .unwrap();
10899 dispatches.push(dispatch);
10900 if dispatches.len() == 2 {
10901 CurrentThreadHostDispatchResult::Unavailable
10902 } else {
10903 CurrentThreadHostDispatchResult::Accepted
10904 }
10905 }
10906
10907 fn fail_current_thread_replacement_publication_once(
10908 dispatch: u64,
10909 ) -> CurrentThreadHostDispatchResult {
10910 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
10911 .lock()
10912 .unwrap()
10913 .push(dispatch);
10914 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
10915 }
10916
10917 fn count_current_thread_shutdown_republish_host_dispatch(
10918 _dispatch: u64,
10919 ) -> CurrentThreadHostDispatchResult {
10920 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10921 CurrentThreadHostDispatchResult::Accepted
10922 }
10923
10924 fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
10925 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
10926 CurrentThreadHostDispatchResult::Accepted
10927 }
10928
10929 fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
10930 let pending = executor.dispatch_pending.load(Ordering::Acquire);
10931 if pending != 0 {
10932 return pending;
10933 }
10934 let dispatch = next_current_thread_dispatch_id();
10935 match executor.dispatch_pending.compare_exchange(
10936 0,
10937 dispatch,
10938 Ordering::AcqRel,
10939 Ordering::Acquire,
10940 ) {
10941 Ok(_) => dispatch,
10942 Err(pending) => pending,
10943 }
10944 }
10945
10946 fn current_thread_dispatch_failure(
10947 dispatch: u64,
10948 epoch: u64,
10949 ) -> CurrentThreadTaskDispatchFailure {
10950 CurrentThreadTaskDispatchFailure { dispatch, epoch }
10951 }
10952
10953 fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
10954 let executor = controller
10955 .running_current_thread_executor()
10956 .expect("the test controller must have a running CurrentThread executor");
10957 current_thread_dispatch(&executor)
10958 }
10959
10960 #[test]
10961 fn current_thread_executor_drives_spawned_tasks() {
10962 let metrics = Arc::new(RuntimeMetrics::default());
10963 let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
10964 let scheduler = Arc::clone(&executor);
10965 let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
10966 scheduler.schedule(runnable);
10967 });
10968
10969 executor.schedule(runnable);
10970
10971 let mut output = None;
10972 {
10973 let mut future = std::pin::pin!(async {
10974 output = Some(task.await);
10975 });
10976 executor.block_on(future.as_mut());
10977 }
10978 assert_eq!(output, Some(42));
10979 assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
10980 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
10981 }
10982
10983 #[cfg(not(target_family = "wasm"))]
10984 #[test]
10985 fn current_thread_shutdown_wake_does_not_repoll_user_future() {
10986 use std::sync::mpsc;
10987
10988 let executor = Arc::new(CurrentThreadExecutor::with_detection(
10989 Arc::new(RuntimeMetrics::default()),
10990 false,
10991 None,
10992 ));
10993 let polls = Arc::new(AtomicUsize::new(0));
10994 let (first_poll_tx, first_poll_rx) = mpsc::channel();
10995 let (outcome_tx, outcome_rx) = mpsc::channel();
10996 let driver_executor = Arc::clone(&executor);
10997 let driver_polls = Arc::clone(&polls);
10998 let driver = std::thread::spawn(move || {
10999 let mut future = std::pin::pin!(StopPollProbe {
11000 polls: driver_polls,
11001 first_poll: Some(first_poll_tx)
11002 });
11003 outcome_tx
11004 .send(driver_executor.block_on(future.as_mut()))
11005 .unwrap();
11006 });
11007
11008 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11009 wait_for_generation_parker_to_sleep(&executor.stop);
11010 executor.stop.begin_shutdown();
11011
11012 assert_eq!(
11013 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11014 BlockOnOutcome::Stopped
11015 );
11016 driver.join().unwrap();
11017 assert_eq!(
11018 polls.load(Ordering::SeqCst),
11019 1,
11020 "a shutdown wake must be checked before the next CurrentThread user poll"
11021 );
11022 }
11023
11024 #[cfg(not(target_family = "wasm"))]
11025 #[test]
11026 fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
11027 use std::sync::mpsc;
11028
11029 let metrics = Arc::new(RuntimeMetrics::default());
11030 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11031 Arc::clone(&metrics),
11032 accept_current_thread_host_dispatch,
11033 ));
11034 let polls = Arc::new(AtomicUsize::new(0));
11035 let task_polls = Arc::clone(&polls);
11036 let (runnable, task) = async_task::spawn(
11037 async move {
11038 task_polls.fetch_add(1, Ordering::SeqCst);
11039 },
11040 |_| {},
11041 );
11042 executor.schedule(runnable);
11043 let dispatch = current_thread_dispatch(&executor);
11044
11045 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
11046 let (release_claim_tx, release_claim_rx) = mpsc::channel();
11047 let driver_executor = Arc::clone(&executor);
11048 let driver = std::thread::spawn(move || {
11049 CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
11050 *slot.borrow_mut() = Some(Box::new(move || {
11051 claim_locked_tx.send(()).unwrap();
11052 release_claim_rx.recv().unwrap();
11053 }));
11054 });
11055 driver_executor.drive_host_turn_for_dispatch(dispatch);
11056 });
11057 claim_locked_rx
11058 .recv_timeout(Duration::from_secs(2))
11059 .expect("the admitted host turn must pause before its runnable claim");
11060
11061 executor.stop.publish_stopping();
11062 release_claim_tx.send(()).unwrap();
11063 driver.join().unwrap();
11064
11065 assert_eq!(
11066 polls.load(Ordering::SeqCst),
11067 0,
11068 "stop publication must outrank a later runnable claim by an already-admitted host turn"
11069 );
11070 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11071
11072 executor.begin_shutdown();
11073 assert!(
11074 futures::executor::block_on(task.fallible()).is_none(),
11075 "shutdown must cancel the runnable rejected by the stopped claim"
11076 );
11077 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11078 }
11079
11080 #[cfg(not(target_family = "wasm"))]
11081 #[test]
11082 fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
11083 use std::sync::mpsc;
11084
11085 let metrics = Arc::new(RuntimeMetrics::default());
11086 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11087 Arc::clone(&metrics),
11088 accept_current_thread_host_dispatch,
11089 ));
11090 let polls = Arc::new(AtomicUsize::new(0));
11091 let task_polls = Arc::clone(&polls);
11092 let (runnable, task) = async_task::spawn(
11093 async move {
11094 task_polls.fetch_add(1, Ordering::SeqCst);
11095 },
11096 |_| {},
11097 );
11098 executor.schedule(runnable);
11099
11100 let queue = executor
11101 .queue
11102 .lock()
11103 .unwrap_or_else(std::sync::PoisonError::into_inner);
11104 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
11105 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
11106 let cancelling_executor = Arc::clone(&executor);
11107 let cancellation = std::thread::spawn(move || {
11108 let scheduler = cancelling_executor
11109 .scheduler_idle_lock
11110 .lock()
11111 .unwrap_or_else(std::sync::PoisonError::into_inner);
11112 let cancelling = cancelling_executor
11113 .begin_failed_dispatch_cancellation()
11114 .expect("the test must publish terminal dispatch cancellation");
11115 drop(scheduler);
11116 cancellation_published_tx.send(()).unwrap();
11117 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
11118 cancellation_done_tx.send(()).unwrap();
11119 });
11120 cancellation_published_rx
11121 .recv_timeout(Duration::from_secs(2))
11122 .expect("terminal dispatch cancellation must publish before queue draining");
11123
11124 let claimant_executor = Arc::clone(&executor);
11125 let claimant = std::thread::spawn(move || claimant_executor.drain_one());
11126 assert!(
11127 !claimant.join().unwrap(),
11128 "a runnable claim after terminal cancellation publication must be rejected"
11129 );
11130 assert_eq!(polls.load(Ordering::SeqCst), 0);
11131 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11132 assert!(
11133 matches!(
11134 cancellation_done_rx.try_recv(),
11135 Err(mpsc::TryRecvError::Empty)
11136 ),
11137 "terminal cancellation must still be waiting to drain the queued runnable"
11138 );
11139
11140 drop(queue);
11141 cancellation_done_rx
11142 .recv_timeout(Duration::from_secs(2))
11143 .expect("terminal cancellation must drain the rejected runnable");
11144 cancellation.join().unwrap();
11145 assert!(
11146 futures::executor::block_on(task.fallible()).is_none(),
11147 "terminal cancellation must cancel the runnable it prevented from starting"
11148 );
11149 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11150 }
11151
11152 #[cfg(not(target_family = "wasm"))]
11153 #[test]
11154 fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
11155 use std::sync::mpsc;
11156
11157 let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
11158 let polls = Arc::new(AtomicUsize::new(0));
11159 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11160 let (outcome_tx, outcome_rx) = mpsc::channel();
11161 let driver_executor = Arc::clone(&executor);
11162 let driver_polls = Arc::clone(&polls);
11163 let driver = std::thread::spawn(move || {
11164 let mut future = std::pin::pin!(StopPollProbe {
11165 polls: driver_polls,
11166 first_poll: Some(first_poll_tx)
11167 });
11168 outcome_tx
11169 .send(driver_executor.block_on(future.as_mut()))
11170 .unwrap();
11171 });
11172
11173 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11174 wait_for_generation_parker_to_sleep(&executor.stop);
11175 executor.stop.begin_shutdown();
11176
11177 assert_eq!(
11178 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11179 BlockOnOutcome::Stopped
11180 );
11181 driver.join().unwrap();
11182 assert_eq!(
11183 polls.load(Ordering::SeqCst),
11184 1,
11185 "a shutdown wake must be checked before the next parking block_on user poll"
11186 );
11187 }
11188
11189 #[cfg(not(target_family = "wasm"))]
11190 #[test]
11191 fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
11192 use std::sync::mpsc;
11193
11194 let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
11195 let polls = Arc::new(AtomicUsize::new(0));
11196 let (first_poll_tx, first_poll_rx) = mpsc::channel();
11197 let (outcome_tx, outcome_rx) = mpsc::channel();
11198 let driver_executor = Arc::clone(&executor);
11199 let driver_polls = Arc::clone(&polls);
11200 let driver = std::thread::spawn(move || {
11201 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
11202 let mut future = std::pin::pin!(StopPollProbe {
11203 polls: driver_polls,
11204 first_poll: Some(first_poll_tx)
11205 });
11206 outcome_tx
11207 .send(driver_executor.block_on(future.as_mut()))
11208 .unwrap();
11209 });
11210
11211 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11212 wait_for_generation_parker_to_sleep(&executor.stop);
11213 executor.stop.begin_shutdown();
11214
11215 assert_eq!(
11216 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11217 BlockOnOutcome::Stopped
11218 );
11219 driver.join().unwrap();
11220 assert_eq!(
11221 polls.load(Ordering::SeqCst),
11222 1,
11223 "a shutdown wake must be checked before the next cooperative user poll"
11224 );
11225 }
11226
11227 #[test]
11228 fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
11229 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11230 Arc::new(RuntimeMetrics::default()),
11231 accept_current_thread_host_dispatch,
11232 ));
11233 let pending = u64::MAX - 1;
11234 executor.dispatch_pending.store(pending, Ordering::Release);
11235 let mut allocations = 0;
11236
11237 executor.request_drain_with(|| {
11238 allocations += 1;
11239 u64::MAX
11240 });
11241
11242 assert_eq!(allocations, 0);
11243 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
11244 }
11245
11246 #[cfg(not(target_family = "wasm"))]
11247 #[test]
11248 fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
11249 use std::sync::mpsc;
11250
11251 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11252 Arc::new(RuntimeMetrics::default()),
11253 accept_current_thread_host_dispatch,
11254 ));
11255 let dispatch = current_thread_dispatch(&executor);
11256 let scheduler = executor
11257 .scheduler_idle_lock
11258 .lock()
11259 .unwrap_or_else(std::sync::PoisonError::into_inner);
11260 let (claiming_tx, claiming_rx) = mpsc::channel();
11261 let (claimed_tx, claimed_rx) = mpsc::channel();
11262 let claiming_executor = Arc::clone(&executor);
11263 let claimant = std::thread::spawn(move || {
11264 claiming_tx.send(()).unwrap();
11265 claimed_tx
11266 .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
11267 .unwrap();
11268 });
11269 claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11270
11271 executor.stop.begin_shutdown();
11272 drop(scheduler);
11273
11274 assert!(
11275 !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11276 "an old-generation dispatch must not claim a host turn after stop becomes visible"
11277 );
11278 claimant.join().unwrap();
11279 assert_eq!(
11280 executor.dispatch_pending.load(Ordering::Acquire),
11281 dispatch,
11282 "rejecting the stopped claim must not consume its stale capability"
11283 );
11284 }
11285
11286 #[cfg(not(target_family = "wasm"))]
11287 #[test]
11288 fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
11289 use std::sync::mpsc;
11290
11291 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11292 let metrics = Arc::new(RuntimeMetrics::default());
11293 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11294 Arc::clone(&metrics),
11295 count_current_thread_stopped_host_dispatch,
11296 ));
11297 let scheduler = executor
11298 .scheduler_idle_lock
11299 .lock()
11300 .unwrap_or_else(std::sync::PoisonError::into_inner);
11301 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
11302 let scheduling_executor = Arc::clone(&executor);
11303 let scheduling = std::thread::spawn(move || {
11304 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
11305 *slot.borrow_mut() = Some(Box::new(move || {
11306 publication_blocked_tx.send(()).unwrap();
11307 }));
11308 });
11309 let scheduler = Arc::clone(&scheduling_executor);
11310 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
11311 scheduler.schedule(runnable);
11312 });
11313 task.detach();
11314 scheduling_executor.schedule(runnable);
11315 });
11316
11317 publication_blocked_rx
11318 .recv_timeout(Duration::from_secs(2))
11319 .expect("dispatch publication must block behind the scheduler-idle mutex");
11320 executor.begin_shutdown();
11321 drop(scheduler);
11322 scheduling.join().unwrap();
11323 executor.wait_until_scheduler_idle();
11324
11325 assert_eq!(
11326 CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
11327 0,
11328 "a publication that reaches the scheduler-idle barrier after stop must not call the host"
11329 );
11330 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
11331 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11332 }
11333
11334 #[test]
11335 fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
11336 use std::{sync::mpsc, time::Duration};
11337
11338 let metrics = Arc::new(RuntimeMetrics::default());
11339 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11340 Arc::clone(&metrics),
11341 accept_current_thread_host_dispatch,
11342 ));
11343 let scheduler = Arc::clone(&executor);
11344 let completed = Arc::new(AtomicBool::new(false));
11345 let completed_task = Arc::clone(&completed);
11346 let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
11347 let shared = async move {
11348 wake_rx.await.expect("wake sender dropped");
11349 }
11350 .boxed()
11351 .shared();
11352 let (runnable, task) = async_task::spawn(
11353 async move {
11354 shared.await;
11355 completed_task.store(true, Ordering::SeqCst);
11356 },
11357 move |runnable| scheduler.schedule(runnable),
11358 );
11359
11360 executor.schedule(runnable);
11361 assert!(
11362 !completed.load(Ordering::SeqCst),
11363 "accepted host dispatch must defer the first poll"
11364 );
11365 executor.drive_host_turn();
11366
11367 let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
11368 std::thread::spawn(move || {
11369 wake_tx.send(()).expect("shared wake receiver dropped");
11370 send_returned_tx.send(()).unwrap();
11371 });
11372 send_returned_rx
11373 .recv_timeout(Duration::from_secs(1))
11374 .expect("Shared wake re-entered its own waker mutex through inline polling");
11375 assert!(
11376 !completed.load(Ordering::SeqCst),
11377 "the wake must remain queued until a fresh host turn"
11378 );
11379
11380 executor.drive_host_turn();
11381 futures::executor::block_on(task);
11382 assert!(completed.load(Ordering::SeqCst));
11383 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11384 }
11385
11386 #[test]
11387 fn current_thread_hostless_wake_is_enqueue_only() {
11388 use std::{sync::mpsc, time::Duration};
11389
11390 struct LockOnSecondPoll {
11391 gate: Arc<Mutex<()>>,
11392 first: bool,
11393 waker: Option<mpsc::Sender<Waker>>,
11394 completed: Arc<AtomicBool>,
11395 }
11396
11397 impl Future for LockOnSecondPoll {
11398 type Output = ();
11399
11400 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
11401 if self.first {
11402 self.first = false;
11403 self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
11404 return Poll::Pending;
11405 }
11406 drop(self.gate.lock().unwrap());
11407 self.completed.store(true, Ordering::SeqCst);
11408 Poll::Ready(())
11409 }
11410 }
11411
11412 let metrics = Arc::new(RuntimeMetrics::default());
11413 let executor = Arc::new(CurrentThreadExecutor::new(metrics));
11414 let gate = Arc::new(Mutex::new(()));
11415 let completed = Arc::new(AtomicBool::new(false));
11416 let (waker_tx, waker_rx) = mpsc::channel();
11417 let scheduler = Arc::clone(&executor);
11418 let (runnable, task) = async_task::spawn(
11419 LockOnSecondPoll {
11420 gate: Arc::clone(&gate),
11421 first: true,
11422 waker: Some(waker_tx),
11423 completed: Arc::clone(&completed),
11424 },
11425 move |runnable| scheduler.schedule(runnable),
11426 );
11427 executor.schedule(runnable);
11428 executor.drive_host_turn();
11429 let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
11430
11431 let (returned_tx, returned_rx) = mpsc::channel();
11432 let wake_gate = Arc::clone(&gate);
11433 let waking = std::thread::spawn(move || {
11434 let _held = wake_gate.lock().unwrap();
11435 waker.wake();
11436 returned_tx.send(()).unwrap();
11437 });
11438 returned_rx
11439 .recv_timeout(Duration::from_secs(1))
11440 .expect("hostless wake must enqueue without polling under the caller's lock");
11441 waking.join().unwrap();
11442 assert!(!completed.load(Ordering::SeqCst));
11443
11444 executor.drive_host_turn();
11445 futures::executor::block_on(task);
11446 assert!(completed.load(Ordering::SeqCst));
11447 }
11448
11449 #[cfg(not(target_family = "wasm"))]
11450 #[test]
11451 fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
11452 use std::sync::mpsc;
11453
11454 let controller = Arc::new(current_thread_controller(
11455 "current-thread-hostless-late-work",
11456 ));
11457 let backend = controller.backend();
11458 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
11459 panic!("configured CurrentThread backend must create a CurrentThread executor");
11460 };
11461 let executor = Arc::clone(executor);
11462 drop(backend);
11463
11464 let (value_tx, value_rx) = oneshot::channel();
11465 let (done_tx, done_rx) = mpsc::channel();
11466 let block_on_controller = Arc::clone(&controller);
11467 let runner = std::thread::spawn(move || {
11468 let mut output = None;
11469 let outcome = {
11470 let mut future = std::pin::pin!(async {
11471 output = Some(value_rx.await.expect("scheduled sender must deliver"));
11472 });
11473 block_on_controller.block_on(future.as_mut())
11474 };
11475 done_tx.send((outcome, output)).unwrap();
11476 });
11477
11478 wait_until(
11479 "the hostless CurrentThread block_on driver to sleep",
11480 || {
11481 executor
11482 .stop
11483 .parkers
11484 .lock()
11485 .unwrap_or_else(std::sync::PoisonError::into_inner)
11486 .iter()
11487 .filter_map(Weak::upgrade)
11488 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11489 },
11490 );
11491
11492 controller
11493 .try_spawn_detached(async move {
11494 value_tx
11495 .send(37usize)
11496 .expect("block_on receiver must remain alive");
11497 })
11498 .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
11499
11500 let (outcome, output) = done_rx
11501 .recv_timeout(Duration::from_secs(2))
11502 .expect("queue publication must wake the sleeping explicit driver");
11503 assert_eq!(outcome, BlockOnOutcome::Completed);
11504 assert_eq!(output, Some(37));
11505 runner.join().unwrap();
11506 controller.shutdown().unwrap();
11507 }
11508
11509 #[cfg(not(target_family = "wasm"))]
11510 #[test]
11511 fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
11512 use std::sync::mpsc;
11513
11514 struct BlockingPoll {
11515 entered: Option<mpsc::Sender<()>>,
11516 release: mpsc::Receiver<()>,
11517 }
11518
11519 impl Future for BlockingPoll {
11520 type Output = ();
11521
11522 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
11523 self.entered.take().unwrap().send(()).unwrap();
11524 self.release.recv().unwrap();
11525 Poll::Ready(())
11526 }
11527 }
11528
11529 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11530 Arc::new(RuntimeMetrics::default()),
11531 reject_current_thread_host_dispatch,
11532 ));
11533 let (value_tx, value_rx) = oneshot::channel();
11534 let (older_done_tx, older_done_rx) = mpsc::channel();
11535 let older_executor = Arc::clone(&executor);
11536 let older = std::thread::spawn(move || {
11537 let mut value = None;
11538 {
11539 let mut future = std::pin::pin!(async {
11540 value = Some(value_rx.await.unwrap());
11541 });
11542 assert_eq!(
11543 older_executor.block_on(future.as_mut()),
11544 BlockOnOutcome::Completed
11545 );
11546 }
11547 older_done_tx.send(value.unwrap()).unwrap();
11548 });
11549 wait_until("the older CurrentThread driver to sleep", || {
11550 executor
11551 .stop
11552 .parkers
11553 .lock()
11554 .unwrap_or_else(std::sync::PoisonError::into_inner)
11555 .iter()
11556 .filter_map(Weak::upgrade)
11557 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11558 });
11559
11560 let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
11561 let (release_newer_tx, release_newer_rx) = mpsc::channel();
11562 let newer_executor = Arc::clone(&executor);
11563 let newer = std::thread::spawn(move || {
11564 let mut future = std::pin::pin!(BlockingPoll {
11565 entered: Some(newer_entered_tx),
11566 release: release_newer_rx
11567 });
11568 assert_eq!(
11569 newer_executor.block_on(future.as_mut()),
11570 BlockOnOutcome::Completed
11571 );
11572 });
11573 newer_entered_rx
11574 .recv_timeout(Duration::from_secs(1))
11575 .expect("the newer driver must be blocked inside its poll");
11576
11577 let scheduler = Arc::clone(&executor);
11578 let (runnable, task) = async_task::spawn(
11579 async move {
11580 value_tx.send(41usize).unwrap();
11581 },
11582 move |runnable| scheduler.schedule(runnable),
11583 );
11584 task.detach();
11585 executor.schedule(runnable);
11586
11587 assert_eq!(
11588 older_done_rx
11589 .recv_timeout(Duration::from_secs(2))
11590 .expect("queue publication must also wake the older sleeping driver"),
11591 41
11592 );
11593 release_newer_tx.send(()).unwrap();
11594 older.join().unwrap();
11595 newer.join().unwrap();
11596 }
11597
11598 #[test]
11599 fn current_thread_shutdown_cancels_host_queued_task() {
11600 use std::sync::mpsc;
11601
11602 struct DropSignal(mpsc::Sender<()>);
11603
11604 impl Drop for DropSignal {
11605 fn drop(&mut self) {
11606 self.0.send(()).unwrap();
11607 }
11608 }
11609
11610 let metrics = Arc::new(RuntimeMetrics::default());
11611 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11612 Arc::clone(&metrics),
11613 accept_current_thread_host_dispatch,
11614 ));
11615 let scheduler = Arc::clone(&executor);
11616 let (dropped_tx, dropped_rx) = mpsc::channel();
11617 let signal = DropSignal(dropped_tx);
11618 let (runnable, task) = async_task::spawn(
11619 async move {
11620 let _signal = signal;
11621 futures::future::pending::<()>().await;
11622 },
11623 move |runnable| scheduler.schedule(runnable),
11624 );
11625
11626 executor.schedule(runnable);
11627 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11628 executor.begin_shutdown();
11629
11630 dropped_rx
11631 .recv_timeout(Duration::from_secs(1))
11632 .expect("shutdown must drop a task queued behind a host turn");
11633 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11634 assert!(
11635 futures::executor::block_on(task.fallible()).is_none(),
11636 "the queued task handle must resolve as cancelled"
11637 );
11638 }
11639
11640 #[test]
11641 fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
11642 reset_current_thread_recovery_host_dispatches();
11643 let metrics = Arc::new(RuntimeMetrics::default());
11644 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11645 Arc::clone(&metrics),
11646 count_current_thread_recovery_host_dispatch,
11647 ));
11648 let scheduler = Arc::clone(&executor);
11649 let completed = Arc::new(AtomicBool::new(false));
11650 let completed_task = Arc::clone(&completed);
11651 let (runnable, task) = async_task::spawn(
11652 async move {
11653 completed_task.store(true, Ordering::SeqCst);
11654 },
11655 move |runnable| scheduler.schedule(runnable),
11656 );
11657
11658 executor.schedule(runnable);
11659 assert_eq!(current_thread_recovery_host_dispatches(), 1);
11660 let lost_dispatch = current_thread_dispatch(&executor);
11661
11662 executor.request_drain_if_queued();
11666 assert_eq!(
11667 current_thread_recovery_host_dispatches(),
11668 2,
11669 "the newly registered host must receive the pending executor dispatch"
11670 );
11671 assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
11672 executor.drive_host_turn_for_dispatch(lost_dispatch);
11673 futures::executor::block_on(task);
11674 assert!(completed.load(Ordering::SeqCst));
11675 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11676
11677 let second_completed = Arc::new(AtomicBool::new(false));
11678 let second_completed_task = Arc::clone(&second_completed);
11679 let second_scheduler = Arc::clone(&executor);
11680 let (second_runnable, second_task) = async_task::spawn(
11681 async move {
11682 second_completed_task.store(true, Ordering::SeqCst);
11683 },
11684 move |runnable| second_scheduler.schedule(runnable),
11685 );
11686 executor.schedule(second_runnable);
11687 assert_eq!(
11688 current_thread_recovery_host_dispatches(),
11689 3,
11690 "work queued after the stale callback must receive a new dispatch"
11691 );
11692 let third_dispatch = current_thread_dispatch(&executor);
11693
11694 executor.drive_host_turn_for_dispatch(lost_dispatch);
11697 assert!(!second_completed.load(Ordering::SeqCst));
11698 executor.drive_host_turn_for_dispatch(third_dispatch);
11699 futures::executor::block_on(second_task);
11700 assert!(second_completed.load(Ordering::SeqCst));
11701
11702 let third_scheduler = Arc::clone(&executor);
11703 let (third_runnable, third_task) =
11704 async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
11705 executor.schedule(third_runnable);
11706 assert_eq!(
11707 current_thread_recovery_host_dispatches(),
11708 4,
11709 "a stale callback must not leave the dispatch latch stuck"
11710 );
11711 executor.drive_host_turn();
11712 futures::executor::block_on(third_task);
11713 }
11714
11715 #[test]
11716 fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
11717 let metrics = Arc::new(RuntimeMetrics::default());
11718 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11719 Arc::clone(&metrics),
11720 accept_current_thread_host_dispatch,
11721 ));
11722 let scheduler = Arc::clone(&executor);
11723 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
11724 scheduler.schedule(runnable)
11725 });
11726
11727 executor.schedule(runnable);
11728 let failed_dispatch = current_thread_dispatch(&executor);
11729 executor.cancel_host_dispatch_for_test(failed_dispatch);
11730 let recovery_dispatch = current_thread_dispatch(&executor);
11731 assert_eq!(
11732 executor.recovery_dispatch.load(Ordering::Acquire),
11733 recovery_dispatch
11734 );
11735
11736 executor.request_drain_if_queued();
11737 let new_host_dispatch = current_thread_dispatch(&executor);
11738 assert_eq!(new_host_dispatch, recovery_dispatch);
11739 assert_eq!(
11740 executor.recovery_dispatch.load(Ordering::Acquire),
11741 recovery_dispatch
11742 );
11743 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11744
11745 executor.drive_host_turn_for_dispatch(new_host_dispatch);
11746 assert_eq!(futures::executor::block_on(task), 17);
11747 }
11748
11749 #[cfg(not(target_family = "wasm"))]
11750 #[test]
11751 fn current_thread_host_admission_serializes_dispatch_publication() {
11752 use std::sync::mpsc;
11753
11754 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11755 let metrics = Arc::new(RuntimeMetrics::default());
11756 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11757 Arc::clone(&metrics),
11758 count_current_thread_admission_host_dispatch,
11759 ));
11760 let first_scheduler = Arc::clone(&executor);
11761 let (first_runnable, first_task) =
11762 async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
11763 first_task.detach();
11764 executor.schedule(first_runnable);
11765 let dispatch = current_thread_dispatch(&executor);
11766 assert_eq!(
11767 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
11768 1
11769 );
11770
11771 let (claimed_tx, claimed_rx) = mpsc::channel();
11772 let (release_tx, release_rx) = mpsc::channel();
11773 let driver_executor = Arc::clone(&executor);
11774 let driver = std::thread::spawn(move || {
11775 CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
11776 *slot.borrow_mut() = Some(Box::new(move || {
11777 claimed_tx.send(()).unwrap();
11778 release_rx.recv().unwrap();
11779 }));
11780 });
11781 driver_executor.drive_host_turn_for_dispatch(dispatch);
11782 });
11783 claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11784
11785 let second_ran = Arc::new(AtomicBool::new(false));
11786 let second_ran_task = Arc::clone(&second_ran);
11787 let scheduling_executor = Arc::clone(&executor);
11788 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
11789 let (scheduled_tx, scheduled_rx) = mpsc::channel();
11790 let scheduling = std::thread::spawn(move || {
11791 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
11792 *slot.borrow_mut() = Some(Box::new(move || {
11793 publication_blocked_tx.send(()).unwrap();
11794 }));
11795 });
11796 let scheduler = Arc::clone(&scheduling_executor);
11797 let (runnable, task) = async_task::spawn(
11798 async move {
11799 second_ran_task.store(true, Ordering::SeqCst);
11800 },
11801 move |runnable| scheduler.schedule(runnable),
11802 );
11803 task.detach();
11804 scheduling_executor.schedule(runnable);
11805 scheduled_tx.send(()).unwrap();
11806 });
11807
11808 publication_blocked_rx
11809 .recv_timeout(Duration::from_secs(2))
11810 .unwrap();
11811 assert!(
11812 matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
11813 "dispatch publication must wait until host admission publishes its draining role"
11814 );
11815 assert_eq!(
11816 CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
11817 1,
11818 "the admission gap must not publish a duplicate host dispatch"
11819 );
11820
11821 release_tx.send(()).unwrap();
11822 scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11823 scheduling.join().unwrap();
11824 driver.join().unwrap();
11825 if !second_ran.load(Ordering::SeqCst) {
11826 executor.drive_host_turn();
11827 }
11828 assert!(second_ran.load(Ordering::SeqCst));
11829 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11830 }
11831
11832 #[test]
11833 fn current_thread_host_turn_yields_after_runnable_budget() {
11834 struct SelfWakingFuture {
11835 remaining: usize,
11836 }
11837
11838 impl Future for SelfWakingFuture {
11839 type Output = ();
11840
11841 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
11842 if self.remaining == 0 {
11843 return Poll::Ready(());
11844 }
11845 self.remaining -= 1;
11846 cx.waker().wake_by_ref();
11847 Poll::Pending
11848 }
11849 }
11850
11851 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11852 let metrics = Arc::new(RuntimeMetrics::default());
11853 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11854 Arc::clone(&metrics),
11855 count_current_thread_budget_host_dispatch,
11856 ));
11857 let scheduler = Arc::clone(&executor);
11858 let (runnable, task) = async_task::spawn(
11859 SelfWakingFuture {
11860 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
11861 },
11862 move |runnable| scheduler.schedule(runnable),
11863 );
11864
11865 executor.schedule(runnable);
11866 executor.drive_host_turn();
11867
11868 assert_eq!(
11869 metrics.runnable_polls.load(Ordering::Relaxed),
11870 CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
11871 );
11872 assert_eq!(
11873 CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
11874 2,
11875 "remaining hot work must continue through a fresh host turn"
11876 );
11877
11878 executor.drive_host_turn();
11879 futures::executor::block_on(task);
11880 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11881 }
11882
11883 #[cfg(not(target_family = "wasm"))]
11884 #[test]
11885 fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
11886 const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
11887
11888 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11889 let metrics = Arc::new(RuntimeMetrics::default());
11890 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11891 Arc::clone(&metrics),
11892 count_current_thread_blocking_budget_host_dispatch,
11893 ));
11894 let work = GenerationWork::new();
11895 let observed = Arc::new(Mutex::new(Vec::new()));
11896
11897 let task_executor = Arc::clone(&executor);
11898 let wake_executor = Arc::clone(&executor);
11899 let producer_executor = Arc::clone(&executor);
11900 let producer_work = Arc::clone(&work);
11901 let producer_observed = Arc::clone(&observed);
11902 let outer_registration = work
11903 .try_register_work()
11904 .expect("the outer blocking job must be accepted");
11905 let (runnable, task) = async_task::spawn(
11906 async move {
11907 let queued = task_executor
11908 .schedule_blocking(
11909 move || {
11910 std::thread::spawn(move || {
11911 (0..QUEUED_BLOCKING_JOBS)
11912 .map(|index| {
11913 let registration = producer_work
11914 .try_register_work()
11915 .expect("queued blocking work must be accepted");
11916 let observed = Arc::clone(&producer_observed);
11917 producer_executor.schedule_blocking(
11918 move || {
11919 observed
11920 .lock()
11921 .unwrap_or_else(std::sync::PoisonError::into_inner)
11922 .push(index);
11923 },
11924 registration,
11925 )
11926 })
11927 .collect::<Vec<_>>()
11928 })
11929 .join()
11930 .expect("the blocking producer must finish")
11931 },
11932 outer_registration,
11933 )
11934 .await
11935 .expect("the inline blocking job must finish");
11936 for handle in queued {
11937 handle.await.expect("queued blocking work must finish");
11938 }
11939 },
11940 move |runnable| wake_executor.schedule(runnable),
11941 );
11942
11943 executor.schedule(runnable);
11944 executor.drive_host_turn();
11945
11946 let first_turn = observed
11947 .lock()
11948 .unwrap_or_else(std::sync::PoisonError::into_inner)
11949 .clone();
11950 assert!(
11951 !first_turn.is_empty(),
11952 "the first host turn must service queued blocking work"
11953 );
11954 assert!(
11955 first_turn.len() < QUEUED_BLOCKING_JOBS,
11956 "inline lane release must not drain the entire blocking FIFO in one host turn"
11957 );
11958 assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
11959 assert_eq!(
11960 CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
11961 2,
11962 "remaining blocking work must continue through a fresh host turn"
11963 );
11964
11965 let mut continuation_turns = 0;
11966 while observed
11967 .lock()
11968 .unwrap_or_else(std::sync::PoisonError::into_inner)
11969 .len()
11970 < QUEUED_BLOCKING_JOBS
11971 {
11972 continuation_turns += 1;
11973 assert!(
11974 continuation_turns <= QUEUED_BLOCKING_JOBS,
11975 "queued blocking work failed to make bounded host-turn progress"
11976 );
11977 executor.drive_host_turn();
11978 }
11979 executor.drive_host_turn();
11980 futures::executor::block_on(task);
11981
11982 assert_eq!(
11983 *observed
11984 .lock()
11985 .unwrap_or_else(std::sync::PoisonError::into_inner),
11986 (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
11987 "blocking jobs must preserve FIFO order across host turns"
11988 );
11989 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11990 }
11991
11992 #[cfg(not(target_family = "wasm"))]
11993 #[test]
11994 fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
11995 use std::sync::mpsc;
11996
11997 struct SelfWakingFuture {
11998 remaining: usize,
11999 }
12000
12001 impl Future for SelfWakingFuture {
12002 type Output = ();
12003
12004 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12005 if self.remaining == 0 {
12006 return Poll::Ready(());
12007 }
12008 self.remaining -= 1;
12009 cx.waker().wake_by_ref();
12010 Poll::Pending
12011 }
12012 }
12013
12014 let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
12015 let old_generation = controller.backend().generation();
12016 controller
12017 .try_spawn_detached(SelfWakingFuture {
12018 remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12019 })
12020 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
12021 let dispatch = controller_current_thread_dispatch(&controller);
12022
12023 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12024 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12025 let driver_controller = Arc::clone(&controller);
12026 let driver = std::thread::spawn(move || {
12027 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12028 *slot.borrow_mut() = Some(Box::new(move || {
12029 dispatch_entered_tx.send(()).unwrap();
12030 release_dispatch_rx.recv().unwrap();
12031 }));
12032 });
12033 driver_controller.drive_current_thread_dispatch(dispatch, false);
12034 });
12035 dispatch_entered_rx
12036 .recv_timeout(Duration::from_secs(2))
12037 .expect("the bounded host turn must begin publishing its continuation");
12038
12039 let (shutdown_tx, shutdown_rx) = mpsc::channel();
12040 let shutdown_controller = Arc::clone(&controller);
12041 let shutdown = std::thread::spawn(move || {
12042 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12043 });
12044 wait_until("CurrentThread shutdown to leave Running", || {
12045 let state = controller
12046 .state
12047 .lock()
12048 .unwrap_or_else(std::sync::PoisonError::into_inner);
12049 !matches!(
12050 &state.lifecycle,
12051 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12052 )
12053 });
12054
12055 let (restart_tx, restart_rx) = mpsc::channel();
12056 let restart_controller = Arc::clone(&controller);
12057 let restart = std::thread::spawn(move || {
12058 let result = restart_controller
12059 .start()
12060 .map(|()| restart_controller.backend().generation());
12061 restart_tx.send(result).unwrap();
12062 });
12063
12064 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12065 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12066 let shutdown_overlapped = early_shutdown.is_some();
12067 let restart_overlapped = early_restart.is_some();
12068 release_dispatch_tx.send(()).unwrap();
12069 driver.join().unwrap();
12070
12071 let shutdown_result = early_shutdown.unwrap_or_else(|| {
12072 shutdown_rx
12073 .recv_timeout(Duration::from_secs(2))
12074 .expect("shutdown must finish after continuation publication retires")
12075 });
12076 shutdown_result.unwrap();
12077 let new_generation = early_restart.unwrap_or_else(|| {
12078 restart_rx
12079 .recv_timeout(Duration::from_secs(2))
12080 .expect("restart must follow continuation publication and shutdown")
12081 });
12082 let new_generation = new_generation.unwrap();
12083 shutdown.join().unwrap();
12084 restart.join().unwrap();
12085
12086 assert!(
12087 !shutdown_overlapped,
12088 "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
12089 );
12090 assert!(
12091 !restart_overlapped,
12092 "restart must not overlap an old-generation continuation dispatch"
12093 );
12094 assert_ne!(new_generation, old_generation);
12095 controller.shutdown().unwrap();
12096 }
12097
12098 #[cfg(not(target_family = "wasm"))]
12099 #[test]
12100 fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
12101 use std::sync::mpsc;
12102
12103 let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
12104 let old_generation = controller.backend().generation();
12105 let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
12106 let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12107 let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12108 let submit_controller = Arc::clone(&controller);
12109 let reentrant_controller = Arc::clone(&controller);
12110 let submitter = std::thread::spawn(move || {
12111 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12112 *slot.borrow_mut() = Some(Box::new(move || {
12113 reentrant_shutdown_tx
12114 .send(reentrant_controller.shutdown())
12115 .unwrap();
12116 dispatch_entered_tx.send(()).unwrap();
12117 release_dispatch_rx.recv().unwrap();
12118 }));
12119 });
12120 submit_controller
12121 .try_spawn_detached(std::future::pending::<()>())
12122 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
12123 });
12124
12125 let reentrant_shutdown = reentrant_shutdown_rx
12126 .recv_timeout(Duration::from_secs(2))
12127 .expect("dispatch publication must not self-deadlock on reentrant shutdown");
12128 dispatch_entered_rx
12129 .recv_timeout(Duration::from_secs(2))
12130 .expect("the initial host dispatch publication must reach the injected pause");
12131
12132 let (shutdown_tx, shutdown_rx) = mpsc::channel();
12133 let shutdown_controller = Arc::clone(&controller);
12134 let shutdown = std::thread::spawn(move || {
12135 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12136 });
12137 wait_until("CurrentThread shutdown to leave Running", || {
12138 let state = controller
12139 .state
12140 .lock()
12141 .unwrap_or_else(std::sync::PoisonError::into_inner);
12142 !matches!(
12143 &state.lifecycle,
12144 RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12145 )
12146 });
12147
12148 let (restart_tx, restart_rx) = mpsc::channel();
12149 let restart_controller = Arc::clone(&controller);
12150 let restart = std::thread::spawn(move || {
12151 let result = restart_controller
12152 .start()
12153 .map(|()| restart_controller.backend().generation());
12154 restart_tx.send(result).unwrap();
12155 });
12156
12157 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12158 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12159 let shutdown_overlapped = early_shutdown.is_some();
12160 let restart_overlapped = early_restart.is_some();
12161 release_dispatch_tx.send(()).unwrap();
12162 submitter.join().unwrap();
12163
12164 let shutdown_result = early_shutdown.unwrap_or_else(|| {
12165 shutdown_rx
12166 .recv_timeout(Duration::from_secs(2))
12167 .expect("shutdown must finish after initial dispatch publication retires")
12168 });
12169 shutdown_result.unwrap();
12170 let new_generation = early_restart.unwrap_or_else(|| {
12171 restart_rx
12172 .recv_timeout(Duration::from_secs(2))
12173 .expect("restart must follow initial dispatch publication and shutdown")
12174 });
12175 let new_generation = new_generation.unwrap();
12176 shutdown.join().unwrap();
12177 restart.join().unwrap();
12178
12179 let reentrant_error = reentrant_shutdown
12180 .expect_err("dispatch publication must be generation-scoped scheduler work");
12181 assert!(
12182 reentrant_error
12183 .to_string()
12184 .contains("work running on that runtime")
12185 );
12186 assert!(
12187 !shutdown_overlapped,
12188 "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
12189 );
12190 assert!(
12191 !restart_overlapped,
12192 "restart must not overlap an old-generation host dispatch call"
12193 );
12194 assert_ne!(new_generation, old_generation);
12195 controller.shutdown().unwrap();
12196 }
12197
12198 #[test]
12199 fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
12200 CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12201 .lock()
12202 .unwrap()
12203 .clear();
12204 CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12205 let metrics = Arc::new(RuntimeMetrics::default());
12206 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12207 Arc::clone(&metrics),
12208 panic_once_current_thread_host_dispatch,
12209 ));
12210 let scheduler = Arc::clone(&executor);
12211 let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12212 scheduler.schedule(runnable)
12213 });
12214
12215 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
12216 assert!(
12217 publication.is_err(),
12218 "the injected host dispatch panic must unwind"
12219 );
12220 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12221 {
12222 let scheduler = executor
12223 .scheduler_idle_lock
12224 .lock()
12225 .unwrap_or_else(std::sync::PoisonError::into_inner);
12226 assert_eq!(scheduler.active_dispatch_calls, 0);
12227 assert!(scheduler.dispatch_publications.is_empty());
12228 }
12229
12230 executor.request_drain_if_queued();
12231
12232 assert_ne!(dispatch, 0);
12233 assert_eq!(
12234 *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12235 .lock()
12236 .unwrap(),
12237 vec![dispatch, dispatch],
12238 "a later host registration must retry the same pending capability"
12239 );
12240 {
12241 let scheduler = executor
12242 .scheduler_idle_lock
12243 .lock()
12244 .unwrap_or_else(std::sync::PoisonError::into_inner);
12245 assert_eq!(scheduler.active_dispatch_calls, 0);
12246 assert!(scheduler.dispatch_publications.is_empty());
12247 }
12248 executor.drive_host_turn_for_dispatch(dispatch);
12249 assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
12250 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12251 }
12252
12253 #[cfg(not(target_family = "wasm"))]
12254 #[test]
12255 fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
12256 use std::sync::mpsc;
12257
12258 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12259 .lock()
12260 .unwrap()
12261 .clear();
12262 CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12263 let metrics = Arc::new(RuntimeMetrics::default());
12264 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12265 Arc::clone(&metrics),
12266 panic_once_current_thread_armed_republish_host_dispatch,
12267 ));
12268 let scheduler = Arc::clone(&executor);
12269 let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
12270 scheduler.schedule(runnable)
12271 });
12272
12273 let (called_tx, called_rx) = mpsc::channel();
12274 let (release_tx, release_rx) = mpsc::channel();
12275 let publishing_executor = Arc::clone(&executor);
12276 let publication = std::thread::spawn(move || {
12277 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12278 *slot.borrow_mut() = Some(Box::new(move || {
12279 called_tx.send(()).unwrap();
12280 release_rx.recv().unwrap();
12281 }));
12282 });
12283 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12284 });
12285 called_rx
12286 .recv_timeout(Duration::from_secs(2))
12287 .expect("the first publication must pause before calling the host dispatcher");
12288
12289 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12290 executor.request_drain_if_queued();
12291 let publication_state = {
12292 let scheduler = executor
12293 .scheduler_idle_lock
12294 .lock()
12295 .unwrap_or_else(std::sync::PoisonError::into_inner);
12296 (
12297 scheduler.active_dispatch_calls,
12298 scheduler.dispatch_publications.len(),
12299 scheduler.dispatch_publications[0].dispatch,
12300 scheduler.dispatch_publications[0].republish_requested,
12301 )
12302 };
12303 release_tx.send(()).unwrap();
12304
12305 let publication = publication.join().unwrap();
12306 assert!(
12307 publication.is_err(),
12308 "the original host-dispatch panic must resume"
12309 );
12310 assert_ne!(dispatch, 0);
12311 assert_eq!(publication_state, (1, 1, dispatch, true));
12312 assert_eq!(
12313 *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12314 .lock()
12315 .unwrap(),
12316 vec![dispatch, dispatch],
12317 "the armed request must retry automatically before the original panic resumes"
12318 );
12319 {
12320 let scheduler = executor
12321 .scheduler_idle_lock
12322 .lock()
12323 .unwrap_or_else(std::sync::PoisonError::into_inner);
12324 assert_eq!(scheduler.active_dispatch_calls, 0);
12325 assert!(scheduler.dispatch_publications.is_empty());
12326 }
12327 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
12328 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12329
12330 executor.drive_host_turn_for_dispatch(dispatch);
12331 assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
12332 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12333 }
12334
12335 #[cfg(not(target_family = "wasm"))]
12336 #[test]
12337 fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
12338 use std::sync::mpsc;
12339
12340 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12341 .lock()
12342 .unwrap()
12343 .clear();
12344 CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
12345 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12346 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12347 {
12348 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12349 .state
12350 .lock()
12351 .unwrap_or_else(std::sync::PoisonError::into_inner);
12352 assert!(state.entries.is_empty());
12353 assert!(state.dispatches.is_empty());
12354 }
12355 let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12356 .register(Arc::new(FailDuringAuthoritativePanicPublication));
12357 let metrics = Arc::new(RuntimeMetrics::default());
12358 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12359 Arc::clone(&metrics),
12360 dispatch_authoritative_panic_current_thread_tasks,
12361 &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
12362 ));
12363 let scheduler = Arc::clone(&executor);
12364 let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
12365 scheduler.schedule(runnable)
12366 });
12367
12368 let (called_tx, called_rx) = mpsc::channel();
12369 let (release_tx, release_rx) = mpsc::channel();
12370 let publishing_executor = Arc::clone(&executor);
12371 let publication = std::thread::spawn(move || {
12372 CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12373 *slot.borrow_mut() = Some(Box::new(move || {
12374 called_tx.send(()).unwrap();
12375 release_rx.recv().unwrap();
12376 }));
12377 });
12378 catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12379 });
12380 called_rx
12381 .recv_timeout(Duration::from_secs(2))
12382 .expect("the first publication must pause before raising its panic");
12383
12384 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12385 executor.request_drain_if_queued();
12386 release_tx.send(()).unwrap();
12387
12388 let payload = publication
12389 .join()
12390 .unwrap()
12391 .expect_err("the first host-dispatch panic must resume");
12392 assert!(
12393 payload.is::<FirstHostileCurrentThreadPanic>(),
12394 "the first panic must remain authoritative over nested recovery"
12395 );
12396 assert_eq!(
12397 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12398 1,
12399 "the later hostile payload must be discarded under containment"
12400 );
12401 assert_eq!(
12402 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12403 0,
12404 "the authoritative payload must remain owned by the resumed unwind"
12405 );
12406 discard_panic_payload(payload);
12407 assert_eq!(
12408 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12409 1
12410 );
12411
12412 let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12413 .lock()
12414 .unwrap()
12415 .clone();
12416 assert_eq!(dispatches.len(), 3);
12417 let replacement_dispatch = dispatches[2];
12418 assert_eq!(dispatches[0], failed_dispatch);
12419 assert_eq!(dispatches[1], failed_dispatch);
12420 assert_ne!(replacement_dispatch, failed_dispatch);
12421 assert_eq!(
12422 executor.dispatch_pending.load(Ordering::Acquire),
12423 replacement_dispatch
12424 );
12425 assert_eq!(
12426 executor.recovery_dispatch.load(Ordering::Acquire),
12427 replacement_dispatch
12428 );
12429 {
12430 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12431 .state
12432 .lock()
12433 .unwrap_or_else(std::sync::PoisonError::into_inner);
12434 assert!(
12435 !state.dispatches.contains_key(&failed_dispatch),
12436 "nested recovery unwind must retire the exact failed epoch"
12437 );
12438 assert!(!state.dispatches.contains_key(&replacement_dispatch));
12439 }
12440
12441 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
12442 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
12443 let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12444 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
12445 executor.request_drain_if_queued();
12446
12447 assert_eq!(
12448 *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12449 .lock()
12450 .unwrap(),
12451 vec![
12452 failed_dispatch,
12453 failed_dispatch,
12454 replacement_dispatch,
12455 replacement_dispatch
12456 ],
12457 "retry must retain the replacement reserved before the nested panic"
12458 );
12459 let delivery = healthy.deliveries()[0];
12460 assert_eq!(
12461 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
12462 Some(replacement_dispatch)
12463 );
12464 let mut host_turn = executor
12465 .try_admit_host_turn(replacement_dispatch)
12466 .expect("the retried replacement delivery must be admitted");
12467 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
12468 host_turn.drive();
12469 assert!(
12470 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12471 .finish_delivery(delivery, false)
12472 .failed_dispatches
12473 .is_empty()
12474 );
12475 assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
12476 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12477
12478 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
12479 let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12480 .state
12481 .lock()
12482 .unwrap_or_else(std::sync::PoisonError::into_inner);
12483 assert!(state.entries.is_empty());
12484 assert!(state.dispatches.is_empty());
12485 }
12486
12487 #[cfg(not(target_family = "wasm"))]
12488 #[test]
12489 fn current_thread_shutdown_suppresses_queued_republish() {
12490 use std::sync::mpsc;
12491
12492 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12493 let metrics = Arc::new(RuntimeMetrics::default());
12494 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12495 Arc::clone(&metrics),
12496 count_current_thread_shutdown_republish_host_dispatch,
12497 ));
12498 let scheduler = Arc::clone(&executor);
12499 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
12500 scheduler.schedule(runnable)
12501 });
12502
12503 let (returned_tx, returned_rx) = mpsc::channel();
12504 let (release_tx, release_rx) = mpsc::channel();
12505 let publishing_executor = Arc::clone(&executor);
12506 let publication = std::thread::spawn(move || {
12507 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12508 *slot.borrow_mut() = Some(Box::new(move || {
12509 returned_tx.send(()).unwrap();
12510 release_rx.recv().unwrap();
12511 }));
12512 });
12513 publishing_executor.schedule(runnable);
12514 });
12515 returned_rx
12516 .recv_timeout(Duration::from_secs(2))
12517 .expect("the initial publication must reach its completion barrier");
12518
12519 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12520 executor.request_drain_if_queued();
12521 let publication_state = {
12522 let scheduler = executor
12523 .scheduler_idle_lock
12524 .lock()
12525 .unwrap_or_else(std::sync::PoisonError::into_inner);
12526 (
12527 scheduler.active_dispatch_calls,
12528 scheduler.dispatch_publications.len(),
12529 scheduler.dispatch_publications[0].dispatch,
12530 scheduler.dispatch_publications[0].republish_requested,
12531 )
12532 };
12533 executor.begin_shutdown();
12534 release_tx.send(()).unwrap();
12535 publication.join().unwrap();
12536 executor.wait_until_scheduler_idle();
12537
12538 assert_ne!(dispatch, 0);
12539 assert_eq!(publication_state, (1, 1, dispatch, true));
12540 assert_eq!(
12541 CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
12542 1
12543 );
12544 {
12545 let scheduler = executor
12546 .scheduler_idle_lock
12547 .lock()
12548 .unwrap_or_else(std::sync::PoisonError::into_inner);
12549 assert_eq!(scheduler.active_dispatch_calls, 0);
12550 assert!(scheduler.dispatch_publications.is_empty());
12551 }
12552 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
12553 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
12554 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12555 assert_eq!(futures::executor::block_on(task.fallible()), None);
12556 }
12557
12558 #[cfg(not(target_family = "wasm"))]
12559 #[test]
12560 fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
12561 use std::sync::mpsc;
12562
12563 CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
12564 .lock()
12565 .unwrap()
12566 .clear();
12567 let metrics = Arc::new(RuntimeMetrics::default());
12568 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12569 Arc::clone(&metrics),
12570 record_current_thread_republish_rearm_dispatch,
12571 ));
12572 let scheduler = Arc::clone(&executor);
12573 let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
12574 scheduler.schedule(runnable)
12575 });
12576
12577 let (first_returned_tx, first_returned_rx) = mpsc::channel();
12578 let (release_first_tx, release_first_rx) = mpsc::channel();
12579 let (second_returned_tx, second_returned_rx) = mpsc::channel();
12580 let (release_second_tx, release_second_rx) = mpsc::channel();
12581 let publishing_executor = Arc::clone(&executor);
12582 let publication = std::thread::spawn(move || {
12583 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12584 *slot.borrow_mut() = Some(Box::new(move || {
12585 first_returned_tx.send(()).unwrap();
12586 release_first_rx.recv().unwrap();
12587 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12588 *slot.borrow_mut() = Some(Box::new(move || {
12589 second_returned_tx.send(()).unwrap();
12590 release_second_rx.recv().unwrap();
12591 }));
12592 });
12593 }));
12594 });
12595 publishing_executor.schedule(runnable);
12596 });
12597 first_returned_rx
12598 .recv_timeout(Duration::from_secs(2))
12599 .expect("the first publication must reach its completion barrier");
12600
12601 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12602 executor.request_drain_if_queued();
12603 executor.request_drain_if_queued();
12604 let first_publication_state = {
12605 let scheduler = executor
12606 .scheduler_idle_lock
12607 .lock()
12608 .unwrap_or_else(std::sync::PoisonError::into_inner);
12609 (
12610 scheduler.active_dispatch_calls,
12611 scheduler.dispatch_publications.len(),
12612 scheduler.dispatch_publications[0].dispatch,
12613 scheduler.dispatch_publications[0].republish_requested,
12614 )
12615 };
12616 release_first_tx.send(()).unwrap();
12617 second_returned_rx
12618 .recv_timeout(Duration::from_secs(2))
12619 .expect("the second publication must reach its completion barrier");
12620
12621 executor.request_drain_if_queued();
12622 executor.request_drain_if_queued();
12623 let second_publication_state = {
12624 let scheduler = executor
12625 .scheduler_idle_lock
12626 .lock()
12627 .unwrap_or_else(std::sync::PoisonError::into_inner);
12628 (
12629 scheduler.active_dispatch_calls,
12630 scheduler.dispatch_publications.len(),
12631 scheduler.dispatch_publications[0].dispatch,
12632 scheduler.dispatch_publications[0].republish_requested,
12633 )
12634 };
12635 release_second_tx.send(()).unwrap();
12636 publication.join().unwrap();
12637
12638 assert_ne!(dispatch, 0);
12639 assert_eq!(first_publication_state, (1, 1, dispatch, true));
12640 assert_eq!(second_publication_state, (1, 1, dispatch, true));
12641 assert_eq!(
12642 *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
12643 vec![dispatch, dispatch, dispatch],
12644 "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
12645 );
12646 {
12647 let scheduler = executor
12648 .scheduler_idle_lock
12649 .lock()
12650 .unwrap_or_else(std::sync::PoisonError::into_inner);
12651 assert_eq!(scheduler.active_dispatch_calls, 0);
12652 assert!(scheduler.dispatch_publications.is_empty());
12653 }
12654 executor.drive_host_turn_for_dispatch(dispatch);
12655 assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
12656 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12657 }
12658
12659 #[cfg(not(target_family = "wasm"))]
12660 #[test]
12661 fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
12662 use std::sync::mpsc;
12663
12664 CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12665 .lock()
12666 .unwrap()
12667 .clear();
12668 let metrics = Arc::new(RuntimeMetrics::default());
12669 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12670 Arc::clone(&metrics),
12671 reject_initial_current_thread_publication_once,
12672 ));
12673 let scheduler = Arc::clone(&executor);
12674 let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
12675 scheduler.schedule(runnable)
12676 });
12677
12678 let (returned_tx, returned_rx) = mpsc::channel();
12679 let (release_tx, release_rx) = mpsc::channel();
12680 let publishing_executor = Arc::clone(&executor);
12681 let publication = std::thread::spawn(move || {
12682 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12683 *slot.borrow_mut() = Some(Box::new(move || {
12684 returned_tx.send(()).unwrap();
12685 release_rx.recv().unwrap();
12686 }));
12687 });
12688 publishing_executor.schedule(runnable);
12689 });
12690 returned_rx
12691 .recv_timeout(Duration::from_secs(2))
12692 .expect("the unavailable initial publication must reach its completion barrier");
12693
12694 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12695 executor.request_drain_if_queued();
12696 let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12697 .lock()
12698 .unwrap()
12699 .len();
12700
12701 release_tx.send(()).unwrap();
12702 publication.join().unwrap();
12703
12704 let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12705 .lock()
12706 .unwrap()
12707 .clone();
12708 assert_ne!(dispatch, 0);
12709 assert_eq!(
12710 attempts_while_unavailable_is_paused, 1,
12711 "a racing host request must join the in-flight publication instead of retiring it independently"
12712 );
12713 assert_eq!(
12714 dispatches,
12715 vec![dispatch, dispatch],
12716 "the publication owner must retry the same internal capability after the new host joins"
12717 );
12718 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
12719 executor.drive_host_turn_for_dispatch(dispatch);
12720 assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
12721 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12722 }
12723
12724 #[cfg(not(target_family = "wasm"))]
12725 #[test]
12726 fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
12727 use std::sync::mpsc;
12728
12729 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12730 .lock()
12731 .unwrap()
12732 .clear();
12733 let metrics = Arc::new(RuntimeMetrics::default());
12734 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12735 Arc::clone(&metrics),
12736 reject_current_thread_replacement_publication_once,
12737 ));
12738 let scheduler = Arc::clone(&executor);
12739 let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
12740 scheduler.schedule(runnable)
12741 });
12742 executor.schedule(runnable);
12743 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12744 assert_ne!(failed_dispatch, 0);
12745
12746 let (returned_tx, returned_rx) = mpsc::channel();
12747 let (release_tx, release_rx) = mpsc::channel();
12748 let cancelling_executor = Arc::clone(&executor);
12749 let cancellation = std::thread::spawn(move || {
12750 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12751 *slot.borrow_mut() = Some(Box::new(move || {
12752 returned_tx.send(()).unwrap();
12753 release_rx.recv().unwrap();
12754 }));
12755 });
12756 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
12757 });
12758 returned_rx
12759 .recv_timeout(Duration::from_secs(2))
12760 .expect("the unavailable replacement publication must reach its completion barrier");
12761
12762 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12763 executor.request_drain_if_queued();
12764 let attempts_while_unavailable_is_paused =
12765 CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12766 .lock()
12767 .unwrap()
12768 .len();
12769
12770 release_tx.send(()).unwrap();
12771 cancellation.join().unwrap();
12772
12773 let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12774 .lock()
12775 .unwrap()
12776 .clone();
12777 assert_ne!(replacement_dispatch, 0);
12778 assert_ne!(replacement_dispatch, failed_dispatch);
12779 assert_eq!(
12780 attempts_while_unavailable_is_paused, 2,
12781 "a racing host request must join the in-flight replacement publication"
12782 );
12783 assert_eq!(
12784 dispatches,
12785 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
12786 "republication must reuse the sole replacement capability instead of allocating another"
12787 );
12788 assert_eq!(
12789 executor.dispatch_pending.load(Ordering::Acquire),
12790 replacement_dispatch
12791 );
12792 assert_eq!(
12793 executor.recovery_dispatch.load(Ordering::Acquire),
12794 replacement_dispatch
12795 );
12796 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12797
12798 executor.drive_host_turn_for_dispatch(replacement_dispatch);
12799 assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
12800 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
12801 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12802 }
12803
12804 #[cfg(not(target_family = "wasm"))]
12805 #[test]
12806 fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
12807 use std::sync::mpsc;
12808
12809 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12810 .lock()
12811 .unwrap()
12812 .clear();
12813 {
12814 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12815 .state
12816 .lock()
12817 .unwrap_or_else(std::sync::PoisonError::into_inner);
12818 assert!(state.entries.is_empty());
12819 assert!(state.dispatches.is_empty());
12820 }
12821 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
12822 let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12823 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
12824 let metrics = Arc::new(RuntimeMetrics::default());
12825 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12826 Arc::clone(&metrics),
12827 fail_current_thread_replacement_publication_once,
12828 &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
12829 ));
12830 let scheduler = Arc::clone(&executor);
12831 let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
12832 scheduler.schedule(runnable)
12833 });
12834 executor.schedule(runnable);
12835 let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12836 assert_ne!(failed_dispatch, 0);
12837 let initial_delivery = initial.deliveries()[0];
12838 let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12839 .finish_delivery(initial_delivery, true);
12840 let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
12841 panic!("the initial accepted delivery must report one exact failure");
12842 };
12843 assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
12844 let failed_dispatch_failure = *failed_dispatch_failure;
12845 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
12846 let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12847 .register(Arc::new(FailDuringReplacementRepublishPublication));
12848
12849 let (returned_tx, returned_rx) = mpsc::channel();
12850 let (release_tx, release_rx) = mpsc::channel();
12851 let cancelling_executor = Arc::clone(&executor);
12852 let cancellation = std::thread::spawn(move || {
12853 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12854 *slot.borrow_mut() = Some(Box::new(move || {
12855 returned_tx.send(()).unwrap();
12856 release_rx.recv().unwrap();
12857 }));
12858 });
12859 cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
12860 });
12861 returned_rx
12862 .recv_timeout(Duration::from_secs(2))
12863 .expect("the failed replacement publication must reach its completion barrier");
12864
12865 let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12866 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
12867 let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12868 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
12869 executor.request_drain_if_queued();
12870 let publication_state = {
12871 let scheduler = executor
12872 .scheduler_idle_lock
12873 .lock()
12874 .unwrap_or_else(std::sync::PoisonError::into_inner);
12875 (
12876 scheduler.active_dispatch_calls,
12877 scheduler.dispatch_publications.len(),
12878 scheduler.dispatch_publications[0].dispatch,
12879 scheduler.dispatch_publications[0].republish_requested,
12880 )
12881 };
12882 release_tx.send(()).unwrap();
12883 cancellation.join().unwrap();
12884
12885 assert_ne!(replacement_dispatch, 0);
12886 assert_ne!(replacement_dispatch, failed_dispatch);
12887 assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
12888 assert_eq!(
12889 *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12890 .lock()
12891 .unwrap(),
12892 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
12893 "the newly armed host request must receive the exact replacement before cancellation"
12894 );
12895 assert_eq!(
12896 executor.dispatch_pending.load(Ordering::Acquire),
12897 replacement_dispatch
12898 );
12899 assert_eq!(
12900 executor.recovery_dispatch.load(Ordering::Acquire),
12901 replacement_dispatch
12902 );
12903 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12904
12905 let delivery = healthy.deliveries()[0];
12906 assert_eq!(delivery.dispatch, replacement_dispatch);
12907 assert_eq!(
12908 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12909 .claim_delivery(delivery.capability()),
12910 Some(replacement_dispatch)
12911 );
12912 let mut host_turn = executor
12913 .try_admit_host_turn(replacement_dispatch)
12914 .expect("the rearmed replacement delivery must be admitted");
12915 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
12916 host_turn.drive();
12917 assert!(
12918 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12919 .finish_delivery(delivery, false)
12920 .failed_dispatches
12921 .is_empty()
12922 );
12923 assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
12924 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
12925 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12926
12927 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
12928 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
12929 let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
12930 .state
12931 .lock()
12932 .unwrap_or_else(std::sync::PoisonError::into_inner);
12933 assert!(state.entries.is_empty());
12934 assert!(state.dispatches.is_empty());
12935 }
12936
12937 #[cfg(not(target_family = "wasm"))]
12938 #[test]
12939 fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
12940 use std::sync::mpsc;
12941
12942 {
12943 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
12944 .state
12945 .lock()
12946 .unwrap_or_else(std::sync::PoisonError::into_inner);
12947 assert!(state.entries.is_empty());
12948 assert!(state.dispatches.is_empty());
12949 }
12950 let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
12951 let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
12952 .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
12953 let metrics = Arc::new(RuntimeMetrics::default());
12954 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12955 Arc::clone(&metrics),
12956 dispatch_stale_failure_current_thread_tasks,
12957 &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
12958 ));
12959 let scheduler = Arc::clone(&executor);
12960 let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
12961 scheduler.schedule(runnable)
12962 });
12963 executor.schedule(runnable);
12964
12965 let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12966 let initial_delivery = initial.deliveries()[0];
12967 let delayed_completion =
12968 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
12969 let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
12970 panic!("the initial accepted delivery must report one exact failure");
12971 };
12972 assert_eq!(
12973 *delayed_failure,
12974 current_thread_dispatch_failure(dispatch, 0)
12975 );
12976 let delayed_failure = *delayed_failure;
12977
12978 let (returned_tx, returned_rx) = mpsc::channel();
12979 let (release_tx, release_rx) = mpsc::channel();
12980 let publishing_executor = Arc::clone(&executor);
12981 let publication = std::thread::spawn(move || {
12982 CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
12983 *slot.borrow_mut() = Some(Box::new(move || {
12984 returned_tx.send(()).unwrap();
12985 release_rx.recv().unwrap();
12986 }));
12987 });
12988 publishing_executor.request_drain_if_queued();
12989 });
12990 returned_rx
12991 .recv_timeout(Duration::from_secs(2))
12992 .expect("the old failed epoch must pause before executor recovery");
12993
12994 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
12995 let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
12996 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
12997 executor.request_drain_if_queued();
12998 release_tx.send(()).unwrap();
12999 publication.join().unwrap();
13000
13001 let healthy_delivery = healthy.deliveries()[0];
13002 let delayed_initial_delivery = initial.deliveries()[1];
13003 {
13004 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13005 .state
13006 .lock()
13007 .unwrap_or_else(std::sync::PoisonError::into_inner);
13008 let dispatch_state = state
13009 .dispatches
13010 .get(&dispatch)
13011 .expect("the rearmed dispatch must remain registered");
13012 assert_eq!(dispatch_state.epoch, 1);
13013 assert!(!dispatch_state.is_cancelling());
13014 }
13015
13016 executor.cancel_host_dispatch(delayed_failure);
13017
13018 assert_eq!(
13019 executor.dispatch_pending.load(Ordering::Acquire),
13020 dispatch,
13021 "the delayed old-epoch failure must not consume the rearmed executor capability"
13022 );
13023 assert_eq!(
13024 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13025 .state
13026 .lock()
13027 .unwrap_or_else(std::sync::PoisonError::into_inner)
13028 .dispatches
13029 .get(&dispatch)
13030 .map(|state| (state.epoch, state.phase)),
13031 Some((1, CurrentThreadDispatchPhase::Pending)),
13032 "the delayed old-epoch cancellation must not remove the accepted rearm"
13033 );
13034
13035 assert_eq!(
13036 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
13037 Some(dispatch)
13038 );
13039 let mut host_turn = executor
13040 .try_admit_host_turn(dispatch)
13041 .expect("the current-epoch delivery must be admitted");
13042 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
13043 host_turn.drive();
13044 assert!(
13045 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13046 .finish_delivery(healthy_delivery, false)
13047 .failed_dispatches
13048 .is_empty()
13049 );
13050 assert!(
13051 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13052 .finish_delivery(delayed_initial_delivery, false)
13053 .failed_dispatches
13054 .is_empty()
13055 );
13056 assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
13057 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13058
13059 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
13060 CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
13061 let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13062 .state
13063 .lock()
13064 .unwrap_or_else(std::sync::PoisonError::into_inner);
13065 assert!(state.entries.is_empty());
13066 assert!(state.dispatches.is_empty());
13067 }
13068
13069 #[cfg(not(target_family = "wasm"))]
13070 #[test]
13071 fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
13072 use std::sync::mpsc;
13073
13074 {
13075 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13076 .state
13077 .lock()
13078 .unwrap_or_else(std::sync::PoisonError::into_inner);
13079 assert!(state.entries.is_empty());
13080 assert!(state.dispatches.is_empty());
13081 }
13082 let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13083 .register(Arc::new(FailDuringLateRegistrationPublication));
13084
13085 let metrics = Arc::new(RuntimeMetrics::default());
13086 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13087 Arc::clone(&metrics),
13088 dispatch_late_registration_current_thread_tasks,
13089 &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
13090 ));
13091 let scheduler = Arc::clone(&executor);
13092 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13093 scheduler.schedule(runnable)
13094 });
13095 metrics.runnable_scheduled();
13096 executor
13097 .queue
13098 .lock()
13099 .unwrap_or_else(std::sync::PoisonError::into_inner)
13100 .runnables
13101 .push_back(runnable);
13102 let replacement_dispatch = next_current_thread_dispatch_id();
13103 executor
13104 .recovery_dispatch
13105 .store(replacement_dispatch, Ordering::Release);
13106 executor
13107 .dispatch_pending
13108 .store(replacement_dispatch, Ordering::Release);
13109
13110 let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
13111 let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
13112 let publishing_executor = Arc::clone(&executor);
13113 let publication = std::thread::spawn(move || {
13114 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13115 *slot.borrow_mut() = Some(Box::new(move || {
13116 owner_retired_tx.send(()).unwrap();
13117 request_blocked_rx
13118 .recv_timeout(Duration::from_secs(2))
13119 .expect("the binding-like drain request must wait for the failure transition");
13120 }));
13121 });
13122 publishing_executor.request_drain_if_queued();
13123 });
13124 owner_retired_rx
13125 .recv_timeout(Duration::from_secs(2))
13126 .expect("the failed replacement publication must retire its owner");
13127
13128 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13131 let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13132 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13133 let requesting_executor = Arc::clone(&executor);
13134 let request = std::thread::spawn(move || {
13135 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13136 *slot.borrow_mut() = Some(Box::new(move || {
13137 request_blocked_tx.send(()).unwrap();
13138 }));
13139 });
13140 requesting_executor.request_drain_if_queued();
13141 });
13142
13143 publication.join().unwrap();
13144 request.join().unwrap();
13145
13146 assert!(
13147 healthy.deliveries().is_empty(),
13148 "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
13149 );
13150 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13151 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13152 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13153 assert_eq!(futures::executor::block_on(task.fallible()), None);
13154 {
13155 let scheduler = executor
13156 .scheduler_idle_lock
13157 .lock()
13158 .unwrap_or_else(std::sync::PoisonError::into_inner);
13159 assert_eq!(scheduler.active_dispatch_calls, 0);
13160 assert!(scheduler.dispatch_publications.is_empty());
13161 }
13162
13163 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
13164
13165 let scheduler = Arc::clone(&executor);
13166 let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
13167 scheduler.schedule(runnable)
13168 });
13169 executor.schedule(later_runnable);
13170 let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13171 let later_delivery = healthy.deliveries()[0];
13172 assert_eq!(
13173 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
13174 Some(later_dispatch)
13175 );
13176 let mut host_turn = executor
13177 .try_admit_host_turn(later_dispatch)
13178 .expect("the later exact delivery must be admitted");
13179 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
13180 host_turn.drive();
13181 assert!(
13182 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13183 .finish_delivery(later_delivery, false)
13184 .failed_dispatches
13185 .is_empty()
13186 );
13187 assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
13188 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13189
13190 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
13191 let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13192 .state
13193 .lock()
13194 .unwrap_or_else(std::sync::PoisonError::into_inner);
13195 assert!(state.entries.is_empty());
13196 assert!(state.dispatches.is_empty());
13197 }
13198
13199 #[cfg(not(target_family = "wasm"))]
13200 #[test]
13201 fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
13202 {
13203 CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13204 .lock()
13205 .unwrap()
13206 .clear();
13207 {
13208 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13209 .state
13210 .lock()
13211 .unwrap_or_else(std::sync::PoisonError::into_inner);
13212 assert!(state.entries.is_empty());
13213 assert!(state.dispatches.is_empty());
13214 }
13215 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13216 .next_delivery
13217 .store(0, Ordering::Relaxed);
13218 let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13219 .register(Arc::new(FailDuringNestedUnwindPublication));
13220
13221 let metrics = Arc::new(RuntimeMetrics::default());
13222 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13223 Arc::clone(&metrics),
13224 dispatch_nested_unwind_current_thread_tasks,
13225 &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
13226 ));
13227 let scheduler = Arc::clone(&executor);
13228 let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
13229 scheduler.schedule(runnable)
13230 });
13231
13232 CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13233 *slot.borrow_mut() = Some(Box::new(|| {
13234 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13235 .next_delivery
13236 .store(u64::MAX, Ordering::Relaxed);
13237 }));
13238 });
13239 let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
13240 assert!(
13241 publication.is_err(),
13242 "replacement delivery-id exhaustion must unwind through the outer failed publication"
13243 );
13244
13245 let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13246 .lock()
13247 .unwrap()
13248 .clone();
13249 assert_eq!(dispatches.len(), 2);
13250 let failed_dispatch = dispatches[0];
13251 let replacement_dispatch = dispatches[1];
13252 assert_ne!(failed_dispatch, replacement_dispatch);
13253 assert_eq!(
13254 executor.dispatch_pending.load(Ordering::Acquire),
13255 replacement_dispatch
13256 );
13257 assert_eq!(
13258 executor.recovery_dispatch.load(Ordering::Acquire),
13259 replacement_dispatch
13260 );
13261 {
13262 let scheduler = executor
13263 .scheduler_idle_lock
13264 .lock()
13265 .unwrap_or_else(std::sync::PoisonError::into_inner);
13266 assert_eq!(scheduler.active_dispatch_calls, 0);
13267 assert!(scheduler.dispatch_publications.is_empty());
13268 }
13269 {
13270 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13271 .state
13272 .lock()
13273 .unwrap_or_else(std::sync::PoisonError::into_inner);
13274 assert!(
13275 !state.dispatches.contains_key(&failed_dispatch),
13276 "outer unwind cleanup must retire the exact old cancelling dispatch"
13277 );
13278 assert!(
13279 !state.dispatches.contains_key(&replacement_dispatch),
13280 "the replacement publication guard must release its aborted registry state"
13281 );
13282 }
13283
13284 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13285 .next_delivery
13286 .store(1, Ordering::Relaxed);
13287 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
13288 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13289 let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13290 .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13291
13292 executor.request_drain_if_queued();
13293
13294 assert_eq!(
13295 *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
13296 vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13297 "retry must reuse the replacement reserved before the nested unwind"
13298 );
13299 let delivery = healthy.deliveries()[0];
13300 assert_eq!(delivery.dispatch, replacement_dispatch);
13301 assert_eq!(
13302 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
13303 Some(replacement_dispatch)
13304 );
13305 let mut host_turn = executor
13306 .try_admit_host_turn(replacement_dispatch)
13307 .expect("the retried replacement delivery must be admitted");
13308 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13309 host_turn.drive();
13310 assert!(
13311 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13312 .finish_delivery(delivery, false)
13313 .failed_dispatches
13314 .is_empty()
13315 );
13316 assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
13317 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13318
13319 CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
13320 let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13321 .state
13322 .lock()
13323 .unwrap_or_else(std::sync::PoisonError::into_inner);
13324 assert!(state.entries.is_empty());
13325 assert!(state.dispatches.is_empty());
13326 }
13327
13328 #[test]
13329 fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
13330 reset_current_thread_recovery_host_dispatches();
13331 let metrics = Arc::new(RuntimeMetrics::default());
13332 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13333 Arc::clone(&metrics),
13334 count_current_thread_recovery_host_dispatch,
13335 ));
13336
13337 let scheduler = Arc::clone(&executor);
13338 let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
13339 scheduler.schedule(runnable)
13340 });
13341 executor.schedule(runnable);
13342 assert_eq!(current_thread_recovery_host_dispatches(), 1);
13343 let failed_dispatch = current_thread_dispatch(&executor);
13344
13345 executor.cancel_host_dispatch_for_test(failed_dispatch);
13348 assert_eq!(
13349 current_thread_recovery_host_dispatches(),
13350 2,
13351 "cancellation must request one replacement without another submission"
13352 );
13353 let replacement_dispatch = current_thread_dispatch(&executor);
13354 assert_ne!(replacement_dispatch, failed_dispatch);
13355 assert_eq!(
13356 executor.recovery_dispatch.load(Ordering::Acquire),
13357 replacement_dispatch
13358 );
13359
13360 executor.drive_host_turn_for_dispatch(replacement_dispatch);
13361 assert_eq!(futures::executor::block_on(task), 1);
13362 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13363 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13364 }
13365
13366 #[test]
13367 fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
13368 reset_current_thread_recovery_host_dispatches();
13369 let metrics = Arc::new(RuntimeMetrics::default());
13370 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13371 Arc::clone(&metrics),
13372 count_current_thread_recovery_host_dispatch,
13373 ));
13374 let backend =
13375 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13376
13377 let registration = backend
13378 .work
13379 .try_register_async()
13380 .expect("the backend must accept work");
13381 let task = spawn_registered(
13382 &backend,
13383 Arc::clone(&metrics),
13384 std::future::pending::<()>(),
13385 registration,
13386 );
13387 let failed_dispatch = current_thread_dispatch(&executor);
13388 executor.cancel_host_dispatch_for_test(failed_dispatch);
13389 let replacement_dispatch = current_thread_dispatch(&executor);
13390
13391 executor.cancel_host_dispatch_for_test(replacement_dispatch);
13392
13393 assert_eq!(
13394 current_thread_recovery_host_dispatches(),
13395 2,
13396 "a second scheduler failure must not create an unbounded retry loop"
13397 );
13398 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13399 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13400 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13401 assert_eq!(
13402 futures::executor::block_on(task).unwrap_err().to_string(),
13403 "async runtime stopped before the task completed",
13404 "terminal host failure must settle the original operation as cancelled"
13405 );
13406 assert_eq!(backend.work.state.lock().unwrap().active, 0);
13407
13408 let later_registration = backend
13409 .work
13410 .try_register_async()
13411 .expect("terminal cancellation must reopen acceptance");
13412 let later_task = spawn_registered(
13413 &backend,
13414 Arc::clone(&metrics),
13415 async { 2usize },
13416 later_registration,
13417 );
13418 assert_eq!(
13419 current_thread_recovery_host_dispatches(),
13420 3,
13421 "terminal cancellation must not permanently close the executor"
13422 );
13423 executor.drive_host_turn();
13424 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
13425 }
13426
13427 #[cfg(not(target_family = "wasm"))]
13428 #[test]
13429 fn current_thread_recovery_reservation_blocks_new_host_publication() {
13430 use std::sync::mpsc;
13431
13432 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13433 let metrics = Arc::new(RuntimeMetrics::default());
13434 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13435 Arc::clone(&metrics),
13436 count_current_thread_interleaved_recovery_host_dispatch,
13437 ));
13438 let backend =
13439 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13440
13441 let registration = backend
13442 .work
13443 .try_register_async()
13444 .expect("the backend must accept work");
13445 let task = spawn_registered(
13446 &backend,
13447 Arc::clone(&metrics),
13448 std::future::pending::<()>(),
13449 registration,
13450 );
13451 let failed_dispatch = current_thread_dispatch(&executor);
13452 assert_eq!(
13453 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13454 1
13455 );
13456
13457 let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
13458 let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
13459 let cancelling_executor = Arc::clone(&executor);
13460 let cancellation = std::thread::spawn(move || {
13461 CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
13462 *slot.borrow_mut() = Some(Box::new(move || {
13463 recovery_cleared_tx.send(()).unwrap();
13464 release_recovery_rx.recv().unwrap();
13465 }));
13466 });
13467 cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
13468 });
13469 recovery_cleared_rx
13470 .recv_timeout(Duration::from_secs(2))
13471 .expect("failed dispatch cancellation must pause after consuming the old capability");
13472
13473 let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13474 let (publication_done_tx, publication_done_rx) = mpsc::channel();
13475 let publishing_executor = Arc::clone(&executor);
13476 let publication = std::thread::spawn(move || {
13477 CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13478 *slot.borrow_mut() = Some(Box::new(move || {
13479 publication_blocked_tx.send(()).unwrap();
13480 }));
13481 });
13482 publishing_executor.request_drain_if_queued();
13483 publication_done_tx.send(()).unwrap();
13484 });
13485
13486 if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
13487 release_recovery_tx.send(()).unwrap();
13488 cancellation.join().unwrap();
13489 publication.join().unwrap();
13490 panic!("a newly registered host must wait for recovery reservation: {error}");
13491 }
13492 assert!(
13493 matches!(
13494 publication_done_rx.try_recv(),
13495 Err(mpsc::TryRecvError::Empty)
13496 ),
13497 "the new host must not publish in the consumed-to-recovery gap"
13498 );
13499 release_recovery_tx.send(()).unwrap();
13500
13501 cancellation.join().unwrap();
13502 publication_done_rx
13503 .recv_timeout(Duration::from_secs(2))
13504 .unwrap();
13505 publication.join().unwrap();
13506
13507 let replacement_dispatch = current_thread_dispatch(&executor);
13508 assert_ne!(replacement_dispatch, failed_dispatch);
13509 assert_eq!(
13510 executor.recovery_dispatch.load(Ordering::Acquire),
13511 replacement_dispatch
13512 );
13513 assert_eq!(
13514 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13515 3,
13516 "the original publication and two host attempts for its one replacement must run"
13517 );
13518
13519 executor.cancel_host_dispatch_for_test(replacement_dispatch);
13520
13521 assert_eq!(
13522 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13523 3,
13524 "failure of the tagged replacement must cancel work without a second replacement"
13525 );
13526 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13527 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13528 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13529 assert_eq!(
13530 futures::executor::block_on(task).unwrap_err().to_string(),
13531 "async runtime stopped before the task completed"
13532 );
13533
13534 let later_registration = backend
13535 .work
13536 .try_register_async()
13537 .expect("terminal cancellation must reopen acceptance");
13538 let later_task = spawn_registered(
13539 &backend,
13540 Arc::clone(&metrics),
13541 async { 2usize },
13542 later_registration,
13543 );
13544 assert_eq!(
13545 CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
13546 4
13547 );
13548 executor.drive_host_turn();
13549 assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
13550 }
13551
13552 #[cfg(not(target_family = "wasm"))]
13553 #[test]
13554 fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
13555 use std::sync::mpsc;
13556
13557 struct BlockingDrop {
13558 entered: mpsc::Sender<()>,
13559 release: mpsc::Receiver<()>,
13560 }
13561
13562 impl Drop for BlockingDrop {
13563 fn drop(&mut self) {
13564 self.entered.send(()).unwrap();
13565 self.release.recv().unwrap();
13566 }
13567 }
13568
13569 reset_current_thread_recovery_host_dispatches();
13570 let metrics = Arc::new(RuntimeMetrics::default());
13571 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13572 Arc::clone(&metrics),
13573 count_current_thread_recovery_host_dispatch,
13574 ));
13575 let backend =
13576 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13577
13578 let holder_registration = backend
13579 .work
13580 .try_register_work()
13581 .expect("the backend must accept the blocking holder");
13582 let (holder_started_tx, holder_started_rx) = mpsc::channel();
13583 let (release_holder_tx, release_holder_rx) = mpsc::channel();
13584 let holder_executor = Arc::clone(&executor);
13585 let holder = std::thread::spawn(move || {
13586 let handle = holder_executor.schedule_blocking(
13587 move || {
13588 holder_started_tx.send(()).unwrap();
13589 release_holder_rx.recv().unwrap();
13590 },
13591 holder_registration,
13592 );
13593 futures::executor::block_on(handle).unwrap();
13594 });
13595 holder_started_rx
13596 .recv_timeout(Duration::from_secs(2))
13597 .expect("the first blocking call must hold the CurrentThread lane");
13598
13599 let cancelled_registration = backend
13600 .work
13601 .try_register_work()
13602 .expect("the backend must accept queued blocking work");
13603 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
13604 let (release_drop_tx, release_drop_rx) = mpsc::channel();
13605 let blocking_drop = BlockingDrop {
13606 entered: drop_entered_tx,
13607 release: release_drop_rx,
13608 };
13609 let cancelled = executor.schedule_blocking(
13610 move || {
13611 let _blocking_drop = blocking_drop;
13612 },
13613 cancelled_registration,
13614 );
13615
13616 let async_registration = backend
13617 .work
13618 .try_register_async()
13619 .expect("the backend must accept queued async work");
13620 let pending = spawn_registered(
13621 &backend,
13622 Arc::clone(&metrics),
13623 std::future::pending::<()>(),
13624 async_registration,
13625 );
13626 let failed_dispatch = current_thread_dispatch(&executor);
13627 executor.cancel_host_dispatch_for_test(failed_dispatch);
13628 let replacement_dispatch = current_thread_dispatch(&executor);
13629
13630 let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
13631 let cancelling_executor = Arc::clone(&executor);
13632 let cancellation = std::thread::spawn(move || {
13633 cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
13634 cancel_done_tx.send(()).unwrap();
13635 });
13636 drop_entered_rx
13637 .recv_timeout(Duration::from_secs(2))
13638 .expect("terminal dispatch cancellation must destroy the queued blocking capture");
13639 let active_during_drop = backend.work.state.lock().unwrap().active;
13640 let cancellation_blocked = cancel_done_rx.try_recv().is_err();
13641
13642 release_drop_tx.send(()).unwrap();
13643 cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13644 cancellation.join().unwrap();
13645
13646 assert_eq!(
13647 active_during_drop, 2,
13648 "the blocking capture's work registration must remain active until its destructor returns"
13649 );
13650 assert!(
13651 cancellation_blocked,
13652 "terminal cancellation must wait for capture destruction"
13653 );
13654 assert!(futures::executor::block_on(cancelled).is_err());
13655 assert!(futures::executor::block_on(pending).is_err());
13656 assert_eq!(backend.work.state.lock().unwrap().active, 1);
13657
13658 release_holder_tx.send(()).unwrap();
13659 holder.join().unwrap();
13660 assert_eq!(backend.work.state.lock().unwrap().active, 0);
13661 }
13662
13663 #[test]
13664 fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
13665 reset_current_thread_one_shot_host_dispatches();
13666 let metrics = Arc::new(RuntimeMetrics::default());
13667 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13668 Arc::clone(&metrics),
13669 accept_only_first_current_thread_host_dispatch,
13670 ));
13671
13672 let scheduler = Arc::clone(&executor);
13673 let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13674 scheduler.schedule(runnable)
13675 });
13676 executor.schedule(runnable);
13677 let failed_dispatch = current_thread_dispatch(&executor);
13678
13679 executor.cancel_host_dispatch_for_test(failed_dispatch);
13680
13681 assert_eq!(
13682 current_thread_one_shot_host_dispatches(),
13683 2,
13684 "the original and its one bounded replacement must be attempted"
13685 );
13686 assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13687 assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13688 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13689 assert!(
13690 futures::executor::block_on(task.fallible()).is_none(),
13691 "an unavailable replacement host must settle the original task as cancelled"
13692 );
13693 }
13694
13695 enum TestCurrentThreadTaskDriverBehavior {
13696 Healthy,
13697 PanicInLivenessAndSweep,
13698 PanicInDispatchAndSweep,
13699 }
13700
13701 struct TestCurrentThreadTaskDriver {
13702 behavior: TestCurrentThreadTaskDriverBehavior,
13703 dispatched: AtomicBool,
13704 }
13705
13706 #[derive(Default)]
13707 struct ManualCurrentThreadTaskDriver {
13708 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
13709 }
13710
13711 struct FailDuringAuthoritativePanicPublication;
13712 struct FailDuringLateRegistrationPublication;
13713 struct FailDuringNestedUnwindPublication;
13714 struct FailDuringReplacementRepublishPublication;
13715 struct FirstHostileCurrentThreadPanic;
13716 struct LaterHostileCurrentThreadPanic;
13717
13718 impl Drop for FirstHostileCurrentThreadPanic {
13719 fn drop(&mut self) {
13720 CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
13721 panic!("the first retained CurrentThread panic payload was dropped");
13722 }
13723 }
13724
13725 impl Drop for LaterHostileCurrentThreadPanic {
13726 fn drop(&mut self) {
13727 CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
13728 panic!("a later CurrentThread panic payload escaped containment");
13729 }
13730 }
13731
13732 impl ManualCurrentThreadTaskDriver {
13733 fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
13734 self.deliveries.lock().unwrap().clone()
13735 }
13736 }
13737
13738 impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
13739 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13740 self.deliveries.lock().unwrap().push(delivery);
13741 true
13742 }
13743 }
13744
13745 impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
13746 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13747 let completion =
13748 CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
13749 assert!(
13750 completion.failed_dispatches.is_empty(),
13751 "the failed delivery must remain provisional until its publication retires"
13752 );
13753 true
13754 }
13755 }
13756
13757 impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
13758 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13759 let completion =
13760 CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
13761 assert!(
13762 completion.failed_dispatches.is_empty(),
13763 "the failed delivery must remain provisional until its publication retires"
13764 );
13765 true
13766 }
13767 }
13768
13769 impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
13770 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13771 let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
13772 assert!(
13773 completion.failed_dispatches.is_empty(),
13774 "the failed delivery must remain provisional until its publication retires"
13775 );
13776 true
13777 }
13778 }
13779
13780 impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
13781 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
13782 let completion =
13783 CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
13784 assert!(
13785 completion.failed_dispatches.is_empty(),
13786 "the failed delivery must remain provisional until its publication retires"
13787 );
13788 true
13789 }
13790 }
13791
13792 struct ReentrantCurrentThreadTaskDriver {
13793 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13794 registration: Mutex<Option<CurrentThreadTaskDriverId>>,
13795 }
13796
13797 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
13798 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13799 true
13800 }
13801
13802 fn is_live(&self) -> bool {
13803 let registration = *self.registration.lock().unwrap();
13804 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
13805 registry.unregister(id);
13806 }
13807 false
13808 }
13809 }
13810
13811 struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
13812 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13813 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
13814 }
13815
13816 impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
13817 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13818 true
13819 }
13820
13821 fn is_live(&self) -> bool {
13822 if let Some(registry) = self.registry.upgrade() {
13823 for registration in self.registrations.lock().unwrap().iter().copied() {
13824 let completion = registry.unregister(registration);
13825 assert!(completion.failed_dispatches.is_empty());
13826 assert!(!completion.redispatch);
13827 }
13828 }
13829 false
13830 }
13831 }
13832
13833 struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
13834 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13835 registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
13836 drops: Arc<AtomicUsize>,
13837 }
13838
13839 impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
13840 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13841 if let Some(registry) = self.registry.upgrade() {
13842 for registration in self.registrations.lock().unwrap().iter().copied() {
13843 let completion = registry.unregister(registration);
13844 assert!(completion.failed_dispatches.is_empty());
13845 assert!(!completion.redispatch);
13846 }
13847 }
13848 true
13849 }
13850 }
13851
13852 impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
13853 fn drop(&mut self) {
13854 self.drops.fetch_add(1, Ordering::SeqCst);
13855 panic!("intentional reentrant CurrentThread task-driver destructor panic");
13856 }
13857 }
13858
13859 struct PanickingDropCurrentThreadTaskDriver {
13860 drops: Arc<AtomicUsize>,
13861 }
13862
13863 impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
13864 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13865 true
13866 }
13867 }
13868
13869 impl Drop for PanickingDropCurrentThreadTaskDriver {
13870 fn drop(&mut self) {
13871 self.drops.fetch_add(1, Ordering::SeqCst);
13872 panic!("intentional CurrentThread task-driver destructor panic");
13873 }
13874 }
13875
13876 struct ReentrantCurrentThreadTaskDriverDrop {
13877 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
13878 fallback: CurrentThreadTaskDriverId,
13879 }
13880
13881 impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
13882 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13883 true
13884 }
13885 }
13886
13887 impl Drop for ReentrantCurrentThreadTaskDriverDrop {
13888 fn drop(&mut self) {
13889 if let Some(registry) = self.registry.upgrade() {
13890 registry.unregister(self.fallback);
13891 }
13892 }
13893 }
13894
13895 impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
13896 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
13897 assert!(
13898 !matches!(
13899 self.behavior,
13900 TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
13901 ),
13902 "intentional task-driver dispatch panic"
13903 );
13904 self.dispatched.store(true, Ordering::SeqCst);
13905 true
13906 }
13907
13908 fn is_live(&self) -> bool {
13909 assert!(
13910 !matches!(
13911 self.behavior,
13912 TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
13913 ),
13914 "intentional task-driver liveness panic"
13915 );
13916 true
13917 }
13918
13919 fn on_swept(&self) {
13920 assert!(
13921 matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
13922 "intentional task-driver sweep panic"
13923 );
13924 }
13925 }
13926
13927 #[test]
13928 fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
13929 let registry = CurrentThreadTaskDriverRegistry::default();
13930 let fallback = Arc::new(TestCurrentThreadTaskDriver {
13931 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
13932 dispatched: AtomicBool::new(false),
13933 });
13934 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
13935 registry.register(Arc::new(TestCurrentThreadTaskDriver {
13936 behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
13937 dispatched: AtomicBool::new(false),
13938 }));
13939 registry.register(Arc::new(TestCurrentThreadTaskDriver {
13940 behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
13941 dispatched: AtomicBool::new(false),
13942 }));
13943
13944 assert_eq!(
13945 registry.dispatch(37),
13946 CurrentThreadHostDispatchResult::Accepted,
13947 "a panicking newest host must fall back to a live host"
13948 );
13949 assert!(fallback.dispatched.load(Ordering::SeqCst));
13950 }
13951
13952 #[test]
13953 fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
13954 let registry = CurrentThreadTaskDriverRegistry::default();
13955 let first = Arc::new(ManualCurrentThreadTaskDriver::default());
13956 let second = Arc::new(ManualCurrentThreadTaskDriver::default());
13957 registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
13958 registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
13959 registry
13960 .next_delivery
13961 .store(u64::MAX - 1, Ordering::Relaxed);
13962
13963 let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
13964 assert!(
13965 publication.is_err(),
13966 "delivery-id exhaustion on the second host must unwind the publication"
13967 );
13968 {
13969 let state = registry
13970 .state
13971 .lock()
13972 .unwrap_or_else(std::sync::PoisonError::into_inner);
13973 assert!(
13974 !state.dispatches.contains_key(&38),
13975 "unwind cleanup must release the exact registry publication barrier"
13976 );
13977 assert!(
13978 state
13979 .entries
13980 .values()
13981 .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
13982 "failed transactional reservation must leave every physical host slot clean"
13983 );
13984 }
13985 assert!(first.deliveries().is_empty());
13986 assert!(second.deliveries().is_empty());
13987
13988 registry.next_delivery.store(0, Ordering::Relaxed);
13989 assert_eq!(
13990 registry.dispatch(38),
13991 CurrentThreadHostDispatchResult::Accepted
13992 );
13993 let first_delivery = first.deliveries()[0];
13994 let second_delivery = second.deliveries()[0];
13995 assert_eq!(
13996 registry.claim_delivery(first_delivery.capability()),
13997 Some(38)
13998 );
13999 assert_eq!(
14000 registry.claim_delivery(second_delivery.capability()),
14001 Some(38)
14002 );
14003 registry.mark_serviced(38);
14004 assert!(
14005 registry
14006 .finish_delivery(first_delivery, false)
14007 .failed_dispatches
14008 .is_empty()
14009 );
14010 assert!(
14011 registry
14012 .finish_delivery(second_delivery, false)
14013 .failed_dispatches
14014 .is_empty()
14015 );
14016
14017 assert_eq!(
14018 registry.dispatch(39),
14019 CurrentThreadHostDispatchResult::Accepted
14020 );
14021 let first_later = first.deliveries()[1];
14022 let second_later = second.deliveries()[1];
14023 assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
14024 assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
14025 registry.mark_serviced(39);
14026 assert!(
14027 registry
14028 .finish_delivery(first_later, false)
14029 .failed_dispatches
14030 .is_empty()
14031 );
14032 assert!(
14033 registry
14034 .finish_delivery(second_later, false)
14035 .failed_dispatches
14036 .is_empty()
14037 );
14038 assert!(
14039 registry
14040 .state
14041 .lock()
14042 .unwrap_or_else(std::sync::PoisonError::into_inner)
14043 .dispatches
14044 .is_empty()
14045 );
14046 let state = registry
14047 .state
14048 .lock()
14049 .unwrap_or_else(std::sync::PoisonError::into_inner);
14050 assert!(
14051 state
14052 .entries
14053 .values()
14054 .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
14055 );
14056 }
14057
14058 #[test]
14059 fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
14060 let registry = CurrentThreadTaskDriverRegistry::default();
14061 let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
14062 let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
14063 let dispatch = 40;
14064
14065 assert_eq!(
14066 registry.dispatch(dispatch),
14067 CurrentThreadHostDispatchResult::Accepted
14068 );
14069 let failed_delivery = failed.deliveries()[0];
14070 let failure = current_thread_dispatch_failure(dispatch, 0);
14071 assert_eq!(
14072 registry
14073 .finish_delivery(failed_delivery, true)
14074 .failed_dispatches,
14075 vec![failure]
14076 );
14077 registry.unregister(failed_id);
14078
14079 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
14080 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
14081 assert!(
14082 registry.rearm_failed_publication(failure),
14083 "a closed unreferenced dispatch must reopen for its armed publication owner"
14084 );
14085 assert_eq!(
14086 registry.dispatch(dispatch),
14087 CurrentThreadHostDispatchResult::Accepted
14088 );
14089 let replacement_delivery = replacement.deliveries()[0];
14090 assert_eq!(replacement_delivery.dispatch, dispatch);
14091 assert_eq!(
14092 registry.claim_delivery(replacement_delivery.capability()),
14093 Some(dispatch)
14094 );
14095 registry.mark_serviced(dispatch);
14096 assert!(
14097 registry
14098 .finish_delivery(replacement_delivery, false)
14099 .failed_dispatches
14100 .is_empty()
14101 );
14102 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14103 }
14104
14105 #[test]
14106 fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
14107 struct RacingTaskDriver {
14108 executor: Arc<CurrentThreadExecutor>,
14109 drives: bool,
14110 dispatches: Mutex<Vec<u64>>,
14111 }
14112
14113 impl CurrentThreadTaskDriver for RacingTaskDriver {
14114 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14115 self.dispatches.lock().unwrap().push(delivery.dispatch);
14116 if self.drives {
14117 self
14118 .executor
14119 .drive_host_turn_for_dispatch(delivery.dispatch);
14120 }
14121 true
14122 }
14123 }
14124
14125 let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14126 Arc::new(RuntimeMetrics::default()),
14127 accept_current_thread_host_dispatch,
14128 ));
14129 let polls = Arc::new(AtomicUsize::new(0));
14130 let task_polls = Arc::clone(&polls);
14131 let scheduler = Arc::clone(&executor);
14132 let (runnable, task) = async_task::spawn(
14133 async move {
14134 task_polls.fetch_add(1, Ordering::SeqCst);
14135 },
14136 move |runnable| scheduler.schedule(runnable),
14137 );
14138 executor.schedule(runnable);
14139 let dispatch = current_thread_dispatch(&executor);
14140
14141 let registry = CurrentThreadTaskDriverRegistry::default();
14142 let responsive = Arc::new(RacingTaskDriver {
14143 executor: Arc::clone(&executor),
14144 drives: true,
14145 dispatches: Mutex::new(Vec::new()),
14146 });
14147 let starved = Arc::new(RacingTaskDriver {
14148 executor: Arc::clone(&executor),
14149 drives: false,
14150 dispatches: Mutex::new(Vec::new()),
14151 });
14152 registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
14153 registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
14155
14156 assert_eq!(
14157 registry.dispatch(dispatch),
14158 CurrentThreadHostDispatchResult::Accepted
14159 );
14160 assert_eq!(
14161 responsive.dispatches.lock().unwrap().as_slice(),
14162 &[dispatch]
14163 );
14164 assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
14165 assert_eq!(
14166 polls.load(Ordering::SeqCst),
14167 1,
14168 "the responsive older host must win even when the newest host is starved"
14169 );
14170
14171 executor.drive_host_turn_for_dispatch(dispatch);
14174 assert_eq!(polls.load(Ordering::SeqCst), 1);
14175 futures::executor::block_on(task);
14176 }
14177
14178 #[test]
14179 fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
14180 const HOSTS: usize = 1_024;
14181
14182 let registry = CurrentThreadTaskDriverRegistry::default();
14183 let mut ids = Vec::with_capacity(HOSTS);
14184 let mut drivers = Vec::with_capacity(HOSTS);
14185 for _ in 0..HOSTS {
14186 let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
14187 ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
14188 drivers.push(driver);
14189 }
14190
14191 let dispatch = u64::MAX - 300;
14192 assert_eq!(
14193 registry.dispatch(dispatch),
14194 CurrentThreadHostDispatchResult::Accepted
14195 );
14196 let deliveries = drivers
14197 .iter()
14198 .map(|driver| driver.deliveries()[0])
14199 .collect::<Vec<_>>();
14200 {
14201 let state = registry
14202 .state
14203 .lock()
14204 .unwrap_or_else(std::sync::PoisonError::into_inner);
14205 assert_eq!(state.delivery_drivers.len(), HOSTS);
14206 assert_eq!(
14207 state
14208 .dispatches
14209 .get(&dispatch)
14210 .map(|state| state.references),
14211 Some(HOSTS)
14212 );
14213 assert_eq!(
14214 state
14215 .ordered_snapshot()
14216 .iter()
14217 .map(|(id, _)| *id)
14218 .collect::<Vec<_>>(),
14219 ids,
14220 "keyed driver lookup must retain registration order"
14221 );
14222 }
14223
14224 for delivery in &deliveries {
14225 assert_eq!(
14226 registry.claim_delivery(delivery.capability()),
14227 Some(dispatch)
14228 );
14229 }
14230 registry.mark_serviced(dispatch);
14231 for delivery in deliveries {
14232 assert!(
14233 registry
14234 .finish_delivery(delivery, false)
14235 .failed_dispatches
14236 .is_empty()
14237 );
14238 }
14239 {
14240 let state = registry
14241 .state
14242 .lock()
14243 .unwrap_or_else(std::sync::PoisonError::into_inner);
14244 assert!(state.delivery_drivers.is_empty());
14245 assert!(state.dispatches.is_empty());
14246 }
14247
14248 let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
14249 for id in &removed {
14250 let completion = registry.unregister(*id);
14251 assert!(completion.failed_dispatches.is_empty());
14252 assert!(!completion.redispatch);
14253 }
14254 let expected = ids
14255 .iter()
14256 .copied()
14257 .filter(|id| !removed.contains(id))
14258 .collect::<Vec<_>>();
14259 let live = registry.live().live;
14260 assert_eq!(
14261 live
14262 .as_slice()
14263 .iter()
14264 .map(|(id, _)| *id)
14265 .collect::<Vec<_>>(),
14266 expected,
14267 "O(1) removals must preserve the relative order of surviving host registrations"
14268 );
14269 drop(live);
14270
14271 for id in expected {
14272 registry.unregister(id);
14273 }
14274 let state = registry
14275 .state
14276 .lock()
14277 .unwrap_or_else(std::sync::PoisonError::into_inner);
14278 assert!(state.entries.is_empty());
14279 assert!(state.oldest.is_none());
14280 assert!(state.newest.is_none());
14281 }
14282
14283 #[test]
14284 fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
14285 let registry = CurrentThreadTaskDriverRegistry::default();
14286 let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
14287 let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
14288 let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14289 registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
14290 registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
14291 registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14292
14293 let dispatch = 41;
14294 assert_eq!(
14295 registry.dispatch(dispatch),
14296 CurrentThreadHostDispatchResult::Accepted
14297 );
14298 let failing_delivery = failing.deliveries()[0];
14299 let delayed_delivery = delayed.deliveries()[0];
14300 let healthy_delivery = healthy.deliveries()[0];
14301 assert_eq!(failing_delivery.dispatch, dispatch);
14302 assert_eq!(delayed_delivery.dispatch, dispatch);
14303 assert_eq!(healthy_delivery.dispatch, dispatch);
14304
14305 assert!(
14306 registry
14307 .finish_delivery(failing_delivery, true)
14308 .failed_dispatches
14309 .is_empty(),
14310 "one host failure must not recover while other accepted attempts remain"
14311 );
14312 assert_eq!(
14313 registry.claim_delivery(healthy_delivery.capability()),
14314 Some(dispatch)
14315 );
14316 registry.mark_serviced(dispatch);
14317 assert!(
14318 registry
14319 .finish_delivery(healthy_delivery, false)
14320 .failed_dispatches
14321 .is_empty()
14322 );
14323 assert_eq!(
14324 registry.claim_delivery(delayed_delivery.capability()),
14325 None,
14326 "a delayed losing delivery must become stale after another host services the dispatch"
14327 );
14328 assert!(
14329 registry
14330 .finish_delivery(delayed_delivery, true)
14331 .failed_dispatches
14332 .is_empty(),
14333 "a stale losing failure must not recover or cancel a serviced dispatch"
14334 );
14335 assert!(
14336 registry.state.lock().unwrap().dispatches.is_empty(),
14337 "all per-dispatch acknowledgement state must retire"
14338 );
14339 }
14340
14341 #[test]
14342 fn current_thread_unclaimed_callback_success_is_delivery_failure() {
14343 let registry = CurrentThreadTaskDriverRegistry::default();
14344 let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
14345 registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
14346
14347 let dispatch = 56;
14348 assert_eq!(
14349 registry.dispatch(dispatch),
14350 CurrentThreadHostDispatchResult::Accepted
14351 );
14352 let delivery = no_op.deliveries()[0];
14353 let completion = registry.finish_delivery(delivery, false);
14354 let failure = current_thread_dispatch_failure(dispatch, 0);
14355 assert_eq!(
14356 completion.failed_dispatches,
14357 vec![failure],
14358 "a callback return without an exact capability claim must enter recovery"
14359 );
14360 assert!(
14361 registry
14362 .state
14363 .lock()
14364 .unwrap()
14365 .dispatches
14366 .get(&dispatch)
14367 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14368 );
14369 registry.finish_cancellation(failure);
14370 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14371 }
14372
14373 #[test]
14374 fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
14375 struct FailDuringPublication {
14376 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14377 }
14378
14379 impl CurrentThreadTaskDriver for FailDuringPublication {
14380 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14381 let completion = self
14382 .registry
14383 .upgrade()
14384 .expect("the registry must outlive publication")
14385 .finish_delivery(delivery, true);
14386 assert!(
14387 completion.failed_dispatches.is_empty(),
14388 "failure must remain provisional until the broadcast closes"
14389 );
14390 true
14391 }
14392 }
14393
14394 struct RejectProvisional;
14395
14396 impl CurrentThreadTaskDriver for RejectProvisional {
14397 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14398 false
14399 }
14400 }
14401
14402 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14403 registry.register(Arc::new(RejectProvisional));
14404 registry.register(Arc::new(FailDuringPublication {
14407 registry: Arc::downgrade(®istry),
14408 }));
14409
14410 let failure = current_thread_dispatch_failure(57, 0);
14411 assert_eq!(
14412 registry.dispatch(57),
14413 CurrentThreadHostDispatchResult::Failed(failure),
14414 "the final provisional rejection must expose the earlier accepted failure"
14415 );
14416 assert!(
14417 registry
14418 .state
14419 .lock()
14420 .unwrap()
14421 .dispatches
14422 .get(&57)
14423 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14424 );
14425 assert_eq!(
14426 registry.dispatch(57),
14427 CurrentThreadHostDispatchResult::Failed(failure),
14428 "a racing replacement publication must preserve terminal failure until cancellation runs"
14429 );
14430 registry.finish_cancellation(failure);
14431 }
14432
14433 #[test]
14434 fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
14435 struct RejectProvisional;
14436
14437 impl CurrentThreadTaskDriver for RejectProvisional {
14438 fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14439 false
14440 }
14441 }
14442
14443 let registry = CurrentThreadTaskDriverRegistry::default();
14444 registry.register(Arc::new(RejectProvisional));
14445 registry.register(Arc::new(RejectProvisional));
14446
14447 assert_eq!(
14448 registry.dispatch(58),
14449 CurrentThreadHostDispatchResult::Unavailable
14450 );
14451 assert!(
14452 registry.state.lock().unwrap().dispatches.is_empty(),
14453 "a broadcast with no accepted attempt must not enter failure recovery"
14454 );
14455 }
14456
14457 #[test]
14458 fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
14459 let registry = CurrentThreadTaskDriverRegistry::default();
14460 let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
14461 let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
14462
14463 assert_eq!(
14464 registry.dispatch(1),
14465 CurrentThreadHostDispatchResult::Accepted
14466 );
14467 let first = blocked.deliveries()[0];
14468 for dispatch in 2..=256 {
14469 assert_eq!(
14470 registry.dispatch(dispatch),
14471 CurrentThreadHostDispatchResult::Accepted
14472 );
14473 assert_eq!(
14474 blocked.deliveries().len(),
14475 1,
14476 "a blocked host must never receive a second queued or in-flight callback"
14477 );
14478 }
14479 {
14480 let state = registry.state.lock().unwrap();
14481 let entry = state
14482 .entries
14483 .get(&blocked_id)
14484 .expect("the blocked host must remain registered");
14485 assert_eq!(entry.pending, Some(256));
14486 assert_eq!(
14487 state.dispatches.len(),
14488 2,
14489 "only the in-flight and latest pending dispatches may retain registry state"
14490 );
14491 }
14492
14493 assert_eq!(registry.claim_delivery(first.capability()), Some(1));
14494 registry.mark_serviced(1);
14495 let completion = registry.finish_delivery(first, false);
14496 assert!(completion.failed_dispatches.is_empty());
14497 assert!(completion.redispatch);
14498 assert_eq!(
14499 registry.dispatch(256),
14500 CurrentThreadHostDispatchResult::Accepted
14501 );
14502 let deliveries = blocked.deliveries();
14503 assert_eq!(deliveries.len(), 2);
14504 assert_eq!(
14505 deliveries[1].dispatch, 256,
14506 "completion must schedule exactly the latest coalesced capability"
14507 );
14508 assert_eq!(registry.claim_delivery(first.capability()), None);
14509 assert_eq!(
14510 registry.claim_delivery(deliveries[1].capability()),
14511 Some(256)
14512 );
14513 registry.mark_serviced(256);
14514 assert!(
14515 registry
14516 .finish_delivery(deliveries[1], false)
14517 .failed_dispatches
14518 .is_empty()
14519 );
14520 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14521 }
14522
14523 #[test]
14524 fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
14525 struct RejectAfterFirst {
14526 calls: AtomicUsize,
14527 deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
14528 }
14529
14530 impl CurrentThreadTaskDriver for RejectAfterFirst {
14531 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14532 self.deliveries.lock().unwrap().push(delivery);
14533 self.calls.fetch_add(1, Ordering::SeqCst) == 0
14534 }
14535 }
14536
14537 let registry = CurrentThreadTaskDriverRegistry::default();
14538 let host = Arc::new(RejectAfterFirst {
14539 calls: AtomicUsize::new(0),
14540 deliveries: Mutex::new(Vec::new()),
14541 });
14542 registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
14543 assert_eq!(
14544 registry.dispatch(11),
14545 CurrentThreadHostDispatchResult::Accepted
14546 );
14547 let first = host.deliveries.lock().unwrap()[0];
14548 assert_eq!(
14549 registry.dispatch(12),
14550 CurrentThreadHostDispatchResult::Accepted
14551 );
14552
14553 assert_eq!(registry.claim_delivery(first.capability()), Some(11));
14554 registry.mark_serviced(11);
14555 let completion = registry.finish_delivery(first, false);
14556 assert!(completion.redispatch);
14557 let failure = current_thread_dispatch_failure(12, 0);
14558 assert_eq!(
14559 registry.dispatch(12),
14560 CurrentThreadHostDispatchResult::Failed(failure),
14561 "an accepted coalesced capability must report failure if its eventual host queue rejects it"
14562 );
14563 assert!(
14564 registry
14565 .state
14566 .lock()
14567 .unwrap()
14568 .dispatches
14569 .get(&12)
14570 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14571 );
14572 registry.finish_cancellation(failure);
14573 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14574 }
14575
14576 #[test]
14577 fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
14578 let registry = CurrentThreadTaskDriverRegistry::default();
14579 let old = Arc::new(ManualCurrentThreadTaskDriver::default());
14580 let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
14581 assert_eq!(
14582 registry.dispatch(73),
14583 CurrentThreadHostDispatchResult::Accepted
14584 );
14585 let old_delivery = old.deliveries()[0];
14586
14587 let completion = registry.unregister(old_id);
14588 let failure = current_thread_dispatch_failure(73, 0);
14589 assert_eq!(
14590 completion.failed_dispatches,
14591 vec![failure],
14592 "unregistering the final accepting host must retire its exact dispatch"
14593 );
14594 registry.finish_cancellation(failure);
14595 let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
14596 let replacement_id =
14597 registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
14598 assert_ne!(replacement_id, old_id);
14599 assert_eq!(
14600 registry.dispatch(74),
14601 CurrentThreadHostDispatchResult::Accepted
14602 );
14603 let replacement_delivery = replacement.deliveries()[0];
14604 assert_ne!(replacement_delivery.capability(), old_delivery.capability());
14605
14606 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
14607 assert!(
14608 registry
14609 .finish_delivery(old_delivery, true)
14610 .failed_dispatches
14611 .is_empty()
14612 );
14613 assert!(
14614 registry
14615 .fail_delivery(old_delivery.capability())
14616 .failed_dispatches
14617 .is_empty()
14618 );
14619 assert_eq!(
14620 registry.claim_delivery(replacement_delivery.capability()),
14621 Some(74)
14622 );
14623 registry.mark_serviced(74);
14624 assert!(
14625 registry
14626 .finish_delivery(replacement_delivery, false)
14627 .failed_dispatches
14628 .is_empty()
14629 );
14630 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14631 }
14632
14633 #[test]
14634 fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
14635 struct QueuePendingThenReject {
14636 registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14637 }
14638
14639 impl CurrentThreadTaskDriver for QueuePendingThenReject {
14640 fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14641 assert_eq!(delivery.dispatch, 81);
14642 let completion = self
14643 .registry
14644 .upgrade()
14645 .expect("the registry must outlive publication")
14646 .dispatch_with_completion(82);
14647 assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
14648 assert!(completion.failed_dispatches.is_empty());
14649 false
14650 }
14651 }
14652
14653 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14654 registry.register(Arc::new(QueuePendingThenReject {
14655 registry: Arc::downgrade(®istry),
14656 }));
14657
14658 let completion = registry.dispatch_with_completion(81);
14659 assert_eq!(
14660 completion.result,
14661 CurrentThreadHostDispatchResult::Unavailable
14662 );
14663 let failure = current_thread_dispatch_failure(82, 0);
14664 assert_eq!(
14665 completion.failed_dispatches,
14666 vec![failure],
14667 "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
14668 );
14669 assert!(
14670 registry
14671 .state
14672 .lock()
14673 .unwrap()
14674 .dispatches
14675 .get(&82)
14676 .is_some_and(CurrentThreadDispatchState::is_cancelling)
14677 );
14678 registry.finish_cancellation(failure);
14679 assert!(registry.state.lock().unwrap().dispatches.is_empty());
14680 }
14681
14682 #[test]
14683 fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
14684 let registry = CurrentThreadTaskDriverRegistry::default();
14685 let host = Arc::new(ManualCurrentThreadTaskDriver::default());
14686 let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
14687 assert_eq!(
14688 registry.dispatch(91),
14689 CurrentThreadHostDispatchResult::Accepted
14690 );
14691 let old_delivery = host.deliveries()[0];
14692 assert_eq!(
14693 registry.dispatch(92),
14694 CurrentThreadHostDispatchResult::Accepted
14695 );
14696
14697 registry.retire_for_shutdown();
14698 {
14699 let state = registry.state.lock().unwrap();
14700 assert!(state.dispatches.is_empty());
14701 let entry = state
14702 .entries
14703 .get(&host_id)
14704 .expect("the host must remain registered");
14705 assert_eq!(entry.pending, None);
14706 assert!(
14707 entry.delivery.is_some(),
14708 "the physical callback remains single-flight until it actually retires"
14709 );
14710 }
14711 assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
14712
14713 assert_eq!(
14714 registry.dispatch(93),
14715 CurrentThreadHostDispatchResult::Accepted
14716 );
14717 assert_eq!(host.deliveries().len(), 1);
14718 let completion = registry.finish_delivery(old_delivery, true);
14719 assert!(
14720 completion.failed_dispatches.is_empty(),
14721 "a retired-generation failure must not cancel restarted work"
14722 );
14723 assert!(completion.redispatch);
14724 assert_eq!(
14725 registry.dispatch(93),
14726 CurrentThreadHostDispatchResult::Accepted
14727 );
14728 let restarted_delivery = host.deliveries()[1];
14729 assert_eq!(restarted_delivery.dispatch, 93);
14730 assert_eq!(
14731 registry.claim_delivery(restarted_delivery.capability()),
14732 Some(93)
14733 );
14734 registry.mark_serviced(93);
14735 assert!(
14736 registry
14737 .finish_delivery(restarted_delivery, false)
14738 .failed_dispatches
14739 .is_empty()
14740 );
14741
14742 registry.unregister(host_id);
14743 let state = registry.state.lock().unwrap();
14744 assert!(state.entries.is_empty());
14745 assert!(state.dispatches.is_empty());
14746 }
14747
14748 #[test]
14749 fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
14750 use std::{sync::mpsc, time::Duration};
14751
14752 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14753 let fallback = Arc::new(TestCurrentThreadTaskDriver {
14754 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
14755 dispatched: AtomicBool::new(false),
14756 });
14757 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
14758 let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
14759 registry: Arc::downgrade(®istry),
14760 registration: Mutex::new(None),
14761 });
14762 let reentrant_id =
14763 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
14764 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
14765
14766 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
14767 let selection_registry = Arc::clone(®istry);
14768 let selection_thread = std::thread::spawn(move || {
14769 selection_tx
14770 .send(selection_registry.current().map(|(id, _)| id))
14771 .unwrap();
14772 });
14773 assert_eq!(
14774 selection_rx
14775 .recv_timeout(Duration::from_secs(10))
14776 .expect("is_live re-entry deadlocked"),
14777 Some(fallback_id)
14778 );
14779 selection_thread.join().unwrap();
14780
14781 let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
14782 Arc::new(ReentrantCurrentThreadTaskDriverDrop {
14783 registry: Arc::downgrade(®istry),
14784 fallback: fallback_id,
14785 });
14786 let drop_id = registry.register(Arc::clone(&drop_driver));
14787 drop(drop_driver);
14788
14789 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
14790 let drop_registry = Arc::clone(®istry);
14791 let drop_thread = std::thread::spawn(move || {
14792 drop_registry.unregister(drop_id);
14793 drop_tx.send(()).unwrap();
14794 });
14795 drop_rx
14796 .recv_timeout(Duration::from_secs(10))
14797 .expect("driver destructor re-entry deadlocked");
14798 drop_thread.join().unwrap();
14799 assert!(
14800 registry.current().is_none(),
14801 "the destructor must unregister the fallback"
14802 );
14803 }
14804
14805 #[test]
14806 fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
14807 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
14808
14809 if std::env::var_os(CHILD_ENV).is_some() {
14810 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14811 let fallback = Arc::new(TestCurrentThreadTaskDriver {
14812 behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
14813 dispatched: AtomicBool::new(false),
14814 });
14815 let fallback_id =
14816 registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
14817 let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
14818 registry: Arc::downgrade(®istry),
14819 registrations: Mutex::new(Vec::new()),
14820 });
14821 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
14822
14823 let first_drops = Arc::new(AtomicUsize::new(0));
14824 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
14825 drops: Arc::clone(&first_drops),
14826 });
14827 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
14828 drop(first);
14829
14830 let second_drops = Arc::new(AtomicUsize::new(0));
14831 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
14832 drops: Arc::clone(&second_drops),
14833 });
14834 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
14835 drop(second);
14836 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
14837
14838 assert_eq!(
14839 registry.current().map(|(id, _)| id),
14840 Some(fallback_id),
14841 "selection must retry after reentrant removals and preserve the healthy fallback"
14842 );
14843 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
14844 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
14845 let state = registry
14846 .state
14847 .lock()
14848 .unwrap_or_else(std::sync::PoisonError::into_inner);
14849 assert_eq!(state.entries.len(), 1);
14850 assert!(state.entries.contains_key(&fallback_id));
14851 assert!(state.dispatches.is_empty());
14852 return;
14853 }
14854
14855 let output = std::process::Command::new(std::env::current_exe().unwrap())
14856 .arg("--exact")
14857 .arg(
14858 "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
14859 )
14860 .arg("--nocapture")
14861 .env(CHILD_ENV, "1")
14862 .output()
14863 .expect("the hostile task-driver snapshot subprocess test must start");
14864 assert!(
14865 output.status.success(),
14866 "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
14867 output.status.code(),
14868 String::from_utf8_lossy(&output.stdout),
14869 String::from_utf8_lossy(&output.stderr)
14870 );
14871 }
14872
14873 #[test]
14874 fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
14875 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
14876
14877 if std::env::var_os(CHILD_ENV).is_some() {
14878 let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14879
14880 let first_drops = Arc::new(AtomicUsize::new(0));
14881 let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
14882 drops: Arc::clone(&first_drops),
14883 });
14884 let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
14885 drop(first);
14886
14887 let second_drops = Arc::new(AtomicUsize::new(0));
14888 let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
14889 drops: Arc::clone(&second_drops),
14890 });
14891 let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
14892 drop(second);
14893
14894 let reentrant_drops = Arc::new(AtomicUsize::new(0));
14895 let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14896 registry: Arc::downgrade(®istry),
14897 registrations: Mutex::new(Vec::new()),
14898 drops: Arc::clone(&reentrant_drops),
14899 });
14900 let reentrant_id =
14901 registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
14902 *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
14903 drop(reentrant);
14904
14905 let dispatch = 59;
14906 let failure = current_thread_dispatch_failure(dispatch, 0);
14907 assert_eq!(
14908 registry.dispatch(dispatch),
14909 CurrentThreadHostDispatchResult::Failed(failure),
14910 "reentrant removal of every provisional host must report accepted delivery failure"
14911 );
14912 assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
14913 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
14914 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
14915 registry.finish_cancellation(failure);
14916 let state = registry
14917 .state
14918 .lock()
14919 .unwrap_or_else(std::sync::PoisonError::into_inner);
14920 assert!(state.entries.is_empty());
14921 assert!(state.dispatches.is_empty());
14922 return;
14923 }
14924
14925 let output = std::process::Command::new(std::env::current_exe().unwrap())
14926 .arg("--exact")
14927 .arg(
14928 "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
14929 )
14930 .arg("--nocapture")
14931 .env(CHILD_ENV, "1")
14932 .output()
14933 .expect("the hostile task-driver delivery subprocess test must start");
14934 assert!(
14935 output.status.success(),
14936 "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
14937 output.status.code(),
14938 String::from_utf8_lossy(&output.stdout),
14939 String::from_utf8_lossy(&output.stderr)
14940 );
14941 }
14942
14943 #[test]
14944 fn dropping_join_handle_detaches_task_like_tokio() {
14945 let completed = Arc::new(AtomicBool::new(false));
14946 let completed_task = Arc::clone(&completed);
14947 let (runnable, task) = async_task::spawn(
14948 async move {
14949 completed_task.store(true, Ordering::SeqCst);
14950 Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
14951 },
14952 |_| {},
14953 );
14954 let handle = JoinHandle(JoinHandleInner::Task {
14955 task: task.fallible(),
14956 awaiter: None,
14957 dependency: Arc::new(TaskDependency::default()),
14958 });
14959
14960 drop(handle);
14961 runnable.run();
14962 assert!(completed.load(Ordering::SeqCst));
14963 }
14964
14965 #[cfg(not(target_family = "wasm"))]
14966 #[test]
14967 fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
14968 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
14969
14970 if std::env::var_os(CHILD_ENV).is_some() {
14971 let wakes = Arc::new(AtomicUsize::new(0));
14972 let drops = Arc::new(AtomicUsize::new(0));
14973 let (runnable, task) = async_task::spawn(
14974 async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
14975 |_| {},
14976 );
14977 let mut handle = JoinHandle(JoinHandleInner::Task {
14978 task: task.fallible(),
14979 awaiter: None,
14980 dependency: Arc::new(TaskDependency::default()),
14981 });
14982 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
14983 {
14984 let mut cx = Context::from_waker(&waker);
14985 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
14986 }
14987 drop(waker);
14988
14989 runnable.run();
14990
14991 let waker = futures::task::noop_waker();
14992 let mut cx = Context::from_waker(&waker);
14993 assert!(matches!(
14994 Pin::new(&mut handle).poll(&mut cx),
14995 Poll::Ready(Ok(()))
14996 ));
14997 assert_eq!(
14998 drops.load(Ordering::SeqCst),
14999 1,
15000 "a completed handle must not retain the caller's awaiter"
15001 );
15002 drop(handle);
15003 assert!(wakes.load(Ordering::SeqCst) >= 1);
15004 assert_eq!(drops.load(Ordering::SeqCst), 1);
15005 return;
15006 }
15007
15008 let output = std::process::Command::new(std::env::current_exe().unwrap())
15009 .arg("--exact")
15010 .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
15011 .arg("--nocapture")
15012 .env(CHILD_ENV, "1")
15013 .output()
15014 .expect("the JoinHandle completion awaiter subprocess must start");
15015 assert!(
15016 output.status.success(),
15017 "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15018 output.status.code(),
15019 String::from_utf8_lossy(&output.stdout),
15020 String::from_utf8_lossy(&output.stderr)
15021 );
15022 }
15023
15024 #[cfg(not(target_family = "wasm"))]
15025 #[test]
15026 fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
15027 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
15028
15029 if std::env::var_os(CHILD_ENV).is_some() {
15030 let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
15031 let wakes = Arc::new(AtomicUsize::new(0));
15032 let drops = Arc::new(AtomicUsize::new(0));
15033 let mut handle = controller.spawn(std::future::pending::<()>());
15034 let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15035 {
15036 let mut cx = Context::from_waker(&waker);
15037 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15038 }
15039 drop(waker);
15040
15041 controller
15042 .shutdown()
15043 .expect("shutdown must survive the hostile awaiter");
15044 drop(handle);
15045 assert!(wakes.load(Ordering::SeqCst) >= 1);
15046 assert_eq!(drops.load(Ordering::SeqCst), 1);
15047 return;
15048 }
15049
15050 let output = std::process::Command::new(std::env::current_exe().unwrap())
15051 .arg("--exact")
15052 .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
15053 .arg("--nocapture")
15054 .env(CHILD_ENV, "1")
15055 .output()
15056 .expect("the JoinHandle shutdown awaiter subprocess must start");
15057 assert!(
15058 output.status.success(),
15059 "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15060 output.status.code(),
15061 String::from_utf8_lossy(&output.stdout),
15062 String::from_utf8_lossy(&output.stderr)
15063 );
15064 }
15065
15066 #[cfg(not(target_family = "wasm"))]
15067 #[test]
15068 fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
15069 let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
15070 let mut handle = controller.spawn(std::future::pending::<()>());
15071 let old_generation = controller.backend().generation();
15072 let state = Arc::new(JoinAwaiterWakerState {
15073 events: Mutex::new(Vec::new()),
15074 });
15075 let waker = {
15076 let _generation = RuntimeGenerationGuard::enter(old_generation);
15077 join_awaiter_waker(Arc::clone(&state))
15078 };
15079 {
15080 let _generation = RuntimeGenerationGuard::enter(old_generation);
15081 let mut cx = Context::from_waker(&waker);
15082 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15083 }
15084 {
15085 let _generation = RuntimeGenerationGuard::enter(old_generation);
15086 drop(waker);
15087 }
15088
15089 controller
15090 .shutdown()
15091 .expect("the first generation must stop");
15092 controller.start().expect("the runtime must restart");
15093 let new_generation = controller.backend().generation();
15094 assert_ne!(new_generation, old_generation);
15095 state.clear();
15096
15097 let replacement = futures::task::noop_waker();
15098 {
15099 let _generation = RuntimeGenerationGuard::enter(new_generation);
15100 let mut cx = Context::from_waker(&replacement);
15101 assert!(matches!(
15102 Pin::new(&mut handle).poll(&mut cx),
15103 Poll::Ready(Err(_))
15104 ));
15105 }
15106
15107 let events = state.snapshot();
15108 assert!(
15109 !events.is_empty(),
15110 "completion must retire the registered awaiter waker"
15111 );
15112 assert!(
15113 events.iter().all(|(kind, generation)| {
15114 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15115 }),
15116 "old-generation awaiter retirement escaped its generation: {events:?}"
15117 );
15118 controller
15119 .shutdown()
15120 .expect("the replacement generation must stop");
15121 }
15122
15123 #[cfg(not(target_family = "wasm"))]
15124 #[test]
15125 fn dropping_join_handle_retains_awaiter_generation_after_restart() {
15126 let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
15127 let mut handle = controller.spawn(std::future::pending::<()>());
15128 let old_generation = controller.backend().generation();
15129 let state = Arc::new(JoinAwaiterWakerState {
15130 events: Mutex::new(Vec::new()),
15131 });
15132 let waker = {
15133 let _generation = RuntimeGenerationGuard::enter(old_generation);
15134 join_awaiter_waker(Arc::clone(&state))
15135 };
15136 {
15137 let _generation = RuntimeGenerationGuard::enter(old_generation);
15138 let mut cx = Context::from_waker(&waker);
15139 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15140 }
15141 {
15142 let _generation = RuntimeGenerationGuard::enter(old_generation);
15143 drop(waker);
15144 }
15145
15146 controller
15147 .shutdown()
15148 .expect("the first generation must stop");
15149 controller.start().expect("the runtime must restart");
15150 let new_generation = controller.backend().generation();
15151 assert_ne!(new_generation, old_generation);
15152 state.clear();
15153
15154 {
15155 let _generation = RuntimeGenerationGuard::enter(new_generation);
15156 drop(handle);
15157 }
15158
15159 let events = state.snapshot();
15160 assert!(
15161 !events.is_empty(),
15162 "handle drop must retire the registered awaiter waker"
15163 );
15164 assert!(
15165 events.iter().all(|(kind, generation)| {
15166 *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15167 }),
15168 "old-generation awaiter destruction escaped its generation: {events:?}"
15169 );
15170 controller
15171 .shutdown()
15172 .expect("the replacement generation must stop");
15173 }
15174
15175 #[cfg(not(target_family = "wasm"))]
15176 #[test]
15177 fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
15178 use std::{sync::mpsc, time::Duration};
15179
15180 let options = RuntimeOptions {
15181 flavor: RuntimeFlavor::MultiThread,
15182 worker_threads: 2,
15183 max_blocking_tasks: 1,
15184 thread_name_prefix: "worker-classification".to_string(),
15185 park_deadline: None,
15186 };
15187 let executor =
15188 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
15189 let id = executor.id;
15190 let (tx, rx) = mpsc::sync_channel(1);
15191
15192 executor.pool.spawn(move || {
15193 tx.send((
15194 ON_POOL_WORKER.with(std::cell::Cell::get),
15195 IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
15196 ))
15197 .unwrap();
15198 });
15199
15200 let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
15201 assert_eq!(
15202 worker,
15203 Some(id),
15204 "nested Rayon work must use cooperative block_on"
15205 );
15206 assert_eq!(
15207 driver, None,
15208 "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
15209 );
15210 }
15211
15212 #[cfg(not(target_family = "wasm"))]
15213 #[test]
15214 fn multi_thread_block_on_does_not_park_pool_worker() {
15215 use std::sync::{Barrier, mpsc};
15220 use std::time::Duration;
15221
15222 let (done_tx, done_rx) = mpsc::channel();
15223 let runner = std::thread::spawn(move || {
15224 let metrics = Arc::new(RuntimeMetrics::default());
15225 let options = RuntimeOptions {
15226 flavor: RuntimeFlavor::MultiThread,
15227 worker_threads: 2,
15228 max_blocking_tasks: 2,
15229 thread_name_prefix: "rd1-test".to_string(),
15230 park_deadline: None,
15231 };
15232 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15233
15234 let task_count = 2usize;
15235 let barrier = Arc::new(Barrier::new(task_count));
15238
15239 let mut tasks = Vec::new();
15240 for _ in 0..task_count {
15241 let exec = Arc::clone(&executor);
15242 let barrier = Arc::clone(&barrier);
15243 let body = async move {
15244 barrier.wait();
15245 let inner_exec = Arc::clone(&exec);
15246 let mut inner = std::pin::pin!(async move {
15247 let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
15248 assert_eq!(value, 7);
15249 });
15250 exec.block_on(inner.as_mut());
15252 };
15253 let scheduler = Arc::clone(&executor);
15254 let (runnable, task) =
15255 async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
15256 executor.schedule(runnable);
15257 tasks.push(task);
15258 }
15259
15260 for task in tasks {
15261 futures::executor::block_on(task);
15262 }
15263 let _ = done_tx.send(());
15264 });
15265
15266 match done_rx.recv_timeout(Duration::from_secs(10)) {
15267 Ok(()) => runner.join().unwrap(),
15268 Err(error) => panic!(
15269 "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
15270 ),
15271 }
15272 }
15273
15274 #[cfg(not(target_family = "wasm"))]
15275 #[test]
15276 fn multi_thread_block_on_from_caller_thread_still_parks() {
15277 let metrics = Arc::new(RuntimeMetrics::default());
15280 let options = RuntimeOptions {
15281 flavor: RuntimeFlavor::MultiThread,
15282 worker_threads: 3,
15283 max_blocking_tasks: 2,
15284 thread_name_prefix: "rd1-caller".to_string(),
15285 park_deadline: None,
15286 };
15287 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15288
15289 let mut output = None;
15290 let mut future = std::pin::pin!(async {
15291 output = Some(41usize + 1);
15292 });
15293 executor.block_on(future.as_mut());
15294 assert_eq!(output, Some(42));
15295 }
15296
15297 #[cfg(not(target_family = "wasm"))]
15298 #[test]
15299 fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
15300 use std::sync::mpsc;
15323 use std::time::Duration;
15324
15325 struct CallerProbe {
15330 task: Task<usize>,
15331 parked_tx: Option<mpsc::Sender<()>>,
15332 }
15333 impl Future for CallerProbe {
15334 type Output = usize;
15335 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
15336 let poll = Pin::new(&mut self.task).poll(cx);
15338 if poll.is_pending()
15339 && let Some(tx) = self.parked_tx.take()
15340 {
15341 let _ = tx.send(());
15343 }
15344 poll
15345 }
15346 }
15347
15348 let (done_tx, done_rx) = mpsc::channel();
15349 let runner = std::thread::spawn(move || {
15350 let metrics = Arc::new(RuntimeMetrics::default());
15351 let options = RuntimeOptions {
15352 flavor: RuntimeFlavor::MultiThread,
15353 worker_threads: 2,
15354 max_blocking_tasks: 2,
15355 thread_name_prefix: "rd10-caller".to_string(),
15356 park_deadline: None,
15357 };
15358 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15359
15360 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
15365 let scheduler = Arc::clone(&executor);
15366 let (runnable, task) = async_task::spawn(
15367 async move { gate_rx.await.expect("gate sender dropped") },
15368 move |r| {
15369 scheduler.schedule(r);
15370 },
15371 );
15372 executor.schedule(runnable);
15373
15374 let (parked_tx, parked_rx) = mpsc::channel::<()>();
15378 let releaser = std::thread::spawn(move || {
15379 if parked_rx.recv().is_ok() {
15380 let _ = gate_tx.send(42usize);
15381 }
15382 });
15383
15384 let mut output = None;
15387 {
15388 let mut future = std::pin::pin!(async {
15389 output = Some(
15390 CallerProbe {
15391 task,
15392 parked_tx: Some(parked_tx),
15393 }
15394 .await,
15395 );
15396 });
15397 executor.block_on(future.as_mut());
15398 }
15399
15400 releaser.join().unwrap();
15401 assert_eq!(
15402 output,
15403 Some(42usize),
15404 "block_on must return the pool-computed value"
15405 );
15406 let _ = done_tx.send(());
15407 });
15408
15409 match done_rx.recv_timeout(Duration::from_secs(10)) {
15410 Ok(()) => runner.join().unwrap(),
15411 Err(error) => panic!(
15412 "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
15413 ),
15414 }
15415 }
15416
15417 #[cfg(not(target_family = "wasm"))]
15418 #[test]
15419 fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
15420 use std::sync::{Barrier, mpsc};
15426 use std::time::Duration;
15427
15428 let (done_tx, done_rx) = mpsc::channel();
15429 let runner = std::thread::spawn(move || {
15430 let metrics = Arc::new(RuntimeMetrics::default());
15431 let options = RuntimeOptions {
15432 flavor: RuntimeFlavor::MultiThread,
15433 worker_threads: 2,
15434 max_blocking_tasks: 2,
15435 thread_name_prefix: "rd1-park".to_string(),
15436 park_deadline: None,
15437 };
15438 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15439
15440 let task_count = 2usize;
15441 let barrier = Arc::new(Barrier::new(task_count));
15442
15443 let mut senders = Vec::new();
15444 let mut tasks = Vec::new();
15445 for _ in 0..task_count {
15446 let (tx, rx) = oneshot::channel::<usize>();
15447 senders.push(tx);
15448 let exec = Arc::clone(&executor);
15449 let barrier = Arc::clone(&barrier);
15450 let body = async move {
15451 barrier.wait();
15453 let child_exec = Arc::clone(&exec);
15456 let (child_runnable, child_task) =
15457 async_task::spawn(async move { rx.await.unwrap() }, move |r| {
15458 child_exec.schedule(r)
15459 });
15460 exec.schedule(child_runnable);
15461 let mut inner = std::pin::pin!(async move {
15462 assert_eq!(child_task.await, 9usize);
15463 });
15464 exec.block_on(inner.as_mut());
15465 };
15466 let scheduler = Arc::clone(&executor);
15467 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
15468 executor.schedule(runnable);
15469 tasks.push(task);
15470 }
15471
15472 std::thread::sleep(Duration::from_millis(200));
15474 for tx in senders {
15475 let _ = tx.send(9usize);
15476 }
15477
15478 for task in tasks {
15479 futures::executor::block_on(task);
15480 }
15481 let _ = done_tx.send(());
15482 });
15483
15484 match done_rx.recv_timeout(Duration::from_secs(10)) {
15485 Ok(()) => runner.join().unwrap(),
15486 Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
15487 }
15488 }
15489
15490 #[cfg(not(target_family = "wasm"))]
15496 fn wait_until(what: &str, condition: impl Fn() -> bool) {
15497 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
15498 while !condition() {
15499 assert!(
15500 std::time::Instant::now() < deadline,
15501 "timed out waiting for: {what}"
15502 );
15503 std::thread::yield_now();
15504 }
15505 }
15506
15507 #[cfg(not(target_family = "wasm"))]
15516 fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
15517 let deadline = std::time::Instant::now() + timeout;
15518 while !handle.is_finished() {
15519 assert!(
15520 std::time::Instant::now() < deadline,
15521 "thread wedged past its {timeout:?} bound while: {what}"
15522 );
15523 std::thread::sleep(Duration::from_millis(10));
15524 }
15525 handle.join().unwrap();
15526 }
15527
15528 #[cfg(not(target_family = "wasm"))]
15529 fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
15530 executor: &Arc<MultiThreadExecutor>,
15531 start_counter: &AtomicU64,
15532 claim: impl FnOnce() -> T + Send + 'static,
15533 ) -> T {
15534 use std::sync::mpsc;
15535
15536 let (claimed_tx, claimed_rx) = mpsc::channel();
15537 let (release_claim_tx, release_claim_rx) = mpsc::channel();
15538 let (started_tx, started_rx) = mpsc::channel();
15539 let (release_start_tx, release_start_rx) = mpsc::channel();
15540 let starts_before = start_counter.load(Ordering::SeqCst);
15541 let worker = std::thread::spawn(move || {
15542 MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
15543 *slot.borrow_mut() = Some(Box::new(move || {
15544 claimed_tx.send(()).unwrap();
15545 release_claim_rx.recv().unwrap();
15546 }));
15547 });
15548 MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
15549 *slot.borrow_mut() = Some(Box::new(move || {
15550 started_tx.send(()).unwrap();
15551 release_start_rx.recv().unwrap();
15552 }));
15553 });
15554 claim()
15555 });
15556 claimed_rx
15557 .recv_timeout(Duration::from_secs(2))
15558 .expect("the scheduler claim must pause after removing work");
15559
15560 let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
15561 let verdict = executor.try_begin_deadlock_verdict();
15562 let claim_verdict_rejected = verdict.is_none();
15563 drop(verdict);
15564 release_claim_tx.send(()).unwrap();
15565
15566 started_rx
15567 .recv_timeout(Duration::from_secs(2))
15568 .expect("the scheduler claim must pause after publishing its start");
15569 let admission_active_after_start =
15570 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
15571 let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
15572 let verdict = executor.try_begin_deadlock_verdict();
15573 let start_verdict_rejected = verdict.is_none();
15574 drop(verdict);
15575 release_start_tx.send(()).unwrap();
15576 let output = worker.join().unwrap();
15577
15578 assert!(
15579 admission_active,
15580 "removing scheduler work must retain a detector admission until start accounting"
15581 );
15582 assert!(
15583 claim_verdict_rejected,
15584 "a deadline verdict must not close while removed work has not published its start"
15585 );
15586 assert!(
15587 start_published,
15588 "work start accounting must publish before admission retires"
15589 );
15590 assert!(
15591 admission_active_after_start,
15592 "the detector admission must remain active until after start accounting"
15593 );
15594 assert!(
15595 start_verdict_rejected,
15596 "a deadline verdict must not close between start accounting and admission retirement"
15597 );
15598 output
15599 }
15600
15601 #[cfg(not(target_family = "wasm"))]
15602 #[test]
15603 fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
15604 let executor =
15605 multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
15606
15607 let fifo_ran = Arc::new(AtomicBool::new(false));
15608 let fifo_task_ran = Arc::clone(&fifo_ran);
15609 let (fifo, fifo_task) = async_task::spawn(
15610 async move {
15611 fifo_task_ran.store(true, Ordering::SeqCst);
15612 },
15613 |_| {},
15614 );
15615 executor.metrics.runnable_scheduled();
15616 executor.queue.lock().unwrap().push_back(fifo);
15617 let fifo_executor = Arc::clone(&executor);
15618 assert!(run_claim_with_deadlock_gate_assertion(
15619 &executor,
15620 &executor.metrics.runnable_polls,
15621 move || {
15622 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
15623 fifo_executor.run_one_fifo_runnable()
15624 }
15625 ));
15626 futures::executor::block_on(fifo_task);
15627 assert!(fifo_ran.load(Ordering::SeqCst));
15628
15629 let fifo_order = Arc::new(Mutex::new(Vec::new()));
15630 let mut fifo_tasks = Vec::new();
15631 for name in ["first", "second"] {
15632 let task_order = Arc::clone(&fifo_order);
15633 let (runnable, task) = async_task::spawn(
15634 async move {
15635 task_order.lock().unwrap().push(name);
15636 },
15637 |_| {},
15638 );
15639 executor.metrics.runnable_scheduled();
15640 executor.queue.lock().unwrap().push_back(runnable);
15641 fifo_tasks.push(task);
15642 }
15643 for _ in 0..2 {
15644 let fifo_executor = Arc::clone(&executor);
15645 assert!(run_claim_with_deadlock_gate_assertion(
15646 &executor,
15647 &executor.metrics.runnable_polls,
15648 move || {
15649 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
15650 fifo_executor.run_one_runnable()
15651 }
15652 ));
15653 }
15654 for task in fifo_tasks {
15655 futures::executor::block_on(task);
15656 }
15657 assert_eq!(
15658 *fifo_order.lock().unwrap(),
15659 ["first", "second"],
15660 "the detector-enabled normal fallback must preserve FIFO order"
15661 );
15662
15663 let lifo_ran = Arc::new(AtomicBool::new(false));
15664 let lifo_task_ran = Arc::clone(&lifo_ran);
15665 let (lifo, lifo_task) = async_task::spawn(
15666 async move {
15667 lifo_task_ran.store(true, Ordering::SeqCst);
15668 },
15669 |_| {},
15670 );
15671 executor.metrics.runnable_scheduled();
15672 let lifo_executor = Arc::clone(&executor);
15673 assert!(run_claim_with_deadlock_gate_assertion(
15674 &executor,
15675 &executor.metrics.runnable_polls,
15676 move || {
15677 let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
15678 LIFO_SLOT.with(|slot| {
15679 assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
15680 });
15681 lifo_executor.run_one_runnable()
15682 }
15683 ));
15684 futures::executor::block_on(lifo_task);
15685 assert!(lifo_ran.load(Ordering::SeqCst));
15686
15687 executor.begin_shutdown();
15688 executor.wait_until_scheduler_idle();
15689 }
15690
15691 #[cfg(not(target_family = "wasm"))]
15692 #[test]
15693 fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
15694 let executor =
15695 multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
15696
15697 let fifo_ran = Arc::new(AtomicBool::new(false));
15698 let fifo_job_ran = Arc::clone(&fifo_ran);
15699 executor.metrics.blocking_scheduled();
15700 executor
15701 .blocking_queue
15702 .lock()
15703 .unwrap()
15704 .push(QueuedBlocking {
15705 id: BlockingJobId(u64::MAX - 90),
15706 run: Box::new(move || {
15707 fifo_job_ran.store(true, Ordering::SeqCst);
15708 }),
15709 });
15710 let fifo_executor = Arc::clone(&executor);
15711 assert!(run_claim_with_deadlock_gate_assertion(
15712 &executor,
15713 &executor.metrics.blocking_tasks_started,
15714 move || {
15715 let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
15716 fifo_executor.run_one_blocking()
15717 }
15718 ));
15719 assert!(fifo_ran.load(Ordering::SeqCst));
15720
15721 let owner = executor.fresh_owner_token();
15722 let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
15723 let exact_job = BlockingJobId(u64::MAX - 91);
15724 let exact_ran = Arc::new(AtomicBool::new(false));
15725 let exact_job_ran = Arc::clone(&exact_ran);
15726 executor.metrics.blocking_scheduled();
15727 executor
15728 .blocking_queue
15729 .lock()
15730 .unwrap()
15731 .push(QueuedBlocking {
15732 id: exact_job,
15733 run: Box::new(move || {
15734 exact_job_ran.store(true, Ordering::SeqCst);
15735 }),
15736 });
15737 let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
15738 dependency.set_owned(BlockingDependency {
15739 job: exact_job,
15740 owner: Some(owner),
15741 });
15742 let exact_executor = Arc::clone(&executor);
15743 assert!(run_claim_with_deadlock_gate_assertion(
15744 &executor,
15745 &executor.metrics.blocking_tasks_started,
15746 move || {
15747 let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
15748 let _owner = BlockingOwnerGuard::enter(Some(owner));
15749 exact_executor.try_owned_blocking_over_cap(&dependency)
15750 }
15751 ));
15752 assert!(exact_ran.load(Ordering::SeqCst));
15753
15754 drop(active_owner);
15755 executor.begin_shutdown();
15756 executor.wait_until_scheduler_idle();
15757 }
15758
15759 #[cfg(not(target_family = "wasm"))]
15760 #[test]
15761 fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
15762 use std::sync::mpsc;
15770 use std::time::Duration;
15771
15772 let registry = Arc::new(ParkedDrivers::default());
15773
15774 let guard = registry.parked.lock().unwrap();
15776 let (done_tx, done_rx) = mpsc::channel();
15777 let waker_thread = {
15778 let registry = Arc::clone(®istry);
15779 std::thread::spawn(move || {
15780 done_tx.send(registry.wake_one()).unwrap();
15781 })
15782 };
15783 let woke = done_rx
15784 .recv_timeout(Duration::from_secs(5))
15785 .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
15786 assert!(!woke, "an empty registry has nobody to wake");
15787 waker_thread.join().unwrap();
15788 drop(guard);
15789
15790 let parker = Arc::new(DriverParker::default());
15794 registry.register(&parker);
15795 let guard = registry.parked.lock().unwrap();
15796 let (done_tx, done_rx) = mpsc::channel();
15797 let waker_thread = {
15798 let registry = Arc::clone(®istry);
15799 std::thread::spawn(move || {
15800 done_tx.send(registry.wake_one()).unwrap();
15801 })
15802 };
15803 assert!(
15804 done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
15805 "wake_one with a registered parker must take the slow path (block on the held lock)"
15806 );
15807 drop(guard);
15808 let woke = done_rx
15809 .recv_timeout(Duration::from_secs(5))
15810 .expect("slow-path wake_one must complete once the lock is released");
15811 assert!(
15812 woke,
15813 "the slow path must pop and wake the registered parker"
15814 );
15815 waker_thread.join().unwrap();
15816 parker.park();
15819 assert_eq!(
15820 registry.count.load(Ordering::SeqCst),
15821 0,
15822 "wake_one deregisters what it pops"
15823 );
15824 }
15825
15826 #[cfg(not(target_family = "wasm"))]
15827 #[test]
15828 fn driver_parker_permit_granted_while_running_is_not_lost() {
15829 use std::sync::mpsc;
15833 use std::time::Duration;
15834
15835 let parker = Arc::new(DriverParker::default());
15836 parker.unpark();
15838 parker.unpark();
15839
15840 let (tx, rx) = mpsc::channel();
15841 let driver = {
15842 let parker = Arc::clone(&parker);
15843 std::thread::spawn(move || {
15844 parker.park(); tx.send(1).unwrap();
15846 parker.park(); tx.send(2).unwrap();
15848 })
15849 };
15850
15851 assert_eq!(
15852 rx.recv_timeout(Duration::from_secs(5))
15853 .expect("a stored permit must not be lost"),
15854 1
15855 );
15856 assert!(
15857 rx.recv_timeout(Duration::from_millis(300)).is_err(),
15858 "two pre-park wakes must coalesce into one permit (second park must sleep)"
15859 );
15860 parker.unpark();
15861 assert_eq!(
15862 rx.recv_timeout(Duration::from_secs(5))
15863 .expect("unpark must wake a sleeping driver"),
15864 2
15865 );
15866 driver.join().unwrap();
15867 }
15868
15869 #[cfg(not(target_family = "wasm"))]
15870 #[test]
15871 fn driver_parker_unrepresentable_timeout_remains_wakeable() {
15872 use std::sync::mpsc;
15873
15874 let parker = Arc::new(DriverParker::default());
15875 assert!(
15876 Instant::now().checked_add(Duration::MAX).is_none(),
15877 "Duration::MAX must exercise the unrepresentable-deadline path"
15878 );
15879 let (done_tx, done_rx) = mpsc::channel();
15880 let driver = {
15881 let parker = Arc::clone(&parker);
15882 std::thread::spawn(move || {
15883 done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
15884 })
15885 };
15886 wait_until("the unbounded timeout driver to sleep", || {
15887 parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
15888 });
15889 assert!(
15890 done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
15891 "an unrepresentable timeout must not expire immediately"
15892 );
15893
15894 let unparker = {
15895 let parker = Arc::clone(&parker);
15896 std::thread::spawn(move || parker.unpark())
15897 };
15898 assert!(
15899 done_rx
15900 .recv_timeout(Duration::from_secs(1))
15901 .expect("an unrepresentable timeout must remain wakeable"),
15902 "a permit must be reported as a wake"
15903 );
15904 unparker.join().unwrap();
15905 driver.join().unwrap();
15906 }
15907
15908 #[cfg(not(target_family = "wasm"))]
15909 #[test]
15910 fn parked_drivers_wake_one_wakes_exactly_one() {
15911 use std::sync::mpsc;
15915 use std::time::Duration;
15916
15917 let registry = Arc::new(ParkedDrivers::default());
15918 let (woke_tx, woke_rx) = mpsc::channel();
15919 let mut drivers = Vec::new();
15920 for index in 0..2 {
15921 let registry = Arc::clone(®istry);
15922 let woke_tx = woke_tx.clone();
15923 drivers.push(std::thread::spawn(move || {
15924 let parker = Arc::new(DriverParker::default());
15925 registry.register(&parker);
15926 parker.park();
15927 registry.deregister(&parker);
15928 woke_tx.send(index).unwrap();
15929 }));
15930 }
15931 wait_until("both drivers registered as parked", || {
15932 registry.count.load(Ordering::SeqCst) == 2
15933 });
15934
15935 assert!(registry.wake_one(), "wake_one must wake a parked driver");
15936 woke_rx
15937 .recv_timeout(Duration::from_secs(5))
15938 .expect("exactly one driver must wake");
15939 assert!(
15940 woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
15941 "the second driver must stay parked after a single wake_one"
15942 );
15943 assert_eq!(
15944 registry.count.load(Ordering::SeqCst),
15945 1,
15946 "one driver must remain registered"
15947 );
15948
15949 assert!(
15950 registry.wake_one(),
15951 "a second wake_one must wake the remaining driver"
15952 );
15953 woke_rx
15954 .recv_timeout(Duration::from_secs(5))
15955 .expect("the second driver must wake");
15956 for driver in drivers {
15957 driver.join().unwrap();
15958 }
15959 }
15960
15961 #[cfg(not(target_family = "wasm"))]
15962 #[test]
15963 fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
15964 use std::sync::mpsc;
15965
15966 struct ReenterRegistryOnDrop {
15967 registry: Arc<ParkedDrivers>,
15968 dropped: mpsc::Sender<()>,
15969 }
15970
15971 #[expect(
15972 clippy::manual_noop_waker,
15973 reason = "the test observes the waker payload's Drop"
15974 )]
15975 impl std::task::Wake for ReenterRegistryOnDrop {
15976 fn wake(self: Arc<Self>) {}
15977 }
15978
15979 impl Drop for ReenterRegistryOnDrop {
15980 fn drop(&mut self) {
15981 let parker = Arc::new(DriverParker::default());
15982 self.registry.register(&parker);
15983 self.registry.deregister(&parker);
15984 self.dropped.send(()).unwrap();
15985 }
15986 }
15987
15988 let registry = Arc::new(ParkedDrivers::default());
15989 let dependency = Arc::new(TaskDependency::default());
15990 let (dropped_tx, dropped_rx) = mpsc::channel();
15991 let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
15992 registry: Arc::clone(®istry),
15993 dropped: dropped_tx,
15994 }));
15995 dependency.register_waiter(&waiter);
15996 drop(waiter);
15997
15998 let parker = Arc::new(DriverParker::default());
15999 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16000 drop(dependency);
16001
16002 let retiring_registry = Arc::clone(®istry);
16003 let retiring_parker = Arc::clone(&parker);
16004 let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
16005 dropped_rx
16006 .recv_timeout(Duration::from_secs(1))
16007 .expect("retained dependency waiters must be destroyed after releasing the registry lock");
16008 retiring.join().unwrap();
16009 }
16010
16011 #[cfg(not(target_family = "wasm"))]
16012 #[test]
16013 fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
16014 use std::sync::mpsc;
16015
16016 let registry = Arc::new(ParkedDrivers::default());
16017 let owner = BlockingOwnerToken {
16018 executor_id: u64::MAX - 70,
16019 frame: u64::MAX - 71,
16020 };
16021 let dependency = Arc::new(TaskDependency::default());
16022 dependency.set_owned(BlockingDependency {
16023 job: BlockingJobId(u64::MAX - 72),
16024 owner: Some(owner),
16025 });
16026 let parker = Arc::new(DriverParker::default());
16027 registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16028
16029 let (predicate_tx, predicate_rx) = mpsc::channel();
16030 let (release_tx, release_rx) = mpsc::channel();
16031 let selecting_registry = Arc::clone(®istry);
16032 let selector = std::thread::spawn(move || {
16033 DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
16034 *slot.borrow_mut() = Some(Box::new(move || {
16035 predicate_tx.send(()).unwrap();
16036 release_rx.recv().unwrap();
16037 }));
16038 });
16039 selecting_registry.wake_one_owner_dependency(owner)
16040 });
16041
16042 predicate_rx
16043 .recv_timeout(Duration::from_secs(2))
16044 .expect("owner selection must reach the dependency predicate");
16045 let registry_was_unlocked = registry.parked.try_lock().is_ok();
16046 release_tx.send(()).unwrap();
16047 assert!(
16048 selector.join().unwrap(),
16049 "the exact-owner parker must still be selected"
16050 );
16051
16052 assert!(
16053 registry_was_unlocked,
16054 "owner dependency predicates must not run while the parked-driver registry is locked"
16055 );
16056 assert!(parker.consume_permit());
16057 assert_eq!(parker.take_owner_handoff(), Some(owner));
16058 }
16059
16060 #[cfg(not(target_family = "wasm"))]
16061 #[test]
16062 fn owner_handoff_skips_newer_untagged_dependency() {
16063 let registry = ParkedDrivers::default();
16064 let owner = BlockingOwnerToken {
16065 executor_id: u64::MAX - 1,
16066 frame: u64::MAX - 2,
16067 };
16068 let owned_dependency = Arc::new(TaskDependency::default());
16069 owned_dependency.set_owned(BlockingDependency {
16070 job: BlockingJobId(u64::MAX - 3),
16071 owner: Some(owner),
16072 });
16073 let untagged_dependency = Arc::new(TaskDependency::default());
16074 untagged_dependency.set_owned(BlockingDependency {
16075 job: BlockingJobId(u64::MAX - 4),
16076 owner: None,
16077 });
16078 let owned = Arc::new(DriverParker::default());
16079 let untagged = Arc::new(DriverParker::default());
16080
16081 registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
16082 registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
16085
16086 assert!(registry.wake_one_owner_dependency(owner));
16087 assert!(
16088 owned.consume_permit(),
16089 "the exact owner dependency must receive the retry"
16090 );
16091 assert!(
16092 !untagged.consume_permit(),
16093 "an untagged dependency must not consume an exact owner handoff"
16094 );
16095 registry.deregister(&untagged);
16096 }
16097
16098 #[cfg(not(target_family = "wasm"))]
16099 #[test]
16100 fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
16101 let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
16102 let owner = executor.fresh_owner_token();
16103 let valid_dependency = Arc::new(TaskDependency::default());
16104 valid_dependency.set_owned(BlockingDependency {
16105 job: BlockingJobId(u64::MAX - 45),
16106 owner: Some(owner),
16107 });
16108 let withdrawn_dependency = Arc::new(TaskDependency::default());
16109 withdrawn_dependency.set_owned(BlockingDependency {
16110 job: BlockingJobId(u64::MAX - 46),
16111 owner: Some(owner),
16112 });
16113 let valid = Arc::new(DriverParker::default());
16114 let withdrawn = Arc::new(DriverParker::default());
16115
16116 executor
16117 .parked_drivers
16118 .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
16119 executor.parked_drivers.register_with_role(
16120 &withdrawn,
16121 true,
16122 Some(Arc::clone(&withdrawn_dependency)),
16123 );
16124 assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
16125 withdrawn_dependency.clear();
16126
16127 assert_eq!(
16128 executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
16129 Some(false),
16130 "a selected dependency withdrawn before retry must forward the owner handoff"
16131 );
16132 assert!(
16133 valid.consume_permit(),
16134 "the next live same-owner dependency must receive the forwarded wake"
16135 );
16136 assert_eq!(
16137 valid.take_owner_handoff(),
16138 Some(owner),
16139 "the forwarded wake must retain the exact owner identity"
16140 );
16141 }
16142
16143 #[cfg(not(target_family = "wasm"))]
16144 #[test]
16145 fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
16146 use std::sync::mpsc;
16147
16148 let executor = multi_thread_executor(2, None, "owner-handoff-publication");
16149 let owner = executor.fresh_owner_token();
16150 let fallback_dependency = Arc::new(TaskDependency::default());
16151 fallback_dependency.set_owned(BlockingDependency {
16152 job: BlockingJobId(u64::MAX - 47),
16153 owner: Some(owner),
16154 });
16155 let exiting_dependency = Arc::new(TaskDependency::default());
16156 exiting_dependency.set_owned(BlockingDependency {
16157 job: BlockingJobId(u64::MAX - 48),
16158 owner: Some(owner),
16159 });
16160 let fallback = Arc::new(DriverParker::default());
16161 let exiting = Arc::new(DriverParker::default());
16162
16163 executor.parked_drivers.register_with_role(
16164 &fallback,
16165 true,
16166 Some(Arc::clone(&fallback_dependency)),
16167 );
16168 executor.parked_drivers.register_with_role(
16169 &exiting,
16170 true,
16171 Some(Arc::clone(&exiting_dependency)),
16172 );
16173
16174 let (selected_tx, selected_rx) = mpsc::channel();
16175 let (release_selection_tx, release_selection_rx) = mpsc::channel();
16176 let selector_executor = Arc::clone(&executor);
16177 let selector = std::thread::spawn(move || {
16178 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
16179 *slot.borrow_mut() = Some(Box::new(move || {
16180 selected_tx.send(()).unwrap();
16181 release_selection_rx.recv().unwrap();
16182 }));
16183 });
16184 assert!(
16185 selector_executor
16186 .parked_drivers
16187 .wake_one_owner_dependency(owner)
16188 );
16189 });
16190 selected_rx
16191 .recv_timeout(Duration::from_secs(2))
16192 .expect("selection must pause after removing the exact-owner parker");
16193
16194 let (exited_tx, exited_rx) = mpsc::channel();
16195 let exiting_executor = Arc::clone(&executor);
16196 let exiting_driver = Arc::clone(&exiting);
16197 let driver = std::thread::spawn(move || {
16198 exiting_driver.park();
16199 exiting_executor.parked_drivers.deregister(&exiting_driver);
16200 exiting_executor.forward_owner_handoff(&exiting_driver);
16201 exited_tx.send(()).unwrap();
16202 });
16203 exiting.unpark();
16204 exited_rx
16205 .recv_timeout(Duration::from_secs(2))
16206 .expect("the direct future wake must let the selected driver exit");
16207
16208 release_selection_tx.send(()).unwrap();
16209 selector.join().unwrap();
16210 driver.join().unwrap();
16211
16212 assert!(
16213 fallback.consume_permit(),
16214 "an exiting selected driver must forward the owner handoff to the next dependency"
16215 );
16216 assert_eq!(
16217 fallback.take_owner_handoff(),
16218 Some(owner),
16219 "the forwarded handoff must retain the exact owner identity"
16220 );
16221 }
16222
16223 #[cfg(not(target_family = "wasm"))]
16224 #[test]
16225 fn cooperative_panic_forwards_pending_owner_handoff() {
16226 use std::sync::mpsc;
16227
16228 struct PanicAfterWake {
16229 dependency: BlockingDependency,
16230 first_poll: Option<mpsc::Sender<()>>,
16231 }
16232
16233 impl Future for PanicAfterWake {
16234 type Output = ();
16235
16236 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16237 if let Some(first_poll) = self.first_poll.take() {
16238 record_blocking_dependency(self.dependency);
16239 first_poll.send(()).unwrap();
16240 return Poll::Pending;
16241 }
16242 panic!("intentional cooperative driver panic after owner handoff");
16243 }
16244 }
16245
16246 let executor = multi_thread_executor(2, None, "panic-owner-handoff");
16247 let owner = executor.fresh_owner_token();
16248 let fallback_dependency = Arc::new(TaskDependency::default());
16249 fallback_dependency.set_owned(BlockingDependency {
16250 job: BlockingJobId(u64::MAX - 49),
16251 owner: Some(owner),
16252 });
16253 let fallback = Arc::new(DriverParker::default());
16254 executor.parked_drivers.register_with_role(
16255 &fallback,
16256 true,
16257 Some(Arc::clone(&fallback_dependency)),
16258 );
16259
16260 let (first_poll_tx, first_poll_rx) = mpsc::channel();
16261 let (panicked_tx, panicked_rx) = mpsc::channel();
16262 let driver_executor = Arc::clone(&executor);
16263 let driver = std::thread::spawn(move || {
16264 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
16265 let mut future = std::pin::pin!(PanicAfterWake {
16266 dependency: BlockingDependency {
16267 job: BlockingJobId(u64::MAX - 50),
16268 owner: Some(owner)
16269 },
16270 first_poll: Some(first_poll_tx),
16271 });
16272 let panicked = catch_unwind(AssertUnwindSafe(|| {
16273 driver_executor.block_on(future.as_mut());
16274 }))
16275 .is_err();
16276 panicked_tx.send(panicked).unwrap();
16277 });
16278
16279 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16280 wait_until("the panicking cooperative driver to park", || {
16281 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
16282 });
16283 assert!(
16284 executor.parked_drivers.wake_one_owner_dependency(owner),
16285 "the exact owner handoff must select the cooperative driver"
16286 );
16287 assert!(
16288 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
16289 "the test future must unwind from its second poll"
16290 );
16291 driver.join().unwrap();
16292
16293 assert!(
16294 fallback.consume_permit(),
16295 "panic cleanup must forward the unconsumed owner handoff"
16296 );
16297 assert_eq!(
16298 fallback.take_owner_handoff(),
16299 Some(owner),
16300 "panic cleanup must preserve the exact owner identity"
16301 );
16302 }
16303
16304 #[cfg(not(target_family = "wasm"))]
16305 #[test]
16306 fn cooperative_panic_flushes_lifo_slot() {
16307 struct ScheduleThenPanic {
16308 executor: Arc<MultiThreadExecutor>,
16309 ran: Arc<AtomicBool>,
16310 }
16311
16312 impl Future for ScheduleThenPanic {
16313 type Output = ();
16314
16315 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16316 let ran = Arc::clone(&self.ran);
16317 let scheduler = Arc::clone(&self.executor);
16318 let (runnable, task) = async_task::spawn(
16319 async move {
16320 ran.store(true, Ordering::SeqCst);
16321 },
16322 move |runnable| scheduler.schedule(runnable),
16323 );
16324 self.executor.schedule(runnable);
16325 task.detach();
16326 panic!("intentional cooperative driver panic with a populated LIFO slot");
16327 }
16328 }
16329
16330 let executor = multi_thread_executor(2, None, "panic-lifo-flush");
16331 executor
16332 .active_drainers
16333 .store(executor.max_drainers, Ordering::SeqCst);
16334 let ran = Arc::new(AtomicBool::new(false));
16335 let panicked = {
16336 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
16337 let mut future = std::pin::pin!(ScheduleThenPanic {
16338 executor: Arc::clone(&executor),
16339 ran: Arc::clone(&ran),
16340 });
16341 catch_unwind(AssertUnwindSafe(|| {
16342 executor.block_on(future.as_mut());
16343 }))
16344 .is_err()
16345 };
16346
16347 assert!(panicked, "the test future must unwind from its first poll");
16348 assert!(
16349 executor.pop_lifo_slot().is_none(),
16350 "panic cleanup must empty the LIFO slot"
16351 );
16352 assert_eq!(
16353 executor.queue.lock().unwrap().len(),
16354 1,
16355 "panic cleanup must move the slot runnable to the shared FIFO"
16356 );
16357 assert!(
16358 executor.run_one(),
16359 "the flushed runnable must remain executable"
16360 );
16361 assert!(ran.load(Ordering::SeqCst));
16362 executor.active_drainers.store(0, Ordering::SeqCst);
16363 }
16364
16365 #[cfg(not(target_family = "wasm"))]
16366 #[test]
16367 fn cooperative_panic_compensates_absorbed_queue_wake() {
16368 use std::sync::mpsc;
16369
16370 struct PanicAfterWake {
16371 first_poll: Option<mpsc::Sender<()>>,
16372 }
16373
16374 impl Future for PanicAfterWake {
16375 type Output = ();
16376
16377 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16378 if let Some(first_poll) = self.first_poll.take() {
16379 first_poll.send(()).unwrap();
16380 return Poll::Pending;
16381 }
16382 panic!("intentional cooperative driver panic after a queue wake");
16383 }
16384 }
16385
16386 let executor = multi_thread_executor(2, None, "panic-wake-compensation");
16387 executor
16388 .active_drainers
16389 .store(executor.max_drainers, Ordering::SeqCst);
16390 let fallback = Arc::new(DriverParker::default());
16391 executor.parked_drivers.register(&fallback);
16392 let (first_poll_tx, first_poll_rx) = mpsc::channel();
16393 let (panicked_tx, panicked_rx) = mpsc::channel();
16394 let driver_executor = Arc::clone(&executor);
16395 let driver = std::thread::spawn(move || {
16396 let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
16397 let mut future = std::pin::pin!(PanicAfterWake {
16398 first_poll: Some(first_poll_tx)
16399 });
16400 let panicked = catch_unwind(AssertUnwindSafe(|| {
16401 driver_executor.block_on(future.as_mut());
16402 }))
16403 .is_err();
16404 panicked_tx.send(panicked).unwrap();
16405 });
16406
16407 first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16408 wait_until(
16409 "the panicking cooperative driver to park after the fallback",
16410 || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
16411 );
16412
16413 let ran = Arc::new(AtomicBool::new(false));
16414 let ran_task = Arc::clone(&ran);
16415 let scheduler = Arc::clone(&executor);
16416 let (runnable, task) = async_task::spawn(
16417 async move {
16418 ran_task.store(true, Ordering::SeqCst);
16419 },
16420 move |runnable| scheduler.schedule(runnable),
16421 );
16422 task.detach();
16423 executor.metrics.runnable_scheduled();
16424 executor.queue.lock().unwrap().push_back(runnable);
16425 assert!(
16426 executor.parked_drivers.wake_one(),
16427 "the queued runnable's wake must be absorbed by the cooperative driver"
16428 );
16429
16430 assert!(
16431 panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
16432 "the test future must unwind after absorbing the queue wake"
16433 );
16434 driver.join().unwrap();
16435 assert!(
16436 fallback.consume_permit(),
16437 "panic cleanup must compensate the absorbed queue wake to the fallback driver"
16438 );
16439 assert!(
16440 executor.run_one(),
16441 "the queued runnable must remain executable"
16442 );
16443 assert!(ran.load(Ordering::SeqCst));
16444 executor.active_drainers.store(0, Ordering::SeqCst);
16445 }
16446
16447 #[cfg(not(target_family = "wasm"))]
16448 #[test]
16449 fn multi_thread_future_wake_targets_its_own_parked_driver() {
16450 use std::sync::mpsc;
16458 use std::time::Duration;
16459
16460 let (done_tx, done_rx) = mpsc::channel();
16461 let runner = std::thread::spawn(move || {
16462 let metrics = Arc::new(RuntimeMetrics::default());
16463 let options = RuntimeOptions {
16464 flavor: RuntimeFlavor::MultiThread,
16465 worker_threads: 2,
16466 max_blocking_tasks: 2,
16467 thread_name_prefix: "wake-target".to_string(),
16468 park_deadline: None,
16469 };
16470 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16471
16472 let (tx1, rx1) = oneshot::channel::<usize>();
16473 let (tx2, rx2) = oneshot::channel::<usize>();
16474 let t1_done = Arc::new(AtomicBool::new(false));
16475 let t2_done = Arc::new(AtomicBool::new(false));
16476
16477 let t1_exec = Arc::clone(&executor);
16479 let t1_flag = Arc::clone(&t1_done);
16480 let t1_body = async move {
16481 let mut inner = std::pin::pin!(async move {
16482 assert_eq!(rx1.await.unwrap(), 7usize);
16483 });
16484 t1_exec.block_on(inner.as_mut());
16485 t1_flag.store(true, Ordering::SeqCst);
16486 };
16487 let sched1 = Arc::clone(&executor);
16488 let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
16489 executor.schedule(runnable1);
16490 wait_until("driver 1 parked", || {
16491 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
16492 });
16493
16494 let t2_exec = Arc::clone(&executor);
16498 let t2_flag = Arc::clone(&t2_done);
16499 let t2_body = async move {
16500 let mut inner = std::pin::pin!(async move {
16501 assert_eq!(rx2.await.unwrap(), 9usize);
16502 });
16503 t2_exec.block_on(inner.as_mut());
16504 t2_flag.store(true, Ordering::SeqCst);
16505 };
16506 let sched2 = Arc::clone(&executor);
16507 let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
16508 executor.metrics.runnable_scheduled(); executor.queue.lock().unwrap().push_back(runnable2);
16510 executor.ensure_drainer();
16511 wait_until("driver 2 parked", || {
16512 executor.parked_drivers.count.load(Ordering::SeqCst) == 2
16513 });
16514
16515 tx1.send(7usize).unwrap();
16518 wait_until("T1 completed via its own driver's wake", || {
16519 t1_done.load(Ordering::SeqCst)
16520 });
16521 futures::executor::block_on(task1);
16522 assert!(
16523 !t2_done.load(Ordering::SeqCst),
16524 "D2 must still be parked: its future was never completed"
16525 );
16526 assert_eq!(
16527 executor.parked_drivers.count.load(Ordering::SeqCst),
16528 1,
16529 "exactly the untargeted driver must remain parked"
16530 );
16531
16532 tx2.send(9usize).unwrap();
16534 futures::executor::block_on(task2);
16535 assert!(t2_done.load(Ordering::SeqCst));
16536 let _ = done_tx.send(());
16537 });
16538
16539 match done_rx.recv_timeout(Duration::from_secs(10)) {
16540 Ok(()) => runner.join().unwrap(),
16541 Err(error) => panic!(
16542 "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
16543 ),
16544 }
16545 }
16546
16547 #[cfg(not(target_family = "wasm"))]
16548 #[test]
16549 fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
16550 use std::sync::mpsc;
16558 use std::time::Duration;
16559
16560 let (done_tx, done_rx) = mpsc::channel();
16561 let runner = std::thread::spawn(move || {
16562 let metrics = Arc::new(RuntimeMetrics::default());
16563 let options = RuntimeOptions {
16564 flavor: RuntimeFlavor::MultiThread,
16565 worker_threads: 1,
16566 max_blocking_tasks: 1,
16567 thread_name_prefix: "wake-compensate".to_string(),
16568 park_deadline: None,
16569 };
16570 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16571
16572 let (gate_tx, gate_rx) = oneshot::channel::<()>();
16574 let td_exec = Arc::clone(&executor);
16575 let td_body = async move {
16576 let mut inner = std::pin::pin!(async move {
16577 gate_rx.await.unwrap();
16578 });
16579 td_exec.block_on(inner.as_mut());
16580 };
16581 let sched = Arc::clone(&executor);
16582 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
16583 executor.schedule(td_runnable);
16584 wait_until("the sole driver parked", || {
16585 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
16586 });
16587
16588 let (ran_tx, ran_rx) = mpsc::channel::<()>();
16591 let sched_stranded = Arc::clone(&executor);
16592 let (stranded_runnable, stranded_task) = async_task::spawn(
16593 async move {
16594 ran_tx.send(()).unwrap();
16595 },
16596 move |r| sched_stranded.schedule(r),
16597 );
16598 executor.metrics.runnable_scheduled(); executor.queue.lock().unwrap().push_back(stranded_runnable);
16600 stranded_task.detach();
16601
16602 {
16607 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
16608 let mut ready = std::pin::pin!(async {});
16609 executor.block_on(ready.as_mut());
16610 }
16611
16612 ran_rx.recv_timeout(Duration::from_secs(5)).expect(
16613 "exit compensation must wake the parked driver to run the stranded queued runnable",
16614 );
16615
16616 gate_tx.send(()).unwrap();
16618 futures::executor::block_on(td_task);
16619 let _ = done_tx.send(());
16620 });
16621
16622 match done_rx.recv_timeout(Duration::from_secs(10)) {
16623 Ok(()) => runner.join().unwrap(),
16624 Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
16625 }
16626 }
16627
16628 #[cfg(not(target_family = "wasm"))]
16629 #[test]
16630 fn cooperative_exit_compensates_blocking_only_residue() {
16631 use std::sync::mpsc;
16632
16633 struct Controlled {
16634 ready: Arc<AtomicBool>,
16635 entered: Option<mpsc::Sender<()>>,
16636 }
16637
16638 impl Future for Controlled {
16639 type Output = ();
16640
16641 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16642 if let Some(entered) = self.entered.take() {
16643 entered.send(()).unwrap();
16644 }
16645 if self.ready.load(Ordering::SeqCst) {
16646 Poll::Ready(())
16647 } else {
16648 Poll::Pending
16649 }
16650 }
16651 }
16652
16653 let options = RuntimeOptions {
16654 flavor: RuntimeFlavor::MultiThread,
16655 worker_threads: 2,
16656 max_blocking_tasks: 1,
16657 thread_name_prefix: "blocking-exit-compensation".to_string(),
16658 park_deadline: None,
16659 };
16660 let executor =
16661 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16662 let ready = Arc::new(AtomicBool::new(false));
16663 let (entered_tx, entered_rx) = mpsc::channel();
16664 let (returned_tx, returned_rx) = mpsc::channel();
16665 let (release_tx, release_rx) = mpsc::channel();
16666 let task_exec = Arc::clone(&executor);
16667 let task_ready = Arc::clone(&ready);
16668 let scheduler = Arc::clone(&executor);
16669 let (runnable, task) = async_task::spawn(
16670 async move {
16671 let mut future = std::pin::pin!(Controlled {
16672 ready: task_ready,
16673 entered: Some(entered_tx)
16674 });
16675 task_exec.block_on(future.as_mut());
16676 returned_tx.send(()).unwrap();
16677 release_rx.recv().unwrap();
16678 },
16679 move |runnable| scheduler.schedule(runnable),
16680 );
16681 executor.schedule(runnable);
16682 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16683
16684 let timer_exec = Arc::clone(&executor);
16685 let timer_scheduler = Arc::clone(&executor);
16686 let (timer_runnable, timer_task) = async_task::spawn(
16687 async move {
16688 heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
16689 },
16690 move |runnable| timer_scheduler.schedule(runnable),
16691 );
16692 executor.schedule(timer_runnable);
16693 wait_until("the dedicated timekeeper parked", || {
16694 executor
16695 .timers
16696 .inner
16697 .lock()
16698 .unwrap()
16699 .timekeeper_parker
16700 .is_some()
16701 });
16702
16703 ready.store(true, Ordering::SeqCst);
16704 let (blocking_tx, blocking_rx) = mpsc::channel();
16705 let blocking = executor.schedule_blocking(move || {
16706 blocking_tx.send(()).unwrap();
16707 });
16708 blocking_rx
16709 .recv_timeout(Duration::from_secs(2))
16710 .expect("blocking-only residue must be serviced by a Rayon worker");
16711 returned_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16712 release_tx.send(()).unwrap();
16713 futures::executor::block_on(task);
16714 futures::executor::block_on(blocking).unwrap();
16715 executor.shutdown_timers();
16716 futures::executor::block_on(timer_task);
16717 }
16718
16719 #[cfg(not(target_family = "wasm"))]
16720 #[test]
16721 fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
16722 let options = RuntimeOptions {
16723 flavor: RuntimeFlavor::MultiThread,
16724 worker_threads: 2,
16725 max_blocking_tasks: 1,
16726 thread_name_prefix: "stopped-exit-compensation".to_string(),
16727 park_deadline: None,
16728 };
16729 let executor =
16730 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16731 let ran = Arc::new(AtomicBool::new(false));
16732 let blocking_ran = Arc::clone(&ran);
16733
16734 executor
16738 .active_drainers
16739 .store(executor.max_drainers, Ordering::SeqCst);
16740 let blocking = executor.schedule_blocking(move || {
16741 blocking_ran.store(true, Ordering::SeqCst);
16742 });
16743 assert!(!executor.blocking_queue.lock().unwrap().is_empty());
16744
16745 executor.stop.begin_shutdown();
16746 executor.compensate_queued_work_wake(true);
16747 let ran_after_stop = ran.load(Ordering::SeqCst);
16748
16749 executor.active_drainers.store(0, Ordering::SeqCst);
16750 executor.begin_shutdown();
16751 let blocking_result = futures::executor::block_on(blocking);
16752
16753 assert!(
16754 !ran_after_stop,
16755 "exit compensation started queued blocking work after stop"
16756 );
16757 assert!(
16758 blocking_result.is_err(),
16759 "shutdown must cancel the queued blocking job"
16760 );
16761 }
16762
16763 #[cfg(not(target_family = "wasm"))]
16764 #[test]
16765 fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
16766 use std::sync::mpsc;
16775 use std::time::Duration;
16776
16777 const ROUNDS: usize = 20_000;
16778
16779 let (done_tx, done_rx) = mpsc::channel();
16780 let registry = Arc::new(ParkedDrivers::default());
16781 let work = Arc::new(AtomicBool::new(false));
16782
16783 let consumer = {
16784 let registry = Arc::clone(®istry);
16785 let work = Arc::clone(&work);
16786 std::thread::spawn(move || {
16787 let parker = Arc::new(DriverParker::default());
16788 for _ in 0..ROUNDS {
16789 loop {
16790 if work.swap(false, Ordering::SeqCst) {
16791 break;
16792 }
16793 registry.register(&parker);
16796 if work.load(Ordering::SeqCst) {
16797 registry.deregister(&parker);
16798 continue;
16799 }
16800 parker.park();
16801 registry.deregister(&parker);
16802 }
16803 }
16804 done_tx.send(()).unwrap();
16805 })
16806 };
16807
16808 for _ in 0..ROUNDS {
16809 work.store(true, Ordering::SeqCst);
16810 let _ = registry.wake_one();
16814 while work.load(Ordering::SeqCst) {
16815 std::hint::spin_loop();
16816 }
16817 }
16818
16819 done_rx
16820 .recv_timeout(Duration::from_secs(30))
16821 .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
16822 consumer.join().unwrap();
16823 }
16824
16825 #[cfg(not(target_family = "wasm"))]
16826 #[test]
16827 fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
16828 let options = RuntimeOptions {
16832 flavor: RuntimeFlavor::MultiThread,
16833 worker_threads: 2,
16834 max_blocking_tasks: 2,
16835 thread_name_prefix: "lifo-order".to_string(),
16836 park_deadline: None,
16837 };
16838 let executor =
16839 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16840 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
16841
16842 let old_order = Arc::clone(&order);
16845 let sched = Arc::clone(&executor);
16846 let (old_runnable, old_task) = async_task::spawn(
16847 async move {
16848 old_order.lock().unwrap().push("old");
16849 },
16850 move |r| sched.schedule(r),
16851 );
16852 executor.metrics.runnable_scheduled(); executor.queue.lock().unwrap().push_back(old_runnable);
16854 old_task.detach();
16855
16856 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
16859 let new_order = Arc::clone(&order);
16860 let sched = Arc::clone(&executor);
16861 let (new_runnable, new_task) = async_task::spawn(
16862 async move {
16863 new_order.lock().unwrap().push("new");
16864 },
16865 move |r| sched.schedule(r),
16866 );
16867 executor.schedule(new_runnable);
16868 new_task.detach();
16869 assert_eq!(
16870 executor.queue.lock().unwrap().len(),
16871 1,
16872 "the slot-path schedule must not push to the shared FIFO"
16873 );
16874
16875 assert!(executor.run_one(), "run_one must pop the slot first");
16876 assert!(executor.run_one(), "run_one must then pop the FIFO");
16877 assert!(!executor.run_one(), "no work must remain");
16878 assert_eq!(
16879 *order.lock().unwrap(),
16880 vec!["new", "old"],
16881 "the slot runnable (newest) must run before older FIFO work"
16882 );
16883 }
16884
16885 #[cfg(not(target_family = "wasm"))]
16886 #[test]
16887 fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
16888 let options = RuntimeOptions {
16889 flavor: RuntimeFlavor::MultiThread,
16890 worker_threads: 2,
16891 max_blocking_tasks: 1,
16892 thread_name_prefix: "fifo-owner".to_string(),
16893 park_deadline: None,
16894 };
16895 let executor =
16896 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16897 let observed_owner = Arc::new(Mutex::new(None));
16898 let observed = Arc::clone(&observed_owner);
16899 let (runnable, task) = async_task::spawn(
16900 async move {
16901 *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
16902 },
16903 |_| {},
16904 );
16905 task.detach();
16906 executor.metrics.runnable_scheduled();
16907 executor.queue.lock().unwrap().push_back(runnable);
16908 executor.active_drainers.store(1, Ordering::Release);
16909
16910 let owner = executor.fresh_owner_token();
16911 let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
16912 Arc::clone(&executor).drain();
16913
16914 assert_eq!(
16915 *observed_owner.lock().unwrap(),
16916 Some(None),
16917 "FIFO runnables must not borrow a blocking closure's over-cap privilege"
16918 );
16919 assert_eq!(
16920 BLOCKING_OWNER.with(std::cell::Cell::get),
16921 Some(owner),
16922 "the ambient blocking owner must be restored after the runnable"
16923 );
16924 }
16925
16926 #[cfg(not(target_family = "wasm"))]
16927 #[test]
16928 fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
16929 use std::sync::mpsc;
16933 use std::time::Duration;
16934
16935 let (done_tx, done_rx) = mpsc::channel();
16936 let runner = std::thread::spawn(move || {
16937 let options = RuntimeOptions {
16938 flavor: RuntimeFlavor::MultiThread,
16939 worker_threads: 1,
16940 max_blocking_tasks: 1,
16941 thread_name_prefix: "lifo-drain".to_string(),
16942 park_deadline: None,
16943 };
16944 let executor =
16945 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
16946 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
16947
16948 let p_exec = Arc::clone(&executor);
16949 let p_order = Arc::clone(&order);
16950 let p_body = async move {
16951 p_order.lock().unwrap().push("P");
16952 let c_order = Arc::clone(&p_order);
16954 let sched = Arc::clone(&p_exec);
16955 let (c_runnable, c_task) = async_task::spawn(
16956 async move {
16957 c_order.lock().unwrap().push("C");
16958 },
16959 move |r| sched.schedule(r),
16960 );
16961 p_exec.schedule(c_runnable);
16962 c_task.detach();
16963 };
16964
16965 let mut tasks = Vec::new();
16969 let sched = Arc::clone(&executor);
16970 let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
16971 executor.metrics.runnable_scheduled();
16972 executor.queue.lock().unwrap().push_back(p_runnable);
16973 tasks.push(p_task);
16974 for name in ["T1", "T2"] {
16975 let t_order = Arc::clone(&order);
16976 let sched = Arc::clone(&executor);
16977 let (t_runnable, t_task) = async_task::spawn(
16978 async move {
16979 t_order.lock().unwrap().push(name);
16980 },
16981 move |r| sched.schedule(r),
16982 );
16983 executor.metrics.runnable_scheduled();
16984 executor.queue.lock().unwrap().push_back(t_runnable);
16985 tasks.push(t_task);
16986 }
16987 executor.ensure_drainer();
16988
16989 for task in tasks {
16990 futures::executor::block_on(task);
16991 }
16992 wait_until("the detached child C ran", || {
16993 order.lock().unwrap().len() == 4
16994 });
16995 assert_eq!(
16996 *order.lock().unwrap(),
16997 vec!["P", "C", "T1", "T2"],
16998 "the slot child C must run right after its scheduler P, ahead of older FIFO work"
16999 );
17000 let _ = done_tx.send(());
17001 });
17002
17003 match done_rx.recv_timeout(Duration::from_secs(10)) {
17004 Ok(()) => runner.join().unwrap(),
17005 Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
17006 }
17007 }
17008
17009 #[cfg(not(target_family = "wasm"))]
17010 #[test]
17011 fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
17012 let options = RuntimeOptions {
17018 flavor: RuntimeFlavor::MultiThread,
17019 worker_threads: 2,
17020 max_blocking_tasks: 2,
17021 thread_name_prefix: "lifo-displace".to_string(),
17022 park_deadline: None,
17023 };
17024 let executor =
17025 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17026 executor
17027 .active_drainers
17028 .store(executor.max_drainers, Ordering::SeqCst);
17029 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17030
17031 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17032 for name in ["a", "b"] {
17033 let n_order = Arc::clone(&order);
17034 let sched = Arc::clone(&executor);
17035 let (runnable, task) = async_task::spawn(
17036 async move {
17037 n_order.lock().unwrap().push(name);
17038 },
17039 move |r| sched.schedule(r),
17040 );
17041 executor.schedule(runnable);
17042 task.detach();
17043 }
17044 assert_eq!(
17045 executor.queue.lock().unwrap().len(),
17046 1,
17047 "the displaced occupant (a) must have been pushed to the shared FIFO"
17048 );
17049
17050 assert!(executor.run_one(), "run_one must pop the slot (b) first");
17051 assert!(
17052 executor.run_one(),
17053 "run_one must then pop the displaced (a) from the FIFO"
17054 );
17055 assert!(!executor.run_one(), "no work must remain");
17056 assert_eq!(
17057 *order.lock().unwrap(),
17058 vec!["b", "a"],
17059 "the newest schedule must win the slot; the displaced one must survive via the FIFO"
17060 );
17061 executor.active_drainers.store(0, Ordering::SeqCst);
17062 }
17063
17064 #[cfg(not(target_family = "wasm"))]
17065 #[test]
17066 fn lifo_slot_is_executor_scoped() {
17067 let options = RuntimeOptions {
17072 flavor: RuntimeFlavor::MultiThread,
17073 worker_threads: 2,
17074 max_blocking_tasks: 2,
17075 thread_name_prefix: "lifo-scope".to_string(),
17076 park_deadline: None,
17077 };
17078 let exec1 =
17079 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17080 let exec2 =
17081 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17082 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
17083 exec1
17084 .active_drainers
17085 .store(exec1.max_drainers, Ordering::SeqCst);
17086 exec2
17087 .active_drainers
17088 .store(exec2.max_drainers, Ordering::SeqCst);
17089 let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17090
17091 let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
17093 let r1_order = Arc::clone(&order);
17094 let sched = Arc::clone(&exec1);
17095 let (r1, t1) = async_task::spawn(
17096 async move {
17097 r1_order.lock().unwrap().push("one");
17098 },
17099 move |r| sched.schedule(r),
17100 );
17101 exec1.schedule(r1);
17102 t1.detach();
17103 assert!(
17104 exec1.queue.lock().unwrap().is_empty(),
17105 "r1 must sit in the slot, not exec1's FIFO"
17106 );
17107
17108 {
17109 let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
17112 let r2_order = Arc::clone(&order);
17113 let sched = Arc::clone(&exec2);
17114 let (r2, t2) = async_task::spawn(
17115 async move {
17116 r2_order.lock().unwrap().push("two");
17117 },
17118 move |r| sched.schedule(r),
17119 );
17120 exec2.schedule(r2);
17121 t2.detach();
17122 assert_eq!(
17123 exec2.queue.lock().unwrap().len(),
17124 1,
17125 "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
17126 );
17127 assert!(
17128 exec2.pop_lifo_slot().is_none(),
17129 "exec2 must not pop exec1's slot entry (executor-scoped pop)"
17130 );
17131 assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
17132 }
17133
17134 assert!(
17136 exec1.run_one(),
17137 "exec1's slot entry must have survived the foreign activity"
17138 );
17139 assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
17140 exec1.active_drainers.store(0, Ordering::SeqCst);
17141 exec2.active_drainers.store(0, Ordering::SeqCst);
17142 }
17143
17144 #[cfg(not(target_family = "wasm"))]
17145 #[test]
17146 fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
17147 use std::sync::mpsc;
17154 use std::time::Duration;
17155
17156 const CHAIN: u64 = 200; fn spawn_link(
17159 executor: &Arc<MultiThreadExecutor>,
17160 remaining: u64,
17161 counter: Arc<AtomicU64>,
17162 done: mpsc::Sender<()>,
17163 ) {
17164 let body_exec = Arc::clone(executor);
17165 let body = async move {
17166 counter.fetch_add(1, Ordering::SeqCst);
17167 if remaining > 0 {
17168 spawn_link(&body_exec, remaining - 1, counter, done);
17169 } else {
17170 done.send(()).unwrap();
17171 }
17172 };
17173 let sched = Arc::clone(executor);
17174 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17175 executor.schedule(runnable);
17176 task.detach();
17177 }
17178
17179 let (done_tx, done_rx) = mpsc::channel();
17180 let runner = std::thread::spawn(move || {
17181 let metrics = Arc::new(RuntimeMetrics::default());
17182 let options = RuntimeOptions {
17183 flavor: RuntimeFlavor::MultiThread,
17184 worker_threads: 1,
17185 max_blocking_tasks: 1,
17186 thread_name_prefix: "lifo-budget".to_string(),
17187 park_deadline: None,
17188 };
17189 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17190
17191 let counter = Arc::new(AtomicU64::new(0));
17192 let (chain_tx, chain_rx) = mpsc::channel();
17193 spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
17194
17195 chain_rx
17196 .recv_timeout(Duration::from_secs(10))
17197 .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
17198 assert_eq!(
17199 counter.load(Ordering::SeqCst),
17200 CHAIN,
17201 "every chain link must have run"
17202 );
17203 wait_until("runnable counters settle to zero (no slot leak)", || {
17204 metrics.queued_runnables.load(Ordering::Relaxed) == 0
17205 && metrics.active_runnables.load(Ordering::Relaxed) == 0
17206 });
17207 let _ = done_tx.send(());
17208 });
17209
17210 match done_rx.recv_timeout(Duration::from_secs(15)) {
17211 Ok(()) => runner.join().unwrap(),
17212 Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
17213 }
17214 }
17215
17216 #[cfg(not(target_family = "wasm"))]
17217 #[test]
17218 fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
17219 let options = RuntimeOptions {
17226 flavor: RuntimeFlavor::MultiThread,
17227 worker_threads: 2,
17228 max_blocking_tasks: 2,
17229 thread_name_prefix: "lifo-flush".to_string(),
17230 park_deadline: None,
17231 };
17232 let executor =
17233 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17234 executor
17235 .active_drainers
17236 .store(executor.max_drainers, Ordering::SeqCst);
17237 let ran = Arc::new(AtomicBool::new(false));
17238
17239 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17240 let ran_flag = Arc::clone(&ran);
17241 let sched = Arc::clone(&executor);
17242 let (runnable, task) = async_task::spawn(
17243 async move {
17244 ran_flag.store(true, Ordering::SeqCst);
17245 },
17246 move |r| sched.schedule(r),
17247 );
17248 executor.schedule(runnable);
17249 task.detach();
17250 assert!(
17251 executor.queue.lock().unwrap().is_empty(),
17252 "the runnable must start in the slot"
17253 );
17254
17255 executor.flush_lifo_slot();
17256 assert!(
17257 executor.pop_lifo_slot().is_none(),
17258 "the slot must be empty after a flush"
17259 );
17260 assert_eq!(
17261 executor.queue.lock().unwrap().len(),
17262 1,
17263 "the flushed runnable must be on the shared FIFO"
17264 );
17265 assert!(executor.run_one(), "the flushed runnable must still run");
17266 assert!(
17267 ran.load(Ordering::SeqCst),
17268 "the flushed task must complete, not be dropped"
17269 );
17270 executor.active_drainers.store(0, Ordering::SeqCst);
17271 }
17272
17273 #[cfg(not(target_family = "wasm"))]
17274 #[test]
17275 fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
17276 let options = RuntimeOptions {
17281 flavor: RuntimeFlavor::MultiThread,
17282 worker_threads: 2,
17283 max_blocking_tasks: 2,
17284 thread_name_prefix: "lifo-owner".to_string(),
17285 park_deadline: None,
17286 };
17287 let executor =
17288 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17289 let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
17290
17291 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17292
17293 let observed_slot = Arc::clone(&observed);
17297 let sched = Arc::clone(&executor);
17298 let (runnable, task) = async_task::spawn(
17299 async move {
17300 *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
17301 },
17302 move |r| sched.schedule(r),
17303 );
17304 executor.schedule(runnable);
17305 task.detach();
17306 assert!(
17307 executor.queue.lock().unwrap().is_empty(),
17308 "the runnable must sit in the slot"
17309 );
17310
17311 let token = executor.fresh_owner_token();
17315 let _owner = BlockingOwnerGuard::enter(Some(token));
17316 assert!(executor.run_one(), "the slot runnable must run");
17317 assert_eq!(
17318 *observed.lock().unwrap(),
17319 Some(None),
17320 "a slot runnable must observe a CLEARED blocking owner"
17321 );
17322 assert_eq!(
17323 BLOCKING_OWNER.with(std::cell::Cell::get),
17324 Some(token),
17325 "the ambient owner frame must be restored after the slot runnable"
17326 );
17327 }
17328
17329 #[cfg(not(target_family = "wasm"))]
17335 struct PingPongShared {
17336 count: AtomicU64,
17337 stop: AtomicBool,
17338 stop_after: u64,
17339 wakers: Mutex<[Option<std::task::Waker>; 2]>,
17340 }
17341
17342 #[cfg(not(target_family = "wasm"))]
17343 struct PingPong {
17344 shared: Arc<PingPongShared>,
17345 me: usize,
17346 }
17347
17348 #[cfg(not(target_family = "wasm"))]
17349 impl Future for PingPong {
17350 type Output = ();
17351 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
17352 let shared = &self.shared;
17353 let polls = shared.count.fetch_add(1, Ordering::SeqCst);
17354 if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
17355 let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
17356 if let Some(partner) = partner {
17357 partner.wake();
17358 }
17359 return Poll::Ready(());
17360 }
17361 let mut wakers = shared.wakers.lock().unwrap();
17362 wakers[self.me] = Some(cx.waker().clone());
17363 if let Some(partner) = wakers[1 - self.me].take() {
17364 drop(wakers);
17365 partner.wake();
17366 }
17367 Poll::Pending
17368 }
17369 }
17370
17371 #[cfg(not(target_family = "wasm"))]
17372 fn spawn_ping_pong_pair(
17373 executor: &Arc<MultiThreadExecutor>,
17374 shared: &Arc<PingPongShared>,
17375 ) -> Vec<Task<()>> {
17376 let mut tasks = Vec::new();
17377 for me in 0..2 {
17378 let sched = Arc::clone(executor);
17379 let (runnable, task) = async_task::spawn(
17380 PingPong {
17381 shared: Arc::clone(shared),
17382 me,
17383 },
17384 move |r| sched.schedule(r),
17385 );
17386 executor.schedule(runnable);
17387 tasks.push(task);
17388 }
17389 tasks
17390 }
17391
17392 #[cfg(not(target_family = "wasm"))]
17393 #[test]
17394 fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
17395 use std::sync::mpsc;
17402 use std::time::Duration;
17403
17404 let (done_tx, done_rx) = mpsc::channel();
17405 let runner = std::thread::spawn(move || {
17406 let options = RuntimeOptions {
17407 flavor: RuntimeFlavor::MultiThread,
17408 worker_threads: 1,
17409 max_blocking_tasks: 0,
17410 thread_name_prefix: "lifo-streak".to_string(),
17411 park_deadline: None,
17412 };
17413 let executor =
17414 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17415
17416 let shared = Arc::new(PingPongShared {
17417 count: AtomicU64::new(0),
17418 stop: AtomicBool::new(false),
17419 stop_after: u64::MAX,
17420 wakers: Mutex::new([None, None]),
17421 });
17422 let pair = spawn_ping_pong_pair(&executor, &shared);
17423
17424 let (t3_tx, t3_rx) = mpsc::channel::<()>();
17426 let sched = Arc::clone(&executor);
17427 let (t3_runnable, t3_task) = async_task::spawn(
17428 async move {
17429 t3_tx.send(()).unwrap();
17430 },
17431 move |r| sched.schedule(r),
17432 );
17433 executor.schedule(t3_runnable);
17434
17435 t3_rx
17436 .recv_timeout(Duration::from_secs(10))
17437 .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
17438 assert!(
17439 shared.count.load(Ordering::SeqCst) >= 32,
17440 "the pair must have been bouncing through the slot before T3 got its turn"
17441 );
17442
17443 shared.stop.store(true, Ordering::SeqCst);
17445 let stored: Vec<_> = shared
17446 .wakers
17447 .lock()
17448 .unwrap()
17449 .iter_mut()
17450 .filter_map(Option::take)
17451 .collect();
17452 for waker in stored {
17453 waker.wake();
17454 }
17455 for task in pair {
17456 futures::executor::block_on(task);
17457 }
17458 futures::executor::block_on(t3_task);
17459 let _ = done_tx.send(());
17460 });
17461
17462 match done_rx.recv_timeout(Duration::from_secs(15)) {
17463 Ok(()) => runner.join().unwrap(),
17464 Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
17465 }
17466 }
17467
17468 #[cfg(not(target_family = "wasm"))]
17469 #[test]
17470 fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
17471 use std::sync::mpsc;
17478 use std::time::Duration;
17479
17480 const BOUNCES: u64 = 2_000; let (done_tx, done_rx) = mpsc::channel();
17483 let runner = std::thread::spawn(move || {
17484 let options = RuntimeOptions {
17485 flavor: RuntimeFlavor::MultiThread,
17486 worker_threads: 1,
17487 max_blocking_tasks: 1,
17488 thread_name_prefix: "lifo-blocking".to_string(),
17489 park_deadline: None,
17490 };
17491 let executor =
17492 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17493
17494 let shared = Arc::new(PingPongShared {
17495 count: AtomicU64::new(0),
17496 stop: AtomicBool::new(false),
17497 stop_after: BOUNCES,
17498 wakers: Mutex::new([None, None]),
17499 });
17500 let pair = spawn_ping_pong_pair(&executor, &shared);
17501
17502 let blocking = executor.schedule_blocking(|| 11usize);
17504
17505 assert_eq!(
17506 futures::executor::block_on(blocking).unwrap(),
17507 11,
17508 "the queued blocking job must complete despite the hot LIFO pair"
17509 );
17510 for task in pair {
17511 futures::executor::block_on(task);
17512 }
17513 assert!(
17514 shared.count.load(Ordering::SeqCst) >= BOUNCES,
17515 "the pair must have actually run its full bounce quota"
17516 );
17517 let _ = done_tx.send(());
17518 });
17519
17520 match done_rx.recv_timeout(Duration::from_secs(15)) {
17521 Ok(()) => runner.join().unwrap(),
17522 Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
17523 }
17524 }
17525
17526 #[cfg(not(target_family = "wasm"))]
17527 #[test]
17528 fn infinite_hot_runnable_yields_to_blocking_fifo() {
17529 use std::sync::mpsc;
17530
17531 struct HotFuture {
17532 stop: Arc<AtomicBool>,
17533 }
17534
17535 impl Future for HotFuture {
17536 type Output = ();
17537
17538 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
17539 if self.stop.load(Ordering::SeqCst) {
17540 Poll::Ready(())
17541 } else {
17542 cx.waker().wake_by_ref();
17543 Poll::Pending
17544 }
17545 }
17546 }
17547
17548 let options = RuntimeOptions {
17549 flavor: RuntimeFlavor::MultiThread,
17550 worker_threads: 2,
17551 max_blocking_tasks: 1,
17552 thread_name_prefix: "blocking-fairness".to_string(),
17553 park_deadline: None,
17554 };
17555 let executor =
17556 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17557 let stop = Arc::new(AtomicBool::new(false));
17558 let scheduler = Arc::clone(&executor);
17559 let (runnable, task) = async_task::spawn(
17560 HotFuture {
17561 stop: Arc::clone(&stop),
17562 },
17563 move |r| scheduler.schedule(r),
17564 );
17565 task.detach();
17566 let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
17567 executor
17568 .blocking_queue
17569 .lock()
17570 .unwrap()
17571 .push(QueuedBlocking {
17572 id: BlockingJobId(u64::MAX),
17573 run: Box::new(move || blocking_tx.send(()).unwrap()),
17574 });
17575
17576 let _driver = OnPoolWorkerGuard::enter(executor.id);
17577 executor.schedule(runnable);
17578 let mut runnable_streak = 0;
17579 for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
17580 assert!(executor.run_one_fair(&mut runnable_streak));
17581 }
17582 blocking_rx
17583 .try_recv()
17584 .expect("an infinite self-waker must yield a bounded quantum to blocking work");
17585
17586 stop.store(true, Ordering::SeqCst);
17587 assert!(executor.run_one_fair(&mut runnable_streak));
17588 }
17589
17590 #[cfg(not(target_family = "wasm"))]
17591 #[test]
17592 fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
17593 use std::sync::mpsc;
17594
17595 struct HotFuture {
17596 stop: Arc<AtomicBool>,
17597 }
17598
17599 impl Future for HotFuture {
17600 type Output = ();
17601
17602 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
17603 if self.stop.load(Ordering::SeqCst) {
17604 Poll::Ready(())
17605 } else {
17606 cx.waker().wake_by_ref();
17607 Poll::Pending
17608 }
17609 }
17610 }
17611
17612 let options = RuntimeOptions {
17613 flavor: RuntimeFlavor::MultiThread,
17614 worker_threads: 1,
17615 max_blocking_tasks: 1,
17616 thread_name_prefix: "exact-dependency-fairness".to_string(),
17617 park_deadline: None,
17618 };
17619 let executor =
17620 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17621 let stop = Arc::new(AtomicBool::new(false));
17622 let (queued_tx, queued_rx) = mpsc::channel();
17623 let (exact_tx, exact_rx) = mpsc::channel();
17624 let owner_executor = Arc::clone(&executor);
17625 let owner_stop = Arc::clone(&stop);
17626 let owner = executor.schedule_blocking(move || {
17627 let scheduler = Arc::clone(&owner_executor);
17628 let hot_stop = Arc::clone(&owner_stop);
17629 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
17630 scheduler.schedule(runnable);
17631 });
17632 owner_executor.schedule(runnable);
17633
17634 let exact_stop = Arc::clone(&owner_stop);
17635 let exact = owner_executor.schedule_blocking(move || {
17636 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
17637 41usize
17638 });
17639 queued_tx.send(()).unwrap();
17640
17641 let mut value = None;
17642 {
17643 let mut future = std::pin::pin!(async {
17644 value = Some(exact.await.unwrap());
17645 hot_task.await;
17646 });
17647 assert_eq!(
17648 owner_executor.block_on(future.as_mut()),
17649 BlockOnOutcome::Completed
17650 );
17651 }
17652 value.unwrap()
17653 });
17654
17655 queued_rx
17656 .recv_timeout(Duration::from_secs(2))
17657 .expect("the exact dependency must be queued behind the saturated owner");
17658 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
17659 stop.store(true, Ordering::SeqCst);
17660 assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
17661 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
17662 assert_eq!(
17663 exact_before_stop,
17664 Some(true),
17665 "a hot local runnable must yield to the exact dependency before it is stopped"
17666 );
17667 }
17668
17669 #[cfg(not(target_family = "wasm"))]
17670 #[test]
17671 fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
17672 use std::sync::mpsc;
17673
17674 struct BlockingPoll {
17675 started: Option<mpsc::Sender<()>>,
17676 released: Arc<(Mutex<bool>, Condvar)>,
17677 }
17678
17679 impl Future for BlockingPoll {
17680 type Output = ();
17681
17682 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17683 if let Some(started) = self.started.take() {
17684 started.send(()).unwrap();
17685 }
17686 let (released, changed) = &*self.released;
17687 let mut released = released
17688 .lock()
17689 .unwrap_or_else(std::sync::PoisonError::into_inner);
17690 while !*released {
17691 released = changed
17692 .wait(released)
17693 .unwrap_or_else(std::sync::PoisonError::into_inner);
17694 }
17695 Poll::Ready(())
17696 }
17697 }
17698
17699 let options = RuntimeOptions {
17700 flavor: RuntimeFlavor::MultiThread,
17701 worker_threads: 3,
17702 max_blocking_tasks: 2,
17703 thread_name_prefix: "exact-before-last-slot".to_string(),
17704 park_deadline: None,
17705 };
17706 let executor =
17707 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17708
17709 let released = Arc::new((Mutex::new(false), Condvar::new()));
17710 let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
17711 let mut blockers = Vec::new();
17712 for _ in 0..2 {
17713 let scheduler = Arc::clone(&executor);
17714 let blocker_released = Arc::clone(&released);
17715 let started = blocker_started_tx.clone();
17716 let (runnable, task) = async_task::spawn(
17717 BlockingPoll {
17718 started: Some(started),
17719 released: blocker_released,
17720 },
17721 move |runnable| scheduler.schedule(runnable),
17722 );
17723 executor.schedule(runnable);
17724 blockers.push(task);
17725 }
17726 drop(blocker_started_tx);
17727 for _ in 0..2 {
17728 blocker_started_rx
17729 .recv_timeout(Duration::from_secs(2))
17730 .expect("two workers must be gated before the owner starts");
17731 }
17732
17733 let dependency_started = Arc::new(AtomicBool::new(false));
17734 let owner_executor = Arc::clone(&executor);
17735 let owner_dependency_started = Arc::clone(&dependency_started);
17736 let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
17737 let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
17738 let owner_signal = Arc::clone(&dependency_signal);
17739 let owner = executor.schedule_blocking(move || {
17740 let unrelated_signal = Arc::clone(&owner_signal);
17741 let unrelated_started = Arc::clone(&owner_dependency_started);
17742 let unrelated = owner_executor.schedule_blocking(move || {
17743 let observed = unrelated_started.load(Ordering::SeqCst);
17744 unrelated_observed_tx.send(observed).unwrap();
17745 let (ready, changed) = &*unrelated_signal;
17746 let mut ready = ready
17747 .lock()
17748 .unwrap_or_else(std::sync::PoisonError::into_inner);
17749 while !*ready {
17750 ready = changed
17751 .wait(ready)
17752 .unwrap_or_else(std::sync::PoisonError::into_inner);
17753 }
17754 });
17755
17756 let exact_signal = Arc::clone(&owner_signal);
17757 let exact_started = Arc::clone(&owner_dependency_started);
17758 let exact = owner_executor.schedule_blocking(move || {
17759 exact_started.store(true, Ordering::SeqCst);
17760 let (ready, changed) = &*exact_signal;
17761 *ready
17762 .lock()
17763 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
17764 changed.notify_all();
17765 41usize
17766 });
17767
17768 let mut output = None;
17769 {
17770 let mut future = std::pin::pin!(async {
17771 output = Some(exact.await.unwrap());
17772 unrelated.await.unwrap();
17773 });
17774 assert_eq!(
17775 owner_executor.block_on(future.as_mut()),
17776 BlockOnOutcome::Completed
17777 );
17778 }
17779 output.unwrap()
17780 });
17781
17782 let (owner_done_tx, owner_done_rx) = mpsc::channel();
17783 let owner_waiter = std::thread::spawn(move || {
17784 owner_done_tx
17785 .send(futures::executor::block_on(owner))
17786 .unwrap();
17787 });
17788 let (completed_while_gated, owner_result) =
17789 match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
17790 Ok(result) => (true, result),
17791 Err(_) => {
17792 let (released, changed) = &*released;
17795 *released
17796 .lock()
17797 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
17798 changed.notify_all();
17799 (
17800 false,
17801 owner_done_rx
17802 .recv_timeout(Duration::from_secs(2))
17803 .expect("releasing the other workers must let the broken ordering recover"),
17804 )
17805 }
17806 };
17807 let unrelated_observed = unrelated_observed_rx
17808 .recv_timeout(Duration::from_secs(2))
17809 .expect("the unrelated FIFO job must eventually run");
17810
17811 {
17812 let (released, changed) = &*released;
17813 *released
17814 .lock()
17815 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
17816 changed.notify_all();
17817 }
17818 owner_waiter.join().unwrap();
17819 for blocker in blockers {
17820 futures::executor::block_on(blocker);
17821 }
17822 executor.begin_shutdown();
17823 executor.wait_until_scheduler_idle();
17824
17825 assert!(
17826 completed_while_gated,
17827 "the exact dependency must complete while unrelated workers remain gated"
17828 );
17829 assert_eq!(owner_result.unwrap(), 41);
17830 assert!(
17831 unrelated_observed,
17832 "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
17833 );
17834 }
17835
17836 #[cfg(not(target_family = "wasm"))]
17837 #[test]
17838 fn blocking_saturation_preserves_a_runnable_execution_lane() {
17839 use std::{sync::mpsc, time::Duration};
17840
17841 let options = RuntimeOptions {
17842 flavor: RuntimeFlavor::MultiThread,
17843 worker_threads: 2,
17844 max_blocking_tasks: 2,
17845 thread_name_prefix: "blocking-reserve".to_string(),
17846 park_deadline: None,
17847 }
17848 .validate()
17849 .expect("production options must reserve a runnable lane");
17850 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
17851 let executor =
17852 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17853 let (release_tx, release_rx) = mpsc::channel();
17854 let (started_tx, started_rx) = mpsc::channel();
17855 let first = executor.schedule_blocking(move || {
17856 started_tx.send(()).unwrap();
17857 release_rx.recv().unwrap();
17858 });
17859 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
17860
17861 let (queued_tx, queued_rx) = mpsc::channel();
17862 let second = executor.schedule_blocking(move || {
17863 queued_tx.send(()).unwrap();
17864 });
17865 assert!(
17866 queued_rx.try_recv().is_err(),
17867 "the second blocking job must remain queued while the sole blocking slot is occupied"
17868 );
17869
17870 let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
17871 let scheduler = Arc::clone(&executor);
17872 let (runnable, task) = async_task::spawn(
17873 async move {
17874 runnable_tx.send(()).unwrap();
17875 },
17876 move |r| scheduler.schedule(r),
17877 );
17878 task.detach();
17879 executor.schedule(runnable);
17880 runnable_rx
17881 .recv_timeout(Duration::from_secs(1))
17882 .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
17883
17884 release_tx.send(()).unwrap();
17885 futures::executor::block_on(first).unwrap();
17886 futures::executor::block_on(second).unwrap();
17887 queued_rx
17888 .recv_timeout(Duration::from_secs(1))
17889 .expect("queued blocking work must run after the occupied slot is released");
17890 }
17891
17892 #[cfg(not(target_family = "wasm"))]
17893 #[test]
17894 fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
17895 use std::sync::mpsc;
17903 use std::time::Duration;
17904
17905 struct SpawnOnReady {
17906 executor: Arc<MultiThreadExecutor>,
17907 child_tx: Option<mpsc::Sender<u64>>,
17908 }
17909 impl Future for SpawnOnReady {
17910 type Output = ();
17911 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17912 let child_tx = self.child_tx.take().expect("polled once");
17913 let sched = Arc::clone(&self.executor);
17914 let (runnable, task) = async_task::spawn(
17915 async move {
17916 child_tx.send(21).unwrap();
17917 },
17918 move |r| sched.schedule(r),
17919 );
17920 self.executor.schedule(runnable);
17922 task.detach();
17923 Poll::Ready(())
17924 }
17925 }
17926
17927 let (done_tx, done_rx) = mpsc::channel();
17928 let runner = std::thread::spawn(move || {
17929 let options = RuntimeOptions {
17930 flavor: RuntimeFlavor::MultiThread,
17931 worker_threads: 2,
17932 max_blocking_tasks: 2,
17933 thread_name_prefix: "exit-flush".to_string(),
17934 park_deadline: None,
17935 };
17936 let executor =
17937 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17938
17939 let (child_tx, child_rx) = mpsc::channel::<u64>();
17940 {
17941 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17944 let mut future = std::pin::pin!(SpawnOnReady {
17945 executor: Arc::clone(&executor),
17946 child_tx: Some(child_tx)
17947 });
17948 executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
17949 }
17950
17951 let value = child_rx
17955 .recv_timeout(Duration::from_secs(5))
17956 .expect("the child scheduled by the final poll was stranded in the LIFO slot");
17957 assert_eq!(value, 21);
17958 let _ = done_tx.send(());
17959 });
17960
17961 match done_rx.recv_timeout(Duration::from_secs(10)) {
17962 Ok(()) => runner.join().unwrap(),
17963 Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
17964 }
17965 }
17966
17967 #[cfg(not(target_family = "wasm"))]
17971 fn spawn_hot_chain(
17972 executor: &Arc<MultiThreadExecutor>,
17973 stop: Arc<AtomicBool>,
17974 hops: Arc<AtomicU64>,
17975 ) {
17976 if stop.load(Ordering::SeqCst) {
17977 return;
17978 }
17979 let link_exec = Arc::clone(executor);
17980 let body = async move {
17981 hops.fetch_add(1, Ordering::SeqCst);
17982 spawn_hot_chain(&link_exec, stop, hops);
17983 };
17984 let sched = Arc::clone(executor);
17985 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17986 executor.schedule(runnable);
17987 task.detach();
17988 }
17989
17990 #[cfg(not(target_family = "wasm"))]
17991 #[test]
17992 fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
17993 use std::sync::mpsc;
18002 use std::time::Duration;
18003
18004 let (done_tx, done_rx) = mpsc::channel();
18005 let runner = std::thread::spawn(move || {
18006 let options = RuntimeOptions {
18007 flavor: RuntimeFlavor::MultiThread,
18008 worker_threads: 1,
18009 max_blocking_tasks: 0,
18012 thread_name_prefix: "coop-budget".to_string(),
18013 park_deadline: None,
18014 };
18015 let executor =
18016 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18017
18018 let stop = Arc::new(AtomicBool::new(false));
18019 let hops = Arc::new(AtomicU64::new(0));
18020
18021 let m_exec = Arc::clone(&executor);
18025 let m_stop = Arc::clone(&stop);
18026 let m_hops = Arc::clone(&hops);
18027 let m_body = async move {
18028 let (tx, rx) = oneshot::channel::<u64>();
18029 let sched_f = Arc::clone(&m_exec);
18031 let (f_runnable, f_task) = async_task::spawn(
18032 async move {
18033 let _ = tx.send(7);
18034 },
18035 move |r| sched_f.schedule(r),
18036 );
18037 m_exec.schedule(f_runnable);
18038 f_task.detach();
18039 spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
18042
18043 let mut inner = std::pin::pin!(async move {
18044 assert_eq!(
18045 rx.await.unwrap(),
18046 7,
18047 "F must run and complete the awaited future"
18048 );
18049 });
18050 m_exec.block_on(inner.as_mut());
18051 m_stop.store(true, Ordering::SeqCst);
18052 };
18053 let sched_m = Arc::clone(&executor);
18054 let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
18055 executor.schedule(m_runnable);
18056
18057 futures::executor::block_on(m_task);
18058 assert!(
18059 hops.load(Ordering::SeqCst) >= 64,
18060 "the chain must have been genuinely hot (>= one budget window) before F ran"
18061 );
18062 let _ = done_tx.send(());
18063 });
18064
18065 match done_rx.recv_timeout(Duration::from_secs(10)) {
18066 Ok(()) => runner.join().unwrap(),
18067 Err(error) => panic!(
18068 "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
18069 ),
18070 }
18071 }
18072
18073 #[cfg(not(target_family = "wasm"))]
18074 #[test]
18075 fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
18076 use std::sync::mpsc;
18077
18078 struct RefillLifo {
18079 executor: Arc<MultiThreadExecutor>,
18080 fifo_ran: Arc<AtomicBool>,
18081 }
18082
18083 impl Future for RefillLifo {
18084 type Output = ();
18085
18086 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18087 if self.fifo_ran.load(Ordering::SeqCst) {
18088 return Poll::Ready(());
18089 }
18090 let scheduler = Arc::clone(&self.executor);
18091 let (runnable, task) =
18092 async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
18093 self.executor.schedule(runnable);
18094 task.detach();
18095 Poll::Pending
18096 }
18097 }
18098
18099 let (done_tx, done_rx) = mpsc::channel();
18100 let runner = std::thread::spawn(move || {
18101 let options = RuntimeOptions {
18102 flavor: RuntimeFlavor::MultiThread,
18103 worker_threads: 1,
18104 max_blocking_tasks: 0,
18105 thread_name_prefix: "forced-fifo".to_string(),
18106 park_deadline: None,
18107 };
18108 let executor =
18109 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18110 executor
18111 .active_drainers
18112 .store(executor.max_drainers, Ordering::SeqCst);
18113
18114 let fifo_ran = Arc::new(AtomicBool::new(false));
18115 let fifo_flag = Arc::clone(&fifo_ran);
18116 let scheduler = Arc::clone(&executor);
18117 let (fifo, task) = async_task::spawn(
18118 async move {
18119 fifo_flag.store(true, Ordering::SeqCst);
18120 },
18121 move |runnable| scheduler.schedule(runnable),
18122 );
18123 task.detach();
18124 executor.metrics.runnable_scheduled();
18125 executor.queue.lock().unwrap().push_back(fifo);
18126
18127 {
18128 let _driver = OnPoolWorkerGuard::enter(executor.id);
18129 let mut future = std::pin::pin!(RefillLifo {
18130 executor: Arc::clone(&executor),
18131 fifo_ran: Arc::clone(&fifo_ran)
18132 });
18133 executor.block_on(future.as_mut());
18134 while executor.run_one() {}
18135 }
18136 executor.active_drainers.store(0, Ordering::SeqCst);
18137 assert!(fifo_ran.load(Ordering::SeqCst));
18138 done_tx.send(()).unwrap();
18139 });
18140 done_rx
18141 .recv_timeout(Duration::from_secs(3))
18142 .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
18143 runner.join().unwrap();
18144 }
18145
18146 #[cfg(not(target_family = "wasm"))]
18147 #[test]
18148 fn blocking_closure_spawn_is_not_stranded_in_slot() {
18149 use std::sync::mpsc;
18156 use std::time::Duration;
18157
18158 let (done_tx, done_rx) = mpsc::channel();
18159 let runner = std::thread::spawn(move || {
18160 let options = RuntimeOptions {
18161 flavor: RuntimeFlavor::MultiThread,
18162 worker_threads: 2,
18163 max_blocking_tasks: 2,
18164 thread_name_prefix: "blk-spawn-wait".to_string(),
18165 park_deadline: None,
18166 };
18167 let executor =
18168 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18169
18170 let blk_exec = Arc::clone(&executor);
18173 let handle = executor.schedule_blocking(move || {
18174 let (tx, rx) = mpsc::channel::<u64>();
18175 let sched = Arc::clone(&blk_exec);
18176 let (runnable, task) = async_task::spawn(
18177 async move {
18178 tx.send(21).unwrap();
18179 },
18180 move |r| sched.schedule(r),
18181 );
18182 blk_exec.schedule(runnable);
18183 task.detach();
18184 rx.recv_timeout(Duration::from_secs(5))
18185 .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
18186 });
18187 assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
18188
18189 let (v_tx, v_rx) = mpsc::channel::<u64>();
18192 let v_exec = Arc::clone(&executor);
18193 let v_handle = executor.schedule_blocking(move || {
18194 let sched = Arc::clone(&v_exec);
18195 let (runnable, task) = async_task::spawn(
18196 async move {
18197 v_tx.send(42).unwrap();
18198 },
18199 move |r| sched.schedule(r),
18200 );
18201 v_exec.schedule(runnable);
18202 task.detach();
18203 1u64
18204 });
18205 assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
18206 assert_eq!(
18207 v_rx
18208 .recv_timeout(Duration::from_secs(5))
18209 .expect("fire-and-forget child must complete"),
18210 42
18211 );
18212 let _ = done_tx.send(());
18213 });
18214
18215 match done_rx.recv_timeout(Duration::from_secs(15)) {
18216 Ok(()) => runner.join().unwrap(),
18217 Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
18218 }
18219 }
18220
18221 #[cfg(not(target_family = "wasm"))]
18222 #[test]
18223 fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
18224 use std::sync::{Barrier, mpsc};
18230 use std::time::Duration;
18231
18232 let (done_tx, done_rx) = mpsc::channel();
18233 let runner = std::thread::spawn(move || {
18234 let metrics = Arc::new(RuntimeMetrics::default());
18235 let options = RuntimeOptions {
18236 flavor: RuntimeFlavor::MultiThread,
18237 worker_threads: 2,
18238 max_blocking_tasks: 2,
18239 thread_name_prefix: "rd1-nested-blk".to_string(),
18240 park_deadline: None,
18241 };
18242 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18243
18244 let outer_count = 2usize; let barrier = Arc::new(Barrier::new(outer_count));
18246
18247 let mut handles = Vec::new();
18248 for _ in 0..outer_count {
18249 let exec = Arc::clone(&executor);
18250 let barrier = Arc::clone(&barrier);
18251 let handle = executor.schedule_blocking(move || {
18252 barrier.wait();
18254 let inner_exec = Arc::clone(&exec);
18255 let mut value = 0usize;
18256 {
18257 let mut inner = std::pin::pin!(async {
18258 value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
18259 });
18260 exec.block_on(inner.as_mut());
18261 }
18262 value
18263 });
18264 handles.push(handle);
18265 }
18266
18267 for handle in handles {
18268 assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
18269 }
18270 let _ = done_tx.send(());
18271 });
18272
18273 match done_rx.recv_timeout(Duration::from_secs(10)) {
18274 Ok(()) => runner.join().unwrap(),
18275 Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
18276 }
18277 }
18278
18279 #[cfg(not(target_family = "wasm"))]
18280 #[test]
18281 fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
18282 let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
18283 let owner = executor.fresh_owner_token();
18284 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18285 let job = BlockingJobId(u64::MAX - 5);
18286 let dependency = TaskDependency::default();
18287 dependency.set_owned(BlockingDependency { job, owner: None });
18288 let ran = Arc::new(AtomicBool::new(false));
18289 let job_ran = Arc::clone(&ran);
18290 executor
18291 .blocking_queue
18292 .lock()
18293 .unwrap()
18294 .push(QueuedBlocking {
18295 id: job,
18296 run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
18297 });
18298
18299 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
18300 assert!(
18301 !executor.try_owned_blocking_over_cap(&dependency),
18302 "an untagged worker must not infer ancestry from a sole active owner"
18303 );
18304 assert!(!ran.load(Ordering::SeqCst));
18305 assert!(
18306 executor
18307 .blocking_queue
18308 .lock()
18309 .unwrap()
18310 .jobs
18311 .contains_key(&job),
18312 "rejected owner inference must leave the exact job queued"
18313 );
18314 }
18315
18316 #[cfg(not(target_family = "wasm"))]
18317 #[test]
18318 fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
18319 use std::sync::{Barrier, mpsc};
18320
18321 let options = RuntimeOptions {
18322 flavor: RuntimeFlavor::MultiThread,
18323 worker_threads: 2,
18324 max_blocking_tasks: 2,
18325 thread_name_prefix: "exact-owner-frame".to_string(),
18326 park_deadline: None,
18327 };
18328 let executor =
18329 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18330 let owners_saturated = Arc::new(Barrier::new(3));
18331 let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
18332 let begin_lending = Arc::new(Barrier::new(3));
18333 let release_dependencies = Arc::new(Barrier::new(3));
18334 let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
18335 let mut owners = Vec::new();
18336
18337 for owner_index in 0..2 {
18338 let owner_executor = Arc::clone(&executor);
18339 let owners_saturated = Arc::clone(&owners_saturated);
18340 let dependency_queued_tx = dependency_queued_tx.clone();
18341 let begin_lending = Arc::clone(&begin_lending);
18342 let release_dependencies = Arc::clone(&release_dependencies);
18343 let dependency_started_tx = dependency_started_tx.clone();
18344 owners.push(executor.schedule_blocking(move || {
18345 let owner_thread = std::thread::current().id();
18346 owners_saturated.wait();
18347 let child_started = dependency_started_tx.clone();
18348 let child_release = Arc::clone(&release_dependencies);
18349 let child = owner_executor.schedule_blocking(move || {
18350 assert_eq!(
18351 std::thread::current().id(),
18352 owner_thread,
18353 "an owner frame must execute only its own exact dependency on its lent lane"
18354 );
18355 child_started.send(owner_index).unwrap();
18356 child_release.wait();
18357 owner_index
18358 });
18359 dependency_queued_tx.send(()).unwrap();
18360 begin_lending.wait();
18361
18362 let mut result = None;
18363 {
18364 let mut future = std::pin::pin!(async {
18365 result = Some(child.await.unwrap());
18366 });
18367 assert_eq!(
18368 owner_executor.block_on(future.as_mut()),
18369 BlockOnOutcome::Completed
18370 );
18371 }
18372 result.unwrap()
18373 }));
18374 }
18375 drop(dependency_queued_tx);
18376 drop(dependency_started_tx);
18377 owners_saturated.wait();
18378
18379 for _ in 0..2 {
18380 dependency_queued_rx
18381 .recv_timeout(Duration::from_secs(2))
18382 .expect("both owner-scoped dependencies must be queued while the cap is saturated");
18383 }
18384 let (unrelated_tx, unrelated_rx) = mpsc::channel();
18385 let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
18386 begin_lending.wait();
18387
18388 let mut started = Vec::new();
18389 for _ in 0..2 {
18390 started.push(
18391 dependency_started_rx
18392 .recv_timeout(Duration::from_secs(2))
18393 .expect("each owner must service its own exact dependency"),
18394 );
18395 }
18396 started.sort_unstable();
18397 assert_eq!(started, vec![0, 1]);
18398 assert!(
18399 unrelated_rx.try_recv().is_err(),
18400 "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
18401 );
18402
18403 release_dependencies.wait();
18404 for (owner_index, owner) in owners.into_iter().enumerate() {
18405 assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
18406 }
18407 unrelated_rx
18408 .recv_timeout(Duration::from_secs(2))
18409 .expect("the unrelated job must run after an owner releases a counted lane");
18410 futures::executor::block_on(unrelated).unwrap();
18411 }
18412
18413 #[cfg(not(target_family = "wasm"))]
18414 #[test]
18415 fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
18416 use std::sync::{Mutex, mpsc};
18417
18418 const DEPENDENCY_COUNT: usize = 4;
18419
18420 let options = RuntimeOptions {
18421 flavor: RuntimeFlavor::MultiThread,
18422 worker_threads: 3,
18423 max_blocking_tasks: 1,
18424 thread_name_prefix: "parked-unrelated-worker".to_string(),
18425 park_deadline: None,
18426 };
18427 let executor =
18428 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18429 let (parked_tx, parked_rx) = mpsc::channel();
18430 let (release_parked_tx, release_parked_rx) = mpsc::channel();
18431 executor.pool.spawn(move || {
18432 parked_tx.send(()).unwrap();
18433 release_parked_rx.recv().unwrap();
18434 });
18435 parked_rx
18436 .recv_timeout(Duration::from_secs(2))
18437 .expect("the unrelated Rayon worker must remain occupied");
18438
18439 let owner_executor = Arc::clone(&executor);
18440 let owner = executor.schedule_blocking(move || {
18441 let owner = BLOCKING_OWNER
18442 .with(std::cell::Cell::get)
18443 .expect("the blocking closure must carry its exact owner frame");
18444 let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
18445 let results = Arc::new(Mutex::new(Vec::new()));
18446 let descendant_results = Arc::clone(&results);
18447 let descendant_executor = Arc::clone(&owner_executor);
18448 rayon::scope(move |scope| {
18449 scope.spawn(move |_| {
18450 assert_eq!(
18451 BLOCKING_OWNER.with(std::cell::Cell::get),
18452 None,
18453 "the parked owner must force its descendant onto the remaining worker"
18454 );
18455 let _owner = BlockingOwnerGuard::enter(Some(owner));
18456 descendant_started_tx.send(()).unwrap();
18457 for value in 0..DEPENDENCY_COUNT {
18458 let handle = descendant_executor.schedule_blocking(move || value);
18459 let mut result = None;
18460 {
18461 let mut future = std::pin::pin!(async {
18462 result = Some(handle.await.unwrap());
18463 });
18464 assert_eq!(
18465 descendant_executor.block_on(future.as_mut()),
18466 BlockOnOutcome::Completed
18467 );
18468 }
18469 descendant_results.lock().unwrap().push(result.unwrap());
18470 }
18471 });
18472 descendant_started_rx
18473 .recv_timeout(Duration::from_secs(2))
18474 .expect("the nested descendant must start on the only unoccupied worker");
18475 });
18476 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
18477 });
18478
18479 assert_eq!(
18480 futures::executor::block_on(owner).unwrap(),
18481 (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
18482 );
18483
18484 let (idle_tx, idle_rx) = mpsc::channel();
18485 let idle_executor = Arc::clone(&executor);
18486 let idle_waiter = std::thread::spawn(move || {
18487 idle_executor.begin_shutdown();
18488 idle_executor.wait_until_scheduler_idle();
18489 idle_tx.send(()).unwrap();
18490 });
18491 idle_rx
18492 .recv_timeout(Duration::from_secs(2))
18493 .expect("an unrelated parked worker must not retain owner-probe idle accounting");
18494
18495 release_parked_tx.send(()).unwrap();
18496 idle_waiter.join().unwrap();
18497 }
18498
18499 #[cfg(not(target_family = "wasm"))]
18500 #[test]
18501 fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
18502 use std::sync::{Condvar, Mutex, mpsc};
18503
18504 let options = RuntimeOptions {
18505 flavor: RuntimeFlavor::MultiThread,
18506 worker_threads: 3,
18507 max_blocking_tasks: 1,
18508 thread_name_prefix: "same-owner-rearm".to_string(),
18509 park_deadline: None,
18510 };
18511 let executor =
18512 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18513 let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
18514 let (job_started_tx, job_started_rx) = mpsc::channel();
18515 let owner_executor = Arc::clone(&executor);
18516 let owner_releases = Arc::clone(&releases);
18517 let owner = executor.schedule_blocking(move || {
18518 let owner = BLOCKING_OWNER
18519 .with(std::cell::Cell::get)
18520 .expect("the blocking closure must carry its exact owner frame");
18521 let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
18522 let results = Arc::new(Mutex::new(Vec::new()));
18523 let scope_results = Arc::clone(&results);
18524 rayon::scope(move |scope| {
18525 for index in 0..2 {
18526 let descendant_ready_tx = descendant_ready_tx.clone();
18527 let descendant_executor = Arc::clone(&owner_executor);
18528 let descendant_results = Arc::clone(&scope_results);
18529 let job_started_tx = job_started_tx.clone();
18530 let releases = Arc::clone(&owner_releases);
18531 scope.spawn(move |_| {
18532 assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
18533 let _owner = BlockingOwnerGuard::enter(Some(owner));
18534 descendant_ready_tx.send(()).unwrap();
18535 let handle = descendant_executor.schedule_blocking(move || {
18536 job_started_tx.send(index).unwrap();
18537 let (released, changed) = &*releases;
18538 let mut state = released.lock().unwrap();
18539 while !state[index] {
18540 state = changed.wait(state).unwrap();
18541 }
18542 index
18543 });
18544 let mut result = None;
18545 {
18546 let mut future = std::pin::pin!(async {
18547 result = Some(handle.await.unwrap());
18548 });
18549 assert_eq!(
18550 descendant_executor.block_on(future.as_mut()),
18551 BlockOnOutcome::Completed
18552 );
18553 }
18554 descendant_results.lock().unwrap().push(result.unwrap());
18555 });
18556 }
18557 drop(descendant_ready_tx);
18558 for _ in 0..2 {
18559 descendant_ready_rx
18560 .recv_timeout(Duration::from_secs(2))
18561 .expect("both descendants must be stolen before the owner leaves its Rayon scope");
18562 }
18563 });
18564 Arc::try_unwrap(results).unwrap().into_inner().unwrap()
18565 });
18566
18567 let first = job_started_rx
18568 .recv_timeout(Duration::from_secs(2))
18569 .expect("one exact dependency must borrow the owner lane");
18570 assert!(
18571 job_started_rx
18572 .recv_timeout(Duration::from_millis(100))
18573 .is_err(),
18574 "one owner frame must not service two dependencies concurrently"
18575 );
18576 let unrelated_parker = Arc::new(DriverParker::default());
18577 executor
18578 .parked_drivers
18579 .register_with_role(&unrelated_parker, true, None);
18580 {
18581 let (released, changed) = &*releases;
18582 released.lock().unwrap()[first] = true;
18583 changed.notify_all();
18584 }
18585
18586 let second = job_started_rx
18587 .recv_timeout(Duration::from_secs(2))
18588 .expect("completion must rearm one parked same-owner dependency");
18589 assert_ne!(second, first);
18590 assert!(
18591 !unrelated_parker.consume_permit(),
18592 "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
18593 );
18594 executor.parked_drivers.deregister(&unrelated_parker);
18595 {
18596 let (released, changed) = &*releases;
18597 released.lock().unwrap()[second] = true;
18598 changed.notify_all();
18599 }
18600
18601 let mut results = futures::executor::block_on(owner).unwrap();
18602 results.sort_unstable();
18603 assert_eq!(results, vec![0, 1]);
18604 }
18605
18606 #[cfg(not(target_family = "wasm"))]
18607 #[test]
18608 fn failed_exact_claim_rearms_parked_same_owner_dependency() {
18609 let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
18610 let owner = executor.fresh_owner_token();
18611 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18612 let failed_dependency = Arc::new(TaskDependency::default());
18613 failed_dependency.set_owned(BlockingDependency {
18614 job: BlockingJobId(u64::MAX - 21),
18615 owner: Some(owner),
18616 });
18617 let parked_dependency = Arc::new(TaskDependency::default());
18618 parked_dependency.set_owned(BlockingDependency {
18619 job: BlockingJobId(u64::MAX - 22),
18620 owner: Some(owner),
18621 });
18622 let parker = Arc::new(DriverParker::default());
18623
18624 let queue = executor.blocking_queue.lock().unwrap();
18625 let claimant_executor = Arc::clone(&executor);
18626 let claimant_dependency = Arc::clone(&failed_dependency);
18627 let claimant = std::thread::spawn(move || {
18628 let _owner = BlockingOwnerGuard::enter(Some(owner));
18629 claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
18630 });
18631 wait_until("the failed claimant to reserve the owner lane", || {
18632 !executor.active_blocking_owners.is_available(owner)
18633 });
18634 executor
18635 .parked_drivers
18636 .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
18637 failed_dependency.clear();
18638 drop(queue);
18639
18640 assert!(
18641 !claimant.join().unwrap(),
18642 "the cancelled dependency must not execute"
18643 );
18644 assert!(
18645 parker.consume_permit(),
18646 "releasing a failed exact claim must hand the owner lane to another parked dependency"
18647 );
18648 }
18649
18650 #[cfg(not(target_family = "wasm"))]
18651 #[test]
18652 fn exact_owner_lending_does_not_start_blocking_after_stop() {
18653 use std::sync::mpsc;
18654
18655 let options = RuntimeOptions {
18656 flavor: RuntimeFlavor::MultiThread,
18657 worker_threads: 2,
18658 max_blocking_tasks: 1,
18659 thread_name_prefix: "stopped-exact-owner".to_string(),
18660 park_deadline: None,
18661 };
18662 let executor =
18663 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18664 let ran = Arc::new(AtomicBool::new(false));
18665 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
18666 let (release_claim_tx, release_claim_rx) = mpsc::channel();
18667 let owner_executor = Arc::clone(&executor);
18668 let child_ran = Arc::clone(&ran);
18669 let owner = executor.schedule_blocking(move || {
18670 BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
18671 *slot.borrow_mut() = Some(Box::new(move || {
18672 claim_entered_tx.send(()).unwrap();
18673 release_claim_rx.recv().unwrap();
18674 }));
18675 });
18676 let child = owner_executor.schedule_blocking(move || {
18677 child_ran.store(true, Ordering::SeqCst);
18678 });
18679 let mut future = std::pin::pin!(async move {
18680 let _ = child.await;
18681 });
18682 owner_executor.block_on(future.as_mut())
18683 });
18684
18685 claim_entered_rx
18686 .recv_timeout(Duration::from_secs(2))
18687 .expect("the cooperative driver must pause before the exact-owner queue claim");
18688 assert_eq!(
18689 executor.blocking_queue.lock().unwrap().jobs.len(),
18690 1,
18691 "the exact dependency must remain queued before stop publication"
18692 );
18693
18694 executor.stop.begin_shutdown();
18697 release_claim_tx.send(()).unwrap();
18698
18699 assert_eq!(
18700 futures::executor::block_on(owner).unwrap(),
18701 BlockOnOutcome::Stopped,
18702 "the cooperative driver must stop instead of executing its queued dependency"
18703 );
18704 assert!(
18705 !ran.load(Ordering::SeqCst),
18706 "exact-owner lending started blocking work after stop"
18707 );
18708 assert_eq!(
18709 executor.blocking_queue.lock().unwrap().jobs.len(),
18710 1,
18711 "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
18712 );
18713
18714 executor.begin_shutdown();
18715 executor.wait_until_scheduler_idle();
18716 assert!(executor.blocking_queue.lock().unwrap().is_empty());
18717 assert!(!ran.load(Ordering::SeqCst));
18718 }
18719
18720 #[cfg(not(target_family = "wasm"))]
18721 #[test]
18722 fn exact_owner_lending_attempts_scale_linearly_with_topology() {
18723 const DEPENDENCY_COUNT: usize = 1024;
18724
18725 let options = RuntimeOptions {
18726 flavor: RuntimeFlavor::MultiThread,
18727 worker_threads: 2,
18728 max_blocking_tasks: 1,
18729 thread_name_prefix: "linear-owner-lending".to_string(),
18730 park_deadline: None,
18731 };
18732 let executor =
18733 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18734 let completed = Arc::new(AtomicUsize::new(0));
18735
18736 for index in 0..DEPENDENCY_COUNT {
18737 let owner = executor.fresh_owner_token();
18738 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18739 let dependency = TaskDependency::new(u64::MAX, Some(owner));
18740 let blocking = BlockingDependency {
18741 job: BlockingJobId(u64::MAX - index as u64),
18742 owner: Some(owner),
18743 };
18744 dependency.set_owned(blocking);
18745 let completed = Arc::clone(&completed);
18746 executor
18747 .blocking_queue
18748 .lock()
18749 .unwrap()
18750 .push(QueuedBlocking {
18751 id: blocking.job,
18752 run: Box::new(move || {
18753 completed.fetch_add(1, Ordering::SeqCst);
18754 }),
18755 });
18756
18757 let _owner = BlockingOwnerGuard::enter(Some(owner));
18758 assert!(executor.try_owned_blocking_over_cap(&dependency));
18759 }
18760
18761 assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
18762 assert_eq!(
18763 executor.owner_lending_attempts.load(Ordering::Relaxed),
18764 DEPENDENCY_COUNT,
18765 "targeted lending must perform one O(1) claim attempt per exact dependency"
18766 );
18767 let queue = executor.blocking_queue.lock().unwrap();
18768 assert!(queue.is_empty());
18769 assert_eq!(queue.head, None);
18770 assert_eq!(queue.tail, None);
18771 }
18772
18773 #[cfg(not(target_family = "wasm"))]
18774 #[test]
18775 fn one_dependency_interspersed_owner_publications_stays_linear() {
18776 const PUBLICATION_COUNT: usize = 1024;
18777
18778 let executor = multi_thread_executor(2, None, "linear-publication-lending");
18779 let owner = executor.fresh_owner_token();
18780 let wrong_owner = executor.fresh_owner_token();
18781 let dependency = TaskDependency::new(u64::MAX, Some(owner));
18782 let completed = Arc::new(AtomicUsize::new(0));
18783 for index in 0..PUBLICATION_COUNT {
18784 let cancelled_source = TaskDependency::default();
18785 cancelled_source.set_owned(BlockingDependency {
18786 job: BlockingJobId(u64::MAX - 100_000 - index as u64),
18787 owner: Some(owner),
18788 });
18789 dependency.set_borrowed(
18790 &cancelled_source
18791 .get()
18792 .expect("the source publication must initially be live"),
18793 );
18794 cancelled_source.clear();
18795
18796 dependency.set_owned(BlockingDependency {
18797 job: BlockingJobId(u64::MAX - 200_000 - index as u64),
18798 owner: Some(wrong_owner),
18799 });
18800 let blocking = BlockingDependency {
18801 job: BlockingJobId(u64::MAX - 1_000 - index as u64),
18802 owner: Some(owner),
18803 };
18804 dependency.set_owned(blocking);
18805 let completed = Arc::clone(&completed);
18806 executor
18807 .blocking_queue
18808 .lock()
18809 .unwrap()
18810 .push(QueuedBlocking {
18811 id: blocking.job,
18812 run: Box::new(move || {
18813 completed.fetch_add(1, Ordering::SeqCst);
18814 }),
18815 });
18816 }
18817 dependency.reset_entry_visits();
18818
18819 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
18820 let _owner = BlockingOwnerGuard::enter(Some(owner));
18821 for _ in 0..PUBLICATION_COUNT {
18822 assert!(
18823 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18824 "the owner-handoff predicate must find the next exact publication"
18825 );
18826 assert!(
18827 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
18828 "the park-availability predicate must find the next serviceable publication"
18829 );
18830 assert!(executor.try_owned_blocking_over_cap(&dependency));
18831 }
18832
18833 assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
18834 assert!(
18835 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18836 "wrong-owner publications must not satisfy an exact handoff"
18837 );
18838 assert!(
18839 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
18840 "wrong-owner publications must not make the owner's lane appear serviceable"
18841 );
18842 assert!(
18843 dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
18844 "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
18845 dependency.entry_visits()
18846 );
18847 assert!(executor.blocking_queue.lock().unwrap().is_empty());
18848 }
18849
18850 #[cfg(not(target_family = "wasm"))]
18851 #[test]
18852 fn binding_unowned_dependency_updates_exact_owner_predicate() {
18853 let owner = BlockingOwnerToken {
18854 executor_id: u64::MAX - 200,
18855 frame: u64::MAX - 201,
18856 };
18857 let dependency = TaskDependency::default();
18858 let blocking = BlockingDependency {
18859 job: BlockingJobId(u64::MAX - 202),
18860 owner: None,
18861 };
18862 dependency.set_owned(blocking);
18863
18864 assert!(
18865 !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18866 "an unowned publication must not satisfy an exact owner handoff"
18867 );
18868 assert!(
18869 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
18870 "an unowned publication is available to the ambient owner"
18871 );
18872
18873 let selected = dependency
18874 .select_for_owner(owner)
18875 .expect("the owner must bind the unowned publication");
18876 assert_eq!(selected.publication.dependency.owner, Some(owner));
18877 assert!(
18878 dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
18879 "binding must immediately publish the entry to exact-owner predicates"
18880 );
18881 assert!(dependency.try_claim(&selected));
18882 assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
18883 }
18884
18885 #[cfg(not(target_family = "wasm"))]
18886 #[test]
18887 fn owner_selection_preserves_unowned_and_exact_publication_order() {
18888 let owner = BlockingOwnerToken {
18889 executor_id: u64::MAX - 210,
18890 frame: u64::MAX - 211,
18891 };
18892 let dependency = TaskDependency::default();
18893 let first = BlockingDependency {
18894 job: BlockingJobId(u64::MAX - 212),
18895 owner: None,
18896 };
18897 let second = BlockingDependency {
18898 job: BlockingJobId(u64::MAX - 213),
18899 owner: Some(owner),
18900 };
18901 dependency.set_owned(first);
18902 dependency.set_owned(second);
18903
18904 let selected = dependency
18905 .select_for_owner(owner)
18906 .expect("the first publication must be found");
18907 assert_eq!(
18908 selected.publication.dependency,
18909 BlockingDependency {
18910 owner: Some(owner),
18911 ..first
18912 }
18913 );
18914 assert!(dependency.try_claim(&selected));
18915
18916 let selected = dependency
18917 .select_for_owner(owner)
18918 .expect("the second publication must remain eligible");
18919 assert_eq!(selected.publication.dependency, second);
18920 }
18921
18922 #[cfg(not(target_family = "wasm"))]
18923 #[test]
18924 fn blocking_queue_exact_removal_reclaims_fifo_links() {
18925 const JOB_COUNT: u64 = 4096;
18926
18927 let mut queue = BlockingQueue {
18928 closed: false,
18929 head: None,
18930 tail: None,
18931 jobs: FxHashMap::default(),
18932 };
18933 for id in 1..=JOB_COUNT {
18934 queue.push(QueuedBlocking {
18935 id: BlockingJobId(id),
18936 run: Box::new(|| {}),
18937 });
18938 }
18939 for id in (2..=JOB_COUNT).step_by(2) {
18940 drop(
18941 queue
18942 .remove(BlockingJobId(id))
18943 .expect("even job must remain indexed"),
18944 );
18945 }
18946 for id in (1..=JOB_COUNT).step_by(2) {
18947 drop(
18948 queue
18949 .remove(BlockingJobId(id))
18950 .expect("odd job must remain indexed"),
18951 );
18952 }
18953 assert!(queue.jobs.is_empty());
18954 assert_eq!(queue.head, None);
18955 assert_eq!(queue.tail, None);
18956
18957 let order = Arc::new(Mutex::new(Vec::new()));
18958 for id in 1..=3 {
18959 let order = Arc::clone(&order);
18960 queue.push(QueuedBlocking {
18961 id: BlockingJobId(id),
18962 run: Box::new(move || order.lock().unwrap().push(id)),
18963 });
18964 }
18965 drop(
18966 queue
18967 .remove(BlockingJobId(2))
18968 .expect("middle job must be removable"),
18969 );
18970 queue.pop_front().expect("FIFO head must remain linked")();
18971 queue.pop_front().expect("FIFO tail must remain linked")();
18972 assert!(queue.pop_front().is_none());
18973 assert_eq!(*order.lock().unwrap(), vec![1, 3]);
18974 assert_eq!(queue.head, None);
18975 assert_eq!(queue.tail, None);
18976 }
18977
18978 #[cfg(not(target_family = "wasm"))]
18979 #[test]
18980 fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
18981 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
18982
18983 if std::env::var_os(CHILD_ENV).is_some() {
18984 configure(RuntimeOptions {
18985 flavor: RuntimeFlavor::MultiThread,
18986 worker_threads: 2,
18987 max_blocking_tasks: 1,
18988 thread_name_prefix: "dependency-lending-metrics".to_string(),
18989 park_deadline: None,
18990 })
18991 .unwrap();
18992
18993 let outer = spawn_blocking(|| {
18994 let inner = spawn_blocking(|| 42usize);
18995 block_on(inner).unwrap()
18996 });
18997 assert_eq!(block_on(outer).unwrap(), 42);
18998
18999 shutdown().unwrap();
19004 let snapshot = metrics();
19005 assert_eq!(snapshot.blocking_tasks_started, 2);
19006 assert_eq!(snapshot.blocking_tasks_completed, 2);
19007 assert_eq!(snapshot.active_blocking_tasks, 0);
19008 assert_eq!(
19009 snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
19010 "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
19011 );
19012 return;
19013 }
19014
19015 let output = std::process::Command::new(std::env::current_exe().unwrap())
19016 .arg("--exact")
19017 .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
19018 .arg("--nocapture")
19019 .env(CHILD_ENV, "1")
19020 .output()
19021 .expect("the dependency-lending metrics subprocess must start");
19022 assert!(
19023 output.status.success(),
19024 "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
19025 output.status.code(),
19026 String::from_utf8_lossy(&output.stdout),
19027 String::from_utf8_lossy(&output.stderr)
19028 );
19029 }
19030
19031 #[cfg(not(target_family = "wasm"))]
19032 #[test]
19033 fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
19034 use std::sync::mpsc;
19035
19036 let (done_tx, done_rx) = mpsc::channel();
19037 let runner = std::thread::spawn(move || {
19038 let options = RuntimeOptions {
19039 flavor: RuntimeFlavor::MultiThread,
19040 worker_threads: 2,
19041 max_blocking_tasks: 1,
19042 thread_name_prefix: "exact-blocking-job".to_string(),
19043 park_deadline: None,
19044 };
19045 let executor =
19046 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19047 let outer_exec = Arc::clone(&executor);
19048 let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
19049 let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
19050 let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
19051 let outer = executor.schedule_blocking(move || {
19052 let sibling = outer_exec.schedule_blocking(move || {
19053 sibling_started_tx.send(()).unwrap();
19054 release_sibling_rx.recv().unwrap();
19055 });
19056 let awaited = outer_exec.schedule_blocking(move || {
19057 awaited_ran_tx.send(()).unwrap();
19058 17usize
19059 });
19060 let mut value = None;
19061 {
19062 let mut future = std::pin::pin!(async {
19063 value = Some(awaited.await.unwrap());
19064 });
19065 outer_exec.block_on(future.as_mut());
19066 }
19067 (sibling, value.unwrap())
19068 });
19069
19070 awaited_ran_rx
19071 .recv_timeout(Duration::from_secs(2))
19072 .expect("the exact awaited job must bypass the earlier detached sibling");
19073 let (sibling, value) = futures::executor::block_on(outer).unwrap();
19074 assert_eq!(value, 17);
19075 sibling_started_rx
19076 .recv_timeout(Duration::from_secs(2))
19077 .expect("the sibling must run after the owner releases its counted slot");
19078 release_sibling_tx.send(()).unwrap();
19079 futures::executor::block_on(sibling).unwrap();
19080 done_tx.send(()).unwrap();
19081 });
19082 done_rx
19083 .recv_timeout(Duration::from_secs(5))
19084 .expect("same-owner sibling selection deadlocked the exact awaited job");
19085 runner.join().unwrap();
19086 }
19087
19088 #[cfg(not(target_family = "wasm"))]
19089 #[test]
19090 fn blocking_dependency_propagates_through_async_join_lineage() {
19091 use std::sync::mpsc;
19092
19093 let (done_tx, done_rx) = mpsc::channel();
19094 let runner = std::thread::spawn(move || {
19095 let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
19096 let owner_controller = Arc::clone(&controller);
19097 let (child_tx, child_rx) = mpsc::channel();
19098 let outer = controller
19099 .try_spawn_blocking(move || {
19100 let async_controller = Arc::clone(&owner_controller);
19101 let async_child = owner_controller
19102 .try_spawn(async move {
19103 async_controller
19104 .try_spawn_blocking(move || {
19105 child_tx.send(()).unwrap();
19106 23usize
19107 })
19108 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
19109 .await
19110 .unwrap()
19111 })
19112 .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
19113 let mut value = None;
19114 {
19115 let mut future = std::pin::pin!(async {
19116 value = Some(async_child.await.unwrap());
19117 });
19118 owner_controller.block_on(future.as_mut());
19119 }
19120 value.unwrap()
19121 })
19122 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19123
19124 child_rx
19125 .recv_timeout(Duration::from_secs(2))
19126 .expect("the async descendant's exact blocking dependency must inherit the owner lane");
19127 assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
19128 controller.shutdown().unwrap();
19129 done_tx.send(()).unwrap();
19130 });
19131 done_rx
19132 .recv_timeout(Duration::from_secs(5))
19133 .expect("blocking dependency lineage deadlocked");
19134 runner.join().unwrap();
19135 }
19136
19137 #[cfg(not(target_family = "wasm"))]
19138 #[test]
19139 fn blocking_dependency_does_not_escape_a_serviced_runnable() {
19140 use std::sync::mpsc;
19141
19142 let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
19143 let outer_controller = Arc::clone(&controller);
19144 let (child_queued_tx, child_queued_rx) = mpsc::channel();
19145 let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
19146 let (release_outer_tx, release_outer_rx) = oneshot::channel();
19147 let outer = controller
19148 .try_spawn_blocking(move || {
19149 let child_controller = Arc::clone(&outer_controller);
19150 let child = outer_controller
19151 .try_spawn(async move {
19152 let blocking = child_controller
19153 .try_spawn_blocking(move || {
19154 unrelated_ran_tx.send(()).unwrap();
19155 31usize
19156 })
19157 .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
19158 child_queued_tx.send(()).unwrap();
19159 blocking.await.unwrap()
19160 })
19161 .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
19162 let mut released = false;
19163 {
19164 let mut future = std::pin::pin!(async {
19165 release_outer_rx.await.unwrap();
19166 released = true;
19167 });
19168 outer_controller.block_on(future.as_mut());
19169 }
19170 assert!(released);
19171 child
19172 })
19173 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19174
19175 child_queued_rx
19176 .recv_timeout(Duration::from_secs(2))
19177 .expect("the cooperative driver must service the unrelated async runnable");
19178 assert!(
19179 unrelated_ran_rx
19180 .recv_timeout(Duration::from_millis(200))
19181 .is_err(),
19182 "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
19183 );
19184
19185 release_outer_tx.send(()).unwrap();
19186 let child = futures::executor::block_on(outer).unwrap();
19187 unrelated_ran_rx
19188 .recv_timeout(Duration::from_secs(2))
19189 .expect("the unrelated blocking job must run after the owner releases its counted slot");
19190 assert_eq!(futures::executor::block_on(child).unwrap(), 31);
19191 controller.shutdown().unwrap();
19192 }
19193
19194 #[cfg(not(target_family = "wasm"))]
19195 #[test]
19196 fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
19197 let parent = Arc::new(TaskDependency::default());
19198 let _context = TaskDependencyGuard::enter(&parent);
19199 let dependency = BlockingDependency {
19200 job: BlockingJobId(u64::MAX),
19201 owner: None,
19202 };
19203 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19204 let mut handle = JoinHandle(JoinHandleInner::Blocking {
19205 receiver,
19206 dependency,
19207 });
19208 let waker = futures::task::noop_waker();
19209 let mut cx = Context::from_waker(&waker);
19210
19211 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19212 assert_eq!(parent.get_dependency(), Some(dependency));
19213 drop(handle);
19214 assert_eq!(
19215 parent.get_dependency(),
19216 None,
19217 "an abandoned JoinHandle must clear stale dependency lineage"
19218 );
19219 drop(sender);
19220 }
19221
19222 #[cfg(not(target_family = "wasm"))]
19223 #[test]
19224 fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
19225 let owner = BlockingOwnerToken {
19226 executor_id: u64::MAX - 40,
19227 frame: u64::MAX - 41,
19228 };
19229 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
19230 let _context = TaskDependencyGuard::enter(&parent);
19231 let dependency = BlockingDependency {
19232 job: BlockingJobId(u64::MAX - 42),
19233 owner: None,
19234 };
19235 let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19236 let mut handle = JoinHandle(JoinHandleInner::Blocking {
19237 receiver,
19238 dependency,
19239 });
19240 let waker = futures::task::noop_waker();
19241 let mut cx = Context::from_waker(&waker);
19242
19243 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19244 assert_eq!(
19245 parent.get_dependency(),
19246 Some(BlockingDependency {
19247 owner: Some(owner),
19248 ..dependency
19249 }),
19250 "the ambient task owner must enrich an untagged direct blocking dependency"
19251 );
19252 drop(handle);
19253 assert_eq!(
19254 parent.get_dependency(),
19255 None,
19256 "detach cleanup must match the stable job id after owner enrichment"
19257 );
19258 drop(sender);
19259 }
19260
19261 #[cfg(not(target_family = "wasm"))]
19262 #[test]
19263 fn one_poll_preserves_every_observed_blocking_dependency() {
19264 let parent = Arc::new(TaskDependency::default());
19265 let _context = TaskDependencyGuard::enter(&parent);
19266 let first = BlockingDependency {
19267 job: BlockingJobId(u64::MAX - 60),
19268 owner: None,
19269 };
19270 let second = BlockingDependency {
19271 job: BlockingJobId(u64::MAX - 61),
19272 owner: None,
19273 };
19274 let (first_sender, first_receiver) =
19275 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19276 let (second_sender, second_receiver) =
19277 oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
19278 let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
19279 receiver: first_receiver,
19280 dependency: first,
19281 });
19282 let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
19283 receiver: second_receiver,
19284 dependency: second,
19285 });
19286 let waker = futures::task::noop_waker();
19287 let mut cx = Context::from_waker(&waker);
19288
19289 assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
19290 assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
19291 assert_eq!(
19292 parent.get_dependencies(),
19293 vec![first, second],
19294 "publishing a later dependency in one user poll must not replace an earlier dependency"
19295 );
19296
19297 drop(first_handle);
19298 drop(second_handle);
19299 drop(first_sender);
19300 drop(second_sender);
19301 }
19302
19303 #[cfg(not(target_family = "wasm"))]
19304 #[test]
19305 fn dependency_tls_borrow_is_released_before_reentrant_wake() {
19306 use std::sync::mpsc;
19307
19308 struct ReenterDependencyContext {
19309 dependency: Arc<TaskDependency>,
19310 woke: mpsc::Sender<()>,
19311 }
19312
19313 impl std::task::Wake for ReenterDependencyContext {
19314 fn wake(self: Arc<Self>) {
19315 let _context = TaskDependencyGuard::enter(&self.dependency);
19316 self.woke.send(()).unwrap();
19317 }
19318 }
19319
19320 let parent = Arc::new(TaskDependency::default());
19321 let nested = Arc::new(TaskDependency::default());
19322 let (woke_tx, woke_rx) = mpsc::channel();
19323 let waiter = Waker::from(Arc::new(ReenterDependencyContext {
19324 dependency: Arc::clone(&nested),
19325 woke: woke_tx,
19326 }));
19327 parent.register_waiter(&waiter);
19328 let blocking = BlockingDependency {
19329 job: BlockingJobId(u64::MAX - 62),
19330 owner: None,
19331 };
19332
19333 let _context = TaskDependencyGuard::enter(&parent);
19334 record_blocking_dependency(blocking);
19335 woke_rx
19336 .recv_timeout(Duration::from_secs(1))
19337 .expect("dependency notification must permit reentrant TLS context access");
19338
19339 parent.register_waiter(&waiter);
19340 clear_blocking_dependency(blocking);
19341 woke_rx
19342 .recv_timeout(Duration::from_secs(1))
19343 .expect("dependency clearing must permit reentrant TLS context access");
19344 }
19345
19346 #[cfg(not(target_family = "wasm"))]
19347 #[test]
19348 fn dependency_free_begin_poll_does_not_lock_state() {
19349 use std::sync::mpsc;
19350
19351 let dependency = Arc::new(TaskDependency::default());
19352 let state = dependency.state.lock().unwrap();
19353 let (completed_tx, completed_rx) = mpsc::channel();
19354 let polling_dependency = Arc::clone(&dependency);
19355 let polling = std::thread::spawn(move || {
19356 polling_dependency.begin_poll();
19357 completed_tx.send(()).unwrap();
19358 });
19359
19360 let completed_without_lock = completed_rx
19361 .recv_timeout(Duration::from_millis(200))
19362 .is_ok();
19363 drop(state);
19364 polling.join().unwrap();
19365 assert!(
19366 completed_without_lock,
19367 "a task with no published dependency must skip the dependency-state mutex"
19368 );
19369 }
19370
19371 #[cfg(not(target_family = "wasm"))]
19372 #[test]
19373 fn deep_dependency_liveness_chain_is_iterative() {
19374 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
19375
19376 if std::env::var_os(CHILD_ENV).is_some() {
19377 const DEPTH: usize = 100_000;
19378
19379 let mut links = Vec::with_capacity(DEPTH + 1);
19380 links.push(DependencyLink::root());
19381 for _ in 0..DEPTH {
19382 links.push(DependencyLink::derived(links.last().unwrap()));
19383 }
19384 assert!(links.last().unwrap().is_live());
19385 links[0].cancel();
19386 assert!(!links.last().unwrap().is_live());
19387 return;
19388 }
19389
19390 let output = std::process::Command::new(std::env::current_exe().unwrap())
19391 .arg("--exact")
19392 .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
19393 .arg("--nocapture")
19394 .env(CHILD_ENV, "1")
19395 .output()
19396 .expect("the subprocess test must start");
19397 assert!(
19398 output.status.success(),
19399 "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
19400 output.status.code(),
19401 String::from_utf8_lossy(&output.stdout),
19402 String::from_utf8_lossy(&output.stderr)
19403 );
19404 }
19405
19406 #[cfg(not(target_family = "wasm"))]
19407 #[test]
19408 fn task_dependency_set_contains_panicking_waiter() {
19409 struct PanicWake;
19410 impl std::task::Wake for PanicWake {
19411 fn wake(self: Arc<Self>) {
19412 panic!("intentional TaskDependency::set waiter panic");
19413 }
19414 }
19415
19416 let dependency = TaskDependency::default();
19417 let blocking = BlockingDependency {
19418 job: BlockingJobId(u64::MAX),
19419 owner: None,
19420 };
19421 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
19422
19423 let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
19424 assert!(
19425 result.is_ok(),
19426 "set notification must not unwind its caller"
19427 );
19428 assert_eq!(
19429 dependency.get_dependency(),
19430 Some(blocking),
19431 "set must commit before notifying its waiter"
19432 );
19433 }
19434
19435 #[cfg(not(target_family = "wasm"))]
19436 #[test]
19437 fn task_dependency_clear_contains_panicking_waiter() {
19438 struct PanicWake;
19439 impl std::task::Wake for PanicWake {
19440 fn wake(self: Arc<Self>) {
19441 panic!("intentional TaskDependency::clear waiter panic");
19442 }
19443 }
19444
19445 let dependency = TaskDependency::default();
19446 dependency.set_owned(BlockingDependency {
19447 job: BlockingJobId(u64::MAX),
19448 owner: None,
19449 });
19450 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
19451
19452 let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
19453 assert!(
19454 result.is_ok(),
19455 "clear notification must not unwind its caller"
19456 );
19457 assert_eq!(
19458 dependency.get_dependency(),
19459 None,
19460 "clear must commit before notifying its waiter"
19461 );
19462 }
19463
19464 #[cfg(not(target_family = "wasm"))]
19465 #[test]
19466 fn task_dependency_clear_if_contains_panicking_waiter() {
19467 struct PanicWake;
19468 impl std::task::Wake for PanicWake {
19469 fn wake(self: Arc<Self>) {
19470 panic!("intentional TaskDependency::clear_if waiter panic");
19471 }
19472 }
19473
19474 let dependency = TaskDependency::default();
19475 let blocking = BlockingDependency {
19476 job: BlockingJobId(u64::MAX),
19477 owner: None,
19478 };
19479 dependency.set_owned(blocking);
19480 dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
19481
19482 let result = catch_unwind(AssertUnwindSafe(|| {
19483 dependency.clear_if_dependency(blocking)
19484 }));
19485 assert!(
19486 result.is_ok(),
19487 "clear_if notification must not unwind its caller"
19488 );
19489 assert_eq!(
19490 dependency.get_dependency(),
19491 None,
19492 "clear_if must commit before notifying its waiter"
19493 );
19494 }
19495
19496 #[cfg(not(target_family = "wasm"))]
19497 #[test]
19498 fn task_dependency_waiter_replacement_contains_panicking_drop() {
19499 use std::sync::mpsc;
19500
19501 const GENERATION: u64 = u64::MAX - 20;
19502
19503 struct PanicOnDropWake {
19504 dropped: mpsc::Sender<Option<u64>>,
19505 }
19506
19507 #[expect(
19508 clippy::manual_noop_waker,
19509 reason = "the test observes the waker payload's Drop"
19510 )]
19511 impl std::task::Wake for PanicOnDropWake {
19512 fn wake(self: Arc<Self>) {}
19513 }
19514
19515 impl Drop for PanicOnDropWake {
19516 fn drop(&mut self) {
19517 self
19518 .dropped
19519 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
19520 .unwrap();
19521 panic!("intentional replaced dependency waiter destructor panic");
19522 }
19523 }
19524
19525 let dependency = TaskDependency::new(GENERATION, None);
19526 let (dropped_tx, dropped_rx) = mpsc::channel();
19527 let waiter = Waker::from(Arc::new(PanicOnDropWake {
19528 dropped: dropped_tx,
19529 }));
19530 dependency.register_waiter(&waiter);
19531 drop(waiter);
19532
19533 let replacement = futures::task::noop_waker();
19534 let result = catch_unwind(AssertUnwindSafe(|| {
19535 dependency.register_waiter(&replacement)
19536 }));
19537 assert!(
19538 result.is_ok(),
19539 "replacing a hostile waiter must not unwind registration"
19540 );
19541 assert_eq!(
19542 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
19543 Some(GENERATION),
19544 "the replaced waiter must be destroyed under its runtime generation"
19545 );
19546 }
19547
19548 #[cfg(not(target_family = "wasm"))]
19549 #[test]
19550 fn task_dependency_retirement_contains_stored_waiter_drop() {
19551 use std::sync::mpsc;
19552
19553 const GENERATION: u64 = u64::MAX - 21;
19554
19555 struct PanicOnDropWake {
19556 dropped: mpsc::Sender<Option<u64>>,
19557 }
19558
19559 #[expect(
19560 clippy::manual_noop_waker,
19561 reason = "the test observes the waker payload's Drop"
19562 )]
19563 impl std::task::Wake for PanicOnDropWake {
19564 fn wake(self: Arc<Self>) {}
19565 }
19566
19567 impl Drop for PanicOnDropWake {
19568 fn drop(&mut self) {
19569 self
19570 .dropped
19571 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
19572 .unwrap();
19573 panic!("intentional stored dependency waiter destructor panic");
19574 }
19575 }
19576
19577 let dependency = TaskDependency::new(GENERATION, None);
19578 let (dropped_tx, dropped_rx) = mpsc::channel();
19579 let waiter = Waker::from(Arc::new(PanicOnDropWake {
19580 dropped: dropped_tx,
19581 }));
19582 dependency.register_waiter(&waiter);
19583 drop(waiter);
19584
19585 let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
19586 assert!(
19587 result.is_ok(),
19588 "retiring a hostile stored waiter must not unwind"
19589 );
19590 assert_eq!(
19591 dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
19592 Some(GENERATION),
19593 "the retained waiter must be destroyed under its runtime generation"
19594 );
19595 }
19596
19597 #[cfg(not(target_family = "wasm"))]
19598 #[test]
19599 fn detaching_task_clears_dependency_waiter_parent_retention() {
19600 struct RetainingWake {
19601 retained_parent: Arc<()>,
19602 }
19603
19604 impl std::task::Wake for RetainingWake {
19605 fn wake(self: Arc<Self>) {
19606 let _ = &self.retained_parent;
19607 }
19608 }
19609
19610 let dependency = Arc::new(TaskDependency::default());
19611 let retained_parent = Arc::new(());
19612 let retained_parent_weak = Arc::downgrade(&retained_parent);
19613 let waiter = Waker::from(Arc::new(RetainingWake {
19614 retained_parent: Arc::clone(&retained_parent),
19615 }));
19616 dependency.register_waiter(&waiter);
19617 drop(waiter);
19618 drop(retained_parent);
19619 assert!(
19620 retained_parent_weak.upgrade().is_some(),
19621 "the stored dependency waiter must retain its parent before detachment"
19622 );
19623
19624 let (runnable, task) = async_task::spawn(
19625 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
19626 |_| {},
19627 );
19628 drop(JoinHandle(JoinHandleInner::Task {
19629 task: task.fallible(),
19630 awaiter: None,
19631 dependency: Arc::clone(&dependency),
19632 }));
19633 assert!(
19634 retained_parent_weak.upgrade().is_none(),
19635 "detachment must clear the dependency waiter instead of retaining its parent task"
19636 );
19637 drop(runnable);
19638 }
19639
19640 #[cfg(not(target_family = "wasm"))]
19641 #[test]
19642 fn detaching_task_clears_many_parent_publications_linearly() {
19643 const PUBLICATION_COUNT: usize = 1024;
19644
19645 struct CountWake(Arc<AtomicUsize>);
19646
19647 impl std::task::Wake for CountWake {
19648 fn wake(self: Arc<Self>) {
19649 self.0.fetch_add(1, Ordering::SeqCst);
19650 }
19651 }
19652
19653 fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
19654 dependency
19655 .state
19656 .lock()
19657 .unwrap_or_else(std::sync::PoisonError::into_inner)
19658 .current
19659 .back()
19660 .expect("the sentinel publication must be appended")
19661 .publication
19662 .clone()
19663 }
19664
19665 let owner = BlockingOwnerToken {
19666 executor_id: u64::MAX - 300,
19667 frame: u64::MAX - 301,
19668 };
19669 let other_owner = BlockingOwnerToken {
19670 executor_id: u64::MAX - 302,
19671 frame: u64::MAX - 303,
19672 };
19673 let child = Arc::new(TaskDependency::default());
19674 for index in 0..PUBLICATION_COUNT {
19675 child.set_owned(BlockingDependency {
19676 job: BlockingJobId(1_000_000 + index as u64),
19677 owner: (index % 2 == 0).then_some(owner),
19678 });
19679 }
19680 let child_publications = child.snapshot_all_live();
19681 let source = child_publications[0].clone();
19682
19683 let parent = Arc::new(TaskDependency::default());
19684 let _context = TaskDependencyGuard::enter(&parent);
19685 let (runnable, task) = async_task::spawn(
19686 futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
19687 |_| {},
19688 );
19689 let mut handle = JoinHandle(JoinHandleInner::Task {
19690 task: task.fallible(),
19691 awaiter: None,
19692 dependency: Arc::clone(&child),
19693 });
19694 let waker = futures::task::noop_waker();
19695 let mut cx = Context::from_waker(&waker);
19696 assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19697 assert_eq!(
19698 parent.get_dependencies().len(),
19699 PUBLICATION_COUNT,
19700 "polling the child handle must propagate every current publication"
19701 );
19702
19703 let mut wrong_claim_source = source.clone();
19704 wrong_claim_source.claim = Arc::new(DependencyClaim::new());
19705 parent.set_borrowed(&wrong_claim_source);
19706 let wrong_claim_publication = latest_publication(&parent);
19707
19708 let mut wrong_job_source = source.clone();
19709 wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
19710 parent.set_borrowed(&wrong_job_source);
19711 let wrong_job_publication = latest_publication(&parent);
19712
19713 let wrong_immediate_source = source.derived(None);
19714 parent.set_borrowed(&wrong_immediate_source);
19715 let wrong_immediate_publication = latest_publication(&parent);
19716
19717 let wakes = Arc::new(AtomicUsize::new(0));
19718 parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
19719 parent.reset_entry_visits();
19720 drop(handle);
19721
19722 assert_eq!(
19723 wakes.load(Ordering::SeqCst),
19724 1,
19725 "one batched parent cleanup must emit one waiter notification"
19726 );
19727 assert!(
19728 parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
19729 "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
19730 parent.entry_visits()
19731 );
19732 assert!(
19733 wrong_claim_publication.is_live(),
19734 "claim identity must remain part of the match"
19735 );
19736 assert!(
19737 wrong_job_publication.is_live(),
19738 "job identity must remain part of the match"
19739 );
19740 assert!(
19741 wrong_immediate_publication.is_live(),
19742 "only an exact immediate-parent link may be retired"
19743 );
19744
19745 {
19746 let state = parent
19747 .state
19748 .lock()
19749 .unwrap_or_else(std::sync::PoisonError::into_inner);
19750 assert_eq!(state.current.len(), 3);
19751 assert_eq!(
19752 state
19753 .job_sequences
19754 .values()
19755 .map(FxHashSet::len)
19756 .sum::<usize>(),
19757 3,
19758 "the job index must retain only the three non-matching sentinels"
19759 );
19760 assert!(state.unowned_sequences.is_empty());
19761 assert_eq!(state.owner_sequences.len(), 1);
19762 assert_eq!(
19763 state.owner_sequences.get(&owner).map(VecDeque::len),
19764 Some(3)
19765 );
19766 }
19767 assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
19768 assert!(
19769 !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
19770 "cancelled unowned publications must not survive in availability predicates"
19771 );
19772 drop(runnable);
19773 }
19774
19775 #[cfg(not(target_family = "wasm"))]
19776 #[test]
19777 fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
19778 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
19779
19780 if std::env::var_os(CHILD_ENV).is_some() {
19781 struct PanicWake;
19782 impl std::task::Wake for PanicWake {
19783 fn wake(self: Arc<Self>) {
19784 panic!("intentional dependency waker panic");
19785 }
19786 }
19787
19788 struct PanicOnDrop;
19789 impl Drop for PanicOnDrop {
19790 fn drop(&mut self) {
19791 panic!("intentional buffered blocking result destructor panic");
19792 }
19793 }
19794
19795 let parent = Arc::new(TaskDependency::default());
19796 let dependency = BlockingDependency {
19797 job: BlockingJobId(u64::MAX - 1),
19798 owner: None,
19799 };
19800 parent.set_owned(dependency);
19801 parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
19802 let _context = TaskDependencyGuard::enter(&parent);
19803 let (sender, receiver) = oneshot::channel();
19804 let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
19805 ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
19806 ));
19807 drop(JoinHandle(JoinHandleInner::Blocking {
19808 receiver,
19809 dependency,
19810 }));
19811 assert_eq!(parent.get_dependency(), None);
19812 return;
19813 }
19814
19815 let output = std::process::Command::new(std::env::current_exe().unwrap())
19816 .arg("--exact")
19817 .arg(
19818 "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
19819 )
19820 .arg("--nocapture")
19821 .env(CHILD_ENV, "1")
19822 .output()
19823 .expect("the subprocess test must start");
19824 assert!(
19825 output.status.success(),
19826 "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
19827 output.status.code(),
19828 String::from_utf8_lossy(&output.stdout),
19829 String::from_utf8_lossy(&output.stderr)
19830 );
19831 }
19832
19833 #[cfg(not(target_family = "wasm"))]
19834 #[test]
19835 fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
19836 use std::sync::{Barrier, mpsc};
19845 use std::time::Duration;
19846
19847 let (done_tx, done_rx) = mpsc::channel();
19848 let peak_out = Arc::new(Mutex::new(None));
19849 let peak_slot = Arc::clone(&peak_out);
19850 let runner = std::thread::spawn(move || {
19851 let metrics = Arc::new(RuntimeMetrics::default());
19852 let options = RuntimeOptions {
19853 flavor: RuntimeFlavor::MultiThread,
19854 worker_threads: 4,
19855 max_blocking_tasks: 2,
19856 thread_name_prefix: "rd1-cap".to_string(),
19857 park_deadline: None,
19858 };
19859 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
19860
19861 let holder_count = 2usize; let driver_count = 2usize;
19863
19864 let (held_tx, held_rx) = mpsc::channel();
19866 let release = Arc::new(Barrier::new(holder_count + 1));
19867
19868 let mut holder_handles = Vec::new();
19869 for _ in 0..holder_count {
19870 let held_tx = held_tx.clone();
19871 let release = Arc::clone(&release);
19872 let handle = executor.schedule_blocking(move || {
19873 held_tx.send(()).unwrap();
19874 release.wait();
19875 0usize
19876 });
19877 holder_handles.push(handle);
19878 }
19879 for _ in 0..holder_count {
19881 held_rx.recv().unwrap();
19882 }
19883
19884 let mut driver_tasks = Vec::new();
19887 for _ in 0..driver_count {
19888 let exec = Arc::clone(&executor);
19889 let body = async move {
19890 let inner_exec = Arc::clone(&exec);
19891 let mut inner = std::pin::pin!(async move {
19892 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19893 assert_eq!(v, 5);
19894 });
19895 exec.block_on(inner.as_mut());
19896 };
19897 let scheduler = Arc::clone(&executor);
19898 let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19899 executor.schedule(runnable);
19900 driver_tasks.push(task);
19901 }
19902
19903 std::thread::sleep(Duration::from_millis(300));
19906
19907 release.wait();
19909
19910 for task in driver_tasks {
19911 futures::executor::block_on(task);
19912 }
19913 for handle in holder_handles {
19914 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
19915 }
19916
19917 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
19918 let _ = done_tx.send(());
19919 });
19920
19921 match done_rx.recv_timeout(Duration::from_secs(10)) {
19922 Ok(()) => runner.join().unwrap(),
19923 Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
19924 }
19925 let peak = peak_out.lock().unwrap().expect("peak captured");
19926 assert!(
19927 peak <= 2,
19928 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
19929 );
19930 }
19931
19932 #[cfg(not(target_family = "wasm"))]
19933 #[test]
19934 fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
19935 use std::sync::{Barrier, mpsc};
19951 use std::time::Duration;
19952
19953 let (done_tx, done_rx) = mpsc::channel();
19954 let peak_out = Arc::new(Mutex::new(None));
19955 let peak_slot = Arc::clone(&peak_out);
19956 let runner = std::thread::spawn(move || {
19957 let metrics = Arc::new(RuntimeMetrics::default());
19958 let options = RuntimeOptions {
19959 flavor: RuntimeFlavor::MultiThread,
19960 worker_threads: 2,
19961 max_blocking_tasks: 2,
19962 thread_name_prefix: "rd1-owner-runnable".to_string(),
19963 park_deadline: None,
19964 };
19965 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
19966
19967 let saturated = Arc::new(Barrier::new(2));
19968 let release = Arc::new(Barrier::new(2));
19969
19970 let h2_saturated = Arc::clone(&saturated);
19972 let h2_release = Arc::clone(&release);
19973 let h2 = executor.schedule_blocking(move || {
19974 h2_saturated.wait();
19975 h2_release.wait();
19976 0usize
19977 });
19978
19979 let o_exec = Arc::clone(&executor);
19982 let o_saturated = Arc::clone(&saturated);
19983 let o = executor.schedule_blocking(move || {
19984 o_saturated.wait(); let r_exec = Arc::clone(&o_exec);
19987 let body = async move {
19988 let inner_exec = Arc::clone(&r_exec);
19989 let mut inner = std::pin::pin!(async move {
19990 let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19991 assert_eq!(v, 5);
19992 });
19993 r_exec.block_on(inner.as_mut());
19994 };
19995 let sched = Arc::clone(&o_exec);
19996 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
19997 o_exec.schedule(runnable);
19998
19999 let mut wait_r = std::pin::pin!(async move {
20000 task.await;
20001 });
20002 o_exec.block_on(wait_r.as_mut());
20003 1usize
20004 });
20005
20006 std::thread::sleep(Duration::from_millis(300));
20009 release.wait();
20011
20012 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
20013 assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
20014
20015 *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
20016 let _ = done_tx.send(());
20017 });
20018
20019 match done_rx.recv_timeout(Duration::from_secs(10)) {
20020 Ok(()) => runner.join().unwrap(),
20021 Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
20022 }
20023 let peak = peak_out.lock().unwrap().expect("peak captured");
20024 assert!(
20025 peak <= 2,
20026 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
20027 );
20028 }
20029
20030 #[cfg(not(target_family = "wasm"))]
20031 #[test]
20032 fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
20033 use std::sync::{Barrier, mpsc};
20042
20043 let (done_tx, done_rx) = mpsc::channel();
20044 let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20045 let order_read = Arc::clone(&order_out);
20046 let runner = std::thread::spawn(move || {
20047 let metrics = Arc::new(RuntimeMetrics::default());
20048 let options = RuntimeOptions {
20049 flavor: RuntimeFlavor::MultiThread,
20050 worker_threads: 2,
20051 max_blocking_tasks: 2,
20052 thread_name_prefix: "rd1-unrelated".to_string(),
20053 park_deadline: None,
20054 };
20055 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
20056
20057 let (held_tx, held_rx) = mpsc::channel();
20060 let u_ready = Arc::new(Barrier::new(2)); let h_release = Arc::new(Barrier::new(2)); let h_held = held_tx.clone();
20065 let h_rel = Arc::clone(&h_release);
20066 let h = executor.schedule_blocking(move || {
20067 h_held.send(()).unwrap();
20068 h_rel.wait();
20069 0usize
20070 });
20071
20072 let o_exec = Arc::clone(&executor);
20075 let o_ready = Arc::clone(&u_ready);
20076 let o_order = Arc::clone(&order_out);
20077 let o = executor.schedule_blocking(move || {
20078 held_tx.send(()).unwrap();
20079 o_ready.wait(); let inner_exec = Arc::clone(&o_exec);
20081 let d_order = Arc::clone(&o_order);
20082 let mut inner = std::pin::pin!(async move {
20083 let v = inner_exec
20084 .schedule_blocking(move || {
20085 d_order.lock().unwrap().push("D");
20086 5usize
20087 })
20088 .await
20089 .unwrap();
20090 assert_eq!(v, 5);
20091 });
20092 o_exec.block_on(inner.as_mut());
20093 1usize
20094 });
20095
20096 held_rx.recv().unwrap();
20098 held_rx.recv().unwrap();
20099
20100 let u_order = Arc::clone(&order_out);
20102 let u = executor.schedule_blocking(move || {
20103 u_order.lock().unwrap().push("U");
20104 2usize
20105 });
20106
20107 u_ready.wait();
20109
20110 assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
20112 assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
20113
20114 h_release.wait();
20116 assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
20117
20118 let _ = done_tx.send(());
20119 });
20120
20121 match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
20122 Ok(()) => runner.join().unwrap(),
20123 Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
20124 }
20125 let order = order_read.lock().unwrap().clone();
20126 assert_eq!(
20127 order,
20128 vec!["D", "U"],
20129 "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
20130 );
20131 }
20132
20133 #[cfg(not(target_family = "wasm"))]
20134 #[test]
20135 fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
20136 use std::sync::{Barrier, mpsc};
20137
20138 let opts = RuntimeOptions {
20139 flavor: RuntimeFlavor::MultiThread,
20140 worker_threads: 2,
20141 max_blocking_tasks: 2,
20142 thread_name_prefix: "rd1-scope".to_string(),
20143 park_deadline: None,
20144 };
20145 let exec1 =
20146 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20147 let exec2 =
20148 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20149 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
20150
20151 let holder_count = opts.max_blocking_tasks;
20152 let (held_tx, held_rx) = mpsc::channel();
20153 let release = Arc::new(Barrier::new(holder_count + 1));
20154 let mut holder_handles = Vec::new();
20155 for _ in 0..holder_count {
20156 let held_tx = held_tx.clone();
20157 let release = Arc::clone(&release);
20158 holder_handles.push(exec2.schedule_blocking(move || {
20159 held_tx.send(()).unwrap();
20160 release.wait();
20161 0usize
20162 }));
20163 }
20164 for _ in 0..holder_count {
20165 held_rx.recv().unwrap();
20166 }
20167 assert_eq!(
20168 exec2.active_blocking.load(Ordering::Acquire),
20169 opts.max_blocking_tasks
20170 );
20171
20172 let exact = BlockingJobId(u64::MAX);
20173 let ran = Arc::new(AtomicBool::new(false));
20174 let ran_job = Arc::clone(&ran);
20175 exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
20176 id: exact,
20177 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20178 });
20179 let dependency = TaskDependency::default();
20180
20181 let token1 = exec1.fresh_owner_token();
20182 {
20183 let _owner = BlockingOwnerGuard::enter(Some(token1));
20184 dependency.set_owned(BlockingDependency {
20185 job: exact,
20186 owner: Some(token1),
20187 });
20188 assert!(
20189 !exec2.try_owned_blocking_over_cap(&dependency),
20190 "a foreign-executor ambient token must fail the executor gate"
20191 );
20192 }
20193
20194 let token2 = exec2.fresh_owner_token();
20195 {
20196 let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
20197 let _owner = BlockingOwnerGuard::enter(Some(token2));
20198 dependency.set_owned(BlockingDependency {
20199 job: BlockingJobId(u64::MAX - 1),
20200 owner: Some(token2),
20201 });
20202 assert!(
20203 !exec2.try_owned_blocking_over_cap(&dependency),
20204 "a foreign or unrelated dependency id must fail the exact-job gate"
20205 );
20206 dependency.set_owned(BlockingDependency {
20207 job: BlockingJobId(exact.0 - 2),
20208 owner: Some(token2),
20209 });
20210 assert!(
20211 !exec2.try_owned_blocking_over_cap(&dependency),
20212 "a different job id must not consume the queued dependency"
20213 );
20214 dependency.set_owned(BlockingDependency {
20215 job: exact,
20216 owner: Some(token2),
20217 });
20218 assert!(
20219 exec2.try_owned_blocking_over_cap(&dependency),
20220 "the exact awaited job must run over cap"
20221 );
20222 }
20223 assert!(
20224 ran.load(Ordering::SeqCst),
20225 "the exact dependency must have run"
20226 );
20227
20228 release.wait();
20229 for handle in holder_handles {
20230 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
20231 }
20232 }
20233
20234 #[cfg(not(target_family = "wasm"))]
20235 #[test]
20236 fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
20237 let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
20238 let first_owner = executor.fresh_owner_token();
20239 let second_owner = executor.fresh_owner_token();
20240 let _first_owner =
20241 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
20242 let _second_owner =
20243 ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
20244 let exact = BlockingJobId(u64::MAX - 30);
20245 let dependency = TaskDependency::default();
20246 dependency.set_owned(BlockingDependency {
20247 job: exact,
20248 owner: None,
20249 });
20250 let ran = Arc::new(AtomicBool::new(false));
20251 let ran_job = Arc::clone(&ran);
20252 executor
20253 .blocking_queue
20254 .lock()
20255 .unwrap()
20256 .push(QueuedBlocking {
20257 id: exact,
20258 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20259 });
20260
20261 assert!(
20262 !executor.try_owned_blocking_over_cap(&dependency),
20263 "an untagged driver must not choose between multiple active owner frames"
20264 );
20265 assert!(!ran.load(Ordering::SeqCst));
20266 let queued = executor.blocking_queue.lock().unwrap().remove(exact);
20267 assert!(
20268 queued.is_some(),
20269 "ambiguous ownership must leave the exact job queued"
20270 );
20271 }
20272
20273 #[cfg(not(target_family = "wasm"))]
20274 #[test]
20275 fn owner_lending_claims_live_dependency_not_stale_snapshot() {
20276 let executor = multi_thread_executor(2, None, "live-parked-dependency");
20277 let owner = executor.fresh_owner_token();
20278 let dependency = TaskDependency::new(u64::MAX, Some(owner));
20279 let stale = BlockingJobId(u64::MAX - 10);
20280 let live = BlockingJobId(u64::MAX - 9);
20281 dependency.set_owned(BlockingDependency {
20282 job: stale,
20283 owner: Some(owner),
20284 });
20285 dependency.begin_poll();
20286 dependency.set_owned(BlockingDependency {
20287 job: live,
20288 owner: Some(owner),
20289 });
20290
20291 let stale_ran = Arc::new(AtomicBool::new(false));
20292 let stale_job_ran = Arc::clone(&stale_ran);
20293 let live_ran = Arc::new(AtomicBool::new(false));
20294 let live_job_ran = Arc::clone(&live_ran);
20295 {
20296 let mut queue = executor
20297 .blocking_queue
20298 .lock()
20299 .unwrap_or_else(std::sync::PoisonError::into_inner);
20300 queue.push(QueuedBlocking {
20301 id: stale,
20302 run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
20303 });
20304 queue.push(QueuedBlocking {
20305 id: live,
20306 run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
20307 });
20308 }
20309
20310 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20311 let _owner = BlockingOwnerGuard::enter(Some(owner));
20312 assert!(
20313 executor.try_owned_blocking_over_cap(&dependency),
20314 "the owner lane must claim the currently published dependency"
20315 );
20316 assert!(
20317 live_ran.load(Ordering::SeqCst),
20318 "the live dependency must run"
20319 );
20320 assert!(
20321 !stale_ran.load(Ordering::SeqCst),
20322 "the stale registered id must remain queued"
20323 );
20324 let remaining = executor
20325 .blocking_queue
20326 .lock()
20327 .unwrap_or_else(std::sync::PoisonError::into_inner)
20328 .jobs
20329 .keys()
20330 .copied()
20331 .collect::<Vec<_>>();
20332 assert_eq!(remaining, vec![stale]);
20333 }
20334
20335 #[cfg(not(target_family = "wasm"))]
20336 #[test]
20337 fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
20338 let executor = multi_thread_executor(2, None, "cancelled-child-publication");
20339 let owner = executor.fresh_owner_token();
20340 let child = TaskDependency::new_task(u64::MAX);
20341 let parent = TaskDependency::new(u64::MAX, Some(owner));
20342 let exact = BlockingDependency {
20343 job: BlockingJobId(u64::MAX - 31),
20344 owner: Some(owner),
20345 };
20346
20347 child.set_owned(exact);
20348 let stale = child
20349 .get()
20350 .expect("the child must publish its direct dependency");
20351 child.begin_poll();
20352 parent.set_borrowed(&stale);
20353
20354 let ran = Arc::new(AtomicBool::new(false));
20355 let ran_job = Arc::clone(&ran);
20356 executor
20357 .blocking_queue
20358 .lock()
20359 .unwrap()
20360 .push(QueuedBlocking {
20361 id: exact.job,
20362 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20363 });
20364 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20365 let _owner = BlockingOwnerGuard::enter(Some(owner));
20366 assert!(
20367 !executor.try_owned_blocking_over_cap(&parent),
20368 "a publication copied before child cancellation must remain cancelled after parent install"
20369 );
20370 assert!(!ran.load(Ordering::SeqCst));
20371
20372 child.set_owned(exact);
20373 parent.set_borrowed(
20374 &child
20375 .get()
20376 .expect("the replacement publication must be live"),
20377 );
20378 assert!(
20379 executor.try_owned_blocking_over_cap(&parent),
20380 "a fresh child publication must still authorize its exact queued job"
20381 );
20382 assert!(ran.load(Ordering::SeqCst));
20383 }
20384
20385 #[cfg(not(target_family = "wasm"))]
20386 #[test]
20387 fn intermediate_repoll_does_not_cancel_child_source_claim() {
20388 let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
20389 let owner = executor.fresh_owner_token();
20390 let child = TaskDependency::new_task(u64::MAX);
20391 let intermediate = TaskDependency::new_task(u64::MAX);
20392 let parent = TaskDependency::new(u64::MAX, Some(owner));
20393 let exact = BlockingDependency {
20394 job: BlockingJobId(u64::MAX - 43),
20395 owner: Some(owner),
20396 };
20397
20398 child.set_owned(exact);
20399 intermediate.set_borrowed(&child.get().expect("the child source must be live"));
20400 parent.set_borrowed(
20401 &intermediate
20402 .get()
20403 .expect("the intermediate publication must be live"),
20404 );
20405
20406 intermediate.begin_poll();
20407 assert!(
20408 child.get().is_some(),
20409 "withdrawing an intermediate hop must not cancel the child's source claim"
20410 );
20411 assert!(
20412 parent.get().is_none(),
20413 "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
20414 );
20415
20416 intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
20417 parent.set_borrowed(
20418 &intermediate
20419 .get()
20420 .expect("the intermediate must republish the still-live source"),
20421 );
20422 let ran = Arc::new(AtomicBool::new(false));
20423 let ran_job = Arc::clone(&ran);
20424 executor
20425 .blocking_queue
20426 .lock()
20427 .unwrap()
20428 .push(QueuedBlocking {
20429 id: exact.job,
20430 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20431 });
20432
20433 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20434 let _owner = BlockingOwnerGuard::enter(Some(owner));
20435 assert!(
20436 executor.try_owned_blocking_over_cap(&parent),
20437 "a fresh chain through the repolled intermediate must retain the exact source claim"
20438 );
20439 assert!(ran.load(Ordering::SeqCst));
20440 }
20441
20442 #[cfg(not(target_family = "wasm"))]
20443 #[test]
20444 fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
20445 use std::sync::mpsc;
20446
20447 let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
20448 let owner = executor.fresh_owner_token();
20449 let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20450 let child = Arc::new(TaskDependency::new_task(u64::MAX));
20451 let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20452 let exact = BlockingDependency {
20453 job: BlockingJobId(u64::MAX - 44),
20454 owner: Some(owner),
20455 };
20456 child.set_owned(exact);
20457 parent.set_borrowed(&child.get().expect("the child source must be live"));
20458
20459 let ran = Arc::new(AtomicBool::new(false));
20460 let ran_job = Arc::clone(&ran);
20461 executor
20462 .blocking_queue
20463 .lock()
20464 .unwrap()
20465 .push(QueuedBlocking {
20466 id: exact.job,
20467 run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
20468 });
20469
20470 let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
20471 let (release_claim_tx, release_claim_rx) = mpsc::channel();
20472 let claimant_executor = Arc::clone(&executor);
20473 let claimant_parent = Arc::clone(&parent);
20474 let claimant = std::thread::spawn(move || {
20475 DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
20476 *slot.borrow_mut() = Some(Box::new(move || {
20477 claim_entered_tx.send(()).unwrap();
20478 release_claim_rx.recv().unwrap();
20479 }));
20480 });
20481 let _owner = BlockingOwnerGuard::enter(Some(owner));
20482 claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
20483 });
20484 claim_entered_rx
20485 .recv_timeout(Duration::from_secs(2))
20486 .expect("the exact claimant must hold the shared transition lock");
20487
20488 let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
20489 let withdrawing_child = Arc::clone(&child);
20490 let withdrawal = std::thread::spawn(move || {
20491 withdrawing_child.begin_poll();
20492 withdrawn_tx.send(()).unwrap();
20493 });
20494 let withdrew_before_claim = withdrawn_rx
20495 .recv_timeout(Duration::from_millis(200))
20496 .is_ok();
20497 release_claim_tx.send(()).unwrap();
20498
20499 assert!(
20500 claimant.join().unwrap(),
20501 "the claim that linearized first must execute the exact job"
20502 );
20503 withdrawal.join().unwrap();
20504 assert!(
20505 !withdrew_before_claim,
20506 "source withdrawal must not interleave after claim validation but before claim commit"
20507 );
20508 assert!(ran.load(Ordering::SeqCst));
20509 }
20510
20511 #[cfg(not(target_family = "wasm"))]
20512 #[test]
20513 fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
20514 struct PendThenReady {
20535 polls: usize,
20536 }
20537 impl Future for PendThenReady {
20538 type Output = ();
20539 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
20540 self.polls += 1;
20541 if self.polls >= 2 {
20542 Poll::Ready(())
20543 } else {
20544 cx.waker().wake_by_ref();
20548 Poll::Pending
20549 }
20550 }
20551 }
20552
20553 fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
20556 let scheduler = Arc::clone(target);
20557 let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
20558 target.queue.lock().unwrap().push_back(runnable);
20559 task.detach();
20560 }
20561
20562 let opts = RuntimeOptions {
20563 flavor: RuntimeFlavor::MultiThread,
20564 worker_threads: 2,
20565 max_blocking_tasks: 2,
20566 thread_name_prefix: "rd-onpool-scope".to_string(),
20567 park_deadline: None,
20568 };
20569 let exec1 =
20570 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20571 let exec2 =
20572 Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
20573 assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
20574
20575 let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
20577
20578 enqueue_runnable(&exec2);
20582 {
20583 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
20584 exec2.block_on(probe.as_mut());
20585 }
20586 assert_eq!(
20587 exec2.metrics.runnable_polls.load(Ordering::Relaxed),
20588 0,
20589 "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
20590 );
20591 assert_eq!(
20592 exec2.queue.lock().unwrap().len(),
20593 1,
20594 "the foreign call must leave exec2's runnable untouched on its queue"
20595 );
20596
20597 enqueue_runnable(&exec1);
20601 {
20602 let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
20603 exec1.block_on(probe.as_mut());
20604 }
20605 assert_eq!(
20606 exec1.metrics.runnable_polls.load(Ordering::Relaxed),
20607 1,
20608 "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
20609 );
20610 assert!(
20611 exec1.queue.lock().unwrap().is_empty(),
20612 "the cooperative branch must have drained exec1's queued runnable"
20613 );
20614
20615 exec2.queue.lock().unwrap().clear();
20617 }
20618
20619 #[cfg(not(target_family = "wasm"))]
20620 #[test]
20621 fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
20622 use std::sync::mpsc;
20626 use std::time::Duration;
20627
20628 const PRODUCER_COUNT: usize = 8;
20629
20630 fn schedule_pool_workload(
20631 executor: &Arc<MultiThreadExecutor>,
20632 tx: &mpsc::Sender<Option<usize>>,
20633 count: usize,
20634 ) {
20635 for i in 0..count {
20636 let tx = tx.clone();
20637 let scheduler = Arc::clone(executor);
20638 let (runnable, task) =
20639 async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
20640 scheduler.schedule(r);
20641 });
20642 executor.schedule(runnable);
20643 task.detach();
20644 }
20645 let tx = tx.clone();
20646 let scheduler = Arc::clone(executor);
20647 let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
20648 scheduler.schedule(r)
20649 });
20650 executor.schedule(runnable);
20651 task.detach();
20652 }
20653
20654 let expected_sum: usize = (0..PRODUCER_COUNT).sum();
20655 let (done_tx, done_rx) = mpsc::sync_channel(1);
20656 let runner = std::thread::spawn(move || {
20657 let options = RuntimeOptions {
20658 flavor: RuntimeFlavor::MultiThread,
20659 worker_threads: 1,
20660 max_blocking_tasks: 1,
20661 thread_name_prefix: "rd2-pre-fix".to_string(),
20662 park_deadline: None,
20663 }
20664 .validate()
20665 .expect("production MultiThread options must provide a reserve lane");
20666 let executor =
20667 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20668
20669 let (tx, rx) = mpsc::channel::<Option<usize>>();
20670 let (started_tx, started_rx) = mpsc::sync_channel(1);
20671 let handle = executor.schedule_blocking(move || {
20672 started_tx.send(()).unwrap();
20673 let mut sum = 0usize;
20674 while let Ok(msg) = rx.recv() {
20675 match msg {
20676 Some(value) => sum += value,
20677 None => break,
20678 }
20679 }
20680 sum
20681 });
20682 started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
20683 schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
20684 let sum = futures::executor::block_on(handle).unwrap();
20685 done_tx.send(sum).unwrap();
20686 });
20687
20688 match done_rx.recv_timeout(Duration::from_secs(10)) {
20689 Ok(sum) => {
20690 runner.join().unwrap();
20691 assert_eq!(sum, expected_sum);
20692 }
20693 Err(error) => {
20694 panic!("a saturated blocking lane starved the async producers that feed it ({error})")
20695 }
20696 }
20697 }
20698
20699 #[test]
20700 fn current_thread_blocking_work_completes_inline() {
20701 let metrics = Arc::new(RuntimeMetrics::default());
20702 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
20703 CurrentThreadExecutor::new(Arc::clone(&metrics)),
20704 )));
20705 let registration = backend.work.try_register_work().unwrap();
20706
20707 let value =
20708 futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
20709 .expect("blocking task should complete");
20710
20711 assert_eq!(value, 7);
20712 assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
20713 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
20714 }
20715
20716 #[cfg(not(target_family = "wasm"))]
20717 #[test]
20718 fn current_thread_blocking_work_obeys_single_lane_across_callers() {
20719 use std::sync::mpsc;
20720
20721 let controller = Arc::new(current_thread_controller(
20722 "current-thread-blocking-admission",
20723 ));
20724 let backend = controller.backend();
20725 let (first_started_tx, first_started_rx) = mpsc::channel();
20726 let (release_first_tx, release_first_rx) = mpsc::channel();
20727 let first_controller = Arc::clone(&controller);
20728 let first = std::thread::spawn(move || {
20729 let handle = first_controller
20730 .try_spawn_blocking(move || {
20731 first_started_tx.send(()).unwrap();
20732 release_first_rx.recv().unwrap();
20733 1usize
20734 })
20735 .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
20736 futures::executor::block_on(handle).unwrap()
20737 });
20738 first_started_rx
20739 .recv_timeout(Duration::from_secs(1))
20740 .unwrap();
20741
20742 let (second_started_tx, second_started_rx) = mpsc::channel();
20743 let second_controller = Arc::clone(&controller);
20744 let second = std::thread::spawn(move || {
20745 let handle = second_controller
20746 .try_spawn_blocking(move || {
20747 second_started_tx.send(()).unwrap();
20748 2usize
20749 })
20750 .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
20751 futures::executor::block_on(handle).unwrap()
20752 });
20753
20754 wait_until("both CurrentThread blocking calls to be accepted", || {
20755 backend
20756 .work
20757 .state
20758 .lock()
20759 .unwrap_or_else(std::sync::PoisonError::into_inner)
20760 .active
20761 == 2
20762 });
20763 assert!(
20764 second_started_rx
20765 .recv_timeout(Duration::from_millis(100))
20766 .is_err(),
20767 "CurrentThread must not execute two blocking closures concurrently"
20768 );
20769
20770 release_first_tx.send(()).unwrap();
20771 second_started_rx
20772 .recv_timeout(Duration::from_secs(1))
20773 .expect("the waiting blocking call must acquire the released lane");
20774 assert_eq!(first.join().unwrap(), 1);
20775 assert_eq!(second.join().unwrap(), 2);
20776 assert_eq!(
20777 controller
20778 .metrics
20779 .blocking_tasks_started
20780 .load(Ordering::Relaxed),
20781 2
20782 );
20783 assert_eq!(
20784 controller
20785 .metrics
20786 .blocking_tasks_completed
20787 .load(Ordering::Relaxed),
20788 2
20789 );
20790 assert_eq!(
20791 controller
20792 .metrics
20793 .active_blocking_tasks
20794 .load(Ordering::Relaxed),
20795 0
20796 );
20797 assert_eq!(
20798 controller
20799 .metrics
20800 .max_active_blocking_tasks
20801 .load(Ordering::Relaxed),
20802 1,
20803 "the public active-lane metric must remain within CurrentThread's reported capacity"
20804 );
20805 controller.shutdown().unwrap();
20806 }
20807
20808 #[cfg(not(target_family = "wasm"))]
20809 #[test]
20810 fn current_thread_nested_blocking_reuses_the_active_lane() {
20811 use std::sync::mpsc;
20812
20813 let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
20814 let (done_tx, done_rx) = mpsc::channel();
20815 let runner_controller = Arc::clone(&controller);
20816 let runner = std::thread::spawn(move || {
20817 let nested_controller = Arc::clone(&runner_controller);
20818 let outer = runner_controller
20819 .try_spawn_blocking(move || {
20820 let inner = nested_controller
20821 .try_spawn_blocking(|| 41usize)
20822 .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
20823 futures::executor::block_on(inner).unwrap() + 1
20824 })
20825 .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
20826 done_tx
20827 .send(futures::executor::block_on(outer).unwrap())
20828 .unwrap();
20829 });
20830
20831 assert_eq!(
20832 done_rx
20833 .recv_timeout(Duration::from_secs(2))
20834 .expect("a nested CurrentThread blocking call must not wait on its own lane"),
20835 42
20836 );
20837 runner.join().unwrap();
20838 assert_eq!(
20839 controller
20840 .metrics
20841 .blocking_tasks_started
20842 .load(Ordering::Relaxed),
20843 2
20844 );
20845 assert_eq!(
20846 controller
20847 .metrics
20848 .blocking_tasks_completed
20849 .load(Ordering::Relaxed),
20850 2
20851 );
20852 assert_eq!(
20853 controller
20854 .metrics
20855 .active_blocking_tasks
20856 .load(Ordering::Relaxed),
20857 0
20858 );
20859 assert_eq!(
20860 controller
20861 .metrics
20862 .max_active_blocking_tasks
20863 .load(Ordering::Relaxed),
20864 1,
20865 "nested work borrows the outer lane instead of reporting a second physical lane"
20866 );
20867 controller.shutdown().unwrap();
20868 }
20869
20870 #[cfg(not(target_family = "wasm"))]
20871 #[test]
20872 fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
20873 use std::sync::mpsc;
20874
20875 struct HotFuture {
20876 stop: Arc<AtomicBool>,
20877 }
20878
20879 impl Future for HotFuture {
20880 type Output = ();
20881
20882 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
20883 if self.stop.load(Ordering::SeqCst) {
20884 Poll::Ready(())
20885 } else {
20886 cx.waker().wake_by_ref();
20887 Poll::Pending
20888 }
20889 }
20890 }
20891
20892 let stop = Arc::new(AtomicBool::new(false));
20893 let (ready_tx, ready_rx) = mpsc::channel();
20894 let (exact_tx, exact_rx) = mpsc::channel();
20895 let (done_tx, done_rx) = mpsc::channel();
20896 let runner_stop = Arc::clone(&stop);
20897 let runner = std::thread::spawn(move || {
20898 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
20899 RuntimeMetrics::default(),
20900 )));
20901 let owner = executor.fresh_owner_token();
20902 let job = BlockingJobId(u64::MAX - 70);
20903 let (result_tx, result_rx) = oneshot::channel();
20904 {
20905 let mut state = executor
20906 .blocking_admission
20907 .state
20908 .lock()
20909 .unwrap_or_else(std::sync::PoisonError::into_inner);
20910 state.owner = Some(owner);
20911 let exact_stop = Arc::clone(&runner_stop);
20912 state.queue.push(QueuedBlocking {
20913 id: job,
20914 run: Box::new(move || {
20915 exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
20916 let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
20917 }),
20918 });
20919 }
20920 let exact = JoinHandle(JoinHandleInner::Blocking {
20921 receiver: result_rx,
20922 dependency: BlockingDependency {
20923 job,
20924 owner: Some(owner),
20925 },
20926 });
20927 let _owner = executor.enter_blocking_owner(owner, false);
20928
20929 let scheduler = Arc::clone(&executor);
20930 let hot_stop = Arc::clone(&runner_stop);
20931 let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
20932 scheduler.schedule(runnable);
20933 });
20934 executor.schedule(runnable);
20935 ready_tx.send(()).unwrap();
20936
20937 let mut value = None;
20938 {
20939 let mut future = std::pin::pin!(async {
20940 value = Some(exact.await.unwrap());
20941 hot_task.await;
20942 });
20943 assert_eq!(
20944 executor.block_on(future.as_mut()),
20945 BlockOnOutcome::Completed
20946 );
20947 }
20948 executor
20949 .blocking_admission
20950 .state
20951 .lock()
20952 .unwrap_or_else(std::sync::PoisonError::into_inner)
20953 .owner = None;
20954 done_tx.send(value.unwrap()).unwrap();
20955 });
20956
20957 ready_rx
20958 .recv_timeout(Duration::from_secs(2))
20959 .expect("the fairness driver must start");
20960 let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
20961 stop.store(true, Ordering::SeqCst);
20962 assert_eq!(
20963 done_rx
20964 .recv_timeout(Duration::from_secs(2))
20965 .expect("the CurrentThread driver must retire"),
20966 41
20967 );
20968 runner.join().unwrap();
20969 let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
20970 assert_eq!(
20971 exact_before_stop,
20972 Some(true),
20973 "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
20974 );
20975 }
20976
20977 #[cfg(not(target_family = "wasm"))]
20978 #[test]
20979 fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
20980 use std::sync::mpsc;
20981
20982 let controller = Arc::new(current_thread_controller(
20983 "current-thread-cross-driver-blocking",
20984 ));
20985 let backend = controller.backend();
20986 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
20987 panic!("configured CurrentThread backend must create a CurrentThread executor");
20988 };
20989 let executor = Arc::clone(executor);
20990 drop(backend);
20991
20992 let (release_driver_tx, release_driver_rx) = oneshot::channel();
20993 let (driver_started_tx, driver_started_rx) = mpsc::channel();
20994 let driver_controller = Arc::clone(&controller);
20995 let driver = std::thread::spawn(move || {
20996 driver_started_tx.send(std::thread::current().id()).unwrap();
20997 block_on_with_controller(&driver_controller, async {
20998 release_driver_rx
20999 .await
21000 .expect("driver release sender must remain alive");
21001 });
21002 });
21003 let driver_thread = driver_started_rx
21004 .recv_timeout(Duration::from_secs(1))
21005 .unwrap();
21006 wait_until("the secondary CurrentThread driver to park", || {
21007 executor
21008 .stop
21009 .parkers
21010 .lock()
21011 .unwrap_or_else(std::sync::PoisonError::into_inner)
21012 .iter()
21013 .filter_map(Weak::upgrade)
21014 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
21015 });
21016
21017 let (child_polled_tx, child_polled_rx) = mpsc::channel();
21018 let (release_child_tx, release_child_rx) = mpsc::channel();
21019 let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
21020 let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
21021 let (owner_done_tx, owner_done_rx) = mpsc::channel();
21022 let owner_controller = Arc::clone(&controller);
21023 let owner = std::thread::spawn(move || {
21024 let owner_thread = std::thread::current().id();
21025 let await_controller = Arc::clone(&owner_controller);
21026 let child_controller = Arc::clone(&owner_controller);
21027 let outer = owner_controller
21028 .try_spawn_blocking(move || {
21029 let spawn_controller = Arc::clone(&child_controller);
21030 let child = spawn_controller
21031 .try_spawn(async move {
21032 child_polled_tx.send(std::thread::current().id()).unwrap();
21033 release_child_rx.recv().unwrap();
21034 child_controller
21035 .try_spawn_blocking(move || {
21036 blocking_ran_tx.send(std::thread::current().id()).unwrap();
21037 41usize
21038 })
21039 .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
21040 .await
21041 .unwrap()
21042 + 1
21043 })
21044 .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
21045
21046 assert_eq!(
21047 child_polled_rx
21048 .recv_timeout(Duration::from_secs(1))
21049 .unwrap(),
21050 driver_thread,
21051 "the secondary driver must own the child poll that reaches blocking admission"
21052 );
21053 owner_awaiting_tx.send(owner_thread).unwrap();
21054 block_on_with_controller(&await_controller, child).unwrap()
21055 })
21056 .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
21057 owner_done_tx
21058 .send(futures::executor::block_on(outer))
21059 .unwrap();
21060 });
21061
21062 let owner_thread = owner_awaiting_rx
21063 .recv_timeout(Duration::from_secs(1))
21064 .unwrap();
21065 wait_until(
21066 "the blocking owner to park while awaiting the cross-driver child",
21067 || {
21068 executor
21069 .stop
21070 .parkers
21071 .lock()
21072 .unwrap_or_else(std::sync::PoisonError::into_inner)
21073 .iter()
21074 .filter_map(Weak::upgrade)
21075 .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
21076 },
21077 );
21078 release_child_tx.send(()).unwrap();
21079
21080 assert_eq!(
21081 blocking_ran_rx
21082 .recv_timeout(Duration::from_secs(2))
21083 .unwrap(),
21084 owner_thread,
21085 "the exact descendant must be serviced by the lane-owning driver"
21086 );
21087 assert_eq!(
21088 owner_done_rx
21089 .recv_timeout(Duration::from_secs(2))
21090 .expect("cross-driver blocking dependency must not deadlock")
21091 .unwrap(),
21092 42
21093 );
21094 release_driver_tx.send(()).unwrap();
21095 owner.join().unwrap();
21096 driver.join().unwrap();
21097 controller.shutdown().unwrap();
21098 assert_eq!(
21099 controller
21100 .metrics
21101 .blocking_tasks_started
21102 .load(Ordering::Relaxed),
21103 2
21104 );
21105 assert_eq!(
21106 controller
21107 .metrics
21108 .blocking_tasks_completed
21109 .load(Ordering::Relaxed),
21110 2
21111 );
21112 assert_eq!(
21113 controller
21114 .metrics
21115 .active_blocking_tasks
21116 .load(Ordering::Relaxed),
21117 0
21118 );
21119 assert_eq!(
21120 controller
21121 .metrics
21122 .max_active_blocking_tasks
21123 .load(Ordering::Relaxed),
21124 1,
21125 "cross-driver dependency service must reuse the owner's physical lane"
21126 );
21127 }
21128
21129 #[cfg(not(target_family = "wasm"))]
21130 #[test]
21131 fn current_thread_blocking_admission_rechecks_stop_under_lock() {
21132 use std::sync::mpsc;
21133
21134 let controller = Arc::new(current_thread_controller(
21135 "current-thread-blocking-stop-race",
21136 ));
21137 let backend = controller.backend();
21138 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
21139 panic!("configured CurrentThread backend must create a CurrentThread executor");
21140 };
21141 let executor = Arc::clone(executor);
21142 drop(backend);
21143
21144 let ran = Arc::new(AtomicBool::new(false));
21145 let blocking_ran = Arc::clone(&ran);
21146 let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
21147 let (release_admission_tx, release_admission_rx) = mpsc::channel();
21148 let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
21149 let submit_controller = Arc::clone(&controller);
21150 let submitter = std::thread::spawn(move || {
21151 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21152 *slot.borrow_mut() = Some(Box::new(move || {
21153 admission_locked_tx.send(()).unwrap();
21154 release_admission_rx.recv().unwrap();
21155 }));
21156 });
21157 let handle = submit_controller
21158 .try_spawn_blocking(move || {
21159 blocking_ran.store(true, Ordering::SeqCst);
21160 })
21161 .unwrap_or_else(|_| {
21162 panic!("the submission linearized before shutdown and must be accepted")
21163 });
21164 blocking_result_tx
21165 .send(futures::executor::block_on(handle))
21166 .unwrap();
21167 });
21168 admission_locked_rx
21169 .recv_timeout(Duration::from_secs(2))
21170 .expect("the blocking submitter must pause while holding the admission lock");
21171
21172 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
21173 let shutdown_controller = Arc::clone(&controller);
21174 let shutdown = std::thread::spawn(move || {
21175 shutdown_result_tx
21176 .send(shutdown_controller.shutdown())
21177 .unwrap();
21178 });
21179 wait_until("CurrentThread shutdown to publish generation stop", || {
21180 executor.stop.is_stopping()
21181 });
21182 release_admission_tx.send(()).unwrap();
21183
21184 let blocking_error = blocking_result_rx
21185 .recv_timeout(Duration::from_secs(2))
21186 .expect("the admitted blocking handle must resolve")
21187 .expect_err("stop publication must reject the blocking lane claim");
21188 assert_eq!(
21189 blocking_error.to_string(),
21190 "the async runtime stopped before the blocking task could start"
21191 );
21192 assert!(
21193 !ran.load(Ordering::SeqCst),
21194 "blocking work must not start after stop publication"
21195 );
21196 shutdown_result_rx
21197 .recv_timeout(Duration::from_secs(2))
21198 .expect("shutdown must finish after the rejected admission retires")
21199 .unwrap();
21200 submitter.join().unwrap();
21201 shutdown.join().unwrap();
21202 }
21203
21204 #[cfg(not(target_family = "wasm"))]
21205 #[test]
21206 fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
21207 use std::sync::mpsc;
21208
21209 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21210 RuntimeMetrics::default(),
21211 )));
21212 let ran = Arc::new(AtomicBool::new(false));
21213 let job_ran = Arc::clone(&ran);
21214 {
21215 let mut state = executor
21216 .blocking_admission
21217 .state
21218 .lock()
21219 .unwrap_or_else(std::sync::PoisonError::into_inner);
21220 state.queue.push(QueuedBlocking {
21221 id: BlockingJobId(u64::MAX - 71),
21222 run: Box::new(move || {
21223 job_ran.store(true, Ordering::SeqCst);
21224 }),
21225 });
21226 }
21227
21228 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21229 let (release_claim_tx, release_claim_rx) = mpsc::channel();
21230 let (claim_result_tx, claim_result_rx) = mpsc::channel();
21231 let claim_executor = Arc::clone(&executor);
21232 let claimant = std::thread::spawn(move || {
21233 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21234 *slot.borrow_mut() = Some(Box::new(move || {
21235 claim_locked_tx.send(()).unwrap();
21236 release_claim_rx.recv().unwrap();
21237 }));
21238 });
21239 claim_result_tx
21240 .send(claim_executor.run_one_blocking())
21241 .unwrap();
21242 });
21243 claim_locked_rx
21244 .recv_timeout(Duration::from_secs(2))
21245 .expect("the FIFO claimant must pause while holding the admission lock");
21246
21247 executor.stop.begin_shutdown();
21248 release_claim_tx.send(()).unwrap();
21249 assert!(
21250 !claim_result_rx
21251 .recv_timeout(Duration::from_secs(2))
21252 .expect("the stopped FIFO claim must finish"),
21253 "stop publication must reject the queued FIFO claim"
21254 );
21255 claimant.join().unwrap();
21256 assert!(
21257 !ran.load(Ordering::SeqCst),
21258 "queued FIFO work must not start after stop publication"
21259 );
21260 assert!(
21261 !executor
21262 .blocking_admission
21263 .state
21264 .lock()
21265 .unwrap_or_else(std::sync::PoisonError::into_inner)
21266 .queue
21267 .is_empty(),
21268 "the rejected FIFO job must remain queued for shutdown cancellation"
21269 );
21270
21271 executor.close_blocking_admission();
21272 assert!(
21273 executor
21274 .blocking_admission
21275 .state
21276 .lock()
21277 .unwrap_or_else(std::sync::PoisonError::into_inner)
21278 .queue
21279 .is_empty()
21280 );
21281 }
21282
21283 #[cfg(not(target_family = "wasm"))]
21284 #[test]
21285 fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
21286 use std::sync::mpsc;
21287
21288 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21289 RuntimeMetrics::default(),
21290 )));
21291 let ran = Arc::new(AtomicBool::new(false));
21292 let job_ran = Arc::clone(&ran);
21293 {
21294 let mut state = executor
21295 .blocking_admission
21296 .state
21297 .lock()
21298 .unwrap_or_else(std::sync::PoisonError::into_inner);
21299 state.queue.push(QueuedBlocking {
21300 id: BlockingJobId(u64::MAX - 73),
21301 run: Box::new(move || {
21302 job_ran.store(true, Ordering::SeqCst);
21303 }),
21304 });
21305 }
21306
21307 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21308 let (release_claim_tx, release_claim_rx) = mpsc::channel();
21309 let (claim_result_tx, claim_result_rx) = mpsc::channel();
21310 let claim_executor = Arc::clone(&executor);
21311 let claimant = std::thread::spawn(move || {
21312 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21313 *slot.borrow_mut() = Some(Box::new(move || {
21314 claim_locked_tx.send(()).unwrap();
21315 release_claim_rx.recv().unwrap();
21316 }));
21317 });
21318 claim_result_tx
21319 .send(claim_executor.run_one_blocking())
21320 .unwrap();
21321 });
21322 claim_locked_rx
21323 .recv_timeout(Duration::from_secs(2))
21324 .expect("the FIFO claimant must pause while holding the admission lock");
21325
21326 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
21327 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
21328 let cancelling_executor = Arc::clone(&executor);
21329 let cancellation = std::thread::spawn(move || {
21330 let cancelling = cancelling_executor
21331 .begin_failed_dispatch_cancellation()
21332 .expect("the test must publish terminal dispatch cancellation");
21333 cancellation_published_tx.send(()).unwrap();
21334 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
21335 cancellation_done_tx.send(()).unwrap();
21336 });
21337 cancellation_published_rx
21338 .recv_timeout(Duration::from_secs(2))
21339 .expect("terminal dispatch cancellation must publish before queue cancellation");
21340 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21341 assert!(
21342 matches!(
21343 cancellation_done_rx.try_recv(),
21344 Err(mpsc::TryRecvError::Empty)
21345 ),
21346 "terminal cancellation must wait for the blocking-admission lock"
21347 );
21348
21349 release_claim_tx.send(()).unwrap();
21350 assert!(
21351 !claim_result_rx
21352 .recv_timeout(Duration::from_secs(2))
21353 .expect("the cancelled FIFO claim must finish"),
21354 "terminal cancellation publication must reject the queued FIFO claim"
21355 );
21356 cancellation_done_rx
21357 .recv_timeout(Duration::from_secs(2))
21358 .expect("terminal cancellation must drain the rejected FIFO job");
21359 claimant.join().unwrap();
21360 cancellation.join().unwrap();
21361
21362 assert!(
21363 !ran.load(Ordering::SeqCst),
21364 "terminally cancelled FIFO work must not start"
21365 );
21366 assert!(
21367 executor
21368 .blocking_admission
21369 .state
21370 .lock()
21371 .unwrap_or_else(std::sync::PoisonError::into_inner)
21372 .queue
21373 .is_empty(),
21374 "terminal cancellation must drain the rejected FIFO job"
21375 );
21376 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21377 }
21378
21379 #[cfg(not(target_family = "wasm"))]
21380 #[test]
21381 fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
21382 use std::sync::mpsc;
21383
21384 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21385 RuntimeMetrics::default(),
21386 )));
21387 let owner = executor.fresh_owner_token();
21388 let job = BlockingJobId(u64::MAX - 72);
21389 let ran = Arc::new(AtomicBool::new(false));
21390 let job_ran = Arc::clone(&ran);
21391 {
21392 let mut state = executor
21393 .blocking_admission
21394 .state
21395 .lock()
21396 .unwrap_or_else(std::sync::PoisonError::into_inner);
21397 state.owner = Some(owner);
21398 state.queue.push(QueuedBlocking {
21399 id: job,
21400 run: Box::new(move || {
21401 job_ran.store(true, Ordering::SeqCst);
21402 }),
21403 });
21404 }
21405 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21406 dependency.set_owned(BlockingDependency {
21407 job,
21408 owner: Some(owner),
21409 });
21410
21411 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21412 let (release_claim_tx, release_claim_rx) = mpsc::channel();
21413 let (claim_result_tx, claim_result_rx) = mpsc::channel();
21414 let claim_executor = Arc::clone(&executor);
21415 let claim_dependency = Arc::clone(&dependency);
21416 let claimant = std::thread::spawn(move || {
21417 let _owner = claim_executor.enter_blocking_owner(owner, false);
21418 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21419 *slot.borrow_mut() = Some(Box::new(move || {
21420 claim_locked_tx.send(()).unwrap();
21421 release_claim_rx.recv().unwrap();
21422 }));
21423 });
21424 claim_result_tx
21425 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
21426 .unwrap();
21427 });
21428 claim_locked_rx
21429 .recv_timeout(Duration::from_secs(2))
21430 .expect("the owner claimant must pause while holding the admission lock");
21431
21432 executor.stop.begin_shutdown();
21433 release_claim_tx.send(()).unwrap();
21434 assert!(
21435 !claim_result_rx
21436 .recv_timeout(Duration::from_secs(2))
21437 .expect("the stopped owner claim must finish"),
21438 "stop publication must reject the exact-owner claim"
21439 );
21440 claimant.join().unwrap();
21441 assert!(
21442 !ran.load(Ordering::SeqCst),
21443 "queued exact-owner work must not start after stop publication"
21444 );
21445 assert!(
21446 !executor
21447 .blocking_admission
21448 .state
21449 .lock()
21450 .unwrap_or_else(std::sync::PoisonError::into_inner)
21451 .queue
21452 .is_empty(),
21453 "the rejected exact-owner job must remain queued for shutdown cancellation"
21454 );
21455
21456 executor
21457 .blocking_admission
21458 .state
21459 .lock()
21460 .unwrap_or_else(std::sync::PoisonError::into_inner)
21461 .owner = None;
21462 executor.close_blocking_admission();
21463 assert!(
21464 executor
21465 .blocking_admission
21466 .state
21467 .lock()
21468 .unwrap_or_else(std::sync::PoisonError::into_inner)
21469 .queue
21470 .is_empty()
21471 );
21472 }
21473
21474 #[cfg(not(target_family = "wasm"))]
21475 #[test]
21476 fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
21477 use std::sync::mpsc;
21478
21479 let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
21480 RuntimeMetrics::default(),
21481 )));
21482 let owner = executor.fresh_owner_token();
21483 let job = BlockingJobId(u64::MAX - 74);
21484 let ran = Arc::new(AtomicBool::new(false));
21485 let job_ran = Arc::clone(&ran);
21486 {
21487 let mut state = executor
21488 .blocking_admission
21489 .state
21490 .lock()
21491 .unwrap_or_else(std::sync::PoisonError::into_inner);
21492 state.owner = Some(owner);
21493 state.queue.push(QueuedBlocking {
21494 id: job,
21495 run: Box::new(move || {
21496 job_ran.store(true, Ordering::SeqCst);
21497 }),
21498 });
21499 }
21500 let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21501 dependency.set_owned(BlockingDependency {
21502 job,
21503 owner: Some(owner),
21504 });
21505
21506 let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
21507 let (release_claim_tx, release_claim_rx) = mpsc::channel();
21508 let (claim_result_tx, claim_result_rx) = mpsc::channel();
21509 let claim_executor = Arc::clone(&executor);
21510 let claim_dependency = Arc::clone(&dependency);
21511 let claimant = std::thread::spawn(move || {
21512 let _owner = claim_executor.enter_blocking_owner(owner, false);
21513 CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
21514 *slot.borrow_mut() = Some(Box::new(move || {
21515 claim_locked_tx.send(()).unwrap();
21516 release_claim_rx.recv().unwrap();
21517 }));
21518 });
21519 claim_result_tx
21520 .send(claim_executor.try_run_owned_blocking(&claim_dependency))
21521 .unwrap();
21522 });
21523 claim_locked_rx
21524 .recv_timeout(Duration::from_secs(2))
21525 .expect("the owner claimant must pause while holding the admission lock");
21526
21527 let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
21528 let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
21529 let cancelling_executor = Arc::clone(&executor);
21530 let cancellation = std::thread::spawn(move || {
21531 let cancelling = cancelling_executor
21532 .begin_failed_dispatch_cancellation()
21533 .expect("the test must publish terminal dispatch cancellation");
21534 cancellation_published_tx.send(()).unwrap();
21535 cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
21536 cancellation_done_tx.send(()).unwrap();
21537 });
21538 cancellation_published_rx
21539 .recv_timeout(Duration::from_secs(2))
21540 .expect("terminal dispatch cancellation must publish before queue cancellation");
21541 assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21542 assert!(
21543 matches!(
21544 cancellation_done_rx.try_recv(),
21545 Err(mpsc::TryRecvError::Empty)
21546 ),
21547 "terminal cancellation must wait for the blocking-admission lock"
21548 );
21549
21550 release_claim_tx.send(()).unwrap();
21551 assert!(
21552 !claim_result_rx
21553 .recv_timeout(Duration::from_secs(2))
21554 .expect("the cancelled owner claim must finish"),
21555 "terminal cancellation publication must reject the exact-owner claim"
21556 );
21557 cancellation_done_rx
21558 .recv_timeout(Duration::from_secs(2))
21559 .expect("terminal cancellation must drain the rejected exact-owner job");
21560 claimant.join().unwrap();
21561 cancellation.join().unwrap();
21562
21563 assert!(
21564 !ran.load(Ordering::SeqCst),
21565 "terminally cancelled exact-owner work must not start"
21566 );
21567 assert!(
21568 executor
21569 .blocking_admission
21570 .state
21571 .lock()
21572 .unwrap_or_else(std::sync::PoisonError::into_inner)
21573 .queue
21574 .is_empty(),
21575 "terminal cancellation must drain the rejected exact-owner job"
21576 );
21577 assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
21578 }
21579
21580 #[cfg(not(target_family = "wasm"))]
21581 #[test]
21582 fn current_thread_shutdown_cancels_waiting_blocking_work() {
21583 use std::sync::mpsc;
21584
21585 struct DropSignal(mpsc::Sender<()>);
21586
21587 impl Drop for DropSignal {
21588 fn drop(&mut self) {
21589 let _ = self.0.send(());
21590 }
21591 }
21592
21593 let controller = Arc::new(current_thread_controller(
21594 "current-thread-blocking-shutdown",
21595 ));
21596 let backend = controller.backend();
21597 let (first_started_tx, first_started_rx) = mpsc::channel();
21598 let (release_first_tx, release_first_rx) = mpsc::channel();
21599 let first_controller = Arc::clone(&controller);
21600 let first = std::thread::spawn(move || {
21601 let handle = first_controller
21602 .try_spawn_blocking(move || {
21603 first_started_tx.send(()).unwrap();
21604 release_first_rx.recv().unwrap();
21605 })
21606 .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
21607 futures::executor::block_on(handle)
21608 });
21609 first_started_rx
21610 .recv_timeout(Duration::from_secs(1))
21611 .unwrap();
21612
21613 let waiting_ran = Arc::new(AtomicBool::new(false));
21614 let waiting_ran_task = Arc::clone(&waiting_ran);
21615 let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
21616 let waiting_drop = DropSignal(waiting_dropped_tx);
21617 let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
21618 let waiting_controller = Arc::clone(&controller);
21619 let waiting = std::thread::spawn(move || {
21620 let handle = waiting_controller
21621 .try_spawn_blocking(move || {
21622 let _waiting_drop = waiting_drop;
21623 waiting_ran_task.store(true, Ordering::SeqCst);
21624 })
21625 .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
21626 waiting_result_tx
21627 .send(futures::executor::block_on(handle))
21628 .unwrap();
21629 });
21630 wait_until("both CurrentThread blocking calls to be accepted", || {
21631 backend
21632 .work
21633 .state
21634 .lock()
21635 .unwrap_or_else(std::sync::PoisonError::into_inner)
21636 .active
21637 == 2
21638 });
21639
21640 let (shutdown_tx, shutdown_rx) = mpsc::channel();
21641 let shutdown_controller = Arc::clone(&controller);
21642 let shutdown = std::thread::spawn(move || {
21643 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
21644 });
21645 waiting_dropped_rx
21646 .recv_timeout(Duration::from_secs(1))
21647 .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
21648 assert!(
21649 !waiting_ran.load(Ordering::SeqCst),
21650 "waiting blocking work must not start"
21651 );
21652 assert!(
21653 waiting_result_rx
21654 .recv_timeout(Duration::from_secs(1))
21655 .unwrap()
21656 .is_err(),
21657 "the waiting blocking handle must resolve as cancelled"
21658 );
21659 assert!(
21660 shutdown_rx
21661 .recv_timeout(Duration::from_millis(100))
21662 .is_err(),
21663 "shutdown must still wait for the blocking closure that already owns the lane"
21664 );
21665
21666 release_first_tx.send(()).unwrap();
21667 first.join().unwrap().unwrap();
21668 waiting.join().unwrap();
21669 shutdown_rx
21670 .recv_timeout(Duration::from_secs(1))
21671 .unwrap()
21672 .unwrap();
21673 shutdown.join().unwrap();
21674 assert_eq!(
21675 controller
21676 .metrics
21677 .blocking_tasks_started
21678 .load(Ordering::Relaxed),
21679 1
21680 );
21681 assert_eq!(
21682 controller
21683 .metrics
21684 .blocking_tasks_completed
21685 .load(Ordering::Relaxed),
21686 1
21687 );
21688 assert_eq!(
21689 controller
21690 .metrics
21691 .active_blocking_tasks
21692 .load(Ordering::Relaxed),
21693 0
21694 );
21695 }
21696
21697 #[cfg(not(target_family = "wasm"))]
21698 #[test]
21699 fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
21700 use std::{panic::panic_any, sync::mpsc};
21701
21702 struct ReentrantPayload {
21703 controller: Arc<RuntimeController>,
21704 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
21705 }
21706
21707 impl Drop for ReentrantPayload {
21708 fn drop(&mut self) {
21709 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
21710 let shutdown = self.controller.shutdown();
21711 self.result.send((generation, shutdown)).unwrap();
21712 }
21713 }
21714
21715 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
21716
21717 impl Drop for PanicPayloadOnDrop {
21718 fn drop(&mut self) {
21719 panic_any(self.0.take().expect("the result must panic only once"));
21720 }
21721 }
21722
21723 let controller = Arc::new(current_thread_controller(
21724 "current-thread-panic-payload-generation",
21725 ));
21726 let backend = controller.backend();
21727 let generation = backend.generation();
21728 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
21729 panic!("configured CurrentThread backend must create a CurrentThread executor");
21730 };
21731 let executor = Arc::clone(executor);
21732 drop(backend);
21733
21734 let owner = executor.fresh_owner_token();
21737 executor
21738 .blocking_admission
21739 .state
21740 .lock()
21741 .unwrap_or_else(std::sync::PoisonError::into_inner)
21742 .owner = Some(owner);
21743 let (result_tx, result_rx) = mpsc::channel();
21744 let payload_controller = Arc::clone(&controller);
21745 let handle = controller
21746 .try_spawn_blocking(move || {
21747 PanicPayloadOnDrop(Some(ReentrantPayload {
21748 controller: payload_controller,
21749 result: result_tx,
21750 }))
21751 })
21752 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
21753 drop(handle);
21754 executor
21755 .blocking_admission
21756 .state
21757 .lock()
21758 .unwrap_or_else(std::sync::PoisonError::into_inner)
21759 .owner = None;
21760 let dispatch = current_thread_dispatch(&executor);
21761 controller.drive_current_thread_dispatch(dispatch, false);
21762
21763 let (observed_generation, shutdown) = result_rx
21764 .recv_timeout(Duration::from_secs(1))
21765 .expect("the caught result panic payload must be destroyed in the scheduler turn");
21766 assert_eq!(observed_generation, Some(generation));
21767 let error = shutdown.expect_err("payload destruction is still work in the active generation");
21768 assert!(error.to_string().contains("work running on that runtime"));
21769 controller.shutdown().unwrap();
21770 }
21771
21772 #[test]
21773 fn current_thread_detached_blocking_result_drop_is_contained() {
21774 use std::sync::mpsc;
21775
21776 struct PanicOnDrop(mpsc::Sender<()>);
21777
21778 impl Drop for PanicOnDrop {
21779 fn drop(&mut self) {
21780 self.0.send(()).unwrap();
21781 panic!("intentional CurrentThread blocking result destructor panic");
21782 }
21783 }
21784
21785 let controller = current_thread_controller("current-thread-blocking-result-drop");
21786 let (dropped_tx, dropped_rx) = mpsc::channel();
21787 let handle = controller
21788 .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
21789 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
21790
21791 let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
21792 dropped_rx
21793 .recv_timeout(Duration::from_secs(1))
21794 .expect("dropping the detached handle must reclaim its blocking result");
21795 assert!(
21796 result.is_ok(),
21797 "the blocking result destructor panic must be contained"
21798 );
21799 controller.shutdown().unwrap();
21800 }
21801
21802 #[test]
21807 fn validate_rejects_zero_worker_threads() {
21808 let error = RuntimeOptions {
21809 flavor: RuntimeFlavor::MultiThread,
21810 worker_threads: 0,
21811 max_blocking_tasks: 1,
21812 thread_name_prefix: "rd8".to_string(),
21813 park_deadline: None,
21814 }
21815 .validate()
21816 .expect_err("worker_threads == 0 must be rejected");
21817 assert_eq!(
21818 error.to_string(),
21819 "worker_threads must be greater than zero"
21820 );
21821 }
21822
21823 #[test]
21824 fn validate_rejects_zero_max_blocking_tasks() {
21825 let error = RuntimeOptions {
21826 flavor: RuntimeFlavor::MultiThread,
21827 worker_threads: 2,
21828 max_blocking_tasks: 0,
21829 thread_name_prefix: "rd8".to_string(),
21830 park_deadline: None,
21831 }
21832 .validate()
21833 .expect_err("max_blocking_tasks == 0 must be rejected");
21834 assert_eq!(
21835 error.to_string(),
21836 "max_blocking_tasks must be greater than zero"
21837 );
21838 }
21839
21840 #[test]
21841 fn validate_current_thread_coerces_worker_threads_to_one() {
21842 let validated = RuntimeOptions {
21845 flavor: RuntimeFlavor::CurrentThread,
21846 worker_threads: 8,
21847 max_blocking_tasks: 8,
21848 thread_name_prefix: "rd8".to_string(),
21849 park_deadline: None,
21850 }
21851 .validate()
21852 .expect("CurrentThread options must validate");
21853 assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
21854 assert_eq!(validated.worker_threads, 1);
21855 assert_eq!(validated.max_blocking_tasks, 1);
21856 }
21857
21858 #[cfg(not(target_family = "wasm"))]
21861 #[test]
21862 fn validate_reserves_one_worker_from_blocking_admission() {
21863 let validated = RuntimeOptions {
21864 flavor: RuntimeFlavor::MultiThread,
21865 worker_threads: 2,
21866 max_blocking_tasks: 8,
21867 thread_name_prefix: "rd8".to_string(),
21868 park_deadline: None,
21869 }
21870 .validate()
21871 .expect("MultiThread options must validate");
21872 assert_eq!(validated.worker_threads, 2);
21873 assert_eq!(validated.max_blocking_tasks, 1);
21874 }
21875
21876 #[cfg(not(target_family = "wasm"))]
21877 #[test]
21878 fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
21879 let validated = RuntimeOptions {
21880 flavor: RuntimeFlavor::MultiThread,
21881 worker_threads: 1,
21882 max_blocking_tasks: 8,
21883 thread_name_prefix: "rd8-minimum".to_string(),
21884 park_deadline: None,
21885 }
21886 .validate()
21887 .expect("MultiThread options must validate");
21888 assert_eq!(validated.worker_threads, 2);
21889 assert_eq!(validated.max_blocking_tasks, 1);
21890 }
21891
21892 #[cfg(not(target_family = "wasm"))]
21893 #[test]
21894 fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
21895 let simulated_32_bit = RuntimeOptions {
21896 flavor: RuntimeFlavor::MultiThread,
21897 worker_threads: 256,
21898 max_blocking_tasks: 256,
21899 thread_name_prefix: "rd8-rayon-cap-32".to_string(),
21900 park_deadline: None,
21901 }
21902 .validate_with_rayon_max_threads(Some(255))
21903 .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
21904 assert_eq!(simulated_32_bit.worker_threads, 255);
21905 assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
21906
21907 let rayon_max_threads = max_num_threads();
21908 let requested_threads = rayon_max_threads
21909 .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
21910 .checked_add(1)
21911 .expect("supported worker counts must leave room for an oversized request");
21912 let controller = RuntimeController::new();
21913 controller
21914 .configure(RuntimeOptions {
21915 flavor: RuntimeFlavor::MultiThread,
21916 worker_threads: requested_threads,
21917 max_blocking_tasks: requested_threads,
21918 thread_name_prefix: "rd8-rayon-cap".to_string(),
21919 park_deadline: None,
21920 })
21921 .expect("an oversized request must clamp to the production worker ceiling");
21922
21923 let reported = controller.options();
21924 let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
21925 assert_eq!(reported.worker_threads, expected_workers);
21926 assert_eq!(
21927 reported.max_blocking_tasks,
21928 expected_workers - 1,
21929 "the blocking cap must reserve a lane from the physically realizable worker count"
21930 );
21931 }
21932
21933 #[cfg(not(target_family = "wasm"))]
21934 #[test]
21935 fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
21936 let controller = RuntimeController::new();
21937 let options = controller.options();
21938 assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
21939 assert_eq!(
21940 options.max_blocking_tasks,
21941 options.worker_threads.saturating_sub(1).max(1),
21942 "the unconfigured Rust API must use the same validated reserve as the binding"
21943 );
21944 }
21945
21946 #[cfg(not(target_family = "wasm"))]
21947 #[test]
21948 fn concurrent_partial_configuration_merges_from_latest_committed_options() {
21949 use std::{sync::mpsc, time::Duration};
21950
21951 let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
21952 let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
21953 let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
21954 let first_controller = Arc::clone(&controller);
21955 let first = std::thread::spawn(move || {
21956 first_controller
21957 .configure_partial_inner(
21958 RuntimeOptionsPatch {
21959 max_blocking_tasks: Some(3),
21960 ..RuntimeOptionsPatch::default()
21961 },
21962 |candidate| {
21963 assert_eq!(
21964 (candidate.worker_threads, candidate.max_blocking_tasks),
21965 (8, 3)
21966 );
21967 first_merged_tx
21968 .send(())
21969 .expect("the race coordinator must still be listening");
21970 release_first_rx
21971 .recv_timeout(Duration::from_secs(5))
21972 .expect("the first partial configuration must be released");
21973 },
21974 )
21975 .expect("the first partial configuration must commit");
21976 });
21977 first_merged_rx
21978 .recv_timeout(Duration::from_secs(5))
21979 .expect("the first partial configuration must hold the controller lock after merging");
21980
21981 let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
21982 let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
21983 let second_controller = Arc::clone(&controller);
21984 let second = std::thread::spawn(move || {
21985 second_started_tx
21986 .send(())
21987 .expect("the race coordinator must still be listening");
21988 second_controller
21989 .configure_partial_inner(
21990 RuntimeOptionsPatch {
21991 worker_threads: Some(6),
21992 ..RuntimeOptionsPatch::default()
21993 },
21994 |candidate| {
21995 second_candidate_tx
21996 .send((candidate.worker_threads, candidate.max_blocking_tasks))
21997 .expect("the race coordinator must still be listening");
21998 },
21999 )
22000 .expect("the second partial configuration must commit");
22001 });
22002 second_started_rx
22003 .recv_timeout(Duration::from_secs(5))
22004 .expect("the second partial configuration thread must start");
22005 release_first_tx
22006 .send(())
22007 .expect("the first partial configuration must still be waiting");
22008
22009 first
22010 .join()
22011 .expect("the first configuration thread must not panic");
22012 second
22013 .join()
22014 .expect("the second configuration thread must not panic");
22015 assert_eq!(
22016 second_candidate_rx
22017 .recv_timeout(Duration::from_secs(5))
22018 .expect("the second partial configuration must expose its merged candidate"),
22019 (6, 3),
22020 "the second caller must merge against the first caller's committed options"
22021 );
22022
22023 let options = controller.options();
22024 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
22025 assert_eq!(options.thread_name_prefix, "partial-config-race");
22026 }
22027
22028 #[cfg(not(target_family = "wasm"))]
22029 #[test]
22030 fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
22031 use std::{sync::mpsc, time::Duration};
22032
22033 let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
22034 let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
22035 let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
22036 let invalid_controller = Arc::clone(&controller);
22037 let invalid = std::thread::spawn(move || {
22038 invalid_controller
22039 .configure_partial_inner(
22040 RuntimeOptionsPatch {
22041 worker_threads: Some(0),
22042 ..RuntimeOptionsPatch::default()
22043 },
22044 |candidate| {
22045 assert_eq!(
22046 (candidate.worker_threads, candidate.max_blocking_tasks),
22047 (0, 2)
22048 );
22049 invalid_merged_tx
22050 .send(())
22051 .expect("the race coordinator must still be listening");
22052 release_invalid_rx
22053 .recv_timeout(Duration::from_secs(5))
22054 .expect("the invalid partial configuration must be released");
22055 },
22056 )
22057 .expect_err("the invalid partial configuration must be rejected")
22058 .to_string()
22059 });
22060 invalid_merged_rx
22061 .recv_timeout(Duration::from_secs(5))
22062 .expect("the invalid partial configuration must hold the lock after merging");
22063
22064 let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
22065 let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
22066 let valid_controller = Arc::clone(&controller);
22067 let valid = std::thread::spawn(move || {
22068 valid_started_tx
22069 .send(())
22070 .expect("the race coordinator must still be listening");
22071 valid_controller
22072 .configure_partial_inner(
22073 RuntimeOptionsPatch {
22074 max_blocking_tasks: Some(4),
22075 ..RuntimeOptionsPatch::default()
22076 },
22077 |candidate| {
22078 valid_candidate_tx
22079 .send((candidate.worker_threads, candidate.max_blocking_tasks))
22080 .expect("the race coordinator must still be listening");
22081 },
22082 )
22083 .expect("the valid partial configuration must commit");
22084 });
22085 valid_started_rx
22086 .recv_timeout(Duration::from_secs(5))
22087 .expect("the valid partial configuration thread must start");
22088 release_invalid_tx
22089 .send(())
22090 .expect("the invalid partial configuration must still be waiting");
22091
22092 assert_eq!(
22093 invalid
22094 .join()
22095 .expect("the invalid configuration thread must not panic"),
22096 "worker_threads must be greater than zero"
22097 );
22098 valid
22099 .join()
22100 .expect("the valid configuration thread must not panic");
22101 assert_eq!(
22102 valid_candidate_rx
22103 .recv_timeout(Duration::from_secs(5))
22104 .expect("the valid partial configuration must expose its merged candidate"),
22105 (6, 4),
22106 "the valid caller must merge against the last committed options, not the rejected candidate"
22107 );
22108
22109 let options = controller.options();
22110 assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
22111 assert_eq!(options.thread_name_prefix, "partial-config-rollback");
22112 }
22113
22114 #[test]
22115 fn configure_after_backend_started_is_rejected() {
22116 let controller = RuntimeController::new();
22119 controller
22120 .configure(RuntimeOptions {
22121 flavor: RuntimeFlavor::CurrentThread,
22122 worker_threads: 1,
22123 max_blocking_tasks: 1,
22124 thread_name_prefix: "rd8".to_string(),
22125 park_deadline: None,
22126 })
22127 .expect("first configure before the backend exists must succeed");
22128
22129 let _backend = controller.backend();
22131
22132 let error = controller
22133 .configure(RuntimeOptions::default())
22134 .expect_err("configure after the backend started must be rejected");
22135 assert_eq!(
22136 error.to_string(),
22137 "the async runtime configuration is frozen; configure it before the first async call"
22138 );
22139 }
22140
22141 #[test]
22142 fn partial_configuration_after_backend_started_is_rejected() {
22143 let controller = current_thread_controller("partial-config-frozen");
22144 let _backend = controller.backend();
22145
22146 let error = controller
22147 .configure_partial(RuntimeOptionsPatch {
22148 worker_threads: Some(2),
22149 ..RuntimeOptionsPatch::default()
22150 })
22151 .expect_err("partial configuration after the backend started must be rejected");
22152 assert_eq!(
22153 error.to_string(),
22154 "the async runtime configuration is frozen; configure it before the first async call"
22155 );
22156 }
22157
22158 fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
22159 let controller = RuntimeController::new();
22160 controller
22161 .configure(RuntimeOptions {
22162 flavor: RuntimeFlavor::CurrentThread,
22163 worker_threads: 1,
22164 max_blocking_tasks: 1,
22165 thread_name_prefix: thread_name_prefix.to_string(),
22166 park_deadline: None,
22167 })
22168 .expect("CurrentThread test configuration must be accepted");
22169 controller
22170 }
22171
22172 #[cfg(not(target_family = "wasm"))]
22173 fn multi_thread_controller(
22174 thread_name_prefix: &str,
22175 worker_threads: usize,
22176 max_blocking_tasks: usize,
22177 ) -> RuntimeController {
22178 let controller = RuntimeController::new();
22179 controller
22180 .configure(RuntimeOptions {
22181 flavor: RuntimeFlavor::MultiThread,
22182 worker_threads,
22183 max_blocking_tasks,
22184 thread_name_prefix: thread_name_prefix.to_string(),
22185 park_deadline: None,
22186 })
22187 .expect("MultiThread test configuration must be accepted");
22188 controller
22189 }
22190
22191 #[test]
22192 fn blocking_backend_creation_failure_preserves_diagnostic() {
22193 let controller = current_thread_controller("blocking-backend-creation-failure");
22194 let ran = Arc::new(AtomicBool::new(false));
22195 let work_ran = Arc::clone(&ran);
22196
22197 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22198 let Err((error, work)) = controller.try_spawn_blocking(move || {
22199 work_ran.store(true, Ordering::SeqCst);
22200 17usize
22201 }) else {
22202 panic!("the injected backend creation failure must reject blocking work");
22203 };
22204 assert_eq!(
22205 error.to_string(),
22206 "injected async runtime backend creation failure"
22207 );
22208 assert!(
22209 !ran.load(Ordering::SeqCst),
22210 "rejected blocking work must not run"
22211 );
22212 assert_eq!(
22213 work(),
22214 17,
22215 "the rejected blocking closure must remain usable"
22216 );
22217 assert!(ran.load(Ordering::SeqCst));
22218
22219 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22220 let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
22221 .expect_err("infallible blocking submission must return a failed join handle");
22222 assert_eq!(
22223 error.to_string(),
22224 "injected async runtime backend creation failure"
22225 );
22226
22227 let handle = controller
22228 .try_spawn_blocking(|| 31usize)
22229 .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
22230 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
22231 controller.shutdown().unwrap();
22232 }
22233
22234 #[cfg(not(target_family = "wasm"))]
22235 #[test]
22236 fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
22237 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
22238
22239 if std::env::var_os(CHILD_ENV).is_some() {
22240 struct PollProbe {
22241 polls: Arc<AtomicUsize>,
22242 }
22243
22244 impl Future for PollProbe {
22245 type Output = ();
22246
22247 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22248 self.polls.fetch_add(1, Ordering::SeqCst);
22249 Poll::Ready(())
22250 }
22251 }
22252
22253 shutdown().expect("the isolated runtime must stop before block_on admission");
22254 let polls = Arc::new(AtomicUsize::new(0));
22255 let mut future = std::pin::pin!(PollProbe {
22256 polls: Arc::clone(&polls)
22257 });
22258 let error = try_block_on_dyn(future.as_mut())
22259 .expect_err("the stopped runtime must reject the borrowed future");
22260 assert_eq!(
22261 error.to_string(),
22262 "the async runtime is stopped; call start before submitting work"
22263 );
22264 assert_eq!(
22265 polls.load(Ordering::SeqCst),
22266 0,
22267 "rejected block_on must not poll its future"
22268 );
22269
22270 start().expect("the isolated runtime must restart");
22271 try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
22272 assert_eq!(polls.load(Ordering::SeqCst), 1);
22273 shutdown().expect("the restarted isolated runtime must stop");
22274 return;
22275 }
22276
22277 let output = std::process::Command::new(std::env::current_exe().unwrap())
22278 .arg("--exact")
22279 .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
22280 .arg("--nocapture")
22281 .env(CHILD_ENV, "1")
22282 .output()
22283 .expect("the stopped block_on subprocess must start");
22284 assert!(
22285 output.status.success(),
22286 "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
22287 output.status.code(),
22288 String::from_utf8_lossy(&output.stdout),
22289 String::from_utf8_lossy(&output.stderr)
22290 );
22291 }
22292
22293 #[cfg(not(target_family = "wasm"))]
22294 #[test]
22295 fn try_block_on_dyn_reports_concurrent_shutdown() {
22296 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
22297
22298 if std::env::var_os(CHILD_ENV).is_some() {
22299 use std::sync::mpsc;
22300
22301 struct PendingProbe {
22302 entered: Option<mpsc::Sender<()>>,
22303 complete: Arc<AtomicBool>,
22304 }
22305
22306 impl Future for PendingProbe {
22307 type Output = ();
22308
22309 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22310 if let Some(entered) = self.entered.take() {
22311 entered.send(()).unwrap();
22312 }
22313 if self.complete.load(Ordering::SeqCst) {
22314 Poll::Ready(())
22315 } else {
22316 Poll::Pending
22317 }
22318 }
22319 }
22320
22321 configure(RuntimeOptions {
22322 flavor: RuntimeFlavor::CurrentThread,
22323 worker_threads: 1,
22324 max_blocking_tasks: 1,
22325 thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
22326 park_deadline: None,
22327 })
22328 .expect("the isolated runtime must accept CurrentThread configuration");
22329 start().expect("the isolated runtime must start");
22330
22331 let (entered_tx, entered_rx) = mpsc::channel();
22332 let (error_tx, error_rx) = mpsc::channel();
22333 let (retry_tx, retry_rx) = mpsc::channel();
22334 let complete = Arc::new(AtomicBool::new(false));
22335 let future_complete = Arc::clone(&complete);
22336 let runner = std::thread::spawn(move || {
22337 let mut future = std::pin::pin!(PendingProbe {
22338 entered: Some(entered_tx),
22339 complete: future_complete
22340 });
22341 let error = try_block_on_dyn(future.as_mut())
22342 .expect_err("concurrent shutdown must interrupt the borrowed future")
22343 .to_string();
22344 error_tx.send(error).unwrap();
22345 retry_rx.recv().unwrap();
22346 try_block_on_dyn(future.as_mut())
22347 .expect("restart must accept the same interrupted borrowed future");
22348 });
22349 entered_rx
22350 .recv_timeout(Duration::from_secs(2))
22351 .expect("the borrowed future must be polled before shutdown");
22352 shutdown().expect("shutdown must wait for the active block_on scope to retire");
22353 assert_eq!(
22354 error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
22355 "the async runtime stopped before block_on completed its future"
22356 );
22357 start().expect("the isolated runtime must restart after the interrupted drive");
22358 complete.store(true, Ordering::SeqCst);
22359 retry_tx.send(()).unwrap();
22360 runner.join().unwrap();
22361 shutdown().expect("the retried isolated runtime must stop");
22362 return;
22363 }
22364
22365 let output = std::process::Command::new(std::env::current_exe().unwrap())
22366 .arg("--exact")
22367 .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
22368 .arg("--nocapture")
22369 .env(CHILD_ENV, "1")
22370 .output()
22371 .expect("the concurrent block_on shutdown subprocess must start");
22372 assert!(
22373 output.status.success(),
22374 "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
22375 output.status.code(),
22376 String::from_utf8_lossy(&output.stdout),
22377 String::from_utf8_lossy(&output.stderr)
22378 );
22379 }
22380
22381 #[test]
22382 fn initial_lifecycle_start_preserves_lazy_configuration() {
22383 let controller = RuntimeController::new();
22384 controller
22385 .start()
22386 .expect("initial lifecycle start must succeed");
22387 controller
22388 .configure(RuntimeOptions {
22389 flavor: RuntimeFlavor::CurrentThread,
22390 worker_threads: 1,
22391 max_blocking_tasks: 1,
22392 thread_name_prefix: "lifecycle-initial-start".to_string(),
22393 park_deadline: None,
22394 })
22395 .expect("module-registration start must not consume the configuration window");
22396
22397 let state = controller
22398 .state
22399 .lock()
22400 .unwrap_or_else(std::sync::PoisonError::into_inner);
22401 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22402 }
22403
22404 #[test]
22405 fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
22406 let controller = RuntimeController::new();
22407
22408 for _ in 0..3 {
22409 controller
22410 .start()
22411 .expect("zero-work lifecycle start must succeed");
22412 assert!(matches!(
22413 &controller
22414 .state
22415 .lock()
22416 .unwrap_or_else(std::sync::PoisonError::into_inner)
22417 .lifecycle,
22418 RuntimeLifecycle::Initial
22419 ));
22420
22421 controller
22422 .shutdown()
22423 .expect("zero-work lifecycle shutdown must succeed");
22424 assert!(matches!(
22425 &controller
22426 .state
22427 .lock()
22428 .unwrap_or_else(std::sync::PoisonError::into_inner)
22429 .lifecycle,
22430 RuntimeLifecycle::StoppedBeforeFirstUse
22431 ));
22432 }
22433
22434 controller
22435 .start()
22436 .expect("a later zero-work restart must remain lazy");
22437 controller
22438 .configure(RuntimeOptions {
22439 flavor: RuntimeFlavor::CurrentThread,
22440 worker_threads: 1,
22441 max_blocking_tasks: 1,
22442 thread_name_prefix: "lifecycle-zero-work-config".to_string(),
22443 park_deadline: None,
22444 })
22445 .expect("zero-work lifecycle cycles must not consume the configuration window");
22446
22447 let state = controller
22448 .state
22449 .lock()
22450 .unwrap_or_else(std::sync::PoisonError::into_inner);
22451 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22452 }
22453
22454 #[cfg(not(target_family = "wasm"))]
22455 #[test]
22456 fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
22457 use std::sync::mpsc;
22458
22459 struct BlockingDropFuture {
22460 entered: mpsc::Sender<()>,
22461 release: mpsc::Receiver<()>,
22462 }
22463
22464 impl Future for BlockingDropFuture {
22465 type Output = ();
22466
22467 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22468 Poll::Pending
22469 }
22470 }
22471
22472 impl Drop for BlockingDropFuture {
22473 fn drop(&mut self) {
22474 self.entered.send(()).unwrap();
22475 self.release.recv().unwrap();
22476 }
22477 }
22478
22479 let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
22480 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
22481 let (release_drop_tx, release_drop_rx) = mpsc::channel();
22482 let submit_controller = Arc::clone(&controller);
22483 let submitter = std::thread::spawn(move || {
22484 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22485 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
22486 entered: drop_entered_tx,
22487 release: release_drop_rx,
22488 }));
22489 assert!(
22490 result.is_err(),
22491 "the injected backend creation failure must reject the submission"
22492 );
22493 });
22494 drop_entered_rx
22495 .recv_timeout(Duration::from_secs(2))
22496 .expect("the rejected future destructor must start");
22497
22498 let retry_ran = Arc::new(AtomicBool::new(false));
22499 let retry_ran_task = Arc::clone(&retry_ran);
22500 let Err(retry) = controller.try_spawn_detached(async move {
22501 retry_ran_task.store(true, Ordering::SeqCst);
22502 }) else {
22503 panic!("backend creation must remain closed during rejected destruction");
22504 };
22505 drop(retry);
22506 assert!(!retry_ran.load(Ordering::SeqCst));
22507
22508 let (shutdown_tx, shutdown_rx) = mpsc::channel();
22509 let shutdown_controller = Arc::clone(&controller);
22510 let shutdown = std::thread::spawn(move || {
22511 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22512 });
22513 wait_until("initial shutdown to close runtime admission", || {
22514 matches!(
22515 controller
22516 .state
22517 .lock()
22518 .unwrap_or_else(std::sync::PoisonError::into_inner)
22519 .lifecycle,
22520 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
22521 )
22522 });
22523 assert!(
22524 shutdown_rx
22525 .recv_timeout(Duration::from_millis(200))
22526 .is_err(),
22527 "initial shutdown must wait for rejected user state destruction"
22528 );
22529 assert!(
22530 controller.try_spawn_detached(async {}).is_err(),
22531 "shutdown must keep first-generation admission closed while it waits"
22532 );
22533
22534 let (start_tx, start_rx) = mpsc::channel();
22535 let start_controller = Arc::clone(&controller);
22536 let starter = std::thread::spawn(move || {
22537 start_tx.send(start_controller.start()).unwrap();
22538 });
22539 assert!(
22540 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
22541 "start must wait for the zero-backend shutdown transition to finish"
22542 );
22543
22544 release_drop_tx.send(()).unwrap();
22545 submitter.join().unwrap();
22546 shutdown_rx
22547 .recv_timeout(Duration::from_secs(2))
22548 .unwrap()
22549 .unwrap();
22550 shutdown.join().unwrap();
22551 start_rx
22552 .recv_timeout(Duration::from_secs(2))
22553 .unwrap()
22554 .unwrap();
22555 starter.join().unwrap();
22556
22557 let ran = Arc::new(AtomicBool::new(false));
22558 let ran_task = Arc::clone(&ran);
22559 controller
22560 .try_spawn_detached(async move {
22561 ran_task.store(true, Ordering::SeqCst);
22562 })
22563 .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
22564 let dispatch = controller_current_thread_dispatch(&controller);
22565 controller.drive_current_thread_dispatch(dispatch, false);
22566 assert!(ran.load(Ordering::SeqCst));
22567 controller.shutdown().unwrap();
22568 }
22569
22570 #[cfg(not(target_family = "wasm"))]
22571 #[test]
22572 fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
22573 use std::sync::mpsc;
22574
22575 struct BlockingDropFuture {
22576 entered: mpsc::Sender<()>,
22577 release: mpsc::Receiver<()>,
22578 }
22579
22580 impl Future for BlockingDropFuture {
22581 type Output = ();
22582
22583 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22584 Poll::Pending
22585 }
22586 }
22587
22588 impl Drop for BlockingDropFuture {
22589 fn drop(&mut self) {
22590 self.entered.send(()).unwrap();
22591 self.release.recv().unwrap();
22592 }
22593 }
22594
22595 let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
22596 let old_generation = controller.backend().generation();
22597 controller.shutdown().unwrap();
22598
22599 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
22600 let (release_drop_tx, release_drop_rx) = mpsc::channel();
22601 let submit_controller = Arc::clone(&controller);
22602 let submitter = std::thread::spawn(move || {
22603 let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
22604 entered: drop_entered_tx,
22605 release: release_drop_rx,
22606 }));
22607 assert!(
22608 result.is_err(),
22609 "the stopped runtime must reject the submission"
22610 );
22611 });
22612 drop_entered_rx
22613 .recv_timeout(Duration::from_secs(2))
22614 .unwrap();
22615
22616 let (shutdown_tx, shutdown_rx) = mpsc::channel();
22617 let shutdown_controller = Arc::clone(&controller);
22618 let shutdown = std::thread::spawn(move || {
22619 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22620 });
22621 wait_until(
22622 "stopped shutdown to enter its zero-backend transition",
22623 || {
22624 matches!(
22625 controller
22626 .state
22627 .lock()
22628 .unwrap_or_else(std::sync::PoisonError::into_inner)
22629 .lifecycle,
22630 RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
22631 )
22632 },
22633 );
22634
22635 let (start_tx, start_rx) = mpsc::channel();
22636 let start_controller = Arc::clone(&controller);
22637 let starter = std::thread::spawn(move || {
22638 start_tx.send(start_controller.start()).unwrap();
22639 });
22640 assert!(
22641 shutdown_rx
22642 .recv_timeout(Duration::from_millis(200))
22643 .is_err(),
22644 "stopped shutdown must wait for rejected destruction"
22645 );
22646 assert!(
22647 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
22648 "restart must wait for the stopped zero-backend transition"
22649 );
22650
22651 release_drop_tx.send(()).unwrap();
22652 submitter.join().unwrap();
22653 shutdown_rx
22654 .recv_timeout(Duration::from_secs(2))
22655 .unwrap()
22656 .unwrap();
22657 shutdown.join().unwrap();
22658 start_rx
22659 .recv_timeout(Duration::from_secs(2))
22660 .unwrap()
22661 .unwrap();
22662 starter.join().unwrap();
22663
22664 let new_generation = controller.backend().generation();
22665 assert_ne!(new_generation, old_generation);
22666 controller.shutdown().unwrap();
22667 }
22668
22669 #[test]
22670 fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
22671 use std::sync::mpsc;
22672
22673 struct ShutdownOnDrop {
22674 controller: Arc<RuntimeController>,
22675 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
22676 }
22677
22678 impl Future for ShutdownOnDrop {
22679 type Output = ();
22680
22681 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22682 Poll::Pending
22683 }
22684 }
22685
22686 impl Drop for ShutdownOnDrop {
22687 fn drop(&mut self) {
22688 self.result.send(self.controller.shutdown()).unwrap();
22689 }
22690 }
22691
22692 let controller = Arc::new(current_thread_controller(
22693 "failed-initial-reentrant-shutdown",
22694 ));
22695 let (result_tx, result_rx) = mpsc::channel();
22696 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22697 let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
22698 controller: Arc::clone(&controller),
22699 result: result_tx,
22700 }));
22701 assert!(rejected.is_err());
22702
22703 let error = result_rx
22704 .recv_timeout(Duration::from_secs(1))
22705 .expect("the rejected destructor must not self-deadlock")
22706 .expect_err("rejected destruction cannot wait for its own shutdown");
22707 assert!(error.to_string().contains("rejected submission"));
22708 {
22709 let state = controller
22710 .state
22711 .lock()
22712 .unwrap_or_else(std::sync::PoisonError::into_inner);
22713 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22714 assert_eq!(state.rejected_drops, 0);
22715 }
22716
22717 let handle = controller
22718 .try_spawn(async { 23usize })
22719 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
22720 let dispatch = controller_current_thread_dispatch(&controller);
22721 controller.drive_current_thread_dispatch(dispatch, false);
22722 assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
22723 controller.shutdown().unwrap();
22724 }
22725
22726 #[test]
22727 fn failed_initial_submission_destructor_cannot_reenter_start() {
22728 use std::sync::mpsc;
22729
22730 struct StartOnDrop {
22731 controller: Arc<RuntimeController>,
22732 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
22733 release: mpsc::Receiver<()>,
22734 }
22735
22736 impl Future for StartOnDrop {
22737 type Output = ();
22738
22739 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22740 Poll::Pending
22741 }
22742 }
22743
22744 impl Drop for StartOnDrop {
22745 fn drop(&mut self) {
22746 self.result.send(self.controller.start()).unwrap();
22747 self.release.recv().unwrap();
22748 }
22749 }
22750
22751 let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
22752 let (result_tx, result_rx) = mpsc::channel();
22753 let (release_drop_tx, release_drop_rx) = mpsc::channel();
22754 let (submission_tx, submission_rx) = mpsc::channel();
22755 let submit_controller = Arc::clone(&controller);
22756 let submitter = std::thread::spawn(move || {
22757 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22758 let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
22759 controller: Arc::clone(&submit_controller),
22760 result: result_tx,
22761 release: release_drop_rx,
22762 }));
22763 submission_tx.send(rejected).unwrap();
22764 });
22765
22766 let error = result_rx
22767 .recv_timeout(Duration::from_secs(1))
22768 .expect("the rejected destructor must not self-deadlock")
22769 .expect_err("rejected destruction must not report a successful runtime start");
22770 assert!(error.to_string().contains("rejected submission"));
22771 {
22772 let state = controller
22773 .state
22774 .lock()
22775 .unwrap_or_else(std::sync::PoisonError::into_inner);
22776 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22777 assert_eq!(state.rejected_drops, 1);
22778 }
22779
22780 let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
22781 let (external_start_tx, external_start_rx) = mpsc::channel();
22782 let start_controller = Arc::clone(&controller);
22783 let starter = std::thread::spawn(move || {
22784 BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
22785 *slot.borrow_mut() = Some(Box::new(move || {
22786 wait_entered_tx.send(()).unwrap();
22787 }));
22788 });
22789 external_start_tx.send(start_controller.start()).unwrap();
22790 });
22791 wait_entered_rx
22792 .recv_timeout(Duration::from_secs(1))
22793 .expect("external start must reach the Initial rejected-destruction wait");
22794 assert!(
22795 external_start_rx.try_recv().is_err(),
22796 "external start must not return while rejected destruction remains active"
22797 );
22798
22799 release_drop_tx.send(()).unwrap();
22800 assert!(
22801 submission_rx
22802 .recv_timeout(Duration::from_secs(2))
22803 .unwrap()
22804 .is_err()
22805 );
22806 submitter.join().unwrap();
22807 external_start_rx
22808 .recv_timeout(Duration::from_secs(2))
22809 .unwrap()
22810 .unwrap();
22811 starter.join().unwrap();
22812
22813 {
22814 let state = controller
22815 .state
22816 .lock()
22817 .unwrap_or_else(std::sync::PoisonError::into_inner);
22818 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
22819 assert_eq!(state.rejected_drops, 0);
22820 }
22821
22822 let handle = controller
22823 .try_spawn(async { 31usize })
22824 .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
22825 let dispatch = controller_current_thread_dispatch(&controller);
22826 controller.drive_current_thread_dispatch(dispatch, false);
22827 assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
22828 controller.shutdown().unwrap();
22829 }
22830
22831 #[test]
22832 fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
22833 use std::sync::mpsc;
22834
22835 struct BlockingDropFuture {
22836 controller: Arc<RuntimeController>,
22837 entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
22838 release: mpsc::Receiver<()>,
22839 }
22840
22841 impl Future for BlockingDropFuture {
22842 type Output = ();
22843
22844 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22845 Poll::Pending
22846 }
22847 }
22848
22849 impl Drop for BlockingDropFuture {
22850 fn drop(&mut self) {
22851 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
22852 self
22853 .entered
22854 .send((generation, self.controller.shutdown()))
22855 .unwrap();
22856 self.release.recv().unwrap();
22857 }
22858 }
22859
22860 let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
22861 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
22862 let (release_drop_tx, release_drop_rx) = mpsc::channel();
22863 let (runner_done_tx, runner_done_rx) = mpsc::channel();
22864 let runner_controller = Arc::clone(&controller);
22865 let runner = std::thread::spawn(move || {
22866 FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
22867 let result = catch_unwind(AssertUnwindSafe(|| {
22868 block_on_with_controller(
22869 &runner_controller,
22870 BlockingDropFuture {
22871 controller: Arc::clone(&runner_controller),
22872 entered: drop_entered_tx,
22873 release: release_drop_rx,
22874 },
22875 );
22876 }));
22877 runner_done_tx.send(result.is_err()).unwrap();
22878 });
22879
22880 let (generation, reentrant_shutdown) = drop_entered_rx
22881 .recv_timeout(Duration::from_secs(2))
22882 .expect("failed block_on acquisition must destroy its owned future");
22883 assert_eq!(generation, None, "no runtime generation was created");
22884 let error = reentrant_shutdown
22885 .expect_err("rejected block_on destruction cannot wait for its own shutdown");
22886 assert!(error.to_string().contains("rejected submission"));
22887
22888 let Err(retry_error) = controller.try_backend() else {
22889 panic!("backend retry must remain closed during rejected block_on destruction");
22890 };
22891 assert!(retry_error.to_string().contains("rejected submission"));
22892
22893 let (shutdown_tx, shutdown_rx) = mpsc::channel();
22894 let shutdown_controller = Arc::clone(&controller);
22895 let shutdown = std::thread::spawn(move || {
22896 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22897 });
22898 assert!(
22899 shutdown_rx
22900 .recv_timeout(Duration::from_millis(200))
22901 .is_err(),
22902 "initial shutdown must wait for rejected block_on destruction"
22903 );
22904
22905 release_drop_tx.send(()).unwrap();
22906 assert!(
22907 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
22908 "block_on must preserve its infallible panic contract after contained cleanup"
22909 );
22910 runner.join().unwrap();
22911 shutdown_rx
22912 .recv_timeout(Duration::from_secs(2))
22913 .unwrap()
22914 .unwrap();
22915 shutdown.join().unwrap();
22916
22917 controller
22918 .start()
22919 .expect("zero-generation shutdown must remain restartable");
22920 let ran = Arc::new(AtomicBool::new(false));
22921 let ran_task = Arc::clone(&ran);
22922 controller
22923 .try_spawn_detached(async move {
22924 ran_task.store(true, Ordering::SeqCst);
22925 })
22926 .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
22927 let dispatch = controller_current_thread_dispatch(&controller);
22928 controller.drive_current_thread_dispatch(dispatch, false);
22929 assert!(ran.load(Ordering::SeqCst));
22930 controller.shutdown().unwrap();
22931 }
22932
22933 #[test]
22934 fn public_block_on_contains_poll_and_drop_double_panic() {
22935 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
22936
22937 if std::env::var_os(CHILD_ENV).is_some() {
22938 struct PanicInPollAndDrop;
22939
22940 impl Future for PanicInPollAndDrop {
22941 type Output = ();
22942
22943 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22944 panic!("intentional block_on poll panic");
22945 }
22946 }
22947
22948 impl Drop for PanicInPollAndDrop {
22949 fn drop(&mut self) {
22950 panic!("intentional block_on future destructor panic");
22951 }
22952 }
22953
22954 let controller = current_thread_controller("block-on-double-panic");
22955 assert!(
22956 catch_unwind(AssertUnwindSafe(|| {
22957 block_on_with_controller(&controller, PanicInPollAndDrop);
22958 }))
22959 .is_err(),
22960 "the original poll panic must still reach the caller"
22961 );
22962 controller
22963 .shutdown()
22964 .expect("contained future destruction must not strand the runtime");
22965 return;
22966 }
22967
22968 let output = std::process::Command::new(std::env::current_exe().unwrap())
22969 .arg("--exact")
22970 .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
22971 .arg("--nocapture")
22972 .env(CHILD_ENV, "1")
22973 .output()
22974 .expect("the subprocess test must start");
22975 assert!(
22976 output.status.success(),
22977 "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
22978 output.status.code(),
22979 String::from_utf8_lossy(&output.stdout),
22980 String::from_utf8_lossy(&output.stderr)
22981 );
22982 }
22983
22984 #[test]
22985 fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
22986 let controller = RuntimeController::new();
22987 controller
22988 .start()
22989 .expect("initial lifecycle start must succeed");
22990 controller
22991 .shutdown()
22992 .expect("zero-work lifecycle shutdown must succeed");
22993 controller
22994 .start()
22995 .expect("zero-work lifecycle restart must remain lazy");
22996 controller
22997 .configure(RuntimeOptions {
22998 flavor: RuntimeFlavor::CurrentThread,
22999 worker_threads: 1,
23000 max_blocking_tasks: 1,
23001 thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
23002 park_deadline: None,
23003 })
23004 .expect("configuration before first real async use must succeed");
23005
23006 let first_generation = controller.backend().generation();
23007 controller
23008 .shutdown()
23009 .expect("the first real generation must stop");
23010 assert!(matches!(
23011 &controller
23012 .state
23013 .lock()
23014 .unwrap_or_else(std::sync::PoisonError::into_inner)
23015 .lifecycle,
23016 RuntimeLifecycle::Stopped
23017 ));
23018 let error = controller
23019 .configure(RuntimeOptions::default())
23020 .expect_err("a real generation must permanently freeze configuration");
23021 assert_eq!(
23022 error.to_string(),
23023 "the async runtime configuration is frozen; configure it before the first async call"
23024 );
23025
23026 controller
23027 .start()
23028 .expect("the stopped real generation must restart");
23029 let restarted_generation = {
23030 let state = controller
23031 .state
23032 .lock()
23033 .unwrap_or_else(std::sync::PoisonError::into_inner);
23034 let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
23035 panic!("a real generation restart must eagerly create its replacement backend");
23036 };
23037 backend.generation()
23038 };
23039 assert_ne!(
23040 restarted_generation, first_generation,
23041 "a real generation restart must not reuse the retired backend"
23042 );
23043 controller
23044 .shutdown()
23045 .expect("the replacement generation must stop");
23046 }
23047
23048 #[test]
23049 fn runtime_controller_rejects_submissions_after_shutdown() {
23050 let controller = current_thread_controller("lifecycle-reject");
23051 controller.start().expect("runtime must start");
23052 drop(controller.backend());
23053 controller.shutdown().expect("runtime must stop");
23054
23055 let async_ran = Arc::new(AtomicBool::new(false));
23056 let async_ran_task = Arc::clone(&async_ran);
23057 let Err(rejected_future) = controller.try_spawn_detached(async move {
23058 async_ran_task.store(true, Ordering::SeqCst);
23059 }) else {
23060 panic!("shutdown must reject async work");
23061 };
23062 assert!(
23063 !async_ran.load(Ordering::SeqCst),
23064 "a rejected future must not be polled"
23065 );
23066 futures::executor::block_on(rejected_future);
23067 assert!(
23068 async_ran.load(Ordering::SeqCst),
23069 "the original future must remain usable"
23070 );
23071
23072 let blocking_ran = Arc::new(AtomicBool::new(false));
23073 let blocking_ran_task = Arc::clone(&blocking_ran);
23074 let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
23075 blocking_ran_task.store(true, Ordering::SeqCst);
23076 7
23077 }) else {
23078 panic!("shutdown must reject blocking work");
23079 };
23080 assert_eq!(
23081 blocking_error.to_string(),
23082 "the async runtime is stopped; call start before submitting work"
23083 );
23084 assert!(
23085 !blocking_ran.load(Ordering::SeqCst),
23086 "rejected blocking work must not run"
23087 );
23088 assert_eq!(
23089 rejected_work(),
23090 7,
23091 "the original closure must remain usable"
23092 );
23093
23094 let config_error = controller
23095 .configure(RuntimeOptions::default())
23096 .expect_err("shutdown must not make runtime configuration mutable again");
23097 assert_eq!(
23098 config_error.to_string(),
23099 "the async runtime configuration is frozen; configure it before the first async call"
23100 );
23101
23102 let state = controller
23103 .state
23104 .lock()
23105 .unwrap_or_else(std::sync::PoisonError::into_inner);
23106 assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
23107 }
23108
23109 #[test]
23110 fn rejected_future_can_be_resubmitted_after_restart() {
23111 let controller = current_thread_controller("lifecycle-retry-future");
23112 controller.start().expect("runtime must start");
23113 drop(controller.backend());
23114 controller.shutdown().expect("runtime must stop");
23115
23116 let runs = Arc::new(AtomicUsize::new(0));
23117 let runs_task = Arc::clone(&runs);
23118 let future = async move {
23119 runs_task.fetch_add(1, Ordering::SeqCst);
23120 23
23121 };
23122 let Err((error, future)) = controller.try_spawn(future) else {
23123 panic!("stopped runtime must reject the future");
23124 };
23125 assert!(error.to_string().contains("runtime is stopped"));
23126 assert_eq!(runs.load(Ordering::SeqCst), 0);
23127
23128 controller.start().expect("runtime must restart");
23129 let handle = controller
23130 .try_spawn(future)
23131 .unwrap_or_else(|_| panic!("restart must accept retained future"));
23132 let dispatch = controller_current_thread_dispatch(&controller);
23133 controller.drive_current_thread_dispatch(dispatch, false);
23134 assert_eq!(
23135 futures::executor::block_on(handle).expect("retained future must complete"),
23136 23
23137 );
23138 assert_eq!(runs.load(Ordering::SeqCst), 1);
23139 controller
23140 .shutdown()
23141 .expect("replacement runtime must stop");
23142 }
23143
23144 #[test]
23145 fn runtime_controller_start_after_shutdown_accepts_work() {
23146 let controller = current_thread_controller("lifecycle-restart");
23147 controller.start().expect("runtime must start");
23148 drop(controller.backend());
23149 controller.shutdown().expect("runtime must stop");
23150 controller.start().expect("runtime must restart");
23151
23152 let async_ran = Arc::new(AtomicBool::new(false));
23153 let async_ran_task = Arc::clone(&async_ran);
23154 controller
23155 .try_spawn_detached(async move {
23156 async_ran_task.store(true, Ordering::SeqCst);
23157 })
23158 .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
23159 assert!(
23160 !async_ran.load(Ordering::SeqCst),
23161 "hostless CurrentThread submission must remain enqueue-only"
23162 );
23163 let dispatch = controller_current_thread_dispatch(&controller);
23164 controller.drive_current_thread_dispatch(dispatch, false);
23165 assert!(
23166 async_ran.load(Ordering::SeqCst),
23167 "restarted runtime must poll submitted work"
23168 );
23169
23170 let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
23171 panic!("restarted runtime must accept blocking work");
23172 };
23173 assert_eq!(
23174 futures::executor::block_on(handle).expect("blocking work must complete after restart"),
23175 11
23176 );
23177 }
23178
23179 #[test]
23180 fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
23181 let controller = current_thread_controller("stale-host-generation");
23182 let old_generation = controller.backend().generation();
23183 let old_dispatch = controller_current_thread_dispatch(&controller);
23184 controller.shutdown().unwrap();
23185 controller.start().unwrap();
23186 let new_generation = controller.backend().generation();
23187 assert_ne!(new_generation, old_generation);
23188
23189 let ran = Arc::new(AtomicBool::new(false));
23190 let ran_task = Arc::clone(&ran);
23191 controller
23192 .try_spawn_detached(async move {
23193 ran_task.store(true, Ordering::SeqCst);
23194 })
23195 .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
23196 let new_dispatch = controller_current_thread_dispatch(&controller);
23197 assert_ne!(new_dispatch, old_dispatch);
23198
23199 controller.drive_current_thread_dispatch(old_dispatch, false);
23200 assert!(
23201 !ran.load(Ordering::SeqCst),
23202 "a callback from the retired generation must not touch the restarted executor"
23203 );
23204 controller.drive_current_thread_dispatch(new_dispatch, false);
23205 assert!(ran.load(Ordering::SeqCst));
23206 controller.shutdown().unwrap();
23207 }
23208
23209 #[test]
23210 fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
23211 let controller = current_thread_controller("stale-host-cancellation");
23212 let old_generation = controller.backend().generation();
23213 let old_dispatch = controller_current_thread_dispatch(&controller);
23214 controller.shutdown().unwrap();
23215 controller.start().unwrap();
23216 let backend = controller.backend();
23217 let new_generation = backend.generation();
23218 assert_ne!(new_generation, old_generation);
23219 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
23220 panic!("configured CurrentThread backend must create a CurrentThread executor");
23221 };
23222 let new_dispatch = current_thread_dispatch(executor);
23223 assert_ne!(new_dispatch, old_dispatch);
23224
23225 controller.cancel_current_thread_dispatch_for_test(old_dispatch);
23226 assert_eq!(
23227 executor.dispatch_pending.load(Ordering::Acquire),
23228 new_dispatch,
23229 "a delayed failure from the retired generation must not release the replacement latch"
23230 );
23231
23232 controller.cancel_current_thread_dispatch_for_test(new_dispatch);
23233 assert_eq!(
23234 executor.dispatch_pending.load(Ordering::Acquire),
23235 0,
23236 "the live generation must be able to cancel its accepted dispatch"
23237 );
23238 drop(backend);
23239 controller.shutdown().unwrap();
23240 }
23241
23242 #[cfg(not(target_family = "wasm"))]
23243 #[test]
23244 fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
23245 use std::sync::mpsc;
23246
23247 type ProbeResult = (
23248 &'static str,
23249 Option<u64>,
23250 Result<(), String>,
23251 Result<(), String>,
23252 Result<(), String>,
23253 );
23254
23255 struct StaleGenerationProbe {
23256 label: &'static str,
23257 controller: Arc<RuntimeController>,
23258 result: mpsc::Sender<ProbeResult>,
23259 }
23260
23261 impl Drop for StaleGenerationProbe {
23262 fn drop(&mut self) {
23263 let generation = active_runtime_generation();
23264 let submission = match self.controller.try_spawn(async {}) {
23265 Ok(handle) => {
23266 drop(handle);
23267 Ok(())
23268 }
23269 Err((error, future)) => {
23270 drop(future);
23271 Err(error.to_string())
23272 }
23273 };
23274 let start = self.controller.start().map_err(|error| error.to_string());
23275 let shutdown = self
23276 .controller
23277 .shutdown()
23278 .map_err(|error| error.to_string());
23279 self
23280 .result
23281 .send((self.label, generation, submission, start, shutdown))
23282 .unwrap();
23283 }
23284 }
23285
23286 let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
23287 let old_generation = controller.backend().generation();
23288 let (result_tx, result_rx) = mpsc::channel();
23289
23290 let async_controller = Arc::clone(&controller);
23291 let async_result = result_tx.clone();
23292 let async_handle = controller
23293 .try_spawn(async move {
23294 StaleGenerationProbe {
23295 label: "async",
23296 controller: async_controller,
23297 result: async_result,
23298 }
23299 })
23300 .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
23301 let old_dispatch = controller_current_thread_dispatch(&controller);
23302 controller.drive_current_thread_dispatch(old_dispatch, false);
23303
23304 let ready_controller = Arc::clone(&controller);
23305 let ready_handle = controller
23306 .try_spawn_blocking(move || StaleGenerationProbe {
23307 label: "ready",
23308 controller: ready_controller,
23309 result: result_tx,
23310 })
23311 .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
23312
23313 controller.shutdown().unwrap();
23314 controller.start().unwrap();
23315 let new_generation = controller.backend().generation();
23316 assert_ne!(new_generation, old_generation);
23317
23318 drop(async_handle);
23319 let async_result = result_rx
23320 .recv_timeout(Duration::from_secs(2))
23321 .expect("the retained async output must be destroyed without waiting on the replacement");
23322 drop(ready_handle);
23323 let ready_result = result_rx
23324 .recv_timeout(Duration::from_secs(2))
23325 .expect("the retained immediate output must be destroyed without touching the replacement");
23326
23327 for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
23328 let (label, generation, submission, start, shutdown) = result;
23329 assert_eq!(label, expected_label);
23330 assert_eq!(generation, Some(old_generation));
23331 for error in [submission, start, shutdown] {
23332 assert!(
23333 error
23334 .expect_err("retired-generation output destruction must be rejected")
23335 .contains("retired generation")
23336 );
23337 }
23338 }
23339
23340 let survivor = controller
23341 .try_spawn(async { 29usize })
23342 .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
23343 let new_dispatch = controller_current_thread_dispatch(&controller);
23344 controller.drive_current_thread_dispatch(new_dispatch, false);
23345 assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
23346 controller.shutdown().unwrap();
23347 }
23348
23349 #[cfg(not(target_family = "wasm"))]
23350 #[test]
23351 fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
23352 use std::sync::mpsc;
23353
23354 struct ShutdownOnDrop {
23355 controller: Arc<RuntimeController>,
23356 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
23357 }
23358
23359 impl Drop for ShutdownOnDrop {
23360 fn drop(&mut self) {
23361 self
23362 .result
23363 .send((active_runtime_generation(), self.controller.shutdown()))
23364 .unwrap();
23365 }
23366 }
23367
23368 let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
23369 let old_generation = controller.backend().generation();
23370 let (result_tx, result_rx) = mpsc::channel();
23371 let old_handle = controller
23372 .try_spawn_blocking({
23373 let controller = Arc::clone(&controller);
23374 move || ShutdownOnDrop {
23375 controller,
23376 result: result_tx,
23377 }
23378 })
23379 .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
23380 wait_until(
23381 "the first-generation blocking result to be buffered",
23382 || {
23383 controller
23384 .metrics
23385 .blocking_tasks_completed
23386 .load(Ordering::Acquire)
23387 == 1
23388 },
23389 );
23390 controller.shutdown().unwrap();
23391 controller.start().unwrap();
23392 let new_generation = controller.backend().generation();
23393 assert_ne!(new_generation, old_generation);
23394
23395 let (holder_started_tx, holder_started_rx) = mpsc::channel();
23396 let (release_holder_tx, release_holder_rx) = mpsc::channel();
23397 let holder = controller
23398 .try_spawn_blocking(move || {
23399 holder_started_tx.send(()).unwrap();
23400 release_holder_rx.recv().unwrap();
23401 })
23402 .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
23403 holder_started_rx
23404 .recv_timeout(Duration::from_secs(2))
23405 .unwrap();
23406
23407 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23408 let shutdown_controller = Arc::clone(&controller);
23409 let shutdown = std::thread::spawn(move || {
23410 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23411 });
23412 wait_until("the replacement generation to enter Stopping", || {
23413 matches!(
23414 controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
23415 RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
23416 )
23417 });
23418
23419 let dropper = std::thread::spawn(move || drop(old_handle));
23420 let (observed_generation, reentrant_shutdown) =
23421 match result_rx.recv_timeout(Duration::from_secs(2)) {
23422 Ok(result) => result,
23423 Err(error) => {
23424 release_holder_tx.send(()).unwrap();
23425 let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
23426 let _ = dropper.join();
23427 let _ = shutdown.join();
23428 panic!("retained first-generation output waited on replacement shutdown: {error}");
23429 }
23430 };
23431 assert_eq!(observed_generation, Some(old_generation));
23432 let error = reentrant_shutdown
23433 .expect_err("a retained first-generation output must not control replacement shutdown");
23434 assert!(error.to_string().contains("retired generation"));
23435
23436 release_holder_tx.send(()).unwrap();
23437 shutdown_rx
23438 .recv_timeout(Duration::from_secs(2))
23439 .unwrap()
23440 .unwrap();
23441 dropper.join().unwrap();
23442 shutdown.join().unwrap();
23443 futures::executor::block_on(holder).unwrap();
23444 }
23445
23446 #[cfg(not(target_family = "wasm"))]
23447 #[test]
23448 fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
23449 use std::sync::mpsc;
23450
23451 let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
23452 let generation = controller.backend().generation();
23453 let dispatch = controller_current_thread_dispatch(&controller);
23454 let (admitted_tx, admitted_rx) = mpsc::channel();
23455 let (release_tx, release_rx) = mpsc::channel();
23456 let driver_controller = Arc::clone(&controller);
23457 let driver = std::thread::spawn(move || {
23458 AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
23459 *slot.borrow_mut() = Some(Box::new(move || {
23460 admitted_tx.send(()).unwrap();
23461 release_rx.recv().unwrap();
23462 }));
23463 });
23464 driver_controller.drive_current_thread_dispatch(dispatch, false);
23465 });
23466 admitted_rx
23467 .recv_timeout(Duration::from_secs(2))
23468 .expect("the host turn must claim its scheduler role before execution");
23469
23470 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23471 let shutdown_controller = Arc::clone(&controller);
23472 let shutdown = std::thread::spawn(move || {
23473 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23474 });
23475 wait_until(
23476 "shutdown to close the admitted CurrentThread generation",
23477 || {
23478 let state = controller
23479 .state
23480 .lock()
23481 .unwrap_or_else(std::sync::PoisonError::into_inner);
23482 matches!(
23483 &state.lifecycle,
23484 RuntimeLifecycle::Stopping(identity) if identity.generation == generation
23485 )
23486 },
23487 );
23488 assert!(
23489 shutdown_rx
23490 .recv_timeout(Duration::from_millis(200))
23491 .is_err(),
23492 "shutdown must wait for an admitted host callback even before it starts draining"
23493 );
23494
23495 release_tx.send(()).unwrap();
23496 driver.join().unwrap();
23497 shutdown_rx
23498 .recv_timeout(Duration::from_secs(2))
23499 .unwrap()
23500 .unwrap();
23501 shutdown.join().unwrap();
23502 }
23503
23504 #[test]
23505 fn spawn_racing_shutdown_cannot_recreate_backend() {
23506 use std::sync::mpsc;
23507
23508 let controller = Arc::new(current_thread_controller("lifecycle-race"));
23509 controller.start().expect("runtime must start");
23510 drop(controller.backend());
23511
23512 let (at_hook_tx, at_hook_rx) = mpsc::channel();
23513 let (release_tx, release_rx) = mpsc::channel();
23514 let ran = Arc::new(AtomicBool::new(false));
23515 let ran_task = Arc::clone(&ran);
23516 let worker_controller = Arc::clone(&controller);
23517 let worker = std::thread::spawn(move || {
23518 BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
23519 *slot.borrow_mut() = Some(Box::new(move || {
23520 at_hook_tx
23521 .send(())
23522 .expect("test coordinator must still be listening");
23523 release_rx
23524 .recv()
23525 .expect("test coordinator must release the submission");
23526 }));
23527 });
23528
23529 let future = async move {
23530 ran_task.store(true, Ordering::SeqCst);
23531 };
23532 match worker_controller.try_spawn_detached(future) {
23533 Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
23534 Err(future) => drop(future),
23535 }
23536 });
23537
23538 at_hook_rx
23539 .recv_timeout(Duration::from_secs(5))
23540 .expect("submitting thread must stop before taking the controller lock");
23541 controller.shutdown().expect("runtime must stop");
23542 release_tx
23543 .send(())
23544 .expect("submitting thread must still be waiting");
23545 worker.join().expect("submitting thread must not panic");
23546
23547 assert!(
23548 !ran.load(Ordering::SeqCst),
23549 "the rejected task must never run"
23550 );
23551 let state = controller
23552 .state
23553 .lock()
23554 .unwrap_or_else(std::sync::PoisonError::into_inner);
23555 assert!(
23556 matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
23557 "the rejected submission must not lazily recreate a backend"
23558 );
23559 }
23560
23561 #[cfg(not(target_family = "wasm"))]
23562 #[test]
23563 fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
23564 use std::sync::mpsc;
23565
23566 struct BlockingDropFuture {
23567 work: Arc<GenerationWork>,
23568 entered: mpsc::Sender<(Option<u64>, usize)>,
23569 release: mpsc::Receiver<()>,
23570 }
23571
23572 impl Future for BlockingDropFuture {
23573 type Output = ();
23574
23575 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23576 Poll::Pending
23577 }
23578 }
23579
23580 impl Drop for BlockingDropFuture {
23581 fn drop(&mut self) {
23582 let active = self
23583 .work
23584 .state
23585 .lock()
23586 .unwrap_or_else(std::sync::PoisonError::into_inner)
23587 .active;
23588 self
23589 .entered
23590 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
23591 .unwrap();
23592 self.release.recv().unwrap();
23593 }
23594 }
23595
23596 let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
23597 let backend = controller.backend();
23598 let old_generation = backend.generation();
23599 let work = Arc::clone(&backend.work);
23600 let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
23601 panic!("configured CurrentThread backend must create a CurrentThread executor");
23602 };
23603 let executor = Arc::clone(executor);
23604 drop(backend);
23605
23606 let (registered_tx, registered_rx) = mpsc::channel();
23607 let (release_submission_tx, release_submission_rx) = mpsc::channel();
23608 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23609 let (release_drop_tx, release_drop_rx) = mpsc::channel();
23610 let submit_controller = Arc::clone(&controller);
23611 let submit_work = Arc::clone(&work);
23612 let submitter = std::thread::spawn(move || {
23613 AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
23614 *slot.borrow_mut() = Some(Box::new(move || {
23615 registered_tx.send(()).unwrap();
23616 release_submission_rx.recv().unwrap();
23617 }));
23618 });
23619 submit_controller
23620 .try_spawn_detached(BlockingDropFuture {
23621 work: submit_work,
23622 entered: drop_entered_tx,
23623 release: release_drop_rx,
23624 })
23625 .unwrap_or_else(|_| {
23626 panic!("the submission linearized before shutdown and must be accepted")
23627 });
23628 });
23629 registered_rx
23630 .recv_timeout(Duration::from_secs(2))
23631 .expect("the submitting thread must register generation work before shutdown");
23632
23633 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23634 let shutdown_controller = Arc::clone(&controller);
23635 let shutdown = std::thread::spawn(move || {
23636 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23637 });
23638 wait_until("CurrentThread shutdown to close the runnable queue", || {
23639 executor
23640 .queue
23641 .lock()
23642 .unwrap_or_else(std::sync::PoisonError::into_inner)
23643 .closed
23644 });
23645 release_submission_tx.send(()).unwrap();
23646
23647 let (drop_generation, active_during_drop) = drop_entered_rx
23648 .recv_timeout(Duration::from_secs(2))
23649 .expect("the closed queue must destroy the accepted unpolled future");
23650 assert_eq!(drop_generation, Some(old_generation));
23651 assert_eq!(
23652 active_during_drop, 1,
23653 "accepted generation work must remain registered through user future destruction"
23654 );
23655
23656 let (start_tx, start_rx) = mpsc::channel();
23657 let start_controller = Arc::clone(&controller);
23658 let starter = std::thread::spawn(move || {
23659 start_tx.send(start_controller.start()).unwrap();
23660 });
23661 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
23662 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
23663 let shutdown_overlapped = early_shutdown.is_some();
23664 let restart_overlapped = early_start.is_some();
23665
23666 release_drop_tx.send(()).unwrap();
23667 submitter.join().unwrap();
23668 let shutdown_result = early_shutdown.unwrap_or_else(|| {
23669 shutdown_rx
23670 .recv_timeout(Duration::from_secs(2))
23671 .expect("shutdown must finish after drop")
23672 });
23673 shutdown_result.unwrap();
23674 shutdown.join().unwrap();
23675 let start_result = early_start.unwrap_or_else(|| {
23676 start_rx
23677 .recv_timeout(Duration::from_secs(2))
23678 .expect("restart must follow shutdown")
23679 });
23680 start_result.unwrap();
23681 starter.join().unwrap();
23682
23683 assert!(
23684 !shutdown_overlapped,
23685 "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
23686 );
23687 assert!(
23688 !restart_overlapped,
23689 "restart must not create a new generation while an accepted old-generation future is being destroyed"
23690 );
23691 assert_ne!(controller.backend().generation(), old_generation);
23692 controller.shutdown().unwrap();
23693 }
23694
23695 #[cfg(not(target_family = "wasm"))]
23696 #[test]
23697 fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
23698 use std::sync::mpsc;
23699
23700 struct BlockingDrop {
23701 work: Arc<GenerationWork>,
23702 entered: mpsc::Sender<(Option<u64>, usize)>,
23703 release: mpsc::Receiver<()>,
23704 }
23705
23706 impl Drop for BlockingDrop {
23707 fn drop(&mut self) {
23708 let active = self
23709 .work
23710 .state
23711 .lock()
23712 .unwrap_or_else(std::sync::PoisonError::into_inner)
23713 .active;
23714 self
23715 .entered
23716 .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
23717 .unwrap();
23718 self.release.recv().unwrap();
23719 }
23720 }
23721
23722 let controller = Arc::new(multi_thread_controller(
23723 "accepted-blocking-submission-drop",
23724 2,
23725 1,
23726 ));
23727 let backend = controller.backend();
23728 let old_generation = backend.generation();
23729 let work = Arc::clone(&backend.work);
23730 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23731 panic!("configured MultiThread backend must create a MultiThread executor");
23732 };
23733 let executor = Arc::downgrade(executor);
23734 drop(backend);
23735
23736 let (registered_tx, registered_rx) = mpsc::channel();
23737 let (release_submission_tx, release_submission_rx) = mpsc::channel();
23738 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23739 let (release_drop_tx, release_drop_rx) = mpsc::channel();
23740 let submit_controller = Arc::clone(&controller);
23741 let submitter = std::thread::spawn(move || {
23742 AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
23743 *slot.borrow_mut() = Some(Box::new(move || {
23744 registered_tx.send(()).unwrap();
23745 release_submission_rx.recv().unwrap();
23746 }));
23747 });
23748 let drop_signal = BlockingDrop {
23749 work,
23750 entered: drop_entered_tx,
23751 release: release_drop_rx,
23752 };
23753 let handle = submit_controller
23754 .try_spawn_blocking(move || {
23755 let _drop_signal = drop_signal;
23756 })
23757 .unwrap_or_else(|_| {
23758 panic!("the submission linearized before shutdown and must be accepted")
23759 });
23760 assert!(
23761 futures::executor::block_on(handle).is_err(),
23762 "the executor must cancel work that reaches its queue after shutdown closed it"
23763 );
23764 });
23765 registered_rx
23766 .recv_timeout(Duration::from_secs(2))
23767 .expect("the submitting thread must register generation work before shutdown");
23768
23769 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23770 let shutdown_controller = Arc::clone(&controller);
23771 let shutdown = std::thread::spawn(move || {
23772 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23773 });
23774 wait_until("MultiThread shutdown to close the blocking queue", || {
23775 executor
23776 .upgrade()
23777 .expect("the retiring executor must remain alive until shutdown completes")
23778 .blocking_queue
23779 .lock()
23780 .unwrap_or_else(std::sync::PoisonError::into_inner)
23781 .closed
23782 });
23783 release_submission_tx.send(()).unwrap();
23784
23785 let (drop_generation, active_during_drop) = drop_entered_rx
23786 .recv_timeout(Duration::from_secs(2))
23787 .expect("the closed queue must destroy the accepted blocking closure");
23788 assert_eq!(drop_generation, Some(old_generation));
23789 assert_eq!(
23790 active_during_drop, 1,
23791 "accepted generation work must remain registered through blocking closure destruction"
23792 );
23793
23794 let (start_tx, start_rx) = mpsc::channel();
23795 let start_controller = Arc::clone(&controller);
23796 let starter = std::thread::spawn(move || {
23797 start_tx.send(start_controller.start()).unwrap();
23798 });
23799 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
23800 let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
23801 let shutdown_overlapped = early_shutdown.is_some();
23802 let restart_overlapped = early_start.is_some();
23803
23804 release_drop_tx.send(()).unwrap();
23805 submitter.join().unwrap();
23806 let shutdown_result = early_shutdown.unwrap_or_else(|| {
23807 shutdown_rx
23808 .recv_timeout(Duration::from_secs(2))
23809 .expect("shutdown must finish after drop")
23810 });
23811 shutdown_result.unwrap();
23812 shutdown.join().unwrap();
23813 let start_result = early_start.unwrap_or_else(|| {
23814 start_rx
23815 .recv_timeout(Duration::from_secs(2))
23816 .expect("restart must follow shutdown")
23817 });
23818 start_result.unwrap();
23819 starter.join().unwrap();
23820
23821 assert!(
23822 !shutdown_overlapped,
23823 "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
23824 );
23825 assert!(
23826 !restart_overlapped,
23827 "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
23828 );
23829 assert_ne!(controller.backend().generation(), old_generation);
23830 controller.shutdown().unwrap();
23831 }
23832
23833 #[cfg(not(target_family = "wasm"))]
23834 #[test]
23835 fn multi_thread_controller_reports_its_exact_physical_worker_count() {
23836 let controller = multi_thread_controller("lifecycle-physical", 1, 8);
23837 let options = controller.options();
23838 assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
23839
23840 let backend = controller.backend();
23841 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23842 panic!("configured MultiThread backend must create a Rayon executor");
23843 };
23844 assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
23845 let lifecycle = Arc::clone(&executor.worker_lifecycle);
23846 drop(backend);
23847
23848 controller.shutdown().expect("runtime must stop");
23849 assert_eq!(
23850 lifecycle.remaining(),
23851 0,
23852 "shutdown must observe every physical worker exit"
23853 );
23854 }
23855
23856 #[cfg(not(target_family = "wasm"))]
23857 #[test]
23858 fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
23859 use std::sync::mpsc;
23876
23877 let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
23878 let backend = controller.backend();
23879 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23880 panic!("configured MultiThread backend must create a Rayon executor");
23881 };
23882 let worker_count = executor.pool.current_num_threads();
23883 let completed = Arc::new(AtomicUsize::new(0));
23884 let probe_completed = Arc::clone(&completed);
23885 executor.pool.broadcast(move |_| {
23886 WORKER_TLS_DROP_PROBE.with(|slot| {
23887 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
23888 action: Some(WorkerTlsDropAction::Record {
23889 completed: Arc::clone(&probe_completed),
23890 }),
23891 });
23892 assert!(
23893 previous.is_none(),
23894 "each worker must install exactly one TLS drop probe"
23895 );
23896 });
23897 });
23898 drop(backend);
23899
23900 assert_eq!(
23901 completed.load(Ordering::SeqCst),
23902 0,
23903 "no worker TLS destructor may run before shutdown retires the workers"
23904 );
23905
23906 let (shutdown_tx, shutdown_rx) = mpsc::channel();
23907 let shutdown_controller = Arc::clone(&controller);
23908 let sample_completed = Arc::clone(&completed);
23909 let shutdown = std::thread::spawn(move || {
23910 let result = shutdown_controller.shutdown();
23911 let seen_at_return = sample_completed.load(Ordering::SeqCst);
23915 shutdown_tx.send((result, seen_at_return)).unwrap();
23916 });
23917 let (shutdown_result, seen_at_return) = shutdown_rx
23918 .recv_timeout(Duration::from_secs(10))
23919 .expect("shutdown must finish once worker TLS destructors have run");
23920 shutdown_result.unwrap();
23921 assert_eq!(
23922 seen_at_return, worker_count,
23923 "every worker TLS destructor must have completed before shutdown() returned: \
23924 the worker-join barrier must include TLS-destructor completion"
23925 );
23926 join_within(
23927 "the shutdown thread exits after teardown",
23928 shutdown,
23929 Duration::from_secs(30),
23930 );
23931 }
23932
23933 #[cfg(not(target_family = "wasm"))]
23934 #[test]
23935 fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
23936 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
23937
23938 if std::env::var_os(CHILD_ENV).is_some() {
23939 use std::sync::mpsc;
23940
23941 let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
23942 let backend = controller.backend();
23943 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
23944 panic!("configured MultiThread backend must create a Rayon executor");
23945 };
23946 let installed = Arc::new(AtomicBool::new(false));
23947 let (completed_tx, completed_rx) = mpsc::channel();
23948 let probe_controller = Arc::clone(&controller);
23949 let probe_installed = Arc::clone(&installed);
23950 executor.pool.broadcast(move |_| {
23951 if probe_installed.swap(true, Ordering::SeqCst) {
23952 return;
23953 }
23954 WORKER_TLS_DROP_PROBE.with(|slot| {
23955 let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
23956 action: Some(WorkerTlsDropAction::ReenterLifecycle {
23957 controller: Arc::clone(&probe_controller),
23958 completed: completed_tx.clone(),
23959 }),
23960 });
23961 assert!(
23962 previous.is_none(),
23963 "the selected worker must have no prior TLS probe"
23964 );
23965 });
23966 });
23967 assert!(
23968 installed.load(Ordering::SeqCst),
23969 "one worker must install the lifecycle probe"
23970 );
23971 drop(backend);
23972
23973 controller
23974 .shutdown()
23975 .expect("external shutdown must join the retiring workers");
23976 let (start, shutdown) = completed_rx
23977 .recv_timeout(Duration::from_secs(2))
23978 .expect("the TLS destructor must complete both lifecycle reentry attempts");
23979 assert_eq!(
23980 start.unwrap_err(),
23981 "cannot restart the async runtime from work in the generation being stopped"
23982 );
23983 assert_eq!(
23984 shutdown.unwrap_err(),
23985 "cannot wait for async runtime shutdown from work in the generation being stopped"
23986 );
23987 return;
23988 }
23989
23990 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
23991 .arg("--exact")
23992 .arg(
23993 "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
23994 )
23995 .arg("--nocapture")
23996 .env(CHILD_ENV, "1")
23997 .stdout(std::process::Stdio::piped())
23998 .stderr(std::process::Stdio::piped())
23999 .spawn()
24000 .expect("the TLS lifecycle-reentry subprocess must start");
24001 let deadline = Instant::now() + Duration::from_secs(5);
24002 let status = loop {
24003 if let Some(status) = child
24004 .try_wait()
24005 .expect("the subprocess status must be readable")
24006 {
24007 break status;
24008 }
24009 if Instant::now() >= deadline {
24010 let _ = child.kill();
24011 let output = child
24012 .wait_with_output()
24013 .expect("the timed-out subprocess must be reaped");
24014 panic!(
24015 "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
24016 String::from_utf8_lossy(&output.stdout),
24017 String::from_utf8_lossy(&output.stderr)
24018 );
24019 }
24020 std::thread::sleep(Duration::from_millis(10));
24021 };
24022 let output = child
24023 .wait_with_output()
24024 .expect("the completed subprocess output must be read");
24025 assert!(
24026 status.success(),
24027 "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
24028 status.code(),
24029 String::from_utf8_lossy(&output.stdout),
24030 String::from_utf8_lossy(&output.stderr)
24031 );
24032 }
24033
24034 #[cfg(not(target_family = "wasm"))]
24035 #[test]
24036 fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
24037 use std::sync::mpsc;
24038
24039 struct DropSignal(mpsc::Sender<()>);
24040 impl Drop for DropSignal {
24041 fn drop(&mut self) {
24042 let _ = self.0.send(());
24043 }
24044 }
24045
24046 let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
24047 let (polled_tx, polled_rx) = mpsc::channel();
24048 let (dropped_tx, dropped_rx) = mpsc::channel();
24049 controller
24050 .try_spawn_detached(async move {
24051 let _drop_signal = DropSignal(dropped_tx);
24052 polled_tx.send(()).unwrap();
24053 std::future::pending::<()>().await;
24054 })
24055 .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
24056 polled_rx
24057 .recv_timeout(Duration::from_secs(5))
24058 .expect("the pending task must be polled before shutdown");
24059
24060 controller
24061 .shutdown()
24062 .expect("shutdown must cancel accepted async work");
24063 dropped_rx
24064 .recv_timeout(Duration::from_secs(5))
24065 .expect("shutdown must drop the accepted task future before returning");
24066 }
24067
24068 #[cfg(not(target_family = "wasm"))]
24069 #[test]
24070 fn current_thread_shutdown_outranks_perpetual_self_wake() {
24071 use std::sync::mpsc;
24072
24073 struct SelfWakingPending {
24074 entered: Option<mpsc::Sender<()>>,
24075 }
24076
24077 impl Future for SelfWakingPending {
24078 type Output = ();
24079
24080 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24081 if let Some(entered) = self.entered.take() {
24082 entered.send(()).unwrap();
24083 }
24084 cx.waker().wake_by_ref();
24085 Poll::Pending
24086 }
24087 }
24088
24089 let controller = Arc::new(current_thread_controller(
24090 "current-thread-self-wake-shutdown",
24091 ));
24092 let (entered_tx, entered_rx) = mpsc::channel();
24093 let (outcome_tx, outcome_rx) = mpsc::channel();
24094 let block_on_controller = Arc::clone(&controller);
24095 let runner = std::thread::spawn(move || {
24096 let mut future = std::pin::pin!(SelfWakingPending {
24097 entered: Some(entered_tx)
24098 });
24099 outcome_tx
24100 .send(block_on_controller.block_on(future.as_mut()))
24101 .unwrap();
24102 });
24103 entered_rx
24104 .recv_timeout(Duration::from_secs(2))
24105 .expect("the self-waking future must be polled before shutdown");
24106
24107 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24108 let shutdown_controller = Arc::clone(&controller);
24109 let shutdown = std::thread::spawn(move || {
24110 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24111 });
24112 shutdown_rx
24113 .recv_timeout(Duration::from_secs(2))
24114 .expect("shutdown must outrank a stored self-wake permit")
24115 .unwrap();
24116 assert_eq!(
24117 outcome_rx
24118 .recv_timeout(Duration::from_secs(2))
24119 .expect("the raw block_on driver must report shutdown"),
24120 BlockOnOutcome::Stopped
24121 );
24122 runner.join().unwrap();
24123 shutdown.join().unwrap();
24124 }
24125
24126 #[cfg(not(target_family = "wasm"))]
24127 #[test]
24128 fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
24129 use std::sync::mpsc;
24130
24131 let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
24132 let backend = controller.backend();
24133 let executor = match &backend.executor {
24134 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
24135 RuntimeExecutor::CurrentThread(_) => unreachable!(),
24136 };
24137 let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
24138 let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
24139 for _ in 0..2 {
24140 let worker_ready_tx = worker_ready_tx.clone();
24141 let release_workers = Arc::clone(&release_workers);
24142 executor.pool.spawn(move || {
24143 worker_ready_tx.send(()).unwrap();
24144 let (released, changed) = &*release_workers;
24145 let mut released = released
24146 .lock()
24147 .unwrap_or_else(std::sync::PoisonError::into_inner);
24148 while !*released {
24149 released = changed
24150 .wait(released)
24151 .unwrap_or_else(std::sync::PoisonError::into_inner);
24152 }
24153 });
24154 }
24155 drop(worker_ready_tx);
24156 for _ in 0..2 {
24157 worker_ready_rx
24158 .recv_timeout(Duration::from_secs(5))
24159 .expect("both workers must be occupied before the FIFO runnable is submitted");
24160 }
24161 drop(executor);
24162 drop(backend);
24163
24164 let polls = Arc::new(AtomicUsize::new(0));
24165 let handle = controller.spawn(StopPollProbe {
24166 polls: Arc::clone(&polls),
24167 first_poll: None,
24168 });
24169 wait_until("the FIFO runnable to be queued", || {
24170 controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
24171 });
24172
24173 let observed_queue = Arc::new(Mutex::new(None));
24174 let hook_observed_queue = Arc::clone(&observed_queue);
24175 let hook_metrics = Arc::clone(&controller.metrics);
24176 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
24177 *slot.borrow_mut() = Some(Box::new(move || {
24178 let (released, changed) = &*release_workers;
24179 *released
24180 .lock()
24181 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
24182 changed.notify_all();
24183 wait_until("the stopped FIFO runnable claim", || {
24184 hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
24185 });
24186 *hook_observed_queue
24187 .lock()
24188 .unwrap_or_else(std::sync::PoisonError::into_inner) =
24189 Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
24190 }));
24191 });
24192
24193 controller
24194 .shutdown()
24195 .expect("shutdown must cancel the stopped FIFO claim");
24196 assert_eq!(polls.load(Ordering::SeqCst), 0);
24197 assert_eq!(
24198 *observed_queue
24199 .lock()
24200 .unwrap_or_else(std::sync::PoisonError::into_inner),
24201 Some(0)
24202 );
24203 assert_eq!(
24204 controller.metrics.queued_runnables.load(Ordering::SeqCst),
24205 0
24206 );
24207 drop(handle);
24208 }
24209
24210 #[cfg(not(target_family = "wasm"))]
24211 #[test]
24212 fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
24213 use std::sync::mpsc;
24214
24215 let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
24216 let backend = controller.backend();
24217 let executor = match &backend.executor {
24218 RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
24219 RuntimeExecutor::CurrentThread(_) => unreachable!(),
24220 };
24221 let polls = Arc::new(AtomicUsize::new(0));
24222 let scheduler = Arc::downgrade(&executor);
24223 let (runnable, task) = async_task::spawn(
24224 StopPollProbe {
24225 polls: Arc::clone(&polls),
24226 first_poll: None,
24227 },
24228 move |runnable| {
24229 if let Some(executor) = scheduler.upgrade() {
24230 executor.schedule(runnable);
24231 }
24232 },
24233 );
24234 let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
24235 let (release_slot_tx, release_slot_rx) = mpsc::channel();
24236 let (claim_done_tx, claim_done_rx) = mpsc::channel();
24237 let worker_executor = Arc::clone(&executor);
24238 let worker_metrics = Arc::clone(&controller.metrics);
24239 executor.pool.spawn(move || {
24240 let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
24241 worker_executor.schedule(runnable);
24242 slot_ready_tx.send(()).unwrap();
24243 release_slot_rx.recv().unwrap();
24244 let claimed = worker_executor.run_one_runnable();
24245 claim_done_tx
24246 .send((
24247 claimed,
24248 worker_metrics.queued_runnables.load(Ordering::SeqCst),
24249 ))
24250 .unwrap();
24251 });
24252 slot_ready_rx
24253 .recv_timeout(Duration::from_secs(5))
24254 .expect("the worker must publish the LIFO runnable before shutdown");
24255 assert_eq!(
24256 controller.metrics.queued_runnables.load(Ordering::SeqCst),
24257 1
24258 );
24259 drop(executor);
24260 drop(backend);
24261
24262 let observed_claim = Arc::new(Mutex::new(None));
24263 let hook_observed_claim = Arc::clone(&observed_claim);
24264 AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
24265 *slot.borrow_mut() = Some(Box::new(move || {
24266 release_slot_tx.send(()).unwrap();
24267 let result = claim_done_rx
24268 .recv_timeout(Duration::from_secs(5))
24269 .expect("the stopped LIFO runnable claim must complete before abort");
24270 *hook_observed_claim
24271 .lock()
24272 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
24273 }));
24274 });
24275
24276 controller
24277 .shutdown()
24278 .expect("shutdown must cancel the stopped LIFO claim");
24279 assert_eq!(polls.load(Ordering::SeqCst), 0);
24280 assert_eq!(
24281 *observed_claim
24282 .lock()
24283 .unwrap_or_else(std::sync::PoisonError::into_inner),
24284 Some((true, 0))
24285 );
24286 assert_eq!(
24287 controller.metrics.queued_runnables.load(Ordering::SeqCst),
24288 0
24289 );
24290 drop(task);
24291 }
24292
24293 #[cfg(not(target_family = "wasm"))]
24294 #[test]
24295 fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
24296 use std::sync::mpsc;
24297
24298 struct BlockingDrop {
24299 entered: mpsc::Sender<()>,
24300 release: mpsc::Receiver<()>,
24301 }
24302
24303 impl Drop for BlockingDrop {
24304 fn drop(&mut self) {
24305 self.entered.send(()).unwrap();
24306 self.release.recv().unwrap();
24307 }
24308 }
24309
24310 struct TwoPollProbe {
24311 first_poll: Option<mpsc::Sender<()>>,
24312 release_first_poll: mpsc::Receiver<()>,
24313 second_poll: mpsc::Sender<()>,
24314 }
24315
24316 impl Future for TwoPollProbe {
24317 type Output = ();
24318
24319 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24320 if let Some(first_poll) = self.first_poll.take() {
24321 first_poll.send(()).unwrap();
24322 self.release_first_poll.recv().unwrap();
24323 } else {
24324 self.second_poll.send(()).unwrap();
24325 }
24326 cx.waker().wake_by_ref();
24327 Poll::Pending
24328 }
24329 }
24330
24331 let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
24332 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24333 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24334 let blocking_drop = BlockingDrop {
24335 entered: drop_entered_tx,
24336 release: release_drop_rx,
24337 };
24338 controller
24339 .try_spawn_detached(async move {
24340 let _drop = blocking_drop;
24341 std::future::pending::<()>().await;
24342 })
24343 .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
24344
24345 let (first_poll_tx, first_poll_rx) = mpsc::channel();
24346 let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
24347 let (second_poll_tx, second_poll_rx) = mpsc::channel();
24348 let (outcome_tx, outcome_rx) = mpsc::channel();
24349 let runner_controller = Arc::clone(&controller);
24350 let runner = std::thread::spawn(move || {
24351 let mut future = std::pin::pin!(TwoPollProbe {
24352 first_poll: Some(first_poll_tx),
24353 release_first_poll: release_first_poll_rx,
24354 second_poll: second_poll_tx,
24355 });
24356 outcome_tx
24357 .send(runner_controller.block_on(future.as_mut()))
24358 .unwrap();
24359 });
24360 first_poll_rx
24361 .recv_timeout(Duration::from_secs(2))
24362 .expect("the active block_on poll must enter before shutdown");
24363
24364 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24365 let shutdown_controller = Arc::clone(&controller);
24366 let shutdown = std::thread::spawn(move || {
24367 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24368 });
24369 drop_entered_rx
24370 .recv_timeout(Duration::from_secs(2))
24371 .expect("shutdown must begin destroying queued work");
24372
24373 release_first_poll_tx.send(()).unwrap();
24374 let polled_again = second_poll_rx
24375 .recv_timeout(Duration::from_millis(200))
24376 .is_ok();
24377 release_drop_tx.send(()).unwrap();
24378
24379 assert!(
24380 !polled_again,
24381 "an active block_on driver must observe generation stop before queued user cleanup blocks"
24382 );
24383 assert_eq!(
24384 outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24385 BlockOnOutcome::Stopped
24386 );
24387 shutdown_rx
24388 .recv_timeout(Duration::from_secs(2))
24389 .unwrap()
24390 .unwrap();
24391 runner.join().unwrap();
24392 shutdown.join().unwrap();
24393 }
24394
24395 #[cfg(not(target_family = "wasm"))]
24396 #[test]
24397 fn shutdown_cancels_nested_block_on_within_deadline() {
24398 use std::sync::mpsc;
24399
24400 let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
24401 let task_controller = Arc::clone(&controller);
24402 let (entered_tx, entered_rx) = mpsc::channel();
24403 let task = controller
24404 .try_spawn(async move {
24405 entered_tx.send(()).unwrap();
24406 block_on_with_controller(&task_controller, std::future::pending::<()>());
24407 })
24408 .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
24409 entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
24410
24411 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24412 let shutdown_controller = Arc::clone(&controller);
24413 let shutdown = std::thread::spawn(move || {
24414 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24415 });
24416 shutdown_rx
24417 .recv_timeout(Duration::from_secs(2))
24418 .expect("shutdown must wake and cancel an active nested block_on")
24419 .unwrap();
24420 shutdown.join().unwrap();
24421 assert!(
24422 futures::executor::block_on(task).is_err(),
24423 "the cancelled nested block_on task must resolve as stopped"
24424 );
24425 }
24426
24427 #[cfg(not(target_family = "wasm"))]
24428 #[test]
24429 fn stopped_public_block_on_drops_future_before_generation_retirement() {
24430 use std::sync::mpsc;
24431
24432 struct PendingDrop {
24433 entered: Option<mpsc::Sender<()>>,
24434 dropped: mpsc::Sender<Option<u64>>,
24435 release: mpsc::Receiver<()>,
24436 }
24437
24438 impl Future for PendingDrop {
24439 type Output = ();
24440
24441 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24442 if let Some(entered) = self.entered.take() {
24443 entered.send(()).unwrap();
24444 }
24445 Poll::Pending
24446 }
24447 }
24448
24449 impl Drop for PendingDrop {
24450 fn drop(&mut self) {
24451 self
24452 .dropped
24453 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24454 .unwrap();
24455 self.release.recv().unwrap();
24456 }
24457 }
24458
24459 let controller = Arc::new(current_thread_controller(
24460 "public-block-on-generation-destruction",
24461 ));
24462 let generation = controller.backend().generation();
24463 let (entered_tx, entered_rx) = mpsc::channel();
24464 let (dropped_tx, dropped_rx) = mpsc::channel();
24465 let (release_tx, release_rx) = mpsc::channel();
24466 let (runner_done_tx, runner_done_rx) = mpsc::channel();
24467 let block_controller = Arc::clone(&controller);
24468 let runner = std::thread::spawn(move || {
24469 let result = catch_unwind(AssertUnwindSafe(|| {
24470 block_on_with_controller(
24471 &block_controller,
24472 PendingDrop {
24473 entered: Some(entered_tx),
24474 dropped: dropped_tx,
24475 release: release_rx,
24476 },
24477 );
24478 }));
24479 runner_done_tx.send(result.is_err()).unwrap();
24480 });
24481 entered_rx
24482 .recv_timeout(Duration::from_secs(2))
24483 .expect("the public block_on future must be polled before shutdown");
24484
24485 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24486 let shutdown_controller = Arc::clone(&controller);
24487 let shutdown = std::thread::spawn(move || {
24488 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24489 });
24490
24491 assert_eq!(
24492 dropped_rx
24493 .recv_timeout(Duration::from_secs(2))
24494 .expect("shutdown must wake block_on and begin destroying its future"),
24495 Some(generation),
24496 "the stopped future must be destroyed under its retiring generation identity"
24497 );
24498 assert!(
24499 shutdown_rx
24500 .recv_timeout(Duration::from_millis(200))
24501 .is_err(),
24502 "shutdown must not publish Stopped while the public block_on future destructor is running"
24503 );
24504
24505 release_tx.send(()).unwrap();
24506 assert!(
24507 runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24508 "the public helper must retain its existing early-stop panic behavior"
24509 );
24510 shutdown_rx
24511 .recv_timeout(Duration::from_secs(2))
24512 .unwrap()
24513 .unwrap();
24514 runner.join().unwrap();
24515 shutdown.join().unwrap();
24516 }
24517
24518 #[cfg(not(target_family = "wasm"))]
24519 #[test]
24520 fn shutdown_contains_panicking_nested_block_on_future_drop() {
24521 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
24522
24523 if std::env::var_os(CHILD_ENV).is_some() {
24524 use std::sync::mpsc;
24525
24526 struct PendingPanicOnDrop {
24527 entered: Option<mpsc::Sender<()>>,
24528 }
24529
24530 impl Future for PendingPanicOnDrop {
24531 type Output = ();
24532
24533 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24534 if let Some(entered) = self.entered.take() {
24535 entered.send(()).unwrap();
24536 }
24537 Poll::Pending
24538 }
24539 }
24540
24541 impl Drop for PendingPanicOnDrop {
24542 fn drop(&mut self) {
24543 panic!("intentional nested block_on future destructor panic");
24544 }
24545 }
24546
24547 let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
24548 let task_controller = Arc::clone(&controller);
24549 let (entered_tx, entered_rx) = mpsc::channel();
24550 let task = controller
24551 .try_spawn(async move {
24552 block_on_with_controller(
24553 &task_controller,
24554 PendingPanicOnDrop {
24555 entered: Some(entered_tx),
24556 },
24557 );
24558 })
24559 .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
24560 entered_rx
24561 .recv_timeout(Duration::from_secs(2))
24562 .expect("the nested block_on future must be polled before shutdown");
24563
24564 controller
24565 .shutdown()
24566 .expect("shutdown must contain nested future destruction");
24567 assert!(
24568 futures::executor::block_on(task).is_err(),
24569 "shutdown cancellation must still surface as a task error"
24570 );
24571 return;
24572 }
24573
24574 let output = std::process::Command::new(std::env::current_exe().unwrap())
24575 .arg("--exact")
24576 .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
24577 .arg("--nocapture")
24578 .env(CHILD_ENV, "1")
24579 .output()
24580 .expect("the subprocess test must start");
24581 assert!(
24582 output.status.success(),
24583 "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
24584 output.status.code(),
24585 String::from_utf8_lossy(&output.stdout),
24586 String::from_utf8_lossy(&output.stderr)
24587 );
24588 }
24589
24590 #[test]
24591 fn panicking_future_drop_is_contained_before_async_task_drop() {
24592 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
24593
24594 if std::env::var_os(CHILD_ENV).is_some() {
24595 struct PanicOnDrop;
24596 impl Drop for PanicOnDrop {
24597 fn drop(&mut self) {
24598 panic!("intentional future destructor panic");
24599 }
24600 }
24601
24602 let controller = current_thread_controller("future-drop-subprocess");
24603 let bomb = PanicOnDrop;
24604 controller
24605 .try_spawn_detached(async move {
24606 let _bomb = bomb;
24607 std::future::pending::<()>().await;
24608 })
24609 .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
24610 controller
24611 .shutdown()
24612 .expect("shutdown must contain the future destructor panic");
24613 return;
24614 }
24615
24616 let output = std::process::Command::new(std::env::current_exe().unwrap())
24617 .arg("--exact")
24618 .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
24619 .arg("--nocapture")
24620 .env(CHILD_ENV, "1")
24621 .output()
24622 .expect("the subprocess test must start");
24623 assert!(
24624 output.status.success(),
24625 "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
24626 output.status.code(),
24627 String::from_utf8_lossy(&output.stdout),
24628 String::from_utf8_lossy(&output.stderr)
24629 );
24630 }
24631
24632 #[test]
24633 fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
24634 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
24635
24636 if std::env::var_os(CHILD_ENV).is_some() {
24637 struct PanicOnDrop;
24638 impl Drop for PanicOnDrop {
24639 fn drop(&mut self) {
24640 panic!("intentional detached output destructor panic");
24641 }
24642 }
24643
24644 let controller = current_thread_controller("output-drop-subprocess");
24645 let handle = controller
24646 .try_spawn(async { PanicOnDrop })
24647 .unwrap_or_else(|_| panic!("runtime must accept the task"));
24648 drop(handle);
24649 let dispatch = controller_current_thread_dispatch(&controller);
24650 controller.drive_current_thread_dispatch(dispatch, false);
24651 controller.shutdown().unwrap();
24652 return;
24653 }
24654
24655 let output = std::process::Command::new(std::env::current_exe().unwrap())
24656 .arg("--exact")
24657 .arg(
24658 "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
24659 )
24660 .arg("--nocapture")
24661 .env(CHILD_ENV, "1")
24662 .output()
24663 .expect("the subprocess test must start");
24664 assert!(
24665 output.status.success(),
24666 "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
24667 output.status.code(),
24668 String::from_utf8_lossy(&output.stdout),
24669 String::from_utf8_lossy(&output.stderr)
24670 );
24671 }
24672
24673 #[cfg(not(target_family = "wasm"))]
24674 #[test]
24675 fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
24676 use std::sync::mpsc;
24677
24678 struct BlockingDrop {
24679 controller: Arc<RuntimeController>,
24680 entered: mpsc::Sender<Option<u64>>,
24681 release: mpsc::Receiver<()>,
24682 reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
24683 }
24684
24685 impl Drop for BlockingDrop {
24686 fn drop(&mut self) {
24687 self
24688 .entered
24689 .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24690 .unwrap();
24691 self.release.recv().unwrap();
24692 self
24693 .reentrant_shutdown
24694 .send(self.controller.shutdown())
24695 .unwrap();
24696 }
24697 }
24698
24699 let controller = Arc::new(current_thread_controller(
24700 "current-thread-host-drain-output-retirement",
24701 ));
24702 let old_generation = controller.backend().generation();
24703 let (entered_tx, entered_rx) = mpsc::channel();
24704 let (release_tx, release_rx) = mpsc::channel();
24705 let (reentrant_tx, reentrant_rx) = mpsc::channel();
24706 let output_controller = Arc::clone(&controller);
24707 let handle = controller
24708 .try_spawn(async move {
24709 BlockingDrop {
24710 controller: output_controller,
24711 entered: entered_tx,
24712 release: release_rx,
24713 reentrant_shutdown: reentrant_tx,
24714 }
24715 })
24716 .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
24717 drop(handle);
24718 let dispatch = controller_current_thread_dispatch(&controller);
24719
24720 let driver_controller = Arc::clone(&controller);
24721 let driver =
24722 std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
24723 assert_eq!(
24724 entered_rx
24725 .recv_timeout(Duration::from_secs(2))
24726 .expect("the host turn must begin destroying the detached output"),
24727 Some(old_generation),
24728 "detached output destruction must retain the generation that produced it"
24729 );
24730
24731 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24732 let shutdown_controller = Arc::clone(&controller);
24733 let shutdown = std::thread::spawn(move || {
24734 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24735 });
24736 wait_until(
24737 "CurrentThread shutdown to enter the old generation's stopping state",
24738 || {
24739 let state = controller
24740 .state
24741 .lock()
24742 .unwrap_or_else(std::sync::PoisonError::into_inner);
24743 matches!(
24744 &state.lifecycle,
24745 RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
24746 )
24747 },
24748 );
24749
24750 let (restart_tx, restart_rx) = mpsc::channel();
24751 let restart_controller = Arc::clone(&controller);
24752 let restart = std::thread::spawn(move || {
24753 restart_controller.start().unwrap();
24754 restart_tx
24755 .send(restart_controller.backend().generation())
24756 .unwrap();
24757 });
24758
24759 let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
24760 let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
24761 let shutdown_overlapped = early_shutdown.is_some();
24762 let restart_overlapped = early_restart.is_some();
24763 release_tx.send(()).unwrap();
24764
24765 let reentrant_error = reentrant_rx
24766 .recv_timeout(Duration::from_secs(2))
24767 .expect("the retiring output destructor must finish its lifecycle check")
24768 .expect_err("old-generation output destruction must not shut down a replacement runtime");
24769 assert!(
24770 reentrant_error
24771 .to_string()
24772 .contains("generation being stopped"),
24773 "the destructor must still observe its old generation as Stopping: {reentrant_error}"
24774 );
24775
24776 let shutdown_result = early_shutdown.unwrap_or_else(|| {
24777 shutdown_rx
24778 .recv_timeout(Duration::from_secs(2))
24779 .expect("shutdown must finish after detached output destruction retires")
24780 });
24781 shutdown_result.unwrap();
24782 let new_generation = early_restart.unwrap_or_else(|| {
24783 restart_rx
24784 .recv_timeout(Duration::from_secs(2))
24785 .expect("restart must finish after the old host turn retires")
24786 });
24787
24788 driver.join().unwrap();
24789 shutdown.join().unwrap();
24790 restart.join().unwrap();
24791 assert!(
24792 !shutdown_overlapped,
24793 "shutdown must not publish Stopped while a host turn is destroying detached output"
24794 );
24795 assert!(
24796 !restart_overlapped,
24797 "restart must not overlap a host turn from the retiring generation"
24798 );
24799 assert_ne!(new_generation, old_generation);
24800 controller.shutdown().unwrap();
24801 }
24802
24803 #[cfg(not(target_family = "wasm"))]
24804 #[test]
24805 fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
24806 use std::sync::mpsc;
24807
24808 struct ShutdownOnDrop {
24809 controller: Arc<RuntimeController>,
24810 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
24811 }
24812
24813 impl Drop for ShutdownOnDrop {
24814 fn drop(&mut self) {
24815 let _ = self.result.send(self.controller.shutdown());
24816 }
24817 }
24818
24819 let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
24820 let (running_tx, running_rx) = mpsc::channel();
24821 let (release_tx, release_rx) = mpsc::channel();
24822 let running = controller
24823 .try_spawn_blocking(move || {
24824 running_tx.send(()).unwrap();
24825 release_rx.recv().unwrap();
24826 })
24827 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
24828 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
24829
24830 let (reentrant_tx, reentrant_rx) = mpsc::channel();
24831 let bomb = ShutdownOnDrop {
24832 controller: Arc::clone(&controller),
24833 result: reentrant_tx,
24834 };
24835 let queued = controller
24836 .try_spawn_blocking(move || {
24837 let _bomb = &bomb;
24838 })
24839 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
24840
24841 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24842 let shutdown_controller = Arc::clone(&controller);
24843 let shutdown = std::thread::spawn(move || {
24844 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24845 });
24846 let error = reentrant_rx
24847 .recv_timeout(Duration::from_secs(2))
24848 .expect("queued destruction must not self-deadlock")
24849 .expect_err("the retiring generation must reject reentrant shutdown");
24850 assert!(error.to_string().contains("generation being stopped"));
24851 release_tx.send(()).unwrap();
24852 shutdown_rx
24853 .recv_timeout(Duration::from_secs(2))
24854 .unwrap()
24855 .unwrap();
24856 shutdown.join().unwrap();
24857 futures::executor::block_on(running).unwrap();
24858 assert!(futures::executor::block_on(queued).is_err());
24859 }
24860
24861 #[cfg(not(target_family = "wasm"))]
24862 #[test]
24863 fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
24864 use std::sync::mpsc;
24865
24866 struct ShutdownAfterRelease {
24867 controller: Arc<RuntimeController>,
24868 entered: mpsc::Sender<()>,
24869 release: mpsc::Receiver<()>,
24870 result: mpsc::Sender<Result<(), RuntimeConfigError>>,
24871 }
24872
24873 impl Drop for ShutdownAfterRelease {
24874 fn drop(&mut self) {
24875 self.entered.send(()).unwrap();
24876 self.release.recv().unwrap();
24877 self.result.send(self.controller.shutdown()).unwrap();
24878 }
24879 }
24880
24881 let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
24882 let (holder_started_tx, holder_started_rx) = mpsc::channel();
24883 let (release_holder_tx, release_holder_rx) = mpsc::channel();
24884 let holder = controller
24885 .try_spawn_blocking(move || {
24886 holder_started_tx.send(()).unwrap();
24887 release_holder_rx.recv().unwrap();
24888 })
24889 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
24890 holder_started_rx
24891 .recv_timeout(Duration::from_secs(2))
24892 .unwrap();
24893
24894 let (shutdown_tx, shutdown_rx) = mpsc::channel();
24895 let shutdown_controller = Arc::clone(&controller);
24896 let shutdown = std::thread::spawn(move || {
24897 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24898 });
24899 wait_until("the runtime entered Stopping", || {
24900 matches!(
24901 controller
24902 .state
24903 .lock()
24904 .unwrap_or_else(std::sync::PoisonError::into_inner)
24905 .lifecycle,
24906 RuntimeLifecycle::Stopping(_)
24907 )
24908 });
24909
24910 let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24911 let (release_drop_tx, release_drop_rx) = mpsc::channel();
24912 let (reentrant_tx, reentrant_rx) = mpsc::channel();
24913 let rejected_controller = Arc::clone(&controller);
24914 let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
24915 let rejected = std::thread::spawn(move || {
24916 let bomb = ShutdownAfterRelease {
24917 controller: Arc::clone(&rejected_controller),
24918 entered: drop_entered_tx,
24919 release: release_drop_rx,
24920 result: reentrant_tx,
24921 };
24922 let handle = rejected_controller.spawn(async move {
24923 let _bomb = bomb;
24924 std::future::pending::<()>().await;
24925 });
24926 rejected_done_tx
24927 .send(futures::executor::block_on(handle))
24928 .unwrap();
24929 });
24930 drop_entered_rx
24931 .recv_timeout(Duration::from_secs(2))
24932 .unwrap();
24933
24934 release_holder_tx.send(()).unwrap();
24935 assert!(
24936 shutdown_rx
24937 .recv_timeout(Duration::from_millis(200))
24938 .is_err(),
24939 "shutdown crossed into Stopped before rejected destruction finished"
24940 );
24941
24942 let (start_tx, start_rx) = mpsc::channel();
24943 let start_controller = Arc::clone(&controller);
24944 let starter = std::thread::spawn(move || {
24945 start_tx.send(start_controller.start()).unwrap();
24946 });
24947 assert!(
24948 start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
24949 "restart crossed into a new generation before rejected destruction finished"
24950 );
24951
24952 release_drop_tx.send(()).unwrap();
24953 let error = reentrant_rx
24954 .recv_timeout(Duration::from_secs(2))
24955 .expect("the rejected destructor must complete")
24956 .expect_err("the retiring generation must reject the destructor's shutdown");
24957 assert!(error.to_string().contains("generation being stopped"));
24958 assert!(
24959 rejected_done_rx
24960 .recv_timeout(Duration::from_secs(2))
24961 .unwrap()
24962 .is_err()
24963 );
24964 rejected.join().unwrap();
24965
24966 shutdown_rx
24967 .recv_timeout(Duration::from_secs(2))
24968 .unwrap()
24969 .unwrap();
24970 shutdown.join().unwrap();
24971 start_rx
24972 .recv_timeout(Duration::from_secs(2))
24973 .unwrap()
24974 .unwrap();
24975 starter.join().unwrap();
24976 futures::executor::block_on(holder).unwrap();
24977 controller.shutdown().unwrap();
24978 }
24979
24980 #[cfg(not(target_family = "wasm"))]
24981 #[test]
24982 fn shutdown_contains_hostile_caught_panic_payload() {
24983 use std::{panic::panic_any, sync::mpsc};
24984
24985 struct PanicAgain;
24986 impl Drop for PanicAgain {
24987 fn drop(&mut self) {
24988 panic!("caught panic payload destructor escaped");
24989 }
24990 }
24991
24992 struct PanicPayloadOnDrop(mpsc::Sender<()>);
24993 impl Drop for PanicPayloadOnDrop {
24994 fn drop(&mut self) {
24995 let _ = self.0.send(());
24996 panic_any(PanicAgain);
24997 }
24998 }
24999
25000 let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
25001 let (running_tx, running_rx) = mpsc::channel();
25002 let (release_tx, release_rx) = mpsc::channel();
25003 let running = controller
25004 .try_spawn_blocking(move || {
25005 running_tx.send(()).unwrap();
25006 release_rx.recv().unwrap();
25007 })
25008 .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
25009 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25010
25011 let (drop_tx, drop_rx) = mpsc::channel();
25012 let payload = PanicPayloadOnDrop(drop_tx);
25013 let queued = controller
25014 .try_spawn_blocking(move || {
25015 let _payload = &payload;
25016 })
25017 .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
25018
25019 let (shutdown_tx, shutdown_rx) = mpsc::channel();
25020 let shutdown_controller = Arc::clone(&controller);
25021 let shutdown = std::thread::spawn(move || {
25022 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25023 });
25024 drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
25025 release_tx.send(()).unwrap();
25026 shutdown_rx
25027 .recv_timeout(Duration::from_secs(2))
25028 .expect("hostile panic payload destruction must remain contained")
25029 .unwrap();
25030 shutdown
25031 .join()
25032 .expect("the caught payload must not panic during destruction");
25033 futures::executor::block_on(running).unwrap();
25034 assert!(futures::executor::block_on(queued).is_err());
25035 }
25036
25037 #[cfg(not(target_family = "wasm"))]
25038 #[test]
25039 fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
25040 use std::sync::mpsc;
25041
25042 struct DropSignal(mpsc::Sender<()>);
25043 impl Drop for DropSignal {
25044 fn drop(&mut self) {
25045 let _ = self.0.send(());
25046 }
25047 }
25048
25049 let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
25050 let (running_tx, running_rx) = mpsc::channel();
25051 let (release_tx, release_rx) = mpsc::channel();
25052 let running = controller
25053 .try_spawn_blocking(move || {
25054 running_tx.send(()).unwrap();
25055 release_rx.recv().unwrap();
25056 7usize
25057 })
25058 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25059 running_rx
25060 .recv_timeout(Duration::from_secs(5))
25061 .expect("the first blocking job must hold the only blocking slot");
25062
25063 let queued_ran = Arc::new(AtomicBool::new(false));
25064 let queued_ran_work = Arc::clone(&queued_ran);
25065 let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
25066 let queued_drop_signal = DropSignal(queued_dropped_tx);
25067 let queued = controller
25068 .try_spawn_blocking(move || {
25069 let _drop_signal = queued_drop_signal;
25070 queued_ran_work.store(true, Ordering::SeqCst);
25071 11usize
25072 })
25073 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
25074
25075 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25076 let shutdown_controller = Arc::clone(&controller);
25077 let shutdown = std::thread::spawn(move || {
25078 shutdown_controller.shutdown().unwrap();
25079 shutdown_done_tx.send(()).unwrap();
25080 });
25081
25082 queued_dropped_rx
25083 .recv_timeout(Duration::from_secs(5))
25084 .expect("shutdown must drop queued blocking work");
25085 assert!(
25086 !queued_ran.load(Ordering::SeqCst),
25087 "queued blocking work must not execute"
25088 );
25089 assert!(
25090 shutdown_done_rx.try_recv().is_err(),
25091 "shutdown must wait while an accepted blocking closure is still running"
25092 );
25093
25094 release_tx.send(()).unwrap();
25095 shutdown_done_rx
25096 .recv_timeout(Duration::from_secs(5))
25097 .expect("shutdown must finish after running blocking work retires");
25098 shutdown.join().unwrap();
25099 assert_eq!(futures::executor::block_on(running).unwrap(), 7);
25100 assert!(
25101 futures::executor::block_on(queued).is_err(),
25102 "the queued blocking handle must resolve as cancelled"
25103 );
25104 }
25105
25106 #[cfg(not(target_family = "wasm"))]
25107 #[test]
25108 fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
25109 use std::sync::mpsc;
25110
25111 struct PanicOnDrop(mpsc::Sender<()>);
25112 impl Drop for PanicOnDrop {
25113 fn drop(&mut self) {
25114 let _ = self.0.send(());
25115 panic!("intentional queued blocking destructor panic");
25116 }
25117 }
25118
25119 let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
25120 let (running_tx, running_rx) = mpsc::channel();
25121 let (release_tx, release_rx) = mpsc::channel();
25122 let running = controller
25123 .try_spawn_blocking(move || {
25124 running_tx.send(()).unwrap();
25125 release_rx.recv().unwrap();
25126 })
25127 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25128 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25129
25130 let (drop_tx, drop_rx) = mpsc::channel();
25131 let panic_on_drop = PanicOnDrop(drop_tx);
25132 let queued = controller
25133 .try_spawn_blocking(move || {
25134 let _keep_captured_until_run = &panic_on_drop;
25135 })
25136 .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
25137
25138 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25139 let shutdown_controller = Arc::clone(&controller);
25140 let shutdown = std::thread::spawn(move || {
25141 shutdown_controller.shutdown().unwrap();
25142 shutdown_done_tx.send(()).unwrap();
25143 });
25144
25145 drop_rx
25146 .recv_timeout(Duration::from_secs(5))
25147 .expect("shutdown must attempt to drop the queued closure");
25148 release_tx.send(()).unwrap();
25149 shutdown_done_rx
25150 .recv_timeout(Duration::from_secs(5))
25151 .expect("a panicking queued destructor must not strand shutdown");
25152 shutdown
25153 .join()
25154 .expect("shutdown must contain the destructor panic");
25155 futures::executor::block_on(running).unwrap();
25156 assert!(futures::executor::block_on(queued).is_err());
25157
25158 controller
25159 .start()
25160 .expect("the stopped controller must restart");
25161 let restarted = controller
25162 .try_spawn_blocking(|| 17usize)
25163 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
25164 assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
25165 controller.shutdown().unwrap();
25166 }
25167
25168 #[cfg(not(target_family = "wasm"))]
25169 #[test]
25170 fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
25171 use std::{panic::panic_any, sync::mpsc};
25172
25173 struct ReentrantPayload {
25174 controller: Arc<RuntimeController>,
25175 result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
25176 }
25177
25178 impl Drop for ReentrantPayload {
25179 fn drop(&mut self) {
25180 let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
25181 let shutdown = self.controller.shutdown();
25182 self.result.send((generation, shutdown)).unwrap();
25183 }
25184 }
25185
25186 struct PanicPayloadOnDrop(Option<ReentrantPayload>);
25187
25188 impl Drop for PanicPayloadOnDrop {
25189 fn drop(&mut self) {
25190 panic_any(self.0.take().expect("the result must panic only once"));
25191 }
25192 }
25193
25194 let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
25195 let generation = controller.backend().generation();
25196 let (started_tx, started_rx) = mpsc::channel();
25197 let (release_tx, release_rx) = mpsc::channel();
25198 let (result_tx, result_rx) = mpsc::channel();
25199 let payload_controller = Arc::clone(&controller);
25200 let handle = controller
25201 .try_spawn_blocking(move || {
25202 started_tx.send(()).unwrap();
25203 release_rx.recv().unwrap();
25204 PanicPayloadOnDrop(Some(ReentrantPayload {
25205 controller: payload_controller,
25206 result: result_tx,
25207 }))
25208 })
25209 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25210 started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25211
25212 drop(handle);
25215 release_tx.send(()).unwrap();
25216 let (observed_generation, reentrant_shutdown) = result_rx
25217 .recv_timeout(Duration::from_secs(5))
25218 .expect("the dropped result panic payload must not self-deadlock");
25219 assert_eq!(observed_generation, Some(generation));
25220 let error =
25221 reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
25222 assert!(error.to_string().contains("work running on that runtime"));
25223
25224 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25225 let shutdown_controller = Arc::clone(&controller);
25226 let shutdown = std::thread::spawn(move || {
25227 shutdown_controller.shutdown().unwrap();
25228 shutdown_done_tx.send(()).unwrap();
25229 });
25230 shutdown_done_rx
25231 .recv_timeout(Duration::from_secs(5))
25232 .expect("a dropped-result destructor panic must not strand shutdown");
25233 shutdown
25234 .join()
25235 .expect("shutdown must contain the destructor panic");
25236
25237 controller
25238 .start()
25239 .expect("the stopped controller must restart");
25240 let restarted = controller
25241 .try_spawn_blocking(|| 19usize)
25242 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
25243 assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
25244 controller.shutdown().unwrap();
25245 }
25246
25247 #[cfg(not(target_family = "wasm"))]
25248 #[test]
25249 fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
25250 use std::sync::mpsc;
25251
25252 struct PanicOnDrop(mpsc::Sender<()>);
25253
25254 impl Drop for PanicOnDrop {
25255 fn drop(&mut self) {
25256 self.0.send(()).unwrap();
25257 panic!("intentional buffered blocking result destructor panic");
25258 }
25259 }
25260
25261 let options = RuntimeOptions {
25262 flavor: RuntimeFlavor::MultiThread,
25263 worker_threads: 2,
25264 max_blocking_tasks: 1,
25265 thread_name_prefix: "buffered-blocking-result-drop".to_string(),
25266 park_deadline: None,
25267 };
25268 let executor =
25269 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25270 let (started_tx, started_rx) = mpsc::channel();
25271 let (dropped_tx, dropped_rx) = mpsc::channel();
25272 let handle = executor.schedule_blocking(move || {
25273 started_tx.send(()).unwrap();
25274 PanicOnDrop(dropped_tx)
25275 });
25276 started_rx
25277 .recv_timeout(Duration::from_secs(2))
25278 .expect("the blocking closure must run");
25279 wait_until("the completed blocking result to be buffered", || {
25280 executor.active_blocking.load(Ordering::Acquire) == 0
25281 });
25282 assert!(
25283 dropped_rx.try_recv().is_err(),
25284 "the buffered result must remain owned by the live receiver"
25285 );
25286
25287 let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
25288 assert!(
25289 dropped.is_ok(),
25290 "dropping the buffered result must contain its destructor panic"
25291 );
25292 dropped_rx
25293 .recv_timeout(Duration::from_secs(2))
25294 .expect("dropping the receiver must reclaim the buffered result");
25295 }
25296
25297 #[cfg(not(target_family = "wasm"))]
25298 #[test]
25299 fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
25300 use std::sync::mpsc;
25301
25302 struct PanicOnDrop(mpsc::Sender<()>);
25303 impl Drop for PanicOnDrop {
25304 fn drop(&mut self) {
25305 let _ = self.0.send(());
25306 panic!("intentional late blocking destructor panic");
25307 }
25308 }
25309
25310 let executor = multi_thread_executor(2, None, "closed-queue-drop");
25311 executor.begin_shutdown();
25312
25313 let (drop_tx, drop_rx) = mpsc::channel();
25314 let panic_on_drop = PanicOnDrop(drop_tx);
25315 let handle = executor.schedule_blocking(move || {
25316 let _keep_captured_until_run = &panic_on_drop;
25317 });
25318
25319 drop_rx
25320 .recv_timeout(Duration::from_secs(5))
25321 .expect("the closed queue must retire the rejected closure");
25322 assert!(
25323 futures::executor::block_on(handle).is_err(),
25324 "the rejected blocking handle must resolve as cancelled"
25325 );
25326 }
25327
25328 #[cfg(not(target_family = "wasm"))]
25329 #[test]
25330 fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
25331 struct PanicWake;
25332 impl std::task::Wake for PanicWake {
25333 fn wake(self: Arc<Self>) {
25334 panic!("intentional timer waker panic");
25335 }
25336 }
25337
25338 let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
25339 let backend = controller.backend();
25340 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25341 panic!("configured MultiThread backend must create a Rayon executor");
25342 };
25343 let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
25344 let waker = Waker::from(Arc::new(PanicWake));
25345 let mut cx = Context::from_waker(&waker);
25346 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
25347 drop(backend);
25348
25349 controller
25350 .shutdown()
25351 .expect("shutdown must contain the timer waker panic");
25352 controller
25353 .start()
25354 .expect("the stopped controller must restart");
25355 let restarted = controller
25356 .try_spawn_blocking(|| 23usize)
25357 .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
25358 assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
25359 controller.shutdown().unwrap();
25360 }
25361
25362 #[cfg(not(target_family = "wasm"))]
25363 #[test]
25364 fn multi_thread_restart_waits_for_old_generation_worker_exit() {
25365 use std::sync::mpsc;
25366
25367 let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
25368 let backend = controller.backend();
25369 let old_generation = backend.generation();
25370 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25371 panic!("configured MultiThread backend must create a Rayon executor");
25372 };
25373 let old_workers = Arc::clone(&executor.worker_lifecycle);
25374 drop(backend);
25375
25376 let (running_tx, running_rx) = mpsc::channel();
25377 let (release_tx, release_rx) = mpsc::channel();
25378 let running = controller
25379 .try_spawn_blocking(move || {
25380 running_tx.send(()).unwrap();
25381 release_rx.recv().unwrap();
25382 })
25383 .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
25384 running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25385
25386 let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
25387 let shutdown_controller = Arc::clone(&controller);
25388 let shutdown = std::thread::spawn(move || {
25389 shutdown_controller.shutdown().unwrap();
25390 shutdown_done_tx.send(()).unwrap();
25391 });
25392 wait_until("controller entered Stopping", || {
25393 let state = controller
25394 .state
25395 .lock()
25396 .unwrap_or_else(std::sync::PoisonError::into_inner);
25397 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
25398 });
25399
25400 let (start_done_tx, start_done_rx) = mpsc::channel();
25401 let start_controller = Arc::clone(&controller);
25402 let starter = std::thread::spawn(move || {
25403 start_controller.start().unwrap();
25404 start_done_tx.send(()).unwrap();
25405 });
25406 assert!(
25407 start_done_rx.try_recv().is_err(),
25408 "start must wait while shutdown is quiescing"
25409 );
25410 assert!(
25411 old_workers.remaining() > 0,
25412 "the held old generation must still own workers"
25413 );
25414
25415 release_tx.send(()).unwrap();
25416 futures::executor::block_on(running).unwrap();
25417 shutdown_done_rx
25418 .recv_timeout(Duration::from_secs(5))
25419 .unwrap();
25420 start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25421 shutdown.join().unwrap();
25422 starter.join().unwrap();
25423 assert_eq!(
25424 old_workers.remaining(),
25425 0,
25426 "restart may begin only after every old worker exits"
25427 );
25428
25429 let backend = controller.backend();
25430 assert_ne!(backend.generation(), old_generation);
25431 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25432 panic!("restarted backend must remain MultiThread");
25433 };
25434 assert_eq!(
25435 executor.pool.current_num_threads(),
25436 controller.options().worker_threads
25437 );
25438 drop(backend);
25439 controller.shutdown().unwrap();
25440 }
25441
25442 #[cfg(not(target_family = "wasm"))]
25443 #[test]
25444 fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
25445 let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
25446 let task_controller = Arc::clone(&controller);
25447 let handle = controller
25448 .try_spawn(async move {
25449 task_controller
25450 .shutdown()
25451 .expect_err("runtime work must not wait for its own generation")
25452 .to_string()
25453 })
25454 .unwrap_or_else(|_| panic!("running runtime must accept the task"));
25455 let message = futures::executor::block_on(handle).unwrap();
25456 assert!(message.contains("cannot shut down"));
25457 controller.shutdown().unwrap();
25458 }
25459
25460 #[cfg(not(target_family = "wasm"))]
25467 #[test]
25468 fn multi_thread_blocking_active_never_exceeds_max_blocking() {
25469 use std::sync::{Barrier, mpsc};
25491 use std::time::Duration;
25492
25493 let (done_tx, done_rx) = mpsc::channel();
25494 let peak_out = Arc::new(Mutex::new(None));
25495 let peak_slot = Arc::clone(&peak_out);
25496 let runner = std::thread::spawn(move || {
25497 let metrics = Arc::new(RuntimeMetrics::default());
25498 let options = RuntimeOptions {
25499 flavor: RuntimeFlavor::MultiThread,
25500 worker_threads: 4,
25501 max_blocking_tasks: 2,
25502 thread_name_prefix: "rd9-blk-cap".to_string(),
25503 park_deadline: None,
25504 };
25505 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
25506
25507 let holder_count = options.max_blocking_tasks; let extra_count = 4usize; let (held_tx, held_rx) = mpsc::channel();
25512 let release = Arc::new(Barrier::new(holder_count + 1));
25513 let mut holder_handles = Vec::new();
25514 for _ in 0..holder_count {
25515 let held_tx = held_tx.clone();
25516 let release = Arc::clone(&release);
25517 holder_handles.push(executor.schedule_blocking(move || {
25518 held_tx.send(()).unwrap();
25519 release.wait();
25520 0usize
25521 }));
25522 }
25523 for _ in 0..holder_count {
25525 held_rx.recv().unwrap();
25526 }
25527
25528 let (extra_started_tx, extra_started_rx) = mpsc::channel();
25530 let mut extra_handles = Vec::new();
25531 for i in 0..extra_count {
25532 let extra_started_tx = extra_started_tx.clone();
25533 extra_handles.push(executor.schedule_blocking(move || {
25534 extra_started_tx.send(i).unwrap();
25535 i * 2
25536 }));
25537 }
25538
25539 assert!(
25542 matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
25543 "a beyond-cap blocking job ran before the cap was released (cap check broken)"
25544 );
25545
25546 release.wait();
25548
25549 for handle in holder_handles {
25550 assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
25551 }
25552 for (i, handle) in extra_handles.into_iter().enumerate() {
25555 assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
25556 }
25557
25558 assert_eq!(
25561 metrics.blocking_tasks_started.load(Ordering::Relaxed),
25562 (holder_count + extra_count) as u64
25563 );
25564
25565 *peak_slot
25566 .lock()
25567 .unwrap_or_else(std::sync::PoisonError::into_inner) =
25568 Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
25569 let _ = done_tx.send(());
25570 });
25571
25572 match done_rx.recv_timeout(Duration::from_secs(10)) {
25573 Ok(()) => runner.join().unwrap(),
25574 Err(error) => panic!("blocking-cap test did not complete ({error})"),
25575 }
25576 let peak = peak_out
25577 .lock()
25578 .unwrap_or_else(std::sync::PoisonError::into_inner)
25579 .expect("peak captured");
25580 assert!(
25581 peak >= 1,
25582 "at least one blocking job must have run (peak {peak})"
25583 );
25584 assert!(
25585 peak <= 2,
25586 "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
25587 );
25588 }
25589
25590 #[cfg(not(target_family = "wasm"))]
25591 #[test]
25592 fn multi_thread_drain_budget_completes_large_runnable_batch() {
25593 use std::sync::mpsc;
25598 use std::time::{Duration, Instant};
25599
25600 let (done_tx, done_rx) = mpsc::channel();
25601 let runner = std::thread::spawn(move || {
25602 let metrics = Arc::new(RuntimeMetrics::default());
25603 let options = RuntimeOptions {
25604 flavor: RuntimeFlavor::MultiThread,
25605 worker_threads: 2,
25606 max_blocking_tasks: 2,
25607 thread_name_prefix: "rd9-budget".to_string(),
25608 park_deadline: None,
25609 };
25610 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
25611
25612 let task_count = 200usize; let completed = Arc::new(AtomicU64::new(0));
25614 let mut tasks = Vec::new();
25615 for _ in 0..task_count {
25616 let completed = Arc::clone(&completed);
25617 let scheduler = Arc::clone(&executor);
25618 let (runnable, task) = async_task::spawn(
25619 async move {
25620 completed.fetch_add(1, Ordering::Relaxed);
25621 },
25622 move |r| scheduler.schedule(r),
25623 );
25624 executor.schedule(runnable);
25625 tasks.push(task);
25626 }
25627 for task in tasks {
25628 futures::executor::block_on(task);
25629 }
25630
25631 assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
25634 assert_eq!(
25635 metrics.runnable_polls.load(Ordering::Relaxed),
25636 task_count as u64
25637 );
25638
25639 let deadline = Instant::now() + Duration::from_secs(5);
25643 loop {
25644 let active = metrics.active_runnables.load(Ordering::Relaxed);
25645 let queued = metrics.queued_runnables.load(Ordering::Relaxed);
25646 if active == 0 && queued == 0 {
25647 break;
25648 }
25649 assert!(
25650 Instant::now() < deadline,
25651 "runnable counters did not settle to 0: active={active} queued={queued}"
25652 );
25653 std::thread::yield_now();
25654 }
25655 let _ = done_tx.send(());
25656 });
25657
25658 match done_rx.recv_timeout(Duration::from_secs(15)) {
25659 Ok(()) => runner.join().unwrap(),
25660 Err(error) => panic!("drain-budget test did not complete ({error})"),
25661 }
25662 }
25663
25664 #[cfg(not(target_family = "wasm"))]
25665 #[test]
25666 fn multi_thread_spawned_panic_surfaces_as_join_error() {
25667 use std::sync::mpsc;
25674 use std::time::Duration;
25675
25676 let (done_tx, done_rx) = mpsc::channel();
25677 let runner = std::thread::spawn(move || {
25678 let metrics = Arc::new(RuntimeMetrics::default());
25679 let options = RuntimeOptions {
25680 flavor: RuntimeFlavor::MultiThread,
25681 worker_threads: 2,
25682 max_blocking_tasks: 2,
25683 thread_name_prefix: "rd9-panic".to_string(),
25684 park_deadline: None,
25685 };
25686 let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
25687
25688 let scheduler = Arc::clone(&executor);
25689 let wrapped = async {
25690 AssertUnwindSafe(async { panic!("x") })
25691 .catch_unwind()
25692 .await
25693 .map(|()| ContainedTaskOutput::new((), u64::MAX))
25694 .map_err(panic_payload_to_join_error)
25695 };
25696 let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
25697 executor.schedule(runnable);
25698
25699 let result: Result<(), JoinError> =
25700 futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
25701 task: task.fallible(),
25702 awaiter: None,
25703 dependency: Arc::new(TaskDependency::default()),
25704 }));
25705 let _ = done_tx.send(result);
25706 });
25707
25708 let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
25709 Ok(result) => {
25710 runner.join().unwrap();
25711 result
25712 }
25713 Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
25714 };
25715 let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
25716 assert_eq!(error.to_string(), "x");
25717 }
25718
25719 #[test]
25720 fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
25721 use std::sync::mpsc;
25722
25723 let metrics = Arc::new(RuntimeMetrics::default());
25724 metrics.tasks_spawned.store(1, Ordering::Relaxed);
25725 metrics.tasks_completed.store(2, Ordering::Relaxed);
25726 metrics.tasks_panicked.store(3, Ordering::Relaxed);
25727 metrics.runnable_schedules.store(4, Ordering::Relaxed);
25728 metrics.runnable_polls.store(5, Ordering::Relaxed);
25729 metrics
25730 .blocking_tasks_scheduled
25731 .store(14, Ordering::Relaxed);
25732 metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
25733 metrics
25734 .blocking_tasks_completed
25735 .store(11, Ordering::Relaxed);
25736 metrics.runnable_scheduled();
25737 let (guards_live_tx, guards_live_rx) = mpsc::channel();
25738 let (release_tx, release_rx) = mpsc::channel();
25739 let guard_metrics = Arc::clone(&metrics);
25740 let guard_thread = std::thread::spawn(move || {
25741 let runnable_guard = guard_metrics.runnable_started();
25742 let blocking_guard = guard_metrics.blocking_started(true);
25743 guards_live_tx.send(()).unwrap();
25744 release_rx.recv().unwrap();
25745 drop(runnable_guard);
25746 drop(blocking_guard);
25747 });
25748 guards_live_rx.recv().unwrap();
25749 let fingerprint_before_reset = metrics.progress_fingerprint();
25750
25751 metrics.reset();
25752
25753 assert!(
25754 metrics.progress_fingerprint() != fingerprint_before_reset,
25755 "a reset must advance the deadlock detector generation even if counters later repeat"
25756 );
25757 assert_eq!(
25758 metrics.tasks_spawned.load(Ordering::Relaxed),
25759 0,
25760 "tasks_spawned"
25761 );
25762 assert_eq!(
25763 metrics.tasks_completed.load(Ordering::Relaxed),
25764 0,
25765 "tasks_completed"
25766 );
25767 assert_eq!(
25768 metrics.tasks_panicked.load(Ordering::Relaxed),
25769 0,
25770 "tasks_panicked"
25771 );
25772 assert_eq!(
25773 metrics.runnable_schedules.load(Ordering::Relaxed),
25774 0,
25775 "runnable_schedules"
25776 );
25777 assert_eq!(
25778 metrics.runnable_polls.load(Ordering::Relaxed),
25779 0,
25780 "runnable_polls"
25781 );
25782 assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
25783 assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
25784 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
25785 assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
25786 assert_eq!(
25787 metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
25788 0,
25789 "blocking_tasks_scheduled"
25790 );
25791 assert_eq!(
25792 metrics.blocking_tasks_started.load(Ordering::Relaxed),
25793 0,
25794 "blocking_tasks_started"
25795 );
25796 assert_eq!(
25797 metrics.blocking_tasks_completed.load(Ordering::Relaxed),
25798 0,
25799 "blocking_tasks_completed"
25800 );
25801 assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
25802 assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
25803
25804 release_tx.send(()).unwrap();
25805 guard_thread.join().unwrap();
25806 assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
25807 assert_eq!(
25808 metrics.active_blocking_tasks.load(Ordering::Relaxed),
25809 0,
25810 "a live blocking guard completed after reset without unsigned underflow"
25811 );
25812 assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25813 }
25814
25815 #[test]
25816 fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
25817 use std::sync::mpsc;
25818
25819 fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
25820 RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
25821 *slot.borrow_mut() = Some(Box::new(move || {
25822 entered.send(()).unwrap();
25823 release.recv().unwrap();
25824 }));
25825 });
25826 }
25827
25828 let options = RuntimeOptions {
25829 flavor: RuntimeFlavor::CurrentThread,
25830 worker_threads: 1,
25831 max_blocking_tasks: 1,
25832 thread_name_prefix: "metrics-snapshot".to_string(),
25833 park_deadline: None,
25834 };
25835
25836 let queued_metrics = Arc::new(RuntimeMetrics::default());
25837 let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
25838 let (queued_release_tx, queued_release_rx) = mpsc::channel();
25839 let queued_writer_metrics = Arc::clone(&queued_metrics);
25840 let queued_writer = std::thread::spawn(move || {
25841 pause_next_high_water_update(queued_entered_tx, queued_release_rx);
25842 queued_writer_metrics.runnable_scheduled();
25843 });
25844 queued_entered_rx.recv().unwrap();
25845 assert_eq!(
25846 queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
25847 0
25848 );
25849 let snapshot = queued_metrics.snapshot(&options);
25850 assert_eq!(snapshot.queued_runnables, 1);
25851 assert_eq!(snapshot.max_queued_runnables, 1);
25852 queued_release_tx.send(()).unwrap();
25853 queued_writer.join().unwrap();
25854 queued_metrics.runnable_cancelled();
25855
25856 let active_metrics = Arc::new(RuntimeMetrics::default());
25857 active_metrics.queued_runnables.store(1, Ordering::Relaxed);
25858 let (active_entered_tx, active_entered_rx) = mpsc::channel();
25859 let (active_release_tx, active_release_rx) = mpsc::channel();
25860 let active_writer_metrics = Arc::clone(&active_metrics);
25861 let active_writer = std::thread::spawn(move || {
25862 pause_next_high_water_update(active_entered_tx, active_release_rx);
25863 let _active = active_writer_metrics.runnable_started();
25864 });
25865 active_entered_rx.recv().unwrap();
25866 assert_eq!(
25867 active_metrics.max_active_runnables.load(Ordering::Relaxed),
25868 0
25869 );
25870 let snapshot = active_metrics.snapshot(&options);
25871 assert_eq!(snapshot.active_runnables, 1);
25872 assert_eq!(snapshot.max_active_runnables, 1);
25873 active_release_tx.send(()).unwrap();
25874 active_writer.join().unwrap();
25875
25876 let blocking_metrics = Arc::new(RuntimeMetrics::default());
25877 let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
25878 let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
25879 let blocking_writer_metrics = Arc::clone(&blocking_metrics);
25880 let blocking_writer = std::thread::spawn(move || {
25881 pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
25882 let _active = blocking_writer_metrics.blocking_started(true);
25883 });
25884 blocking_entered_rx.recv().unwrap();
25885 assert_eq!(
25886 blocking_metrics
25887 .max_active_blocking_tasks
25888 .load(Ordering::Relaxed),
25889 0
25890 );
25891 let snapshot = blocking_metrics.snapshot(&options);
25892 assert_eq!(snapshot.active_blocking_tasks, 1);
25893 assert_eq!(snapshot.max_active_blocking_tasks, 1);
25894 blocking_release_tx.send(()).unwrap();
25895 blocking_writer.join().unwrap();
25896 }
25897
25898 #[test]
25899 fn runtime_metrics_reset_fails_before_generation_wraparound() {
25900 let metrics = RuntimeMetrics::default();
25901 metrics
25902 .reset_generation
25903 .store(u64::MAX - 1, Ordering::SeqCst);
25904 metrics.tasks_spawned.store(7, Ordering::Relaxed);
25905
25906 let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
25907 assert!(exhausted.is_err());
25908 assert!(!metrics.reset_lock.is_poisoned());
25909 assert_eq!(
25910 metrics.reset_generation.load(Ordering::SeqCst),
25911 u64::MAX - 1
25912 );
25913 assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
25914 }
25915
25916 struct NeverReady;
25928 impl Future for NeverReady {
25929 type Output = ();
25930 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25931 Poll::Pending
25932 }
25933 }
25934
25935 #[cfg(not(target_family = "wasm"))]
25936 struct ReadyAfterOwnerLanePublication {
25937 dependency: BlockingDependency,
25938 ready: Arc<AtomicBool>,
25939 }
25940
25941 #[cfg(not(target_family = "wasm"))]
25942 impl Future for ReadyAfterOwnerLanePublication {
25943 type Output = ();
25944
25945 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25946 if self.ready.load(Ordering::SeqCst) {
25947 Poll::Ready(())
25948 } else {
25949 record_blocking_dependency(self.dependency);
25950 Poll::Pending
25951 }
25952 }
25953 }
25954
25955 #[test]
25956 fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
25957 use std::sync::mpsc;
25964
25965 let (done_tx, done_rx) = mpsc::channel();
25966 let runner = std::thread::spawn(move || {
25967 let metrics = Arc::new(RuntimeMetrics::default());
25968 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
25971
25972 let payload = catch_unwind(AssertUnwindSafe(|| {
25973 let mut future = std::pin::pin!(NeverReady);
25974 executor.block_on(future.as_mut());
25975 }))
25976 .expect_err("a threadless park with an empty queue and no pending wake must panic");
25977 let diagnostic = payload
25978 .downcast_ref::<BlockOnDeadlock>()
25979 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
25980 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
25981 assert_eq!(
25982 diagnostic.park_deadline, None,
25983 "the certain case carries no deadline"
25984 );
25985 let message = diagnostic.to_string();
25986 assert!(
25987 message.contains("block_on") && message.contains("JS"),
25988 "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
25989 );
25990 let _ = done_tx.send(());
25991 });
25992
25993 match done_rx.recv_timeout(Duration::from_secs(10)) {
25994 Ok(()) => runner.join().unwrap(),
25995 Err(error) => panic!(
25996 "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
25997 ),
25998 }
25999 }
26000
26001 #[test]
26002 fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
26003 use std::sync::mpsc;
26009
26010 struct PendThenReady {
26011 polls: usize,
26012 }
26013 impl Future for PendThenReady {
26014 type Output = ();
26015 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
26016 self.polls += 1;
26017 if self.polls >= 2 {
26018 Poll::Ready(())
26019 } else {
26020 cx.waker().wake_by_ref();
26021 Poll::Pending
26022 }
26023 }
26024 }
26025
26026 let (done_tx, done_rx) = mpsc::channel();
26027 let runner = std::thread::spawn(move || {
26028 let metrics = Arc::new(RuntimeMetrics::default());
26029 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
26030 let mut future = std::pin::pin!(PendThenReady { polls: 0 });
26031 executor.block_on(future.as_mut());
26032 assert_eq!(
26033 future.polls, 2,
26034 "the self-woken future must have been re-polled to completion"
26035 );
26036 let _ = done_tx.send(());
26037 });
26038
26039 match done_rx.recv_timeout(Duration::from_secs(10)) {
26040 Ok(()) => runner.join().unwrap(),
26041 Err(error) => panic!(
26042 "a self-waking Pending future must complete without panicking on a threadless build ({error})"
26043 ),
26044 }
26045 }
26046
26047 #[cfg(not(target_family = "wasm"))]
26048 #[test]
26049 fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
26050 use std::sync::mpsc;
26055
26056 let (done_tx, done_rx) = mpsc::channel();
26057 let runner = std::thread::spawn(move || {
26058 let metrics = Arc::new(RuntimeMetrics::default());
26059 let executor = Arc::new(CurrentThreadExecutor::with_detection(
26060 metrics,
26061 false,
26062 Some(Duration::from_millis(50)),
26063 ));
26064
26065 let payload = catch_unwind(AssertUnwindSafe(|| {
26066 let mut future = std::pin::pin!(NeverReady);
26067 executor.block_on(future.as_mut());
26068 }))
26069 .expect_err("a dead native CT park must fire the armed deadline");
26070 let diagnostic = payload
26071 .downcast_ref::<BlockOnDeadlock>()
26072 .expect("typed payload");
26073 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
26074 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
26075 drop(executor);
26078 let _ = done_tx.send(());
26079 });
26080
26081 match done_rx.recv_timeout(Duration::from_secs(10)) {
26082 Ok(()) => join_within(
26083 "the CT deadline-fire runner exits after teardown",
26084 runner,
26085 Duration::from_secs(30),
26086 ),
26087 Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
26088 }
26089 }
26090
26091 #[cfg(not(target_family = "wasm"))]
26092 #[test]
26093 fn current_thread_native_park_deadline_resets_on_runtime_progress() {
26094 use std::sync::mpsc;
26111
26112 let deadline = Duration::from_millis(1000);
26113 let metrics = Arc::new(RuntimeMetrics::default());
26114 let executor = Arc::new(CurrentThreadExecutor::with_detection(
26115 Arc::clone(&metrics),
26116 false,
26117 Some(deadline),
26118 ));
26119
26120 let (value_tx, value_rx) = oneshot::channel::<usize>();
26121 let (done_tx, done_rx) = mpsc::channel();
26122 let runner = std::thread::spawn(move || {
26123 let mut output = None;
26124 {
26125 let mut future = std::pin::pin!(async {
26126 output = Some(value_rx.await.unwrap());
26127 });
26128 executor.block_on(future.as_mut());
26129 }
26130 assert_eq!(output, Some(9));
26131 drop(executor);
26133 let _ = done_tx.send(());
26134 });
26135
26136 let hold_until = Instant::now() + deadline * 5 / 2;
26142 while Instant::now() < hold_until {
26143 std::thread::sleep(Duration::from_millis(25));
26144 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
26145 }
26146 value_tx.send(9).unwrap();
26147
26148 match done_rx.recv_timeout(Duration::from_secs(30)) {
26149 Ok(()) => join_within(
26150 "the CT deadline-reset runner exits after teardown",
26151 runner,
26152 Duration::from_secs(30),
26153 ),
26154 Err(error) => panic!(
26155 "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
26156 ),
26157 }
26158 }
26159
26160 #[cfg(not(target_family = "wasm"))]
26161 #[test]
26162 fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
26163 use std::sync::mpsc;
26168
26169 let (done_tx, done_rx) = mpsc::channel();
26170 let runner = std::thread::spawn(move || {
26171 let options = RuntimeOptions {
26172 flavor: RuntimeFlavor::MultiThread,
26173 worker_threads: 1,
26174 max_blocking_tasks: 1,
26175 thread_name_prefix: "deadline-fire".to_string(),
26176 park_deadline: Some(Duration::from_millis(50)),
26177 };
26178 let executor =
26179 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26180
26181 let payload = {
26182 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26183 catch_unwind(AssertUnwindSafe(|| {
26184 let mut future = std::pin::pin!(NeverReady);
26185 executor.block_on(future.as_mut());
26186 }))
26187 .expect_err("a dead cooperative park must fire the armed deadline")
26188 };
26189 let diagnostic = payload
26190 .downcast_ref::<BlockOnDeadlock>()
26191 .expect("typed payload");
26192 assert_eq!(
26193 diagnostic.kind,
26194 BlockOnDeadlockKind::MultiThreadCooperativeDeadline
26195 );
26196 assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
26197 let message = diagnostic.to_string();
26198 assert!(
26199 message.contains("block_on"),
26200 "diagnostic must name the hazard, got: {message}"
26201 );
26202 assert_eq!(
26203 executor.parked_drivers.count.load(Ordering::SeqCst),
26204 0,
26205 "the firing driver must deregister itself before panicking"
26206 );
26207 drop(executor);
26209 let _ = done_tx.send(());
26210 });
26211
26212 match done_rx.recv_timeout(Duration::from_secs(10)) {
26213 Ok(()) => join_within(
26214 "the MT deadline-fire runner exits after executor teardown",
26215 runner,
26216 Duration::from_secs(30),
26217 ),
26218 Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
26219 }
26220 }
26221
26222 #[cfg(not(target_family = "wasm"))]
26223 #[test]
26224 fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
26225 use std::sync::mpsc;
26241
26242 let (done_tx, done_rx) = mpsc::channel();
26243 let runner = std::thread::spawn(move || {
26244 let deadline = Duration::from_millis(1000);
26245 let metrics = Arc::new(RuntimeMetrics::default());
26246 let options = RuntimeOptions {
26247 flavor: RuntimeFlavor::MultiThread,
26248 worker_threads: 1,
26249 max_blocking_tasks: 1,
26250 thread_name_prefix: "deadline-reset".to_string(),
26251 park_deadline: Some(deadline),
26252 };
26253 let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26254
26255 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
26258 let td_exec = Arc::clone(&executor);
26259 let completed = Arc::new(AtomicBool::new(false));
26260 let td_flag = Arc::clone(&completed);
26261 let td_body = async move {
26262 let mut inner = std::pin::pin!(async move {
26263 assert_eq!(gate_rx.await.unwrap(), 9);
26264 });
26265 td_exec.block_on(inner.as_mut());
26266 td_flag.store(true, Ordering::SeqCst);
26267 };
26268 let sched = Arc::clone(&executor);
26269 let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
26270 executor.schedule(td_runnable);
26271 wait_until("the sole driver parked", || {
26272 executor.parked_drivers.count.load(Ordering::SeqCst) == 1
26273 });
26274
26275 let hold_until = Instant::now() + deadline * 5 / 2;
26281 while Instant::now() < hold_until {
26282 std::thread::sleep(Duration::from_millis(25));
26283 metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
26284 }
26285 assert!(
26286 !completed.load(Ordering::SeqCst),
26287 "the driver must still be parked awaiting its late wake"
26288 );
26289 gate_tx.send(9).unwrap();
26290 futures::executor::block_on(td_task);
26291 assert!(
26292 completed.load(Ordering::SeqCst),
26293 "the late wake must complete the parked driver"
26294 );
26295 drop(executor);
26297 let _ = done_tx.send(());
26298 });
26299
26300 match done_rx.recv_timeout(Duration::from_secs(30)) {
26301 Ok(()) => join_within(
26302 "the MT deadline-reset runner exits after executor teardown",
26303 runner,
26304 Duration::from_secs(30),
26305 ),
26306 Err(error) => panic!(
26307 "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
26308 ),
26309 }
26310 }
26311
26312 #[cfg(not(target_family = "wasm"))]
26313 #[test]
26314 fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
26315 use std::sync::mpsc;
26320
26321 let (done_tx, done_rx) = mpsc::channel();
26322 let runner = std::thread::spawn(move || {
26323 let options = RuntimeOptions {
26324 flavor: RuntimeFlavor::MultiThread,
26325 worker_threads: 2,
26326 max_blocking_tasks: 2,
26327 thread_name_prefix: "foreign-exempt".to_string(),
26328 park_deadline: Some(Duration::from_millis(40)),
26329 };
26330 let executor =
26331 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26332
26333 let (value_tx, value_rx) = oneshot::channel::<usize>();
26334 let waker_thread = std::thread::spawn(move || {
26335 std::thread::sleep(Duration::from_millis(300));
26337 let _ = value_tx.send(7);
26338 });
26339
26340 let started = Instant::now();
26341 let mut output = None;
26342 {
26343 let mut future = std::pin::pin!(async {
26344 output = Some(value_rx.await.unwrap());
26345 });
26346 executor.block_on(future.as_mut());
26348 }
26349 assert_eq!(output, Some(7));
26350 assert!(
26351 started.elapsed() >= Duration::from_millis(250),
26352 "the park must genuinely have outlived the 40ms deadline many times over"
26353 );
26354 waker_thread.join().unwrap();
26355 drop(executor);
26360 let _ = done_tx.send(());
26361 });
26362
26363 match done_rx.recv_timeout(Duration::from_secs(10)) {
26364 Ok(()) => join_within(
26365 "the exempt foreign-park runner exits after executor teardown",
26366 runner,
26367 Duration::from_secs(30),
26368 ),
26369 Err(error) => {
26370 panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
26371 }
26372 }
26373 }
26374
26375 #[cfg(not(target_family = "wasm"))]
26376 #[test]
26377 fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
26378 use std::sync::mpsc;
26393
26394 let (done_tx, done_rx) = mpsc::channel();
26395 let runner = std::thread::spawn(move || {
26396 let options = RuntimeOptions {
26397 flavor: RuntimeFlavor::MultiThread,
26398 worker_threads: 1,
26399 max_blocking_tasks: 1,
26400 thread_name_prefix: "edge-wake".to_string(),
26401 park_deadline: Some(Duration::from_millis(40)),
26402 };
26403 let executor =
26404 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26405
26406 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
26408 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
26410
26411 let hook_exec = Arc::clone(&executor);
26412 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
26413 *slot.borrow_mut() = Some(Box::new(move || {
26414 let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
26415 let handle = hook_exec.schedule_blocking(move || {
26419 let _ = gate_tx.send(5usize);
26420 0usize
26421 });
26422 drop(handle); let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
26424 hook_saw_tx.send((before, after)).unwrap();
26425 }));
26426 });
26427
26428 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26431 let mut output = None;
26432 {
26433 let mut future = std::pin::pin!(async {
26434 output = Some(gate_rx.await.unwrap());
26435 });
26436 executor.block_on(future.as_mut());
26437 }
26438 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
26443 assert_eq!(
26444 output,
26445 Some(5),
26446 "the edge-delivered wake's job must have run and completed the future"
26447 );
26448
26449 let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
26450 assert_eq!(
26451 before, 1,
26452 "the hook must observe the parker STILL registered (race window)"
26453 );
26454 assert_eq!(
26455 after, 0,
26456 "the racing wake_one must have popped the parker (wake DELIVERED)"
26457 );
26458 drop(executor);
26460 let _ = done_tx.send(());
26461 });
26462
26463 match done_rx.recv_timeout(Duration::from_secs(10)) {
26464 Ok(()) => join_within(
26465 "the expiry-edge-wake runner exits after executor teardown",
26466 runner,
26467 Duration::from_secs(30),
26468 ),
26469 Err(error) => panic!(
26470 "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
26471 ),
26472 }
26473 }
26474
26475 #[cfg(not(target_family = "wasm"))]
26476 #[test]
26477 fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
26478 use std::sync::mpsc;
26490
26491 let (done_tx, done_rx) = mpsc::channel();
26492 let runner = std::thread::spawn(move || {
26493 let options = RuntimeOptions {
26494 flavor: RuntimeFlavor::MultiThread,
26495 worker_threads: 1,
26496 max_blocking_tasks: 1,
26497 thread_name_prefix: "verdict-blocking".to_string(),
26498 park_deadline: Some(Duration::from_millis(40)),
26499 };
26500 let executor =
26501 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26502 executor
26503 .active_drainers
26504 .store(executor.max_drainers, Ordering::SeqCst);
26505
26506 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
26508 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
26511
26512 let hook_exec = Arc::clone(&executor);
26513 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
26514 *slot.borrow_mut() = Some(Box::new(move || {
26515 let handle = hook_exec.schedule_blocking(move || {
26516 let _ = gate_tx.send(11usize);
26517 0usize
26518 });
26519 drop(handle); hook_saw_tx
26521 .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
26522 .unwrap();
26523 }));
26524 });
26525
26526 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26529 let mut output = None;
26530 {
26531 let mut future = std::pin::pin!(async {
26532 output = Some(gate_rx.await.unwrap());
26533 });
26534 executor.block_on(future.as_mut());
26535 }
26536 assert_eq!(
26537 output,
26538 Some(11),
26539 "the enqueued job must have been run by the surviving driver via run_one"
26540 );
26541
26542 let registered_at_hook = hook_saw_rx
26543 .recv()
26544 .expect("the verdict hook must have fired");
26545 assert_eq!(
26546 registered_at_hook, 0,
26547 "the hook fires post-deregister: no permit can be delivered, only the fingerprint's enqueue counter can catch this submission"
26548 );
26549 executor.active_drainers.store(0, Ordering::SeqCst);
26550 let _ = done_tx.send(());
26551 });
26552
26553 match done_rx.recv_timeout(Duration::from_secs(10)) {
26554 Ok(()) => runner.join().unwrap(),
26555 Err(error) => panic!(
26556 "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
26557 ),
26558 }
26559 }
26560
26561 #[cfg(not(target_family = "wasm"))]
26562 #[test]
26563 fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
26564 use std::sync::mpsc;
26570
26571 struct ReadyAfterPublication(Arc<AtomicBool>);
26572
26573 impl Future for ReadyAfterPublication {
26574 type Output = ();
26575
26576 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26577 if self.0.load(Ordering::SeqCst) {
26578 Poll::Ready(())
26579 } else {
26580 Poll::Pending
26581 }
26582 }
26583 }
26584
26585 let (done_tx, done_rx) = mpsc::channel();
26586 let runner = std::thread::spawn(move || {
26587 let options = RuntimeOptions {
26588 flavor: RuntimeFlavor::MultiThread,
26589 worker_threads: 1,
26590 max_blocking_tasks: 1,
26591 thread_name_prefix: "final-verdict-publication".to_string(),
26592 park_deadline: Some(Duration::from_millis(40)),
26593 };
26594 let executor =
26595 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26596
26597 let ready = Arc::new(AtomicBool::new(false));
26598 let baseline_fingerprint = executor.metrics.progress_fingerprint();
26599 let baseline_publications = executor.deadlock_state.publications();
26600 let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
26601 let hook_exec = Arc::clone(&executor);
26602 let hook_ready = Arc::clone(&ready);
26603 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
26604 *slot.borrow_mut() = Some(Box::new(move || {
26605 let publication_executor = Arc::clone(&hook_exec);
26606 std::thread::spawn(move || {
26607 let mut admission = publication_executor
26608 .begin_deadlock_admission()
26609 .expect("the deadline detector must enable admission accounting");
26610 hook_ready.store(true, Ordering::SeqCst);
26611 admission.mark_published();
26612 })
26613 .join()
26614 .unwrap();
26615 hook_saw_tx
26616 .send((
26617 hook_exec.deadlock_state.publications(),
26618 !hook_exec.has_queued_runnable(),
26619 hook_exec
26620 .blocking_queue
26621 .lock()
26622 .unwrap_or_else(std::sync::PoisonError::into_inner)
26623 .is_empty(),
26624 hook_exec.next_timer_deadline().is_none(),
26625 hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
26626 hook_exec.parked_drivers.count.load(Ordering::SeqCst),
26627 ))
26628 .unwrap();
26629 }));
26630 });
26631
26632 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26633 let mut future = std::pin::pin!(ReadyAfterPublication(ready));
26634 assert_eq!(
26635 executor.block_on(future.as_mut()),
26636 BlockOnOutcome::Completed,
26637 "the publication epoch must veto the first verdict and permit the next poll"
26638 );
26639 let (
26640 publications,
26641 runnable_queue_empty,
26642 blocking_queue_empty,
26643 timer_heap_empty,
26644 fingerprint_unchanged,
26645 parked_drivers,
26646 ) = hook_saw_rx
26647 .recv()
26648 .expect("the final-verdict hook must have fired");
26649 assert_eq!(
26650 publications,
26651 baseline_publications
26652 .checked_add(1)
26653 .expect("test publication epoch must not overflow"),
26654 "the cross-thread admission must publish exactly one epoch"
26655 );
26656 assert!(
26657 runnable_queue_empty,
26658 "the publication test must not queue a runnable"
26659 );
26660 assert!(
26661 blocking_queue_empty,
26662 "the publication test must not queue blocking work"
26663 );
26664 assert!(
26665 timer_heap_empty,
26666 "the publication test must not register a timer"
26667 );
26668 assert!(
26669 fingerprint_unchanged,
26670 "ordinary metrics progress must not veto the publication-only verdict"
26671 );
26672 assert_eq!(
26673 parked_drivers, 0,
26674 "the publication must land after the cooperative parker was deregistered"
26675 );
26676 let _ = done_tx.send(());
26677 });
26678
26679 match done_rx.recv_timeout(Duration::from_secs(10)) {
26680 Ok(()) => runner.join().unwrap(),
26681 Err(error) => panic!(
26682 "a publication-only cross-thread admission was not observed by the final verdict ({error})"
26683 ),
26684 }
26685 }
26686
26687 #[cfg(not(target_family = "wasm"))]
26688 #[test]
26689 fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
26690 use std::sync::mpsc;
26698
26699 let executor = multi_thread_executor(
26700 1,
26701 Some(Duration::from_millis(40)),
26702 "deadline-selected-handoff",
26703 );
26704 let owner = executor.fresh_owner_token();
26705 executor.active_blocking_owners.register(owner);
26706 executor
26707 .active_blocking
26708 .store(executor.max_blocking, Ordering::SeqCst);
26709
26710 let ready = Arc::new(AtomicBool::new(false));
26711 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
26712 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
26713 let (selected_tx, selected_rx) = mpsc::channel();
26714 let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
26715 let (released_tx, released_rx) = mpsc::channel();
26716 let (renewed_tx, renewed_rx) = mpsc::channel();
26717 let release_executor = Arc::clone(&executor);
26718 let renewal_executor = Arc::clone(&executor);
26719 let release_ready = Arc::clone(&ready);
26720 let releaser = std::thread::spawn(move || {
26721 let reservation =
26722 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
26723 .expect("the synthetic exact-owner lane must start reserved");
26724 reservation_ready_tx.send(()).unwrap();
26725 OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
26726 *slot.borrow_mut() = Some(Box::new(move || {
26727 selected_tx.send(()).unwrap();
26728 allow_unpark_rx.recv().unwrap();
26729 let renewed = renewal_executor
26730 .active_blocking_owners
26731 .reserve(owner)
26732 .expect("the released exact-owner lane must be reservable before unpark");
26733 release_ready.store(true, Ordering::SeqCst);
26734 renewed_tx.send(renewed).unwrap();
26735 }));
26736 });
26737 release_reservation_rx.recv().unwrap();
26738 released_tx
26739 .send(release_executor.release_owner_lane_and_wake(reservation))
26740 .unwrap();
26741 });
26742 reservation_ready_rx
26743 .recv_timeout(Duration::from_secs(2))
26744 .expect("the exact-owner reservation must be established before the park");
26745
26746 let baseline_fingerprint = executor.metrics.progress_fingerprint();
26747 let baseline_publications = executor.deadlock_state.publications();
26748 let expiry_executor = Arc::clone(&executor);
26749 DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
26750 *slot.borrow_mut() = Some(Box::new(move || {
26751 release_reservation_tx.send(()).unwrap();
26752 selected_rx
26753 .recv_timeout(Duration::from_secs(2))
26754 .expect("reservation release must select the still-registered exact-owner driver");
26755 assert_eq!(
26756 expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
26757 0,
26758 "handoff selection must remove the driver before pausing ahead of unpark"
26759 );
26760 }));
26761 });
26762
26763 let verdict_executor = Arc::clone(&executor);
26764 let verdict_ready = Arc::clone(&ready);
26765 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
26766 *slot.borrow_mut() = Some(Box::new(move || {
26767 assert!(
26768 verdict_executor.active_blocking_owners.is_available(owner),
26769 "reservation release must publish lane availability before handoff selection"
26770 );
26771 assert!(!verdict_executor.has_queued_runnable());
26772 assert!(
26773 verdict_executor
26774 .blocking_queue
26775 .lock()
26776 .unwrap_or_else(std::sync::PoisonError::into_inner)
26777 .is_empty()
26778 );
26779 assert!(verdict_executor.next_timer_deadline().is_none());
26780 assert!(
26781 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
26782 "ordinary metrics progress must not save the selected-handoff verdict"
26783 );
26784 assert_eq!(
26785 verdict_executor.deadlock_state.publications(),
26786 baseline_publications
26787 );
26788 assert!(
26789 !verdict_ready.load(Ordering::SeqCst),
26790 "the selected handoff must still be paused before unpark at the gated verdict"
26791 );
26792 allow_unpark_tx.send(()).unwrap();
26793 }));
26794 });
26795 let exact_owner_executor = Arc::clone(&executor);
26796 DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
26797 *slot.borrow_mut() = Some(Box::new(move |parker| {
26798 assert!(
26799 parker.consume_permit(),
26800 "the selected handoff must unpark before exact-owner final predicates run"
26801 );
26802 assert!(
26803 parker.has_owner_handoff(),
26804 "consuming the ordinary permit must leave the exact-owner handoff pending"
26805 );
26806 assert!(
26807 !exact_owner_executor
26808 .active_blocking_owners
26809 .is_available(owner),
26810 "the renewed reservation must prevent lane availability from saving the verdict"
26811 );
26812 }));
26813 });
26814
26815 {
26816 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26817 let _owner = BlockingOwnerGuard::enter(Some(owner));
26818 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
26819 dependency: BlockingDependency {
26820 job: BlockingJobId(u64::MAX - 80),
26821 owner: Some(owner)
26822 },
26823 ready: Arc::clone(&ready),
26824 });
26825 assert_eq!(
26826 executor.block_on(future.as_mut()),
26827 BlockOnOutcome::Completed
26828 );
26829 }
26830 assert!(
26831 released_rx
26832 .recv_timeout(Duration::from_secs(2))
26833 .expect("the selected handoff publication must finish"),
26834 "reservation release must have selected the cooperative driver"
26835 );
26836 let renewed = renewed_rx
26837 .recv_timeout(Duration::from_secs(2))
26838 .expect("the selected handoff must renew the owner reservation before unpark");
26839 releaser.join().unwrap();
26840
26841 executor.active_blocking_owners.release(owner, renewed);
26842 executor.active_blocking.store(0, Ordering::SeqCst);
26843 executor.active_blocking_owners.unregister(owner);
26844 executor.begin_shutdown();
26845 executor.wait_until_scheduler_idle();
26846 }
26847
26848 #[cfg(not(target_family = "wasm"))]
26849 #[test]
26850 fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
26851 use std::sync::mpsc;
26856
26857 let executor = multi_thread_executor(
26858 1,
26859 Some(Duration::from_millis(40)),
26860 "deadline-owner-no-parker",
26861 );
26862 let owner = executor.fresh_owner_token();
26863 executor.active_blocking_owners.register(owner);
26864 executor
26865 .active_blocking
26866 .store(executor.max_blocking, Ordering::SeqCst);
26867
26868 let ready = Arc::new(AtomicBool::new(false));
26869 let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
26870 let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
26871 let (released_tx, released_rx) = mpsc::channel();
26872 let release_executor = Arc::clone(&executor);
26873 let release_ready = Arc::clone(&ready);
26874 let releaser = std::thread::spawn(move || {
26875 let reservation =
26876 OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
26877 .expect("the synthetic exact-owner lane must start reserved");
26878 reservation_ready_tx.send(()).unwrap();
26879 release_reservation_rx.recv().unwrap();
26880 let woke = release_executor.release_owner_lane_and_wake(reservation);
26881 release_ready.store(true, Ordering::SeqCst);
26882 released_tx.send(woke).unwrap();
26883 });
26884 reservation_ready_rx
26885 .recv_timeout(Duration::from_secs(2))
26886 .expect("the exact-owner reservation must be established before the park");
26887
26888 let baseline_fingerprint = executor.metrics.progress_fingerprint();
26889 let baseline_publications = executor.deadlock_state.publications();
26890 let verdict_executor = Arc::clone(&executor);
26891 DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
26892 *slot.borrow_mut() = Some(Box::new(move || {
26893 assert_eq!(
26894 verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
26895 0,
26896 "the release must begin only after deadline deregistration"
26897 );
26898 release_reservation_tx.send(()).unwrap();
26899 assert!(
26900 !released_rx
26901 .recv_timeout(Duration::from_secs(2))
26902 .expect("the no-parker owner release must finish"),
26903 "no registered parker may receive the released owner lane"
26904 );
26905 assert!(verdict_executor.active_blocking_owners.is_available(owner));
26906 assert!(!verdict_executor.has_queued_runnable());
26907 assert!(
26908 verdict_executor
26909 .blocking_queue
26910 .lock()
26911 .unwrap_or_else(std::sync::PoisonError::into_inner)
26912 .is_empty()
26913 );
26914 assert!(verdict_executor.next_timer_deadline().is_none());
26915 assert!(
26916 verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
26917 "ordinary metrics progress must not save the no-parker release verdict"
26918 );
26919 assert_eq!(
26920 verdict_executor.deadlock_state.publications(),
26921 baseline_publications
26922 );
26923 }));
26924 });
26925
26926 {
26927 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
26928 let _owner = BlockingOwnerGuard::enter(Some(owner));
26929 let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
26930 dependency: BlockingDependency {
26931 job: BlockingJobId(u64::MAX - 81),
26932 owner: Some(owner)
26933 },
26934 ready: Arc::clone(&ready),
26935 });
26936 assert_eq!(
26937 executor.block_on(future.as_mut()),
26938 BlockOnOutcome::Completed
26939 );
26940 }
26941 releaser.join().unwrap();
26942
26943 executor.active_blocking.store(0, Ordering::SeqCst);
26944 executor.active_blocking_owners.unregister(owner);
26945 executor.begin_shutdown();
26946 executor.wait_until_scheduler_idle();
26947 }
26948
26949 #[cfg(not(target_family = "wasm"))]
26950 #[test]
26951 fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
26952 use std::sync::mpsc;
26966
26967 let controller = Arc::new(RuntimeController::new());
26968 controller
26969 .configure(RuntimeOptions {
26970 flavor: RuntimeFlavor::MultiThread,
26971 worker_threads: 2,
26972 max_blocking_tasks: 1,
26973 thread_name_prefix: "shutdown-gated-verdict".to_string(),
26974 park_deadline: Some(Duration::from_millis(40)),
26975 })
26976 .expect("the shutdown race test configuration must be accepted");
26977
26978 let backend = controller.backend();
26979 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26980 panic!("the configured backend must be MultiThread");
26981 };
26982 let executor_id = executor.id;
26983 let weak_executor = Arc::downgrade(executor);
26984 drop(backend);
26985
26986 let (task_polled_tx, task_polled_rx) = mpsc::channel();
26987 let task = controller
26988 .try_spawn(async move {
26989 task_polled_tx.send(()).unwrap();
26990 std::future::pending::<()>().await;
26991 })
26992 .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
26993 task_polled_rx
26994 .recv_timeout(Duration::from_secs(2))
26995 .expect("the accepted task must publish an abortable pending waker");
26996
26997 let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
26998 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
26999 let (driver_result_tx, driver_result_rx) = mpsc::channel();
27000 let driver_controller = Arc::clone(&controller);
27001 let driver = std::thread::spawn(move || {
27002 DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
27003 *slot.borrow_mut() = Some(Box::new(move || {
27004 gate_closed_tx.send(()).unwrap();
27005 release_verdict_rx.recv().unwrap();
27006 }));
27007 });
27008
27009 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
27010 let mut pending = std::pin::pin!(NeverReady);
27011 let result = catch_unwind(AssertUnwindSafe(|| {
27012 driver_controller.block_on(pending.as_mut())
27013 }));
27014 let result = match result {
27015 Ok(outcome) => Ok(outcome),
27016 Err(payload) => {
27017 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
27018 discard_panic_payload(payload);
27019 Err(was_deadlock)
27020 }
27021 };
27022 driver_result_tx.send(result).unwrap();
27023 });
27024
27025 gate_closed_rx
27026 .recv_timeout(Duration::from_secs(2))
27027 .expect("the cooperative driver must pause with the verdict gate closed");
27028
27029 let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
27030 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
27031 let shutdown_controller = Arc::clone(&controller);
27032 let shutdown = std::thread::spawn(move || {
27033 DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
27034 *slot.borrow_mut() = Some(Box::new(move || {
27035 abort_blocked_tx.send(()).unwrap();
27036 }));
27037 });
27038 shutdown_result_tx
27039 .send(shutdown_controller.shutdown())
27040 .unwrap();
27041 });
27042
27043 abort_blocked_rx
27044 .recv_timeout(Duration::from_secs(2))
27045 .expect("the accepted task's abort wake must reach the closed verdict gate");
27046 let lifecycle_is_stopping = {
27047 let state = controller
27048 .state
27049 .lock()
27050 .unwrap_or_else(std::sync::PoisonError::into_inner);
27051 matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
27052 };
27053 let stop_was_published = weak_executor
27054 .upgrade()
27055 .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
27056
27057 release_verdict_tx.send(()).unwrap();
27058
27059 let driver_result = driver_result_rx
27060 .recv_timeout(Duration::from_secs(2))
27061 .expect("the gated driver must resolve after observing shutdown");
27062 let shutdown_result = shutdown_result_rx
27063 .recv_timeout(Duration::from_secs(5))
27064 .expect("shutdown must finish after the verdict gate reopens");
27065 join_within(
27070 "the gated cooperative driver exits after teardown",
27071 driver,
27072 Duration::from_secs(30),
27073 );
27074 join_within(
27075 "the shutdown thread exits after teardown",
27076 shutdown,
27077 Duration::from_secs(30),
27078 );
27079
27080 assert!(
27081 lifecycle_is_stopping,
27082 "the abort wake must occur after Running -> Stopping"
27083 );
27084 assert!(
27085 stop_was_published,
27086 "generation stop must be visible before shutdown invokes accepted-task aborts"
27087 );
27088 assert_eq!(
27089 driver_result,
27090 Ok(BlockOnOutcome::Stopped),
27091 "shutdown must not be reported as a gated BlockOnDeadlock"
27092 );
27093 shutdown_result.unwrap();
27094 assert!(
27095 futures::executor::block_on(task).is_err(),
27096 "the accepted pending task must resolve as cancelled"
27097 );
27098 }
27099
27100 #[cfg(not(target_family = "wasm"))]
27101 #[test]
27102 fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
27103 use std::sync::mpsc;
27108
27109 let controller = Arc::new(RuntimeController::new());
27110 controller
27111 .configure(RuntimeOptions {
27112 flavor: RuntimeFlavor::MultiThread,
27113 worker_threads: 2,
27114 max_blocking_tasks: 1,
27115 thread_name_prefix: "verdict-before-shutdown".to_string(),
27116 park_deadline: Some(Duration::from_millis(40)),
27117 })
27118 .expect("the verdict-first test configuration must be accepted");
27119
27120 let backend = controller.backend();
27121 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27122 panic!("the configured backend must be MultiThread");
27123 };
27124 let executor_id = executor.id;
27125 drop(backend);
27126
27127 let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
27128 let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
27129 let (driver_result_tx, driver_result_rx) = mpsc::channel();
27130 let driver_controller = Arc::clone(&controller);
27131 let driver = std::thread::spawn(move || {
27132 DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
27133 *slot.borrow_mut() = Some(Box::new(move || {
27134 verdict_locked_tx.send(()).unwrap();
27135 release_verdict_rx.recv().unwrap();
27136 }));
27137 });
27138
27139 let _on_pool = OnPoolWorkerGuard::enter(executor_id);
27140 let mut pending = std::pin::pin!(NeverReady);
27141 let result = catch_unwind(AssertUnwindSafe(|| {
27142 driver_controller.block_on(pending.as_mut())
27143 }));
27144 let result = match result {
27145 Ok(outcome) => Ok(outcome),
27146 Err(payload) => {
27147 let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
27148 discard_panic_payload(payload);
27149 Err(was_deadlock)
27150 }
27151 };
27152 driver_result_tx.send(result).unwrap();
27153 });
27154
27155 verdict_locked_rx
27156 .recv_timeout(Duration::from_secs(2))
27157 .expect("the verdict must hold the synchronized stop-publication guard");
27158
27159 let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
27160 let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
27161 let shutdown_controller = Arc::clone(&controller);
27162 let shutdown = std::thread::spawn(move || {
27163 BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
27164 *slot.borrow_mut() = Some(Box::new(move || {
27165 shutdown_waiting_tx.send(()).unwrap();
27166 }));
27167 });
27168 shutdown_result_tx
27169 .send(shutdown_controller.shutdown())
27170 .unwrap();
27171 });
27172
27173 shutdown_waiting_rx
27174 .recv_timeout(Duration::from_secs(2))
27175 .expect("shutdown must reach the lifecycle-to-publication lock edge");
27176 let early_shutdown = shutdown_result_rx
27177 .recv_timeout(Duration::from_millis(100))
27178 .ok();
27179 let shutdown_was_blocked = early_shutdown.is_none();
27180 release_verdict_tx.send(()).unwrap();
27181
27182 let driver_result = driver_result_rx
27183 .recv_timeout(Duration::from_secs(2))
27184 .expect("the verdict-first driver must finish after release");
27185 let shutdown_result = early_shutdown.unwrap_or_else(|| {
27186 shutdown_result_rx
27187 .recv_timeout(Duration::from_secs(5))
27188 .expect("shutdown must finish after the verdict releases publication")
27189 });
27190 driver.join().unwrap();
27191 shutdown.join().unwrap();
27192
27193 assert!(
27194 shutdown_was_blocked,
27195 "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
27196 );
27197 assert_eq!(
27198 driver_result,
27199 Err(true),
27200 "a verdict that linearizes before shutdown must retain the typed deadlock"
27201 );
27202 shutdown_result.unwrap();
27203 }
27204
27205 #[cfg(not(target_family = "wasm"))]
27206 #[test]
27207 fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
27208 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
27209
27210 if std::env::var_os(CHILD_ENV).is_some() {
27211 let executor =
27212 multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
27213 executor
27216 .active_drainers
27217 .store(executor.max_drainers, Ordering::SeqCst);
27218
27219 let hook_returned = Arc::new(AtomicBool::new(false));
27220 let hook_flag = Arc::clone(&hook_returned);
27221 let hook_executor = Arc::clone(&executor);
27222 let previous_hook = std::panic::take_hook();
27223 std::panic::set_hook(Box::new(move |_| {
27224 let handle = hook_executor.schedule_blocking(|| ());
27225 drop(handle);
27226 hook_flag.store(true, Ordering::SeqCst);
27227 }));
27228
27229 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27230 let result = catch_unwind(AssertUnwindSafe(|| {
27231 let mut pending = std::pin::pin!(std::future::pending::<()>());
27232 executor.block_on(pending.as_mut());
27233 }));
27234 std::panic::set_hook(previous_hook);
27235
27236 let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
27237 assert!(
27238 payload.downcast_ref::<BlockOnDeadlock>().is_some(),
27239 "the deadline must retain its structured diagnostic"
27240 );
27241 assert!(
27242 hook_returned.load(Ordering::SeqCst),
27243 "the panic hook's scheduler submission must return before unwind"
27244 );
27245
27246 executor.active_drainers.store(0, Ordering::SeqCst);
27247 executor.begin_shutdown();
27248 executor.wait_until_scheduler_idle();
27249 return;
27250 }
27251
27252 let mut child = std::process::Command::new(std::env::current_exe().unwrap())
27253 .arg("--exact")
27254 .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
27255 .arg("--nocapture")
27256 .env(CHILD_ENV, "1")
27257 .stdout(std::process::Stdio::piped())
27258 .stderr(std::process::Stdio::piped())
27259 .spawn()
27260 .expect("the panic-hook subprocess must start");
27261 let deadline = Instant::now() + Duration::from_secs(5);
27262 let status = loop {
27263 if let Some(status) = child
27264 .try_wait()
27265 .expect("the subprocess status must be readable")
27266 {
27267 break status;
27268 }
27269 if Instant::now() >= deadline {
27270 let _ = child.kill();
27271 let output = child
27272 .wait_with_output()
27273 .expect("the timed-out subprocess must be reaped");
27274 panic!(
27275 "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
27276 String::from_utf8_lossy(&output.stdout),
27277 String::from_utf8_lossy(&output.stderr)
27278 );
27279 }
27280 std::thread::sleep(Duration::from_millis(10));
27281 };
27282 let output = child
27283 .wait_with_output()
27284 .expect("the completed subprocess output must be read");
27285 assert!(
27286 status.success(),
27287 "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
27288 status.code(),
27289 String::from_utf8_lossy(&output.stdout),
27290 String::from_utf8_lossy(&output.stderr)
27291 );
27292 }
27293
27294 #[test]
27305 fn contained_waker_separates_wake_and_drop_panics() {
27306 struct PanicWakeAndDrop;
27307
27308 impl std::task::Wake for PanicWakeAndDrop {
27309 fn wake(self: Arc<Self>) {
27310 panic!("intentional consuming wake panic");
27311 }
27312
27313 fn wake_by_ref(self: &Arc<Self>) {
27314 panic!("intentional borrowed wake panic");
27315 }
27316 }
27317
27318 impl Drop for PanicWakeAndDrop {
27319 fn drop(&mut self) {
27320 panic!("intentional RawWaker destructor panic");
27321 }
27322 }
27323
27324 wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
27325 }
27326
27327 struct TestHostileRawWakerState {
27328 clones: AtomicUsize,
27329 drop_panics: AtomicUsize,
27330 panic_on_clone: Option<usize>,
27331 }
27332
27333 struct TestHostileRawWakerToken {
27334 state: Arc<TestHostileRawWakerState>,
27335 panic_on_drop: bool,
27336 }
27337
27338 unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
27339 let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
27340 let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
27341 assert_ne!(
27342 token.state.panic_on_clone,
27343 Some(clone),
27344 "intentional RawWaker clone panic"
27345 );
27346 let clone = Box::new(TestHostileRawWakerToken {
27347 state: Arc::clone(&token.state),
27348 panic_on_drop: true,
27349 });
27350 std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
27351 }
27352
27353 unsafe fn wake_test_hostile_waker(data: *const ()) {
27354 unsafe { drop_test_hostile_waker(data) };
27355 }
27356
27357 unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
27358
27359 unsafe fn drop_test_hostile_waker(data: *const ()) {
27360 let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
27361 if token.panic_on_drop {
27362 token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
27363 panic!("intentional cloned RawWaker destructor panic");
27364 }
27365 }
27366
27367 static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
27368 clone_test_hostile_waker,
27369 wake_test_hostile_waker,
27370 wake_test_hostile_waker_by_ref,
27371 drop_test_hostile_waker,
27372 );
27373
27374 fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
27375 let token = Box::new(TestHostileRawWakerToken {
27376 state,
27377 panic_on_drop: false,
27378 });
27379 let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
27380 unsafe { Waker::from_raw(raw) }
27381 }
27382
27383 struct CountingWake(AtomicUsize);
27385 impl std::task::Wake for CountingWake {
27386 fn wake(self: Arc<Self>) {
27387 self.0.fetch_add(1, Ordering::SeqCst);
27388 }
27389 }
27390
27391 type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
27393
27394 struct StubHostTimerDriver {
27398 timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
27399 }
27400
27401 impl StubHostTimerDriver {
27402 fn new() -> Self {
27403 Self {
27404 timers: Mutex::new(FxHashMap::default()),
27405 }
27406 }
27407 }
27408
27409 impl TimerDriver for StubHostTimerDriver {
27410 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
27411 let mut timers = self.timers.lock().unwrap();
27412 if let Some(cell) = timers.get(&id) {
27413 *cell.0.lock().unwrap() = waker;
27414 return;
27415 }
27416 let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
27417 timers.insert(id, Arc::clone(&cell));
27418 std::thread::spawn(move || {
27419 let now = Instant::now();
27420 if deadline > now {
27421 std::thread::sleep(deadline - now);
27422 }
27423 if !cell.1.load(Ordering::SeqCst) {
27424 cell.0.lock().unwrap().wake_by_ref();
27425 }
27426 });
27427 }
27428
27429 fn cancel(&self, id: TimerId) {
27430 let removed = self.timers.lock().unwrap().remove(&id);
27431 if let Some(cell) = removed {
27432 cell.1.store(true, Ordering::SeqCst);
27433 }
27434 }
27435 }
27436
27437 struct ManualStubDriver {
27443 live: AtomicBool,
27444 registers: Mutex<Vec<(TimerId, Instant)>>,
27445 cancels: Mutex<Vec<TimerId>>,
27446 pending: Mutex<FxHashMap<TimerId, Waker>>,
27447 swept: AtomicBool,
27448 }
27449
27450 impl ManualStubDriver {
27451 fn new() -> Arc<Self> {
27452 Arc::new(Self {
27453 live: AtomicBool::new(true),
27454 registers: Mutex::new(Vec::new()),
27455 cancels: Mutex::new(Vec::new()),
27456 pending: Mutex::new(FxHashMap::default()),
27457 swept: AtomicBool::new(false),
27458 })
27459 }
27460
27461 fn kill(&self) {
27462 self.live.store(false, Ordering::SeqCst);
27463 }
27464 }
27465
27466 impl TimerDriver for ManualStubDriver {
27467 fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
27468 self.registers.lock().unwrap().push((id, deadline));
27469 self.pending.lock().unwrap().insert(id, waker);
27470 }
27471
27472 fn cancel(&self, id: TimerId) {
27473 self.cancels.lock().unwrap().push(id);
27474 self.pending.lock().unwrap().remove(&id);
27475 }
27476
27477 fn is_live(&self) -> bool {
27478 self.live.load(Ordering::SeqCst)
27479 }
27480
27481 fn on_swept(&self) {
27482 self.swept.store(true, Ordering::SeqCst);
27485 let wakers: Vec<Waker> = self
27486 .pending
27487 .lock()
27488 .unwrap()
27489 .drain()
27490 .map(|(_, w)| w)
27491 .collect();
27492 for waker in wakers {
27493 waker.wake();
27494 }
27495 }
27496 }
27497
27498 struct PanickingTimerDriver {
27499 swept: AtomicBool,
27500 }
27501
27502 impl TimerDriver for PanickingTimerDriver {
27503 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27504
27505 fn cancel(&self, _id: TimerId) {}
27506
27507 fn is_live(&self) -> bool {
27508 panic!("intentional timer-driver liveness panic");
27509 }
27510
27511 fn on_swept(&self) {
27512 self.swept.store(true, Ordering::SeqCst);
27513 panic!("intentional timer-driver sweep panic");
27514 }
27515 }
27516
27517 struct PanickingRegisterTimerDriver {
27518 registers: AtomicUsize,
27519 cancels: Mutex<Vec<TimerId>>,
27520 pending: Mutex<FxHashMap<TimerId, Waker>>,
27521 }
27522
27523 impl PanickingRegisterTimerDriver {
27524 fn new() -> Arc<Self> {
27525 Arc::new(Self {
27526 registers: AtomicUsize::new(0),
27527 cancels: Mutex::new(Vec::new()),
27528 pending: Mutex::new(FxHashMap::default()),
27529 })
27530 }
27531 }
27532
27533 impl TimerDriver for PanickingRegisterTimerDriver {
27534 fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
27535 self.registers.fetch_add(1, Ordering::SeqCst);
27536 self.pending.lock().unwrap().insert(id, waker);
27537 panic!("intentional timer-driver register panic after arming");
27538 }
27539
27540 fn cancel(&self, id: TimerId) {
27541 self.cancels.lock().unwrap().push(id);
27542 self.pending.lock().unwrap().remove(&id);
27543 }
27544 }
27545
27546 struct PanicBeforeRetainingTimerDriver {
27547 registers: AtomicUsize,
27548 }
27549
27550 impl TimerDriver for PanicBeforeRetainingTimerDriver {
27551 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
27552 self.registers.fetch_add(1, Ordering::SeqCst);
27553 panic!("intentional timer-driver register panic before retaining its waker");
27554 }
27555
27556 fn cancel(&self, _id: TimerId) {}
27557 }
27558
27559 struct PanickingCancelDropTimerDriver {
27560 cancels: Arc<AtomicUsize>,
27561 drops: Arc<AtomicUsize>,
27562 }
27563
27564 impl TimerDriver for PanickingCancelDropTimerDriver {
27565 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27566
27567 fn cancel(&self, _id: TimerId) {
27568 self.cancels.fetch_add(1, Ordering::SeqCst);
27569 panic!("intentional timer-driver cancel panic");
27570 }
27571 }
27572
27573 impl Drop for PanickingCancelDropTimerDriver {
27574 fn drop(&mut self) {
27575 self.drops.fetch_add(1, Ordering::SeqCst);
27576 panic!("intentional timer-driver destructor panic");
27577 }
27578 }
27579
27580 struct UnregisterLaterThenPanicTimerDriver {
27581 registry: Weak<TimerDriverRegistry>,
27582 later: Mutex<Option<TimerDriverId>>,
27583 registers: AtomicUsize,
27584 }
27585
27586 impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
27587 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
27588 self.registers.fetch_add(1, Ordering::SeqCst);
27589 let later = *self.later.lock().unwrap();
27590 if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
27591 registry.unregister(later);
27592 }
27593 panic!("intentional earlier timer-driver panic after unregistering a later driver");
27594 }
27595
27596 fn cancel(&self, _id: TimerId) {}
27597 }
27598
27599 struct ReentrantLivenessDriver {
27600 registry: std::sync::Weak<TimerDriverRegistry>,
27601 registration: Mutex<Option<TimerDriverId>>,
27602 }
27603
27604 impl TimerDriver for ReentrantLivenessDriver {
27605 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27606
27607 fn cancel(&self, _id: TimerId) {}
27608
27609 fn is_live(&self) -> bool {
27610 let registration = *self.registration.lock().unwrap();
27611 if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
27612 registry.unregister(id);
27613 }
27614 false
27615 }
27616 }
27617
27618 struct ReentrantMultiUnregisterTimerDriver {
27619 registry: std::sync::Weak<TimerDriverRegistry>,
27620 registrations: Mutex<Vec<TimerDriverId>>,
27621 }
27622
27623 impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
27624 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27625
27626 fn cancel(&self, _id: TimerId) {}
27627
27628 fn is_live(&self) -> bool {
27629 if let Some(registry) = self.registry.upgrade() {
27630 for registration in self.registrations.lock().unwrap().iter().copied() {
27631 registry.unregister(registration);
27632 }
27633 }
27634 false
27635 }
27636 }
27637
27638 struct ReentrantSweepTimerDriver {
27639 registry: std::sync::Weak<TimerDriverRegistry>,
27640 registration: Mutex<Option<TimerDriverId>>,
27641 sweeps: AtomicUsize,
27642 removed_before_sweep: AtomicBool,
27643 }
27644
27645 impl TimerDriver for ReentrantSweepTimerDriver {
27646 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27647
27648 fn cancel(&self, _id: TimerId) {}
27649
27650 fn is_live(&self) -> bool {
27651 false
27652 }
27653
27654 fn on_swept(&self) {
27655 self.sweeps.fetch_add(1, Ordering::SeqCst);
27656 let registration = self.registration.lock().unwrap().take();
27657 if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
27658 let state = registry
27659 .state
27660 .try_lock()
27661 .expect("the sweep callback must run outside the timer-driver registry lock");
27662 assert!(
27663 !state.entries.contains_key(®istration),
27664 "the dead driver must be unlinked before its sweep callback runs"
27665 );
27666 self.removed_before_sweep.store(true, Ordering::SeqCst);
27667 drop(state);
27668 registry.unregister(registration);
27671 }
27672 }
27673 }
27674
27675 struct ReentrantDropDriver {
27676 registry: std::sync::Weak<TimerDriverRegistry>,
27677 fallback: TimerDriverId,
27678 }
27679
27680 impl TimerDriver for ReentrantDropDriver {
27681 fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
27682
27683 fn cancel(&self, _id: TimerId) {}
27684 }
27685
27686 impl Drop for ReentrantDropDriver {
27687 fn drop(&mut self) {
27688 if let Some(registry) = self.registry.upgrade() {
27689 registry.unregister(self.fallback);
27690 }
27691 }
27692 }
27693
27694 #[derive(Default)]
27697 struct WakeFlag(AtomicBool);
27698
27699 impl std::task::Wake for WakeFlag {
27700 fn wake(self: Arc<Self>) {
27701 self.0.store(true, Ordering::SeqCst);
27702 }
27703 }
27704
27705 #[cfg(not(target_family = "wasm"))]
27706 fn multi_thread_executor(
27707 workers: usize,
27708 park_deadline: Option<Duration>,
27709 prefix: &str,
27710 ) -> Arc<MultiThreadExecutor> {
27711 let options = RuntimeOptions {
27712 flavor: RuntimeFlavor::MultiThread,
27713 worker_threads: workers,
27714 max_blocking_tasks: workers,
27715 thread_name_prefix: prefix.to_string(),
27716 park_deadline,
27717 };
27718 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
27719 }
27720
27721 fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
27724 let registry = Arc::new(TimerDriverRegistry::default());
27725 registry.register(driver);
27726 registry
27727 }
27728
27729 #[cfg(not(target_family = "wasm"))]
27730 fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
27731 make_sleep(
27733 &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
27734 &Arc::new(TimerDriverRegistry::default()),
27735 deadline,
27736 )
27737 }
27738
27739 #[cfg(not(target_family = "wasm"))]
27740 #[test]
27741 fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
27742 use std::sync::mpsc;
27743
27744 let executor = multi_thread_executor(
27745 1,
27746 Some(Duration::from_secs(1)),
27747 "timer-registration-admission",
27748 );
27749 let (entered_tx, entered_rx) = mpsc::channel();
27750 let (release_tx, release_rx) = mpsc::channel();
27751 let registration_executor = Arc::clone(&executor);
27752 let registration = std::thread::spawn(move || {
27753 TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
27754 *slot.borrow_mut() = Some(Box::new(move || {
27755 entered_tx.send(()).unwrap();
27756 release_rx.recv().unwrap();
27757 }));
27758 });
27759 registration_executor.register_timer(
27760 1,
27761 Instant::now() + Duration::from_secs(60),
27762 futures::task::noop_waker(),
27763 &Arc::new(AtomicBool::new(false)),
27764 );
27765 });
27766
27767 entered_rx
27768 .recv_timeout(Duration::from_secs(2))
27769 .expect("timer registration must pause after entering verdict admission");
27770 let verdict = executor.try_begin_deadlock_verdict();
27771 let admission_blocked_verdict = verdict.is_none();
27772 drop(verdict);
27773 release_tx.send(()).unwrap();
27774 registration.join().unwrap();
27775
27776 assert!(
27777 admission_blocked_verdict,
27778 "a timer registration must remain announced until its heap entry is visible"
27779 );
27780 assert!(
27781 executor.next_timer_deadline().is_some(),
27782 "the admitted registration must publish its timer after release"
27783 );
27784 executor.begin_shutdown();
27785 executor.wait_until_scheduler_idle();
27786 }
27787
27788 #[cfg(not(target_family = "wasm"))]
27789 #[derive(Clone, Copy, Debug)]
27790 enum TimerFireAdmissionPath {
27791 Timekeeper,
27792 Cooperative,
27793 Shutdown,
27794 }
27795
27796 #[cfg(not(target_family = "wasm"))]
27797 struct ScheduleAfterTimerWakeRelease {
27798 executor: Arc<MultiThreadExecutor>,
27799 runnable: Mutex<Option<Runnable>>,
27800 entered: std::sync::mpsc::Sender<()>,
27801 release: Mutex<std::sync::mpsc::Receiver<()>>,
27802 scheduled: std::sync::mpsc::Sender<()>,
27803 }
27804
27805 #[cfg(not(target_family = "wasm"))]
27806 impl ScheduleAfterTimerWakeRelease {
27807 fn publish(&self) {
27808 self.entered.send(()).unwrap();
27809 self
27810 .release
27811 .lock()
27812 .unwrap_or_else(std::sync::PoisonError::into_inner)
27813 .recv()
27814 .unwrap();
27815 let runnable = self
27816 .runnable
27817 .lock()
27818 .unwrap_or_else(std::sync::PoisonError::into_inner)
27819 .take();
27820 if let Some(runnable) = runnable {
27821 self.executor.schedule(runnable);
27822 }
27823 self.scheduled.send(()).unwrap();
27824 }
27825 }
27826
27827 #[cfg(not(target_family = "wasm"))]
27828 impl std::task::Wake for ScheduleAfterTimerWakeRelease {
27829 fn wake(self: Arc<Self>) {
27830 self.publish();
27831 }
27832
27833 fn wake_by_ref(self: &Arc<Self>) {
27834 self.publish();
27835 }
27836 }
27837
27838 #[cfg(not(target_family = "wasm"))]
27839 fn insert_timer_for_fire_admission_test(
27840 executor: &MultiThreadExecutor,
27841 id: TimerId,
27842 deadline: Instant,
27843 waker: Waker,
27844 fired: Arc<AtomicBool>,
27845 ) {
27846 let mut inner = executor
27847 .timers
27848 .inner
27849 .lock()
27850 .unwrap_or_else(std::sync::PoisonError::into_inner);
27851 inner.entries.insert(id, HeapTimerEntry { waker, fired });
27852 inner.queue.push(deadline, id);
27853 }
27854
27855 #[cfg(not(target_family = "wasm"))]
27856 fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
27857 use std::sync::mpsc;
27858
27859 let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
27860 let (runnable, task) = async_task::spawn(async {}, |_| {});
27861 task.detach();
27862 let (entered_tx, entered_rx) = mpsc::channel();
27863 let (release_tx, release_rx) = mpsc::channel();
27864 let (scheduled_tx, scheduled_rx) = mpsc::channel();
27865 let fired = Arc::new(AtomicBool::new(false));
27866 let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
27867 executor: Arc::clone(&executor),
27868 runnable: Mutex::new(Some(runnable)),
27869 entered: entered_tx,
27870 release: Mutex::new(release_rx),
27871 scheduled: scheduled_tx,
27872 }));
27873
27874 let fire_thread = match path {
27875 TimerFireAdmissionPath::Timekeeper => {
27876 executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
27877 None
27878 }
27879 TimerFireAdmissionPath::Cooperative => {
27880 insert_timer_for_fire_admission_test(
27881 &executor,
27882 1,
27883 Instant::now(),
27884 waker,
27885 Arc::clone(&fired),
27886 );
27887 let fire_executor = Arc::clone(&executor);
27888 Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
27889 }
27890 TimerFireAdmissionPath::Shutdown => {
27891 insert_timer_for_fire_admission_test(
27892 &executor,
27893 1,
27894 Instant::now() + Duration::from_secs(60),
27895 waker,
27896 Arc::clone(&fired),
27897 );
27898 let fire_executor = Arc::clone(&executor);
27899 Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
27900 }
27901 };
27902
27903 entered_rx
27907 .recv_timeout(Duration::from_secs(10))
27908 .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
27909 assert!(
27910 executor.next_timer_deadline().is_none(),
27911 "{path:?} must exercise the remove-before-wake window"
27912 );
27913 assert!(
27914 executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
27915 "{path:?} must retain admission while its waker is publishing"
27916 );
27917 let verdict = executor.try_begin_deadlock_verdict();
27918 let admission_blocked_verdict = verdict.is_none();
27919 drop(verdict);
27920
27921 release_tx.send(()).unwrap();
27922 scheduled_rx
27923 .recv_timeout(Duration::from_secs(10))
27924 .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
27925 if let Some(fire_thread) = fire_thread {
27926 fire_thread.join().unwrap();
27927 }
27928 assert!(
27929 admission_blocked_verdict,
27930 "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
27931 );
27932
27933 executor.begin_shutdown();
27934 executor.wait_until_scheduler_idle();
27935 }
27936
27937 #[cfg(not(target_family = "wasm"))]
27938 #[test]
27939 fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
27940 for path in [
27941 TimerFireAdmissionPath::Timekeeper,
27942 TimerFireAdmissionPath::Cooperative,
27943 TimerFireAdmissionPath::Shutdown,
27944 ] {
27945 assert_timer_fire_path_holds_deadlock_admission(path);
27946 }
27947 }
27948
27949 #[cfg(not(target_family = "wasm"))]
27950 #[test]
27951 fn multi_thread_sleep_fires_from_foreign_block_on() {
27952 use std::sync::mpsc;
27957
27958 let (done_tx, done_rx) = mpsc::channel();
27959 let runner = std::thread::spawn(move || {
27960 let executor = multi_thread_executor(2, None, "timer-foreign");
27961 let started = Instant::now();
27962 let mut future = std::pin::pin!(async {
27963 heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
27964 });
27965 executor.block_on(future.as_mut());
27966 assert!(
27967 started.elapsed() >= Duration::from_millis(80),
27968 "the sleep must not resolve before its deadline"
27969 );
27970 let _ = done_tx.send(());
27971 });
27972
27973 match done_rx.recv_timeout(Duration::from_secs(10)) {
27974 Ok(()) => runner.join().unwrap(),
27975 Err(error) => {
27976 panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
27977 }
27978 }
27979 }
27980
27981 #[cfg(not(target_family = "wasm"))]
27982 #[test]
27983 fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
27984 use std::sync::mpsc;
27987
27988 let (done_tx, done_rx) = mpsc::channel();
27989 let runner = std::thread::spawn(move || {
27990 let executor = multi_thread_executor(1, None, "timer-drainer");
27991
27992 let sleep_exec = Arc::clone(&executor);
27995 let sleep_done = Arc::new(AtomicBool::new(false));
27996 let sleep_flag = Arc::clone(&sleep_done);
27997 let sched = Arc::clone(&executor);
27998 let (sleep_runnable, sleep_task) = async_task::spawn(
27999 async move {
28000 heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
28001 sleep_flag.store(true, Ordering::SeqCst);
28002 },
28003 move |r| sched.schedule(r),
28004 );
28005 executor.schedule(sleep_runnable);
28006 wait_until("the timekeeper parked on the 60s deadline", || {
28007 executor
28008 .timers
28009 .inner
28010 .lock()
28011 .unwrap()
28012 .timekeeper_parker
28013 .is_some()
28014 });
28015
28016 let counter = Arc::new(AtomicUsize::new(0));
28018 let mut tasks = Vec::new();
28019 for _ in 0..32 {
28020 let counter = Arc::clone(&counter);
28021 let sched = Arc::clone(&executor);
28022 let (runnable, task) = async_task::spawn(
28023 async move {
28024 counter.fetch_add(1, Ordering::SeqCst);
28025 },
28026 move |r| sched.schedule(r),
28027 );
28028 executor.schedule(runnable);
28029 tasks.push(task);
28030 }
28031 for task in tasks {
28032 futures::executor::block_on(task);
28033 }
28034 assert_eq!(counter.load(Ordering::SeqCst), 32);
28035 assert!(
28036 !sleep_done.load(Ordering::SeqCst),
28037 "the 60s timer must still be pending: the work ran DURING the timer wait"
28038 );
28039 executor.shutdown_timers();
28041 futures::executor::block_on(sleep_task);
28042 let _ = done_tx.send(());
28043 });
28044
28045 match done_rx.recv_timeout(Duration::from_secs(15)) {
28046 Ok(()) => runner.join().unwrap(),
28047 Err(error) => {
28048 panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
28049 }
28050 }
28051 }
28052
28053 #[cfg(not(target_family = "wasm"))]
28054 #[test]
28055 fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
28056 let executor = multi_thread_executor(2, None, "timer-lazy-start");
28057 assert!(
28058 executor.timers.thread.lock().unwrap().is_none(),
28059 "constructing a runtime without timers must not create an extra OS thread"
28060 );
28061
28062 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28063 assert!(
28064 executor.timers.thread.lock().unwrap().is_none(),
28065 "an unpolled sleep has not registered a deadline"
28066 );
28067 let waker = futures::task::noop_waker();
28068 let mut cx = Context::from_waker(&waker);
28069 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28070 assert!(
28071 executor.timers.thread.lock().unwrap().is_some(),
28072 "the first timer registration must start the dedicated service"
28073 );
28074 }
28075
28076 #[cfg(not(target_family = "wasm"))]
28077 #[test]
28078 fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
28079 use std::sync::mpsc;
28080
28081 let executor = multi_thread_executor(1, None, "timer-closed-start");
28082 executor.shutdown_timers();
28083 let handle_guard = executor.timers.thread.lock().unwrap();
28084 let ensure_executor = Arc::clone(&executor);
28085 let (completed_tx, completed_rx) = mpsc::channel();
28086 let ensure = std::thread::spawn(move || {
28087 ensure_executor.timers.ensure_started().unwrap();
28088 completed_tx.send(()).unwrap();
28089 });
28090
28091 completed_rx.recv_timeout(Duration::from_secs(10)).expect(
28094 "ensure_started consulted or joined the timer handle before observing the closed heap",
28095 );
28096 drop(handle_guard);
28097 ensure.join().unwrap();
28098 }
28099
28100 #[cfg(not(target_family = "wasm"))]
28101 #[test]
28102 fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
28103 const TIMERS: u64 = 4096;
28104
28105 let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
28106 let base = Instant::now() + Duration::from_secs(3600);
28107 for id in 0..TIMERS {
28108 executor.register_timer(
28109 id,
28110 base + Duration::from_secs(id),
28111 futures::task::noop_waker(),
28112 &Arc::new(AtomicBool::new(false)),
28113 );
28114 }
28115 {
28116 let inner = executor.timers.inner.lock().unwrap();
28117 assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
28118 assert_eq!(inner.queue.len(), inner.entries.len());
28119 }
28120
28121 for id in 1..TIMERS {
28124 executor.cancel_timer(id);
28125 }
28126 {
28127 let inner = executor.timers.inner.lock().unwrap();
28128 assert_eq!(inner.entries.len(), 1);
28129 assert_eq!(inner.queue.len(), 1);
28130 assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
28131 }
28132
28133 executor.cancel_timer(0);
28134 {
28135 let inner = executor.timers.inner.lock().unwrap();
28136 assert!(inner.entries.is_empty());
28137 assert!(inner.queue.is_empty());
28138 }
28139 executor.shutdown_timers();
28140 executor.timers.join();
28141 }
28142
28143 #[cfg(not(target_family = "wasm"))]
28144 #[test]
28145 fn timer_registration_is_transactional_when_timekeeper_start_fails() {
28146 let executor = multi_thread_executor(2, None, "timer-start-failure");
28147 executor
28148 .timers
28149 .fail_next_start
28150 .store(true, Ordering::SeqCst);
28151
28152 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
28153 let waker = Waker::from(Arc::clone(&wake_count));
28154 let baseline_waker_refs = Arc::strong_count(&wake_count);
28155 let mut cx = Context::from_waker(&waker);
28156 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28157
28158 let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
28159 assert!(
28160 failure.is_err(),
28161 "the injected timer-thread start failure must panic the poll"
28162 );
28163 {
28164 let inner = executor.timers.inner.lock().unwrap();
28165 assert!(
28166 inner.entries.is_empty(),
28167 "a failed start must not retain a timer entry"
28168 );
28169 assert!(
28170 inner.queue.is_empty(),
28171 "a failed start must not retain a stale heap node"
28172 );
28173 assert!(
28174 inner.timekeeper_parker.is_none(),
28175 "a failed start must not publish a timekeeper parker"
28176 );
28177 }
28178 assert!(
28179 executor.timers.thread.lock().unwrap().is_none(),
28180 "a failed start must not retain a thread handle"
28181 );
28182 assert_eq!(
28183 Arc::strong_count(&wake_count),
28184 baseline_waker_refs,
28185 "a failed start must release the cloned timer waker"
28186 );
28187
28188 assert!(
28189 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
28190 "the same Sleep must be safely retryable after a transient start failure"
28191 );
28192 {
28193 let inner = executor.timers.inner.lock().unwrap();
28194 assert_eq!(
28195 inner.entries.len(),
28196 1,
28197 "the retry must publish exactly one timer entry"
28198 );
28199 assert_eq!(
28200 inner.queue.len(),
28201 1,
28202 "the retry must publish exactly one heap node"
28203 );
28204 }
28205 assert!(
28206 executor.timers.thread.lock().unwrap().is_some(),
28207 "the retry must start the dedicated timekeeper"
28208 );
28209
28210 drop(sleep);
28211 assert!(
28212 executor.timers.inner.lock().unwrap().entries.is_empty(),
28213 "dropping the retried Sleep must cancel its registration"
28214 );
28215 executor.shutdown_timers();
28216 executor.wait_until_scheduler_idle();
28217 assert!(
28218 executor.timers.thread.lock().unwrap().is_none(),
28219 "shutdown after a recovered start failure must join the timekeeper"
28220 );
28221 }
28222
28223 #[cfg(not(target_family = "wasm"))]
28224 #[test]
28225 fn pending_timer_preserves_every_configured_rayon_worker() {
28226 use std::sync::mpsc;
28227
28228 let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
28229 let waker = futures::task::noop_waker();
28230 let mut cx = Context::from_waker(&waker);
28231 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28232 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28233 wait_until("the dedicated timer thread to park", || {
28234 executor
28235 .timers
28236 .inner
28237 .lock()
28238 .unwrap()
28239 .timekeeper_parker
28240 .is_some()
28241 });
28242
28243 let released = Arc::new((Mutex::new(false), Condvar::new()));
28244 let (started_tx, started_rx) = mpsc::channel();
28245 let (completed_tx, completed_rx) = mpsc::channel();
28246 for _ in 0..2 {
28247 let released = Arc::clone(&released);
28248 let started_tx = started_tx.clone();
28249 let completed_tx = completed_tx.clone();
28250 executor.pool.spawn(move || {
28251 started_tx.send(()).unwrap();
28252 let (lock, changed) = &*released;
28253 let mut released = lock
28254 .lock()
28255 .unwrap_or_else(std::sync::PoisonError::into_inner);
28256 while !*released {
28257 released = changed
28258 .wait(released)
28259 .unwrap_or_else(std::sync::PoisonError::into_inner);
28260 }
28261 completed_tx.send(()).unwrap();
28262 });
28263 }
28264 drop(started_tx);
28265 drop(completed_tx);
28266
28267 let first = started_rx.recv_timeout(Duration::from_secs(2));
28268 let second = started_rx.recv_timeout(Duration::from_secs(2));
28269 {
28270 let (lock, changed) = &*released;
28271 *lock
28272 .lock()
28273 .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
28274 changed.notify_all();
28275 }
28276 drop(sleep);
28277 for _ in 0..2 {
28278 completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
28279 }
28280
28281 assert!(
28282 first.is_ok(),
28283 "the first configured Rayon worker did not start"
28284 );
28285 assert!(
28286 second.is_ok(),
28287 "a pending timer consumed one of the two configured Rayon workers"
28288 );
28289 }
28290
28291 #[cfg(not(target_family = "wasm"))]
28292 #[test]
28293 fn timekeeper_never_enters_a_blocking_closure() {
28294 use std::{
28295 sync::{Barrier, mpsc},
28296 time::Duration,
28297 };
28298
28299 let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
28300 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
28301 let waker = Waker::from(Arc::clone(&wake_count));
28302 let mut cx = Context::from_waker(&waker);
28303 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
28304 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28305 wait_until(
28306 "the timekeeper parked before blocking work was submitted",
28307 || {
28308 executor
28309 .timers
28310 .inner
28311 .lock()
28312 .unwrap()
28313 .timekeeper_parker
28314 .is_some()
28315 },
28316 );
28317
28318 let release = Arc::new(Barrier::new(2));
28319 let blocker_release = Arc::clone(&release);
28320 let (started_tx, started_rx) = mpsc::sync_channel(1);
28321 let blocking = executor.schedule_blocking(move || {
28322 started_tx.send(()).unwrap();
28323 blocker_release.wait();
28324 });
28325 started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
28328
28329 wait_until(
28330 "the timer waker to fire while the Rayon worker is blocked",
28331 || wake_count.0.load(Ordering::SeqCst) != 0,
28332 );
28333 release.wait();
28334 futures::executor::block_on(blocking).unwrap();
28335 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
28336 }
28337
28338 #[cfg(not(target_family = "wasm"))]
28339 #[test]
28340 fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
28341 use std::sync::mpsc;
28342
28343 struct PendingEntered(Option<mpsc::Sender<()>>);
28344
28345 impl Future for PendingEntered {
28346 type Output = ();
28347
28348 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28349 if let Some(entered) = self.0.take() {
28350 entered.send(()).unwrap();
28351 }
28352 Poll::Pending
28353 }
28354 }
28355
28356 let options = RuntimeOptions {
28357 flavor: RuntimeFlavor::MultiThread,
28358 worker_threads: 2,
28359 max_blocking_tasks: 1,
28360 thread_name_prefix: "nested-timer-wake".to_string(),
28361 park_deadline: None,
28362 };
28363 let executor =
28364 Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
28365
28366 let timer_exec = Arc::clone(&executor);
28367 let (timer_tx, timer_rx) = mpsc::channel();
28368 let timer_scheduler = Arc::clone(&executor);
28369 let (timer_runnable, timer_task) = async_task::spawn(
28370 async move {
28371 heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
28372 timer_tx.send(()).unwrap();
28373 },
28374 move |runnable| timer_scheduler.schedule(runnable),
28375 );
28376 executor.schedule(timer_runnable);
28377 wait_until(
28378 "the dedicated timekeeper parked before nested block_on",
28379 || {
28380 executor
28381 .timers
28382 .inner
28383 .lock()
28384 .unwrap()
28385 .timekeeper_parker
28386 .is_some()
28387 },
28388 );
28389
28390 let (entered_tx, entered_rx) = mpsc::channel();
28391 let nested_exec = Arc::clone(&executor);
28392 let nested_scheduler = Arc::clone(&executor);
28393 let (nested_runnable, nested_task) = async_task::spawn(
28394 async move {
28395 let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
28396 let outcome = nested_exec.block_on(pending.as_mut());
28397 drop(pending);
28398 assert_eq!(
28399 outcome,
28400 BlockOnOutcome::Stopped,
28401 "shutdown must stop the raw nested block_on driver"
28402 );
28403 },
28404 move |runnable| nested_scheduler.schedule(runnable),
28405 );
28406 executor.schedule(nested_runnable);
28407 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
28408
28409 let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
28410 let (release_tx, release_rx) = mpsc::channel();
28411 let blocking = executor.schedule_blocking(move || {
28412 blocking_started_tx.send(()).unwrap();
28413 release_rx.recv().unwrap();
28414 });
28415 blocking_started_rx
28416 .recv_timeout(Duration::from_secs(2))
28417 .expect("a general worker must service blocking work");
28418 timer_rx
28419 .recv_timeout(Duration::from_secs(2))
28420 .expect("the timer wake must reach the nested cooperative driver");
28421
28422 release_tx.send(()).unwrap();
28423 futures::executor::block_on(blocking).unwrap();
28424 executor.begin_shutdown();
28425 assert_eq!(
28426 futures::executor::block_on(nested_task.fallible()),
28427 Some(())
28428 );
28429 futures::executor::block_on(timer_task);
28430 }
28431
28432 #[cfg(not(target_family = "wasm"))]
28433 #[test]
28434 fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
28435 use std::sync::mpsc;
28439
28440 let (done_tx, done_rx) = mpsc::channel();
28441 let runner = std::thread::spawn(move || {
28442 let executor = multi_thread_executor(2, None, "timer-rearm");
28443
28444 let long_exec = Arc::clone(&executor);
28445 let long_done = Arc::new(AtomicBool::new(false));
28446 let long_flag = Arc::clone(&long_done);
28447 let sched = Arc::clone(&executor);
28448 let (long_runnable, long_task) = async_task::spawn(
28449 async move {
28450 heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
28451 long_flag.store(true, Ordering::SeqCst);
28452 },
28453 move |r| sched.schedule(r),
28454 );
28455 executor.schedule(long_runnable);
28456 wait_until("the timekeeper parked on the 60s sentinel", || {
28457 executor
28458 .timers
28459 .inner
28460 .lock()
28461 .unwrap()
28462 .timekeeper_parker
28463 .is_some()
28464 });
28465
28466 let started = Instant::now();
28467 let mut future = std::pin::pin!(async {
28468 heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
28469 });
28470 executor.block_on(future.as_mut());
28471 let elapsed = started.elapsed();
28472 assert!(
28473 elapsed >= Duration::from_millis(60),
28474 "fired before its deadline: {elapsed:?}"
28475 );
28476 assert!(
28477 !long_done.load(Ordering::SeqCst),
28478 "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
28479 timekeeper, not by the sentinel expiring"
28480 );
28481 executor.shutdown_timers();
28482 futures::executor::block_on(long_task);
28483 let _ = done_tx.send(());
28484 });
28485
28486 match done_rx.recv_timeout(Duration::from_secs(15)) {
28487 Ok(()) => runner.join().unwrap(),
28488 Err(error) => panic!(
28489 "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
28490 ),
28491 }
28492 }
28493
28494 #[cfg(not(target_family = "wasm"))]
28495 #[test]
28496 fn multi_thread_sleep_cancels_registration_on_drop() {
28497 let executor = multi_thread_executor(1, None, "timer-cancel");
28500 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
28501 let waker = Waker::from(Arc::clone(&wake_count));
28502 let mut cx = Context::from_waker(&waker);
28503
28504 let deadline = Instant::now() + Duration::from_millis(250);
28508 let mut sleep = heap_sleep(&executor, deadline);
28509 assert!(
28510 Pin::new(&mut sleep).poll(&mut cx).is_pending(),
28511 "must register, not resolve"
28512 );
28513 assert!(
28514 executor.next_timer_deadline().is_some(),
28515 "the registration must be in the heap"
28516 );
28517 drop(sleep);
28518 assert!(
28519 executor.next_timer_deadline().is_none(),
28520 "cancel-on-drop must retire the heap registration"
28521 );
28522 while Instant::now() < deadline + Duration::from_millis(25) {
28527 std::thread::sleep(Duration::from_millis(10));
28528 }
28529 executor.fire_due_timers();
28530 assert_eq!(
28531 wake_count.0.load(Ordering::SeqCst),
28532 0,
28533 "a dropped Sleep's waker must never fire"
28534 );
28535 }
28536
28537 #[cfg(not(target_family = "wasm"))]
28538 #[test]
28539 fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
28540 let executor = multi_thread_executor(2, None, "timer-cancel-retire");
28541 let waker = futures::task::noop_waker();
28542 let mut cx = Context::from_waker(&waker);
28543 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
28544
28545 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28546 wait_until(
28547 "the timekeeper parked on the timer that will be cancelled",
28548 || {
28549 executor
28550 .timers
28551 .inner
28552 .lock()
28553 .unwrap()
28554 .timekeeper_parker
28555 .is_some()
28556 },
28557 );
28558
28559 drop(sleep);
28560 wait_until(
28561 "the cancelled timer's timekeeper returned to its idle wait",
28562 || {
28563 executor.next_timer_deadline().is_none()
28564 && executor
28565 .timers
28566 .inner
28567 .lock()
28568 .unwrap()
28569 .timekeeper_parker
28570 .is_none()
28571 },
28572 );
28573 }
28574
28575 #[cfg(not(target_family = "wasm"))]
28576 #[test]
28577 fn timer_waker_replacement_drops_outside_heap_lock() {
28578 use std::sync::mpsc;
28579
28580 struct DropOtherTimer(Mutex<Option<Sleep>>);
28581
28582 #[expect(
28583 clippy::manual_noop_waker,
28584 reason = "the no-op wake type has a reentrant Drop used by this regression"
28585 )]
28586 impl std::task::Wake for DropOtherTimer {
28587 fn wake(self: Arc<Self>) {}
28588 }
28589
28590 impl Drop for DropOtherTimer {
28591 fn drop(&mut self) {
28592 drop(self.0.lock().unwrap().take());
28593 }
28594 }
28595
28596 let (done_tx, done_rx) = mpsc::channel();
28597 let runner = std::thread::spawn(move || {
28598 let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
28599 let deadline = Instant::now() + Duration::from_secs(120);
28600 let mut other = heap_sleep(&executor, deadline);
28601 let noop = futures::task::noop_waker();
28602 let mut noop_cx = Context::from_waker(&noop);
28603 assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
28604
28605 let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
28606 let mut primary = heap_sleep(&executor, deadline);
28607 let mut old_cx = Context::from_waker(&old);
28608 assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
28609 drop(old);
28610
28611 let replacement = futures::task::noop_waker();
28612 let mut replacement_cx = Context::from_waker(&replacement);
28613 assert!(
28614 Pin::new(&mut primary)
28615 .poll(&mut replacement_cx)
28616 .is_pending()
28617 );
28618 drop(primary);
28619 executor.shutdown_timers();
28620 done_tx.send(()).unwrap();
28621 });
28622 done_rx
28625 .recv_timeout(Duration::from_secs(10))
28626 .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
28627 runner.join().unwrap();
28628 }
28629
28630 #[cfg(not(target_family = "wasm"))]
28631 #[test]
28632 fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
28633 use std::sync::mpsc;
28634
28635 struct DropFinalExecutorOnWake {
28636 executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
28637 completed: mpsc::Sender<()>,
28638 }
28639
28640 impl std::task::Wake for DropFinalExecutorOnWake {
28641 fn wake(self: Arc<Self>) {
28642 let executor = self
28643 .executor
28644 .lock()
28645 .unwrap_or_else(std::sync::PoisonError::into_inner)
28646 .take()
28647 .expect("the timer waker must own the final executor reference");
28648 assert_eq!(
28649 ON_TIMER_THREAD.with(std::cell::Cell::get),
28650 Some(executor.id),
28651 "the dedicated timer thread must deliver the timer wake"
28652 );
28653 drop(executor);
28654 self
28655 .completed
28656 .send(())
28657 .expect("the test receiver must outlive the timer-thread callback");
28658 }
28659 }
28660
28661 let executor = multi_thread_executor(1, None, "timer-final-arc");
28662 let (completed_tx, completed_rx) = mpsc::channel();
28663 let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
28664 {
28665 let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
28666 executor: Mutex::new(Some(Arc::clone(&executor))),
28667 completed: completed_tx,
28668 }));
28669 let mut cx = Context::from_waker(&waker);
28670 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28671 }
28672 wait_until(
28673 "the timer thread parked before owning the final executor Arc",
28674 || {
28675 executor
28676 .timers
28677 .inner
28678 .lock()
28679 .unwrap()
28680 .timekeeper_parker
28681 .is_some()
28682 },
28683 );
28684
28685 drop(executor);
28689 completed_rx
28692 .recv_timeout(Duration::from_secs(10))
28693 .expect("dropping the final executor Arc on the timer thread attempted to join itself");
28694 }
28695
28696 #[cfg(not(target_family = "wasm"))]
28697 #[test]
28698 fn multi_thread_shutdown_with_pending_timers_drain_fires() {
28699 use std::sync::mpsc;
28704
28705 let (done_tx, done_rx) = mpsc::channel();
28706 let runner = std::thread::spawn(move || {
28707 let controller = Arc::new(RuntimeController::new());
28708 let backend = controller.backend();
28710 let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28711 panic!("default native backend must be MultiThread");
28712 };
28713 let executor = Arc::downgrade(executor);
28714 let mut late = heap_sleep(
28715 &executor
28716 .upgrade()
28717 .expect("the initial executor must be live"),
28718 Instant::now() + Duration::from_secs(120),
28719 );
28720 drop(backend);
28721
28722 let (parked_tx, parked_rx) = mpsc::channel();
28723 let block_controller = Arc::clone(&controller);
28724 let block_exec = Weak::clone(&executor);
28725 let blocker = std::thread::spawn(move || {
28726 let mut future = std::pin::pin!(async {
28727 let executor = block_exec
28728 .upgrade()
28729 .expect("the runtime must remain live while blocking");
28730 let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
28731 drop(executor);
28732 parked_tx.send(()).unwrap();
28733 sleep.await;
28734 });
28735 block_controller.block_on(future.as_mut());
28736 });
28737 parked_rx.recv().unwrap();
28738 wait_until("the 120s sleep registered in the heap", || {
28739 executor
28740 .upgrade()
28741 .is_some_and(|executor| executor.next_timer_deadline().is_some())
28742 });
28743
28744 controller
28746 .shutdown()
28747 .expect("runtime shutdown must succeed");
28748 blocker
28749 .join()
28750 .expect("the blocked sleep must be drain-fired by shutdown, not hang");
28751
28752 let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
28755 let mut cx = Context::from_waker(&waker);
28756 assert!(
28757 Pin::new(&mut late).poll(&mut cx).is_ready(),
28758 "a Sleep polled after shutdown must fire early, never park a closing runtime"
28759 );
28760 let _ = done_tx.send(());
28761 });
28762
28763 match done_rx.recv_timeout(Duration::from_secs(10)) {
28764 Ok(()) => runner.join().unwrap(),
28765 Err(error) => {
28766 panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
28767 }
28768 }
28769 }
28770
28771 #[cfg(not(target_family = "wasm"))]
28772 #[test]
28773 fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
28774 use std::sync::mpsc;
28783
28784 let (done_tx, done_rx) = mpsc::channel();
28785 let runner = std::thread::spawn(move || {
28786 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
28787
28788 let task_exec = Arc::clone(&executor);
28789 let body = async move {
28790 let started = Instant::now();
28791 let mut inner = std::pin::pin!(async {
28792 heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
28793 });
28794 task_exec.block_on(inner.as_mut());
28796 assert!(started.elapsed() >= Duration::from_millis(250));
28797 };
28798 let sched = Arc::clone(&executor);
28799 let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
28800 executor.schedule(runnable);
28801
28802 futures::executor::block_on(task);
28806 drop(executor);
28812 let _ = done_tx.send(());
28813 });
28814
28815 match done_rx.recv_timeout(Duration::from_secs(10)) {
28816 Ok(()) => join_within(
28817 "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
28818 runner,
28819 Duration::from_secs(30),
28820 ),
28821 Err(error) => {
28822 panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
28823 }
28824 }
28825 }
28826
28827 #[cfg(not(target_family = "wasm"))]
28828 #[test]
28829 fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
28830 use std::sync::mpsc;
28837
28838 struct GateWake {
28841 gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
28842 }
28843 impl std::task::Wake for GateWake {
28844 fn wake(self: Arc<Self>) {
28845 let gate = self.gate.lock().unwrap().take();
28846 if let Some(gate) = gate {
28847 let _ = gate.send(23);
28848 }
28849 }
28850 }
28851
28852 let (done_tx, done_rx) = mpsc::channel();
28853 let runner = std::thread::spawn(move || {
28854 let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
28855 executor
28859 .active_drainers
28860 .store(executor.max_drainers, Ordering::SeqCst);
28861
28862 let (gate_tx, gate_rx) = oneshot::channel::<usize>();
28865 let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
28866
28867 let hook_exec = Arc::clone(&executor);
28868 let hook_gate = Arc::clone(&gate_tx);
28869 let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
28872 let hook_sleep_slot = Arc::clone(&parked_sleep);
28873 DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
28874 *slot.borrow_mut() = Some(Box::new(move || {
28875 let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
28876 let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
28877 let mut cx = Context::from_waker(&waker);
28878 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
28879 *hook_sleep_slot.lock().unwrap() = Some(sleep);
28880 }));
28881 });
28882
28883 let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28886 let mut output = None;
28887 {
28888 let mut future = std::pin::pin!(async {
28889 output = Some(gate_rx.await.unwrap());
28890 });
28891 executor.block_on(future.as_mut());
28892 }
28893 DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
28899 assert_eq!(
28900 output,
28901 Some(23),
28902 "the edge-registered timer must have fired and released the gate"
28903 );
28904 executor.active_drainers.store(0, Ordering::SeqCst);
28905 drop(executor);
28908 let _ = done_tx.send(());
28909 });
28910
28911 match done_rx.recv_timeout(Duration::from_secs(10)) {
28912 Ok(()) => join_within(
28913 "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
28914 runner,
28915 Duration::from_secs(30),
28916 ),
28917 Err(error) => panic!(
28918 "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
28919 ),
28920 }
28921 }
28922
28923 #[test]
28924 fn current_thread_sleep_without_host_driver_fails_loud() {
28925 let metrics = Arc::new(RuntimeMetrics::default());
28929 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
28930 let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
28931
28932 let payload = catch_unwind(AssertUnwindSafe(|| {
28933 let _sleep = make_sleep(
28934 &backend,
28935 &Arc::new(TimerDriverRegistry::default()),
28936 Instant::now() + Duration::from_millis(10),
28937 );
28938 }))
28939 .expect_err("sleep_until on CurrentThread without a driver must panic");
28940 let message = payload
28941 .downcast_ref::<String>()
28942 .cloned()
28943 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
28944 .expect("panic payload must be a message");
28945 assert!(
28946 message.contains("CurrentThread runtime has no live timer driver registered"),
28947 "the diagnostic must name the missing driver, got: {message}"
28948 );
28949 }
28950
28951 #[test]
28952 fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
28953 let registry = TimerDriverRegistry::default();
28957 let a = ManualStubDriver::new();
28958 let b = ManualStubDriver::new();
28959 let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
28960 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
28961 assert_ne!(a_id, b_id, "registration handles must be distinct");
28962 assert_eq!(
28963 registry.current().map(|(id, _)| id),
28964 Some(b_id),
28965 "newest live registrant serves"
28966 );
28967 assert!(registry.has_live_driver());
28968
28969 b.kill();
28972 assert_eq!(
28973 registry.current().map(|(id, _)| id),
28974 Some(a_id),
28975 "dead newest must be skipped"
28976 );
28977 b.live.store(true, Ordering::SeqCst);
28980 assert_eq!(
28981 registry.current().map(|(id, _)| id),
28982 Some(a_id),
28983 "swept entries stay gone"
28984 );
28985
28986 registry.unregister(a_id);
28989 assert!(registry.current().is_none());
28990 assert!(
28991 !registry.has_live_driver(),
28992 "an emptied registry must not report a live driver"
28993 );
28994 }
28995
28996 #[test]
28997 fn timer_driver_registry_contains_callback_panics_and_falls_back() {
28998 let registry = TimerDriverRegistry::default();
28999 let fallback = ManualStubDriver::new();
29000 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
29001 let panicking = Arc::new(PanickingTimerDriver {
29002 swept: AtomicBool::new(false),
29003 });
29004 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
29005
29006 assert_eq!(
29007 registry.current().map(|(id, _)| id),
29008 Some(fallback_id),
29009 "a panicking liveness callback must be treated as a dead driver"
29010 );
29011 assert!(
29012 panicking.swept.load(Ordering::SeqCst),
29013 "a panicking liveness callback must still run its contained sweep hook"
29014 );
29015 assert!(
29016 registry.has_live_driver(),
29017 "the healthy fallback must remain selectable"
29018 );
29019 }
29020
29021 #[test]
29022 fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
29023 use std::{sync::mpsc, time::Duration};
29024
29025 let registry = Arc::new(TimerDriverRegistry::default());
29026 let fallback = ManualStubDriver::new();
29027 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
29028 let reentrant = Arc::new(ReentrantLivenessDriver {
29029 registry: Arc::downgrade(®istry),
29030 registration: Mutex::new(None),
29031 });
29032 let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
29033 *reentrant.registration.lock().unwrap() = Some(reentrant_id);
29034
29035 let (selection_tx, selection_rx) = mpsc::sync_channel(1);
29036 let selection_registry = Arc::clone(®istry);
29037 let selection_thread = std::thread::spawn(move || {
29038 selection_tx
29039 .send(selection_registry.current().map(|(id, _)| id))
29040 .unwrap();
29041 });
29042 assert_eq!(
29043 selection_rx
29044 .recv_timeout(Duration::from_secs(10))
29045 .expect("is_live re-entry deadlocked"),
29046 Some(fallback_id)
29047 );
29048 selection_thread.join().unwrap();
29049
29050 let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
29051 registry: Arc::downgrade(®istry),
29052 fallback: fallback_id,
29053 });
29054 let drop_id = registry.register(Arc::clone(&drop_driver));
29055 drop(drop_driver);
29056
29057 let (drop_tx, drop_rx) = mpsc::sync_channel(1);
29058 let drop_registry = Arc::clone(®istry);
29059 let drop_thread = std::thread::spawn(move || {
29060 drop_registry.unregister(drop_id);
29061 drop_tx.send(()).unwrap();
29062 });
29063 drop_rx
29064 .recv_timeout(Duration::from_secs(10))
29065 .expect("driver destructor re-entry deadlocked");
29066 drop_thread.join().unwrap();
29067 assert!(
29068 registry.current().is_none(),
29069 "the destructor must be able to unregister the fallback"
29070 );
29071 }
29072
29073 #[test]
29074 fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
29075 const DRIVER_PAIRS: usize = 2_048;
29076
29077 let registry = Arc::new(TimerDriverRegistry::default());
29078 let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
29079 let mut live = Vec::with_capacity(DRIVER_PAIRS);
29080 let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
29081 for _ in 0..DRIVER_PAIRS {
29082 let driver = Arc::new(ReentrantSweepTimerDriver {
29083 registry: Arc::downgrade(®istry),
29084 registration: Mutex::new(None),
29085 sweeps: AtomicUsize::new(0),
29086 removed_before_sweep: AtomicBool::new(false),
29087 });
29088 let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29089 *driver.registration.lock().unwrap() = Some(id);
29090 dead.push(driver);
29091
29092 let driver = ManualStubDriver::new();
29093 live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
29094 live.push(driver);
29095 }
29096 let dead_tail = Arc::new(ReentrantSweepTimerDriver {
29097 registry: Arc::downgrade(®istry),
29098 registration: Mutex::new(None),
29099 sweeps: AtomicUsize::new(0),
29100 removed_before_sweep: AtomicBool::new(false),
29101 });
29102 let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
29103 *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
29104 dead.push(dead_tail);
29105
29106 let selection = registry.live();
29107 assert_eq!(
29108 selection
29109 .as_slice()
29110 .iter()
29111 .map(|(id, _)| *id)
29112 .collect::<Vec<_>>(),
29113 live_ids,
29114 "sweeping every interleaved dead host must retain live registration order"
29115 );
29116 drop(selection);
29117 assert!(
29118 dead.iter().all(|driver| {
29119 driver.sweeps.load(Ordering::SeqCst) == 1
29120 && driver.removed_before_sweep.load(Ordering::SeqCst)
29121 && driver.registration.lock().unwrap().is_none()
29122 }),
29123 "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
29124 );
29125
29126 {
29127 let state = registry
29128 .state
29129 .lock()
29130 .unwrap_or_else(std::sync::PoisonError::into_inner);
29131 assert_eq!(state.entries.len(), DRIVER_PAIRS);
29132 assert_eq!(state.oldest, live_ids.first().copied());
29133 assert_eq!(state.newest, live_ids.last().copied());
29134 assert_eq!(
29135 state
29136 .ordered_snapshot()
29137 .iter()
29138 .map(|(id, _)| *id)
29139 .collect::<Vec<_>>(),
29140 live_ids,
29141 "keyed removals must preserve the linked order of every live host"
29142 );
29143 }
29144 let second_selection = registry.live();
29145 assert_eq!(
29146 second_selection
29147 .as_slice()
29148 .iter()
29149 .map(|(id, _)| *id)
29150 .collect::<Vec<_>>(),
29151 live_ids,
29152 "a second selection must retain the same live order"
29153 );
29154 drop(second_selection);
29155 assert!(
29156 dead
29157 .iter()
29158 .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
29159 "permanently swept hosts must not receive repeated eviction callbacks"
29160 );
29161 assert_eq!(
29162 registry.current().map(|(id, _)| id),
29163 live_ids.last().copied(),
29164 "newest-live compatibility selection must survive the mass sweep"
29165 );
29166
29167 drop(live);
29168 drop(dead);
29169 }
29170
29171 #[test]
29172 fn timer_driver_registry_snapshot_contains_each_driver_drop() {
29173 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
29174
29175 if std::env::var_os(CHILD_ENV).is_some() {
29176 let registry = Arc::new(TimerDriverRegistry::default());
29177 let fallback = ManualStubDriver::new();
29178 let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
29179 let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
29180 registry: Arc::downgrade(®istry),
29181 registrations: Mutex::new(Vec::new()),
29182 });
29183 registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
29184
29185 let first_cancels = Arc::new(AtomicUsize::new(0));
29186 let first_drops = Arc::new(AtomicUsize::new(0));
29187 let first = Arc::new(PanickingCancelDropTimerDriver {
29188 cancels: Arc::clone(&first_cancels),
29189 drops: Arc::clone(&first_drops),
29190 });
29191 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
29192 drop(first);
29193
29194 let second_cancels = Arc::new(AtomicUsize::new(0));
29195 let second_drops = Arc::new(AtomicUsize::new(0));
29196 let second = Arc::new(PanickingCancelDropTimerDriver {
29197 cancels: Arc::clone(&second_cancels),
29198 drops: Arc::clone(&second_drops),
29199 });
29200 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
29201 drop(second);
29202 *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
29203
29204 assert_eq!(
29205 registry.current().map(|(id, _)| id),
29206 Some(fallback_id),
29207 "selection must retry after reentrant removals and preserve the healthy fallback"
29208 );
29209 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
29210 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
29211 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
29212 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
29213 assert_eq!(
29214 registry
29215 .state
29216 .lock()
29217 .unwrap_or_else(std::sync::PoisonError::into_inner)
29218 .entries
29219 .len(),
29220 1
29221 );
29222 return;
29223 }
29224
29225 let output = std::process::Command::new(std::env::current_exe().unwrap())
29226 .arg("--exact")
29227 .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
29228 .arg("--nocapture")
29229 .env(CHILD_ENV, "1")
29230 .output()
29231 .expect("the hostile timer-driver snapshot subprocess test must start");
29232 assert!(
29233 output.status.success(),
29234 "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
29235 output.status.code(),
29236 String::from_utf8_lossy(&output.stdout),
29237 String::from_utf8_lossy(&output.stderr)
29238 );
29239 }
29240
29241 #[test]
29242 fn timer_driver_registry_live_result_contains_each_driver_drop() {
29243 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
29244
29245 if std::env::var_os(CHILD_ENV).is_some() {
29246 let registry = TimerDriverRegistry::default();
29247
29248 let first_cancels = Arc::new(AtomicUsize::new(0));
29249 let first_drops = Arc::new(AtomicUsize::new(0));
29250 let first = Arc::new(PanickingCancelDropTimerDriver {
29251 cancels: Arc::clone(&first_cancels),
29252 drops: Arc::clone(&first_drops),
29253 });
29254 let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
29255 drop(first);
29256
29257 let second_cancels = Arc::new(AtomicUsize::new(0));
29258 let second_drops = Arc::new(AtomicUsize::new(0));
29259 let second = Arc::new(PanickingCancelDropTimerDriver {
29260 cancels: Arc::clone(&second_cancels),
29261 drops: Arc::clone(&second_drops),
29262 });
29263 let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
29264 drop(second);
29265
29266 let live = registry.live();
29267 assert_eq!(live.len(), 2);
29268 registry.unregister(first_id);
29269 registry.unregister(second_id);
29270 drop(live);
29271
29272 assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
29273 assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
29274 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
29275 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
29276 assert!(
29277 registry
29278 .state
29279 .lock()
29280 .unwrap_or_else(std::sync::PoisonError::into_inner)
29281 .entries
29282 .is_empty()
29283 );
29284 return;
29285 }
29286
29287 let output = std::process::Command::new(std::env::current_exe().unwrap())
29288 .arg("--exact")
29289 .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
29290 .arg("--nocapture")
29291 .env(CHILD_ENV, "1")
29292 .output()
29293 .expect("the hostile live timer-driver snapshot subprocess test must start");
29294 assert!(
29295 output.status.success(),
29296 "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
29297 output.status.code(),
29298 String::from_utf8_lossy(&output.stdout),
29299 String::from_utf8_lossy(&output.stderr)
29300 );
29301 }
29302
29303 #[test]
29304 fn timer_driver_registry_drop_contains_each_driver_destructor() {
29305 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
29306
29307 if std::env::var_os(CHILD_ENV).is_some() {
29308 let first_drops = Arc::new(AtomicUsize::new(0));
29309 let second_drops = Arc::new(AtomicUsize::new(0));
29310 let registry = TimerDriverRegistry::default();
29311 let first = Arc::new(PanickingCancelDropTimerDriver {
29312 cancels: Arc::new(AtomicUsize::new(0)),
29313 drops: Arc::clone(&first_drops),
29314 });
29315 let second = Arc::new(PanickingCancelDropTimerDriver {
29316 cancels: Arc::new(AtomicUsize::new(0)),
29317 drops: Arc::clone(&second_drops),
29318 });
29319 registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
29320 registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
29321 drop(first);
29322 drop(second);
29323
29324 drop(registry);
29325
29326 assert_eq!(first_drops.load(Ordering::SeqCst), 1);
29327 assert_eq!(second_drops.load(Ordering::SeqCst), 1);
29328 return;
29329 }
29330
29331 let output = std::process::Command::new(std::env::current_exe().unwrap())
29332 .arg("--exact")
29333 .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
29334 .arg("--nocapture")
29335 .env(CHILD_ENV, "1")
29336 .output()
29337 .expect("the hostile timer-driver registry drop subprocess test must start");
29338 assert!(
29339 output.status.success(),
29340 "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
29341 output.status.code(),
29342 String::from_utf8_lossy(&output.stdout),
29343 String::from_utf8_lossy(&output.stderr)
29344 );
29345 }
29346
29347 #[test]
29348 fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
29349 const DRIVERS: usize = 512;
29350
29351 let metrics = Arc::new(RuntimeMetrics::default());
29352 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29353 let backend =
29354 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29355 let registry = Arc::new(TimerDriverRegistry::default());
29356 let drivers = (0..DRIVERS)
29357 .map(|_| {
29358 let driver = ManualStubDriver::new();
29359 registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29360 driver
29361 })
29362 .collect::<Vec<_>>();
29363 let mut sleep = make_sleep(
29364 &backend,
29365 ®istry,
29366 Instant::now() + Duration::from_secs(60),
29367 );
29368 let mut cx = Context::from_waker(Waker::noop());
29369
29370 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29371 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29372 let timer_id = drivers[0].registers.lock().unwrap()[0].0;
29373 match &sleep.inner {
29374 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
29375 #[cfg(not(target_family = "wasm"))]
29376 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
29377 }
29378 for driver in &drivers {
29379 assert_eq!(
29380 driver.registers.lock().unwrap().len(),
29381 2,
29382 "a re-poll must refresh each live driver exactly once"
29383 );
29384 }
29385
29386 for driver in drivers.iter().step_by(2) {
29387 driver.kill();
29388 }
29389 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29390 match &sleep.inner {
29391 SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
29392 #[cfg(not(target_family = "wasm"))]
29393 SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
29394 }
29395 for (index, driver) in drivers.iter().enumerate() {
29396 let registers = driver.registers.lock().unwrap().len();
29397 let cancels = driver.cancels.lock().unwrap().clone();
29398 if index % 2 == 0 {
29399 assert_eq!(registers, 2);
29400 assert_eq!(cancels, vec![timer_id]);
29401 } else {
29402 assert_eq!(registers, 3);
29403 assert!(cancels.is_empty());
29404 }
29405 }
29406
29407 drop(sleep);
29408 for driver in drivers {
29409 assert_eq!(
29410 driver.cancels.lock().unwrap().as_slice(),
29411 &[timer_id],
29412 "every physical arm must be cancelled exactly once"
29413 );
29414 }
29415 assert_eq!(registry.pending_len(), 0);
29416 assert!(!executor.host_timers.has_pending());
29417 }
29418
29419 #[test]
29420 fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
29421 let metrics = Arc::new(RuntimeMetrics::default());
29422 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29423 let backend =
29424 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29425 let healthy = ManualStubDriver::new();
29426 let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
29427 let deadline = Instant::now() + Duration::from_secs(60);
29428 let mut sleep = make_sleep(&backend, ®istry, deadline);
29429 let mut cx = Context::from_waker(Waker::noop());
29430
29431 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29432 let timer_id = healthy.registers.lock().unwrap()[0].0;
29433
29434 let panicking = PanickingRegisterTimerDriver::new();
29435 registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
29436 let result = catch_unwind(AssertUnwindSafe(|| {
29437 let _ = Pin::new(&mut sleep).poll(&mut cx);
29438 }));
29439 assert!(
29440 result.is_err(),
29441 "the hostile register panic must retain its normal propagation"
29442 );
29443 assert_eq!(
29444 healthy.registers.lock().unwrap().len(),
29445 2,
29446 "the previously armed driver must have completed its refresh before the later panic"
29447 );
29448 assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
29449 assert!(
29450 panicking.pending.lock().unwrap().contains_key(&timer_id),
29451 "the hostile driver must model an arm committed before it panics"
29452 );
29453
29454 drop(sleep);
29455 assert_eq!(
29456 healthy.cancels.lock().unwrap().as_slice(),
29457 &[timer_id],
29458 "unwind must preserve cancellation ownership for the previously armed driver"
29459 );
29460 assert_eq!(
29461 panicking.cancels.lock().unwrap().as_slice(),
29462 &[timer_id],
29463 "unwind must preserve cancellation ownership for the driver that armed and then panicked"
29464 );
29465 assert!(panicking.pending.lock().unwrap().is_empty());
29466 assert_eq!(registry.pending_len(), 0);
29467 assert!(!executor.host_timers.has_pending());
29468 }
29469
29470 #[test]
29471 fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
29472 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
29473
29474 if std::env::var_os(CHILD_ENV).is_some() {
29475 let metrics = Arc::new(RuntimeMetrics::default());
29476 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29477 let backend =
29478 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29479 let registry = Arc::new(TimerDriverRegistry::default());
29480 let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
29481 registry: Arc::downgrade(®istry),
29482 later: Mutex::new(None),
29483 registers: AtomicUsize::new(0),
29484 });
29485 registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
29486
29487 let later_cancels = Arc::new(AtomicUsize::new(0));
29488 let later_drops = Arc::new(AtomicUsize::new(0));
29489 let later = Arc::new(PanickingCancelDropTimerDriver {
29490 cancels: Arc::clone(&later_cancels),
29491 drops: Arc::clone(&later_drops),
29492 });
29493 let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
29494 *earlier.later.lock().unwrap() = Some(later_id);
29495 drop(later);
29496
29497 let mut sleep = make_sleep(
29498 &backend,
29499 ®istry,
29500 Instant::now() + Duration::from_secs(60),
29501 );
29502 let mut cx = Context::from_waker(Waker::noop());
29503 let result = catch_unwind(AssertUnwindSafe(|| {
29504 let _ = Pin::new(&mut sleep).poll(&mut cx);
29505 }));
29506
29507 assert!(
29508 result.is_err(),
29509 "the earlier driver's register panic must still propagate"
29510 );
29511 assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
29512 assert_eq!(
29513 later_drops.load(Ordering::SeqCst),
29514 0,
29515 "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
29516 );
29517
29518 drop(sleep);
29519 assert_eq!(
29520 later_cancels.load(Ordering::SeqCst),
29521 1,
29522 "the later driver must remain owned for cancellation despite its registry removal"
29523 );
29524 assert_eq!(
29525 later_drops.load(Ordering::SeqCst),
29526 1,
29527 "the later driver's final destructor must run once behind containment"
29528 );
29529 assert_eq!(registry.pending_len(), 0);
29530 assert!(!executor.host_timers.has_pending());
29531 return;
29532 }
29533
29534 let output = std::process::Command::new(std::env::current_exe().unwrap())
29535 .arg("--exact")
29536 .arg(
29537 "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
29538 )
29539 .arg("--nocapture")
29540 .env(CHILD_ENV, "1")
29541 .output()
29542 .expect("the hostile host timer snapshot subprocess test must start");
29543 assert!(
29544 output.status.success(),
29545 "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
29546 output.status.code(),
29547 String::from_utf8_lossy(&output.stdout),
29548 String::from_utf8_lossy(&output.stderr)
29549 );
29550 }
29551
29552 #[test]
29553 fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
29554 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
29555
29556 if std::env::var_os(CHILD_ENV).is_some() {
29557 let metrics = Arc::new(RuntimeMetrics::default());
29558 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29559 let backend =
29560 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29561 let cancels = Arc::new(AtomicUsize::new(0));
29562 let drops = Arc::new(AtomicUsize::new(0));
29563 let hostile = Arc::new(PanickingCancelDropTimerDriver {
29564 cancels: Arc::clone(&cancels),
29565 drops: Arc::clone(&drops),
29566 });
29567 let healthy = ManualStubDriver::new();
29568 let registry = Arc::new(TimerDriverRegistry::default());
29569 let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
29570 registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
29571 let mut sleep = make_sleep(
29572 &backend,
29573 ®istry,
29574 Instant::now() + Duration::from_secs(60),
29575 );
29576 let mut cx = Context::from_waker(Waker::noop());
29577
29578 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29579 let timer_id = healthy.registers.lock().unwrap()[0].0;
29580
29581 registry.unregister(hostile_id);
29584 drop(hostile);
29585 drop(sleep);
29586 assert_eq!(cancels.load(Ordering::SeqCst), 1);
29587 assert_eq!(drops.load(Ordering::SeqCst), 1);
29588 assert_eq!(
29589 healthy.cancels.lock().unwrap().as_slice(),
29590 &[timer_id],
29591 "one hostile cancellation must not skip later armed drivers"
29592 );
29593 assert_eq!(registry.pending_len(), 0);
29594 assert!(!executor.host_timers.has_pending());
29595 return;
29596 }
29597
29598 let output = std::process::Command::new(std::env::current_exe().unwrap())
29599 .arg("--exact")
29600 .arg(
29601 "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
29602 )
29603 .arg("--nocapture")
29604 .env(CHILD_ENV, "1")
29605 .output()
29606 .expect("the hostile timer-driver cancellation subprocess test must start");
29607 assert!(
29608 output.status.success(),
29609 "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
29610 output.status.code(),
29611 String::from_utf8_lossy(&output.stdout),
29612 String::from_utf8_lossy(&output.stderr)
29613 );
29614 }
29615
29616 #[test]
29617 fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
29618 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
29619
29620 if std::env::var_os(CHILD_ENV).is_some() {
29621 let metrics = Arc::new(RuntimeMetrics::default());
29622 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29623 let backend =
29624 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29625 let driver = ManualStubDriver::new();
29626 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29627 let state = Arc::new(TestHostileRawWakerState {
29628 clones: AtomicUsize::new(0),
29629 drop_panics: AtomicUsize::new(0),
29630 panic_on_clone: Some(2),
29631 });
29632
29633 let result = catch_unwind(AssertUnwindSafe(|| {
29634 let mut sleep = make_sleep(
29635 &backend,
29636 ®istry,
29637 Instant::now() + Duration::from_secs(60),
29638 );
29639 let waker = test_hostile_waker(Arc::clone(&state));
29640 let mut cx = Context::from_waker(&waker);
29641 let _ = Pin::new(&mut sleep).poll(&mut cx);
29642 }));
29643 assert!(
29644 result.is_err(),
29645 "the original RawWaker clone panic must still propagate"
29646 );
29647 assert_eq!(state.clones.load(Ordering::SeqCst), 2);
29648 assert_eq!(
29649 state.drop_panics.load(Ordering::SeqCst),
29650 1,
29651 "the first clone must be destroyed once behind a contained boundary"
29652 );
29653 assert!(driver.registers.lock().unwrap().is_empty());
29654 assert_eq!(registry.pending_len(), 0);
29655 assert!(!executor.host_timers.has_pending());
29656 return;
29657 }
29658
29659 let output = std::process::Command::new(std::env::current_exe().unwrap())
29660 .arg("--exact")
29661 .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
29662 .arg("--nocapture")
29663 .env(CHILD_ENV, "1")
29664 .output()
29665 .expect("the hostile RawWaker subprocess test must start");
29666 assert!(
29667 output.status.success(),
29668 "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
29669 output.status.code(),
29670 String::from_utf8_lossy(&output.stdout),
29671 String::from_utf8_lossy(&output.stderr)
29672 );
29673 }
29674
29675 #[test]
29676 fn host_sleep_register_unwind_contains_unretained_waker_drop() {
29677 const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
29678
29679 if std::env::var_os(CHILD_ENV).is_some() {
29680 let metrics = Arc::new(RuntimeMetrics::default());
29681 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29682 let backend =
29683 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29684 let driver = Arc::new(PanicBeforeRetainingTimerDriver {
29685 registers: AtomicUsize::new(0),
29686 });
29687 let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
29688 let state = Arc::new(TestHostileRawWakerState {
29689 clones: AtomicUsize::new(0),
29690 drop_panics: AtomicUsize::new(0),
29691 panic_on_clone: None,
29692 });
29693 let mut sleep = make_sleep(
29694 &backend,
29695 ®istry,
29696 Instant::now() + Duration::from_secs(60),
29697 );
29698 let waker = test_hostile_waker(Arc::clone(&state));
29699 let mut cx = Context::from_waker(&waker);
29700
29701 let result = catch_unwind(AssertUnwindSafe(|| {
29702 let _ = Pin::new(&mut sleep).poll(&mut cx);
29703 }));
29704 assert!(
29705 result.is_err(),
29706 "the timer-driver register panic must still propagate"
29707 );
29708 assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
29709 assert_eq!(state.clones.load(Ordering::SeqCst), 3);
29710 assert_eq!(
29711 state.drop_panics.load(Ordering::SeqCst),
29712 1,
29713 "the unretained register argument must be destroyed once behind the proxy boundary"
29714 );
29715 assert_eq!(registry.pending_len(), 1);
29716 assert!(executor.host_timers.has_pending());
29717
29718 drop(sleep);
29719 assert_eq!(
29720 state.drop_panics.load(Ordering::SeqCst),
29721 3,
29722 "repoll and registry wakers must retain their existing contained cleanup"
29723 );
29724 assert_eq!(registry.pending_len(), 0);
29725 assert!(!executor.host_timers.has_pending());
29726 return;
29727 }
29728
29729 let output = std::process::Command::new(std::env::current_exe().unwrap())
29730 .arg("--exact")
29731 .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
29732 .arg("--nocapture")
29733 .env(CHILD_ENV, "1")
29734 .output()
29735 .expect("the hostile register-waker subprocess test must start");
29736 assert!(
29737 output.status.success(),
29738 "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
29739 output.status.code(),
29740 String::from_utf8_lossy(&output.stdout),
29741 String::from_utf8_lossy(&output.stderr)
29742 );
29743 }
29744
29745 #[test]
29746 fn host_sleep_rearms_on_next_live_driver_after_eviction() {
29747 let metrics = Arc::new(RuntimeMetrics::default());
29753 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29754 let backend =
29755 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29756
29757 let a = ManualStubDriver::new();
29758 let b = ManualStubDriver::new();
29759 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
29760 let deadline = Instant::now() + Duration::from_secs(60);
29761
29762 let (timer_id, b_id) = {
29763 let mut sleep = std::pin::pin!(make_sleep(&backend, ®istry, deadline));
29764 let mut cx = Context::from_waker(Waker::noop());
29765
29766 assert!(sleep.as_mut().poll(&mut cx).is_pending());
29768 let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
29769 assert_eq!(armed_deadline, deadline);
29770 assert!(
29771 executor.host_timers.has_pending(),
29772 "first poll must arm the wake-token"
29773 );
29774
29775 a.kill();
29779 let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
29780 assert!(sleep.as_mut().poll(&mut cx).is_pending());
29781 assert_eq!(
29782 b.registers.lock().unwrap().as_slice(),
29783 &[(timer_id, deadline)],
29784 "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
29785 );
29786 assert_eq!(
29787 a.cancels.lock().unwrap().as_slice(),
29788 &[timer_id],
29789 "the dead driver gets a best-effort cancel for the old arm"
29790 );
29791 assert_eq!(
29792 registry.current().map(|(id, _)| id),
29793 Some(b_id),
29794 "the dead driver was evicted"
29795 );
29796 assert!(
29797 executor.host_timers.has_pending(),
29798 "the wake-token survives the re-arm (still exactly this sleep's entry)"
29799 );
29800 (timer_id, b_id)
29801 };
29802
29803 let _ = b_id;
29806 assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
29807 assert!(
29808 !executor.host_timers.has_pending(),
29809 "drop must retire the wake-token"
29810 );
29811 }
29812
29813 #[test]
29814 fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
29815 let metrics = Arc::new(RuntimeMetrics::default());
29816 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29817 let backend =
29818 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29819 let responsive = Arc::new(StubHostTimerDriver::new());
29820 let starved = ManualStubDriver::new();
29821 let registry = Arc::new(TimerDriverRegistry::default());
29822 registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
29823 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
29826
29827 let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29828 let waker = Waker::from(Arc::clone(&wake_count));
29829 let mut cx = Context::from_waker(&waker);
29830 let deadline = Instant::now() + Duration::from_millis(500);
29835 let mut sleep = make_sleep(&backend, ®istry, deadline);
29836 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29837
29838 let responsive_timer = *responsive
29839 .timers
29840 .lock()
29841 .unwrap()
29842 .keys()
29843 .next()
29844 .expect("responsive timer must be armed");
29845 assert_eq!(
29846 starved.registers.lock().unwrap().as_slice(),
29847 &[(responsive_timer, deadline)],
29848 "every live host must receive the same timer identity and absolute deadline"
29849 );
29850
29851 let fire_bound = Instant::now() + Duration::from_secs(30);
29858 while wake_count.0.load(Ordering::SeqCst) == 0 {
29859 assert!(
29860 Instant::now() < fire_bound,
29861 "the responsive host must wake a sleep whose newest host is live but starved"
29862 );
29863 std::thread::sleep(Duration::from_millis(10));
29864 }
29865 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
29868 assert_eq!(
29869 starved.cancels.lock().unwrap().as_slice(),
29870 &[responsive_timer],
29871 "completion through the winning host must cancel the starved losing arm"
29872 );
29873 }
29874
29875 #[test]
29876 fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
29877 use std::sync::mpsc;
29878
29879 struct ReentrantTimerRegistrationWake {
29880 registry: Weak<TimerDriverRegistry>,
29881 wakes: AtomicUsize,
29882 reentered: AtomicBool,
29883 }
29884
29885 impl std::task::Wake for ReentrantTimerRegistrationWake {
29886 fn wake(self: Arc<Self>) {
29887 self.wakes.fetch_add(1, Ordering::SeqCst);
29888 if let Some(registry) = self.registry.upgrade() {
29889 let _ = registry.has_live_driver();
29890 self.reentered.store(true, Ordering::SeqCst);
29891 }
29892 }
29893 }
29894
29895 let metrics = Arc::new(RuntimeMetrics::default());
29896 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
29897 let backend =
29898 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
29899 let registry = Arc::new(TimerDriverRegistry::default());
29900 let starved = ManualStubDriver::new();
29901 registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
29902
29903 let deadline = Instant::now() + Duration::from_secs(2);
29910 let mut first = make_sleep(&backend, ®istry, deadline);
29911 let mut second = make_sleep(&backend, ®istry, deadline);
29912 let first_wake = Arc::new(ReentrantTimerRegistrationWake {
29913 registry: Arc::downgrade(®istry),
29914 wakes: AtomicUsize::new(0),
29915 reentered: AtomicBool::new(false),
29916 });
29917 let second_wake = Arc::new(ReentrantTimerRegistrationWake {
29918 registry: Arc::downgrade(®istry),
29919 wakes: AtomicUsize::new(0),
29920 reentered: AtomicBool::new(false),
29921 });
29922 let first_waker = Waker::from(Arc::clone(&first_wake));
29923 let second_waker = Waker::from(Arc::clone(&second_wake));
29924 let mut first_cx = Context::from_waker(&first_waker);
29925 let mut second_cx = Context::from_waker(&second_waker);
29926
29927 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
29928 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
29929 let initial = starved.registers.lock().unwrap().clone();
29930 let first_id = initial[0].0;
29931 let second_id = initial[1].0;
29932 assert_ne!(first_id, second_id);
29933 assert_eq!(
29934 registry.pending_len(),
29935 2,
29936 "one stable repoll entry is retained per sleep"
29937 );
29938
29939 let responsive = Arc::new(StubHostTimerDriver::new());
29940 let (registered_tx, registered_rx) = mpsc::sync_channel(1);
29941 let registration_registry = Arc::clone(®istry);
29942 let registration_driver = Arc::clone(&responsive);
29943 let registration = std::thread::spawn(move || {
29944 let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
29945 registered_tx.send(id).unwrap();
29946 });
29947 registered_rx
29951 .recv_timeout(Duration::from_secs(10))
29952 .expect("late driver registration or repoll callback re-entry deadlocked");
29953 registration.join().unwrap();
29954
29955 assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
29956 assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
29957 assert!(first_wake.reentered.load(Ordering::SeqCst));
29958 assert!(second_wake.reentered.load(Ordering::SeqCst));
29959 assert!(
29960 responsive.timers.lock().unwrap().is_empty(),
29961 "registration requests bounded re-polls instead of invoking driver callbacks inline"
29962 );
29963
29964 assert!(
29969 Instant::now() < deadline,
29970 "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
29971 this runner is starved beyond the margin this test budgets for"
29972 );
29973 assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
29974 assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
29975 let responsive_ids = responsive
29976 .timers
29977 .lock()
29978 .unwrap()
29979 .keys()
29980 .copied()
29981 .collect::<FxHashSet<_>>();
29982 assert_eq!(
29983 responsive_ids,
29984 FxHashSet::from_iter([first_id, second_id]),
29985 "every pending sleep must re-arm its existing identity on the new driver"
29986 );
29987 assert_eq!(
29988 starved
29989 .registers
29990 .lock()
29991 .unwrap()
29992 .iter()
29993 .filter(|(id, _)| *id == first_id)
29994 .count(),
29995 2,
29996 "the old host receives only the normal waker refresh on the one re-poll"
29997 );
29998 assert_eq!(
29999 starved
30000 .registers
30001 .lock()
30002 .unwrap()
30003 .iter()
30004 .filter(|(id, _)| *id == second_id)
30005 .count(),
30006 2,
30007 "the old host receives only the normal waker refresh on the one re-poll"
30008 );
30009 assert_eq!(
30010 registry.pending_len(),
30011 2,
30012 "late registration must not duplicate pending state"
30013 );
30014
30015 let fire_bound = Instant::now() + Duration::from_secs(30);
30022 while first_wake.wakes.load(Ordering::SeqCst) < 2
30023 || second_wake.wakes.load(Ordering::SeqCst) < 2
30024 {
30025 assert!(
30026 Instant::now() < fire_bound,
30027 "the late responsive host must fire both timers while the original host remains starved"
30028 );
30029 std::thread::sleep(Duration::from_millis(10));
30030 }
30031 assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
30032 assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
30033
30034 let mut cancelled = starved.cancels.lock().unwrap().clone();
30035 cancelled.sort_unstable();
30036 let mut expected = vec![first_id, second_id];
30037 expected.sort_unstable();
30038 assert_eq!(
30039 cancelled, expected,
30040 "completion must cancel both losing starved-host arms"
30041 );
30042 assert_eq!(
30043 registry.pending_len(),
30044 0,
30045 "completion retires every repoll entry"
30046 );
30047 assert!(!executor.host_timers.has_pending());
30048 }
30049
30050 #[test]
30051 fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
30052 let metrics = Arc::new(RuntimeMetrics::default());
30056 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30057 let backend =
30058 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30059
30060 let a = ManualStubDriver::new();
30061 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
30062 let mut sleep = std::pin::pin!(make_sleep(
30063 &backend,
30064 ®istry,
30065 Instant::now() + Duration::from_secs(60)
30066 ));
30067 let mut cx = Context::from_waker(Waker::noop());
30068 assert!(sleep.as_mut().poll(&mut cx).is_pending());
30069
30070 a.kill();
30071 let payload = catch_unwind(AssertUnwindSafe(|| {
30072 let _ = sleep.as_mut().poll(&mut cx);
30073 }))
30074 .expect_err("a sleep with every driver dead must panic, not pend silently");
30075 let message = payload
30076 .downcast_ref::<String>()
30077 .cloned()
30078 .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
30079 .expect("panic payload must be a message");
30080 assert!(
30081 message.contains("lost every live timer driver mid-sleep"),
30082 "the diagnostic must name the mid-flight driver loss, got: {message}"
30083 );
30084 }
30085
30086 #[test]
30087 fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
30088 let metrics = Arc::new(RuntimeMetrics::default());
30095 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30096 let backend =
30097 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30098
30099 let a = ManualStubDriver::new();
30100 let b = ManualStubDriver::new();
30101 let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
30102 let mut sleep = std::pin::pin!(make_sleep(
30103 &backend,
30104 ®istry,
30105 Instant::now() + Duration::from_secs(60)
30106 ));
30107
30108 let flag = Arc::new(WakeFlag::default());
30110 let waker = Waker::from(Arc::clone(&flag));
30111 let mut cx = Context::from_waker(&waker);
30112 assert!(sleep.as_mut().poll(&mut cx).is_pending());
30113 assert!(!flag.0.load(Ordering::SeqCst));
30114
30115 a.kill();
30118 registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
30119 let _ = registry.has_live_driver();
30120
30121 assert!(
30122 a.swept.load(Ordering::SeqCst),
30123 "the sweep must notify the swept driver"
30124 );
30125 assert!(
30126 flag.0.load(Ordering::SeqCst),
30127 "a sleep armed on the swept driver must be woken into re-selection, not stranded"
30128 );
30129
30130 assert!(sleep.as_mut().poll(&mut cx).is_pending());
30132 assert_eq!(
30133 b.registers.lock().unwrap().len(),
30134 1,
30135 "the re-poll must re-arm on the live driver"
30136 );
30137 }
30138
30139 #[test]
30140 fn current_thread_sleep_fires_through_stub_host_driver() {
30141 use std::sync::mpsc;
30144
30145 let (done_tx, done_rx) = mpsc::channel();
30146 let runner = std::thread::spawn(move || {
30147 let metrics = Arc::new(RuntimeMetrics::default());
30148 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30149 let backend =
30150 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30151 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30152
30153 let started = Instant::now();
30154 let mut future = std::pin::pin!(async {
30155 make_sleep(
30156 &backend,
30157 ®istry_with(driver),
30158 Instant::now() + Duration::from_millis(60),
30159 )
30160 .await;
30161 });
30162 executor.block_on(future.as_mut());
30163 assert!(started.elapsed() >= Duration::from_millis(60));
30164 assert!(
30165 !executor.host_timers.has_pending(),
30166 "a completed sleep must retire its pending-registry entry"
30167 );
30168 let _ = done_tx.send(());
30169 });
30170
30171 match done_rx.recv_timeout(Duration::from_secs(10)) {
30172 Ok(()) => runner.join().unwrap(),
30173 Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
30174 }
30175 }
30176
30177 #[test]
30178 fn current_thread_timer_shutdown_retires_pending_repoll_state() {
30179 let metrics = Arc::new(RuntimeMetrics::default());
30180 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30181 let backend =
30182 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30183 let drivers = Arc::new(TimerDriverRegistry::default());
30184 let driver = ManualStubDriver::new();
30185 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30186 let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
30187 let mut cx = Context::from_waker(Waker::noop());
30188
30189 assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30190 let timer_id = driver.registers.lock().unwrap()[0].0;
30191 assert_eq!(drivers.pending_len(), 1);
30192
30193 executor.host_timers.shutdown();
30194 assert_eq!(
30195 drivers.pending_len(),
30196 0,
30197 "shutdown must retire the registration-repoll index before the Sleep is dropped"
30198 );
30199 assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
30200 assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
30201 }
30202
30203 #[test]
30204 fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
30205 use std::sync::mpsc;
30206
30207 let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
30208 let backend = controller.backend();
30209 let drivers = Arc::new(TimerDriverRegistry::default());
30210 let driver = ManualStubDriver::new();
30211 drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30212 let sleep = make_sleep(
30213 &backend,
30214 &drivers,
30215 Instant::now() + Duration::from_secs(120),
30216 );
30217 drop(backend);
30218
30219 let (polled_tx, polled_rx) = mpsc::channel();
30220 let block_controller = Arc::clone(&controller);
30221 let blocker = std::thread::spawn(move || {
30222 let mut announced = false;
30223 let mut sleep = std::pin::pin!(sleep);
30224 let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
30225 let poll = sleep.as_mut().poll(cx);
30226 if !announced {
30227 announced = true;
30228 polled_tx.send(()).unwrap();
30229 }
30230 poll
30231 }));
30232 block_controller.block_on(future.as_mut());
30233 });
30234 polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
30235
30236 let (shutdown_tx, shutdown_rx) = mpsc::channel();
30237 let shutdown_controller = Arc::clone(&controller);
30238 let shutdown = std::thread::spawn(move || {
30239 shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30240 });
30241 shutdown_rx
30242 .recv_timeout(Duration::from_secs(2))
30243 .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
30244 .unwrap();
30245 shutdown.join().unwrap();
30246 blocker.join().unwrap();
30247 assert_eq!(driver.cancels.lock().unwrap().len(), 1);
30248 }
30249
30250 #[test]
30251 fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
30252 use std::sync::mpsc;
30263
30264 let (done_tx, done_rx) = mpsc::channel();
30265 let runner = std::thread::spawn(move || {
30266 let metrics = Arc::new(RuntimeMetrics::default());
30267 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30271 let backend =
30272 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30273 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30274
30275 let payload = catch_unwind(AssertUnwindSafe(|| {
30276 let mut future = std::pin::pin!(async {
30277 make_sleep(
30278 &backend,
30279 ®istry_with(driver),
30280 Instant::now() + Duration::from_millis(60),
30281 )
30282 .await;
30283 });
30284 executor.block_on(future.as_mut());
30285 }))
30286 .expect_err("a threadless park must self-detect even while a host timer is pending");
30287 let diagnostic = payload
30288 .downcast_ref::<BlockOnDeadlock>()
30289 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
30290 assert_eq!(
30291 diagnostic.kind,
30292 BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
30293 );
30294 assert_eq!(
30295 diagnostic.park_deadline, None,
30296 "the certain case carries no deadline"
30297 );
30298 let message = diagnostic.to_string();
30299 assert!(
30300 message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
30301 "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
30302 );
30303 let _ = done_tx.send(());
30304 });
30305
30306 match done_rx.recv_timeout(Duration::from_secs(10)) {
30307 Ok(()) => runner.join().unwrap(),
30308 Err(error) => panic!(
30309 "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})"
30310 ),
30311 }
30312 }
30313
30314 #[test]
30315 fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
30316 use std::sync::mpsc;
30325
30326 let (done_tx, done_rx) = mpsc::channel();
30327 let runner = std::thread::spawn(move || {
30328 let metrics = Arc::new(RuntimeMetrics::default());
30329 let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30330 let backend =
30331 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30332 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30333
30334 let _unpolled = make_sleep(
30337 &backend,
30338 ®istry_with(driver),
30339 Instant::now() + Duration::from_secs(60),
30340 );
30341
30342 let payload = catch_unwind(AssertUnwindSafe(|| {
30343 let mut future = std::pin::pin!(NeverReady);
30344 executor.block_on(future.as_mut());
30345 }))
30346 .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
30347 let diagnostic = payload
30348 .downcast_ref::<BlockOnDeadlock>()
30349 .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
30350 assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
30351 let _ = done_tx.send(());
30352 });
30353
30354 match done_rx.recv_timeout(Duration::from_secs(10)) {
30355 Ok(()) => runner.join().unwrap(),
30356 Err(error) => panic!(
30357 "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
30358 ),
30359 }
30360 }
30361
30362 #[cfg(not(target_family = "wasm"))]
30363 #[test]
30364 fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
30365 use std::sync::mpsc;
30370
30371 let (done_tx, done_rx) = mpsc::channel();
30372 let runner = std::thread::spawn(move || {
30373 let metrics = Arc::new(RuntimeMetrics::default());
30374 let executor = Arc::new(CurrentThreadExecutor::with_detection(
30386 metrics,
30387 false,
30388 Some(Duration::from_secs(2)),
30389 ));
30390 let backend =
30391 RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30392 let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
30393
30394 let started = Instant::now();
30395 let mut future = std::pin::pin!(async {
30396 make_sleep(
30397 &backend,
30398 ®istry_with(driver),
30399 Instant::now() + Duration::from_millis(200),
30400 )
30401 .await;
30402 });
30403 executor.block_on(future.as_mut());
30404 assert!(started.elapsed() >= Duration::from_millis(200));
30405 let _ = done_tx.send(());
30406 });
30407
30408 match done_rx.recv_timeout(Duration::from_secs(30)) {
30409 Ok(()) => runner.join().unwrap(),
30410 Err(error) => {
30411 panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
30412 }
30413 }
30414 }
30415
30416 #[test]
30417 fn host_timer_unrepresentable_grace_remains_live() {
30418 let registry = HostTimerRegistry::default();
30419 let fired = Arc::new(AtomicBool::new(false));
30420 let drivers = Arc::new(TimerDriverRegistry::default());
30421 let repoll = Arc::new(HostTimerRepoll::default());
30422 assert!(
30423 Instant::now().checked_add(Duration::MAX).is_none(),
30424 "Duration::MAX must exercise the unrepresentable-grace path"
30425 );
30426 assert!(
30427 registry.register(
30428 1,
30429 Instant::now(),
30430 futures::task::noop_waker(),
30431 &fired,
30432 &drivers,
30433 &repoll,
30434 ),
30435 "the open registry must accept the timer"
30436 );
30437
30438 assert!(
30439 registry.has_live_wait(Duration::MAX),
30440 "an unrepresentable grace deadline is effectively unbounded"
30441 );
30442 registry.remove(1);
30443 }
30444
30445 #[test]
30446 fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
30447 let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
30452 let expected = diagnostic.to_string();
30453 let payload: Box<dyn Any + Send> = Box::new(diagnostic);
30454 assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
30455 }
30456}