Commit ab31b1a6 by Pat Rogers Committed by Arnaud Charlet

a-rttiev.ads, [...]: This is a significant redesign primarily for the sake of…

a-rttiev.ads, [...]: This is a significant redesign primarily for the sake of automatic timer task...

2006-10-31  Pat Rogers  <rogers@adacore.com>

	* a-rttiev.ads, a-rttiev.adb: 
	This is a significant redesign primarily for the sake of automatic
	timer task termination but also to fix a design flaw.
	Therefore we are now using an RTS lock, instead of a protected
	object, to provide mutual exclusion to the queue of pending events
	and the type Timing_Event is no longer a protected type.

From-SVN: r118327
parent 7440d86c
...@@ -31,14 +31,25 @@ ...@@ -31,14 +31,25 @@
-- -- -- --
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
with System.Task_Primitives.Operations;
with System.Tasking.Utilities; with System.Tasking.Utilities;
-- for Make_Independent with System.Soft_Links;
-- used for Abort_Defer/Undefer
with Ada.Containers.Doubly_Linked_Lists; with Ada.Containers.Doubly_Linked_Lists;
pragma Elaborate_All (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 package body Ada.Real_Time.Timing_Events is
use System.Task_Primitives.Operations;
-- for Write_Lock and Unlock
package SSL renames System.Soft_Links;
type Any_Timing_Event is access all Timing_Event'Class; type Any_Timing_Event is access all Timing_Event'Class;
-- We must also handle user-defined types derived from Timing_Event -- We must also handle user-defined types derived from Timing_Event
...@@ -46,26 +57,31 @@ package body Ada.Real_Time.Timing_Events is ...@@ -46,26 +57,31 @@ package body Ada.Real_Time.Timing_Events is
-- Events -- -- Events --
------------ ------------
package Events is package Events is new Ada.Containers.Doubly_Linked_Lists (Any_Timing_Event);
new Ada.Containers.Doubly_Linked_Lists (Any_Timing_Event); -- Provides the type for the container holding pointers to events
----------------- All_Events : Events.List;
-- Event_Queue -- -- The queue of pending events, ordered by increasing timeout value, that
----------------- -- have been "set" by the user via Set_Handler.
protected Event_Queue is Event_Queue_Lock : aliased System.Task_Primitives.RTS_Lock;
pragma Priority (System.Priority'Last); -- Used for mutually exclusive access to All_Events
procedure Insert (This : Any_Timing_Event); procedure Process_Queued_Events;
-- Inserts This into the queue in ascending order by Timeout -- 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 Process_Events; procedure Insert_Into_Queue (This : Any_Timing_Event);
-- Iterates over the list of events and calls the handlers for any of -- Insert the specified event pointer into the queue of pending events
-- those that have timed out. Deletes those that have timed out. -- with mutually exclusive access via Event_Queue_Lock.
private procedure Remove_From_Queue (This : Any_Timing_Event);
All_Events : Events.List; -- Remove the specified event pointer from the queue of pending events
end Event_Queue; -- with mutually exclusive access via Event_Queue_Lock.
-- This procedure is used by the client-side routines (Set_Handler, etc.).
----------- -----------
-- Timer -- -- Timer --
...@@ -73,6 +89,7 @@ package body Ada.Real_Time.Timing_Events is ...@@ -73,6 +89,7 @@ package body Ada.Real_Time.Timing_Events is
task Timer is task Timer is
pragma Priority (System.Priority'Last); pragma Priority (System.Priority'Last);
entry Start;
end Timer; end Timer;
task body Timer is task body Timer is
...@@ -81,104 +98,158 @@ package body Ada.Real_Time.Timing_Events is ...@@ -81,104 +98,158 @@ package body Ada.Real_Time.Timing_Events is
-- selected is arbitrary and could be changed to suit the application -- selected is arbitrary and could be changed to suit the application
-- requirements. Obviously a shorter period would give better resolution -- requirements. Obviously a shorter period would give better resolution
-- at the cost of more overhead. -- at the cost of more overhead.
begin begin
System.Tasking.Utilities.Make_Independent; System.Tasking.Utilities.Make_Independent;
-- We await the call to Start to ensure that Event_Queue_Lock has been
-- initialized by the package executable part prior to accessing it in
-- the loop. The task is activated before the first statement of the
-- executable part so it would otherwise be possible for the task to
-- call EnterCriticalSection in Process_Queued_Events before the
-- initialization.
-- We don't simply put the initialization here, prior to the loop,
-- because other application tasks could call the visible routines that
-- also call Enter/LeaveCriticalSection prior to this task doing the
-- initialization.
accept Start;
loop loop
Event_Queue.Process_Events; Process_Queued_Events;
delay until Clock + Period; delay until Clock + Period;
end loop; end loop;
end Timer; end Timer;
------------ ---------------------------
-- Sooner -- -- Process_Queued_Events --
------------ ---------------------------
function Sooner (Left, Right : Any_Timing_Event) return Boolean;
-- Used by the Event_Queue insertion routine to keep the events in
-- ascending order by timeout value.
-----------------
-- Event_Queue --
-----------------
protected body Event_Queue is
procedure Insert (This : Any_Timing_Event) is procedure Process_Queued_Events is
package By_Timeout is new Events.Generic_Sorting (Sooner); Next_Event : Any_Timing_Event;
-- Used to keep the events in ascending order by timeout value
begin begin
All_Events.Append (This); loop
SSL.Abort_Defer.all;
-- 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); Write_Lock (Event_Queue_Lock'Access);
end Insert;
procedure Process_Events is if All_Events.Is_Empty then
Next_Event : Any_Timing_Event; Unlock (Event_Queue_Lock'Access);
begin SSL.Abort_Undefer.all;
while not All_Events.Is_Empty loop return;
else
Next_Event := All_Events.First_Element; Next_Event := All_Events.First_Element;
end if;
-- Clients can cancel a timeout (setting the handler to null) but if Next_Event.Timeout > Clock then
-- cannot otherwise change the timeout/handler tuple until the
-- call to Reset below.
if Next_Event.Control.Current_Timeout > Clock then
-- We found one that has not yet timed-out. The queue is in -- We found one that has not yet timed-out. The queue is in
-- ascending order by Timeout so there is no need to continue -- ascending order by Timeout so there is no need to continue
-- processing (and indeed we must not continue since we always -- processing (and indeed we must not continue since we always
-- delete the first element). -- delete the first element).
Unlock (Event_Queue_Lock'Access);
SSL.Abort_Undefer.all;
return; return;
end if; end if;
declare -- We have an event that has timed out so we will process it. It
Response : Timing_Event_Handler; -- must be the first in the queue so no search is needed.
begin All_Events.Delete_First;
-- We take a local snapshot of the handler to avoid a race
-- condition because we evaluate the handler value in the
-- if-statement and again in the call and the client might have
-- set it to null between those two evaluations.
Response := Next_Event.Control.Current_Handler; -- 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.
if Response /= null then Unlock (Event_Queue_Lock'Access);
-- D.15 (13/2) says we only invoke the handler if it is SSL.Abort_Undefer.all;
-- set when the timeout expires.
Response (Timing_Event (Next_Event.all)); -- There is no race condition with the user changing the handler
end if; -- 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 (Timing_Event (Next_Event.all));
end if;
exception exception
when others => when others =>
null; -- per D.15 (21/2) null;
end; end;
end loop;
end Process_Queued_Events;
Next_Event.Control.Reset; -----------------------
-- Insert_Into_Queue --
-----------------------
-- Clients can now change the timeout/handler pair for this event procedure Insert_Into_Queue (This : Any_Timing_Event) is
-- And now we can delete the event from the queue. Any item we function Sooner (Left, Right : Any_Timing_Event) return Boolean;
-- delete would be the first in the queue because we exit the loop -- Compares events in terms of timeout values
-- when we first find one that is not yet timed-out. This fact
-- allows us to use these "First oriented" list processing
-- routines instead of the cursor oriented versions because we can
-- avoid handling the way deletion affects cursors.
All_Events.Delete_First; package By_Timeout is new Events.Generic_Sorting (Sooner);
end loop; -- Used to keep the events in ascending order by timeout value
end Process_Events;
end Event_Queue; function Sooner (Left, Right : Any_Timing_Event) return Boolean is
begin
return Left.Timeout < Right.Timeout;
end Sooner;
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 -- -- Set_Handler --
...@@ -190,18 +261,18 @@ package body Ada.Real_Time.Timing_Events is ...@@ -190,18 +261,18 @@ package body Ada.Real_Time.Timing_Events is
Handler : Timing_Event_Handler) Handler : Timing_Event_Handler)
is is
begin begin
Event.Control.Cancel; Remove_From_Queue (Event'Unchecked_Access);
Event.Handler := null;
if At_Time <= Clock then if At_Time <= Clock then
if Handler /= null then if Handler /= null then
Handler (Event); Handler (Event);
end if; end if;
return; return;
end if; end if;
if Handler /= null then if Handler /= null then
Event.Control.Set (At_Time, Handler); Event.Timeout := At_Time;
Event_Queue.Insert (Event'Unchecked_Access); Event.Handler := Handler;
Insert_Into_Queue (Event'Unchecked_Access);
end if; end if;
end Set_Handler; end Set_Handler;
...@@ -215,63 +286,21 @@ package body Ada.Real_Time.Timing_Events is ...@@ -215,63 +286,21 @@ package body Ada.Real_Time.Timing_Events is
Handler : Timing_Event_Handler) Handler : Timing_Event_Handler)
is is
begin begin
Event.Control.Cancel; Remove_From_Queue (Event'Unchecked_Access);
Event.Handler := null;
if In_Time <= Time_Span_Zero then if In_Time <= Time_Span_Zero then
if Handler /= null then if Handler /= null then
Handler (Event); Handler (Event);
end if; end if;
return; return;
end if; end if;
if Handler /= null then if Handler /= null then
Event.Control.Set (Clock + In_Time, Handler); Event.Timeout := Clock + In_Time;
Event_Queue.Insert (Event'Unchecked_Access); Event.Handler := Handler;
Insert_Into_Queue (Event'Unchecked_Access);
end if; end if;
end Set_Handler; end Set_Handler;
-----------------
-- Event_State --
-----------------
protected body Event_State is
entry Set
(Timeout : Time;
Handler : Timing_Event_Handler)
when
Available
is
begin
Event_State.Timeout := Set.Timeout;
Event_State.Handler := Set.Handler;
Available := False;
end Set;
procedure Reset is
begin
Cancel;
Available := True;
end Reset;
procedure Cancel is
begin
Handler := null;
Timeout := Time_First;
end Cancel;
function Current_Timeout return Time is
begin
return Timeout;
end Current_Timeout;
function Current_Handler return Timing_Event_Handler is
begin
return Handler;
end Current_Handler;
end Event_State;
--------------------- ---------------------
-- Current_Handler -- -- Current_Handler --
--------------------- ---------------------
...@@ -280,7 +309,7 @@ package body Ada.Real_Time.Timing_Events is ...@@ -280,7 +309,7 @@ package body Ada.Real_Time.Timing_Events is
(Event : Timing_Event) return Timing_Event_Handler (Event : Timing_Event) return Timing_Event_Handler
is is
begin begin
return Event.Control.Current_Handler; return Event.Handler;
end Current_Handler; end Current_Handler;
-------------------- --------------------
...@@ -292,8 +321,9 @@ package body Ada.Real_Time.Timing_Events is ...@@ -292,8 +321,9 @@ package body Ada.Real_Time.Timing_Events is
Cancelled : out Boolean) Cancelled : out Boolean)
is is
begin begin
Cancelled := Event.Control.Current_Handler /= null; Remove_From_Queue (Event'Unchecked_Access);
Event.Control.Cancel; Cancelled := Event.Handler /= null;
Event.Handler := null;
end Cancel_Handler; end Cancel_Handler;
------------------- -------------------
...@@ -302,18 +332,9 @@ package body Ada.Real_Time.Timing_Events is ...@@ -302,18 +332,9 @@ package body Ada.Real_Time.Timing_Events is
function Time_Of_Event (Event : Timing_Event) return Time is function Time_Of_Event (Event : Timing_Event) return Time is
begin begin
return Event.Control.Current_Timeout; return Event.Timeout;
end Time_Of_Event; end Time_Of_Event;
------------
-- Sooner --
------------
function Sooner (Left, Right : Any_Timing_Event) return Boolean is
begin
return Left.Control.Current_Timeout < Right.Control.Current_Timeout;
end Sooner;
-------------- --------------
-- Finalize -- -- Finalize --
-------------- --------------
...@@ -322,7 +343,11 @@ package body Ada.Real_Time.Timing_Events is ...@@ -322,7 +343,11 @@ package body Ada.Real_Time.Timing_Events is
begin begin
-- D.15 (19/2) says finalization clears the event -- D.15 (19/2) says finalization clears the event
This.Control.Cancel; This.Handler := null;
Remove_From_Queue (This'Unchecked_Access);
end Finalize; end Finalize;
begin
Initialize_Lock (Event_Queue_Lock'Access, Level => PO_Level);
Timer.Start;
end Ada.Real_Time.Timing_Events; end Ada.Real_Time.Timing_Events;
...@@ -65,42 +65,7 @@ package Ada.Real_Time.Timing_Events is ...@@ -65,42 +65,7 @@ package Ada.Real_Time.Timing_Events is
private private
protected type Event_State is type Timing_Event is new Ada.Finalization.Limited_Controlled with record
-- D.15 (22/2) requires atomicity with respect to the operations
-- provided by the package and the timing events they manipulate. On
-- real-time operating systems suitable for implementing this package, a
-- different implementation strategy would be employed to meet that
-- requirement.
entry Set (Timeout : Time; Handler : Timing_Event_Handler);
-- Changes the timeout and handler values for procedure Set_Handler. Can
-- only execute when the event is 'available', to prevent a race
-- condition between the caller of Set_Handler and the internal Timer
-- task that processes the events. In particular, D.15 (22/2) requires
-- that there be no possibility of a new handler executing in response
-- to an old timeout.
procedure Reset;
-- First resets the timeout to Time_First and the handler to
-- null. Indicates that Set (for Set_Handler) can now change the timeout
-- and/or handler. Called only by the interal Timer task.
procedure Cancel;
-- Resets the timeout to Time_First and the handler to
-- null. Called by procedure Cancel_Handler and by procedure Reset.
function Current_Timeout return Time;
-- Returns the currently set timeout. The value Time_First is returned
-- if the Timing_Event is in the "cleared" state. Called by function
-- Time_of_Event.
function Current_Handler return Timing_Event_Handler;
-- Returns the currently set handler. The value null is returned if the
-- Timing_Event is in the "cleared" state. Called by function
-- Curent_Handler.
private
Timeout : Time := Time_First; Timeout : Time := Time_First;
-- The time at which the user's handler should be invoked when the -- The time at which the user's handler should be invoked when the
-- event is "set" (i.e., when Handler is not null). -- event is "set" (i.e., when Handler is not null).
...@@ -109,16 +74,6 @@ private ...@@ -109,16 +74,6 @@ private
-- An access value designating the protected procedure to be invoked -- An access value designating the protected procedure to be invoked
-- at the Timeout time in the future. When this value is null the event -- at the Timeout time in the future. When this value is null the event
-- is said to be "cleared" and no timeout is processed. -- is said to be "cleared" and no timeout is processed.
Available : Boolean := True;
-- A flag controlling when users can change the Timeout and Handler
-- tuple. In particular the entry Set, called by procedure Set_Handler,
-- is controlled by this flag.
end Event_State;
type Timing_Event is new Ada.Finalization.Limited_Controlled with record
Control : Event_State;
end record; end record;
overriding procedure Finalize (This : in out Timing_Event); overriding procedure Finalize (This : in out Timing_Event);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment