Skip to main content

ThreadpoolTimer

Struct ThreadpoolTimer 

Source
pub struct ThreadpoolTimer { /* private fields */ }
Expand description

An owned one-shot thread-pool timer.

Each arming produces exactly one firing. Arm it with ThreadpoolTimer::set_after or ThreadpoolTimer::set_at, and stop it with ThreadpoolTimer::disarm. Arming again replaces the previous setting rather than adding to it.

For repetition, either re-arm from inside the callback with TimerFiring::rearm_after – which keeps firings strictly sequential – or use ThreadpoolPeriodicTimer when a fixed cadence matters more than avoiding overlap.

§When firings can overlap

Re-arming through TimerFiring never overlaps: the request is applied after the callback returns, so the next firing cannot begin until this one has finished. That is the intended way to repeat.

Arming from outside the callback is a different matter. Calling ThreadpoolTimer::set_after while a callback is running can queue the next firing before the current one returns, and the two then run concurrently on different pool threads. The callback is Fn + Sync, so this is permitted rather than unsound – but it means a callback that assumes it is the only one running must not be driven that way. Re-arm from the callback, or use ThreadpoolTimer::disarm and ThreadpoolTimer::wait before re-arming externally.

Drop disarms before draining callbacks, so the captured closure stays valid for the full lifetime of every callback execution.

§Examples

Fire once:

use std::sync::mpsc;
use std::time::Duration;
use windows_threadpool_sys::timer::ThreadpoolTimer;

let (tx, rx) = mpsc::channel();
let sender = std::sync::Mutex::new(tx);

let timer = ThreadpoolTimer::new(move |_firing| {
    let _ = sender.lock().expect("send").send(());
}, None)?;

timer.set_after(Duration::from_millis(10));
rx.recv().expect("the timer should fire");

Repeat without ever overlapping, by re-arming from inside the callback. The gap is measured from the end of each firing, so a slow callback delays the next one instead of racing it:

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use windows_threadpool_sys::timer::ThreadpoolTimer;

let ticks = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&ticks);

let timer = ThreadpoolTimer::new(move |firing| {
    // Stop after three firings by simply not re-arming.
    if counter.fetch_add(1, Ordering::SeqCst) < 2 {
        firing.rearm_after(Duration::from_millis(1));
    }
}, None)?;

timer.set_after(Duration::from_millis(1));
while ticks.load(Ordering::SeqCst) < 3 {
    std::thread::yield_now();
}
timer.disarm();
timer.wait();
assert_eq!(ticks.load(Ordering::SeqCst), 3);

Implementations§

Source§

impl ThreadpoolTimer

Source

pub fn new<F>( callback: F, env: Option<&mut CallbackEnviron<'_>>, ) -> Result<Self>
where F: Fn(&TimerFiring<'_>) + Send + Sync + 'static,

Create an idle timer that invokes callback each time it expires.

Pass Some(env) to select a private pool or callback priority; None uses the process-default pool with default priority.

The callback runs on a shared, process-managed pool thread. It must restore any thread state it changes and must not terminate its thread. It must not panic: a panic unwinds to the extern "system" trampoline and aborts the process.

§Errors

Returns the error from CreateThreadpoolTimer.

Source

pub fn set_after(&self, delay: Duration)

Fire once, delay from now.

The delay counts only time the system is awake. A zero delay makes the timer due immediately.

Source

pub fn set_at(&self, when: SystemTime)

Fire once at the wall-clock instant when.

Unlike a relative due time, an absolute one passes through sleep and hibernation: if when elapses while the machine is asleep, the timer fires promptly on resume. An instant already in the past fires immediately.

Source

pub fn set_after_with_window(&self, delay: Duration, window: Duration)

Fire once after delay, allowing the system a coalescing window.

window is the tolerance the system may add to the due time so it can group this timer with other expirations and wake the processor less often. A larger window trades timing precision for power.

Source

pub fn disarm(&self)

Stop the timer.

New callbacks stop being queued, but a callback already queued still runs; use ThreadpoolTimer::cancel_pending to drop those as well. Disarming an idle timer is a no-op.

Source

pub fn is_set(&self) -> bool

Whether the timer currently has a due time.

This reports whether the timer has been armed and not since disarmed. It is not a prediction that the timer will fire again: expiring does not clear the due time, so a fired timer still reports true. Only ThreadpoolTimer::disarm makes it false.

Source

pub fn wait(&self)

Let every queued callback run, and block until none is executing.

This does not leave a self-re-arming timer idle. A callback’s TimerFiring::rearm_after is applied after the callback returns, so a firing that runs during this call installs a fresh due time and the timer is armed again when it returns. Use stop_and_drain to reach quiescence.

Source

pub fn cancel_pending(&self)

Drop callbacks that have not started, then wait for any executing one.

Like wait, this does not by itself leave a self-re-arming timer idle: it does not suppress the deferred re-arm of a callback that is already running. Use stop_and_drain when the timer must actually be quiescent afterwards.

Source

pub fn stop_and_drain(&self)

Stop the timer and block until it is idle, leaving it reusable.

This exists because neither disarm nor cancel_pending can stop a self-re-arming timer on its own: a callback already running requests its re-arm through TimerFiring::rearm_after, and the trampoline applies it after the callback returns – which is after any disarm from outside. This suppresses that deferred re-arm for the duration of the call, using the same mechanism Drop uses, and lifts the suppression before returning so the timer can be armed again afterwards.

§What this guarantees

On return, provided no other thread arms the timer during the call:

  • no callback is queued or executing, and
  • the timer has no due time – a re-arm requested by a callback that ran during the call is discarded rather than deferred.
§What it does not

A concurrent arm from another thread is not excluded. ThreadpoolTimer is Sync and set_after, set_at and set_after_with_window all take &self, so they do not pass through the suppression this uses. Nothing in this crate orders such a call against this one.

In practice the drain currently cancels a due time installed that way – WaitForThreadpoolTimerCallbacks with cancellation clears one even when no callback is queued, measurably so. That is not a documented contract and is not relied upon here: if a caller needs the timer to be provably idle, it must ensure nothing else arms it for the duration, by owning it exclusively or serializing access to it.

Calling this from inside the timer’s own callback would deadlock, because it waits for that callback to finish.

Trait Implementations§

Source§

impl Debug for ThreadpoolTimer

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for ThreadpoolTimer

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl Send for ThreadpoolTimer

Source§

impl Sync for ThreadpoolTimer

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.