1use std::sync::Arc;
3use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering, fence};
4use std::task::{Context, Poll};
5use std::time::{Duration, Instant};
6use std::{any::Any, fmt, future::Future, panic, pin::Pin, thread};
7
8use crossbeam_channel::{Receiver, Select, Sender, TrySendError, bounded, unbounded};
9
10pub fn spawn_blocking<F, R>(f: F) -> BlockingResult<R>
19where
20 F: FnOnce() -> R + Send + 'static,
21 R: Send + 'static,
22{
23 if let Some(sys) = crate::System::try_current() {
24 sys.spawn_blocking(f)
25 } else {
26 ThreadPool::execute_inplace(f)
27 }
28}
29
30#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub struct BlockingError;
36
37impl std::error::Error for BlockingError {}
38
39impl fmt::Display for BlockingError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 "Blocking task failed or was canceled".fmt(f)
42 }
43}
44
45#[derive(Debug)]
47pub struct BlockingResult<T> {
48 rx: oneshot::AsyncReceiver<Result<T, Box<dyn Any + Send>>>,
49 detached: Option<Arc<AtomicBool>>,
50}
51
52impl<T> BlockingResult<T> {
53 pub fn detach(self) {
57 if let Some(detached) = &self.detached {
58 detached.store(true, Ordering::Relaxed);
60 }
61 }
62}
63
64#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
71#[non_exhaustive]
72pub struct PoolMetrics {
73 pub thread_limit: usize,
75 pub threads: usize,
77 pub active_tasks: usize,
79 pub queue_depth: usize,
81 pub tasks: u64,
83 pub wait_duration: Duration,
85 pub busy_duration: Duration,
87}
88
89#[derive(Debug, Default)]
90struct PoolStats {
91 active: AtomicUsize,
92 tasks: AtomicU64,
93 wait: AtomicU64,
95 busy: AtomicU64,
96}
97
98fn as_nanos(d: Duration) -> u64 {
99 u64::try_from(d.as_nanos()).unwrap_or(u64::MAX)
100}
101
102type BoxedDispatchable = Box<dyn Dispatchable + Send>;
103
104pub(crate) trait Dispatchable: Send + 'static {
105 fn run(self: Box<Self>);
106}
107
108impl<F> Dispatchable for F
109where
110 F: FnOnce() + Send + 'static,
111{
112 fn run(self: Box<Self>) {
113 (*self)();
114 }
115}
116
117struct CounterGuard(Arc<AtomicUsize>);
119
120impl CounterGuard {
121 fn reserve(counter: &Arc<AtomicUsize>, limit: usize) -> Option<(Self, usize)> {
122 counter
123 .try_update(Ordering::AcqRel, Ordering::Acquire, |cnt| {
124 (cnt < limit).then_some(cnt + 1)
125 })
126 .ok()
127 .map(|cnt| (CounterGuard(counter.clone()), cnt))
128 }
129}
130
131impl Drop for CounterGuard {
132 fn drop(&mut self) {
133 self.0.fetch_sub(1, Ordering::AcqRel);
134 }
135}
136
137fn run_task(f: BoxedDispatchable) {
139 if let Err(payload) = panic::catch_unwind(panic::AssertUnwindSafe(|| f.run())) {
140 log::error!("Blocking pool task panicked during completion");
141 std::mem::forget(payload);
143 }
144}
145
146fn worker(
147 receiver_high_prio: Receiver<BoxedDispatchable>,
148 receiver_low_prio: Receiver<BoxedDispatchable>,
149 guard: CounterGuard,
150 thread_limit: usize,
151 timeout: Duration,
152) -> impl FnOnce() {
153 move || {
154 let mut guard = guard;
155 let mut sel = Select::new_biased();
156 sel.recv(&receiver_high_prio);
157 sel.recv(&receiver_low_prio);
158 loop {
159 let task = match sel.select_timeout(timeout) {
160 Ok(op) if op.index() == 0 => op.recv(&receiver_high_prio),
161 Ok(op) => op.recv(&receiver_low_prio),
162 Err(_) => {
163 let counter = guard.0.clone();
166 drop(guard);
167 fence(Ordering::SeqCst);
168 if receiver_high_prio.is_empty() {
169 return;
170 }
171 match CounterGuard::reserve(&counter, thread_limit) {
172 Some((g, _)) => guard = g,
173 None => return,
174 }
175 continue;
176 }
177 };
178 if let Ok(f) = task {
179 run_task(f);
180 } else {
181 while let Ok(f) = receiver_high_prio.try_recv() {
184 run_task(f);
185 }
186 return;
187 }
188 }
189 }
190}
191
192#[derive(Debug, Clone)]
201pub struct ThreadPool {
202 name: String,
203 sender_low_prio: Sender<BoxedDispatchable>,
204 receiver_low_prio: Receiver<BoxedDispatchable>,
205 sender_high_prio: Sender<BoxedDispatchable>,
206 receiver_high_prio: Receiver<BoxedDispatchable>,
207 counter: Arc<AtomicUsize>,
208 stats: Arc<PoolStats>,
209 thread_limit: usize,
210 recv_timeout: Duration,
211}
212
213impl ThreadPool {
214 pub fn new(name: &str, thread_limit: usize, recv_timeout: Duration) -> Self {
219 let (sender_low_prio, receiver_low_prio) = bounded(0);
220 let (sender_high_prio, receiver_high_prio) = unbounded();
221 Self {
222 sender_low_prio,
223 receiver_low_prio,
224 sender_high_prio,
225 receiver_high_prio,
226 thread_limit: thread_limit.max(1),
227 recv_timeout,
228 name: format!("{name}:pool-wrk"),
229 counter: Arc::new(AtomicUsize::new(0)),
230 stats: Arc::default(),
231 }
232 }
233
234 pub fn metrics(&self) -> PoolMetrics {
239 PoolMetrics {
240 thread_limit: self.thread_limit,
241 threads: self.counter.load(Ordering::Relaxed),
242 active_tasks: self.stats.active.load(Ordering::Relaxed),
243 queue_depth: self.receiver_high_prio.len(),
244 tasks: self.stats.tasks.load(Ordering::Relaxed),
245 wait_duration: Duration::from_nanos(self.stats.wait.load(Ordering::Relaxed)),
246 busy_duration: Duration::from_nanos(self.stats.busy.load(Ordering::Relaxed)),
247 }
248 }
249
250 pub(crate) fn execute_inplace<F, R>(f: F) -> BlockingResult<R>
251 where
252 F: FnOnce() -> R + Send + 'static,
253 R: Send + 'static,
254 {
255 let (tx, rx) = oneshot::async_channel();
256 let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
257 let _ = tx.send(result);
258 BlockingResult { rx, detached: None }
259 }
260
261 #[allow(clippy::missing_panics_doc)]
262 pub fn execute<F, R>(&self, f: F) -> BlockingResult<R>
269 where
270 F: FnOnce() -> R + Send + 'static,
271 R: Send + 'static,
272 {
273 let (tx, rx) = oneshot::async_channel();
274 let detached = Arc::new(AtomicBool::new(false));
275 let res = BlockingResult {
276 rx,
277 detached: Some(detached.clone()),
278 };
279 let stats = self.stats.clone();
280 let queued = Instant::now();
281 let f = Box::new(move || {
282 if !tx.is_closed() || {
285 fence(Ordering::Acquire);
286 detached.load(Ordering::Relaxed)
287 } {
288 let start = Instant::now();
289 stats.tasks.fetch_add(1, Ordering::Relaxed);
290 stats
291 .wait
292 .fetch_add(as_nanos(start - queued), Ordering::Relaxed);
293 stats.active.fetch_add(1, Ordering::Relaxed);
294
295 let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
296
297 stats.active.fetch_sub(1, Ordering::Relaxed);
298 stats
299 .busy
300 .fetch_add(as_nanos(start.elapsed()), Ordering::Relaxed);
301 let _ = tx.send(result);
302 }
303 });
304
305 let f = match self.sender_low_prio.try_send(f) {
307 Ok(()) => return res,
308 Err(TrySendError::Full(f)) => f,
309 Err(TrySendError::Disconnected(_)) => {
310 unreachable!("receiver should not all disconnected")
311 }
312 };
313
314 self.sender_high_prio
315 .send(f)
316 .expect("the channel should not be closed");
317 fence(Ordering::SeqCst);
320
321 if let Some((guard, idx)) = CounterGuard::reserve(&self.counter, self.thread_limit) {
322 let result = thread::Builder::new()
323 .name(format!("{}:{}", self.name, idx))
324 .spawn(worker(
325 self.receiver_high_prio.clone(),
326 self.receiver_low_prio.clone(),
327 guard,
328 self.thread_limit,
329 self.recv_timeout,
330 ));
331 if let Err(e) = result {
332 log::error!("Cannot start blocking pool thread: {e}");
333 while self.counter.load(Ordering::Acquire) == 0 {
336 if self.receiver_high_prio.try_recv().is_err() {
337 break;
338 }
339 }
340 }
341 }
342 res
343 }
344}
345
346impl<R> Future for BlockingResult<R> {
347 type Output = Result<R, BlockingError>;
348
349 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
350 let this = self.get_mut();
351
352 match Pin::new(&mut this.rx).poll(cx) {
353 Poll::Pending => Poll::Pending,
354 Poll::Ready(result) => Poll::Ready(
355 result
356 .map_err(|_| BlockingError)
357 .and_then(|res| res.map_err(|_| BlockingError)),
358 ),
359 }
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use std::time::Instant;
366
367 use super::*;
368
369 fn wait<R>(fut: BlockingResult<R>, timeout: Duration) -> Option<Result<R, BlockingError>> {
370 let mut fut = std::pin::pin!(fut);
371 let mut cx = Context::from_waker(std::task::Waker::noop());
372 let start = Instant::now();
373 loop {
374 if let Poll::Ready(res) = fut.as_mut().poll(&mut cx) {
375 return Some(res);
376 }
377 if start.elapsed() > timeout {
378 return None;
379 }
380 thread::sleep(Duration::from_millis(1));
381 }
382 }
383
384 #[test]
385 fn thread_limit_respected() {
386 let pool = ThreadPool::new("test", 2, Duration::from_secs(1));
387 let running = Arc::new(AtomicUsize::new(0));
388 let max = Arc::new(AtomicUsize::new(0));
389 let barrier = Arc::new(std::sync::Barrier::new(8));
390
391 let submitters: Vec<_> = (0..8)
393 .map(|_| {
394 let (pool, running, max, barrier) =
395 (pool.clone(), running.clone(), max.clone(), barrier.clone());
396 thread::spawn(move || {
397 barrier.wait();
398 (0..8)
399 .map(|_| {
400 let (running, max) = (running.clone(), max.clone());
401 pool.execute(move || {
402 let cnt = running.fetch_add(1, Ordering::SeqCst) + 1;
403 max.fetch_max(cnt, Ordering::SeqCst);
404 thread::sleep(Duration::from_millis(5));
405 running.fetch_sub(1, Ordering::SeqCst);
406 })
407 })
408 .collect::<Vec<_>>()
409 })
410 })
411 .collect();
412 for s in submitters {
413 for res in s.join().unwrap() {
414 assert_eq!(wait(res, Duration::from_secs(10)), Some(Ok(())));
415 }
416 }
417 assert!(max.load(Ordering::SeqCst) <= 2, "{max:?} tasks ran at once");
418 }
419
420 #[test]
421 fn idle_workers_do_not_strand_tasks() {
422 let pool = ThreadPool::new("test", 1, Duration::from_millis(2));
423 for i in 0..300u64 {
424 thread::sleep(Duration::from_micros(1500 + (i % 10) * 100));
426 let res = wait(pool.execute(move || i), Duration::from_secs(5));
427 assert_eq!(res, Some(Ok(i)), "task {i} was not executed");
428 }
429 }
430
431 #[test]
432 fn spawn_blocking_without_system() {
433 thread::spawn(|| {
434 let tid = thread::current().id();
435 let res = spawn_blocking(move || thread::current().id() == tid);
436 assert_eq!(wait(res, Duration::from_secs(1)), Some(Ok(true)));
437
438 let res = spawn_blocking(|| panic!("blocking"));
439 assert_eq!(
440 wait(res, Duration::from_secs(1)),
441 Some(Err::<(), _>(BlockingError))
442 );
443 })
444 .join()
445 .unwrap();
446 assert_eq!(
447 BlockingError.to_string(),
448 "Blocking task failed or was canceled"
449 );
450 }
451
452 #[test]
453 fn detached_blocking_task_runs() {
454 crate::System::new("test", crate::testing::TestRunner).block_on(async {
455 let (tx, rx) = oneshot::async_channel();
456 spawn_blocking(move || tx.send(1).unwrap()).detach();
457 assert_eq!(rx.await, Ok(1));
458 });
459 }
460
461 #[test]
462 fn detach_without_runtime() {
463 thread::spawn(|| spawn_blocking(|| 1).detach())
464 .join()
465 .unwrap();
466
467 let pool = ThreadPool::new("test", 1, Duration::from_secs(1));
469 let (start_tx, start_rx) = std::sync::mpsc::channel();
470 let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
471 let first = pool.execute(move || {
472 start_tx.send(()).unwrap();
473 release_rx.recv().unwrap();
474 });
475 start_rx.recv().unwrap();
476
477 let dropped = Arc::new(AtomicBool::new(false));
478 let dropped2 = dropped.clone();
479 drop(pool.execute(move || dropped2.store(true, Ordering::Relaxed)));
480 let (tx, rx) = std::sync::mpsc::channel();
481 pool.execute(move || tx.send(1).unwrap()).detach();
482 release_tx.send(()).unwrap();
483
484 assert_eq!(rx.recv_timeout(Duration::from_secs(5)), Ok(1));
485 assert_eq!(wait(first, Duration::from_secs(5)), Some(Ok(())));
486 assert!(!dropped.load(Ordering::Relaxed));
488 }
489
490 #[test]
491 fn metrics() {
492 let pool = ThreadPool::new("test", 1, Duration::from_secs(1));
493 let m = pool.metrics();
494 assert_eq!((m.thread_limit, m.threads, m.tasks), (1, 0, 0));
495
496 let (start_tx, start_rx) = std::sync::mpsc::channel();
498 let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
499 let first = pool.execute(move || {
500 start_tx.send(()).unwrap();
501 release_rx.recv().unwrap();
502 });
503 start_rx.recv().unwrap();
504 let second = pool.execute(|| ());
505
506 let m = pool.metrics();
507 assert_eq!((m.threads, m.active_tasks, m.queue_depth), (1, 1, 1));
508 assert_eq!(m.tasks, 1);
509
510 let sleep = Duration::from_millis(20);
511 thread::sleep(sleep);
512 release_tx.send(()).unwrap();
513 assert_eq!(wait(first, Duration::from_secs(5)), Some(Ok(())));
514 assert_eq!(wait(second, Duration::from_secs(5)), Some(Ok(())));
515
516 let m = pool.metrics();
517 assert_eq!((m.active_tasks, m.queue_depth, m.tasks), (0, 0, 2));
518 assert!(m.wait_duration >= sleep);
519 assert!(m.busy_duration >= sleep);
520 }
521
522 #[test]
523 fn dropped_pool_releases_workers() {
524 let pool = ThreadPool::new("test", 1, Duration::from_secs(60));
526 let counter = pool.counter.clone();
527 let (start_tx, start_rx) = std::sync::mpsc::channel();
528 let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
529 let first = pool.execute(move || {
530 start_tx.send(()).unwrap();
531 release_rx.recv().unwrap();
532 });
533 start_rx.recv().unwrap();
534 let queued = pool.execute(|| 1);
535 drop(pool);
536 release_tx.send(()).unwrap();
537
538 assert_eq!(wait(first, Duration::from_secs(5)), Some(Ok(())));
539 assert_eq!(wait(queued, Duration::from_secs(5)), Some(Ok(1)));
540 let start = Instant::now();
541 while counter.load(Ordering::Acquire) != 0 {
542 assert!(start.elapsed() < Duration::from_secs(5), "worker is alive");
543 thread::sleep(Duration::from_millis(1));
544 }
545 }
546
547 #[test]
548 fn result_drop_panic_keeps_worker() {
549 struct PanicOnDrop;
550 impl Drop for PanicOnDrop {
551 fn drop(&mut self) {
552 panic!("result destructor");
553 }
554 }
555
556 let pool = ThreadPool::new("test", 1, Duration::from_secs(60));
557 let (start_tx, start_rx) = std::sync::mpsc::channel();
558 let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
559 let first = pool.execute(move || {
560 start_tx.send(()).unwrap();
561 release_rx.recv().unwrap();
562 PanicOnDrop
563 });
564 start_rx.recv().unwrap();
565 let queued = pool.execute(|| 1);
566 drop(first);
568 release_tx.send(()).unwrap();
569
570 assert_eq!(wait(queued, Duration::from_secs(2)), Some(Ok(1)));
571 assert_eq!(pool.metrics().threads, 1);
572 }
573
574 #[test]
575 fn zero_thread_limit() {
576 let pool = ThreadPool::new("test", 0, Duration::from_secs(1));
577 assert_eq!(
578 wait(pool.execute(|| 1), Duration::from_secs(5)),
579 Some(Ok(1))
580 );
581 }
582}