osal_rs/posix/thread.rs
1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21//! Task/thread creation, management, and notifications for POSIX.
22//!
23//! [`Thread`] wraps a pthread, adding what pthreads lacks natively but
24//! FreeRTOS tasks provide directly: suspend/resume (emulated with a pair of
25//! real-time signals), a single-slot notification value
26//! (`notify`/`wait_notification`, backed by a process-wide table keyed by
27//! thread handle), and metadata queries (`get_metadata`, backed by a similar
28//! registry so [`crate::os::System`] can enumerate every thread spawned
29//! through this API).
30//!
31//! # Examples
32//!
33//! ```
34//! use osal_rs::os::*;
35//! use std::sync::Arc;
36//!
37//! let mut thread = Thread::new("worker", 1024, 5);
38//! let spawned = thread.spawn_simple(|| {
39//! println!("Working...");
40//! Ok(Arc::new(()))
41//! }).unwrap();
42//!
43//! spawned.join(core::ptr::null_mut()).unwrap();
44//! ```
45
46use core::cell::UnsafeCell;
47use core::ffi::{c_int, c_long, c_void};
48use core::fmt::{Debug, Display, Formatter};
49use core::ops::Deref;
50use core::ptr::null_mut;
51use core::time::Duration;
52use std::collections::HashMap;
53
54use alloc::sync::Arc;
55
56use crate::os::{Mutex, MutexFn, MutexGuard, ThreadSimpleFnPtr};
57use crate::posix::config::TICK_PERIOD_MS;
58#[cfg(feature = "real_time")]
59use crate::posix::ffi::{PTHREAD_EXPLICIT_SCHED, SCHED_FIFO, pthread_attr_setinheritsched, pthread_attr_setschedparam, pthread_attr_setschedpolicy, sched_param};
60use crate::posix::ffi::{
61 __libc_current_sigrtmin, CLOCK_MONOTONIC, ETIMEDOUT, PTHREAD_ONCE_INIT, PTHREAD_STACK_MIN, clock_gettime, pthread_attr_init, pthread_attr_setstacksize, pthread_attr_t,
62 pthread_cond_broadcast, pthread_cond_destroy, pthread_cond_init, pthread_cond_t, pthread_cond_timedwait, pthread_cond_wait, pthread_condattr_init, pthread_condattr_setclock,
63 pthread_condattr_t, pthread_create, pthread_detach, pthread_join, pthread_kill, pthread_once, pthread_once_t, pthread_self, pthread_setname_np, sigdelset, sigfillset, sigset_t, signal,
64 sigsuspend,
65 timespec,
66};
67use crate::posix::types::{BaseType, StackType, ThreadHandle, TickType, UBaseType};
68use crate::traits::{ThreadFn, ThreadFnPtr, ThreadMetadata, ThreadNotification, ThreadParam, ThreadState, ToPriority, ToTick};
69use crate::traits::MAX_TASK_NAME_LEN;
70use crate::utils::{Bytes, DoublePtr, Error, Result};
71
72/// Real-time signal sent to a thread to ask it to suspend itself; see
73/// [`suspend_signal_handler`].
74fn suspend_signal() -> c_int {
75 unsafe { __libc_current_sigrtmin() }
76}
77
78/// Real-time signal sent to a thread parked in [`suspend_signal_handler`] to
79/// wake it back up. Always `suspend_signal() + 1`, so it lands on the next
80/// glibc-usable real-time signal.
81fn resume_signal() -> c_int {
82 suspend_signal() + 1
83}
84
85/// Handler for [`suspend_signal`]: parks the calling thread until [`resume_signal`] arrives.
86///
87/// pthreads has no native suspend/resume, so this crate emulates it with a
88/// pair of real-time signals. `sigsuspend()` atomically swaps in a mask that
89/// blocks every signal except the resume one and blocks the thread until a
90/// signal is delivered; since nothing else can get through, that signal can
91/// only be the resume one. When `sigsuspend()` returns, this handler returns
92/// too, and the thread it interrupted simply continues from wherever it was
93/// — that's what makes the suspension transparent to the thread's own code.
94///
95/// # Caveat
96///
97/// If `resume()` runs before the target thread has actually reached
98/// `sigsuspend()` below (the suspend signal was sent but not yet delivered),
99/// the resume signal is delivered with nothing waiting for it and is lost,
100/// leaving the thread suspended until a further `resume()` call. Callers
101/// needing a hard guarantee should pair `suspend()`/`resume()` with their
102/// own synchronization.
103extern "C" fn suspend_signal_handler(_sig: c_int) {
104 let mut mask: sigset_t = Default::default();
105
106 unsafe {
107 sigfillset(&mut mask);
108 sigdelset(&mut mask, resume_signal());
109 sigsuspend(&mask);
110 }
111}
112
113/// No-op handler for [`resume_signal`].
114///
115/// Its only purpose is to exist: installing a handler is what lets this
116/// signal interrupt `sigsuspend()` in [`suspend_signal_handler`] instead of
117/// being blocked, and — since this is a real-time signal — it avoids the
118/// default action of terminating the process.
119extern "C" fn resume_signal_handler(_sig: c_int) {}
120
121/// Installs [`suspend_signal_handler`]/[`resume_signal_handler`], once per process.
122fn ensure_suspend_signal_handlers() {
123 static mut ONCE: pthread_once_t = PTHREAD_ONCE_INIT;
124
125 extern "C" fn init() {
126 unsafe {
127 signal(suspend_signal(), suspend_signal_handler as *const () as usize);
128 signal(resume_signal(), resume_signal_handler as *const () as usize);
129 }
130 }
131
132 unsafe {
133 pthread_once(&raw mut ONCE, Some(init));
134 }
135}
136
137/// Condition variable backing [`NotifySlot`]'s wait/wake, and [`ensure_suspend_signal_handlers`]'s
138/// [`pthread_once_t`]-based sibling for one-time initialization.
139///
140/// Backed directly by `pthread_cond_t` rather than `std::sync::Condvar`:
141/// the latter's `wait`/`wait_timeout` only accept `std::sync::MutexGuard`,
142/// which can't pair with [`crate::os::Mutex`]'s own guard type.
143struct RawCondvar(UnsafeCell<pthread_cond_t>);
144
145unsafe impl Send for RawCondvar {}
146unsafe impl Sync for RawCondvar {}
147
148impl RawCondvar {
149 fn new() -> Self {
150 let mut attr: pthread_condattr_t = Default::default();
151 let mut cond: pthread_cond_t = Default::default();
152
153 unsafe {
154 pthread_condattr_init(&mut attr);
155 pthread_condattr_setclock(&mut attr, CLOCK_MONOTONIC);
156 pthread_cond_init(&mut cond, &attr);
157 }
158
159 Self(UnsafeCell::new(cond))
160 }
161
162 /// Atomically unlocks `guard`'s mutex and blocks until [`notify_all`](Self::notify_all)
163 /// wakes it, re-locking the mutex before returning. May return spuriously;
164 /// callers must re-check their predicate in a loop, same as with any condvar.
165 fn wait<T: ?Sized>(&self, guard: &MutexGuard<'_, T>) {
166 unsafe {
167 pthread_cond_wait(self.0.get(), guard.raw_handle());
168 }
169 }
170
171 /// As [`wait`](Self::wait), but gives up once the monotonic-clock `deadline`
172 /// passes. Returns `true` if it gave up because of the deadline, `false` if
173 /// woken normally (which, same as [`wait`](Self::wait), may be spurious).
174 fn wait_until<T: ?Sized>(&self, guard: &MutexGuard<'_, T>, deadline: timespec) -> bool {
175 unsafe { pthread_cond_timedwait(self.0.get(), guard.raw_handle(), &deadline) == ETIMEDOUT }
176 }
177
178 fn notify_all(&self) {
179 unsafe {
180 pthread_cond_broadcast(self.0.get());
181 }
182 }
183}
184
185impl Default for RawCondvar {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191impl Drop for RawCondvar {
192 fn drop(&mut self) {
193 unsafe {
194 pthread_cond_destroy(self.0.get());
195 }
196 }
197}
198
199/// Computes an absolute deadline `timeout` from now on the monotonic clock,
200/// for [`RawCondvar::wait_until`] (its `pthread_condattr_setclock(CLOCK_MONOTONIC)`
201/// counterpart to `pthread_cond_timedwait`'s absolute `abstime`).
202fn monotonic_deadline(timeout: Duration) -> timespec {
203 let mut now = timespec::default();
204 unsafe {
205 clock_gettime(CLOCK_MONOTONIC, &mut now);
206 }
207
208 let mut tv_sec = now.tv_sec + timeout.as_secs() as c_long;
209 let mut tv_nsec = now.tv_nsec + timeout.subsec_nanos() as c_long;
210
211 if tv_nsec >= 1_000_000_000 {
212 tv_sec += 1;
213 tv_nsec -= 1_000_000_000;
214 }
215
216 timespec { tv_sec, tv_nsec }
217}
218
219/// A thread's pending task-notification value, plus whether one is pending.
220///
221/// Mirrors the single-slot notification FreeRTOS keeps directly on its task
222/// control block: `pending` is what `wait_notification()` blocks on, and
223/// `value` is what it hands back once woken.
224#[derive(Default)]
225struct NotifyState {
226 value: u32,
227 pending: bool,
228}
229
230/// The synchronization primitives backing one thread's notification slot.
231struct NotifySlot {
232 state: Mutex<NotifyState>,
233 cv: RawCondvar,
234}
235
236impl Default for NotifySlot {
237 fn default() -> Self {
238 Self {
239 state: Mutex::new(NotifyState::default()),
240 cv: RawCondvar::default(),
241 }
242 }
243}
244
245/// Process-wide table of notification slots, keyed by `pthread_t`.
246///
247/// pthreads has nothing resembling FreeRTOS's per-task notification value,
248/// so this crate keeps its own, addressed by thread handle rather than
249/// stored on `Thread` itself — `Thread` is freely cloned, but a notification
250/// belongs to the underlying OS thread, not to any one Rust handle to it.
251fn notify_registry() -> &'static Mutex<HashMap<ThreadHandle, Arc<NotifySlot>>> {
252 static mut ONCE: pthread_once_t = PTHREAD_ONCE_INIT;
253 static mut REGISTRY: *mut Mutex<HashMap<ThreadHandle, Arc<NotifySlot>>> = null_mut();
254
255 extern "C" fn init() {
256 unsafe {
257 REGISTRY = Box::into_raw(Box::new(Mutex::new(HashMap::new())));
258 }
259 }
260
261 unsafe {
262 pthread_once(&raw mut ONCE, Some(init));
263 &*REGISTRY
264 }
265}
266
267/// Returns `handle`'s notification slot, creating it on first use.
268fn notify_slot(handle: ThreadHandle) -> Arc<NotifySlot> {
269 notify_registry()
270 .lock()
271 .unwrap()
272 .entry(handle)
273 .or_insert_with(|| Arc::new(NotifySlot::default()))
274 .clone()
275}
276
277/// Drops `handle`'s notification slot, if any.
278///
279/// glibc recycles `pthread_t` values once a thread has been joined, so a
280/// slot left behind after that point could be silently inherited by an
281/// unrelated future thread. Called once a thread is known to be gone
282/// (`delete()`/`join()` returning successfully).
283fn forget_notify_slot(handle: ThreadHandle) {
284 if let Ok(mut registry) = notify_registry().lock() {
285 registry.remove(&handle);
286 }
287}
288
289/// Process-wide table of threads spawned through this crate's `Thread` API,
290/// keyed by `pthread_t`.
291///
292/// pthreads exposes no enumeration API of its own, so `System::get_all_thread()`
293/// and `System::count_threads()` are backed by this registry instead: every
294/// successful `spawn()`/`spawn_simple()` adds an entry, and `join()`/`delete()`
295/// remove it once the thread is known to be gone.
296fn thread_registry() -> &'static Mutex<HashMap<ThreadHandle, ThreadMetadata>> {
297 static mut ONCE: pthread_once_t = PTHREAD_ONCE_INIT;
298 static mut REGISTRY: *mut Mutex<HashMap<ThreadHandle, ThreadMetadata>> = null_mut();
299
300 extern "C" fn init() {
301 unsafe {
302 REGISTRY = Box::into_raw(Box::new(Mutex::new(HashMap::new())));
303 }
304 }
305
306 unsafe {
307 pthread_once(&raw mut ONCE, Some(init));
308 &*REGISTRY
309 }
310}
311
312/// Records `metadata` under `metadata.thread` for [`System::get_all_thread()`].
313fn register_thread(metadata: ThreadMetadata) {
314 if let Ok(mut registry) = thread_registry().lock() {
315 registry.insert(metadata.thread, metadata);
316 }
317}
318
319/// Drops `handle`'s registry entry, if any (see [`forget_notify_slot`] for why).
320fn forget_thread(handle: ThreadHandle) {
321 if let Ok(mut registry) = thread_registry().lock() {
322 registry.remove(&handle);
323 }
324}
325
326/// Updates `handle`'s tracked [`ThreadState`] in the registry, if it has an entry.
327///
328/// Threads not spawned through this crate's API (e.g. a foreign thread only
329/// ever wrapped via [`Thread::new_with_handle`]) have no registry entry and
330/// are silently ignored, same as [`forget_thread`].
331fn set_thread_state(handle: ThreadHandle, state: ThreadState) {
332 if let Ok(mut registry) = thread_registry().lock() {
333 if let Some(metadata) = registry.get_mut(&handle) {
334 metadata.state = state;
335 }
336 }
337}
338
339/// Returns `handle`'s registry entry, if any.
340fn registered_thread_metadata(handle: ThreadHandle) -> Option<ThreadMetadata> {
341 thread_registry().lock().ok().and_then(|registry| registry.get(&handle).cloned())
342}
343
344/// Resolves `handle`'s effective [`ThreadState`] for metadata queries.
345///
346/// A thread can only ask about its own metadata while it's actually
347/// executing: `suspend()` parks the target thread inside `sigsuspend()`
348/// (see [`suspend_signal_handler`]), so a genuinely suspended thread can
349/// never itself reach this call. `pthread_self()` therefore always
350/// overrides `tracked` with [`ThreadState::Running`]; for every other
351/// handle, the last state recorded by [`set_thread_state`] is the best
352/// information available.
353fn effective_thread_state(handle: ThreadHandle, tracked: ThreadState) -> ThreadState {
354 if handle == unsafe { pthread_self() } { ThreadState::Running } else { tracked }
355}
356
357/// Snapshot of every thread currently registered via `spawn()`/`spawn_simple()`.
358///
359/// Used by [`crate::posix::system::System::get_all_thread`].
360pub(crate) fn all_registered_threads() -> Vec<ThreadMetadata> {
361 thread_registry()
362 .lock()
363 .map(|registry| registry.values().cloned().collect())
364 .unwrap_or_default()
365}
366
367/// Number of threads currently registered via `spawn()`/`spawn_simple()`.
368///
369/// Used by [`crate::posix::system::System::count_threads`].
370pub(crate) fn registered_thread_count() -> usize {
371 thread_registry().lock().map(|registry| registry.len()).unwrap_or(0)
372}
373
374/// Applies a [`ThreadNotification`] action to `state`, FreeRTOS-`xTaskNotify`-style.
375///
376/// Every action except [`ThreadNotification::SetValueWithoutOverwrite`]
377/// always succeeds. That one only updates `value` if no notification is
378/// currently pending (i.e. the previous one was consumed by
379/// `wait_notification()`); if one is already pending, it fails without
380/// touching `value`, matching FreeRTOS's `xTaskNotify(eSetValueWithoutOverwrite)`
381/// returning `pdFAIL`.
382fn apply_notification(state: &mut NotifyState, notification: ThreadNotification) -> Result<()> {
383 use ThreadNotification::*;
384 match notification {
385 NoAction => {}
386 SetBits(bits) => state.value |= bits,
387 Increment => state.value = state.value.wrapping_add(1),
388 SetValueWithOverwrite(value) => state.value = value,
389 SetValueWithoutOverwrite(value) => {
390 if state.pending {
391 return Err(Error::QueueFull);
392 }
393 state.value = value;
394 }
395 }
396
397 state.pending = true;
398 Ok(())
399}
400
401/// A schedulable unit of execution backed by a POSIX thread (`pthread_t`).
402///
403/// Created in a "not yet spawned" state via [`Thread::new`], and only backed
404/// by a real OS thread once [`ThreadFn::spawn`]/[`ThreadFn::spawn_simple`] is
405/// called on it. See [`Thread::new`] for a complete, testable example.
406#[derive(Clone)]
407pub struct Thread {
408 handle: ThreadHandle,
409 name: Bytes<MAX_TASK_NAME_LEN>,
410 stack_depth: StackType,
411 priority: UBaseType,
412 callback: Option<Arc<ThreadFnPtr>>,
413 param: Option<ThreadParam>,
414}
415
416unsafe impl Send for Thread {}
417unsafe impl Sync for Thread {}
418
419impl Thread {
420 /// Describes a not-yet-spawned thread: `name`/`stack_depth`/`priority`
421 /// are recorded now and used when [`ThreadFn::spawn`]/`spawn_simple` is
422 /// called on it. [`ThreadFn::is_null`] is `true` until then.
423 ///
424 /// # Examples
425 ///
426 /// ```
427 /// use osal_rs::os::*;
428 ///
429 /// let thread = Thread::new("worker", 1024, 5);
430 /// assert!(thread.is_null());
431 /// ```
432 pub fn new(name: &str, stack_depth: StackType, priority: UBaseType) -> Self {
433 Self {
434 handle: 0,
435 name: Bytes::from_str(name),
436 stack_depth,
437 priority,
438 callback: None,
439 param: None,
440 }
441 }
442
443 /// Wraps an already-running thread's handle (e.g. one obtained from
444 /// [`ThreadFn::get_current`]), rather than spawning a new one. Fails
445 /// with [`Error::NullPtr`] if `handle` is the null sentinel (`0`).
446 ///
447 /// # Examples
448 ///
449 /// ```
450 /// use osal_rs::os::*;
451 ///
452 /// let current = Thread::get_current();
453 /// let wrapped = Thread::new_with_handle(*current, "current", 0, 0).unwrap();
454 /// assert!(!wrapped.is_null());
455 /// ```
456 pub fn new_with_handle(handle: ThreadHandle, name: &str, stack_depth: StackType, priority: UBaseType) -> Result<Self> {
457 if handle == 0 {
458 return Err(Error::NullPtr);
459 }
460
461 Ok(Self {
462 handle,
463 name: Bytes::from_str(name),
464 stack_depth,
465 priority,
466 callback: None,
467 param: None,
468 })
469 }
470
471 /// Same as [`Thread::new`], but accepts any [`ToPriority`] value instead
472 /// of a raw [`UBaseType`] priority.
473 ///
474 /// # Examples
475 ///
476 /// ```
477 /// use osal_rs::os::*;
478 /// use osal_rs::os::types::UBaseType;
479 ///
480 /// enum Priority { High }
481 ///
482 /// impl ToPriority for Priority {
483 /// fn to_priority(&self) -> UBaseType { 5 }
484 /// }
485 ///
486 /// let thread = Thread::new_with_to_priority("worker", 1024, Priority::High);
487 /// assert!(thread.is_null());
488 /// ```
489 #[inline]
490 pub fn new_with_to_priority(name: &str, stack_depth: StackType, priority: impl ToPriority) -> Self {
491 Self::new(name, stack_depth, priority.to_priority())
492 }
493
494 /// Same as [`Thread::new_with_handle`], but accepts any [`ToPriority`]
495 /// value instead of a raw [`UBaseType`] priority.
496 ///
497 /// # Examples
498 ///
499 /// ```
500 /// use osal_rs::os::*;
501 /// use osal_rs::os::types::UBaseType;
502 ///
503 /// enum Priority { Normal }
504 ///
505 /// impl ToPriority for Priority {
506 /// fn to_priority(&self) -> UBaseType { 0 }
507 /// }
508 ///
509 /// let current = Thread::get_current();
510 /// let wrapped = Thread::new_with_handle_and_to_priority(*current, "current", 0, Priority::Normal).unwrap();
511 /// assert!(!wrapped.is_null());
512 /// ```
513 #[inline]
514 pub fn new_with_handle_and_to_priority(handle: ThreadHandle, name: &str, stack_depth: StackType, priority: impl ToPriority) -> Result<Self> {
515 Self::new_with_handle(handle, name, stack_depth, priority.to_priority())
516 }
517
518 /// Looks up a [`ThreadMetadata`] snapshot for a raw [`ThreadHandle`],
519 /// without needing a [`Thread`] value. Used by
520 /// [`crate::os::SystemFn::get_all_thread`] to report threads it only knows
521 /// by handle.
522 ///
523 /// # Examples
524 ///
525 /// ```
526 /// use osal_rs::os::*;
527 ///
528 /// let current = Thread::get_current();
529 /// let metadata = Thread::get_metadata_from_handle(*current);
530 /// assert_eq!(metadata.thread, *current);
531 /// ```
532 pub fn get_metadata_from_handle(handle: ThreadHandle) -> ThreadMetadata {
533 if handle == 0 {
534 return ThreadMetadata::default();
535 }
536
537 match registered_thread_metadata(handle) {
538 Some(metadata) => ThreadMetadata {
539 state: effective_thread_state(handle, metadata.state),
540 ..metadata
541 },
542 None => ThreadMetadata {
543 thread: handle,
544 name: Bytes::from_str("thread"),
545 stack_depth: 0,
546 priority: 0,
547 thread_number: 0,
548 state: effective_thread_state(handle, ThreadState::Ready),
549 current_priority: 0,
550 base_priority: 0,
551 run_time_counter: 0,
552 stack_high_water_mark: 0,
553 },
554 }
555 }
556
557 /// Builds a [`ThreadMetadata`] snapshot from a [`Thread`] value
558 /// directly - same information as [`Thread::get_metadata_from_handle`],
559 /// but also works for a not-yet-spawned thread (reported as
560 /// [`ThreadState::Invalid`]).
561 ///
562 /// # Examples
563 ///
564 /// ```
565 /// use osal_rs::os::*;
566 ///
567 /// let thread = Thread::new("worker", 1024, 3);
568 /// let metadata = Thread::get_metadata(&thread);
569 /// assert_eq!(metadata.state, ThreadState::Invalid);
570 /// assert_eq!(metadata.priority, 3);
571 /// ```
572 pub fn get_metadata(thread: &Thread) -> ThreadMetadata {
573 // Name/stack/priority reflect what was passed to `new()` regardless of
574 // whether the thread has been spawned yet; only `state`/`thread` depend
575 // on there being a live `pthread_t` behind it.
576 let state = if thread.is_null() {
577 ThreadState::Invalid
578 } else {
579 let tracked = registered_thread_metadata(thread.handle).map(|metadata| metadata.state).unwrap_or(ThreadState::Ready);
580 effective_thread_state(thread.handle, tracked)
581 };
582
583 ThreadMetadata {
584 thread: thread.handle,
585 name: thread.name.clone(),
586 stack_depth: thread.stack_depth,
587 priority: thread.priority,
588 thread_number: thread.handle,
589 state,
590 current_priority: thread.priority,
591 base_priority: thread.priority,
592 run_time_counter: 0,
593 stack_high_water_mark: 0,
594 }
595 }
596
597 /// Blocks like [`ThreadFn::wait_notification`], but accepts any
598 /// [`ToTick`] timeout (e.g. a [`core::time::Duration`]) instead of a raw
599 /// tick count.
600 ///
601 /// # Examples
602 ///
603 /// ```
604 /// use osal_rs::os::*;
605 /// use core::time::Duration;
606 ///
607 /// let current = Thread::get_current();
608 /// current.notify(ThreadNotification::SetValueWithOverwrite(7)).unwrap();
609 ///
610 /// let value = current.wait_notification_with_to_tick(0, 0, Duration::from_millis(50)).unwrap();
611 /// assert_eq!(value, 7);
612 /// ```
613 #[inline]
614 pub fn wait_notification_with_to_tick(&self, bits_to_clear_on_entry: u32, bits_to_clear_on_exit: u32, timeout_ticks: impl ToTick) -> Result<u32> {
615 self.wait_notification(bits_to_clear_on_entry, bits_to_clear_on_exit, timeout_ticks.to_ticks())
616 }
617
618 fn metadata(&self) -> ThreadMetadata {
619 Self::get_metadata(self)
620 }
621
622 /// Gives up on ever joining this thread: `pthread_detach(3)` makes the
623 /// system reclaim it on its own once it returns, and its registry and
624 /// notification-slot entries are dropped now rather than at the join
625 /// [`ThreadFn::delete`] would have performed.
626 ///
627 /// Only useful where joining is impossible rather than merely unwanted -
628 /// namely from the thread itself, which is why this is crate-internal
629 /// (`posix::Timer` needs it when a timer callback drops the last handle
630 /// to its own timer). Everything else should use [`ThreadFn::delete`].
631 pub(super) fn detach(&self) {
632 let _ = unsafe { pthread_detach(self.handle) };
633 forget_notify_slot(self.handle);
634 forget_thread(self.handle);
635 }
636}
637
638/// Internal C-compatible wrapper for thread callbacks.
639///
640/// Bridges between the pthreads C API and Rust closures. It unpacks the
641/// boxed thread instance, resolves the thread's own handle via
642/// `pthread_self()` (avoiding any race with `pthread_create()`'s caller
643/// writing `*thread`, which may not have happened yet once this routine
644/// starts running), and invokes the user-provided callback.
645///
646/// The callback's `Result<ThreadParam>` is boxed and returned as the raw
647/// `void *` the pthreads API uses for a thread's exit value: whoever calls
648/// `Thread::join()` on this thread receives this same pointer back and can
649/// reconstruct it with `Box::from_raw(ptr as *mut Result<ThreadParam>)`.
650///
651/// # Safety
652///
653/// - `param_ptr` must be a valid pointer produced by `Box::into_raw` on a `Thread`
654/// - Called only by `pthread_create()` as the thread's start routine
655unsafe extern "C" fn callback_c_wrapper(param_ptr: *mut c_void) -> *mut c_void {
656 if param_ptr.is_null() {
657 return null_mut();
658 }
659
660 let mut thread_instance: Box<Thread> = unsafe { Box::from_raw(param_ptr as *mut _) };
661
662 thread_instance.as_mut().handle = unsafe { pthread_self() };
663 let handle = thread_instance.handle;
664
665 let param_arc: Option<ThreadParam> = thread_instance.param.clone();
666
667 // Note: intentionally does *not* call `Thread::delete()`/`join()` here —
668 // that would have this thread call `pthread_join()` on its own ID, which
669 // is undefined behavior (self-join). Reaping/cleanup is left to whichever
670 // other thread eventually calls `join()`/`delete()` on this handle.
671 let ret = if let Some(callback) = &thread_instance.callback.clone() {
672 callback(thread_instance, param_arc)
673 } else {
674 Err(Error::NullPtr)
675 };
676
677 // The callback has returned: the thread is finished even though nobody
678 // has joined it yet, so reflect that in the registry rather than leaving
679 // whatever state (e.g. `Ready`) was last tracked while it was running.
680 set_thread_state(handle, ThreadState::Deleted);
681
682 Box::into_raw(Box::new(ret)) as *mut c_void
683}
684
685/// Internal C-compatible wrapper for simple (parameter-less) thread callbacks.
686///
687/// Unpacks the boxed `Arc<ThreadSimpleFnPtr>` and invokes it directly; unlike
688/// [`callback_c_wrapper`] there is no `Thread` instance to reconstruct here.
689/// The callback's `Result<ThreadParam>` is boxed and returned as the raw
690/// `void *` exit value, the same way `callback_c_wrapper` does, so `Thread::join()`
691/// works identically for threads spawned with `spawn_simple()`.
692///
693/// # Safety
694///
695/// - `param_ptr` must be a valid pointer produced by `Box::into_raw` on an `Arc<ThreadSimpleFnPtr>`
696/// - Called only by `pthread_create()` as the thread's start routine
697unsafe extern "C" fn simple_callback_c_wrapper(param_ptr: *mut c_void) -> *mut c_void {
698 if param_ptr.is_null() {
699 return null_mut();
700 }
701
702 let func: Box<Arc<ThreadSimpleFnPtr>> = unsafe { Box::from_raw(param_ptr as *mut _) };
703 let ret = func();
704
705 // See the equivalent comment in `callback_c_wrapper`.
706 set_thread_state(unsafe { pthread_self() }, ThreadState::Deleted);
707
708 Box::into_raw(Box::new(ret)) as *mut c_void
709}
710
711impl ThreadFn for Thread {
712
713 /// Returns `true` if this handle refers to no thread - either
714 /// [`Thread::new`] was never followed by `spawn`/`spawn_simple`, or the
715 /// pthread ID happens to be the reserved `0` sentinel.
716 ///
717 /// # Examples
718 ///
719 /// ```
720 /// use osal_rs::os::*;
721 ///
722 /// let thread = Thread::new("worker", 1024, 5);
723 /// assert!(thread.is_null());
724 /// ```
725 fn is_null(&self) -> bool {
726 self.handle == 0
727 }
728
729 /// Spawns a new pthread running `callback(self_handle, param)`, passing
730 /// through an arbitrary [`ThreadParam`] (an `Arc<dyn Any + Send + Sync>`)
731 /// that the callback can downcast back to its concrete type. Prefer
732 /// [`ThreadFn::spawn_simple`] when no parameter is needed.
733 ///
734 /// # Examples
735 ///
736 /// ```
737 /// use osal_rs::os::*;
738 /// use std::sync::Arc;
739 /// use std::sync::atomic::{AtomicI32, Ordering};
740 ///
741 /// static RECEIVED: AtomicI32 = AtomicI32::new(0);
742 ///
743 /// let mut thread = Thread::new("worker", 1024, 5);
744 /// let param: ThreadParam = Arc::new(42i32);
745 ///
746 /// let spawned = thread.spawn(Some(param), |_handle, param| {
747 /// if let Some(value) = param.and_then(|p| p.downcast_ref::<i32>().copied()) {
748 /// RECEIVED.store(value, Ordering::SeqCst);
749 /// }
750 /// Ok(Arc::new(()))
751 /// }).unwrap();
752 ///
753 /// spawned.join(core::ptr::null_mut()).unwrap();
754 /// assert_eq!(RECEIVED.load(Ordering::SeqCst), 42);
755 /// ```
756 fn spawn<F>(&mut self, param: Option<ThreadParam>, callback: F) -> Result<Self>
757 where
758 F: Fn(Box<dyn ThreadFn>, Option<ThreadParam>) -> Result<ThreadParam>,
759 F: Send + Sync + 'static,
760 Self: Sized,
761 {
762 let func: Arc<ThreadFnPtr> = Arc::new(callback);
763 self.callback = Some(func);
764 self.param = param.clone();
765
766 let mut attr: pthread_attr_t = Default::default();
767
768 unsafe {
769 pthread_attr_init (&mut attr);
770 }
771
772 let requested_stack_size = PTHREAD_STACK_MIN + self.stack_depth as usize;
773
774 let min_safe_stack_size = 1024usize * 1024usize;
775
776 unsafe {
777 pthread_attr_setstacksize (&mut attr, if requested_stack_size < min_safe_stack_size { min_safe_stack_size } else { requested_stack_size });
778 }
779
780 #[cfg(feature = "real_time")]
781 unsafe {
782 let fifo_param = sched_param {
783 sched_priority: self.priority as core::ffi::c_int,
784 };
785 pthread_attr_setinheritsched(&mut attr, PTHREAD_EXPLICIT_SCHED);
786 pthread_attr_setschedpolicy(&mut attr, SCHED_FIFO);
787 pthread_attr_setschedparam(&mut attr, &fifo_param);
788 }
789
790 let boxed_thread = Box::new(self.clone());
791
792 let ret = unsafe {
793 pthread_create(&mut self.handle, &attr, Some(callback_c_wrapper), Box::into_raw(boxed_thread) as *mut c_void)
794 };
795
796 if ret != 0 {
797 return Err(Error::ReturnWithCode(ret));
798 }
799
800 unsafe {
801 pthread_setname_np(self.handle, self.name.as_cstr().as_ptr());
802 }
803
804 register_thread(ThreadMetadata {
805 thread: self.handle,
806 name: self.name.clone(),
807 stack_depth: self.stack_depth,
808 priority: self.priority,
809 thread_number: 0,
810 state: ThreadState::Ready,
811 current_priority: self.priority,
812 base_priority: self.priority,
813 run_time_counter: 0,
814 stack_high_water_mark: 0,
815 });
816
817 Ok(Self {
818 handle: self.handle,
819 name: self.name.clone(),
820 stack_depth: self.stack_depth,
821 priority: self.priority,
822 callback: self.callback.clone(),
823 param,
824 })
825 }
826
827 /// Spawns a new pthread running `callback()`. Simpler than
828 /// [`ThreadFn::spawn`] when no parameter needs to be passed in.
829 ///
830 /// # Examples
831 ///
832 /// ```
833 /// use osal_rs::os::*;
834 /// use std::sync::Arc;
835 ///
836 /// let mut thread = Thread::new("worker", 1024, 5);
837 /// let spawned = thread.spawn_simple(|| {
838 /// println!("Working...");
839 /// Ok(Arc::new(()))
840 /// }).unwrap();
841 ///
842 /// spawned.join(core::ptr::null_mut()).unwrap();
843 /// ```
844 fn spawn_simple<F>(&mut self, callback: F) -> Result<Self>
845 where
846 F: Fn() -> Result<ThreadParam> + Send + Sync + 'static,
847 Self: Sized,
848 {
849 let func: Arc<ThreadSimpleFnPtr> = Arc::new(callback);
850 let boxed_func = Box::new(func);
851
852
853 let mut attr: pthread_attr_t = Default::default();
854
855 unsafe {
856 pthread_attr_init (&mut attr);
857 }
858
859 let requested_stack_size = PTHREAD_STACK_MIN + self.stack_depth as usize;
860
861 let min_safe_stack_size = 1024usize * 1024usize;
862
863 unsafe {
864 pthread_attr_setstacksize (&mut attr, if requested_stack_size < min_safe_stack_size { min_safe_stack_size } else { requested_stack_size });
865 }
866
867 #[cfg(feature = "real_time")]
868 unsafe {
869 let fifo_param = sched_param {
870 sched_priority: self.priority as core::ffi::c_int,
871 };
872 pthread_attr_setinheritsched(&mut attr, PTHREAD_EXPLICIT_SCHED);
873 pthread_attr_setschedpolicy(&mut attr, SCHED_FIFO);
874 pthread_attr_setschedparam(&mut attr, &fifo_param);
875 }
876
877 let ret = unsafe {
878 pthread_create(&mut self.handle, &attr, Some(simple_callback_c_wrapper), Box::into_raw(boxed_func) as *mut c_void)
879 };
880
881 if ret != 0 {
882 return Err(Error::ReturnWithCode(ret));
883 }
884
885 unsafe {
886 pthread_setname_np(self.handle, self.name.as_cstr().as_ptr());
887 }
888
889 register_thread(ThreadMetadata {
890 thread: self.handle,
891 name: self.name.clone(),
892 stack_depth: self.stack_depth,
893 priority: self.priority,
894 thread_number: 0,
895 state: ThreadState::Ready,
896 current_priority: self.priority,
897 base_priority: self.priority,
898 run_time_counter: 0,
899 stack_high_water_mark: 0,
900 });
901
902 Ok(Self {
903 handle: self.handle,
904 name: self.name.clone(),
905 stack_depth: self.stack_depth,
906 priority: self.priority,
907 callback: self.callback.clone(),
908 param: self.param.clone(),
909 })
910 }
911
912 /// Joins the thread (blocking until it finishes) and forgets its
913 /// registry/notification-slot entries, discarding any error from the
914 /// underlying `pthread_join`. Prefer [`ThreadFn::join`] when the exit
915 /// status matters.
916 ///
917 /// # Examples
918 ///
919 /// ```
920 /// use osal_rs::os::*;
921 /// use std::sync::Arc;
922 ///
923 /// let mut thread = Thread::new("worker", 1024, 5);
924 /// let spawned = thread.spawn_simple(|| Ok(Arc::new(()))).unwrap();
925 /// spawned.delete();
926 /// ```
927 fn delete(&self) {
928 let _ = unsafe { pthread_join(self.handle, null_mut()) };
929 forget_notify_slot(self.handle);
930 forget_thread(self.handle);
931 }
932
933 /// Suspends the thread by sending it a dedicated real-time signal that
934 /// parks it until [`ThreadFn::resume`] sends the matching wake-up signal
935 /// (pthreads has no native suspend/resume of its own). A no-op if this
936 /// handle [`ThreadFn::is_null`].
937 ///
938 /// # Examples
939 ///
940 /// ```
941 /// use osal_rs::os::*;
942 /// use std::sync::Arc;
943 /// use std::sync::atomic::{AtomicU32, Ordering};
944 ///
945 /// static COUNTER: AtomicU32 = AtomicU32::new(0);
946 ///
947 /// let mut thread = Thread::new("counter", 1024, 1);
948 /// let worker = thread.spawn_simple(|| {
949 /// loop {
950 /// COUNTER.fetch_add(1, Ordering::SeqCst);
951 /// System::delay(1);
952 /// }
953 /// }).unwrap();
954 ///
955 /// System::delay(30);
956 /// worker.suspend();
957 ///
958 /// let paused_at = COUNTER.load(Ordering::SeqCst);
959 /// System::delay(50);
960 /// // No progress while suspended.
961 /// assert_eq!(COUNTER.load(Ordering::SeqCst), paused_at);
962 ///
963 /// worker.resume();
964 /// System::delay(30);
965 /// // Progress resumes.
966 /// assert!(COUNTER.load(Ordering::SeqCst) > paused_at);
967 /// ```
968 fn suspend(&self) {
969 if self.is_null() {
970 return;
971 }
972
973 ensure_suspend_signal_handlers();
974
975 unsafe {
976 pthread_kill(self.handle, suspend_signal());
977 }
978
979 set_thread_state(self.handle, ThreadState::Suspended);
980 }
981
982 /// Resumes a thread previously suspended with [`ThreadFn::suspend`]. See
983 /// [`ThreadFn::suspend`] for a complete example. A no-op if this handle
984 /// [`ThreadFn::is_null`].
985 fn resume(&self) {
986 if self.is_null() {
987 return;
988 }
989
990 ensure_suspend_signal_handlers();
991
992 unsafe {
993 pthread_kill(self.handle, resume_signal());
994 }
995
996 set_thread_state(self.handle, ThreadState::Ready);
997 }
998
999 /// Blocks until the thread finishes, writing its exit value (boxed by
1000 /// [`ThreadFn::spawn`]/`spawn_simple`) to `*ret_val` if non-null. Fails
1001 /// with [`Error::NullPtr`] if this handle [`ThreadFn::is_null`].
1002 ///
1003 /// # Examples
1004 ///
1005 /// ```
1006 /// use osal_rs::os::*;
1007 /// use std::sync::Arc;
1008 ///
1009 /// let mut thread = Thread::new("worker", 1024, 5);
1010 /// let spawned = thread.spawn_simple(|| Ok(Arc::new(()))).unwrap();
1011 /// assert!(spawned.join(core::ptr::null_mut()).is_ok());
1012 /// ```
1013 fn join(&self, ret_val: DoublePtr) -> Result<i32> {
1014 if self.is_null() {
1015 return Err(Error::NullPtr);
1016 }
1017
1018 // When the caller does not want the exit value, collect it into a
1019 // scratch pointer anyway so the `Box` the thread wrapper leaked can be
1020 // reclaimed instead of lost - `freertos::Thread::join` does the same.
1021 let mut discarded: *mut c_void = null_mut();
1022 let out = if ret_val.is_null() { &raw mut discarded } else { ret_val };
1023
1024 let ret = unsafe { pthread_join(self.handle, out) };
1025
1026 if ret != 0 {
1027 Err(Error::ReturnWithCode(ret))
1028 } else {
1029 if ret_val.is_null() && !discarded.is_null() {
1030 // SAFETY: the wrapper returns `Box::into_raw(Box::new(Result<ThreadParam>))`.
1031 drop(unsafe { Box::from_raw(discarded as *mut Result<ThreadParam>) });
1032 }
1033
1034 forget_notify_slot(self.handle);
1035 forget_thread(self.handle);
1036 Ok(0)
1037 }
1038 }
1039
1040 /// Returns a [`ThreadMetadata`] snapshot for this thread. See
1041 /// [`Thread::get_metadata`] (the inherent, static-style helper this
1042 /// delegates to) for a complete example.
1043 fn get_metadata(&self) -> ThreadMetadata {
1044 self.metadata()
1045 }
1046
1047 /// Returns a [`Thread`] handle for the calling thread itself - works
1048 /// whether called from the "main" thread or from inside a callback
1049 /// running on a thread this crate spawned.
1050 ///
1051 /// # Examples
1052 ///
1053 /// ```
1054 /// use osal_rs::os::*;
1055 ///
1056 /// let current = Thread::get_current();
1057 /// assert!(!current.is_null());
1058 /// ```
1059 fn get_current() -> Self
1060 where
1061 Self: Sized,
1062 {
1063 // `pthread_self()` returns whichever thread calls it, so this is
1064 // correct whether `get_current()` runs on the "main" thread or from
1065 // inside a callback running on a thread this crate spawned (see
1066 // `callback_c_wrapper`, which relies on the same call for the same
1067 // reason).
1068 Self {
1069 handle: unsafe { pthread_self() },
1070 name: Bytes::from_str("current"),
1071 stack_depth: 0,
1072 priority: 0,
1073 callback: None,
1074 param: None,
1075 }
1076 }
1077
1078 /// Sets or updates this thread's single-slot notification value (see
1079 /// [`ThreadNotification`] for the available update strategies) and wakes
1080 /// it if it's blocked in [`ThreadFn::wait_notification`].
1081 ///
1082 /// # Examples
1083 ///
1084 /// ```
1085 /// use osal_rs::os::*;
1086 ///
1087 /// let current = Thread::get_current();
1088 /// current.notify(ThreadNotification::SetValueWithOverwrite(5)).unwrap();
1089 ///
1090 /// let value = current.wait_notification(0, 0, 0).unwrap();
1091 /// assert_eq!(value, 5);
1092 /// ```
1093 fn notify(&self, notification: ThreadNotification) -> Result<()> {
1094 if self.is_null() {
1095 return Err(Error::NullPtr);
1096 }
1097
1098 let slot = notify_slot(self.handle);
1099
1100 let result = {
1101 let mut state = slot.state.lock().unwrap();
1102 apply_notification(&mut state, notification)
1103 };
1104
1105 if result.is_ok() {
1106 // Wake a thread blocked in wait_notification() below; a no-op if none is.
1107 slot.cv.notify_all();
1108 }
1109
1110 result
1111 }
1112
1113 /// ISR-safe variant of [`ThreadFn::notify`]; identical on POSIX (there
1114 /// is no real interrupt context, and thus no scheduler decision to
1115 /// report back through `higher_priority_task_woken`, which is always set
1116 /// to `0`).
1117 ///
1118 /// # Examples
1119 ///
1120 /// ```
1121 /// use osal_rs::os::*;
1122 ///
1123 /// let current = Thread::get_current();
1124 /// let mut woken = 0;
1125 /// current.notify_from_isr(ThreadNotification::Increment, &mut woken).unwrap();
1126 /// assert_eq!(woken, 0);
1127 ///
1128 /// let value = current.wait_notification(0, 0, 0).unwrap();
1129 /// assert_eq!(value, 1);
1130 /// ```
1131 fn notify_from_isr(&self, notification: ThreadNotification, higher_priority_task_woken: &mut BaseType) -> Result<()> {
1132 // No real interrupt context on POSIX, and thus no scheduler decision
1133 // to report back — matches `System`'s other `_from_isr` stand-ins.
1134 *higher_priority_task_woken = 0;
1135
1136 self.notify(notification)
1137 }
1138
1139 /// Blocks until a notification is pending or `timeout_ticks` elapses
1140 /// (pass [`TickType::MAX`] to wait forever), returning the notification
1141 /// value. `bits_to_clear_on_entry`/`bits_to_clear_on_exit` clear the
1142 /// matching bits from the value before waiting/before returning,
1143 /// respectively. Fails with [`Error::Timeout`] on timeout.
1144 ///
1145 /// # Examples
1146 ///
1147 /// ```
1148 /// use osal_rs::os::*;
1149 ///
1150 /// let current = Thread::get_current();
1151 ///
1152 /// // Nothing notified yet: times out instead of blocking forever.
1153 /// assert!(current.wait_notification(0, 0, 10).is_err());
1154 ///
1155 /// current.notify(ThreadNotification::SetValueWithOverwrite(9)).unwrap();
1156 /// assert_eq!(current.wait_notification(0, 0, 10).unwrap(), 9);
1157 /// ```
1158 fn wait_notification(&self, bits_to_clear_on_entry: u32, bits_to_clear_on_exit: u32, timeout_ticks: TickType) -> Result<u32> {
1159 if self.is_null() {
1160 return Err(Error::NullPtr);
1161 }
1162
1163 let slot = notify_slot(self.handle);
1164 let mut state = slot.state.lock().unwrap();
1165
1166 state.value &= !bits_to_clear_on_entry;
1167
1168 if !state.pending {
1169 set_thread_state(self.handle, ThreadState::Blocked);
1170
1171 if timeout_ticks == TickType::MAX {
1172 // Not a `while !state.pending` loop: `pending` is flipped by
1173 // `notify()` through a *different* `MutexGuard` (its own
1174 // `slot.state.lock()`) while this thread is parked inside
1175 // `wait()` — invisible to clippy's `while_immutable_condition`,
1176 // which only looks for reassignment in the loop body.
1177 loop {
1178 if state.pending {
1179 break;
1180 }
1181 slot.cv.wait(&state);
1182 }
1183 } else {
1184 let deadline = monotonic_deadline(Duration::from_millis((timeout_ticks as u64).saturating_mul(TICK_PERIOD_MS)));
1185
1186 loop {
1187 if state.pending {
1188 break;
1189 }
1190 if slot.cv.wait_until(&state, deadline) {
1191 break;
1192 }
1193 }
1194 }
1195
1196 set_thread_state(self.handle, ThreadState::Ready);
1197 }
1198
1199 if !state.pending {
1200 return Err(Error::Timeout);
1201 }
1202
1203 state.pending = false;
1204 let value = state.value;
1205 state.value &= !bits_to_clear_on_exit;
1206
1207 Ok(value)
1208 }
1209}
1210
1211impl Deref for Thread {
1212 type Target = ThreadHandle;
1213
1214 fn deref(&self) -> &Self::Target {
1215 &self.handle
1216 }
1217}
1218
1219impl Debug for Thread {
1220 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1221 f.debug_struct("Thread")
1222 .field("handle", &self.handle)
1223 .field("name", &self.name)
1224 .field("stack_depth", &self.stack_depth)
1225 .field("priority", &self.priority)
1226 .field("callback", &self.callback.as_ref().map(|_| "Some(...)"))
1227 .field("param", &self.param)
1228 .finish()
1229 }
1230}
1231
1232impl Display for Thread {
1233 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1234 write!(
1235 f,
1236 "Thread {{ handle: {:?}, name: {}, priority: {}, stack_depth: {} }}",
1237 self.handle,
1238 self.name,
1239 self.priority,
1240 self.stack_depth
1241 )
1242 }
1243}