1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
|
------------------------------------------------------------------------------
-- --
-- GNAT RUN-TIME LIBRARY (GNARL) COMPONENTS --
-- --
-- S Y S T E M . T A S K I N G . A S Y N C _ D E L A Y S --
-- --
-- B o d y --
-- --
-- Copyright (C) 1998-2018, Free Software Foundation, Inc. --
-- --
-- GNARL 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/>. --
-- --
-- GNARL was developed by the GNARL team at Florida State University. --
-- Extensive contributions were provided by Ada Core Technologies, Inc. --
-- --
------------------------------------------------------------------------------
pragma Polling (Off);
-- Turn off polling, we do not want ATC polling to take place during
-- tasking operations. It causes infinite loops and other problems.
with Ada.Unchecked_Conversion;
with Ada.Task_Identification;
with System.Task_Primitives.Operations;
with System.Tasking.Utilities;
with System.Tasking.Initialization;
with System.Tasking.Debug;
with System.OS_Primitives;
with System.Interrupt_Management.Operations;
package body System.Tasking.Async_Delays is
package STPO renames System.Task_Primitives.Operations;
package ST renames System.Tasking;
package STU renames System.Tasking.Utilities;
package STI renames System.Tasking.Initialization;
package OSP renames System.OS_Primitives;
use Parameters;
function To_System is new Ada.Unchecked_Conversion
(Ada.Task_Identification.Task_Id, Task_Id);
Timer_Attention : Boolean := False;
pragma Atomic (Timer_Attention);
task Timer_Server is
pragma Interrupt_Priority (System.Any_Priority'Last);
end Timer_Server;
Timer_Server_ID : constant ST.Task_Id := To_System (Timer_Server'Identity);
-- The timer queue is a circular doubly linked list, ordered by absolute
-- wakeup time. The first item in the queue is Timer_Queue.Succ.
-- It is given a Resume_Time that is larger than any legitimate wakeup
-- time, so that the ordered insertion will always stop searching when it
-- gets back to the queue header block.
Timer_Queue : aliased Delay_Block;
package Init_Timer_Queue is end Init_Timer_Queue;
pragma Unreferenced (Init_Timer_Queue);
-- Initialize the Timer_Queue. This is a package to work around the
-- fact that statements are syntactically illegal here. We want this
-- initialization to happen before the Timer_Server is activated. A
-- build-in-place function would also work, but that's not supported
-- on all platforms (e.g. cil).
package body Init_Timer_Queue is
begin
Timer_Queue.Succ := Timer_Queue'Unchecked_Access;
Timer_Queue.Pred := Timer_Queue'Unchecked_Access;
Timer_Queue.Resume_Time := Duration'Last;
end Init_Timer_Queue;
------------------------
-- Cancel_Async_Delay --
------------------------
-- This should (only) be called from the compiler-generated cleanup routine
-- for an async. select statement with delay statement as trigger. The
-- effect should be to remove the delay from the timer queue, and exit one
-- ATC nesting level.
-- The usage and logic are similar to Cancel_Protected_Entry_Call, but
-- simplified because this is not a true entry call.
procedure Cancel_Async_Delay (D : Delay_Block_Access) is
Dpred : Delay_Block_Access;
Dsucc : Delay_Block_Access;
begin
-- Note that we mark the delay as being cancelled
-- using a level value that is reserved.
-- make this operation idempotent
if D.Level = ATC_Level_Infinity then
return;
end if;
D.Level := ATC_Level_Infinity;
-- remove self from timer queue
STI.Defer_Abort_Nestable (D.Self_Id);
if Single_Lock then
STPO.Lock_RTS;
end if;
STPO.Write_Lock (Timer_Server_ID);
Dpred := D.Pred;
Dsucc := D.Succ;
Dpred.Succ := Dsucc;
Dsucc.Pred := Dpred;
D.Succ := D;
D.Pred := D;
STPO.Unlock (Timer_Server_ID);
-- Note that the above deletion code is required to be
-- idempotent, since the block may have been dequeued
-- previously by the Timer_Server.
-- leave the asynchronous select
STPO.Write_Lock (D.Self_Id);
STU.Exit_One_ATC_Level (D.Self_Id);
STPO.Unlock (D.Self_Id);
if Single_Lock then
STPO.Unlock_RTS;
end if;
STI.Undefer_Abort_Nestable (D.Self_Id);
end Cancel_Async_Delay;
----------------------
-- Enqueue_Duration --
----------------------
function Enqueue_Duration
(T : Duration;
D : Delay_Block_Access) return Boolean
is
begin
if T <= 0.0 then
D.Timed_Out := True;
STPO.Yield;
return False;
else
-- The corresponding call to Undefer_Abort is performed by the
-- expanded code (see exp_ch9).
STI.Defer_Abort (STPO.Self);
Time_Enqueue
(STPO.Monotonic_Clock
+ Duration'Min (T, OSP.Max_Sensible_Delay), D);
return True;
end if;
end Enqueue_Duration;
------------------
-- Time_Enqueue --
------------------
-- Allocate a queue element for the wakeup time T and put it in the
-- queue in wakeup time order. Assume we are on an asynchronous
-- select statement with delay trigger. Put the calling task to
-- sleep until either the delay expires or is cancelled.
-- We use one entry call record for this delay, since we have
-- to increment the ATC nesting level, but since it is not a
-- real entry call we do not need to use any of the fields of
-- the call record. The following code implements a subset of
-- the actions for the asynchronous case of Protected_Entry_Call,
-- much simplified since we know this never blocks, and does not
-- have the full semantics of a protected entry call.
procedure Time_Enqueue
(T : Duration;
D : Delay_Block_Access)
is
Self_Id : constant Task_Id := STPO.Self;
Q : Delay_Block_Access;
begin
pragma Debug (Debug.Trace (Self_Id, "Async_Delay", 'P'));
pragma Assert (Self_Id.Deferral_Level = 1,
"async delay from within abort-deferred region");
if Self_Id.ATC_Nesting_Level = ATC_Level'Last then
raise Storage_Error with "not enough ATC nesting levels";
end if;
Self_Id.ATC_Nesting_Level := Self_Id.ATC_Nesting_Level + 1;
pragma Debug
(Debug.Trace (Self_Id, "ASD: entered ATC level: " &
ATC_Level'Image (Self_Id.ATC_Nesting_Level), 'A'));
D.Level := Self_Id.ATC_Nesting_Level;
D.Self_Id := Self_Id;
D.Resume_Time := T;
if Single_Lock then
STPO.Lock_RTS;
end if;
STPO.Write_Lock (Timer_Server_ID);
-- Previously, there was code here to dynamically create
-- the Timer_Server task, if one did not already exist.
-- That code had a timing window that could allow multiple
-- timer servers to be created. Luckily, the need for
-- postponing creation of the timer server should now be
-- gone, since this package will only be linked in if
-- there are calls to enqueue calls on the timer server.
-- Insert D in the timer queue, at the position determined
-- by the wakeup time T.
Q := Timer_Queue.Succ;
while Q.Resume_Time < T loop
Q := Q.Succ;
end loop;
-- Q is the block that has Resume_Time equal to or greater than
-- T. After the insertion we want Q to be the successor of D.
D.Succ := Q;
D.Pred := Q.Pred;
D.Pred.Succ := D;
Q.Pred := D;
-- If the new element became the head of the queue,
-- signal the Timer_Server to wake up.
if Timer_Queue.Succ = D then
Timer_Attention := True;
STPO.Wakeup (Timer_Server_ID, ST.Timer_Server_Sleep);
end if;
STPO.Unlock (Timer_Server_ID);
if Single_Lock then
STPO.Unlock_RTS;
end if;
end Time_Enqueue;
---------------
-- Timed_Out --
---------------
function Timed_Out (D : Delay_Block_Access) return Boolean is
begin
return D.Timed_Out;
end Timed_Out;
------------------
-- Timer_Server --
------------------
task body Timer_Server is
Ignore : constant Boolean := STU.Make_Independent;
-- Local Declarations
Next_Wakeup_Time : Duration := Duration'Last;
Timedout : Boolean;
Yielded : Boolean;
Now : Duration;
Dequeued : Delay_Block_Access;
Dequeued_Task : Task_Id;
pragma Unreferenced (Timedout, Yielded);
begin
pragma Assert (Timer_Server_ID = STPO.Self);
-- 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.
Interrupt_Management.Operations.Setup_Interrupt_Mask;
-- Initialize the timer queue to empty, and make the wakeup time of the
-- header node be larger than any real wakeup time we will ever use.
loop
STI.Defer_Abort (Timer_Server_ID);
if Single_Lock then
STPO.Lock_RTS;
end if;
STPO.Write_Lock (Timer_Server_ID);
-- The timer server needs to catch pending aborts after finalization
-- of library packages. If it doesn't poll for it, the server will
-- sometimes hang.
if not Timer_Attention then
Timer_Server_ID.Common.State := ST.Timer_Server_Sleep;
if Next_Wakeup_Time = Duration'Last then
Timer_Server_ID.User_State := 1;
Next_Wakeup_Time :=
STPO.Monotonic_Clock + OSP.Max_Sensible_Delay;
else
Timer_Server_ID.User_State := 2;
end if;
STPO.Timed_Sleep
(Timer_Server_ID, Next_Wakeup_Time,
OSP.Absolute_RT, ST.Timer_Server_Sleep,
Timedout, Yielded);
Timer_Server_ID.Common.State := ST.Runnable;
end if;
-- Service all of the wakeup requests on the queue whose times have
-- been reached, and update Next_Wakeup_Time to next wakeup time
-- after that (the wakeup time of the head of the queue if any, else
-- a time far in the future).
Timer_Server_ID.User_State := 3;
Timer_Attention := False;
Now := STPO.Monotonic_Clock;
while Timer_Queue.Succ.Resume_Time <= Now loop
-- Dequeue the waiting task from the front of the queue
pragma Debug (System.Tasking.Debug.Trace
(Timer_Server_ID, "Timer service: waking up waiting task", 'E'));
Dequeued := Timer_Queue.Succ;
Timer_Queue.Succ := Dequeued.Succ;
Dequeued.Succ.Pred := Dequeued.Pred;
Dequeued.Succ := Dequeued;
Dequeued.Pred := Dequeued;
-- We want to abort the queued task to the level of the async.
-- select statement with the delay. To do that, we need to lock
-- the ATCB of that task, but to avoid deadlock we need to release
-- the lock of the Timer_Server. This leaves a window in which
-- another task might perform an enqueue or dequeue operation on
-- the timer queue, but that is OK because we always restart the
-- next iteration at the head of the queue.
STPO.Unlock (Timer_Server_ID);
STPO.Write_Lock (Dequeued.Self_Id);
Dequeued_Task := Dequeued.Self_Id;
Dequeued.Timed_Out := True;
STI.Locked_Abort_To_Level
(Timer_Server_ID, Dequeued_Task, Dequeued.Level - 1);
STPO.Unlock (Dequeued_Task);
STPO.Write_Lock (Timer_Server_ID);
end loop;
Next_Wakeup_Time := Timer_Queue.Succ.Resume_Time;
-- Service returns the Next_Wakeup_Time.
-- The Next_Wakeup_Time is either an infinity (no delay request)
-- or the wakeup time of the queue head. This value is used for
-- an actual delay in this server.
STPO.Unlock (Timer_Server_ID);
if Single_Lock then
STPO.Unlock_RTS;
end if;
STI.Undefer_Abort (Timer_Server_ID);
end loop;
end Timer_Server;
end System.Tasking.Async_Delays;
|