pub struct Timer { /* private fields */ }
Expand description
A Timer
tracks the current time, and notifies Sleep
and Timeout
futures when they complete.
This timer implementation uses a hierarchical timer wheel to track
large numbers of Sleep
futures efficiently.
Creating Futures
A Timer
instance is necessary to create Sleep
and Timeout
futures.
Once a Sleep
or Timeout
future is created by a Timer
, they are
bound to that Timer
instance, and will be woken by the Timer
once it
advances past the deadline for that future.
The Timer::sleep
and Timer::timeout
methods create Sleep
and
Timeout
futures, respectively. In addition, fallible
Timer::try_sleep
and Timer::try_timeout
methods are available, which
do not panic on invalid durations. These methods may be used in systems
where panicking must be avoided.
Setting a Global Timer
In addition to creating Sleep
and Timeout
futures using methods on a
Timer
instance, a timer may also be set as a [global default timer]. This
allows the use of the free functions sleep
, timeout
,
try_sleep
, and try_timeout
, which do not require a reference to a
Timer
to be passed in. See the documentation on global timers
for details.
Driving Timers
⚠️ A timer wheel at rest will remain at rest unless acted upon by an outside force!
Since maitake
is intended for bare-metal platforms without an operating
system, a Timer
instance cannot automatically advance time. Instead, the
operating system’s run loop must periodically call the Timer::turn
method, which advances the timer wheel to the current time and wakes any
futures whose timers have completed.
The Timer::turn
method may be called on a periodic interrupt, or on
every iteration of a system run loop. If the system is also using the
maitake::scheduler
module, calling Timer::turn
after a call to Scheduler::tick
is generally appropriate.
Clocks
In addition to driving the timer, user code must also provide a Clock
when constructing a Timer
. The Clock
is a representation of a
hardware time source used to determine the current timestamp. See the
Clock
type’s documentation for details on implementing clocks.
Hardware Time Sources
Depending on the hardware platform, a time source may be a timer interrupt that fires on a known interval1, or a timestamp that’s read by reading from a special register2, a memory-mapped IO location, or by executing a special instruction3. A combination of multiple time sources can also be used.
Periodic and One-Shot Timer Interrupts
Generally, hardware timer interrupts operate in one of two modes: periodic timers, which fire on a regular interval, and one-shot timers, where the timer counts down to a particular time, fires the interrupt, and then stops until it is reset by software. Depending on the particular hardware platform, one or both of these timer modes may be available.
Using a periodic timer with the maitake
timer wheel is quite simple:
construct the timer wheel with the minimum granularity
set to the period of the timer interrupt, and call Timer::try_turn
every
time the periodic interrupt occurs.
However, if the hardware platform provides a way to put the processor in a
low-power state while waiting for an interrupt, it may be desirable to
instead use a one-shot timer mode. When a timer wheel is advanced, it
returns a Turn
structure describing what happened while advancing the
wheel. Among other things, this includes the duration until the next
scheduled timer expires. If the timer is
advanced when the system has no other work to perform, and no new work was
scheduled as a result of advancing the timer wheel to the current time, the
system can then instruct the one-shot timer to fire in
Turn::time_to_next_deadline
, and put the processor in a low-power state
to wait for that interrupt. This allows the system to idle more efficiently
than if it was woken repeatedly by a periodic timer interrupt.
Timestamp-Driven Timers
When the timer is advanced by reading from a time source, the
Timer::turn
method should be called periodically as part of a runtime
loop (as discussed in he previous section), such as every time the
scheduler is ticked. Advancing the timer more frequently
will result in Sleep
futures firing with a higher resolution, while less
frequent calls to Timer::turn
will result in more noise in when Sleep
futures actually complete.
Timer Granularity
Within the timer wheel, the duration of a Sleep
future is represented as
a number of abstract “timer ticks”. The actual duration in real life time
that’s represented by a number of ticks depends on the timer’s _granularity.
When constructing Timer
(e.g. by calling Timer::new
), the minimum
granularity of the timer is determined by the Clock
’s tick_duration
value. The selected tick duration influences both the resolution of the
timer (i.e. the minimum difference in duration between two Sleep
futures
that can be distinguished by the timer), and the maximum duration that can
be represented by a Sleep
future (which is limited by the size of a 64-bit
integer).
A longer tick duration will allow represented longer sleeps, as the maximum
allowable sleep is the timer’s granularity multiplied by u64::MAX
. A
shorter tick duration will allow for more precise sleeps at the expense of
reducing the maximum allowed sleep.
Such as the 8253 PIT interrupt on most x86 systems. ↩
Such as the
CCNT
register on ARMv7. ↩Such as the
rdtsc
instruction on x86_64. ↩
Implementations§
source§impl Timer
impl Timer
sourcepub fn timeout<F: Future>(
&self,
duration: Duration,
future: F
) -> Timeout<'_, F> ⓘ
pub fn timeout<F: Future>( &self, duration: Duration, future: F ) -> Timeout<'_, F> ⓘ
Returns a new Timeout
future that fails if future
does not
complete within the specified duration
.
The timeout will be driven by this timer.
Output
Ok
(F::Output)
if the inner future completed before the specified timeout.Err
(
Elapsed
)
if the timeout elapsed before the innerFuture
completed.
Cancellation
Dropping a Timeout
future cancels the timeout. The wrapped Future
can
be extracted from the Timeout
future by calling Timeout::into_inner
,
allowing the future to be polled without failing if the timeout elapses.
Panics
This method panics if the provided duration exceeds the maximum sleep duration allowed this timer.
For a version of this method that does not panic, use the
Timer::try_timeout
method instead.
sourcepub fn try_timeout<F: Future>(
&self,
duration: Duration,
future: F
) -> Result<Timeout<'_, F>, TimerError>
pub fn try_timeout<F: Future>( &self, duration: Duration, future: F ) -> Result<Timeout<'_, F>, TimerError>
Returns a new Timeout
future that fails if future
does not
complete within the specified duration
.
The timeout will be driven by this timer.
Returns
Ok
(
Timeout
)
if a newTimeout
future was created successfully.Err
(
TimerError::DurationTooLong
)
if the requested timeout duration exceeds this timer’s maximum sleep duration.
Output
Ok
(F::Output)
if the inner future completed before the specified timeout.Err
(
Elapsed
)
if the timeout elapsed before the innerFuture
completed.
Cancellation
Dropping a Timeout
future cancels the timeout. The wrapped Future
can
be extracted from the Timeout
future by calling Timeout::into_inner
,
allowing the future to be polled without failing if the timeout elapses.
Panics
This method does not panic. For a version of this methodthat panics
rather than returning a TimerError
, use Timer::timeout
.
source§impl Timer
impl Timer
sourcepub fn max_duration(&self) -> Duration
pub fn max_duration(&self) -> Duration
Returns the maximum duration of Sleep
futures driven by this timer.
sourcepub fn sleep(&self, duration: Duration) -> Sleep<'_> ⓘ
pub fn sleep(&self, duration: Duration) -> Sleep<'_> ⓘ
Returns a Future
that will complete in duration
.
Returns
The returned Sleep
future will be driven by this timer, and will
complete once this timer has advanced by at least duration
.
Panics
This method panics if the provided duration exceeds the maximum sleep duration allowed by this timer.
For a version of this function that does not panic, see
Timer::try_sleep
.
sourcepub fn try_sleep(&self, duration: Duration) -> Result<Sleep<'_>, TimerError>
pub fn try_sleep(&self, duration: Duration) -> Result<Sleep<'_>, TimerError>
Returns a Future
that will complete in duration
.
Returns
Ok
(
Sleep
)
if a newSleep
future was created successfully.Err
(
TimerError::DurationTooLong
)
if the requested sleep duration exceeds this timer’s maximum sleep duration.
The returned Sleep
future will be driven by this timer, and will
complete once this timer has advanced by at least duration
.
Panics
This method does not panic. For a version of this method that panics
rather than returning an error, see Timer::sleep
.
sourcepub fn sleep_ticks(&self, ticks: Ticks) -> Sleep<'_> ⓘ
pub fn sleep_ticks(&self, ticks: Ticks) -> Sleep<'_> ⓘ
sourcepub fn try_turn(&self) -> Option<Turn>
pub fn try_turn(&self) -> Option<Turn>
Attempt to turn the timer to the current now
if the timer is not
locked, potentially waking any Sleep
futures that have completed.
Returns
Some
(
Turn
)
if the lock was acquired and the wheel was advanced. ATurn
structure describes what occurred during this turn of the wheel, including the current time and the deadline of the next expiring timer, if one exists.None
if the wheel was not advanced because the lock was already held.
Interrupt Safety
This method will never spin if the timer wheel lock is held; instead, it will add any new ticks to a counter of “pending” ticks and return immediately. Therefore, it is safe to call this method in an interrupt handler, as it will never acquire a lock that may already be locked.
The Timer::turn
method will spin to lock the timer wheel lock if
it is currently held, ensuring that any pending wakeups are processed.
That method should never be called in an interrupt handler.
If a timer is driven primarily by calling try_turn
in an interrupt
handler, it may be desirable to occasionally call Timer::turn
outside of an interrupt handler (i.e., as as part of an occasional
runtime bookkeeping process). This ensures that any pending ticks are
observed by the timer in a relatively timely manner.
sourcepub fn turn(&self) -> Turn
pub fn turn(&self) -> Turn
Advance the timer to the current time, ensuring any Sleep
futures that
have completed are woken, even if a lock must be acquired.
Returns
A Turn
structure describing what occurred during this turn of the
wheel, including including the current time and the deadline
of the next expiring timer, if one exists.
Interrupt Safety
This method will spin to acquire the timer wheel lock if it is currently held elsewhere. Therefore, this method must NEVER be called in an interrupt handler!
If a timer is advanced inside an interrupt handler, use the
Timer::try_turn
method instead. If a timer is advanced primarily by
calls to Timer::try_turn
in an interrupt handler, it may be
desirable to occasionally call turn
outside an interrupt handler, to
ensure that pending ticks are drained frequently.