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