1use crate::cx::Cx;
26use crate::runtime::blocking_pool::{BlockingPoolHandle, BlockingTaskHandle};
27use parking_lot::Mutex;
28use std::sync::Arc;
29use std::sync::atomic::{AtomicUsize, Ordering};
30use std::task::Waker;
31use std::thread;
32
33const MAX_FALLBACK_THREADS: usize = 256;
36
37static FALLBACK_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0);
39
40struct CancelOnDrop {
41 handle: BlockingTaskHandle,
42 done: bool,
43}
44
45impl CancelOnDrop {
46 fn new(handle: BlockingTaskHandle) -> Self {
47 Self {
48 handle,
49 done: false,
50 }
51 }
52
53 fn mark_done(&mut self) {
54 self.done = true;
55 }
56}
57
58impl Drop for CancelOnDrop {
59 fn drop(&mut self) {
60 if !self.done {
61 self.handle.cancel();
62 }
63 }
64}
65
66struct BlockingOneshotState<T> {
67 result: Option<std::thread::Result<T>>,
68 waker: Option<Waker>,
69 done: bool,
70 closed_without_result: bool,
71}
72
73struct BlockingOneshot<T> {
74 state: Arc<Mutex<BlockingOneshotState<T>>>,
75 sent: bool,
76}
77
78impl<T> BlockingOneshot<T> {
79 fn new() -> (Self, BlockingOneshotReceiver<T>) {
80 let state = Arc::new(Mutex::new(BlockingOneshotState {
81 result: None,
82 waker: None,
83 done: false,
84 closed_without_result: false,
85 }));
86 (
87 Self {
88 state: state.clone(),
89 sent: false,
90 },
91 BlockingOneshotReceiver {
92 state,
93 completed: false,
94 },
95 )
96 }
97
98 fn send(mut self, val: std::thread::Result<T>) {
99 let waker = {
100 let mut guard = self.state.lock();
101 guard.result = Some(val);
102 guard.done = true;
103 guard.closed_without_result = false;
104 guard.waker.take()
105 };
106 self.sent = true;
107 if let Some(waker) = waker {
108 waker.wake();
109 }
110 }
111}
112
113impl<T> Drop for BlockingOneshot<T> {
114 fn drop(&mut self) {
115 if self.sent {
116 return;
117 }
118
119 let waker = {
120 let mut guard = self.state.lock();
121 if guard.done {
122 return;
123 }
124 guard.done = true;
125 guard.closed_without_result = true;
126 guard.waker.take()
127 };
128
129 if let Some(waker) = waker {
130 waker.wake();
131 }
132 }
133}
134
135struct BlockingOneshotReceiver<T> {
136 state: Arc<Mutex<BlockingOneshotState<T>>>,
137 completed: bool,
138}
139
140impl<T> Drop for BlockingOneshotReceiver<T> {
141 fn drop(&mut self) {
142 self.state.lock().waker = None;
143 }
144}
145
146impl<T> std::future::Future for BlockingOneshotReceiver<T> {
147 type Output = T;
148
149 fn poll(
150 self: std::pin::Pin<&mut Self>,
151 cx: &mut std::task::Context<'_>,
152 ) -> std::task::Poll<Self::Output> {
153 let this = self.get_mut();
154 assert!(
155 !this.completed,
156 "blocking operation polled after completion"
157 );
158
159 let mut guard = this.state.lock();
160 if guard.done {
161 this.completed = true;
162 let result = guard.result.take();
163 let closed_without_result = guard.closed_without_result;
164 drop(guard);
165
166 result.map_or_else(
167 || {
168 if closed_without_result {
169 panic!("blocking operation ended without producing a result"); } else {
171 panic!("blocking operation polled after completion"); }
173 },
174 |result| match result {
175 Ok(val) => std::task::Poll::Ready(val),
176 Err(payload) => std::panic::resume_unwind(payload),
177 },
178 )
179 } else {
180 if !guard
181 .waker
182 .as_ref()
183 .is_some_and(|w| w.will_wake(cx.waker()))
184 {
185 guard.waker = Some(cx.waker().clone());
186 }
187 std::task::Poll::Pending
188 }
189 }
190}
191
192pub async fn spawn_blocking<F, T>(f: F) -> T
213where
214 F: FnOnce() -> T + Send + 'static,
215 T: Send + 'static,
216{
217 if let Some(cx) = Cx::current() {
218 if let Some(pool) = cx.blocking_pool_handle() {
219 return spawn_blocking_on_pool(pool, f).await;
220 }
221 return f();
223 }
224
225 spawn_blocking_on_thread(f).await
226}
227
228pub async fn spawn_blocking_io<F, T>(f: F) -> std::io::Result<T>
232where
233 F: FnOnce() -> std::io::Result<T> + Send + 'static,
234 T: Send + 'static,
235{
236 spawn_blocking(f).await
237}
238
239pub(crate) async fn spawn_blocking_on_pool<F, T>(pool: BlockingPoolHandle, f: F) -> T
240where
241 F: FnOnce() -> T + Send + 'static,
242 T: Send + 'static,
243{
244 let (tx, rx) = BlockingOneshot::new();
245 let handle = pool.spawn(move || {
246 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
247 tx.send(result);
248 });
249
250 let mut guard = CancelOnDrop::new(handle);
251 let result = rx.await;
252 guard.mark_done();
253 result
254}
255
256struct FallbackGuard;
257
258impl Drop for FallbackGuard {
259 fn drop(&mut self) {
260 FALLBACK_THREAD_COUNT.fetch_sub(1, Ordering::Release);
261 }
262}
263
264pub(crate) async fn spawn_blocking_on_thread<F, T>(f: F) -> T
265where
266 F: FnOnce() -> T + Send + 'static,
267 T: Send + 'static,
268{
269 loop {
272 let current = FALLBACK_THREAD_COUNT.load(Ordering::Relaxed);
273 if current < MAX_FALLBACK_THREADS {
274 if FALLBACK_THREAD_COUNT
275 .compare_exchange_weak(current, current + 1, Ordering::Release, Ordering::Relaxed)
276 .is_ok()
277 {
278 break;
279 }
280 } else {
281 crate::runtime::yield_now::yield_now().await;
284 }
285 }
286
287 let (tx, rx) = BlockingOneshot::new();
288
289 let f_cell = Arc::new(Mutex::new(Some(f)));
292 let f_for_thread = Arc::clone(&f_cell);
293 let thread_result = thread::Builder::new()
294 .name("asupersync-blocking".to_string())
295 .spawn(move || {
296 let _guard = FallbackGuard;
297 let f = f_for_thread
298 .lock()
299 .take()
300 .expect("spawn_blocking_on_thread fn missing");
301 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
302 tx.send(result);
303 });
304
305 match thread_result {
306 Ok(_) => rx.await,
307 Err(_err) => {
308 FALLBACK_THREAD_COUNT.fetch_sub(1, Ordering::Release);
309 let f = f_cell
310 .lock()
311 .take()
312 .expect("spawn_blocking_on_thread fn missing");
313 f()
314 }
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 #![allow(
321 clippy::pedantic,
322 clippy::nursery,
323 clippy::expect_fun_call,
324 clippy::map_unwrap_or,
325 clippy::cast_possible_wrap,
326 clippy::future_not_send
327 )]
328 use super::*;
329 use crate::conformance::{ConformanceTarget, LabRuntimeTarget, TestConfig};
330 use crate::runtime::yield_now::yield_now;
331 use crate::types::{Budget, RegionId, TaskId};
332 use futures_lite::future;
333 use serde_json::Value;
334 use std::sync::Arc;
335 use std::sync::atomic::{AtomicU32, Ordering};
336 use std::sync::{Condvar, Mutex as StdMutex};
337 use std::time::Duration;
338
339 fn init_test(name: &str) {
340 crate::test_utils::init_test_logging();
341 crate::test_phase!(name);
342 }
343
344 #[test]
345 fn spawn_blocking_returns_result() {
346 init_test("spawn_blocking_returns_result");
347 future::block_on(async {
348 let result = spawn_blocking(|| 42).await;
349 crate::assert_with_log!(result == 42, "result", 42, result);
350 });
351 crate::test_complete!("spawn_blocking_returns_result");
352 }
353
354 #[test]
355 fn spawn_blocking_io_returns_result() {
356 init_test("spawn_blocking_io_returns_result");
357 future::block_on(async {
358 let result = spawn_blocking_io(|| Ok::<_, std::io::Error>(42))
359 .await
360 .unwrap();
361 crate::assert_with_log!(result == 42, "result", 42, result);
362 });
363 crate::test_complete!("spawn_blocking_io_returns_result");
364 }
365
366 #[test]
367 fn spawn_blocking_io_propagates_error() {
368 init_test("spawn_blocking_io_propagates_error");
369 future::block_on(async {
370 let result: std::io::Result<()> = spawn_blocking_io(|| {
371 Err(std::io::Error::new(
372 std::io::ErrorKind::NotFound,
373 "test error",
374 ))
375 })
376 .await;
377 crate::assert_with_log!(result.is_err(), "is error", true, result.is_err());
378 });
379 crate::test_complete!("spawn_blocking_io_propagates_error");
380 }
381
382 #[test]
383 fn spawn_blocking_captures_closure() {
384 init_test("spawn_blocking_captures_closure");
385 future::block_on(async {
386 let counter = Arc::new(AtomicU32::new(0));
387 let counter_clone = Arc::clone(&counter);
388
389 spawn_blocking(move || {
390 counter_clone.fetch_add(1, Ordering::Relaxed);
391 })
392 .await;
393
394 let count = counter.load(Ordering::Relaxed);
395 crate::assert_with_log!(count == 1, "counter incremented", 1u32, count);
396 });
397 crate::test_complete!("spawn_blocking_captures_closure");
398 }
399
400 #[test]
401 fn spawn_blocking_uses_pool_when_current() {
402 init_test("spawn_blocking_uses_pool_when_current");
403 let pool = crate::runtime::BlockingPool::new(1, 1);
404 let cx = Cx::new_with_drivers(
405 RegionId::new_for_test(0, 1),
406 TaskId::new_for_test(0, 0),
407 Budget::INFINITE,
408 None,
409 None,
410 None,
411 None,
412 None,
413 )
414 .with_blocking_pool_handle(Some(pool.handle()));
415
416 let _guard = Cx::set_current(Some(cx));
417
418 let thread_name = future::block_on(async {
419 spawn_blocking(|| {
420 std::thread::current()
421 .name()
422 .unwrap_or("unnamed")
423 .to_string()
424 })
425 .await
426 });
427
428 crate::assert_with_log!(
429 thread_name.contains("-blocking-"),
430 "thread name uses pool",
431 true,
432 thread_name.contains("-blocking-")
433 );
434 crate::test_complete!("spawn_blocking_uses_pool_when_current");
435 }
436
437 #[test]
438 fn spawn_blocking_inline_when_no_pool() {
439 init_test("spawn_blocking_inline_when_no_pool");
440 let cx: Cx = Cx::for_testing();
441 let _guard = Cx::set_current(Some(cx));
442 let current_id = std::thread::current().id();
443
444 let thread_id =
445 future::block_on(async { spawn_blocking(|| std::thread::current().id()).await });
446
447 crate::assert_with_log!(
448 thread_id == current_id,
449 "same thread",
450 current_id,
451 thread_id
452 );
453 crate::test_complete!("spawn_blocking_inline_when_no_pool");
454 }
455
456 #[test]
457 fn spawn_blocking_runs_in_parallel() {
458 init_test("spawn_blocking_runs_in_parallel");
459 future::block_on(async {
460 let counter = Arc::new(AtomicU32::new(0));
461
462 let c1 = Arc::clone(&counter);
463 let h1 = spawn_blocking(move || {
464 thread::sleep(Duration::from_millis(10));
465 c1.fetch_add(1, Ordering::Relaxed);
466 1
467 });
468
469 let c2 = Arc::clone(&counter);
470 let h2 = spawn_blocking(move || {
471 thread::sleep(Duration::from_millis(10));
472 c2.fetch_add(1, Ordering::Relaxed);
473 2
474 });
475
476 let (r1, r2) = future::zip(h1, h2).await;
479
480 let count = counter.load(Ordering::Relaxed);
481 crate::assert_with_log!(count == 2, "both completed", 2u32, count);
482 crate::assert_with_log!(r1 == 1, "first result", 1, r1);
483 crate::assert_with_log!(r2 == 2, "second result", 2, r2);
484 });
485 crate::test_complete!("spawn_blocking_runs_in_parallel");
486 }
487
488 #[test]
489 fn spawn_blocking_pool_overflow_queues_under_lab_runtime() {
490 init_test("spawn_blocking_pool_overflow_queues_under_lab_runtime");
491
492 let config = TestConfig::new()
493 .with_seed(0x5A0B_B10C)
494 .with_tracing(true)
495 .with_max_steps(20_000);
496 let mut runtime = LabRuntimeTarget::create_runtime(config);
497 let pool = crate::runtime::BlockingPool::new(1, 1);
498 let pool_handle = pool.handle();
499 let checkpoints = Arc::new(StdMutex::new(Vec::<Value>::new()));
500 let gate = Arc::new((StdMutex::new(false), Condvar::new()));
501 let first_started = Arc::new(std::sync::atomic::AtomicBool::new(false));
502 let second_started = Arc::new(std::sync::atomic::AtomicBool::new(false));
503
504 let (first_value, second_value, queued_before_release, second_started_after, checkpoints) =
505 LabRuntimeTarget::block_on(&mut runtime, async move {
506 let cx = Cx::current().expect("lab runtime should install a current Cx");
507 let first_spawn_cx = cx.clone();
508 let second_spawn_cx = cx.clone();
509
510 let first_task = LabRuntimeTarget::spawn(&first_spawn_cx, Budget::INFINITE, {
511 let pool_handle = pool_handle.clone();
512 let checkpoints = Arc::clone(&checkpoints);
513 let gate = Arc::clone(&gate);
514 let first_started = Arc::clone(&first_started);
515 async move {
516 spawn_blocking_on_pool(pool_handle, move || {
517 first_started.store(true, Ordering::SeqCst);
518 let started = serde_json::json!({
519 "phase": "first_started",
520 });
521 tracing::info!(event = %started, "spawn_blocking_lab_checkpoint");
522 checkpoints
523 .lock()
524 .unwrap_or_else(std::sync::PoisonError::into_inner)
525 .push(started);
526
527 let (lock, cvar) = &*gate;
528 let mut released = lock
529 .lock()
530 .unwrap_or_else(std::sync::PoisonError::into_inner);
531 while !*released {
532 released = cvar
533 .wait(released)
534 .unwrap_or_else(std::sync::PoisonError::into_inner);
535 }
536
537 let completed = serde_json::json!({
538 "phase": "first_completed",
539 "value": 11,
540 });
541 tracing::info!(event = %completed, "spawn_blocking_lab_checkpoint");
542 checkpoints
543 .lock()
544 .unwrap_or_else(std::sync::PoisonError::into_inner)
545 .push(completed);
546 11
547 })
548 .await
549 }
550 });
551
552 while !first_started.load(Ordering::SeqCst) {
553 yield_now().await;
554 }
555
556 let second_task = LabRuntimeTarget::spawn(&second_spawn_cx, Budget::INFINITE, {
557 let pool_handle = pool_handle.clone();
558 let checkpoints = Arc::clone(&checkpoints);
559 let second_started = Arc::clone(&second_started);
560 async move {
561 spawn_blocking_on_pool(pool_handle, move || {
562 second_started.store(true, Ordering::SeqCst);
563 let started = serde_json::json!({
564 "phase": "second_started",
565 });
566 tracing::info!(event = %started, "spawn_blocking_lab_checkpoint");
567 checkpoints
568 .lock()
569 .unwrap_or_else(std::sync::PoisonError::into_inner)
570 .push(started);
571 22
572 })
573 .await
574 }
575 });
576
577 yield_now().await;
578 yield_now().await;
579
580 let queued_before_release = !second_started.load(Ordering::SeqCst);
581 let queued = serde_json::json!({
582 "phase": "queue_observed",
583 "second_started": second_started.load(Ordering::SeqCst),
584 });
585 tracing::info!(event = %queued, "spawn_blocking_lab_checkpoint");
586 checkpoints
587 .lock()
588 .unwrap_or_else(std::sync::PoisonError::into_inner)
589 .push(queued);
590
591 {
592 let (lock, cvar) = &*gate;
593 let mut released = lock
594 .lock()
595 .unwrap_or_else(std::sync::PoisonError::into_inner);
596 *released = true;
597 cvar.notify_all();
598 }
599
600 let first_outcome = first_task.await;
601 crate::assert_with_log!(
602 matches!(first_outcome, crate::types::Outcome::Ok(11)),
603 "first blocking task completes successfully",
604 true,
605 matches!(first_outcome, crate::types::Outcome::Ok(11))
606 );
607 let crate::types::Outcome::Ok(first_value) = first_outcome else {
608 panic!("first blocking task should finish successfully");
609 };
610
611 let second_outcome = second_task.await;
612 crate::assert_with_log!(
613 matches!(second_outcome, crate::types::Outcome::Ok(22)),
614 "second blocking task completes successfully",
615 true,
616 matches!(second_outcome, crate::types::Outcome::Ok(22))
617 );
618 let crate::types::Outcome::Ok(second_value) = second_outcome else {
619 panic!("second blocking task should finish successfully");
620 };
621
622 (
623 first_value,
624 second_value,
625 queued_before_release,
626 second_started.load(Ordering::SeqCst),
627 checkpoints
628 .lock()
629 .unwrap_or_else(std::sync::PoisonError::into_inner)
630 .clone(),
631 )
632 });
633
634 assert_eq!(first_value, 11);
635 assert_eq!(second_value, 22);
636 assert!(
637 queued_before_release,
638 "second blocking task should remain queued while the single worker is occupied"
639 );
640 assert!(
641 second_started_after,
642 "second blocking task should eventually start after the first releases the worker"
643 );
644 assert!(
645 checkpoints
646 .iter()
647 .any(|event| event["phase"] == "first_started"),
648 "first task start checkpoint should be recorded"
649 );
650 assert!(
651 checkpoints.iter().any(|event| {
652 event["phase"] == "queue_observed" && event["second_started"] == false
653 }),
654 "queue observation checkpoint should record that the second task was still queued"
655 );
656 assert!(
657 checkpoints
658 .iter()
659 .any(|event| event["phase"] == "second_started"),
660 "second task start checkpoint should be recorded"
661 );
662
663 let violations = runtime.oracles.check_all(runtime.now());
664 assert!(
665 violations.is_empty(),
666 "spawn_blocking lab-runtime overflow test should leave runtime invariants clean: {violations:?}"
667 );
668 assert!(
669 pool.shutdown_and_wait(Duration::from_secs(1)),
670 "blocking pool should shut down cleanly after the test"
671 );
672 }
673
674 #[test]
675 fn blocking_oneshot_sender_drop_fails_closed() {
676 init_test("blocking_oneshot_sender_drop_fails_closed");
677 let (tx, rx) = BlockingOneshot::<u32>::new();
678 drop(tx);
679
680 let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
681 future::block_on(rx);
682 }));
683
684 let payload = panic.expect_err("receiver should fail closed when sender drops");
685 let message = payload
686 .downcast_ref::<&str>()
687 .map(ToString::to_string)
688 .or_else(|| payload.downcast_ref::<String>().cloned())
689 .unwrap_or_default();
690
691 crate::assert_with_log!(
692 message.contains("without producing a result"),
693 "receiver panic message",
694 true,
695 message.contains("without producing a result")
696 );
697 crate::test_complete!("blocking_oneshot_sender_drop_fails_closed");
698 }
699
700 #[test]
701 fn blocking_oneshot_success_repoll_fails_closed() {
702 init_test("blocking_oneshot_success_repoll_fails_closed");
703 let (tx, rx) = BlockingOneshot::<u32>::new();
704 tx.send(Ok(42));
705
706 let mut rx = Box::pin(rx);
707 let first = future::block_on(std::future::poll_fn(|cx| rx.as_mut().poll(cx)));
708 crate::assert_with_log!(first == 42, "first result", 42u32, first);
709
710 let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
711 future::block_on(std::future::poll_fn(|cx| rx.as_mut().poll(cx)));
712 }));
713
714 let payload = panic.expect_err("second poll should fail closed");
715 let message = payload
716 .downcast_ref::<&str>()
717 .map(ToString::to_string)
718 .or_else(|| payload.downcast_ref::<String>().cloned())
719 .unwrap_or_default();
720
721 crate::assert_with_log!(
722 message.contains("polled after completion"),
723 "repoll panic message",
724 true,
725 message.contains("polled after completion")
726 );
727 crate::test_complete!("blocking_oneshot_success_repoll_fails_closed");
728 }
729
730 #[test]
731 fn blocking_oneshot_sender_drop_repoll_fails_closed() {
732 init_test("blocking_oneshot_sender_drop_repoll_fails_closed");
733 let (tx, rx) = BlockingOneshot::<u32>::new();
734 drop(tx);
735
736 let mut rx = Box::pin(rx);
737 let first = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
738 future::block_on(std::future::poll_fn(|cx| rx.as_mut().poll(cx)));
739 }));
740 let first_payload = first.expect_err("first poll should fail closed on sender drop");
741 let first_message = first_payload
742 .downcast_ref::<&str>()
743 .map(ToString::to_string)
744 .or_else(|| first_payload.downcast_ref::<String>().cloned())
745 .unwrap_or_default();
746 crate::assert_with_log!(
747 first_message.contains("without producing a result"),
748 "first sender-drop panic message",
749 true,
750 first_message.contains("without producing a result")
751 );
752
753 let second = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
754 future::block_on(std::future::poll_fn(|cx| rx.as_mut().poll(cx)));
755 }));
756 let second_payload = second.expect_err("second poll should fail closed");
757 let second_message = second_payload
758 .downcast_ref::<&str>()
759 .map(ToString::to_string)
760 .or_else(|| second_payload.downcast_ref::<String>().cloned())
761 .unwrap_or_default();
762 crate::assert_with_log!(
763 second_message.contains("polled after completion"),
764 "second sender-drop panic message",
765 true,
766 second_message.contains("polled after completion")
767 );
768 crate::test_complete!("blocking_oneshot_sender_drop_repoll_fails_closed");
769 }
770
771 #[test]
772 #[should_panic(expected = "test panic")]
773 fn spawn_blocking_propagates_panic() {
774 future::block_on(async {
775 spawn_blocking(|| std::panic::panic_any("test panic")).await;
776 });
777 }
778}