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}