1 ------------------------------------------------------------------------------
3 -- GNAT RUN-TIME LIBRARY (GNARL) COMPONENTS --
5 -- S Y S T E M . T A S K I N G . A S Y N C _ D E L A Y S --
9 -- Copyright (C) 1998-2023, Free Software Foundation, Inc. --
11 -- GNARL is free software; you can redistribute it and/or modify it under --
12 -- terms of the GNU General Public License as published by the Free Soft- --
13 -- ware Foundation; either version 3, or (at your option) any later ver- --
14 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
15 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
16 -- or FITNESS FOR A PARTICULAR PURPOSE. --
18 -- As a special exception under Section 7 of GPL version 3, you are granted --
19 -- additional permissions described in the GCC Runtime Library Exception, --
20 -- version 3.1, as published by the Free Software Foundation. --
22 -- You should have received a copy of the GNU General Public License and --
23 -- a copy of the GCC Runtime Library Exception along with this program; --
24 -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
25 -- <http://www.gnu.org/licenses/>. --
27 -- GNARL was developed by the GNARL team at Florida State University. --
28 -- Extensive contributions were provided by Ada Core Technologies, Inc. --
30 ------------------------------------------------------------------------------
32 with Ada
.Unchecked_Conversion
;
33 with Ada
.Task_Identification
;
35 with System
.Task_Primitives
.Operations
;
36 with System
.Tasking
.Utilities
;
37 with System
.Tasking
.Initialization
;
38 with System
.Tasking
.Debug
;
39 with System
.OS_Primitives
;
40 with System
.Interrupt_Management
.Operations
;
42 package body System
.Tasking
.Async_Delays
is
44 package STPO
renames System
.Task_Primitives
.Operations
;
45 package ST
renames System
.Tasking
;
46 package STU
renames System
.Tasking
.Utilities
;
47 package STI
renames System
.Tasking
.Initialization
;
48 package OSP
renames System
.OS_Primitives
;
50 function To_System
is new Ada
.Unchecked_Conversion
51 (Ada
.Task_Identification
.Task_Id
, Task_Id
);
53 Timer_Attention
: Boolean := False;
54 pragma Atomic
(Timer_Attention
);
57 pragma Interrupt_Priority
(System
.Any_Priority
'Last);
60 Timer_Server_ID
: constant ST
.Task_Id
:= To_System
(Timer_Server
'Identity);
62 -- The timer queue is a circular doubly linked list, ordered by absolute
63 -- wakeup time. The first item in the queue is Timer_Queue.Succ.
64 -- It is given a Resume_Time that is larger than any legitimate wakeup
65 -- time, so that the ordered insertion will always stop searching when it
66 -- gets back to the queue header block.
68 Timer_Queue
: aliased Delay_Block
;
70 package Init_Timer_Queue
is end Init_Timer_Queue
;
71 pragma Unreferenced
(Init_Timer_Queue
);
72 -- Initialize the Timer_Queue. This is a package to work around the
73 -- fact that statements are syntactically illegal here. We want this
74 -- initialization to happen before the Timer_Server is activated. A
75 -- build-in-place function would also work, but that's not supported
76 -- on all platforms (e.g. cil).
78 package body Init_Timer_Queue
is
80 Timer_Queue
.Succ
:= Timer_Queue
'Unchecked_Access;
81 Timer_Queue
.Pred
:= Timer_Queue
'Unchecked_Access;
82 Timer_Queue
.Resume_Time
:= Duration'Last;
85 ------------------------
86 -- Cancel_Async_Delay --
87 ------------------------
89 -- This should (only) be called from the compiler-generated cleanup routine
90 -- for an async. select statement with delay statement as trigger. The
91 -- effect should be to remove the delay from the timer queue, and exit one
94 -- The usage and logic are similar to Cancel_Protected_Entry_Call, but
95 -- simplified because this is not a true entry call.
97 procedure Cancel_Async_Delay
(D
: Delay_Block_Access
) is
98 Dpred
: Delay_Block_Access
;
99 Dsucc
: Delay_Block_Access
;
102 -- A delay block level of Level_No_Pending_Abort indicates the delay
103 -- has been canceled. If the delay has already been canceled, there is
104 -- nothing more to be done.
106 if D
.Level
= Level_No_Pending_Abort
then
110 D
.Level
:= Level_No_Pending_Abort
;
112 -- Remove self from timer queue
114 STI
.Defer_Abort_Nestable
(D
.Self_Id
);
115 STPO
.Write_Lock
(Timer_Server_ID
);
122 STPO
.Unlock
(Timer_Server_ID
);
124 -- Note that the above deletion code is required to be
125 -- idempotent, since the block may have been dequeued
126 -- previously by the Timer_Server.
128 -- leave the asynchronous select
130 STPO
.Write_Lock
(D
.Self_Id
);
131 STU
.Exit_One_ATC_Level
(D
.Self_Id
);
132 STPO
.Unlock
(D
.Self_Id
);
133 STI
.Undefer_Abort_Nestable
(D
.Self_Id
);
134 end Cancel_Async_Delay
;
136 ----------------------
137 -- Enqueue_Duration --
138 ----------------------
140 function Enqueue_Duration
142 D
: Delay_Block_Access
) return Boolean
151 -- The corresponding call to Undefer_Abort is performed by the
152 -- expanded code (see exp_ch9).
154 STI
.Defer_Abort
(STPO
.Self
);
156 (STPO
.Monotonic_Clock
157 + Duration'Min (T
, OSP
.Max_Sensible_Delay
), D
);
160 end Enqueue_Duration
;
166 -- Allocate a queue element for the wakeup time T and put it in the
167 -- queue in wakeup time order. Assume we are on an asynchronous
168 -- select statement with delay trigger. Put the calling task to
169 -- sleep until either the delay expires or is canceled.
171 -- We use one entry call record for this delay, since we have
172 -- to increment the ATC nesting level, but since it is not a
173 -- real entry call we do not need to use any of the fields of
174 -- the call record. The following code implements a subset of
175 -- the actions for the asynchronous case of Protected_Entry_Call,
176 -- much simplified since we know this never blocks, and does not
177 -- have the full semantics of a protected entry call.
179 procedure Time_Enqueue
181 D
: Delay_Block_Access
)
183 Self_Id
: constant Task_Id
:= STPO
.Self
;
184 Q
: Delay_Block_Access
;
187 pragma Debug
(Debug
.Trace
(Self_Id
, "Async_Delay", 'P'));
188 pragma Assert
(Self_Id
.Deferral_Level
= 1,
189 "async delay from within abort-deferred region");
191 if Self_Id
.ATC_Nesting_Level
= ATC_Level
'Last then
192 raise Storage_Error
with "not enough ATC nesting levels";
195 Self_Id
.ATC_Nesting_Level
:= Self_Id
.ATC_Nesting_Level
+ 1;
198 (Debug
.Trace
(Self_Id
, "ASD: entered ATC level: " &
199 ATC_Level
'Image (Self_Id
.ATC_Nesting_Level
), 'A'));
201 D
.Level
:= Self_Id
.ATC_Nesting_Level
;
202 D
.Self_Id
:= Self_Id
;
204 STPO
.Write_Lock
(Timer_Server_ID
);
206 -- Previously, there was code here to dynamically create
207 -- the Timer_Server task, if one did not already exist.
208 -- That code had a timing window that could allow multiple
209 -- timer servers to be created. Luckily, the need for
210 -- postponing creation of the timer server should now be
211 -- gone, since this package will only be linked in if
212 -- there are calls to enqueue calls on the timer server.
214 -- Insert D in the timer queue, at the position determined
215 -- by the wakeup time T.
217 Q
:= Timer_Queue
.Succ
;
219 while Q
.Resume_Time
< T
loop
223 -- Q is the block that has Resume_Time equal to or greater than
224 -- T. After the insertion we want Q to be the successor of D.
231 -- If the new element became the head of the queue,
232 -- signal the Timer_Server to wake up.
234 if Timer_Queue
.Succ
= D
then
235 Timer_Attention
:= True;
236 STPO
.Wakeup
(Timer_Server_ID
, ST
.Timer_Server_Sleep
);
239 STPO
.Unlock
(Timer_Server_ID
);
246 function Timed_Out
(D
: Delay_Block_Access
) return Boolean is
255 task body Timer_Server
is
256 Ignore
: constant Boolean := STU
.Make_Independent
;
258 -- Local Declarations
260 Next_Wakeup_Time
: Duration := Duration'Last;
264 Dequeued
: Delay_Block_Access
;
265 Dequeued_Task
: Task_Id
;
268 pragma Assert
(Timer_Server_ID
= STPO
.Self
);
270 -- Since this package may be elaborated before System.Interrupt,
271 -- we need to call Setup_Interrupt_Mask explicitly to ensure that
272 -- this task has the proper signal mask.
274 Interrupt_Management
.Operations
.Setup_Interrupt_Mask
;
276 -- Initialize the timer queue to empty, and make the wakeup time of the
277 -- header node be larger than any real wakeup time we will ever use.
280 STI
.Defer_Abort
(Timer_Server_ID
);
281 STPO
.Write_Lock
(Timer_Server_ID
);
283 -- The timer server needs to catch pending aborts after finalization
284 -- of library packages. If it doesn't poll for it, the server will
287 if not Timer_Attention
then
288 Timer_Server_ID
.Common
.State
:= ST
.Timer_Server_Sleep
;
290 if Next_Wakeup_Time
= Duration'Last then
291 Timer_Server_ID
.User_State
:= 1;
293 STPO
.Monotonic_Clock
+ OSP
.Max_Sensible_Delay
;
296 Timer_Server_ID
.User_State
:= 2;
300 (Timer_Server_ID
, Next_Wakeup_Time
,
301 OSP
.Absolute_RT
, ST
.Timer_Server_Sleep
,
303 Timer_Server_ID
.Common
.State
:= ST
.Runnable
;
306 -- Service all of the wakeup requests on the queue whose times have
307 -- been reached, and update Next_Wakeup_Time to next wakeup time
308 -- after that (the wakeup time of the head of the queue if any, else
309 -- a time far in the future).
311 Timer_Server_ID
.User_State
:= 3;
312 Timer_Attention
:= False;
314 Now
:= STPO
.Monotonic_Clock
;
315 while Timer_Queue
.Succ
.Resume_Time
<= Now
loop
317 -- Dequeue the waiting task from the front of the queue
319 pragma Debug
(System
.Tasking
.Debug
.Trace
320 (Timer_Server_ID
, "Timer service: waking up waiting task", 'E'));
322 Dequeued
:= Timer_Queue
.Succ
;
323 Timer_Queue
.Succ
:= Dequeued
.Succ
;
324 Dequeued
.Succ
.Pred
:= Dequeued
.Pred
;
325 Dequeued
.Succ
:= Dequeued
;
326 Dequeued
.Pred
:= Dequeued
;
328 -- We want to abort the queued task to the level of the async.
329 -- select statement with the delay. To do that, we need to lock
330 -- the ATCB of that task, but to avoid deadlock we need to release
331 -- the lock of the Timer_Server. This leaves a window in which
332 -- another task might perform an enqueue or dequeue operation on
333 -- the timer queue, but that is OK because we always restart the
334 -- next iteration at the head of the queue.
336 STPO
.Unlock
(Timer_Server_ID
);
337 STPO
.Write_Lock
(Dequeued
.Self_Id
);
338 Dequeued_Task
:= Dequeued
.Self_Id
;
339 Dequeued
.Timed_Out
:= True;
340 STI
.Locked_Abort_To_Level
341 (Timer_Server_ID
, Dequeued_Task
, Dequeued
.Level
- 1);
342 STPO
.Unlock
(Dequeued_Task
);
343 STPO
.Write_Lock
(Timer_Server_ID
);
346 Next_Wakeup_Time
:= Timer_Queue
.Succ
.Resume_Time
;
348 -- Service returns the Next_Wakeup_Time.
349 -- The Next_Wakeup_Time is either an infinity (no delay request)
350 -- or the wakeup time of the queue head. This value is used for
351 -- an actual delay in this server.
353 STPO
.Unlock
(Timer_Server_ID
);
354 STI
.Undefer_Abort
(Timer_Server_ID
);
358 end System
.Tasking
.Async_Delays
;