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, it
must be driven by a time source, which calls the Timer::advance
method
and/or the Timer::pend_duration
and Timer::force_advance
methods.
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.
In any case, the timer must be advanced periodically by the time source.
§Interrupt-Driven Timers
When the timer is interrupt-driven, the interrupt handler for the timer
interrupt should call either the Timer::pend_duration
or
Timer::advance
methods.
Timer::advance
will attempt to optimistically acquire a spinlock, and
advance the timer if it is acquired, or add to the pending tick counter if
the timer wheel is currently locked. Therefore, it is safe to call in an
interrupt handler, as it and cannot cause a deadlock.
However, if interrupt handlers must be extremely short, the
Timer::pend_duration
method can be used, instead. This method will
never acquire a lock, and does not actually turn the timer wheel. Instead,
it always performs only a single atomic add. If the time source is an
interrupt handler which calls Timer::pend_duration
, though, the timer
wheel must be turned externally. This can be done by calling the
Timer::force_advance_ticks
method periodically outside of the interrupt
handler, with a duration of 0 ticks. In general, this should be done as some
form of runtime bookkeeping action. For example, the timer can be advanced
in a system’s run loop every time the Scheduler::tick
method completes.
§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::advance_ticks
(1)
or Timer::pend_ticks
(1)
in the interrupt
handler, as discused above.
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::advance
method should generally be used to drive the timer. Prior
to calling Timer::advance
, the time source is read to determine the
duration that has elapsed since the last time Timer::advance
was called,
and that duration is provided when calling advance
.
This should occur periodically as part of a runtime loop (as discussed in
the 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::advance
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 a Timer
(e.g. by calling Timer::new
), the minimum
granularity of the timer is selected by providing the Duration
represented by a single timer tick. 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.
When using an interrupt-driven time source, the tick duration should generally be the interval that the timer interrupt fires at. A finer resolution won’t have any benefit, as the timer only fires at that frequency, and all sleeps that complete between two timer interrupts will be woken at the same time anyway.
When using a timestamp-driven time source, selecting the resolution of the timestamp counter as the timer’s tick duration is probably a good choice.
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 const fn new(tick_duration: Duration) -> Self
pub const fn new(tick_duration: Duration) -> Self
Returns a new Timer
with the specified tick_duration
for a single timer
tick.
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: u64) -> Sleep<'_> ⓘ
pub fn sleep_ticks(&self, ticks: u64) -> Sleep<'_> ⓘ
sourcepub fn pend_duration(&self, duration: Duration)
pub fn pend_duration(&self, duration: Duration)
Add pending time to the timer without turning the wheel.
This function will never acquire a lock, and will never notify any
waiting Sleep
futures. It can be called in an interrupt handler that
cannot perform significant amounts of work.
However, if this method is used, then Timer::force_advance
must be
called frequently from outside of the interrupt handler.
sourcepub fn pend_ticks(&self, ticks: u64)
pub fn pend_ticks(&self, ticks: u64)
Add pending ticks to the timer without turning the wheel.
This function will never acquire a lock, and will never notify any
waiting Sleep
futures. It can be called in an interrupt handler that
cannot perform significant amounts of work.
However, if this method is used, then Timer::force_advance
must be
called frequently from outside of the interrupt handler.
sourcepub fn advance(&self, duration: Duration)
pub fn advance(&self, duration: Duration)
Advance the timer by duration
, 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 force_advance
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 advance
in an interrupt
handler, it may be desirable to occasionally call force_advance
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 advance_ticks(&self, ticks: u64)
pub fn advance_ticks(&self, ticks: u64)
Advance the timer by ticks
timer ticks, 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 force_advance_ticks
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 advance
in an interrupt
handler, it may be desirable to occasionally call force_advance_ticks
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 force_advance(&self, duration: Duration) -> Turn
pub fn force_advance(&self, duration: Duration) -> Turn
Advance the timer by duration
, 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 advance
method instead. If a timer is advanced primarily by calls to
advance
, it may be desirable to occasionally call force_advance
outside an interrupt handler, to ensure that pending ticks are drained
frequently.
sourcepub fn force_advance_ticks(&self, ticks: u64) -> Turn
pub fn force_advance_ticks(&self, ticks: u64) -> Turn
Advance the timer by ticks
timer ticks, 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 advance_ticks
method instead. If a timer is advanced primarily by calls to
advance_ticks
, it may be desirable to occasionally call force_advance
outside an interrupt handler, to ensure that pending ticks are drained
frequently.