Skip to main content

qubit_thread_pool/fixed/
fixed_thread_pool.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 ******************************************************************************/
10use std::{
11    sync::Arc,
12    thread::JoinHandle,
13};
14
15use qubit_executor::service::{
16    ExecutorService,
17    ExecutorServiceLifecycle,
18    StopReport,
19    SubmissionError,
20};
21use qubit_executor::task::spi::TaskEndpointPair;
22use qubit_executor::{
23    TaskHandle,
24    TrackedTask,
25};
26use qubit_function::{
27    Callable,
28    Runnable,
29};
30
31use super::fixed_thread_pool_builder::FixedThreadPoolBuilder;
32use super::fixed_thread_pool_inner::FixedThreadPoolInner;
33use super::fixed_worker::FixedWorker;
34use super::fixed_worker_runtime::FixedWorkerRuntime;
35use crate::{
36    ExecutorServiceBuilderError,
37    PoolJob,
38    ThreadPoolStats,
39};
40
41/// Fixed-size thread pool implementing [`ExecutorService`].
42///
43/// `FixedThreadPool` prestarts a fixed number of worker threads and does not
44/// support runtime pool-size changes. Use [`crate::ThreadPool`] when dynamic
45/// core/maximum sizes or keep-alive policies are required.
46pub struct FixedThreadPool {
47    /// Shared fixed pool state.
48    inner: Arc<FixedThreadPoolInner>,
49}
50
51impl FixedThreadPool {
52    /// Builds a fixed pool from a validated [`FixedThreadPoolBuilder`].
53    ///
54    /// # Parameters
55    ///
56    /// * `builder` - Configuration produced by [`FixedThreadPoolBuilder`].
57    ///
58    /// # Returns
59    ///
60    /// A fixed thread-pool handle with workers already started.
61    ///
62    /// # Errors
63    ///
64    /// Returns [`ExecutorServiceBuilderError`] when a worker thread cannot be spawned.
65    pub(crate) fn new_with_builder(builder: FixedThreadPoolBuilder) -> Result<Self, ExecutorServiceBuilderError> {
66        let FixedThreadPoolBuilder {
67            pool_size,
68            queue_capacity,
69            thread_name_prefix,
70            stack_size,
71            hooks,
72        } = builder;
73        let mut worker_runtimes = Vec::with_capacity(pool_size);
74        for index in 0..pool_size {
75            let worker_runtime = FixedWorkerRuntime::new(index);
76            worker_runtimes.push(worker_runtime);
77        }
78        let inner = Arc::new(FixedThreadPoolInner::with_hooks(pool_size, queue_capacity, hooks));
79        let mut worker_handles = Vec::with_capacity(pool_size);
80        for (index, worker_runtime) in worker_runtimes.into_iter().enumerate() {
81            inner.reserve_worker_slot();
82            let worker_inner = Arc::clone(&inner);
83            let thread_name = format!("{}-{}", thread_name_prefix, index);
84            let mut builder = std::thread::Builder::new().name(thread_name);
85            if let Some(stack_size) = stack_size {
86                builder = builder.stack_size(stack_size);
87            }
88            match builder.spawn(move || FixedWorker::run(worker_inner, worker_runtime)) {
89                Ok(handle) => worker_handles.push(handle),
90                Err(source) => {
91                    inner.rollback_worker_slot();
92                    inner.stop_after_failed_build();
93                    join_started_workers(worker_handles);
94                    return Err(ExecutorServiceBuilderError::SpawnWorker {
95                        index: Some(index),
96                        source,
97                    });
98                }
99            }
100        }
101        Ok(Self { inner })
102    }
103
104    /// Creates a fixed thread pool with `pool_size` prestarted workers.
105    ///
106    /// # Parameters
107    ///
108    /// * `pool_size` - Number of worker threads.
109    ///
110    /// # Returns
111    ///
112    /// A fixed thread pool.
113    ///
114    /// # Errors
115    ///
116    /// Returns [`ExecutorServiceBuilderError`] if the worker count is zero or a worker
117    /// cannot be spawned.
118    pub fn new(pool_size: usize) -> Result<Self, ExecutorServiceBuilderError> {
119        Self::builder().pool_size(pool_size).build()
120    }
121
122    /// Creates a fixed pool builder.
123    ///
124    /// # Returns
125    ///
126    /// Builder with CPU parallelism defaults.
127    pub fn builder() -> FixedThreadPoolBuilder {
128        FixedThreadPoolBuilder::new()
129    }
130
131    /// Returns the fixed worker count.
132    ///
133    /// # Returns
134    ///
135    /// Number of workers in this pool.
136    pub fn pool_size(&self) -> usize {
137        self.inner.pool_size()
138    }
139
140    /// Returns the queued task count.
141    ///
142    /// # Returns
143    ///
144    /// Number of accepted tasks waiting to run.
145    pub fn queued_count(&self) -> usize {
146        self.inner.queued_count()
147    }
148
149    /// Returns the running task count.
150    ///
151    /// # Returns
152    ///
153    /// Number of tasks currently held by workers.
154    pub fn running_count(&self) -> usize {
155        self.inner.running_count()
156    }
157
158    /// Returns the live worker count.
159    ///
160    /// # Returns
161    ///
162    /// Number of worker loops that have not exited.
163    pub fn live_worker_count(&self) -> usize {
164        self.inner.state.read(|state| state.live_workers)
165    }
166
167    /// Returns a point-in-time stats snapshot.
168    ///
169    /// # Returns
170    ///
171    /// Snapshot containing queue, worker, and lifecycle counters.
172    pub fn stats(&self) -> ThreadPoolStats {
173        self.inner.stats()
174    }
175
176    /// Blocks until all accepted work has completed.
177    ///
178    /// This is a join-style wait for quiescence: it does not request shutdown
179    /// and does not wait for worker threads to exit. Concurrent submissions may
180    /// extend the wait until those accepted jobs also drain.
181    #[inline]
182    pub fn join(&self) {
183        self.inner.wait_until_idle();
184    }
185}
186
187impl Default for FixedThreadPool {
188    /// Creates a fixed thread pool using [`FixedThreadPoolBuilder::default`].
189    ///
190    /// # Returns
191    ///
192    /// A fixed thread pool with CPU parallelism defaults and prestarted workers.
193    ///
194    /// # Panics
195    ///
196    /// Panics when the default builder fails to spawn a worker thread.
197    fn default() -> Self {
198        FixedThreadPoolBuilder::default()
199            .build()
200            .expect("failed to build default FixedThreadPool")
201    }
202}
203
204impl Drop for FixedThreadPool {
205    /// Requests graceful shutdown when the pool handle is dropped.
206    fn drop(&mut self) {
207        self.inner.shutdown();
208    }
209}
210
211impl ExecutorService for FixedThreadPool {
212    type ResultHandle<R, E>
213        = TaskHandle<R, E>
214    where
215        R: Send + 'static,
216        E: Send + 'static;
217
218    type TrackedHandle<R, E>
219        = TrackedTask<R, E>
220    where
221        R: Send + 'static,
222        E: Send + 'static;
223
224    /// Accepts a runnable and queues it for fixed pool workers.
225    fn submit<T, E>(&self, task: T) -> Result<(), SubmissionError>
226    where
227        T: Runnable<E> + Send + 'static,
228        E: Send + 'static,
229    {
230        self.inner.submit(PoolJob::detached(task))
231    }
232
233    /// Accepts a callable and queues it for fixed pool workers.
234    ///
235    /// # Parameters
236    ///
237    /// * `task` - Callable to execute on a fixed pool worker.
238    ///
239    /// # Returns
240    ///
241    /// A [`TaskHandle`] for the accepted task.
242    ///
243    /// # Errors
244    ///
245    /// Returns [`SubmissionError::Shutdown`] after shutdown or
246    /// [`SubmissionError::Saturated`] when a bounded queue is full.
247    fn submit_callable<C, R, E>(&self, task: C) -> Result<Self::ResultHandle<R, E>, SubmissionError>
248    where
249        C: Callable<R, E> + Send + 'static,
250        R: Send + 'static,
251        E: Send + 'static,
252    {
253        let (handle, completion) = TaskEndpointPair::new().into_parts();
254        let job = PoolJob::from_task(task, completion);
255        self.inner.submit(job)?;
256        Ok(handle)
257    }
258
259    /// Accepts a callable and queues it with a tracked handle.
260    ///
261    /// # Parameters
262    ///
263    /// * `task` - Callable to execute on a fixed pool worker.
264    ///
265    /// # Returns
266    ///
267    /// A [`TrackedTask`] that reports task status and can observe completion,
268    /// failure, or queued cancellation.
269    ///
270    /// # Errors
271    ///
272    /// Returns [`SubmissionError::Shutdown`] after shutdown or
273    /// [`SubmissionError::Saturated`] when a bounded queue is full.
274    fn submit_tracked_callable<C, R, E>(&self, task: C) -> Result<Self::TrackedHandle<R, E>, SubmissionError>
275    where
276        C: Callable<R, E> + Send + 'static,
277        R: Send + 'static,
278        E: Send + 'static,
279    {
280        let (handle, completion) = TaskEndpointPair::new().into_tracked_parts();
281        let job = PoolJob::from_task(task, completion);
282        self.inner.submit(job)?;
283        Ok(handle)
284    }
285
286    /// Stops accepting new work and drains accepted queued tasks.
287    fn shutdown(&self) {
288        self.inner.shutdown();
289    }
290
291    /// Stops accepting work and cancels queued tasks.
292    ///
293    /// # Returns
294    ///
295    /// A count-based shutdown report.
296    fn stop(&self) -> StopReport {
297        self.inner.stop()
298    }
299
300    /// Returns the current lifecycle state.
301    fn lifecycle(&self) -> ExecutorServiceLifecycle {
302        self.inner.lifecycle()
303    }
304
305    /// Returns whether shutdown has been requested.
306    ///
307    /// # Returns
308    ///
309    /// `true` when this pool no longer accepts new work.
310    fn is_not_running(&self) -> bool {
311        self.inner.is_not_running()
312    }
313
314    /// Returns whether this pool is fully terminated.
315    ///
316    /// # Returns
317    ///
318    /// `true` after shutdown and after all workers have exited.
319    fn is_terminated(&self) -> bool {
320        self.inner.is_terminated()
321    }
322
323    /// Blocks until this fixed pool has terminated.
324    fn wait_termination(&self) {
325        self.inner.wait_for_termination();
326    }
327}
328
329/// Joins workers that were already spawned during a failed build.
330///
331/// # Parameters
332///
333/// * `worker_handles` - Join handles for workers started before construction failed.
334fn join_started_workers(worker_handles: Vec<JoinHandle<()>>) {
335    for worker_handle in worker_handles {
336        let _ignored = worker_handle.join();
337    }
338}