syncweb_core/daemon/
pool.rs1use std::fmt;
2
3use crate::error::{Result, SyncwebError};
4
5pub struct ManagedPool {
7 pool: rayon::ThreadPool,
8 name: String,
9 thread_count: usize,
10}
11
12impl ManagedPool {
13 pub fn new(name: impl Into<String>, thread_count: usize) -> Result<Self> {
21 let pool_name = name.into();
22 let normalized_threads = if thread_count == 0 {
23 std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get)
24 } else {
25 thread_count
26 };
27 let thread_name = pool_name.clone();
28 let pool = rayon::ThreadPoolBuilder::new()
29 .num_threads(normalized_threads)
30 .thread_name(move |index| format!("{thread_name}-{index}"))
31 .build()
32 .map_err(|error| SyncwebError::operation("failed to create managed thread pool", error))?;
33 Ok(Self {
34 pool,
35 name: pool_name,
36 thread_count: normalized_threads,
37 })
38 }
39
40 pub fn install<F, R>(&self, operation: F) -> R
42 where
43 F: FnOnce() -> R + Send,
44 R: Send,
45 {
46 self.pool.install(operation)
47 }
48
49 pub fn spawn_fifo<F>(&self, operation: F)
51 where
52 F: FnOnce() + Send + 'static,
53 {
54 self.pool.spawn_fifo(operation);
55 }
56
57 #[must_use]
59 pub const fn thread_count(&self) -> usize {
60 self.thread_count
61 }
62}
63
64impl fmt::Debug for ManagedPool {
65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 formatter
67 .debug_struct("ManagedPool")
68 .field("name", &self.name)
69 .field("thread_count", &self.thread_count)
70 .finish_non_exhaustive()
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use std::{sync::mpsc, thread, time::Duration};
77
78 use super::ManagedPool;
79
80 #[test]
81 fn custom_thread_count_is_fixed() -> crate::error::Result<()> {
82 let pool = ManagedPool::new("test-archive", 2)?;
83 if pool.thread_count() != 2 {
84 return Err(crate::error::SyncwebError::InvalidConfig(
85 "managed pool did not retain its configured thread count".to_owned(),
86 ));
87 }
88 Ok(())
89 }
90
91 #[test]
92 fn install_runs_on_named_pool_thread() -> crate::error::Result<()> {
93 let pool = ManagedPool::new("test-archive", 1)?;
94 let worker_name_result = pool.install(|| thread::current().name().map(str::to_owned));
95 let worker_name = worker_name_result
96 .ok_or_else(|| crate::error::SyncwebError::InvalidConfig("managed pool thread has no name".to_owned()))?;
97 if !worker_name.starts_with("test-archive-") {
98 return Err(crate::error::SyncwebError::InvalidConfig(
99 "managed pool worker name has an unexpected prefix".to_owned(),
100 ));
101 }
102 Ok(())
103 }
104
105 #[test]
106 fn spawn_fifo_executes() -> crate::error::Result<()> {
107 let pool = ManagedPool::new("test-archive", 1)?;
108 let (sender, receiver) = mpsc::channel();
109 pool.spawn_fifo(move || {
110 let _ = sender.send(());
111 });
112 receiver
113 .recv_timeout(Duration::from_secs(1))
114 .map_err(|error| crate::error::SyncwebError::operation("managed pool task did not run", error))?;
115 Ok(())
116 }
117}