qubit_executor/task/task_slot.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 std::sync::Arc;
11
12use qubit_function::Callable;
13
14use super::{
15 TaskResult,
16 task_runner::TaskRunner,
17 task_state::TaskState,
18};
19
20/// Runner-side slot for one task submission.
21///
22/// This low-level endpoint is exposed so custom executor services built on top
23/// of `qubit-executor` can wire their own scheduling while still returning the
24/// standard [`crate::TaskHandle`]. Executor implementations should call
25/// [`Self::accept`] only after submission succeeds; this arms lifecycle hook
26/// reporting for later start and finish events. Normal callers should use
27/// [`crate::TaskHandle`] and executor/service submission methods instead.
28///
29/// Dropping an accepted slot reports [`crate::TaskExecutionError::Dropped`]
30/// because it means the runner endpoint was abandoned without making an
31/// explicit terminal decision. Executor services that intentionally discard
32/// accepted work before it starts, such as during
33/// [`crate::ExecutorService::stop`], should call [`Self::cancel_unstarted`] so
34/// callers observe [`crate::TaskExecutionError::Cancelled`] instead.
35pub struct TaskSlot<R, E> {
36 /// Shared state updated by this completion endpoint.
37 pub(crate) state: Arc<TaskState<R, E>>,
38}
39
40impl<R, E> TaskSlot<R, E> {
41 /// Marks this runner endpoint as accepted and arms lifecycle hook reporting.
42 ///
43 /// Calling this method emits `on_accepted` before any later `on_started` or
44 /// `on_finished` event for the same task. Executor implementations must call
45 /// it only after submission has succeeded. Dropping a slot before acceptance
46 /// still releases result waiters with `Dropped`, but does not emit lifecycle
47 /// hook events for a task that was rejected before acceptance.
48 #[inline]
49 pub fn accept(&self) {
50 let _accepted_now = self.state.accept();
51 }
52
53 /// Cancels this accepted runner endpoint before it starts running.
54 ///
55 /// This method is the runner-side service-provider API for an executor or
56 /// executor service that intentionally removes queued, scheduled, or other
57 /// unstarted accepted work. It publishes
58 /// [`crate::TaskExecutionError::Cancelled`] when this slot wins the
59 /// pending-task terminal-state race. The slot is consumed to make the
60 /// explicit cancellation decision the final runner-side action.
61 ///
62 /// If the slot has already been accepted, successful cancellation emits the
63 /// finished lifecycle hook with [`crate::TaskStatus::Cancelled`]. If it has
64 /// not been accepted, cancellation still releases result waiters but does
65 /// not emit lifecycle hook events.
66 ///
67 /// # Returns
68 ///
69 /// `true` if this call moved the task from pending to cancelled, or `false`
70 /// if another path had already started or completed the task.
71 #[inline]
72 pub fn cancel_unstarted(self) -> bool {
73 self.state.try_cancel_pending()
74 }
75
76 /// Marks the task as started if it was not cancelled first.
77 ///
78 /// # Returns
79 ///
80 /// `true` if the runner should execute the task, or `false` if the task was
81 /// already completed through cancellation.
82 pub(crate) fn start(&self) -> bool {
83 self.state.try_start(self.state.is_accepted())
84 }
85
86 /// Completes the task with its final result.
87 ///
88 /// If another path has already completed the task, this result is ignored.
89 ///
90 /// # Parameters
91 ///
92 /// * `result` - Final task result to publish if the task is not already
93 /// completed.
94 #[inline]
95 pub(crate) fn complete(&self, result: TaskResult<R, E>) {
96 let _completed = self.state.try_complete(result, self.state.is_accepted());
97 }
98
99 /// Starts the task and completes it with a lazily produced result.
100 ///
101 /// The supplied closure is executed only if this completion endpoint wins
102 /// the start race. If the handle was cancelled first, the closure is not
103 /// called and the existing cancellation result is preserved.
104 ///
105 /// # Parameters
106 ///
107 /// * `task` - Closure that runs the accepted task and returns its final
108 /// result.
109 ///
110 /// # Returns
111 ///
112 /// `true` if the closure was executed and its result was published, or
113 /// `false` if the task had already been completed by cancellation.
114 #[inline]
115 pub(crate) fn start_and_complete<F>(&self, task: F) -> bool
116 where
117 F: FnOnce() -> TaskResult<R, E>,
118 {
119 if !self.start() {
120 return false;
121 }
122 self.complete(task());
123 true
124 }
125
126 /// Starts this slot and runs a callable to completion.
127 ///
128 /// # Parameters
129 ///
130 /// * `task` - Callable to run if the task has not been cancelled.
131 ///
132 /// # Returns
133 ///
134 /// `true` if the callable ran and published a result, or `false` if the
135 /// task had already been cancelled.
136 #[inline]
137 pub fn run<C>(self, task: C) -> bool
138 where
139 C: Callable<R, E>,
140 {
141 self.start_and_complete(|| TaskRunner::new(task).call())
142 }
143}
144
145impl<R, E> Drop for TaskSlot<R, E> {
146 /// Publishes a dropped-result error when the runner endpoint is abandoned.
147 #[inline]
148 fn drop(&mut self) {
149 let _ignored = self.state.try_drop_unfinished(self.state.is_accepted());
150 }
151}