Use the clock that matches the promise.

Wait for elapsed time, one or many monotonic deadlines, or a civil time on the adjustable wall clock. Batch deadlines without creating one task per timer, and return backward wall-clock movement to the application instead of silently extending its schedule.

TIMER 01

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_Time deadline. 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_Time deadlines 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.Calendar target. Use it when the requested time is a point on the adjustable wall clock and the application must react to a backward correction.
TIMER 02

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.

relative pacing
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.

TIMER 03

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.

one 250 ms budget
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.

TIMER 04

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.

three deadlines, one waiting task
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.

bounded wait
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 Cancel or Replace removes it first. Arm replaces 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_Next only while Armed_Count is positive; there is no event that could wake a wait on a task-confined empty set.
TIMER 05

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.

wall-clock wait with explicit outcome
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.

TIMER 06

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.

allow up to 10 ms of lost wall-clock progress
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_Reached once the observed wall clock is at or beyond the target.
TIMER 07

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 timerfd wait uses TFD_TIMER_CANCEL_ON_SET so a discontinuous real-time clock change wakes the wait for classification.
Darwin
A relative Mach-absolute kqueue timer 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.

TIMER 08

Match common schedules to one policy.

Operation timeout
Compute one Ada.Real_Time deadline 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.Calendar target, call Wait_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.
TIMER 09

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_Error is 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.