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}