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>(
150 &self,
151 task: C,
152 ) -> Result<Self::ResultHandle<R, E>, SubmissionError>
153 where
154 C: Callable<R, E> + Send + 'static,
155 R: Send + 'static,
156 E: Send + 'static;
157
158 /// Submits a runnable task and returns a tracked handle.
159 ///
160 /// # Parameters
161 ///
162 /// * `task` - A fallible background action with no business return value.
163 ///
164 /// # Returns
165 ///
166 /// `Ok(handle)` if the service accepts the task. The handle exposes status,
167 /// pre-start cancellation, and final unit result retrieval.
168 ///
169 /// # Errors
170 ///
171 /// Returns [`SubmissionError`] when the service refuses the task before
172 /// accepting it.
173 #[inline]
174 fn submit_tracked<T, E>(&self, task: T) -> Result<Self::TrackedHandle<(), E>, SubmissionError>
175 where
176 T: Runnable<E> + Send + 'static,
177 E: Send + 'static,
178 {
179 let mut task = task;
180 self.submit_tracked_callable(move || task.run())
181 }
182
183 /// Submits a callable task and returns a tracked handle.
184 ///
185 /// # Parameters
186 ///
187 /// * `task` - A fallible computation whose success value should be captured
188 /// in the returned handle.
189 ///
190 /// # Returns
191 ///
192 /// `Ok(handle)` if the service accepts the task. The handle exposes status,
193 /// pre-start cancellation, and final result retrieval.
194 ///
195 /// # Errors
196 ///
197 /// Returns [`SubmissionError`] when the service refuses the task before
198 /// accepting it.
199 fn submit_tracked_callable<C, R, E>(
200 &self,
201 task: C,
202 ) -> Result<Self::TrackedHandle<R, E>, SubmissionError>
203 where
204 C: Callable<R, E> + Send + 'static,
205 R: Send + 'static,
206 E: Send + 'static;
207
208 /// Initiates an orderly shutdown.
209 ///
210 /// After shutdown starts, the service rejects new submissions and enters
211 /// the [`ExecutorServiceLifecycle::ShuttingDown`] path. Already accepted
212 /// work is allowed to complete normally, including work that is queued,
213 /// scheduled, or running, unless the concrete service documents a stronger
214 /// cancellation policy.
215 ///
216 /// This method is an admission gate change, not a wait operation. Use
217 /// [`wait_termination`](Self::wait_termination) to block until all accepted
218 /// work has completed or the service has otherwise terminated.
219 fn shutdown(&self);
220
221 /// Attempts to stop accepting new tasks and stop accepted work immediately.
222 ///
223 /// After stop starts, the service rejects new submissions and enters the
224 /// [`ExecutorServiceLifecycle::Stopping`] path. The implementation should
225 /// cancel queued, scheduled, or unstarted work where possible, and abort
226 /// runtime-managed work where its runtime provides an abort mechanism.
227 ///
228 /// `stop` is best effort. It cannot promise to interrupt arbitrary Rust
229 /// code, blocking calls, or already-running OS-thread work. Such work may
230 /// continue until it returns, and service termination waits for any
231 /// non-interruptible accepted work that remains active.
232 ///
233 /// # Returns
234 ///
235 /// A count-based stop report describing queued, running, and cancelled work
236 /// observed while handling the request.
237 fn stop(&self) -> StopReport;
238
239 /// Returns the current lifecycle state.
240 ///
241 /// # Returns
242 ///
243 /// The lifecycle state currently observed by this service.
244 fn lifecycle(&self) -> ExecutorServiceLifecycle;
245
246 /// Returns whether the service accepts new tasks.
247 ///
248 /// # Returns
249 ///
250 /// `true` only while the lifecycle is [`ExecutorServiceLifecycle::Running`].
251 #[inline]
252 fn is_running(&self) -> bool {
253 self.lifecycle() == ExecutorServiceLifecycle::Running
254 }
255
256 /// Returns whether graceful shutdown is in progress.
257 ///
258 /// # Returns
259 ///
260 /// `true` only while the lifecycle is
261 /// [`ExecutorServiceLifecycle::ShuttingDown`].
262 #[inline]
263 fn is_shutting_down(&self) -> bool {
264 self.lifecycle() == ExecutorServiceLifecycle::ShuttingDown
265 }
266
267 /// Returns whether abrupt stop is in progress.
268 ///
269 /// # Returns
270 ///
271 /// `true` only while the lifecycle is [`ExecutorServiceLifecycle::Stopping`].
272 #[inline]
273 fn is_stopping(&self) -> bool {
274 self.lifecycle() == ExecutorServiceLifecycle::Stopping
275 }
276
277 /// Returns whether this service is not running.
278 ///
279 /// # Returns
280 ///
281 /// `true` once the service has started graceful shutdown, abrupt stop, or has
282 /// already terminated.
283 #[inline]
284 fn is_not_running(&self) -> bool {
285 self.lifecycle() != ExecutorServiceLifecycle::Running
286 }
287
288 /// Returns whether the service has terminated.
289 ///
290 /// # Returns
291 ///
292 /// `true` only after shutdown or stop has been requested and all accepted
293 /// tasks have completed or been cancelled.
294 #[inline]
295 fn is_terminated(&self) -> bool {
296 self.lifecycle() == ExecutorServiceLifecycle::Terminated
297 }
298
299 /// Blocks the current thread until the service has terminated.
300 ///
301 /// This method is a synchronous, blocking wait. It returns only after
302 /// [`shutdown`](Self::shutdown) or [`stop`](Self::stop) has been requested
303 /// and no accepted tasks remain active. If it is called while the service is
304 /// still [`ExecutorServiceLifecycle::Running`] and no other thread requests
305 /// shutdown or stop, it may block forever.
306 ///
307 /// This method is the portable way to wait for service-owned resources to
308 /// quiesce after an explicit shutdown or stop request. Dropping a service
309 /// handle is not a substitute for calling this method when deterministic
310 /// cleanup matters.
311 ///
312 /// Implementations must not present this method as an asynchronous or
313 /// non-blocking operation.
314 fn wait_termination(&self);
315}