osal_rs/posix/timer.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//! Software timer support for POSIX.
22//!
23//! pthreads has no notion of a shared "timer daemon task" the way FreeRTOS
24//! does, so each [`Timer`] gets its own dedicated background thread plus a
25//! real kernel timer (`timer_create(2)`) that notifies that thread directly:
26//!
27//! 1. `SIGALRM` is blocked in the calling thread (`sigprocmask`) before the
28//! background thread is spawned, so the mask — and with it, the block —
29//! is inherited by the new thread too. A blocked signal isn't discarded;
30//! it becomes *pending* until something explicitly consumes it.
31//! 2. The background thread publishes its kernel thread ID (`gettid(2)`,
32//! distinct from its `pthread_t`) and then loops on `sigwait(3)`, which
33//! synchronously consumes one pending, blocked `SIGALRM` at a time and
34//! invokes the user callback in response.
35//! 3. `timer_create` is configured with `SIGEV_THREAD_ID` notification,
36//! targeting that kernel thread ID directly — so this timer's expirations
37//! can only ever wake up this timer's own background thread, never any
38//! other timer's or unrelated code's.
39//!
40//! This mirrors a common pattern for per-thread POSIX timers (create a
41//! dedicated waiter thread, mask + `sigwait` instead of an async-signal
42//! handler, `SIGEV_THREAD_ID` to target it precisely).
43//!
44//! # Caveats inherited from this design
45//!
46//! - Blocking `SIGALRM` in the calling thread is permanent for that thread:
47//! this crate never unblocks it afterwards, so a thread that creates a
48//! `Timer` can no longer receive `SIGALRM` itself.
49//! - A one-shot timer's background thread exits after its single callback
50//! invocation. Calling `start()`/`reset()` again on an already-fired
51//! one-shot timer re-arms the kernel timer, but nothing is left running to
52//! consume its `SIGALRM` — create a new `Timer` instead of reusing one.
53//!
54//! # Examples
55//!
56//! ```
57//! use osal_rs::os::*;
58//! use std::sync::Arc;
59//! use std::sync::atomic::{AtomicBool, Ordering};
60//! use core::time::Duration;
61//!
62//! static FIRED: AtomicBool = AtomicBool::new(false);
63//!
64//! let timer = Timer::new_with_to_tick(
65//! "heartbeat",
66//! Duration::from_millis(10),
67//! false, // one-shot
68//! None,
69//! |_timer, _param| {
70//! FIRED.store(true, Ordering::SeqCst);
71//! Ok(Arc::new(()))
72//! }
73//! ).unwrap();
74//!
75//! timer.start(0);
76//! System::delay(50);
77//! assert!(FIRED.load(Ordering::SeqCst));
78//! ```
79
80use core::ffi::{c_int, c_long, c_void};
81use core::fmt::{Debug, Display};
82use core::ops::Deref;
83use core::ptr::null_mut;
84
85use std::sync::Mutex;
86use std::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, AtomicU32, Ordering};
87
88use alloc::boxed::Box;
89use alloc::sync::{Arc, Weak};
90
91use crate::os::ThreadFn;
92use crate::posix::config::TICK_PERIOD_MS;
93use crate::posix::ffi::{
94 CLOCK_MONOTONIC, SIGALRM, SIGEV_THREAD_ID, SIG_BLOCK, gettid, itimerspec, pthread_kill, sched_yield, sigaddset, sigemptyset, sigevent, sigevent_un, sigprocmask, sigset_t, sigwait,
95 timer_create, timer_delete, timer_settime, timer_t, timespec,
96};
97use crate::posix::thread::Thread;
98use crate::posix::types::{StackType, TickType, TimerHandle, UBaseType};
99use crate::traits::{MAX_TASK_NAME_LEN, TimerFn, TimerFnPtr, TimerParam, ToTick};
100use crate::utils::{Bytes, Error, OsalRsBool, Result};
101
102/// Name (glibc `pthread_setname_np`, `<= 15` chars) given to every timer's
103/// background thread. Fixed rather than derived from the timer's own name so
104/// it's always valid regardless of what the caller passed to `Timer::new`.
105const TIMER_THREAD_NAME: &str = "os_timer";
106
107/// Stack size requested for a timer's background thread. `Thread::spawn`
108/// enforces its own safe minimum regardless, so this only matters as a
109/// lower bound.
110const TIMER_THREAD_STACK: StackType = 1024;
111
112/// Priority given to a timer's background thread. Only meaningful with the
113/// `sched_fifo` feature enabled; otherwise the thread inherits the creating
114/// thread's scheduling policy/priority.
115const TIMER_THREAD_PRIORITY: UBaseType = 1;
116
117const NSECS_PER_SEC: u64 = 1_000_000_000;
118
119/// State shared, via `Arc`, between every clone of a given [`Timer`] and its
120/// background thread.
121///
122/// [`Timer`] itself is freely `Clone` (matching every other handle type in
123/// this crate), but the POSIX timer, its background thread, and its
124/// mutable period all belong to one underlying resource — this is that
125/// resource.
126struct TimerShared {
127 /// The real POSIX timer, once `ready` is set. Not read before then:
128 /// on modern glibc/Linux, `timer_t` is a small kernel-assigned integer
129 /// cast to a pointer, so the *first* timer a process ever creates gets
130 /// the all-zero handle — a bit pattern that would otherwise look
131 /// indistinguishable from "not created yet".
132 timerid: AtomicPtr<c_void>,
133 /// Set once `timerid` holds a real value from a successful
134 /// `timer_create`. Guards every read of `timerid` instead of checking
135 /// it for null (see `timerid`'s docs for why null isn't a safe sentinel
136 /// here), and lets `delete()` claim deletion exactly once.
137 ready: AtomicBool,
138 /// Kernel TID (`gettid()`) of the background thread, published once it
139 /// starts running; 0 until then.
140 thread_id: AtomicI32,
141 /// Current period, in microseconds.
142 us: AtomicU32,
143 oneshot: AtomicBool,
144 /// Set by teardown to tell the background thread's `sigwait` loop to
145 /// stop instead of invoking the callback again.
146 exit: AtomicBool,
147 /// The background thread, so teardown can wake and join it.
148 thread: Mutex<Option<Thread>>,
149}
150
151impl TimerShared {
152 /// Destroys the kernel timer and reaps the background thread, at most
153 /// once however many handles ask for it. Shared by [`TimerFn::delete`]
154 /// and by `Drop`.
155 fn destroy(&self) {
156 // `swap` (not `load` + `store`) so concurrent teardowns from clones
157 // of the same `Timer` can't both attempt to delete the underlying
158 // resources.
159 if self.ready.swap(false, Ordering::AcqRel) {
160 let timerid = self.timerid.load(Ordering::Acquire);
161 unsafe {
162 timer_delete(timerid);
163 }
164 }
165
166 self.exit.store(true, Ordering::Release);
167
168 let Ok(mut guard) = self.thread.lock() else {
169 return;
170 };
171
172 let Some(bg_thread) = guard.take() else {
173 return;
174 };
175
176 // Wake the background thread out of `sigwait` so it observes `exit`
177 // and returns; harmless if it's already on its way out because the
178 // timer just fired for the last time.
179 unsafe {
180 pthread_kill(*bg_thread, SIGALRM);
181 }
182
183 if unsafe { gettid() } == self.thread_id.load(Ordering::Acquire) {
184 // Teardown is running *on* the background thread, which happens
185 // when a callback drops the last `Timer` handle. Joining would
186 // be joining ourselves, so detach instead and let the thread
187 // release itself once it falls out of the loop.
188 bg_thread.detach();
189 return;
190 }
191
192 bg_thread.delete();
193 }
194}
195
196/// Destroys the underlying timer once the last [`Timer`] handle referring to
197/// it is gone - the RAII half of [`TimerFn::delete`], and the reason `Timer`
198/// itself has no `Drop` of its own (a per-handle `Drop` would tear the timer
199/// down as soon as *any* clone was dropped).
200///
201/// For this to be reachable at all, the background thread must hold only a
202/// [`Weak`] reference and must not hold even that across `sigwait` - see
203/// [`run_timer_thread`].
204impl Drop for TimerShared {
205 fn drop(&mut self) {
206 self.destroy();
207 }
208}
209
210/// A software timer backed by a POSIX `timer_create`/`SIGALRM` timer and a
211/// dedicated background thread that waits for the signal and invokes the
212/// user callback. Freely [`Clone`]-able - every clone shares the same
213/// underlying timer. See [`Timer::new`] for a complete, testable example.
214#[derive(Clone)]
215pub struct Timer {
216 /// Raw handle to the underlying `timer_t`, exposed for diagnostics
217 /// (`Debug`/`Display`). `null` until [`Timer::new`] successfully calls
218 /// `timer_create`.
219 pub handle: TimerHandle,
220 /// In the same fixed-size buffer every other named object in this crate
221 /// uses. `Bytes` is `Copy`, so handing a named handle to the callback on
222 /// every firing costs nothing.
223 name: Bytes<MAX_TASK_NAME_LEN>,
224 callback: Option<Arc<TimerFnPtr>>,
225 param: Option<TimerParam>,
226 shared: Option<Arc<TimerShared>>,
227}
228
229unsafe impl Send for Timer {}
230unsafe impl Sync for Timer {}
231
232/// Converts a tick count (this crate's ticks are milliseconds, see
233/// [`TICK_PERIOD_MS`]) to microseconds, saturating instead of overflowing
234/// `u32` (the width `timer_settime`'s nanosecond math is done in below).
235fn ticks_to_us(ticks: TickType) -> u32 {
236 (ticks as u64).saturating_mul(TICK_PERIOD_MS).saturating_mul(1000).min(u32::MAX as u64) as u32
237}
238
239/// Arms `shared`'s timer to fire `us` microseconds from now, or disarms it
240/// if `us == 0` (per `timer_settime(2)`, an all-zero `it_value` always
241/// disarms regardless of `it_interval`). No-op (returns `False`) if the
242/// timer hasn't been created yet.
243fn arm(shared: &TimerShared, us: u32) -> OsalRsBool {
244 if !shared.ready.load(Ordering::Acquire) {
245 return OsalRsBool::False;
246 }
247
248 let timerid = shared.timerid.load(Ordering::Acquire);
249
250 let nanoseconds = (us as u64) * 1000;
251 let it_value = timespec {
252 tv_sec: (nanoseconds / NSECS_PER_SEC) as c_long,
253 tv_nsec: (nanoseconds % NSECS_PER_SEC) as c_long,
254 };
255
256 let it_interval = if us == 0 || shared.oneshot.load(Ordering::Acquire) {
257 timespec::default()
258 } else {
259 it_value
260 };
261
262 let its = itimerspec { it_interval, it_value };
263
264 match unsafe { timer_settime(timerid, 0, &its, null_mut()) } {
265 0 => OsalRsBool::True,
266 _ => OsalRsBool::False,
267 }
268}
269
270/// Body of the background thread every [`Timer`] spawns: publishes its
271/// kernel TID, then loops accepting one blocked `SIGALRM` at a time via
272/// `sigwait` and invoking the user callback in response. See the module
273/// docs for the full rationale.
274///
275/// Holds a [`Weak`] rather than an [`Arc`], and does not hold even that
276/// across `sigwait`: a strong reference parked here for the lifetime of the
277/// thread would keep [`TimerShared`] alive for as long as the thread runs,
278/// and the thread only stops when [`TimerShared`] is dropped - a cycle in
279/// which neither ever goes away.
280fn run_timer_thread(weak: Weak<TimerShared>, name: Bytes<MAX_TASK_NAME_LEN>, callback: Option<Arc<TimerFnPtr>>, mut param: Option<TimerParam>) -> Result<TimerParam> {
281 if let Some(shared) = weak.upgrade() {
282 shared.thread_id.store(unsafe { gettid() }, Ordering::Release);
283 }
284
285 let mut sigset = sigset_t::default();
286 unsafe {
287 sigemptyset(&mut sigset);
288 sigaddset(&mut sigset, SIGALRM);
289 }
290
291 loop {
292 let mut sig: c_int = 0;
293
294 if unsafe { sigwait(&sigset, &mut sig) } != 0 || sig != SIGALRM {
295 continue;
296 }
297
298 // The signal that just woke us might be the real timer expiration,
299 // or the artificial one teardown sends to break out of `sigwait`.
300 let Some(shared) = weak.upgrade() else {
301 break;
302 };
303
304 if shared.exit.load(Ordering::Acquire) {
305 break;
306 }
307
308 if let Some(cb) = &callback {
309 // The callback is handed a fresh handle onto the same shared
310 // state - never anything that owns the timer, since
311 // `TimerFnPtr` takes its `Box<dyn TimerFn>` by value and drops
312 // it on return.
313 let timer_self = Timer {
314 handle: shared.timerid.load(Ordering::Acquire),
315 name,
316 callback: callback.clone(),
317 param: param.clone(),
318 shared: Some(shared.clone()),
319 };
320
321 if let Ok(new_param) = cb(Box::new(timer_self), param.clone()) {
322 param = Some(new_param);
323 }
324 }
325
326 if shared.oneshot.load(Ordering::Acquire) {
327 break;
328 }
329 }
330
331 let final_param: TimerParam = match param {
332 Some(p) => p,
333 None => Arc::new(()),
334 };
335
336 Ok(final_param)
337}
338
339impl Timer {
340 /// Same as [`Timer::new`], but accepts any [`ToTick`] period (e.g. a
341 /// [`core::time::Duration`]) instead of a raw tick count. See the
342 /// module-level docs above for a complete example.
343 #[inline]
344 pub fn new_with_to_tick<F>(name: &str, timer_period_in_ticks: impl ToTick, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
345 where
346 F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static,
347 {
348 Self::new(name, timer_period_in_ticks.to_ticks(), auto_reload, param, callback)
349 }
350
351 /// Same as [`TimerFn::start`], but accepts any [`ToTick`] value (e.g. a
352 /// [`core::time::Duration`]) instead of a raw tick count.
353 ///
354 /// # Examples
355 ///
356 /// ```
357 /// use osal_rs::os::*;
358 /// use std::sync::Arc;
359 /// use core::time::Duration;
360 ///
361 /// let timer = Timer::new_with_to_tick("t", Duration::from_millis(50), false, None, |_t, _p| Ok(Arc::new(()))).unwrap();
362 /// assert_eq!(timer.start_with_to_tick(Duration::from_millis(10)), osal_rs::utils::OsalRsBool::True);
363 /// ```
364 #[inline]
365 pub fn start_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
366 self.start(ticks_to_wait.to_ticks())
367 }
368
369 /// Same as [`TimerFn::stop`], but accepts any [`ToTick`] value instead
370 /// of a raw tick count.
371 #[inline]
372 pub fn stop_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
373 self.stop(ticks_to_wait.to_ticks())
374 }
375
376 /// Same as [`TimerFn::reset`], but accepts any [`ToTick`] value instead
377 /// of a raw tick count.
378 #[inline]
379 pub fn reset_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
380 self.reset(ticks_to_wait.to_ticks())
381 }
382
383 /// Same as [`TimerFn::change_period`], but accepts any [`ToTick`] values
384 /// instead of raw tick counts.
385 #[inline]
386 pub fn change_period_with_to_tick(&self, new_period_in_ticks: impl ToTick, new_period_ticks: impl ToTick) -> OsalRsBool {
387 self.change_period(new_period_in_ticks.to_ticks(), new_period_ticks.to_ticks())
388 }
389
390 /// Same as [`TimerFn::delete`], but accepts any [`ToTick`] value instead
391 /// of a raw tick count.
392 #[inline]
393 pub fn delete_with_to_tick(&mut self, ticks_to_wait: impl ToTick) -> OsalRsBool {
394 self.delete(ticks_to_wait.to_ticks())
395 }
396
397 /// Creates a new timer named `name`, firing `callback` every
398 /// `timer_period_in_ticks` ticks if `auto_reload` (one-shot otherwise).
399 /// `param` is handed to the first callback invocation; each invocation
400 /// can return an updated value for the next one. The timer is created
401 /// stopped - call [`TimerFn::start`] to arm it.
402 ///
403 /// # Examples
404 ///
405 /// ```
406 /// use osal_rs::os::*;
407 /// use std::sync::Arc;
408 ///
409 /// let timer = Timer::new("t", 50, false, None, |_timer, _param| Ok(Arc::new(()))).unwrap();
410 /// assert!(!timer.is_null());
411 /// ```
412 pub fn new<F>(name: &str, timer_period_in_ticks: TickType, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
413 where
414 F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static,
415 {
416 let shared = Arc::new(TimerShared {
417 timerid: AtomicPtr::new(null_mut()),
418 ready: AtomicBool::new(false),
419 thread_id: AtomicI32::new(0),
420 us: AtomicU32::new(ticks_to_us(timer_period_in_ticks)),
421 oneshot: AtomicBool::new(!auto_reload),
422 exit: AtomicBool::new(false),
423 thread: Mutex::new(None),
424 });
425
426 let name = Bytes::<MAX_TASK_NAME_LEN>::from_str(name);
427
428 let mut timer = Self {
429 handle: null_mut(),
430 name,
431 callback: Some(Arc::new(callback)),
432 param,
433 shared: Some(shared.clone()),
434 };
435
436 // Block SIGALRM here so the background thread we're about to spawn
437 // inherits it blocked too (see module docs).
438 let mut sigset = sigset_t::default();
439 unsafe {
440 sigemptyset(&mut sigset);
441 sigaddset(&mut sigset, SIGALRM);
442 sigprocmask(SIG_BLOCK, &sigset, null_mut());
443 }
444
445 let bg_shared = Arc::downgrade(&shared);
446 let bg_name = name;
447 let bg_callback = timer.callback.clone();
448 let bg_param = timer.param.clone();
449
450 let mut bg_thread = Thread::new(TIMER_THREAD_NAME, TIMER_THREAD_STACK, TIMER_THREAD_PRIORITY);
451 let bg_thread = bg_thread.spawn_simple(move || run_timer_thread(bg_shared.clone(), bg_name, bg_callback.clone(), bg_param.clone()))?;
452
453 // Handed over before anything below can fail, so that an early
454 // return reaps the thread through `TimerShared::drop` rather than
455 // stranding it in `sigwait`.
456 *shared.thread.lock().unwrap() = Some(bg_thread);
457
458 // Wait until the background thread has published its kernel TID,
459 // needed below to target it with SIGEV_THREAD_ID.
460 while shared.thread_id.load(Ordering::Acquire) == 0 {
461 unsafe {
462 sched_yield();
463 }
464 }
465
466 let sev = sigevent {
467 sigev_notify: SIGEV_THREAD_ID,
468 sigev_signo: SIGALRM,
469 sigev_un: sigevent_un {
470 tid: shared.thread_id.load(Ordering::Acquire),
471 },
472 ..Default::default()
473 };
474
475 let mut timerid: timer_t = null_mut();
476 let ret = unsafe { timer_create(CLOCK_MONOTONIC, &sev, &mut timerid) };
477
478 if ret != 0 {
479 // Dropping `timer` and `shared` on the way out runs
480 // `TimerShared::drop`, which wakes and reaps the thread spawned
481 // above. `ready` is still false, so it won't try to delete a
482 // kernel timer that was never created.
483 return Err(Error::ReturnWithCode(ret));
484 }
485
486 shared.timerid.store(timerid, Ordering::Release);
487 shared.ready.store(true, Ordering::Release);
488
489 timer.handle = timerid;
490 Ok(timer)
491 }
492}
493
494impl TimerFn for Timer {
495 /// Returns `true` if this timer has been [`TimerFn::delete`]d (or the
496 /// underlying kernel timer failed to create).
497 ///
498 /// # Examples
499 ///
500 /// ```
501 /// use osal_rs::os::*;
502 /// use std::sync::Arc;
503 ///
504 /// let mut timer = Timer::new("t", 50, false, None, |_t, _p| Ok(Arc::new(()))).unwrap();
505 /// assert!(!timer.is_null());
506 ///
507 /// timer.delete(0);
508 /// assert!(timer.is_null());
509 /// ```
510 fn is_null(&self) -> bool {
511 match &self.shared {
512 Some(shared) => !shared.ready.load(Ordering::Acquire),
513 None => true,
514 }
515 }
516
517 /// Arms the timer to fire after its configured period. See the
518 /// module-level docs above for a complete example.
519 fn start(&self, _ticks_to_wait: TickType) -> OsalRsBool {
520 let Some(shared) = &self.shared else {
521 return OsalRsBool::False;
522 };
523
524 arm(shared, shared.us.load(Ordering::Acquire))
525 }
526
527 /// Disarms the timer; a no-op if it isn't currently running.
528 ///
529 /// # Examples
530 ///
531 /// ```
532 /// use osal_rs::os::*;
533 /// use std::sync::Arc;
534 /// use std::sync::atomic::{AtomicBool, Ordering};
535 ///
536 /// static FIRED: AtomicBool = AtomicBool::new(false);
537 ///
538 /// let timer = Timer::new("t", 20, false, None, |_t, _p| {
539 /// FIRED.store(true, Ordering::SeqCst);
540 /// Ok(Arc::new(()))
541 /// }).unwrap();
542 ///
543 /// timer.start(0);
544 /// timer.stop(0); // cancels before it can fire
545 ///
546 /// System::delay(50);
547 /// assert!(!FIRED.load(Ordering::SeqCst));
548 /// ```
549 fn stop(&self, _ticks_to_wait: TickType) -> OsalRsBool {
550 let Some(shared) = &self.shared else {
551 return OsalRsBool::False;
552 };
553
554 arm(shared, 0)
555 }
556
557 /// Restarts the countdown from now, using the current period -
558 /// equivalent to calling [`TimerFn::start`] again, whether the timer was
559 /// previously running or stopped.
560 fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool {
561 // A relative `timer_settime` call always restarts the countdown
562 // from now, whether the timer was previously running or stopped.
563 self.start(ticks_to_wait)
564 }
565
566 /// Updates the timer's period and immediately (re)arms it with the new
567 /// value.
568 ///
569 /// # Examples
570 ///
571 /// ```
572 /// use osal_rs::os::*;
573 /// use std::sync::Arc;
574 /// use std::sync::atomic::{AtomicBool, Ordering};
575 ///
576 /// static FIRED: AtomicBool = AtomicBool::new(false);
577 ///
578 /// let timer = Timer::new("t", 10_000, false, None, |_t, _p| {
579 /// FIRED.store(true, Ordering::SeqCst);
580 /// Ok(Arc::new(()))
581 /// }).unwrap();
582 ///
583 /// // Original 10s period would never fire in time; shrink it to 10ms.
584 /// timer.change_period(10, 0);
585 ///
586 /// System::delay(50);
587 /// assert!(FIRED.load(Ordering::SeqCst));
588 /// ```
589 fn change_period(&self, new_period_in_ticks: TickType, ticks_to_wait: TickType) -> OsalRsBool {
590 let Some(shared) = &self.shared else {
591 return OsalRsBool::False;
592 };
593
594 shared.us.store(ticks_to_us(new_period_in_ticks), Ordering::Release);
595 self.start(ticks_to_wait)
596 }
597
598 /// Destroys the underlying kernel timer and its background thread,
599 /// resetting this [`Timer`] to its "null" state. See
600 /// [`TimerFn::is_null`] for a complete example.
601 fn delete(&mut self, _ticks_to_wait: TickType) -> OsalRsBool {
602 // Giving up the shared state is what makes this handle null; the
603 // timer itself is destroyed by whichever handle gets there first,
604 // and every clone sees it through the `ready` flag.
605 let Some(shared) = self.shared.take() else {
606 return OsalRsBool::False;
607 };
608
609 shared.destroy();
610
611 self.handle = null_mut();
612 OsalRsBool::True
613 }
614}
615
616impl Deref for Timer {
617 type Target = TimerHandle;
618
619 fn deref(&self) -> &Self::Target {
620 &self.handle
621 }
622}
623
624impl Debug for Timer {
625 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
626 f.debug_struct("Timer")
627 .field("handle", &self.handle)
628 .field("name", &self.name)
629 .field("has_callback", &self.callback.is_some())
630 .field("has_param", &self.param.is_some())
631 .field("is_null", &self.is_null())
632 .finish()
633 }
634}
635
636impl Display for Timer {
637 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
638 write!(f, "Timer {{ name: {}, handle: {:?} }}", self.name, self.handle)
639 }
640}