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>
21where
22 F: FnOnce() -> R + Send + 'static,
23 R: Send + 'static,
24{
25 if let Some(sys) = crate::System::try_current() {
26 sys.spawn_blocking(f)
27 } else {
28 ThreadPool::execute_inplace(f)
29 }
30}
31
32#[derive(Copy, Clone, Debug, PartialEq, Eq)]
37pub struct BlockingError;
38
39impl std::error::Error for BlockingError {}
40
41impl fmt::Display for BlockingError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 "Blocking task failed or was canceled".fmt(f)
44 }
45}
46
47#[derive(Debug)]
54pub struct BlockingResult<T> {
55 rx: oneshot::AsyncReceiver<Result<T, Box<dyn Any + Send>>>,
56 detached: Option<Arc<AtomicBool>>,
57}
58
59impl<T> BlockingResult<T> {
60 pub fn detach(self) {
64 if let Some(detached) = &self.detached {
65 detached.store(true, Ordering::Relaxed);
67 }
68 }
69}
70
71#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
78#[non_exhaustive]
79pub struct PoolMetrics {
80 pub thread_limit: usize,
82 pub threads: usize,
84 pub active_tasks: usize,
86 pub queue_depth: usize,
88 pub tasks: u64,
90 pub wait_duration: Duration,
92 pub busy_duration: Duration,
94}
95
96#[derive(Debug, Default)]
97struct PoolStats {
98 active: AtomicUsize,
99 tasks: AtomicU64,
100 wait: AtomicU64,
102 busy: AtomicU64,
103}
104
105fn as_nanos(d: Duration) -> u64 {
106 u64::try_from(d.as_nanos()).unwrap_or(u64::MAX)
107}
108
109type BoxedDispatchable = Box<dyn Dispatchable + Send>;
110
111pub(crate) trait Dispatchable: Send + 'static {
112 fn run(self: Box<Self>);
113}
114
115impl<F> Dispatchable for F
116where
117 F: FnOnce() + Send + 'static,
118{
119 fn run(self: Box<Self>) {
120 (*self)();
121 }
122}
123
124struct CounterGuard(Arc<AtomicUsize>);
126
127impl CounterGuard {
128 fn reserve(counter: &Arc<AtomicUsize>, limit: usize) -> Option<(Self, usize)> {
129 counter
130 .try_update(Ordering::AcqRel, Ordering::Acquire, |cnt| {
131 (cnt < limit).then_some(cnt + 1)
132 })
133 .ok()
134 .map(|cnt| (CounterGuard(counter.clone()), cnt))
135 }
136}
137
138impl Drop for CounterGuard {
139 fn drop(&mut self) {
140 self.0.fetch_sub(1, Ordering::AcqRel);
141 }
142}
143
144fn run_task(f: BoxedDispatchable) {
146 if let Err(payload) = panic::catch_unwind(panic::AssertUnwindSafe(|| f.run())) {
147 log::error!("Blocking pool task panicked during completion");
148 std::mem::forget(payload);
150 }
151}
152
153fn worker(
154 receiver_high_prio: Receiver<BoxedDispatchable>,
155 receiver_low_prio: Receiver<BoxedDispatchable>,
156 guard: CounterGuard,
157 thread_limit: usize,
158 timeout: Duration,
159) -> impl FnOnce() {
160 move || {
161 let mut guard = guard;
162 let mut sel = Select::new_biased();
163 sel.recv(&receiver_high_prio);
164 sel.recv(&receiver_low_prio);
165 loop {
166 let task = match sel.select_timeout(timeout) {
167 Ok(op) if op.index() == 0 => op.recv(&receiver_high_prio),
168 Ok(op) => op.recv(&receiver_low_prio),
169 Err(_) => {
170 let counter = guard.0.clone();
173 drop(guard);
174 fence(Ordering::SeqCst);
175 if receiver_high_prio.is_empty() {
176 return;
177 }
178 match CounterGuard::reserve(&counter, thread_limit) {
179 Some((g, _)) => guard = g,
180 None => return,
181 }
182 continue;
183 }
184 };
185 if let Ok(f) = task {
186 run_task(f);
187 } else {
188 while let Ok(f) = receiver_high_prio.try_recv() {
191 run_task(f);
192 }
193 return;
194 }
195 }
196 }
197}
198
199#[derive(Debug, Clone)]
206pub struct ThreadPool {
207 name: String,
208 sender_low_prio: Sender<BoxedDispatchable>,
209 receiver_low_prio: Receiver<BoxedDispatchable>,
210 sender_high_prio: Sender<BoxedDispatchable>,
211 receiver_high_prio: Receiver<BoxedDispatchable>,
212 counter: Arc<AtomicUsize>,
213 stats: Arc<PoolStats>,
214 thread_limit: usize,
215 recv_timeout: Duration,
216}
217
218impl ThreadPool {
219 pub fn new(name: &str, thread_limit: usize, recv_timeout: Duration) -> Self {
225 let (sender_low_prio, receiver_low_prio) = bounded(0);
226 let (sender_high_prio, receiver_high_prio) = unbounded();
227 Self {
228 sender_low_prio,
229 receiver_low_prio,
230 sender_high_prio,
231 receiver_high_prio,
232 thread_limit: thread_limit.max(1),
233 recv_timeout,
234 name: format!("{name}:pool-wrk"),
235 counter: Arc::new(AtomicUsize::new(0)),
236 stats: Arc::default(),
237 }
238 }
239
240 pub fn metrics(&self) -> PoolMetrics {
245 PoolMetrics {
246 thread_limit: self.thread_limit,
247 threads: self.counter.load(Ordering::Relaxed),
248 active_tasks: self.stats.active.load(Ordering::Relaxed),
249 queue_depth: self.receiver_high_prio.len(),
250 tasks: self.stats.tasks.load(Ordering::Relaxed),
251 wait_duration: Duration::from_nanos(self.stats.wait.load(Ordering::Relaxed)),
252 busy_duration: Duration::from_nanos(self.stats.busy.load(Ordering::Relaxed)),
253 }
254 }
255
256 pub(crate) fn execute_inplace<F, R>(f: F) -> BlockingResult<R>
257 where
258 F: FnOnce() -> R + Send + 'static,
259 R: Send + 'static,
260 {
261 let (tx, rx) = oneshot::async_channel();
262 let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
263 let _ = tx.send(result);
264 BlockingResult { rx, detached: None }
265 }
266
267 #[allow(clippy::missing_panics_doc)]
268 pub fn execute<F, R>(&self, f: F) -> BlockingResult<R>
280 where
281 F: FnOnce() -> R + Send + 'static,
282 R: Send + 'static,
283 {
284 let (tx, rx) = oneshot::async_channel();
285 let detached = Arc::new(AtomicBool::new(false));
286 let res = BlockingResult {
287 rx,
288 detached: Some(detached.clone()),
289 };
290 let stats = self.stats.clone();
291 let queued = Instant::now();
292 let f = Box::new(move || {
293 if !tx.is_closed() || {
296 fence(Ordering::Acquire);
297 detached.load(Ordering::Relaxed)
298 } {
299 let start = Instant::now();
300 stats.tasks.fetch_add(1, Ordering::Relaxed);
301 stats
302 .wait
303 .fetch_add(as_nanos(start - queued), Ordering::Relaxed);
304 stats.active.fetch_add(1, Ordering::Relaxed);
305
306 let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
307
308 stats.active.fetch_sub(1, Ordering::Relaxed);
309 stats
310 .busy
311 .fetch_add(as_nanos(start.elapsed()), Ordering::Relaxed);
312 let _ = tx.send(result);
313 }
314 });
315
316 let f = match self.sender_low_prio.try_send(f) {
318 Ok(()) => return res,
319 Err(TrySendError::Full(f)) => f,
320 Err(TrySendError::Disconnected(_)) => {
321 unreachable!("receiver should not all disconnected")
322 }
323 };
324
325 self.sender_high_prio
326 .send(f)
327 .expect("the channel should not be closed");
328 fence(Ordering::SeqCst);
331
332 if let Some((guard, idx)) = CounterGuard::reserve(&self.counter, self.thread_limit) {
333 let result = thread::Builder::new()
334 .name(format!("{}:{}", self.name, idx))
335 .spawn(worker(
336 self.receiver_high_prio.clone(),
337 self.receiver_low_prio.clone(),
338 guard,
339 self.thread_limit,
340 self.recv_timeout,
341 ));
342 if let Err(e) = result {
343 log::error!("Cannot start blocking pool thread: {e}");
344 while self.counter.load(Ordering::Acquire) == 0 {
347 if self.receiver_high_prio.try_recv().is_err() {
348 break;
349 }
350 }
351 }
352 }
353 res
354 }
355}
356
357impl<R> Future for BlockingResult<R> {
358 type Output = Result<R, BlockingError>;
359
360 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
361 let this = self.get_mut();
362
363 match Pin::new(&mut this.rx).poll(cx) {
364 Poll::Pending => Poll::Pending,
365 Poll::Ready(result) => Poll::Ready(
366 result
367 .map_err(|_| BlockingError)
368 .and_then(|res| res.map_err(|_| BlockingError)),
369 ),
370 }
371 }
372}
373
374#[cfg(test)]
375#[path = "../tests/unit/pool.rs"]
376mod tests;