Skip to main content

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}