Crate ztimer

Source
Expand description

ztimer,where z stands for zero-overhead, provides an implementation of a near O(1) timer. It operates under the assumption that, in normal application usage, the number of timer expirations across different scenarios is typically limited. Therefore, there’s no need to support a huge number of different expires.

§Concept

The main concepts introduced in ztimer are AutoDropTimer, Timer, and Clock.

  • An instance of Timer held in the application is an indirect reference to the real timer instance stored in the Clock. It holds a callback provided by the application, which will be invoked after a specified duration. A Timer instance can be canceled before it expires.

  • If a Timer instance is not canceled by the application but instead is dropped before expiring, nothing will happen. The Clock will still call the callback when the underlying timer instance expires. The application needs to handle this case correctly.

  • AutoDropTimer is a wrapper of Timer with an additional benefit: when the AutoDropTimer instance is dropped, it automatically cancels the underlying timer if it is not expired, preventing further callback invocations.

  • The Clock class is used to group timers. An instance of Clock can hold multiple or even all the timers within one process. The number of clocks in one process follows these restrictions:

    1. At least one clock instance is required.
    2. Up to CPUs multiplied by 4 for collocated clocks.
    3. For non-collocated clocks, application can create as many as it wants, the library will return new or reused clock instances accordingly, it is transparent to the application.

Application shall create at least one Clock instance before using the timer. A suggestion is to allocate several clocks based on functional groups.

§Example of use AutoDropTimer:

use std::thread::sleep;
use std::time::Duration;
use ztimer::{AutoDropTimer, Clock} ;// Only one of AutoDropTimer or Timer is needed in general
let clock = Clock::new(None).unwrap(); // One process can have one or a few number of clock instances
let t1 = AutoDropTimer::new(clock, Duration::from_secs(1), ||{println!("T1 expired");}, "t1".to_string()).unwrap();
let t2 = AutoDropTimer::new(clock, Duration::from_secs(10), ||{println!("T2 expired");}, "t2".to_string()).unwrap();
let mut t3 = AutoDropTimer::new(clock, Duration::from_secs(10), ||{println!("T3 expired");}, "t2".to_string()).unwrap();
assert_eq!(clock.len(), 3);
sleep(Duration::from_secs(2));// T1 expired
assert_eq!(clock.len(), 2);
{
    let _t4 = t2;
}
// The timer t2 is moved to _t4, then dropped automatically without invoking timeout function.
assert_eq!(clock.len(), 1);
t3.cancel().unwrap();
assert_eq!(clock.len(), 0);

If an application needs to wait for the underlying timer thread before terminate the process, here is what the application shall execute #Example of waiting for underlying timer thread

use ztimer::Clock;
let _ = Clock::new(None);
//Before terminating:
let tjh = Clock::take_thread_handle();
Clock::terminate_for_process_exit();
let _ = tjh.unwrap().join();

Re-exports§

pub use error::MyError;

Modules§

error

Structs§

AutoDropTimer
Clock
Tick
Timer

Type Aliases§

NonBlockTickBridge
NonBlockTickBridge: a call back to pass the Tick to the instance of clock. Application must provide one when colocated_timer is enabled At the end, normally it’s the receiving portion of application thread, application needs to call Clock::on_tick() to transfer the Tick event. As indicated by the name, the function for TickBridge CANNOT have any blocking operations.