Skip to main content

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}