1use crate::loom::sync::{Arc, Condvar, Mutex};
4use crate::loom::thread;
5use crate::runtime::blocking::schedule::BlockingSchedule;
6use crate::runtime::blocking::sharded::ShardedImpl;
7use crate::runtime::blocking::{shutdown, BlockingTask};
8use crate::runtime::builder::ThreadNameFn;
9use crate::runtime::task::{self, JoinHandle};
10use crate::runtime::{AutoBox, Builder, Callback, Handle};
11use crate::util::metric_atomics::MetricAtomicUsize;
12use crate::util::trace::{blocking_task, SpawnMeta};
13
14use std::collections::{HashMap, VecDeque};
15use std::fmt;
16use std::io;
17use std::sync::atomic::Ordering;
18use std::time::Duration;
19
20pub(crate) struct BlockingPool {
21 spawner: Spawner,
22 shutdown_rx: shutdown::Receiver,
23}
24
25#[derive(Clone)]
26pub(crate) struct Spawner {
27 inner: Arc<Inner>,
28}
29
30#[derive(Default)]
31pub(crate) struct SpawnerMetrics {
32 num_threads: MetricAtomicUsize,
33 num_idle_threads: MetricAtomicUsize,
34 queue_depth: MetricAtomicUsize,
35}
36
37impl SpawnerMetrics {
38 pub(super) fn num_threads(&self) -> usize {
39 self.num_threads.load(Ordering::Relaxed)
40 }
41
42 pub(super) fn num_idle_threads(&self) -> usize {
43 self.num_idle_threads.load(Ordering::Relaxed)
44 }
45
46 cfg_unstable_metrics! {
47 fn queue_depth(&self) -> usize {
48 self.queue_depth.load(Ordering::Relaxed)
49 }
50 }
51
52 fn inc_num_threads(&self) {
53 self.num_threads.increment();
54 }
55
56 pub(super) fn dec_num_threads(&self) {
57 self.num_threads.decrement();
58 }
59
60 pub(super) fn inc_num_idle_threads(&self) {
61 self.num_idle_threads.increment();
62 }
63
64 pub(super) fn dec_num_idle_threads(&self) -> usize {
65 self.num_idle_threads.decrement()
66 }
67
68 pub(super) fn inc_queue_depth(&self) {
69 self.queue_depth.increment();
70 }
71
72 pub(super) fn dec_queue_depth(&self) {
73 self.queue_depth.decrement();
74 }
75}
76
77struct Inner {
78 inner_impl: InnerImpl,
80
81 thread_name: ThreadNameFn,
83
84 stack_size: Option<usize>,
86
87 after_start: Option<Callback>,
89
90 before_stop: Option<Callback>,
92
93 thread_cap: usize,
95
96 scheduler_threads: usize,
98
99 keep_alive: Duration,
101
102 metrics: SpawnerMetrics,
104}
105
106enum InnerImpl {
108 Locked(LockedImpl),
109 Sharded(ShardedImpl),
110}
111
112struct LockedImpl {
114 mutex: Mutex<LockedInner>,
115 condvar: Condvar,
116}
117
118struct LockedInner {
119 queue: VecDeque<Task>,
120 num_notify: u32,
121 thread_mgmt_state: ThreadManagementState,
124}
125
126pub(super) type ShutdownHandles = (
130 Option<thread::JoinHandle<()>>,
131 HashMap<usize, thread::JoinHandle<()>>,
132);
133
134pub(super) struct ThreadManagementState {
136 pub(super) shutdown: bool,
137 pub(super) shutdown_tx: Option<shutdown::Sender>,
138 pub(super) last_exiting_thread: Option<thread::JoinHandle<()>>,
144 pub(super) worker_threads: HashMap<usize, thread::JoinHandle<()>>,
147 pub(super) worker_thread_index: usize,
150}
151
152impl ThreadManagementState {
153 pub(super) fn begin_shutdown(&mut self) -> Option<ShutdownHandles> {
157 if self.shutdown {
158 return None;
159 }
160 self.shutdown = true;
161 self.shutdown_tx = None;
162
163 let last_exited_thread = std::mem::take(&mut self.last_exiting_thread);
164 let workers = std::mem::take(&mut self.worker_threads);
165 Some((last_exited_thread, workers))
166 }
167
168 pub(super) fn worker_timed_out(
173 &mut self,
174 worker_thread_id: usize,
175 ) -> Option<thread::JoinHandle<()>> {
176 let my_handle = self.worker_threads.remove(&worker_thread_id);
177 std::mem::replace(&mut self.last_exiting_thread, my_handle)
178 }
179}
180
181pub(crate) struct Task {
182 task: task::UnownedTask<BlockingSchedule>,
183 mandatory: Mandatory,
184}
185
186#[derive(PartialEq, Eq)]
187pub(crate) enum Mandatory {
188 #[cfg_attr(any(not(feature = "fs"), target_os = "emscripten"), allow(dead_code))]
189 Mandatory,
190 NonMandatory,
191}
192
193pub(crate) enum SpawnError {
194 ShuttingDown,
196 NoThreads(io::Error),
199}
200
201impl From<SpawnError> for io::Error {
202 fn from(e: SpawnError) -> Self {
203 match e {
204 SpawnError::ShuttingDown => io::Error::other("blocking pool shutting down"),
205 SpawnError::NoThreads(e) => e,
206 }
207 }
208}
209
210impl Task {
211 pub(crate) fn new(task: task::UnownedTask<BlockingSchedule>, mandatory: Mandatory) -> Task {
212 Task { task, mandatory }
213 }
214
215 pub(super) fn shutdown(self) {
216 self.task.shutdown();
217 }
218
219 pub(super) fn run(self) {
220 self.task.run();
221 }
222
223 pub(super) fn shutdown_or_run_if_mandatory(self) {
224 match self.mandatory {
225 Mandatory::NonMandatory => self.task.shutdown(),
226 Mandatory::Mandatory => self.task.run(),
227 }
228 }
229}
230
231const KEEP_ALIVE: Duration = Duration::from_secs(10);
232
233#[track_caller]
237#[cfg_attr(target_os = "wasi", allow(dead_code))]
238pub(crate) fn spawn_blocking<F, R>(func: F) -> JoinHandle<R>
239where
240 F: FnOnce() -> R + Send + 'static,
241 R: Send + 'static,
242{
243 let rt = Handle::current();
244 rt.spawn_blocking(func)
245}
246
247cfg_io_blocking! {
248 #[cfg_attr(any(
249 all(loom, not(test)), test,
251 target_os = "emscripten", ), allow(dead_code))]
253 pub(crate) fn spawn_mandatory_blocking<F, R>(func: F) -> Option<JoinHandle<R>>
258 where
259 F: FnOnce() -> R + Send + 'static,
260 R: Send + 'static,
261 {
262 let rt = Handle::current();
263 rt.inner.blocking_spawner().spawn_mandatory_blocking(&rt, func)
264 }
265}
266
267impl BlockingPool {
270 pub(crate) fn new(
271 builder: &Builder,
272 thread_cap: usize,
273 scheduler_threads: usize,
274 ) -> BlockingPool {
275 let (shutdown_tx, shutdown_rx) = shutdown::channel();
276 let keep_alive = builder.keep_alive.unwrap_or(KEEP_ALIVE);
277
278 let thread_mgmt_state = ThreadManagementState {
279 shutdown: false,
280 shutdown_tx: Some(shutdown_tx),
281 last_exiting_thread: None,
282 worker_threads: HashMap::new(),
283 worker_thread_index: 0,
284 };
285
286 let inner_impl = if builder.sharded_blocking_queue {
287 InnerImpl::Sharded(ShardedImpl::new(thread_mgmt_state))
288 } else {
289 InnerImpl::Locked(LockedImpl::new(thread_mgmt_state))
290 };
291
292 BlockingPool {
293 spawner: Spawner {
294 inner: Arc::new(Inner {
295 inner_impl,
296 thread_name: builder.thread_name.clone(),
297 stack_size: builder.thread_stack_size,
298 after_start: builder.after_start.clone(),
299 before_stop: builder.before_stop.clone(),
300 thread_cap,
301 scheduler_threads,
302 keep_alive,
303 metrics: SpawnerMetrics::default(),
304 }),
305 },
306 shutdown_rx,
307 }
308 }
309
310 pub(crate) fn spawner(&self) -> &Spawner {
311 &self.spawner
312 }
313
314 pub(crate) fn shutdown(&mut self, timeout: Option<Duration>) {
315 let inner = &self.spawner.inner;
319 let (last_exited_thread, workers) = match inner.inner_impl.begin_shutdown(&inner.metrics) {
320 Some(x) => x,
321 None => return,
322 };
323
324 if self.shutdown_rx.wait(timeout) {
325 let _ = last_exited_thread.map(thread::JoinHandle::join);
326
327 #[cfg(loom)]
330 let workers: Vec<(usize, thread::JoinHandle<()>)> = {
331 let mut workers: Vec<_> = workers.into_iter().collect();
332 workers.sort_by_key(|(id, _)| *id);
333 workers
334 };
335
336 for (_id, handle) in workers {
337 let _ = handle.join();
338 }
339 }
340 }
341}
342
343impl Drop for BlockingPool {
344 fn drop(&mut self) {
345 self.shutdown(None);
346 }
347}
348
349impl fmt::Debug for BlockingPool {
350 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
351 fmt.debug_struct("BlockingPool").finish()
352 }
353}
354
355impl Spawner {
358 #[track_caller]
359 pub(crate) fn spawn_blocking<F, R>(&self, rt: &Handle, func: F) -> JoinHandle<R>
360 where
361 F: FnOnce() -> R + Send + 'static,
362 R: Send + 'static,
363 {
364 let fn_size = std::mem::size_of::<F>();
365 let (join_handle, spawn_result) = if AutoBox::<F>::SHOULD_BOX {
366 self.spawn_blocking_inner(
367 Box::new(func),
368 Mandatory::NonMandatory,
369 SpawnMeta::new_unnamed(fn_size),
370 rt,
371 )
372 } else {
373 self.spawn_blocking_inner(
374 func,
375 Mandatory::NonMandatory,
376 SpawnMeta::new_unnamed(fn_size),
377 rt,
378 )
379 };
380
381 match spawn_result {
382 Ok(()) => join_handle,
383 Err(SpawnError::ShuttingDown) => join_handle,
385 Err(SpawnError::NoThreads(e)) => {
386 panic!("OS can't spawn worker thread: {e}")
387 }
388 }
389 }
390
391 cfg_io_blocking! {
392 #[track_caller]
393 #[cfg_attr(any(
394 all(loom, not(test)), test,
396 target_os = "emscripten", ), allow(dead_code))]
398 pub(crate) fn spawn_mandatory_blocking<F, R>(&self, rt: &Handle, func: F) -> Option<JoinHandle<R>>
399 where
400 F: FnOnce() -> R + Send + 'static,
401 R: Send + 'static,
402 {
403 let fn_size = std::mem::size_of::<F>();
404 let (join_handle, spawn_result) = if AutoBox::<F>::SHOULD_BOX {
405 self.spawn_blocking_inner(
406 Box::new(func),
407 Mandatory::Mandatory,
408 SpawnMeta::new_unnamed(fn_size),
409 rt,
410 )
411 } else {
412 self.spawn_blocking_inner(
413 func,
414 Mandatory::Mandatory,
415 SpawnMeta::new_unnamed(fn_size),
416 rt,
417 )
418 };
419
420 if spawn_result.is_ok() {
421 Some(join_handle)
422 } else {
423 None
424 }
425 }
426 }
427
428 #[track_caller]
429 pub(crate) fn spawn_blocking_inner<F, R>(
430 &self,
431 func: F,
432 is_mandatory: Mandatory,
433 spawn_meta: SpawnMeta<'_>,
434 rt: &Handle,
435 ) -> (JoinHandle<R>, Result<(), SpawnError>)
436 where
437 F: FnOnce() -> R + Send + 'static,
438 R: Send + 'static,
439 {
440 let id = task::Id::next();
441 let fut =
442 blocking_task::<F, BlockingTask<F>>(BlockingTask::new(func), spawn_meta, id.as_u64());
443
444 let (task, handle) = task::unowned(
445 fut,
446 BlockingSchedule::new(rt),
447 id,
448 task::SpawnLocation::capture(),
449 );
450
451 let spawned = self.spawn_task(Task::new(task, is_mandatory), rt);
452 (handle, spawned)
453 }
454
455 pub(crate) fn num_blocking_threads(&self) -> usize {
456 self.inner
457 .metrics
458 .num_threads()
459 .saturating_sub(self.inner.scheduler_threads)
460 }
461
462 fn spawn_task(&self, task: Task, rt: &Handle) -> Result<(), SpawnError> {
463 self.inner
467 .inner_impl
468 .spawn_task(task, &self.inner.metrics, |thread_mgmt_state| {
469 if self.inner.metrics.num_threads() == self.inner.thread_cap {
472 return Ok(());
474 }
475
476 assert!(thread_mgmt_state.shutdown_tx.is_some());
477 let shutdown_tx = thread_mgmt_state.shutdown_tx.clone();
478
479 if let Some(shutdown_tx) = shutdown_tx {
480 let id = thread_mgmt_state.worker_thread_index;
481
482 match self.spawn_thread(shutdown_tx, rt, id) {
483 Ok(handle) => {
484 self.inner.metrics.inc_num_threads();
485 thread_mgmt_state.worker_thread_index += 1;
486 thread_mgmt_state.worker_threads.insert(id, handle);
487 }
488 Err(ref e)
489 if is_temporary_os_thread_error(e)
490 && self.num_blocking_threads() > 0 =>
491 {
492 }
496 Err(e) => {
497 return Err(SpawnError::NoThreads(e));
500 }
501 }
502 }
503
504 Ok(())
505 })
506 }
507
508 fn spawn_thread(
509 &self,
510 shutdown_tx: shutdown::Sender,
511 rt: &Handle,
512 id: usize,
513 ) -> io::Result<thread::JoinHandle<()>> {
514 let mut builder = thread::Builder::new().name((self.inner.thread_name)());
515
516 if let Some(stack_size) = self.inner.stack_size {
517 builder = builder.stack_size(stack_size);
518 }
519
520 let rt = rt.clone();
521
522 builder.spawn(move || {
523 let _enter = rt.enter();
525 rt.inner.blocking_spawner().inner.run(id);
526 drop(shutdown_tx);
527 })
528 }
529}
530
531cfg_unstable_metrics! {
532 impl Spawner {
533 pub(crate) fn num_idle_threads(&self) -> usize {
534 self.inner.metrics.num_idle_threads()
535 }
536
537 pub(crate) fn queue_depth(&self) -> usize {
538 self.inner.metrics.queue_depth()
539 }
540 }
541}
542
543impl InnerImpl {
549 fn spawn_task<F>(
550 &self,
551 task: Task,
552 metrics: &SpawnerMetrics,
553 on_no_idle: F,
554 ) -> Result<(), SpawnError>
555 where
556 F: FnOnce(&mut ThreadManagementState) -> Result<(), SpawnError>,
557 {
558 match self {
559 InnerImpl::Locked(l) => l.spawn_task(task, metrics, on_no_idle),
560 InnerImpl::Sharded(s) => s.spawn_task(task, metrics, on_no_idle),
561 }
562 }
563
564 fn run_worker(
565 &self,
566 metrics: &SpawnerMetrics,
567 keep_alive: Duration,
568 worker_thread_id: usize,
569 ) -> Option<thread::JoinHandle<()>> {
570 match self {
571 InnerImpl::Locked(l) => l.run_worker(metrics, keep_alive, worker_thread_id),
572 InnerImpl::Sharded(s) => s.run_worker(metrics, keep_alive, worker_thread_id),
573 }
574 }
575
576 fn begin_shutdown(&self, metrics: &SpawnerMetrics) -> Option<ShutdownHandles> {
577 match self {
578 InnerImpl::Locked(l) => l.begin_shutdown(),
579 InnerImpl::Sharded(s) => s.begin_shutdown(metrics),
580 }
581 }
582}
583
584impl LockedImpl {
589 fn new(thread_mgmt_state: ThreadManagementState) -> LockedImpl {
590 LockedImpl {
591 mutex: Mutex::new(LockedInner {
592 queue: VecDeque::new(),
593 num_notify: 0,
594 thread_mgmt_state,
595 }),
596 condvar: Condvar::new(),
597 }
598 }
599
600 fn spawn_task<F>(
604 &self,
605 task: Task,
606 metrics: &SpawnerMetrics,
607 on_no_idle: F,
608 ) -> Result<(), SpawnError>
609 where
610 F: FnOnce(&mut ThreadManagementState) -> Result<(), SpawnError>,
611 {
612 let mut locked = self.mutex.lock();
613
614 if locked.thread_mgmt_state.shutdown {
615 drop(locked);
616
617 task.task.shutdown();
621 return Err(SpawnError::ShuttingDown);
622 }
623
624 locked.queue.push_back(task);
625 metrics.inc_queue_depth();
626
627 if metrics.num_idle_threads() == 0 {
628 on_no_idle(&mut locked.thread_mgmt_state)?;
629 } else {
630 metrics.dec_num_idle_threads();
636 locked.num_notify += 1;
637 self.condvar.notify_one();
638 }
639
640 Ok(())
641 }
642
643 fn run_worker(
645 &self,
646 metrics: &SpawnerMetrics,
647 keep_alive: Duration,
648 worker_thread_id: usize,
649 ) -> Option<thread::JoinHandle<()>> {
650 let mut locked = self.mutex.lock();
651 let mut join_on_thread = None;
652 let mut is_counted_idle;
654
655 'main: loop {
656 while let Some(task) = locked.queue.pop_front() {
658 metrics.dec_queue_depth();
659 drop(locked);
660 task.run();
661
662 locked = self.mutex.lock();
663 }
664
665 metrics.inc_num_idle_threads();
667 is_counted_idle = true;
669
670 while !locked.thread_mgmt_state.shutdown {
671 let lock_result = self.condvar.wait_timeout(locked, keep_alive).unwrap();
672
673 locked = lock_result.0;
674 let timeout_result = lock_result.1;
675
676 if locked.num_notify != 0 {
677 locked.num_notify -= 1;
681 is_counted_idle = false;
685 break;
686 }
687
688 if !locked.thread_mgmt_state.shutdown && timeout_result.timed_out() {
692 join_on_thread = locked.thread_mgmt_state.worker_timed_out(worker_thread_id);
693
694 break 'main;
695 }
696
697 }
699
700 if locked.thread_mgmt_state.shutdown {
701 while let Some(task) = locked.queue.pop_front() {
703 metrics.dec_queue_depth();
704 drop(locked);
705
706 task.shutdown_or_run_if_mandatory();
707
708 locked = self.mutex.lock();
709 }
710
711 break;
712 }
713 }
714
715 metrics.dec_num_threads();
717
718 if is_counted_idle {
720 let prev_idle = metrics.dec_num_idle_threads();
724 assert_ne!(
725 prev_idle, 0,
726 "`num_idle_threads` underflowed on thread exit"
727 );
728 }
729
730 if locked.thread_mgmt_state.shutdown && metrics.num_threads() == 0 {
731 self.condvar.notify_one();
732 }
733
734 drop(locked);
735
736 join_on_thread
737 }
738
739 fn begin_shutdown(&self) -> Option<ShutdownHandles> {
743 let mut locked = self.mutex.lock();
744 let handles = locked.thread_mgmt_state.begin_shutdown()?;
745 self.condvar.notify_all();
746 Some(handles)
747 }
748}
749
750#[inline]
752fn is_temporary_os_thread_error(error: &io::Error) -> bool {
753 matches!(error.kind(), io::ErrorKind::WouldBlock)
754}
755
756impl Inner {
757 fn run(&self, worker_thread_id: usize) {
758 if let Some(f) = &self.after_start {
759 f();
760 }
761
762 let join_on_thread =
763 self.inner_impl
764 .run_worker(&self.metrics, self.keep_alive, worker_thread_id);
765
766 if let Some(f) = &self.before_stop {
767 f();
768 }
769
770 if let Some(handle) = join_on_thread {
771 let _ = handle.join();
772 }
773 }
774}
775
776impl fmt::Debug for Spawner {
777 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
778 fmt.debug_struct("blocking::Spawner").finish()
779 }
780}