moirai_core/task/
handle.rs1use crate::error::TaskError;
2
3use super::id_and_context::TaskId;
4
5#[cfg(feature = "std")]
8use core::mem::ManuallyDrop;
9
10#[cfg(feature = "std")]
11use std::sync::{Arc, atomic::AtomicU8};
12#[cfg(feature = "std")]
13use std::thread;
14
15#[cfg(feature = "std")]
16use moirai_utils::{CacheAligned, ResultCell};
17
18#[cfg(feature = "std")]
21pub(super) mod result_wait {
22 pub(super) mod sealed {
23 pub trait Sealed {}
24 }
25
26 pub trait ResultWaitPolicy: sealed::Sealed {
32 const SPIN_ATTEMPTS: usize;
34 }
35
36 #[derive(Debug, Clone, Copy, Default)]
38 pub struct BlockingResultWait;
39
40 impl sealed::Sealed for BlockingResultWait {}
41
42 impl ResultWaitPolicy for BlockingResultWait {
43 const SPIN_ATTEMPTS: usize = super::super::MAX_SPIN_ATTEMPTS;
44 }
45}
46
47#[cfg(feature = "std")]
48pub use result_wait::{BlockingResultWait, ResultWaitPolicy};
49
50#[cfg(feature = "std")]
70struct TaskResultSlot<T> {
71 cell: ResultCell<Result<T, TaskError>, thread::Thread, CacheAligned<AtomicU8>>,
72}
73
74#[cfg(feature = "std")]
83impl<T> TaskResultSlot<T> {
84 fn new() -> Self {
85 Self {
86 cell: ResultCell::new(),
87 }
88 }
89
90 fn complete(&self, result: Result<T, TaskError>) {
91 self.cell.complete(result);
92 }
93
94 unsafe fn wait<P>(&self) -> Result<T, TaskError>
101 where
102 P: ResultWaitPolicy,
103 {
104 if let Some(result) = self.try_take_ready() {
105 return result;
106 }
107
108 for _ in 0..P::SPIN_ATTEMPTS {
109 if let Some(result) = self.try_take_observed_ready() {
110 return result;
111 }
112 core::hint::spin_loop();
113 }
114
115 unsafe { self.register_waiter() };
117
118 loop {
119 if let Some(result) = self.try_take_observed_ready() {
120 return result;
121 }
122
123 thread::park();
124 }
125 }
126
127 fn is_completed(&self) -> bool {
128 self.cell.is_completed()
129 }
130
131 fn try_take_ready(&self) -> Option<Result<T, TaskError>> {
132 self.cell.try_take_ready()
133 }
134
135 fn try_take_observed_ready(&self) -> Option<Result<T, TaskError>> {
136 self.cell.try_take_observed_ready()
137 }
138
139 unsafe fn register_waiter(&self) {
143 unsafe { self.cell.register(&thread::current()) };
145 }
146
147 #[cfg(feature = "result-diagnostics")]
148 fn has_registered_waiter(&self) -> bool {
149 self.cell.has_registered_waiter()
150 }
151}
152#[cfg(all(feature = "std", feature = "result-diagnostics"))]
155const DIAGNOSTIC_READY_VALUE: usize = 42;
156
157#[cfg(all(feature = "std", feature = "result-diagnostics"))]
159#[doc(hidden)]
160pub fn diagnostic_result_slot_ready_take() -> usize {
161 let slot = TaskResultSlot::new();
162 slot.complete(Ok(DIAGNOSTIC_READY_VALUE));
163 match slot.try_take_ready() {
164 Some(Ok(value)) => value,
165 _ => 0,
166 }
167}
168
169#[cfg(all(feature = "std", feature = "result-diagnostics"))]
171#[doc(hidden)]
172pub fn diagnostic_result_slot_spin_miss() -> usize {
173 let slot = TaskResultSlot::<usize>::new();
174 let mut misses = 0usize;
175 for _ in 0..BlockingResultWait::SPIN_ATTEMPTS {
176 if slot.try_take_observed_ready().is_none() {
177 misses = misses.wrapping_add(1);
178 }
179 core::hint::spin_loop();
180 }
181 misses
182}
183
184#[cfg(all(feature = "std", feature = "result-diagnostics"))]
186#[doc(hidden)]
187pub fn diagnostic_result_slot_register_waiter() -> usize {
188 let slot = TaskResultSlot::<usize>::new();
189 unsafe { slot.register_waiter() };
191 usize::from(slot.has_registered_waiter())
192}
193
194#[cfg(all(feature = "std", feature = "result-diagnostics"))]
196#[doc(hidden)]
197pub fn diagnostic_result_slot_complete_waiting() -> usize {
198 let slot = TaskResultSlot::new();
199 unsafe { slot.register_waiter() };
201 slot.complete(Ok(DIAGNOSTIC_READY_VALUE));
202 match slot.try_take_ready() {
203 Some(Ok(value)) => value,
204 _ => 0,
205 }
206}
207
208#[cfg(feature = "std")]
212#[allow(clippy::module_name_repetitions)]
213pub struct TaskHandle<T> {
214 id: TaskId,
215 result_slot: Option<Arc<TaskResultSlot<T>>>,
216}
217
218#[cfg(feature = "std")]
219impl<T> TaskHandle<T> {
220 #[must_use]
222 pub fn new_pending(id: TaskId) -> (Self, TaskResultSender<T>) {
223 let slot = Arc::new(TaskResultSlot::new());
224 (
225 Self {
226 id,
227 result_slot: Some(Arc::clone(&slot)),
228 },
229 TaskResultSender { slot: Some(slot) },
230 )
231 }
232
233 #[must_use]
235 pub fn ready(id: TaskId, result: Result<T, TaskError>) -> Self {
236 let slot = Arc::new(TaskResultSlot::new());
237 slot.complete(result);
238 Self {
239 id,
240 result_slot: Some(slot),
241 }
242 }
243
244 #[must_use]
252 pub fn new_detached(id: TaskId) -> Self {
253 Self {
254 id,
255 result_slot: None,
256 }
257 }
258
259 #[must_use]
264 pub fn id(&self) -> TaskId {
265 self.id
266 }
267
268 #[must_use]
275 pub fn join(mut self) -> Option<Result<T, TaskError>> {
276 self.result_slot
277 .take()
278 .map(|slot| unsafe { slot.wait::<BlockingResultWait>() })
281 }
282
283 #[must_use]
288 pub fn is_finished(&self) -> bool {
289 self.result_slot
290 .as_ref()
291 .is_some_and(|slot| slot.is_completed())
292 }
293}
294
295#[cfg(feature = "std")]
299#[allow(clippy::module_name_repetitions)]
300pub struct TaskResultSender<T> {
301 slot: Option<Arc<TaskResultSlot<T>>>,
302}
303
304#[cfg(feature = "std")]
305impl<T> TaskResultSender<T> {
306 pub fn send(self, result: Result<T, TaskError>) {
308 let mut sender = ManuallyDrop::new(self);
309 if let Some(slot) = sender.slot.take() {
310 slot.complete(result);
311 }
312 }
313}
314
315#[cfg(feature = "std")]
316impl<T> Drop for TaskResultSender<T> {
317 fn drop(&mut self) {
318 if let Some(slot) = self.slot.take() {
319 slot.complete(Err(TaskError::Cancelled));
320 }
321 }
322}
323
324#[cfg(not(feature = "std"))]
328pub struct TaskHandle<T> {
329 id: TaskId,
330 _phantom: core::marker::PhantomData<T>,
331}
332
333#[cfg(not(feature = "std"))]
334impl<T> TaskHandle<T> {
335 pub fn new(id: TaskId) -> Self {
337 Self {
338 id,
339 _phantom: core::marker::PhantomData,
340 }
341 }
342
343 pub fn new_detached(id: TaskId) -> Self {
345 Self::new(id)
346 }
347
348 pub fn id(&self) -> TaskId {
350 self.id
351 }
352}