Skip to main content

qubit_thread_pool/thread_pool/
thread_pool_builder.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9use std::{
10    sync::Arc,
11    thread,
12    time::Duration,
13};
14
15use super::thread_pool::ThreadPool;
16use super::thread_pool_build_error::ThreadPoolBuildError;
17use super::thread_pool_config::ThreadPoolConfig;
18use super::thread_pool_inner::ThreadPoolInner;
19
20/// Default thread name prefix used by [`ThreadPoolBuilder`].
21const DEFAULT_THREAD_NAME_PREFIX: &str = "qubit-thread-pool";
22
23/// Default idle lifetime for workers above the core pool size.
24const DEFAULT_KEEP_ALIVE: Duration = Duration::from_secs(60);
25
26/// Builder for [`ThreadPool`].
27///
28/// The default builder uses the available CPU parallelism as both core and
29/// maximum pool size, with an unbounded FIFO queue.
30///
31/// # Author
32///
33/// Haixing Hu
34#[derive(Debug, Clone)]
35pub struct ThreadPoolBuilder {
36    /// Configured core pool size.
37    core_pool_size: usize,
38    /// Configured maximum pool size.
39    maximum_pool_size: usize,
40    /// Optional maximum number of jobs that may wait in the queue.
41    queue_capacity: Option<usize>,
42    /// Prefix used when naming worker threads.
43    thread_name_prefix: String,
44    /// Optional stack size in bytes for worker threads.
45    stack_size: Option<usize>,
46    /// Idle timeout for workers allowed to retire.
47    keep_alive: Duration,
48    /// Whether core workers may retire after the keep-alive timeout.
49    allow_core_thread_timeout: bool,
50    /// Whether [`Self::build`] should start all core workers eagerly.
51    prestart_core_threads: bool,
52}
53
54impl ThreadPoolBuilder {
55    /// Sets both the core and maximum pool size to the same value.
56    ///
57    /// # Parameters
58    ///
59    /// * `pool_size` - Pool size applied as both core and maximum limits.
60    ///
61    /// # Returns
62    ///
63    /// This builder for fluent configuration.
64    #[inline]
65    pub fn pool_size(mut self, pool_size: usize) -> Self {
66        self.core_pool_size = pool_size;
67        self.maximum_pool_size = pool_size;
68        self
69    }
70
71    /// Sets the core pool size.
72    ///
73    /// A submitted task creates a new worker while the live worker count is
74    /// below this value. Once the core size is reached, tasks are queued before
75    /// the pool considers growing toward the maximum size.
76    ///
77    /// # Parameters
78    ///
79    /// * `core_pool_size` - Core pool size.
80    ///
81    /// # Returns
82    ///
83    /// This builder for fluent configuration.
84    #[inline]
85    pub fn core_pool_size(mut self, core_pool_size: usize) -> Self {
86        self.core_pool_size = core_pool_size;
87        self
88    }
89
90    /// Sets the maximum pool size.
91    ///
92    /// The pool grows above the core size only when the queue cannot accept a
93    /// submitted task.
94    ///
95    /// # Parameters
96    ///
97    /// * `maximum_pool_size` - Maximum pool size.
98    ///
99    /// # Returns
100    ///
101    /// This builder for fluent configuration.
102    #[inline]
103    pub fn maximum_pool_size(mut self, maximum_pool_size: usize) -> Self {
104        self.maximum_pool_size = maximum_pool_size;
105        self
106    }
107
108    /// Sets a bounded queue capacity.
109    ///
110    /// The capacity counts only tasks waiting in the queue. Tasks already held
111    /// by worker threads are not included.
112    ///
113    /// # Parameters
114    ///
115    /// * `capacity` - Maximum number of queued tasks.
116    ///
117    /// # Returns
118    ///
119    /// This builder for fluent configuration.
120    #[inline]
121    pub fn queue_capacity(mut self, capacity: usize) -> Self {
122        self.queue_capacity = Some(capacity);
123        self
124    }
125
126    /// Uses an unbounded queue.
127    ///
128    /// # Returns
129    ///
130    /// This builder for fluent configuration.
131    #[inline]
132    pub fn unbounded_queue(mut self) -> Self {
133        self.queue_capacity = None;
134        self
135    }
136
137    /// Sets the worker thread name prefix.
138    ///
139    /// Worker names are created by appending the worker index to this prefix.
140    ///
141    /// # Parameters
142    ///
143    /// * `prefix` - Prefix for worker thread names.
144    ///
145    /// # Returns
146    ///
147    /// This builder for fluent configuration.
148    #[inline]
149    pub fn thread_name_prefix(mut self, prefix: &str) -> Self {
150        self.thread_name_prefix = prefix.to_owned();
151        self
152    }
153
154    /// Sets the worker thread stack size.
155    ///
156    /// # Parameters
157    ///
158    /// * `stack_size` - Stack size in bytes for each worker thread.
159    ///
160    /// # Returns
161    ///
162    /// This builder for fluent configuration.
163    #[inline]
164    pub fn stack_size(mut self, stack_size: usize) -> Self {
165        self.stack_size = Some(stack_size);
166        self
167    }
168
169    /// Sets the idle timeout for workers above the core pool size.
170    ///
171    /// # Parameters
172    ///
173    /// * `keep_alive` - Duration an excess worker may stay idle.
174    ///
175    /// # Returns
176    ///
177    /// This builder for fluent configuration.
178    #[inline]
179    pub fn keep_alive(mut self, keep_alive: Duration) -> Self {
180        self.keep_alive = keep_alive;
181        self
182    }
183
184    /// Allows core workers to retire after the keep-alive timeout.
185    ///
186    /// # Parameters
187    ///
188    /// * `allow` - Whether idle core workers may time out.
189    ///
190    /// # Returns
191    ///
192    /// This builder for fluent configuration.
193    #[inline]
194    pub fn allow_core_thread_timeout(mut self, allow: bool) -> Self {
195        self.allow_core_thread_timeout = allow;
196        self
197    }
198
199    /// Starts all core workers during [`Self::build`].
200    ///
201    /// Without this option, workers are created lazily as tasks are submitted,
202    /// matching the default JDK `ThreadPoolExecutor` behavior.
203    ///
204    /// # Returns
205    ///
206    /// This builder for fluent configuration.
207    #[inline]
208    pub fn prestart_core_threads(mut self) -> Self {
209        self.prestart_core_threads = true;
210        self
211    }
212
213    /// Builds the configured thread pool.
214    ///
215    /// # Returns
216    ///
217    /// `Ok(ThreadPool)` if the configuration is valid and all requested
218    /// prestarted workers are spawned successfully.
219    ///
220    /// # Errors
221    ///
222    /// Returns [`ThreadPoolBuildError`] if the configuration is invalid or a
223    /// prestarted worker thread cannot be spawned.
224    pub fn build(self) -> Result<ThreadPool, ThreadPoolBuildError> {
225        self.validate()?;
226        let prestart_core_threads = self.prestart_core_threads;
227        let inner = Arc::new(ThreadPoolInner::new(ThreadPoolConfig {
228            core_pool_size: self.core_pool_size,
229            maximum_pool_size: self.maximum_pool_size,
230            queue_capacity: self.queue_capacity,
231            thread_name_prefix: self.thread_name_prefix,
232            stack_size: self.stack_size,
233            keep_alive: self.keep_alive,
234            allow_core_thread_timeout: self.allow_core_thread_timeout,
235        }));
236        if prestart_core_threads {
237            inner
238                .prestart_all_core_threads()
239                .map_err(ThreadPoolBuildError::from_rejected_execution)?;
240        }
241        Ok(ThreadPool::from_inner(inner))
242    }
243
244    /// Validates this builder configuration.
245    ///
246    /// # Returns
247    ///
248    /// `Ok(())` when all configured values are internally consistent.
249    ///
250    /// # Errors
251    ///
252    /// Returns [`ThreadPoolBuildError`] for zero maximum size, core size larger
253    /// than maximum size, zero bounded queue capacity, zero stack size, or zero
254    /// keep-alive timeout.
255    fn validate(&self) -> Result<(), ThreadPoolBuildError> {
256        if self.maximum_pool_size == 0 {
257            return Err(ThreadPoolBuildError::ZeroMaximumPoolSize);
258        }
259        if self.core_pool_size > self.maximum_pool_size {
260            return Err(ThreadPoolBuildError::CorePoolSizeExceedsMaximum {
261                core_pool_size: self.core_pool_size,
262                maximum_pool_size: self.maximum_pool_size,
263            });
264        }
265        if self.queue_capacity == Some(0) {
266            return Err(ThreadPoolBuildError::ZeroQueueCapacity);
267        }
268        if self.stack_size == Some(0) {
269            return Err(ThreadPoolBuildError::ZeroStackSize);
270        }
271        if self.keep_alive.is_zero() {
272            return Err(ThreadPoolBuildError::ZeroKeepAlive);
273        }
274        Ok(())
275    }
276}
277
278impl Default for ThreadPoolBuilder {
279    /// Creates a builder with CPU parallelism defaults.
280    ///
281    /// # Returns
282    ///
283    /// A builder configured with CPU parallelism for both core and maximum
284    /// sizes, an unbounded queue, and the default keep-alive timeout.
285    fn default() -> Self {
286        let pool_size = default_pool_size();
287        Self {
288            core_pool_size: pool_size,
289            maximum_pool_size: pool_size,
290            queue_capacity: None,
291            thread_name_prefix: DEFAULT_THREAD_NAME_PREFIX.to_owned(),
292            stack_size: None,
293            keep_alive: DEFAULT_KEEP_ALIVE,
294            allow_core_thread_timeout: false,
295            prestart_core_threads: false,
296        }
297    }
298}
299
300/// Returns the default core and maximum pool size for new builders.
301///
302/// # Returns
303///
304/// The available CPU parallelism, or `1` if it cannot be detected.
305fn default_pool_size() -> usize {
306    thread::available_parallelism()
307        .map(usize::from)
308        .unwrap_or(1)
309}