Skip to main content

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}