Choose by what must remain stable.
The three timer operations answer different questions. Make the clock choice part of the operation's contract rather than treating the APIs as interchangeable.
Sleep_For- Wait for an elapsed duration. Use it for simple pacing and short pauses where no reusable absolute deadline is needed.
Sleep_Until- Wait for an
Ada.Real_Timedeadline. Use it for timeouts, retry budgets, and periodic work that must not follow civil-clock corrections. Timer_Set- Wait for many one-shot
Ada.Real_Timedeadlines from one task. Use it when one loop should receive complete due batches without creating one task per deadline. Wait_Until- Wait for an
Ada.Calendartarget. Use it when the requested time is a point on the adjustable wall clock and the application must react to a backward correction.
Wait for an elapsed interval.
Sleep_For takes seconds as a Duration. A positive value waits for at least that interval, subject to normal scheduling delay. A zero or negative value performs one delay 0.0 yield.
with Flyology.IO.Timers;
Flyology.IO.Timers.Sleep_For (0.050); -- 50 ms
Flyology.IO.Timers.Sleep_For (0.0); -- yield once
Repeatedly sleeping for a period after work completes accumulates the work time as drift. Use a sequence of monotonic deadlines when the intended schedule is fixed-rate.
Carry one monotonic deadline.
Sleep_Until accepts an absolute Ada.Real_Time.Time. Compute the deadline once and pass it through the operation so partial progress does not restart the budget. A past deadline returns at the next scheduling point.
with Ada.Real_Time;
with Flyology.IO.Timers;
use type Ada.Real_Time.Time;
Deadline : constant Ada.Real_Time.Time :=
Ada.Real_Time.Clock + Ada.Real_Time.Milliseconds (250);
-- Other work may consume part of the same budget.
Flyology.IO.Timers.Sleep_Until (Deadline);
Changes to the system's civil time do not move an Ada.Real_Time deadline. This makes it the default clock for elapsed-time guarantees.
Wait for many deadlines from one task.
Timer_Set is a bounded, caller-owned collection of one-shot monotonic timers. Declare its capacity and a matching Activation_Batch, then arm individual ids or replace the complete set from a one-based deadline array. The set allocates no storage after declaration.
with Ada.Real_Time;
with Flyology.IO.Timers;
procedure Wait_For_Timers is
use Ada.Real_Time;
package Timers renames Flyology.IO.Timers;
Schedule : Timers.Timer_Set (3);
Activated : Timers.Activation_Batch (3);
Deadlines : constant Timers.Deadline_Array (1 .. 3) :=
(1 => Clock + Milliseconds (10),
2 => Clock + Milliseconds (20),
3 => Clock + Milliseconds (30));
begin
Timers.Replace (Schedule, Deadlines);
while Timers.Armed_Count (Schedule) > 0 loop
Timers.Wait_Next (Schedule, Activated);
for Position in 1 .. Activated.Count loop
Handle (Activated.Ids (Position));
end loop;
end loop;
end Wait_For_Timers;
Wait_Next takes one monotonic-clock sample and returns every timer due at that sample. The ids are disarmed before the call returns, and callers must not depend on their batch order. Re-arm an id after handling it when the application wants another occurrence.
The timed overload bounds one wait while preserving later arms. It returns Timers_Activated with a nonempty batch or Wait_Timed_Out with an empty batch. A zero timeout polls once; timers due at that terminal sample take precedence over timeout.
declare
Outcome : Timers.Timer_Wait_Outcome;
begin
Timers.Wait_Next
(Schedule, Activated, Timeout => 0.050, Outcome => Outcome);
case Outcome is
when Timers.Timers_Activated =>
Process (Activated);
when Timers.Wait_Timed_Out =>
Run_Maintenance;
end case;
end;
- One scheduler wait
- The set keeps an indexed min-heap and registers only its earliest deadline through the calling task's normal delay path.
- One-shot delivery
- Each arm is returned once unless
CancelorReplaceremoves it first.Armreplaces the deadline of an already armed id. - Caller ownership
- The object is not task safe. One task must serialize arming, cancellation, replacement, inspection, and waiting.
- Empty collection
- Call
Wait_Nextonly whileArmed_Countis positive; there is no event that could wake a wait on a task-confined empty set.
Wait for a time that hits the clock.
Wait_Until accepts an absolute Ada.Calendar.Time. A forward clock change may make the target immediately due. A backward change beyond the selected tolerance returns Clock_Moved_Backward, giving the application a chance to recompute or confirm its civil schedule.
with Ada.Calendar;
with Flyology.IO.Timers;
use type Ada.Calendar.Time;
package Timers renames Flyology.IO.Timers;
Target : constant Ada.Calendar.Time := Ada.Calendar.Clock + 60.0;
Result : constant Timers.Wall_Clock_Wait_Result :=
Timers.Wait_Until (Target);
case Result.Outcome is
when Timers.Target_Reached =>
Run_Scheduled_Work;
when Timers.Clock_Moved_Backward =>
Revalidate_Schedule
(Observed => Result.Observed_Time,
Adjustment => Result.Backward_Adjustment);
end case;
The result includes the wall-clock sample used for the decision and an estimated lost-progress duration. The adjustment is zero when the target was reached.
Separate clock jitter from a schedule change.
The default backstep tolerance is 1 ms. Smaller lost-progress observations are treated as jitter and the wait is rearmed. A larger loss returns to the caller rather than waiting longer without notice. Set a nonnegative application-specific tolerance when the default is not appropriate.
Result := Timers.Wait_Until
(Target => Target,
Backstep_Tolerance => 0.010);
Detection compares wall-clock progress with monotonic elapsed time. Each wall read is bracketed by two steady reads; broad brackets are retried, brackets wider than one second are rejected, and classification uses the least elapsed time consistent with the accepted brackets. Descheduling between the reads therefore cannot manufacture a backstep. The returned adjustment is a conservative estimate for policy decisions, not an audit record of the system clock.
- Small backward jitter
- Rearm the target and continue waiting.
- Material backstep
- Return
Clock_Moved_Backward; the caller decides whether to recompute, confirm, skip, or wait again. - Forward correction
- Return
Target_Reachedonce the observed wall clock is at or beyond the target.
Keep the same call in either lane.
The public call and outcome are lane-neutral. The waiting mechanism follows the current task designation.
- Lightweight task
- The fiber suspends on its execution group's event loop. Other ready tasks in that group may continue on the same loop thread.
- Native task
- The wait uses the native path and blocks only that task's pthread.
- Linux
- An absolute
timerfdwait usesTFD_TIMER_CANCEL_ON_SETso a discontinuous real-time clock change wakes the wait for classification. - Darwin
- A relative Mach-absolute
kqueuetimer is armed from a fresh wall-clock sample and paired with the system clock-set notification. It uses at-most-one-second active-time slices that continue after resume, so a missing or missed notification has bounded active-time detection latency apart from task scheduling delay.
Both platform paths recheck wall and monotonic time after arming, closing the setup window in which the clock might otherwise change unnoticed.
Match common schedules to one policy.
- Operation timeout
- Compute one
Ada.Real_Timedeadline and retain it across retries and partial progress. - Fixed-rate work
- Advance a monotonic deadline by the period instead of sleeping for the period after each iteration. Decide explicitly whether an overrun catches up or skips missed ticks.
- Civil appointment
- Compute an
Ada.Calendartarget, callWait_Until, and revalidate the appointment after a reported backstep. - Recurring civil time
- After each completion or clock correction, derive the next occurrence from calendar rules. Do not model local-midnight or daily schedules as repeated 24-hour sleeps.
Account for what a timer cannot promise.
- A timer becomes eligible at its deadline; ordinary OS and Flyology scheduling delay may move actual execution later.
- The monotonic clock and kernel waits pause during system sleep. The operation does not wake a suspended computer, and resume-time wall-clock changes are classified when the application runs again.
- Clock-change notifications identify a need to recheck; the wall and monotonic samples determine the public outcome.
Flyology.IO.Device_Erroris raised if wall-clock timer setup or waiting fails.- Use the exact contracts in the generated API reference when handling errors or building reusable abstractions.