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>(&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}