Skip to main content

qubit_thread_pool/fixed/
fixed_thread_pool_builder.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Builder for [`super::FixedThreadPool`].
11
12use std::thread;
13
14use super::fixed_thread_pool::FixedThreadPool;
15use crate::{
16    ExecutorServiceBuilderError,
17    ThreadPoolHooks,
18};
19
20/// Default thread name prefix used by [`FixedThreadPoolBuilder`].
21const DEFAULT_FIXED_THREAD_NAME_PREFIX: &str = "qubit-fixed-thread-pool";
22
23/// Builder for [`FixedThreadPool`].
24///
25/// The fixed pool prestarts exactly `pool_size` workers and never changes that
26/// count during runtime.
27#[derive(Debug, Clone)]
28pub struct FixedThreadPoolBuilder {
29    /// Number of workers to prestart.
30    pub(crate) pool_size: usize,
31    /// Optional maximum queued task count.
32    pub(crate) queue_capacity: Option<usize>,
33    /// Prefix used for worker thread names.
34    pub(crate) thread_name_prefix: String,
35    /// Optional worker stack size.
36    pub(crate) stack_size: Option<usize>,
37    /// Optional worker and task lifecycle hooks.
38    pub(crate) hooks: ThreadPoolHooks,
39}
40
41impl FixedThreadPoolBuilder {
42    /// Creates a builder with CPU parallelism defaults.
43    ///
44    /// # Returns
45    ///
46    /// A builder with a fixed worker count equal to available parallelism.
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Sets the fixed worker count.
52    ///
53    /// # Parameters
54    ///
55    /// * `pool_size` - Number of workers to create.
56    ///
57    /// # Returns
58    ///
59    /// This builder for fluent configuration.
60    pub fn pool_size(mut self, pool_size: usize) -> Self {
61        self.pool_size = pool_size;
62        self
63    }
64
65    /// Sets a bounded queue capacity.
66    ///
67    /// # Parameters
68    ///
69    /// * `capacity` - Maximum number of queued tasks.
70    ///
71    /// # Returns
72    ///
73    /// This builder for fluent configuration.
74    pub fn queue_capacity(mut self, capacity: usize) -> Self {
75        self.queue_capacity = Some(capacity);
76        self
77    }
78
79    /// Uses an unbounded queue.
80    ///
81    /// # Returns
82    ///
83    /// This builder for fluent configuration.
84    pub fn unbounded_queue(mut self) -> Self {
85        self.queue_capacity = None;
86        self
87    }
88
89    /// Sets the worker thread name prefix.
90    ///
91    /// # Parameters
92    ///
93    /// * `prefix` - Prefix used for worker thread names.
94    ///
95    /// # Returns
96    ///
97    /// This builder for fluent configuration.
98    pub fn thread_name_prefix(mut self, prefix: &str) -> Self {
99        self.thread_name_prefix = prefix.to_owned();
100        self
101    }
102
103    /// Sets the worker stack size.
104    ///
105    /// # Parameters
106    ///
107    /// * `stack_size` - Stack size in bytes.
108    ///
109    /// # Returns
110    ///
111    /// This builder for fluent configuration.
112    pub fn stack_size(mut self, stack_size: usize) -> Self {
113        self.stack_size = Some(stack_size);
114        self
115    }
116
117    /// Installs a callback invoked when a worker thread starts.
118    ///
119    /// # Parameters
120    ///
121    /// * `hook` - Callback receiving the stable worker index.
122    ///
123    /// # Returns
124    ///
125    /// This builder for fluent configuration.
126    pub fn before_worker_start<F>(mut self, hook: F) -> Self
127    where
128        F: Fn(usize) + Send + Sync + 'static,
129    {
130        self.hooks = self.hooks.before_worker_start(hook);
131        self
132    }
133
134    /// Installs a callback invoked before a worker thread exits.
135    ///
136    /// # Parameters
137    ///
138    /// * `hook` - Callback receiving the stable worker index.
139    ///
140    /// # Returns
141    ///
142    /// This builder for fluent configuration.
143    pub fn after_worker_stop<F>(mut self, hook: F) -> Self
144    where
145        F: Fn(usize) + Send + Sync + 'static,
146    {
147        self.hooks = self.hooks.after_worker_stop(hook);
148        self
149    }
150
151    /// Installs a callback invoked before each job is run.
152    ///
153    /// # Parameters
154    ///
155    /// * `hook` - Callback receiving the stable worker index.
156    ///
157    /// # Returns
158    ///
159    /// This builder for fluent configuration.
160    pub fn before_task<F>(mut self, hook: F) -> Self
161    where
162        F: Fn(usize) + Send + Sync + 'static,
163    {
164        self.hooks = self.hooks.before_task(hook);
165        self
166    }
167
168    /// Installs a callback invoked after each job is run.
169    ///
170    /// # Parameters
171    ///
172    /// * `hook` - Callback receiving the stable worker index.
173    ///
174    /// # Returns
175    ///
176    /// This builder for fluent configuration.
177    pub fn after_task<F>(mut self, hook: F) -> Self
178    where
179        F: Fn(usize) + Send + Sync + 'static,
180    {
181        self.hooks = self.hooks.after_task(hook);
182        self
183    }
184
185    /// Builds the configured fixed thread pool.
186    ///
187    /// # Returns
188    ///
189    /// A fixed pool with all workers prestarted.
190    ///
191    /// # Errors
192    ///
193    /// Returns [`ExecutorServiceBuilderError`] when configuration is invalid or a
194    /// worker thread cannot be spawned.
195    pub fn build(self) -> Result<FixedThreadPool, ExecutorServiceBuilderError> {
196        self.validate()?;
197        FixedThreadPool::new_with_builder(self)
198    }
199
200    /// Validates this builder configuration.
201    ///
202    /// # Returns
203    ///
204    /// `Ok(())` when configuration is valid.
205    ///
206    /// # Errors
207    ///
208    /// Returns [`ExecutorServiceBuilderError`] for zero pool size, zero queue capacity,
209    /// or zero stack size.
210    fn validate(&self) -> Result<(), ExecutorServiceBuilderError> {
211        if self.pool_size == 0 {
212            return Err(ExecutorServiceBuilderError::ZeroPoolSize);
213        }
214        if self.queue_capacity == Some(0) {
215            return Err(ExecutorServiceBuilderError::ZeroQueueCapacity);
216        }
217        if self.stack_size == Some(0) {
218            return Err(ExecutorServiceBuilderError::ZeroStackSize);
219        }
220        Ok(())
221    }
222}
223
224impl Default for FixedThreadPoolBuilder {
225    /// Creates a builder using available CPU parallelism.
226    ///
227    /// # Returns
228    ///
229    /// Default fixed-pool builder.
230    fn default() -> Self {
231        Self {
232            pool_size: default_fixed_pool_size(),
233            queue_capacity: None,
234            thread_name_prefix: DEFAULT_FIXED_THREAD_NAME_PREFIX.to_owned(),
235            stack_size: None,
236            hooks: ThreadPoolHooks::default(),
237        }
238    }
239}
240
241/// Returns the default fixed worker count.
242///
243/// # Returns
244///
245/// Available CPU parallelism, or `1` if it cannot be detected.
246fn default_fixed_pool_size() -> usize {
247    thread::available_parallelism()
248        .map(usize::from)
249        .unwrap_or(1)
250}