ax_std/thread/multi.rs
1//! Thread APIs for multi-threading configuration.
2
3extern crate alloc;
4
5use alloc::{string::String, sync::Arc};
6use core::{cell::UnsafeCell, num::NonZeroU64};
7
8use ax_api::task::{self as api, AxTaskHandle};
9
10use crate::{StdError, StdResult};
11
12/// A unique identifier for a running thread.
13#[derive(Eq, PartialEq, Clone, Copy, Debug)]
14pub struct ThreadId(NonZeroU64);
15
16/// A handle to a thread.
17pub struct Thread {
18 id: ThreadId,
19}
20
21impl ThreadId {
22 /// This returns a numeric identifier for the thread identified by this
23 /// `ThreadId`.
24 pub fn as_u64(&self) -> NonZeroU64 {
25 self.0
26 }
27}
28
29impl Thread {
30 fn from_id(id: u64) -> Self {
31 Self {
32 id: ThreadId(NonZeroU64::new(id).unwrap()),
33 }
34 }
35
36 /// Gets the thread's unique identifier.
37 pub fn id(&self) -> ThreadId {
38 self.id
39 }
40}
41
42/// Thread factory, which can be used in order to configure the properties of
43/// a new thread.
44///
45/// Methods can be chained on it in order to configure it.
46#[derive(Debug)]
47pub struct Builder {
48 // A name for the thread-to-be, for identification in panic messages
49 name: Option<String>,
50 // The size of the stack for the spawned thread in bytes
51 stack_size: Option<usize>,
52}
53
54impl Builder {
55 /// Generates the base configuration for spawning a thread, from which
56 /// configuration methods can be chained.
57 pub const fn new() -> Builder {
58 Builder {
59 name: None,
60 stack_size: None,
61 }
62 }
63
64 /// Names the thread-to-be.
65 pub fn name(mut self, name: String) -> Builder {
66 self.name = Some(name);
67 self
68 }
69
70 /// Sets the size of the stack (in bytes) for the new thread.
71 pub fn stack_size(mut self, size: usize) -> Builder {
72 self.stack_size = Some(size);
73 self
74 }
75
76 /// Spawns a new thread by taking ownership of the `Builder`, and returns an
77 /// [`StdResult`] to its [`JoinHandle`].
78 ///
79 /// The spawned thread may outlive the caller (unless the caller thread
80 /// is the main thread; the whole process is terminated when the main
81 /// thread finishes). The join handle can be used to block on
82 /// termination of the spawned thread.
83 pub fn spawn<F, T>(self, f: F) -> StdResult<JoinHandle<T>>
84 where
85 F: FnOnce() -> T,
86 F: Send + 'static,
87 T: Send + 'static,
88 {
89 unsafe { self.spawn_unchecked(f) }
90 }
91
92 unsafe fn spawn_unchecked<F, T>(self, f: F) -> StdResult<JoinHandle<T>>
93 where
94 F: FnOnce() -> T,
95 F: Send + 'static,
96 T: Send + 'static,
97 {
98 let name = self.name.unwrap_or_default();
99 let stack_size = self.stack_size.unwrap_or(ax_api::config::TASK_STACK_SIZE);
100
101 let my_packet = Arc::new(Packet {
102 result: UnsafeCell::new(None),
103 });
104 let their_packet = my_packet.clone();
105
106 let main = move || {
107 let ret = f();
108 // SAFETY: `their_packet` as been built just above and moved by the
109 // closure (it is an Arc<...>) and `my_packet` will be stored in the
110 // same `JoinHandle` as this closure meaning the mutation will be
111 // safe (not modify it and affect a value far away).
112 unsafe { *their_packet.result.get() = Some(ret) };
113 drop(their_packet);
114 };
115
116 let task = api::ax_spawn(main, name, stack_size);
117 Ok(JoinHandle {
118 thread: Thread::from_id(task.id()),
119 native: task,
120 packet: my_packet,
121 })
122 }
123}
124
125impl Default for Builder {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131/// Gets a handle to the thread that invokes it.
132pub fn current() -> Thread {
133 let id = api::ax_current_task_id();
134 Thread::from_id(id)
135}
136
137/// Spawns a new thread, returning a [`JoinHandle`] for it.
138///
139/// The join handle provides a [`join`] method that can be used to join the
140/// spawned thread.
141///
142/// The default task name is an empty string. The default thread stack size is
143/// [`ax_api::config::TASK_STACK_SIZE`].
144///
145/// [`join`]: JoinHandle::join
146pub fn spawn<T, F>(f: F) -> JoinHandle<T>
147where
148 F: FnOnce() -> T + Send + 'static,
149 T: Send + 'static,
150{
151 Builder::new().spawn(f).expect("failed to spawn thread")
152}
153
154struct Packet<T> {
155 result: UnsafeCell<Option<T>>,
156}
157
158unsafe impl<T> Sync for Packet<T> {}
159
160/// An owned permission to join on a thread (block on its termination).
161///
162/// A `JoinHandle` *detaches* the associated thread when it is dropped, which
163/// means that there is no longer any handle to the thread and no way to `join`
164/// on it.
165pub struct JoinHandle<T> {
166 native: AxTaskHandle,
167 thread: Thread,
168 packet: Arc<Packet<T>>,
169}
170
171unsafe impl<T> Send for JoinHandle<T> {}
172unsafe impl<T> Sync for JoinHandle<T> {}
173
174impl<T> JoinHandle<T> {
175 /// Extracts a handle to the underlying thread.
176 pub fn thread(&self) -> &Thread {
177 &self.thread
178 }
179
180 /// Waits for the associated thread to finish.
181 ///
182 /// This function will return immediately if the associated thread has
183 /// already finished.
184 #[track_caller]
185 pub fn join(mut self) -> StdResult<T> {
186 api::ax_wait_for_exit(self.native);
187 Arc::get_mut(&mut self.packet)
188 .unwrap()
189 .result
190 .get_mut()
191 .take()
192 .ok_or(StdError::ThreadResultUnavailable)
193 }
194}