nstd_sys/thread.rs
1//! Thread spawning, joining, and detaching.
2use crate::{
3 alloc::CBox,
4 core::{
5 cstr::nstd_core_cstr_get_null,
6 optional::{gen_optional, NSTDOptional},
7 result::NSTDResult,
8 str::{nstd_core_str_as_cstr, NSTDOptionalStr, NSTDStr},
9 time::NSTDDuration,
10 },
11 heap_ptr::NSTDOptionalHeapPtr,
12 io::NSTDIOError,
13 NSTDBool, NSTDUInt,
14};
15use nstdapi::nstdapi;
16use std::thread::{Builder, JoinHandle, Thread, ThreadId};
17
18/// Represents a running thread.
19#[nstdapi]
20pub struct NSTDThread {
21 /// The thread join handle.
22 thread: CBox<JoinHandle<NSTDThreadResult>>,
23}
24gen_optional!(NSTDOptionalThread, NSTDThread);
25
26/// A handle to a running thread.
27#[nstdapi]
28pub struct NSTDThreadHandle {
29 /// A handle to the thread.
30 handle: CBox<Thread>,
31}
32
33/// A thread's unique identifier.
34#[nstdapi]
35pub struct NSTDThreadID {
36 /// The thread ID.
37 id: CBox<ThreadId>,
38}
39
40/// Describes the creation of a new thread.
41///
42/// This type is passed to the `nstd_thread_spawn_with_desc` function.
43#[nstdapi]
44#[derive(Clone, Copy)]
45pub struct NSTDThreadDescriptor {
46 /// The name of the thread.
47 ///
48 /// If present, this must not contain any null bytes.
49 pub name: NSTDOptionalStr,
50 /// The number of bytes that the thread's stack should have.
51 ///
52 /// Set this to 0 to let the host decide how much stack memory should be allocated.
53 pub stack_size: NSTDUInt,
54}
55
56/// A thread function's return value.
57pub type NSTDThreadResult = NSTDOptionalHeapPtr<'static>;
58
59/// Returned from `nstd_thread_join`, contains the thread function's return value on success.
60pub type NSTDOptionalThreadResult = NSTDOptional<NSTDThreadResult>;
61
62/// Returned from `nstd_thread_count`, contains the number of threads detected on the system on
63/// success.
64pub type NSTDThreadCountResult = NSTDResult<NSTDUInt, NSTDIOError>;
65
66/// Spawns a new thread executing the function `thread_fn` and returns a handle to the new thread.
67///
68/// # Parameters:
69///
70/// - `NSTDThreadResult (*thread_fn)(NSTDOptionalHeapPtr)` - The thread function.
71///
72/// - `NSTDOptionalHeapPtr data` - Data to send to the thread.
73///
74/// - `const NSTDThreadDescriptor *desc` - The thread descriptor. This value may be null.
75///
76/// # Returns
77///
78/// `NSTDOptionalThread thread` - A handle to the new thread on success, or an uninitialized "none"
79/// variant on error.
80///
81/// # Safety
82///
83/// - The caller of this function must guarantee that `thread_fn` is a valid function pointer.
84///
85/// - This operation can cause undefined behavior if `desc.name`'s data is invalid.
86///
87/// - The data type that `data` holds must be able to be safely sent between threads.
88///
89/// # Example
90///
91/// ```
92/// use nstd_sys::{
93/// core::optional::NSTDOptional,
94/// heap_ptr::NSTDOptionalHeapPtr,
95/// thread::{nstd_thread_join, nstd_thread_spawn, NSTDThreadResult},
96/// };
97///
98/// unsafe extern "C" fn thread_fn(data: NSTDOptionalHeapPtr) -> NSTDThreadResult {
99/// NSTDOptional::None
100/// }
101///
102/// let thread = unsafe { nstd_thread_spawn(thread_fn, NSTDOptional::None, None) };
103/// if let NSTDOptional::Some(thread) = thread {
104/// if let NSTDOptional::Some(ret) = unsafe { nstd_thread_join(thread) } {
105/// if let NSTDOptional::Some(_) = ret {
106/// panic!("this shouldn't be here");
107/// }
108/// }
109/// }
110/// ```
111#[nstdapi]
112pub unsafe fn nstd_thread_spawn(
113 #[cfg(not(feature = "capi"))] thread_fn: unsafe fn(NSTDOptionalHeapPtr<'_>) -> NSTDThreadResult,
114 #[cfg(feature = "capi")] thread_fn: unsafe extern "C" fn(
115 NSTDOptionalHeapPtr<'_>,
116 ) -> NSTDThreadResult,
117 data: NSTDOptionalHeapPtr<'static>,
118 desc: Option<&NSTDThreadDescriptor>,
119) -> NSTDOptionalThread {
120 // Create the thread builder.
121 let mut builder = Builder::new();
122 if let Some(desc) = desc {
123 // Set the thread name.
124 if let NSTDOptional::Some(name) = &desc.name {
125 // Make sure `name` doesn't contain any null bytes.
126 let c_name = nstd_core_str_as_cstr(name);
127 if !nstd_core_cstr_get_null(&c_name).is_null() {
128 return NSTDOptional::None;
129 }
130 builder = builder.name(name.as_str().to_string());
131 }
132 // Set the thread stack size.
133 if desc.stack_size != 0 {
134 builder = builder.stack_size(desc.stack_size);
135 }
136 }
137 // Spawn the new thread.
138 if let Ok(thread) = builder.spawn(move || thread_fn(data)) {
139 if let Some(thread) = CBox::new(thread) {
140 return NSTDOptional::Some(NSTDThread { thread });
141 }
142 }
143 NSTDOptional::None
144}
145
146/// Returns a handle to the calling thread.
147///
148/// # Returns
149///
150/// `NSTDThreadHandle handle` - A handle to the current thread.
151///
152/// # Panics
153///
154/// Panics if allocating for the thread handle fails.
155#[inline]
156#[nstdapi]
157pub fn nstd_thread_current() -> NSTDThreadHandle {
158 NSTDThreadHandle {
159 handle: CBox::new(std::thread::current()).expect("failed to allocate for a thread handle"),
160 }
161}
162
163/// Retrieves a raw handle to a thread.
164///
165/// # Parameters:
166///
167/// - `const NSTDThread *thread` - A handle to the thread.
168///
169/// # Returns
170///
171/// `NSTDThreadHandle handle` - A raw handle to the thread.
172///
173/// # Panics
174///
175/// Panics if allocating for the thread handle fails.
176#[inline]
177#[nstdapi]
178pub fn nstd_thread_handle(thread: &NSTDThread) -> NSTDThreadHandle {
179 NSTDThreadHandle {
180 handle: CBox::new(thread.thread.thread().clone())
181 .expect("failed to allocate for a thread handle"),
182 }
183}
184
185/// Checks if a thread has finished running.
186///
187/// # Parameters:
188///
189/// - `const NSTDThread *thread` - A handle to the thread.
190///
191/// # Returns
192///
193/// `NSTDBool is_finished` - True if the thread associated with the handle has finished executing.
194#[inline]
195#[nstdapi]
196pub fn nstd_thread_is_finished(thread: &NSTDThread) -> NSTDBool {
197 thread.thread.is_finished()
198}
199
200/// Joins a thread by it's handle.
201///
202/// # Parameters:
203///
204/// - `NSTDThread thread` - The thread handle.
205///
206/// # Returns
207///
208/// `NSTDOptionalThreadResult errc` - The thread function's return code, or none if joining the
209/// thread fails.
210///
211/// # Safety
212///
213/// The data type that the thread function returns must be able to be safely sent between threads.
214#[inline]
215#[nstdapi]
216pub unsafe fn nstd_thread_join(thread: NSTDThread) -> NSTDOptionalThreadResult {
217 thread
218 .thread
219 .into_inner()
220 .join()
221 .map_or(NSTDOptional::None, NSTDOptional::Some)
222}
223
224/// Detaches a thread from it's handle, allowing it to run in the background.
225///
226/// # Parameters:
227///
228/// - `NSTDThread thread` - The thread handle.
229#[inline]
230#[nstdapi]
231#[allow(
232 unused_variables,
233 clippy::missing_const_for_fn,
234 clippy::needless_pass_by_value
235)]
236pub fn nstd_thread_detach(thread: NSTDThread) {}
237
238/// Returns the name of a thread.
239///
240/// # Parameters:
241///
242/// - `const NSTDThreadHandle *handle` - A handle to the thread.
243///
244/// # Returns
245///
246/// `NSTDOptionalStr name` - The name of the thread, or none if the thread is unnamed.
247#[inline]
248#[nstdapi]
249pub fn nstd_thread_name(handle: &NSTDThreadHandle) -> NSTDOptionalStr {
250 handle.handle.name().map_or(NSTDOptional::None, |name| {
251 NSTDOptional::Some(NSTDStr::from_str(name))
252 })
253}
254
255/// Returns a thread's unique identifier.
256///
257/// # Parameters:
258///
259/// - `const NSTDThreadHandle *handle` - A handle to the thread.
260///
261/// # Returns
262///
263/// `NSTDThreadID id` - The thread's unique ID.
264///
265/// # Panics
266///
267/// Panics if allocating for the thread ID fails.
268#[inline]
269#[nstdapi]
270pub fn nstd_thread_id(handle: &NSTDThreadHandle) -> NSTDThreadID {
271 NSTDThreadID {
272 id: CBox::new(handle.handle.id()).expect("failed to allocate for a thread ID"),
273 }
274}
275
276/// Frees an instance of `NSTDThreadHandle`.
277///
278/// # Parameters:
279///
280/// - `NSTDThreadHandle handle` - The handle to free.
281#[inline]
282#[nstdapi]
283#[allow(
284 unused_variables,
285 clippy::missing_const_for_fn,
286 clippy::needless_pass_by_value
287)]
288pub fn nstd_thread_handle_free(handle: NSTDThreadHandle) {}
289
290/// Puts the current thread to sleep for a specified duration.
291///
292/// # Parameters:
293///
294/// - `NSTDDuration duration` - The duration to put the thread to sleep for.
295///
296/// # Panics
297///
298/// Panics if `duration` is negative, overflows Rust's `Duration` structure, or is non-finite.
299#[inline]
300#[nstdapi]
301pub fn nstd_thread_sleep(duration: NSTDDuration) {
302 std::thread::sleep(duration.into_duration());
303}
304
305/// Returns the number of recommended threads that a program should use.
306///
307/// # Returns
308///
309/// `NSTDThreadCountResult threads` - The estimated default amount of parallelism a program should
310/// use on success, or the I/O error code on failure.
311#[inline]
312#[nstdapi]
313pub fn nstd_thread_count() -> NSTDThreadCountResult {
314 match std::thread::available_parallelism() {
315 Ok(threads) => NSTDResult::Ok(threads.get()),
316 Err(err) => NSTDResult::Err(NSTDIOError::from_err(err.kind())),
317 }
318}
319
320/// Checks if the current thread is unwinding due to a panic.
321///
322/// # Returns
323///
324/// `NSTDBool is_panicking` - Determines whether or not the calling thread is panicking.
325#[inline]
326#[nstdapi]
327#[allow(clippy::missing_const_for_fn)]
328pub fn nstd_thread_is_panicking() -> NSTDBool {
329 #[cfg(panic = "unwind")]
330 return std::thread::panicking();
331 #[cfg(panic = "abort")]
332 return false;
333}
334
335/// Compares two thread identifiers.
336///
337/// # Parameters:
338///
339/// - `const NSTDThreadID *xid` - The first identifier.
340///
341/// - `const NSTDThreadID *yid` - The second identifier.
342///
343/// # Returns
344///
345/// `NSTDBool is_eq` - True if the two identifiers refer to the same thread.
346#[inline]
347#[nstdapi]
348pub fn nstd_thread_id_compare(x_id: &NSTDThreadID, y_id: &NSTDThreadID) -> NSTDBool {
349 *x_id.id == *y_id.id
350}
351
352/// Frees an instance of `NSTDThreadID`.
353///
354/// # Parameters:
355///
356/// - `NSTDThreadID id` - A thread identifier.
357#[inline]
358#[nstdapi]
359#[allow(
360 unused_variables,
361 clippy::missing_const_for_fn,
362 clippy::needless_pass_by_value
363)]
364pub fn nstd_thread_id_free(id: NSTDThreadID) {}