qubit_executor/service/executor_service.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 qubit_function::{
11 Callable,
12 Runnable,
13};
14
15use crate::task::spi::{
16 TaskResultHandle,
17 TrackedTaskHandle,
18};
19
20use super::{
21 ExecutorServiceLifecycle,
22 StopReport,
23 SubmissionError,
24};
25
26/// Managed task service with submission and lifecycle control.
27///
28/// `ExecutorService` is intentionally separate from
29/// [`Executor`](crate::Executor). An executor describes an
30/// execution strategy; an executor service accepts tasks into a managed service
31/// that may queue, schedule, assign workers, and track lifecycle.
32///
33/// `submit` and `submit_callable` return `Result` values whose outer `Ok`
34/// means only that the service accepted the task. It does **not** mean the task
35/// has started or succeeded. `submit` is fire-and-forget; callable and tracked
36/// variants return handles for observing the final task result.
37///
38/// ## Lifecycle
39///
40/// A service starts in [`ExecutorServiceLifecycle::Running`]. While running,
41/// submissions may be accepted. Calling [`shutdown`](Self::shutdown) starts an
42/// orderly shutdown and moves the service toward
43/// [`ExecutorServiceLifecycle::ShuttingDown`]: later submissions are rejected,
44/// while work accepted before shutdown is allowed to finish normally. Calling
45/// [`stop`](Self::stop) starts an abrupt stop and moves the service toward
46/// [`ExecutorServiceLifecycle::Stopping`]: later submissions are rejected and
47/// the implementation attempts to cancel or abort accepted work that can still
48/// be stopped.
49///
50/// `shutdown` and `stop` are both terminal admission decisions; neither allows
51/// the service to become running again. The difference is how accepted work is
52/// treated. `shutdown` preserves accepted work, including queued or scheduled
53/// work, unless a concrete service documents a stronger policy. `stop` is a
54/// best-effort interruption request for queued, scheduled, unstarted, or
55/// runtime-abortable work. Services built with
56/// [`TaskSlot`](crate::task::spi::TaskSlot) should publish
57/// [`TaskExecutionError::Cancelled`](crate::TaskExecutionError::Cancelled) for
58/// accepted work that is intentionally removed before it starts, typically by
59/// calling
60/// [`TaskSlot::cancel_unstarted`](crate::task::spi::TaskSlot::cancel_unstarted).
61/// Work already running in ordinary Rust code, blocking calls, or OS threads may
62/// not be forcibly interrupted, so termination can still wait for that work to
63/// return.
64///
65/// A service reaches [`ExecutorServiceLifecycle::Terminated`] after shutdown or
66/// stop has been requested and no accepted work remains active. Accepted work
67/// may have completed normally, failed, panicked, been cancelled, or been
68/// dropped by its runner endpoint, or been aborted according to the concrete
69/// service's capabilities.
70///
71/// ## Resource cleanup
72///
73/// Dropping an executor-service handle is not a portable resource-release
74/// protocol. Concrete services may request shutdown from `Drop`, but `Drop`
75/// should not be assumed to block until worker threads, helper threads, runtime
76/// tasks, queues, or other service-owned resources have fully exited. Blocking
77/// in `Drop` would make ordinary handle destruction unexpectedly wait for
78/// arbitrary user code, blocking calls, or OS-thread tasks that cannot be
79/// interrupted.
80///
81/// Code that needs deterministic cleanup must request termination explicitly and
82/// then wait for it:
83///
84/// 1. Call [`shutdown`](Self::shutdown) to drain accepted work, or
85/// [`stop`](Self::stop) to request best-effort cancellation or abort of work
86/// that has not become non-interruptible.
87/// 2. Call [`wait_termination`](Self::wait_termination) to block until the
88/// service reports that no accepted work remains active.
89/// 3. Drop the service handle and any task handles after the wait returns.
90///
91/// If a service owns OS threads or blocking tasks, already-running task bodies
92/// can keep external resources such as file descriptors, sockets, locks, or
93/// reference-counted objects alive until those task bodies return. Services that
94/// need stronger cleanup behavior should expose an explicit close/join API
95/// rather than relying on destructor side effects.
96pub trait ExecutorService: Send + Sync {
97 /// Result handle returned for an accepted callable task.
98 type ResultHandle<R, E>: TaskResultHandle<R, E>
99 where
100 R: Send + 'static,
101 E: Send + 'static;
102
103 /// Tracked handle returned for accepted tasks that expose status.
104 type TrackedHandle<R, E>: TrackedTaskHandle<R, E>
105 where
106 R: Send + 'static,
107 E: Send + 'static;
108
109 /// Submits a runnable task to this service.
110 ///
111 /// # Parameters
112 ///
113 /// * `task` - A fallible background action with no business return value.
114 ///
115 /// # Returns
116 ///
117 /// `Ok(())` if the service accepts the task. This only reports acceptance;
118 /// it does not report task start or task success. Returns
119 /// `Err(SubmissionError)` if the service refuses the task before
120 /// accepting it.
121 ///
122 /// # Errors
123 ///
124 /// Returns [`SubmissionError`] when the service refuses the task before
125 /// accepting it.
126 fn submit<T, E>(&self, task: T) -> Result<(), SubmissionError>
127 where
128 T: Runnable<E> + Send + 'static,
129 E: Send + 'static;
130
131 /// Submits a callable task to this service.
132 ///
133 /// # Parameters
134 ///
135 /// * `task` - A fallible computation whose success value should be captured
136 /// in the returned handle.
137 ///
138 /// # Returns
139 ///
140 /// `Ok(handle)` if the service accepts the task. This only reports
141 /// acceptance; task success, task failure, panic, or cancellation must be
142 /// observed through the returned handle. Returns `Err(SubmissionError)` if
143 /// the service refuses the task before accepting it.
144 ///
145 /// # Errors
146 ///
147 /// Returns [`SubmissionError`] when the service refuses the task before
148 /// accepting it.
149 fn submit_callable<C, R, E>(&self, task: C) -> Result<Self::ResultHandle<R, E>, SubmissionError>
150 where
151 C: Callable<R, E> + Send + 'static,
152 R: Send + 'static,
153 E: Send + 'static;
154
155 /// Submits a runnable task and returns a tracked handle.
156 ///
157 /// # Parameters
158 ///
159 /// * `task` - A fallible background action with no business return value.
160 ///
161 /// # Returns
162 ///
163 /// `Ok(handle)` if the service accepts the task. The handle exposes status,
164 /// pre-start cancellation, and final unit result retrieval.
165 ///
166 /// # Errors
167 ///
168 /// Returns [`SubmissionError`] when the service refuses the task before
169 /// accepting it.
170 #[inline]
171 fn submit_tracked<T, E>(&self, task: T) -> Result<Self::TrackedHandle<(), E>, SubmissionError>
172 where
173 T: Runnable<E> + Send + 'static,
174 E: Send + 'static,
175 {
176 let mut task = task;
177 self.submit_tracked_callable(move || task.run())
178 }
179
180 /// Submits a callable task and returns a tracked handle.
181 ///
182 /// # Parameters
183 ///
184 /// * `task` - A fallible computation whose success value should be captured
185 /// in the returned handle.
186 ///
187 /// # Returns
188 ///
189 /// `Ok(handle)` if the service accepts the task. The handle exposes status,
190 /// pre-start cancellation, and final result retrieval.
191 ///
192 /// # Errors
193 ///
194 /// Returns [`SubmissionError`] when the service refuses the task before
195 /// accepting it.
196 fn submit_tracked_callable<C, R, E>(&self, task: C) -> Result<Self::TrackedHandle<R, E>, SubmissionError>
197 where
198 C: Callable<R, E> + Send + 'static,
199 R: Send + 'static,
200 E: Send + 'static;
201
202 /// Initiates an orderly shutdown.
203 ///
204 /// After shutdown starts, the service rejects new submissions and enters
205 /// the [`ExecutorServiceLifecycle::ShuttingDown`] path. Already accepted
206 /// work is allowed to complete normally, including work that is queued,
207 /// scheduled, or running, unless the concrete service documents a stronger
208 /// cancellation policy.
209 ///
210 /// This method is an admission gate change, not a wait operation. Use
211 /// [`wait_termination`](Self::wait_termination) to block until all accepted
212 /// work has completed or the service has otherwise terminated.
213 fn shutdown(&self);
214
215 /// Attempts to stop accepting new tasks and stop accepted work immediately.
216 ///
217 /// After stop starts, the service rejects new submissions and enters the
218 /// [`ExecutorServiceLifecycle::Stopping`] path. The implementation should
219 /// cancel queued, scheduled, or unstarted work where possible, and abort
220 /// runtime-managed work where its runtime provides an abort mechanism.
221 ///
222 /// `stop` is best effort. It cannot promise to interrupt arbitrary Rust
223 /// code, blocking calls, or already-running OS-thread work. Such work may
224 /// continue until it returns, and service termination waits for any
225 /// non-interruptible accepted work that remains active.
226 ///
227 /// # Returns
228 ///
229 /// A count-based stop report describing queued, running, and cancelled work
230 /// observed while handling the request.
231 fn stop(&self) -> StopReport;
232
233 /// Returns the current lifecycle state.
234 ///
235 /// # Returns
236 ///
237 /// The lifecycle state currently observed by this service.
238 fn lifecycle(&self) -> ExecutorServiceLifecycle;
239
240 /// Returns whether the service accepts new tasks.
241 ///
242 /// # Returns
243 ///
244 /// `true` only while the lifecycle is [`ExecutorServiceLifecycle::Running`].
245 #[inline]
246 fn is_running(&self) -> bool {
247 self.lifecycle() == ExecutorServiceLifecycle::Running
248 }
249
250 /// Returns whether graceful shutdown is in progress.
251 ///
252 /// # Returns
253 ///
254 /// `true` only while the lifecycle is
255 /// [`ExecutorServiceLifecycle::ShuttingDown`].
256 #[inline]
257 fn is_shutting_down(&self) -> bool {
258 self.lifecycle() == ExecutorServiceLifecycle::ShuttingDown
259 }
260
261 /// Returns whether abrupt stop is in progress.
262 ///
263 /// # Returns
264 ///
265 /// `true` only while the lifecycle is [`ExecutorServiceLifecycle::Stopping`].
266 #[inline]
267 fn is_stopping(&self) -> bool {
268 self.lifecycle() == ExecutorServiceLifecycle::Stopping
269 }
270
271 /// Returns whether this service is not running.
272 ///
273 /// # Returns
274 ///
275 /// `true` once the service has started graceful shutdown, abrupt stop, or has
276 /// already terminated.
277 #[inline]
278 fn is_not_running(&self) -> bool {
279 self.lifecycle() != ExecutorServiceLifecycle::Running
280 }
281
282 /// Returns whether the service has terminated.
283 ///
284 /// # Returns
285 ///
286 /// `true` only after shutdown or stop has been requested and all accepted
287 /// tasks have completed or been cancelled.
288 #[inline]
289 fn is_terminated(&self) -> bool {
290 self.lifecycle() == ExecutorServiceLifecycle::Terminated
291 }
292
293 /// Blocks the current thread until the service has terminated.
294 ///
295 /// This method is a synchronous, blocking wait. It returns only after
296 /// [`shutdown`](Self::shutdown) or [`stop`](Self::stop) has been requested
297 /// and no accepted tasks remain active. If it is called while the service is
298 /// still [`ExecutorServiceLifecycle::Running`] and no other thread requests
299 /// shutdown or stop, it may block forever.
300 ///
301 /// This method is the portable way to wait for service-owned resources to
302 /// quiesce after an explicit shutdown or stop request. Dropping a service
303 /// handle is not a substitute for calling this method when deterministic
304 /// cleanup matters.
305 ///
306 /// Implementations must not present this method as an asynchronous or
307 /// non-blocking operation.
308 fn wait_termination(&self);
309}