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.
28pub struct TaskSlot<R, E> {
29 /// Shared state updated by this completion endpoint.
30 pub(crate) state: Arc<TaskState<R, E>>,
31}
32
33impl<R, E> TaskSlot<R, E> {
34 /// Marks this runner endpoint as accepted and arms lifecycle hook reporting.
35 ///
36 /// Calling this method emits `on_accepted` before any later `on_started` or
37 /// `on_finished` event for the same task. Executor implementations must call
38 /// it only after submission has succeeded. Dropping a slot before acceptance
39 /// still releases result waiters with `Dropped`, but does not emit lifecycle
40 /// hook events for a task that was rejected before acceptance.
41 #[inline]
42 pub fn accept(&self) {
43 let _accepted_now = self.state.accept();
44 }
45
46 /// Marks the task as started if it was not cancelled first.
47 ///
48 /// # Returns
49 ///
50 /// `true` if the runner should execute the task, or `false` if the task was
51 /// already completed through cancellation.
52 pub(crate) fn start(&self) -> bool {
53 self.state.try_start(self.state.is_accepted())
54 }
55
56 /// Completes the task with its final result.
57 ///
58 /// If another path has already completed the task, this result is ignored.
59 ///
60 /// # Parameters
61 ///
62 /// * `result` - Final task result to publish if the task is not already
63 /// completed.
64 #[inline]
65 pub(crate) fn complete(&self, result: TaskResult<R, E>) {
66 let _completed = self.state.try_complete(result, self.state.is_accepted());
67 }
68
69 /// Starts the task and completes it with a lazily produced result.
70 ///
71 /// The supplied closure is executed only if this completion endpoint wins
72 /// the start race. If the handle was cancelled first, the closure is not
73 /// called and the existing cancellation result is preserved.
74 ///
75 /// # Parameters
76 ///
77 /// * `task` - Closure that runs the accepted task and returns its final
78 /// result.
79 ///
80 /// # Returns
81 ///
82 /// `true` if the closure was executed and its result was published, or
83 /// `false` if the task had already been completed by cancellation.
84 #[inline]
85 pub(crate) fn start_and_complete<F>(&self, task: F) -> bool
86 where
87 F: FnOnce() -> TaskResult<R, E>,
88 {
89 if !self.start() {
90 return false;
91 }
92 self.complete(task());
93 true
94 }
95
96 /// Starts this slot and runs a callable to completion.
97 ///
98 /// # Parameters
99 ///
100 /// * `task` - Callable to run if the task has not been cancelled.
101 ///
102 /// # Returns
103 ///
104 /// `true` if the callable ran and published a result, or `false` if the
105 /// task had already been cancelled.
106 #[inline]
107 pub fn run<C>(self, task: C) -> bool
108 where
109 C: Callable<R, E>,
110 {
111 self.start_and_complete(|| TaskRunner::new(task).call())
112 }
113}
114
115impl<R, E> Drop for TaskSlot<R, E> {
116 /// Publishes a dropped-result error when the runner endpoint is abandoned.
117 #[inline]
118 fn drop(&mut self) {
119 let _ignored = self.state.try_drop_unfinished(self.state.is_accepted());
120 }
121}