1use flume::{Receiver, Sender};
7use futures::channel::oneshot;
8use std::{sync::Arc, thread};
9
10use crate::prelude::*;
11
12#[derive(Clone, Copy, Debug)]
13pub enum Priority {
14 High,
15 Medium,
16 Low,
17}
18
19#[derive(Debug)]
20pub struct WorkerPool {
21 high: Sender<Box<dyn FnOnce() + Send>>,
22 med: Sender<Box<dyn FnOnce() + Send>>,
23 low: Sender<Box<dyn FnOnce() + Send>>,
24}
25
26impl WorkerPool {
27 pub fn new(n1: usize, n2: usize, n3: usize) -> Self {
28 Self::build(n1, n2, n3).0
29 }
30
31 fn build(n1: usize, n2: usize, n3: usize) -> (Self, std::sync::Weak<()>) {
34 let (high, rx_high) = flume::unbounded();
35 let (med, rx_med) = flume::unbounded();
36 let (low, rx_low) = flume::unbounded();
37
38 let rx_high = Arc::new(rx_high);
39 let rx_med = Arc::new(rx_med);
40 let rx_low = Arc::new(rx_low);
41
42 let alive = Arc::new(());
43 let witness = Arc::downgrade(&alive);
44
45 for _ in 0..n1 {
47 let rx_high = Arc::clone(&rx_high);
48 let alive = Arc::clone(&alive);
49 thread::spawn(move || {
50 worker_loop(&[rx_high]);
51 drop(alive);
52 });
53 }
54
55 for _ in 0..n2 {
57 let rx_high = Arc::clone(&rx_high);
58 let rx_med = Arc::clone(&rx_med);
59 let alive = Arc::clone(&alive);
60 thread::spawn(move || {
61 worker_loop(&[rx_high, rx_med]);
62 drop(alive);
63 });
64 }
65
66 for _ in 0..n3 {
68 let rx_high = Arc::clone(&rx_high);
69 let rx_med = Arc::clone(&rx_med);
70 let rx_low = Arc::clone(&rx_low);
71 let alive = Arc::clone(&alive);
72 thread::spawn(move || {
73 worker_loop(&[rx_high, rx_med, rx_low]);
74 drop(alive);
75 });
76 }
77
78 drop(alive);
79 (Self { high, med, low }, witness)
80 }
81
82 pub fn spawn<F, T>(
84 &self,
85 priority: Priority,
86 f: F,
87 ) -> impl std::future::Future<Output = ClResult<T>> + use<F, T>
88 where
89 F: FnOnce() -> T + Send + 'static,
90 T: Send + 'static,
91 {
92 let (res_tx, res_rx) = oneshot::channel();
93
94 let job = Box::new(move || {
95 let result = f();
96 let _ = res_tx.send(result);
97 });
98
99 match priority {
100 Priority::High => {
101 if self.high.send(job).is_err() {
102 error!("Failed to send job to high priority worker queue");
103 }
104 }
105 Priority::Medium => {
106 if self.med.send(job).is_err() {
107 error!("Failed to send job to medium priority worker queue");
108 }
109 }
110 Priority::Low => {
111 if self.low.send(job).is_err() {
112 error!("Failed to send job to low priority worker queue");
113 }
114 }
115 }
116
117 async move {
118 res_rx.await.map_err(|_| {
119 error!("Worker dropped result channel (task may have panicked)");
120 Error::Internal("worker task failed".into())
121 })
122 }
123 }
124
125 pub fn run<F, T>(&self, f: F) -> impl std::future::Future<Output = ClResult<T>> + use<F, T>
126 where
127 F: FnOnce() -> T + Send + 'static,
128 T: Send + 'static,
129 {
130 let (res_tx, res_rx) = oneshot::channel();
131
132 let job = Box::new(move || {
133 let result = f();
134 let _ignore = res_tx.send(result);
135 });
136
137 if self.med.send(job).is_err() {
138 error!("Failed to send job to medium priority worker queue");
139 }
140
141 async move {
142 res_rx.await.map_err(|_| {
143 error!("Worker dropped result channel (task may have panicked)");
144 Error::Internal("worker task failed".into())
145 })
146 }
147 }
148
149 pub fn run_immed<F, T>(
150 &self,
151 f: F,
152 ) -> impl std::future::Future<Output = ClResult<T>> + use<F, T>
153 where
154 F: FnOnce() -> T + Send + 'static,
155 T: Send + 'static,
156 {
157 let (res_tx, res_rx) = oneshot::channel();
158
159 let job = Box::new(move || {
160 let result = f();
161 let _ignore = res_tx.send(result);
162 });
163
164 if self.high.send(job).is_err() {
165 error!("Failed to send job to high priority worker queue");
166 }
167
168 async move {
169 res_rx.await.map_err(|_| {
170 error!("Worker dropped result channel (task may have panicked)");
171 Error::Internal("worker task failed".into())
172 })
173 }
174 }
175
176 pub fn run_slow<F, T>(&self, f: F) -> impl std::future::Future<Output = ClResult<T>> + use<F, T>
177 where
178 F: FnOnce() -> T + Send + 'static,
179 T: Send + 'static,
180 {
181 let (res_tx, res_rx) = oneshot::channel();
182
183 let job = Box::new(move || {
184 let result = f();
185 let _ignore = res_tx.send(result);
186 });
187
188 if self.low.send(job).is_err() {
189 error!("Failed to send job to low priority worker queue");
190 }
191
192 async move {
193 res_rx.await.map_err(|_| {
194 error!("Worker dropped result channel (task may have panicked)");
195 Error::Internal("worker task failed".into())
196 })
197 }
198 }
199
200 pub fn try_run<F, T>(&self, f: F) -> impl std::future::Future<Output = ClResult<T>> + use<F, T>
203 where
204 F: FnOnce() -> ClResult<T> + Send + 'static,
205 T: Send + 'static,
206 {
207 let fut = self.run(f);
208 async move { fut.await? }
209 }
210
211 pub fn try_run_immed<F, T>(
214 &self,
215 f: F,
216 ) -> impl std::future::Future<Output = ClResult<T>> + use<F, T>
217 where
218 F: FnOnce() -> ClResult<T> + Send + 'static,
219 T: Send + 'static,
220 {
221 let fut = self.run_immed(f);
222 async move { fut.await? }
223 }
224
225 pub fn try_run_slow<F, T>(
228 &self,
229 f: F,
230 ) -> impl std::future::Future<Output = ClResult<T>> + use<F, T>
231 where
232 F: FnOnce() -> ClResult<T> + Send + 'static,
233 T: Send + 'static,
234 {
235 let fut = self.run_slow(f);
236 async move { fut.await? }
237 }
238}
239
240type JobQueue = Arc<Receiver<Box<dyn FnOnce() + Send>>>;
241
242fn run_job(job: Box<dyn FnOnce() + Send>) {
243 if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(job)) {
244 error!("Worker thread caught panic: {:?}", e);
245 }
246}
247
248fn worker_loop(queues: &[JobQueue]) {
249 let mut live: Vec<&JobQueue> = queues.iter().collect();
254
255 loop {
256 let mut job = None;
258 for rx in &live {
259 if let Ok(j) = rx.try_recv() {
260 job = Some(j);
261 break;
262 }
263 }
264 if let Some(job) = job {
265 run_job(job);
266 continue;
267 }
268
269 live.retain(|rx| !rx.is_disconnected() || !rx.is_empty());
270 if live.is_empty() {
271 break;
272 }
273
274 let mut selector = flume::Selector::new();
276 for &rx in &live {
277 selector = selector.recv(rx, |res| res);
278 }
279 match selector.wait() {
280 Ok(job) => run_job(job),
281 Err(flume::RecvError::Disconnected) => (),
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
291
292 use super::*;
293 use std::sync::{
294 Weak,
295 atomic::{AtomicBool, Ordering},
296 };
297 use std::time::{Duration, Instant};
298
299 fn wait_for_exit(alive: &Weak<()>, timeout: Duration) -> usize {
301 let deadline = Instant::now() + timeout;
302 loop {
303 let count = alive.strong_count();
304 if count == 0 || Instant::now() >= deadline {
305 return count;
306 }
307 thread::sleep(Duration::from_millis(5));
308 }
309 }
310
311 #[test]
312 fn workers_exit_when_pool_is_dropped() {
313 let (pool, alive) = WorkerPool::build(1, 1, 1);
314 assert_eq!(alive.strong_count(), 3, "expected 3 worker threads");
315
316 drop(pool);
317
318 assert_eq!(
319 wait_for_exit(&alive, Duration::from_secs(2)),
320 0,
321 "worker threads did not exit after the pool was dropped"
322 );
323 }
324
325 #[test]
326 fn queued_jobs_run_before_workers_exit() {
327 let (pool, alive) = WorkerPool::build(0, 1, 0);
329
330 let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
332 let blocker = pool.run(move || {
333 let _ignore = release_rx.recv();
334 });
335
336 let ran = Arc::new(AtomicBool::new(false));
337 let flag = Arc::clone(&ran);
338 let queued = pool.run(move || flag.store(true, Ordering::SeqCst));
339
340 drop(pool);
341 drop(blocker);
342 drop(queued);
343
344 let _ignore = release_tx.send(());
346 drop(release_tx);
347
348 assert_eq!(
349 wait_for_exit(&alive, Duration::from_secs(2)),
350 0,
351 "worker thread did not exit after the pool was dropped"
352 );
353 assert!(ran.load(Ordering::SeqCst), "job queued before the pool dropped was never run");
354 }
355}
356
357