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}