Real-Time Ada: delay until, Priorities, Requeue, Ravenscar & Jorvik

Write periodic tasks with delay until and Ada.Real_Time, assign priorities and understand priority ceilings (Ceiling_Locking) and scheduling policies, requeue callers between entries, use Suspension_Object and Task_Identification, and learn the Ravenscar and Jorvik profiles for analysable, certifiable concurrency.

Ada is the language of choice for many hard real-time systems — flight controls, railway signalling, satellites. The Real-Time Systems Annex (Annex D) adds precise timing, fixed-priority preemptive scheduling, priority ceilings for protected objects, and restricted tasking profiles whose timing behavior can be analysed and certified.

Periodic Tasks and Priorities

The canonical periodic task: 'Next := Clock; loop Do_Work; Next := Next + Period; delay until Next; end loop;'. Because the release time is absolute, jitter in Do_Work does not accumulate. Priorities are set with the Priority aspect (or pragma) on tasks and protected objects: 'task Sampler with Priority => System.Default_Priority + 1;'. With pragma Task_Dispatching_Policy (FIFO_Within_Priorities), the highest-priority ready task always runs. Protected objects get a ceiling priority (Locking_Policy (Ceiling_Locking)): a task executing a protected operation runs at the ceiling, which bounds priority inversion and prevents deadlock on a single processor.

• delay until Next — drift-free periodic release. • Priority aspect on tasks/protected objects; higher runs first. • Ceiling_Locking prevents unbounded priority inversion. • Policies: FIFO_Within_Priorities, EDF, Round_Robin.

Requeue, Suspension Objects, Task Identification

'requeue Other_Entry;' inside an entry body (or accept) moves the caller to another entry queue with the same parameters, without releasing it — useful for two-stage protocols (take a ticket, then wait until served) and for implementing events and resource allocators. Ada.Synchronous_Task_Control.Suspension_Object is a lightweight binary semaphore (Set_True, Suspend_Until_True) usable even under Ravenscar. Ada.Task_Identification gives Current_Task, Image, Abort_Task, and Is_Terminated for any task ID. Other annex packages: Ada.Execution_Time (CPU-time budgets), Ada.Real_Time.Timing_Events (timed handlers), Ada.Dispatching.EDF.

• requeue E; — move caller to another entry (same parameters). • Suspension_Object: Set_True / Suspend_Until_True. • Task_Identification, Execution_Time, Timing_Events.

Ravenscar and Jorvik Profiles

'pragma Profile (Ravenscar);' restricts tasking to a statically analysable subset: library-level tasks only (created at startup, never terminating), no rendezvous (tasks communicate via protected objects), at most one entry per protected object with a simple Boolean barrier and at most one waiting caller, no select, no abort, no relative delay (only delay until), no dynamic priorities. In exchange, the runtime is tiny and certifiable, and response-time analysis can prove deadlines are met. Ada 2022's Jorvik profile relaxes a few Ravenscar limits (multiple entries/queued callers, more barrier forms, relative delays). Both are also supported by SPARK for proof of absence of data races and deadlocks.

• Ravenscar: static tasks + protected objects, no rendezvous/select/abort. • Enables schedulability analysis and certified runtimes. • Jorvik (Ada 2022): a slightly relaxed Ravenscar.

Code Examples

The sampler waits on a suspension object, then runs five 50 ms periods with delay until. The Event entry requeues the listener onto Waiting until Signal opens its barrier. GNAT notes that Waiting doesn't reference Ticket — the value set in Wait is kept because a requeue passes the same parameters along. Output: Main priority: 15, Sampler priority: 16 Main task id: available Sampler: 5 periods of 50 ms took at least 250 ms? TRUE Cycles recorded: 1 2 3 4 5 Listener still waiting? TRUE Listener released with ticket 1

A typical Ravenscar skeleton (declarations only; configuration pragmas apply to the whole partition).

loop Work; delay 0.05; end loop;

Next := Clock; loop Work; Next := Next + Milliseconds (50); delay until Next; end loop;

Relative delays accumulate drift (and are forbidden under Ravenscar).

protected Data with Priority => 5 is... -- called by a priority-10 task protected Data with Priority => 10 is...

Under Ceiling_Locking, calling with a priority above the ceiling raises Program_Error.

-- Ravenscar program declaring tasks inside a procedure -- declare all tasks at library level (in packages)

Ravenscar forbids dynamically created and local tasks.

Why use 'delay until' rather than 'delay' for periodic tasks? A. It's shorter B. Absolute release times avoid cumulative drift C. It uses less memory D. No difference

What does requeue do? A. Restarts the task B. Moves the caller to another entry queue with the same parameters C. Aborts the call D. Changes priority

Which is forbidden under Ravenscar? A. Protected objects B. delay until C. Rendezvous (task entries) D. Library-level tasks

What problem does the Ceiling_Locking policy address? A. Memory leaks B. Unbounded priority inversion C. Stack overflow D. Floating-point errors Trace the real-time example.

Real output: Main priority: 15, Sampler priority: 16 Main task id: available Sampler: 5 periods of 50 ms took at least 250 ms? TRUE Cycles recorded: 1 2 3 4 5 Listener still waiting? TRUE Listener released with ticket 1

with Ada.Text_IO;   use Ada.Text_IO;
with Ada.Real_Time; use Ada.Real_Time;
with System;
with Ada.Task_Identification;
with Ada.Synchronous_Task_Control; use Ada.Synchronous_Task_Control;

procedure L56_Realtime is
   Period : constant Time_Span := Milliseconds (50);
   Cycles : array (1 .. 5) of Integer := [others => 0];
   Released : Suspension_Object;           --  a simple binary semaphore

   --  A periodic task with an explicit priority
   task Sampler with Priority => System.Default_Priority + 1;

   task body Sampler is
      Next  : Time := Clock;
      Start : constant Time := Next;
   begin
      Suspend_Until_True (Released);       --  wait for the start signal
      Next := Clock;
      for I in Cycles'Range loop
         Cycles (I) := I;                  --  "sample"
         Next := Next + Period;
         delay until Next;                 --  absolute delay: no cumulative drift
      end loop;
      Put_Line ("Sampler: 5 periods of 50 ms took at least 250 ms? "
                & Boolean'Image (Clock - Start >= Milliseconds (250)));
   end Sampler;

   --  Requeue: a protected "event" that callers wait on until it is signalled
   protected Event is
      entry Wait (
…
pragma Profile (Ravenscar);   -- in gnat.adc or at the top of the partition
pragma Partition_Elaboration_Policy (Sequential);

task Controller with Priority => 20;
protected Sensor_Data with Priority => 21 is   -- ceiling >= callers' priorities
   procedure Write (V : Float);
   entry Wait_New (V : out Float);             -- single entry, simple barrier
private
   Value : Float := 0.0;
   Fresh : Boolean := False;
end Sensor_Data;