1------------------------------------------------------------------------------
2--                                                                          --
3--                 GNAT RUN-TIME LIBRARY (GNARL) COMPONENTS                 --
4--                                                                          --
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          --
6--                                                                          --
7--                                  B o d y                                 --
8--                                                                          --
9--         Copyright (C) 1998-2021, Free Software Foundation, Inc.          --
10--                                                                          --
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.                                     --
17--                                                                          --
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.               --
21--                                                                          --
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/>.                                          --
26--                                                                          --
27-- GNARL was developed by the GNARL team at Florida State University.       --
28-- Extensive contributions were provided by Ada Core Technologies, Inc.     --
29--                                                                          --
30------------------------------------------------------------------------------
31
32with Ada.Unchecked_Conversion;
33with Ada.Task_Identification;
34
35with System.Task_Primitives.Operations;
36with System.Tasking.Utilities;
37with System.Tasking.Initialization;
38with System.Tasking.Debug;
39with System.OS_Primitives;
40with System.Interrupt_Management.Operations;
41
42package body System.Tasking.Async_Delays is
43
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;
49
50   function To_System is new Ada.Unchecked_Conversion
51     (Ada.Task_Identification.Task_Id, Task_Id);
52
53   Timer_Attention : Boolean := False;
54   pragma Atomic (Timer_Attention);
55
56   task Timer_Server is
57      pragma Interrupt_Priority (System.Any_Priority'Last);
58   end Timer_Server;
59
60   Timer_Server_ID : constant ST.Task_Id := To_System (Timer_Server'Identity);
61
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.
67
68   Timer_Queue : aliased Delay_Block;
69
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).
77
78   package body Init_Timer_Queue is
79   begin
80      Timer_Queue.Succ := Timer_Queue'Unchecked_Access;
81      Timer_Queue.Pred := Timer_Queue'Unchecked_Access;
82      Timer_Queue.Resume_Time := Duration'Last;
83   end Init_Timer_Queue;
84
85   ------------------------
86   -- Cancel_Async_Delay --
87   ------------------------
88
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
92   --  ATC nesting level.
93
94   --  The usage and logic are similar to Cancel_Protected_Entry_Call, but
95   --  simplified because this is not a true entry call.
96
97   procedure Cancel_Async_Delay (D : Delay_Block_Access) is
98      Dpred : Delay_Block_Access;
99      Dsucc : Delay_Block_Access;
100
101   begin
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.
105
106      if D.Level = Level_No_Pending_Abort then
107         return;
108      end if;
109
110      D.Level := Level_No_Pending_Abort;
111
112      --  Remove self from timer queue
113
114      STI.Defer_Abort_Nestable (D.Self_Id);
115      STPO.Write_Lock (Timer_Server_ID);
116      Dpred := D.Pred;
117      Dsucc := D.Succ;
118      Dpred.Succ := Dsucc;
119      Dsucc.Pred := Dpred;
120      D.Succ := D;
121      D.Pred := D;
122      STPO.Unlock (Timer_Server_ID);
123
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.
127
128      --  leave the asynchronous select
129
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;
135
136   ----------------------
137   -- Enqueue_Duration --
138   ----------------------
139
140   function Enqueue_Duration
141     (T : Duration;
142      D : Delay_Block_Access) return Boolean
143   is
144   begin
145      if T <= 0.0 then
146         D.Timed_Out := True;
147         STPO.Yield;
148         return False;
149
150      else
151         --  The corresponding call to Undefer_Abort is performed by the
152         --  expanded code (see exp_ch9).
153
154         STI.Defer_Abort (STPO.Self);
155         Time_Enqueue
156           (STPO.Monotonic_Clock
157            + Duration'Min (T, OSP.Max_Sensible_Delay), D);
158         return True;
159      end if;
160   end Enqueue_Duration;
161
162   ------------------
163   -- Time_Enqueue --
164   ------------------
165
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.
170
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.
178
179   procedure Time_Enqueue
180     (T : Duration;
181      D : Delay_Block_Access)
182   is
183      Self_Id : constant Task_Id  := STPO.Self;
184      Q       : Delay_Block_Access;
185
186   begin
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");
190
191      if Self_Id.ATC_Nesting_Level = ATC_Level'Last then
192         raise Storage_Error with "not enough ATC nesting levels";
193      end if;
194
195      Self_Id.ATC_Nesting_Level := Self_Id.ATC_Nesting_Level + 1;
196
197      pragma Debug
198        (Debug.Trace (Self_Id, "ASD: entered ATC level: " &
199         ATC_Level'Image (Self_Id.ATC_Nesting_Level), 'A'));
200
201      D.Level := Self_Id.ATC_Nesting_Level;
202      D.Self_Id := Self_Id;
203      D.Resume_Time := T;
204      STPO.Write_Lock (Timer_Server_ID);
205
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.
213
214      --  Insert D in the timer queue, at the position determined
215      --  by the wakeup time T.
216
217      Q := Timer_Queue.Succ;
218
219      while Q.Resume_Time < T loop
220         Q := Q.Succ;
221      end loop;
222
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.
225
226      D.Succ := Q;
227      D.Pred := Q.Pred;
228      D.Pred.Succ := D;
229      Q.Pred := D;
230
231      --  If the new element became the head of the queue,
232      --  signal the Timer_Server to wake up.
233
234      if Timer_Queue.Succ = D then
235         Timer_Attention := True;
236         STPO.Wakeup (Timer_Server_ID, ST.Timer_Server_Sleep);
237      end if;
238
239      STPO.Unlock (Timer_Server_ID);
240   end Time_Enqueue;
241
242   ---------------
243   -- Timed_Out --
244   ---------------
245
246   function Timed_Out (D : Delay_Block_Access) return Boolean is
247   begin
248      return D.Timed_Out;
249   end Timed_Out;
250
251   ------------------
252   -- Timer_Server --
253   ------------------
254
255   task body Timer_Server is
256      Ignore : constant Boolean := STU.Make_Independent;
257
258      --  Local Declarations
259
260      Next_Wakeup_Time : Duration := Duration'Last;
261      Timedout         : Boolean;
262      Yielded          : Boolean;
263      Now              : Duration;
264      Dequeued         : Delay_Block_Access;
265      Dequeued_Task    : Task_Id;
266
267      pragma Unreferenced (Timedout, Yielded);
268
269   begin
270      pragma Assert (Timer_Server_ID = STPO.Self);
271
272      --  Since this package may be elaborated before System.Interrupt,
273      --  we need to call Setup_Interrupt_Mask explicitly to ensure that
274      --  this task has the proper signal mask.
275
276      Interrupt_Management.Operations.Setup_Interrupt_Mask;
277
278      --  Initialize the timer queue to empty, and make the wakeup time of the
279      --  header node be larger than any real wakeup time we will ever use.
280
281      loop
282         STI.Defer_Abort (Timer_Server_ID);
283         STPO.Write_Lock (Timer_Server_ID);
284
285         --  The timer server needs to catch pending aborts after finalization
286         --  of library packages. If it doesn't poll for it, the server will
287         --  sometimes hang.
288
289         if not Timer_Attention then
290            Timer_Server_ID.Common.State := ST.Timer_Server_Sleep;
291
292            if Next_Wakeup_Time = Duration'Last then
293               Timer_Server_ID.User_State := 1;
294               Next_Wakeup_Time :=
295                 STPO.Monotonic_Clock + OSP.Max_Sensible_Delay;
296
297            else
298               Timer_Server_ID.User_State := 2;
299            end if;
300
301            STPO.Timed_Sleep
302              (Timer_Server_ID, Next_Wakeup_Time,
303               OSP.Absolute_RT, ST.Timer_Server_Sleep,
304               Timedout, Yielded);
305            Timer_Server_ID.Common.State := ST.Runnable;
306         end if;
307
308         --  Service all of the wakeup requests on the queue whose times have
309         --  been reached, and update Next_Wakeup_Time to next wakeup time
310         --  after that (the wakeup time of the head of the queue if any, else
311         --  a time far in the future).
312
313         Timer_Server_ID.User_State := 3;
314         Timer_Attention := False;
315
316         Now := STPO.Monotonic_Clock;
317         while Timer_Queue.Succ.Resume_Time <= Now loop
318
319            --  Dequeue the waiting task from the front of the queue
320
321            pragma Debug (System.Tasking.Debug.Trace
322              (Timer_Server_ID, "Timer service: waking up waiting task", 'E'));
323
324            Dequeued := Timer_Queue.Succ;
325            Timer_Queue.Succ := Dequeued.Succ;
326            Dequeued.Succ.Pred := Dequeued.Pred;
327            Dequeued.Succ := Dequeued;
328            Dequeued.Pred := Dequeued;
329
330            --  We want to abort the queued task to the level of the async.
331            --  select statement with the delay. To do that, we need to lock
332            --  the ATCB of that task, but to avoid deadlock we need to release
333            --  the lock of the Timer_Server. This leaves a window in which
334            --  another task might perform an enqueue or dequeue operation on
335            --  the timer queue, but that is OK because we always restart the
336            --  next iteration at the head of the queue.
337
338            STPO.Unlock (Timer_Server_ID);
339            STPO.Write_Lock (Dequeued.Self_Id);
340            Dequeued_Task := Dequeued.Self_Id;
341            Dequeued.Timed_Out := True;
342            STI.Locked_Abort_To_Level
343              (Timer_Server_ID, Dequeued_Task, Dequeued.Level - 1);
344            STPO.Unlock (Dequeued_Task);
345            STPO.Write_Lock (Timer_Server_ID);
346         end loop;
347
348         Next_Wakeup_Time := Timer_Queue.Succ.Resume_Time;
349
350         --  Service returns the Next_Wakeup_Time.
351         --  The Next_Wakeup_Time is either an infinity (no delay request)
352         --  or the wakeup time of the queue head. This value is used for
353         --  an actual delay in this server.
354
355         STPO.Unlock (Timer_Server_ID);
356         STI.Undefer_Abort (Timer_Server_ID);
357      end loop;
358   end Timer_Server;
359
360end System.Tasking.Async_Delays;
361