blob: 14014d91663a65c2949919d035c25be4e5150936 [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT RUN-TIME COMPONENTS --
-- --
-- A D A . R E A L _ T I M E . T I M I N G _ E V E N T S --
-- --
-- B o d y --
-- --
-- Copyright (C) 2005-2022, Free Software Foundation, Inc. --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- ware Foundation; either version 3, or (at your option) any later ver- --
-- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE. --
-- --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception, --
-- version 3.1, as published by the Free Software Foundation. --
-- --
-- You should have received a copy of the GNU General Public License and --
-- a copy of the GCC Runtime Library Exception along with this program; --
-- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
-- <http://www.gnu.org/licenses/>. --
-- --
-- GNAT was originally developed by the GNAT team at New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc. --
-- --
------------------------------------------------------------------------------
with System.Task_Primitives.Operations;
with System.Tasking.Utilities;
with System.Soft_Links;
with System.Interrupt_Management.Operations;
with Ada.Containers.Doubly_Linked_Lists;
pragma Elaborate_All (Ada.Containers.Doubly_Linked_Lists);
---------------------------------
-- Ada.Real_Time.Timing_Events --
---------------------------------
package body Ada.Real_Time.Timing_Events is
use System.Task_Primitives.Operations;
package SSL renames System.Soft_Links;
type Any_Timing_Event is access all Timing_Event'Class;
-- We must also handle user-defined types derived from Timing_Event
------------
-- Events --
------------
package Events is new Ada.Containers.Doubly_Linked_Lists (Any_Timing_Event);
-- Provides the type for the container holding pointers to events
All_Events : Events.List;
-- The queue of pending events, ordered by increasing timeout value, that
-- have been "set" by the user via Set_Handler.
Event_Queue_Lock : aliased System.Task_Primitives.RTS_Lock;
-- Used for mutually exclusive access to All_Events
-- We need to Initialize_Lock before Timer is activated. The purpose of the
-- Dummy package is to get around Ada's syntax rules.
package Dummy is end Dummy;
package body Dummy is
begin
Initialize_Lock (Event_Queue_Lock'Access, Level => PO_Level);
end Dummy;
procedure Process_Queued_Events;
-- Examine the queue of pending events for any that have timed out. For
-- those that have timed out, remove them from the queue and invoke their
-- handler (unless the user has cancelled the event by setting the handler
-- pointer to null). Mutually exclusive access is held via Event_Queue_Lock
-- during part of the processing.
procedure Insert_Into_Queue (This : Any_Timing_Event);
-- Insert the specified event pointer into the queue of pending events
-- with mutually exclusive access via Event_Queue_Lock.
procedure Remove_From_Queue (This : Any_Timing_Event);
-- Remove the specified event pointer from the queue of pending events with
-- mutually exclusive access via Event_Queue_Lock. This procedure is used
-- by the client-side routines (Set_Handler, etc.).
-----------
-- Timer --
-----------
task Timer is
pragma Priority (System.Priority'Last);
end Timer;
task body Timer is
Period : constant Time_Span := Milliseconds (100);
-- This is a "chiming" clock timer that fires periodically. The period
-- selected is arbitrary and could be changed to suit the application
-- requirements. Obviously a shorter period would give better resolution
-- at the cost of more overhead.
Ignore : constant Boolean := System.Tasking.Utilities.Make_Independent;
pragma Unreferenced (Ignore);
begin
-- Since this package may be elaborated before System.Interrupt,
-- we need to call Setup_Interrupt_Mask explicitly to ensure that
-- this task has the proper signal mask.
System.Interrupt_Management.Operations.Setup_Interrupt_Mask;
loop
Process_Queued_Events;
delay until Clock + Period;
end loop;
end Timer;
---------------------------
-- Process_Queued_Events --
---------------------------
procedure Process_Queued_Events is
Next_Event : Any_Timing_Event;
begin
loop
SSL.Abort_Defer.all;
Write_Lock (Event_Queue_Lock'Access);
if All_Events.Is_Empty then
Unlock (Event_Queue_Lock'Access);
SSL.Abort_Undefer.all;
return;
else
Next_Event := All_Events.First_Element;
end if;
if Next_Event.Timeout > Clock then
-- We found one that has not yet timed out. The queue is in
-- ascending order by Timeout so there is no need to continue
-- processing (and indeed we must not continue since we always
-- delete the first element).
Unlock (Event_Queue_Lock'Access);
SSL.Abort_Undefer.all;
return;
end if;
-- We have an event that has timed out so we will process it. It must
-- be the first in the queue so no search is needed.
All_Events.Delete_First;
-- A fundamental issue is that the invocation of the event's handler
-- might call Set_Handler on itself to re-insert itself back into the
-- queue of future events. Thus we cannot hold the lock on the queue
-- while invoking the event's handler.
Unlock (Event_Queue_Lock'Access);
SSL.Abort_Undefer.all;
-- There is no race condition with the user changing the handler
-- pointer while we are processing because we are executing at the
-- highest possible application task priority and are not doing
-- anything to block prior to invoking their handler.
declare
Handler : constant Timing_Event_Handler := Next_Event.Handler;
begin
-- The first act is to clear the event, per D.15(13/2). Besides,
-- we cannot clear the handler pointer *after* invoking the
-- handler because the handler may have re-inserted the event via
-- Set_Event. Thus we take a copy and then clear the component.
Next_Event.Handler := null;
if Handler /= null then
Handler.all (Timing_Event (Next_Event.all));
end if;
-- Ignore exceptions propagated by Handler.all, as required by
-- RM D.15(21/2).
exception
when others =>
null;
end;
end loop;
end Process_Queued_Events;
-----------------------
-- Insert_Into_Queue --
-----------------------
procedure Insert_Into_Queue (This : Any_Timing_Event) is
function Sooner (Left, Right : Any_Timing_Event) return Boolean;
-- Compares events in terms of timeout values
package By_Timeout is new Events.Generic_Sorting (Sooner);
-- Used to keep the events in ascending order by timeout value
------------
-- Sooner --
------------
function Sooner (Left, Right : Any_Timing_Event) return Boolean is
begin
return Left.Timeout < Right.Timeout;
end Sooner;
-- Start of processing for Insert_Into_Queue
begin
SSL.Abort_Defer.all;
Write_Lock (Event_Queue_Lock'Access);
All_Events.Append (This);
-- A critical property of the implementation of this package is that
-- all occurrences are in ascending order by Timeout. Thus the first
-- event in the queue always has the "next" value for the Timer task
-- to use in its delay statement.
By_Timeout.Sort (All_Events);
Unlock (Event_Queue_Lock'Access);
SSL.Abort_Undefer.all;
end Insert_Into_Queue;
-----------------------
-- Remove_From_Queue --
-----------------------
procedure Remove_From_Queue (This : Any_Timing_Event) is
use Events;
Location : Cursor;
begin
SSL.Abort_Defer.all;
Write_Lock (Event_Queue_Lock'Access);
Location := All_Events.Find (This);
if Location /= No_Element then
All_Events.Delete (Location);
end if;
Unlock (Event_Queue_Lock'Access);
SSL.Abort_Undefer.all;
end Remove_From_Queue;
-----------------
-- Set_Handler --
-----------------
procedure Set_Handler
(Event : in out Timing_Event;
At_Time : Time;
Handler : Timing_Event_Handler)
is
begin
Remove_From_Queue (Event'Unchecked_Access);
Event.Handler := null;
-- RM D.15(15/2) required that at this point, we check whether the time
-- has already passed, and if so, call Handler.all directly from here
-- instead of doing the enqueuing below. However, this caused a nasty
-- race condition and potential deadlock. If the current task has
-- already locked the protected object of Handler.all, and the time has
-- passed, deadlock would occur. It has been fixed by AI05-0094-1, which
-- says that the handler should be executed as soon as possible, meaning
-- that the timing event will be executed after the protected action
-- finishes (Handler.all should not be called directly from here).
-- The same comment applies to the other Set_Handler below.
if Handler /= null then
Event.Timeout := At_Time;
Event.Handler := Handler;
Insert_Into_Queue (Event'Unchecked_Access);
end if;
end Set_Handler;
-----------------
-- Set_Handler --
-----------------
procedure Set_Handler
(Event : in out Timing_Event;
In_Time : Time_Span;
Handler : Timing_Event_Handler)
is
begin
Remove_From_Queue (Event'Unchecked_Access);
Event.Handler := null;
-- See comment in the other Set_Handler above
if Handler /= null then
Event.Timeout := Clock + In_Time;
Event.Handler := Handler;
Insert_Into_Queue (Event'Unchecked_Access);
end if;
end Set_Handler;
---------------------
-- Current_Handler --
---------------------
function Current_Handler
(Event : Timing_Event) return Timing_Event_Handler
is
begin
return Event.Handler;
end Current_Handler;
--------------------
-- Cancel_Handler --
--------------------
procedure Cancel_Handler
(Event : in out Timing_Event;
Cancelled : out Boolean)
is
begin
Remove_From_Queue (Event'Unchecked_Access);
Cancelled := Event.Handler /= null;
Event.Handler := null;
end Cancel_Handler;
-------------------
-- Time_Of_Event --
-------------------
function Time_Of_Event (Event : Timing_Event) return Time is
begin
-- RM D.15(18/2): Time_First must be returned in the event is not set
return (if Event.Handler = null then Time_First else Event.Timeout);
end Time_Of_Event;
--------------
-- Finalize --
--------------
procedure Finalize (This : in out Timing_Event) is
begin
-- D.15 (19/2) says finalization clears the event
This.Handler := null;
Remove_From_Queue (This'Unchecked_Access);
end Finalize;
end Ada.Real_Time.Timing_Events;