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}