Skip to main content

qubit_executor/task/
task_endpoint_pair.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 oneshot::Receiver;
13use oneshot::channel;
14
15use crate::hook::{
16    TaskHook,
17    next_task_id,
18};
19
20use super::task_execution_error::TaskResult;
21use super::task_handle::TaskHandle;
22use super::task_slot::TaskSlot;
23use super::task_state::TaskState;
24use super::tracked_task::TrackedTask;
25
26/// One-shot pair of endpoints for a task submission.
27///
28/// A pair owns the shared task completion endpoint and the result receiver
29/// until it is split into caller-facing and runner-facing endpoints. Pairs
30/// created with [`Self::new`] do not install a lifecycle hook, avoiding the
31/// allocation and dynamic dispatch cost of a no-op hook on the default path.
32///
33/// Custom executors using this SPI should call [`TaskSlot::accept`] or the
34/// crate-internal handle acceptance path only after submission has succeeded.
35/// Dropping a runner slot before acceptance releases result waiters with
36/// `Dropped` but does not emit hook lifecycle events.
37pub struct TaskEndpointPair<R, E> {
38    /// Receiver consumed by the caller-facing handle.
39    receiver: Receiver<TaskResult<R, E>>,
40    /// Shared completion state consumed by the runner-facing endpoint.
41    state: Arc<TaskState<R, E>>,
42}
43
44impl<R, E> TaskEndpointPair<R, E> {
45    /// Creates a new unsplit task completion pair without lifecycle hooks.
46    ///
47    /// # Returns
48    ///
49    /// A pair that can be split once into its handle and completion endpoints.
50    #[inline]
51    pub fn new() -> Self {
52        Self::with_optional_hook(None)
53    }
54
55    /// Creates a new unsplit task completion pair with a lifecycle hook.
56    ///
57    /// # Parameters
58    ///
59    /// * `hook` - Hook notified about this task's lifecycle.
60    ///
61    /// # Returns
62    ///
63    /// A pair that can be split once into its handle and runner slot.
64    #[inline]
65    pub fn with_hook(hook: Arc<dyn TaskHook>) -> Self {
66        Self::with_optional_hook(Some(hook))
67    }
68
69    /// Creates a new unsplit task completion pair with an optional lifecycle hook.
70    ///
71    /// # Parameters
72    ///
73    /// * `hook` - Hook notified about this task's lifecycle after acceptance, or
74    ///   `None` for the fast path with no hook allocation or callback dispatch.
75    ///
76    /// # Returns
77    ///
78    /// A pair that can be split once into its handle and runner slot.
79    #[inline]
80    pub(crate) fn with_optional_hook(hook: Option<Arc<dyn TaskHook>>) -> Self {
81        let (sender, receiver) = channel();
82        Self {
83            receiver,
84            state: Arc::new(TaskState::new(next_task_id(), sender, hook)),
85        }
86    }
87
88    /// Splits this pair into a result handle and completion endpoint.
89    ///
90    /// # Returns
91    ///
92    /// A [`TaskHandle`] for the caller and a [`TaskSlot`] for the runner.
93    #[inline]
94    pub fn into_parts(self) -> (TaskHandle<R, E>, TaskSlot<R, E>) {
95        let handle = TaskHandle::new(Arc::clone(&self.state), self.receiver);
96        let slot = TaskSlot { state: self.state };
97        (handle, slot)
98    }
99
100    /// Splits this pair into a tracked result handle and completion endpoint.
101    ///
102    /// # Returns
103    ///
104    /// A [`TrackedTask`] for the caller and a [`TaskSlot`] for the runner.
105    #[inline]
106    pub fn into_tracked_parts(self) -> (TrackedTask<R, E>, TaskSlot<R, E>) {
107        let handle = TaskHandle::new(Arc::clone(&self.state), self.receiver);
108        let tracked = TrackedTask::new(handle);
109        let slot = TaskSlot { state: self.state };
110        (tracked, slot)
111    }
112}
113
114impl<R, E> Default for TaskEndpointPair<R, E> {
115    /// Creates a new unsplit task completion pair.
116    #[inline]
117    fn default() -> Self {
118        Self::new()
119    }
120}