qubit_execution_services/execution_services_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 the execution-services facade.
11
12use std::{
13 thread,
14 time::Duration,
15};
16
17use super::{
18 BlockingExecutorService,
19 BlockingExecutorServiceBuilder,
20 ExecutionServices,
21 ExecutionServicesBuildError,
22 RayonExecutorService,
23 RayonExecutorServiceBuilder,
24 TokioBlockingExecutorService,
25 TokioIoExecutorService,
26};
27
28/// Builder for [`ExecutionServices`].
29///
30/// The builder exposes blocking-pool options by delegating to
31/// [`BlockingExecutorServiceBuilder`] and CPU-pool options by delegating to
32/// [`RayonExecutorServiceBuilder`]. Tokio-backed domains are created with their
33/// default constructors because they do not currently expose custom builders.
34#[derive(Debug, Clone)]
35pub struct ExecutionServicesBuilder {
36 /// Builder for the blocking executor domain.
37 blocking: BlockingExecutorServiceBuilder,
38 /// Builder for the CPU executor domain.
39 cpu: RayonExecutorServiceBuilder,
40}
41
42impl ExecutionServicesBuilder {
43 /// Sets both the blocking core and maximum pool sizes to the same value.
44 ///
45 /// # Parameters
46 ///
47 /// * `pool_size` - Pool size applied as both core and maximum limits.
48 ///
49 /// # Returns
50 ///
51 /// This builder for fluent configuration.
52 #[inline]
53 pub fn blocking_pool_size(mut self, pool_size: usize) -> Self {
54 self.blocking = self.blocking.pool_size(pool_size);
55 self
56 }
57
58 /// Sets the blocking core pool size.
59 ///
60 /// # Parameters
61 ///
62 /// * `core_pool_size` - Core pool size for the blocking domain.
63 ///
64 /// # Returns
65 ///
66 /// This builder for fluent configuration.
67 #[inline]
68 pub fn blocking_core_pool_size(mut self, core_pool_size: usize) -> Self {
69 self.blocking = self.blocking.core_pool_size(core_pool_size);
70 self
71 }
72
73 /// Sets the blocking maximum pool size.
74 ///
75 /// # Parameters
76 ///
77 /// * `maximum_pool_size` - Maximum pool size for the blocking domain.
78 ///
79 /// # Returns
80 ///
81 /// This builder for fluent configuration.
82 #[inline]
83 pub fn blocking_maximum_pool_size(mut self, maximum_pool_size: usize) -> Self {
84 self.blocking = self.blocking.maximum_pool_size(maximum_pool_size);
85 self
86 }
87
88 /// Sets a bounded queue capacity for the blocking domain.
89 ///
90 /// # Parameters
91 ///
92 /// * `capacity` - Maximum number of queued blocking tasks.
93 ///
94 /// # Returns
95 ///
96 /// This builder for fluent configuration.
97 #[inline]
98 pub fn blocking_queue_capacity(mut self, capacity: usize) -> Self {
99 self.blocking = self.blocking.queue_capacity(capacity);
100 self
101 }
102
103 /// Configures the blocking domain to use an unbounded queue.
104 ///
105 /// # Returns
106 ///
107 /// This builder for fluent configuration.
108 #[inline]
109 pub fn blocking_unbounded_queue(mut self) -> Self {
110 self.blocking = self.blocking.unbounded_queue();
111 self
112 }
113
114 /// Sets the blocking worker-thread name prefix.
115 ///
116 /// # Parameters
117 ///
118 /// * `prefix` - Prefix appended with the worker index.
119 ///
120 /// # Returns
121 ///
122 /// This builder for fluent configuration.
123 #[inline]
124 pub fn blocking_thread_name_prefix(mut self, prefix: &str) -> Self {
125 self.blocking = self.blocking.thread_name_prefix(prefix);
126 self
127 }
128
129 /// Sets the blocking worker-thread stack size.
130 ///
131 /// # Parameters
132 ///
133 /// * `stack_size` - Stack size in bytes for each blocking worker.
134 ///
135 /// # Returns
136 ///
137 /// This builder for fluent configuration.
138 #[inline]
139 pub fn blocking_stack_size(mut self, stack_size: usize) -> Self {
140 self.blocking = self.blocking.stack_size(stack_size);
141 self
142 }
143
144 /// Sets the blocking worker keep-alive timeout.
145 ///
146 /// # Parameters
147 ///
148 /// * `keep_alive` - Idle timeout for blocking workers allowed to retire.
149 ///
150 /// # Returns
151 ///
152 /// This builder for fluent configuration.
153 #[inline]
154 pub fn blocking_keep_alive(mut self, keep_alive: Duration) -> Self {
155 self.blocking = self.blocking.keep_alive(keep_alive);
156 self
157 }
158
159 /// Allows blocking core workers to retire after keep-alive timeout.
160 ///
161 /// # Parameters
162 ///
163 /// * `allow` - Whether idle blocking core workers may time out.
164 ///
165 /// # Returns
166 ///
167 /// This builder for fluent configuration.
168 #[inline]
169 pub fn blocking_allow_core_thread_timeout(mut self, allow: bool) -> Self {
170 self.blocking = self.blocking.allow_core_thread_timeout(allow);
171 self
172 }
173
174 /// Starts all blocking core workers during build.
175 ///
176 /// # Returns
177 ///
178 /// This builder for fluent configuration.
179 #[inline]
180 pub fn blocking_prestart_core_threads(mut self) -> Self {
181 self.blocking = self.blocking.prestart_core_threads();
182 self
183 }
184
185 /// Sets the number of Rayon worker threads in the CPU domain.
186 ///
187 /// # Parameters
188 ///
189 /// * `num_threads` - Number of Rayon worker threads.
190 ///
191 /// # Returns
192 ///
193 /// This builder for fluent configuration.
194 #[inline]
195 pub fn cpu_threads(mut self, num_threads: usize) -> Self {
196 self.cpu = self.cpu.num_threads(num_threads);
197 self
198 }
199
200 /// Sets the Rayon worker-thread name prefix in the CPU domain.
201 ///
202 /// # Parameters
203 ///
204 /// * `prefix` - Prefix appended with the worker index.
205 ///
206 /// # Returns
207 ///
208 /// This builder for fluent configuration.
209 #[inline]
210 pub fn cpu_thread_name_prefix(mut self, prefix: &str) -> Self {
211 self.cpu = self.cpu.thread_name_prefix(prefix);
212 self
213 }
214
215 /// Sets the Rayon worker-thread stack size in the CPU domain.
216 ///
217 /// # Parameters
218 ///
219 /// * `stack_size` - Stack size in bytes for each Rayon worker.
220 ///
221 /// # Returns
222 ///
223 /// This builder for fluent configuration.
224 #[inline]
225 pub fn cpu_stack_size(mut self, stack_size: usize) -> Self {
226 self.cpu = self.cpu.stack_size(stack_size);
227 self
228 }
229
230 /// Builds the configured execution-services facade.
231 ///
232 /// # Returns
233 ///
234 /// `Ok(ExecutionServices)` if the blocking and CPU domains build
235 /// successfully.
236 ///
237 /// # Errors
238 ///
239 /// Returns [`ExecutionServicesBuildError`] if either the blocking or CPU
240 /// domain rejects its builder configuration.
241 pub fn build(self) -> Result<ExecutionServices, ExecutionServicesBuildError> {
242 let blocking = self
243 .blocking
244 .build()
245 .map_err(|source| ExecutionServicesBuildError::Blocking { source })?;
246 let cpu = self
247 .cpu
248 .build()
249 .map_err(|source| ExecutionServicesBuildError::Cpu { source })?;
250 let tokio_blocking = TokioBlockingExecutorService::new();
251 let io = TokioIoExecutorService::new();
252 Ok(ExecutionServices::from_parts(
253 blocking,
254 cpu,
255 tokio_blocking,
256 io,
257 ))
258 }
259}
260
261impl Default for ExecutionServicesBuilder {
262 /// Creates a builder with CPU-parallelism defaults.
263 ///
264 /// # Returns
265 ///
266 /// A builder configured with available parallelism for both blocking and
267 /// CPU domains.
268 fn default() -> Self {
269 let pool_size = default_pool_size();
270 Self {
271 blocking: BlockingExecutorService::builder().pool_size(pool_size),
272 cpu: RayonExecutorService::builder().num_threads(pool_size),
273 }
274 }
275}
276
277/// Returns the default pool size for blocking and CPU domains.
278///
279/// # Returns
280///
281/// The available CPU parallelism, or `1` if it cannot be detected.
282fn default_pool_size() -> usize {
283 thread::available_parallelism()
284 .map(usize::from)
285 .unwrap_or(1)
286}