| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT RUN-TIME LIBRARY (GNARL) COMPONENTS -- |
| -- -- |
| -- S Y S T E M . T A S K I N G . U T I L I T I E S -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 1992-2022, 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. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| -- This package provides RTS Internal Declarations |
| |
| -- These declarations are not part of the GNARLI |
| |
| with System.Tasking.Debug; |
| with System.Task_Primitives.Operations; |
| with System.Tasking.Initialization; |
| with System.Tasking.Queuing; |
| |
| package body System.Tasking.Utilities is |
| |
| package STPO renames System.Task_Primitives.Operations; |
| |
| use Tasking.Debug; |
| use Task_Primitives; |
| use Task_Primitives.Operations; |
| |
| -------------------- |
| -- Abort_One_Task -- |
| -------------------- |
| |
| -- Similar to Locked_Abort_To_Level (Self_ID, T, Level_Completed_Task), |
| -- but: |
| -- (1) caller should be holding no locks |
| -- (2) may be called for tasks that have not yet been activated |
| -- (3) always aborts whole task |
| |
| procedure Abort_One_Task (Self_ID : Task_Id; T : Task_Id) is |
| begin |
| Write_Lock (T); |
| |
| if T.Common.State = Unactivated then |
| T.Common.Activator := null; |
| T.Common.State := Terminated; |
| T.Callable := False; |
| Cancel_Queued_Entry_Calls (T); |
| |
| elsif T.Common.State /= Terminated then |
| Initialization.Locked_Abort_To_Level |
| (Self_ID, T, Level_Completed_Task); |
| end if; |
| |
| Unlock (T); |
| end Abort_One_Task; |
| |
| ----------------- |
| -- Abort_Tasks -- |
| ----------------- |
| |
| -- This must be called to implement the abort statement. |
| -- Much of the actual work of the abort is done by the abortee, |
| -- via the Abort_Handler signal handler, and propagation of the |
| -- Abort_Signal special exception. |
| |
| procedure Abort_Tasks (Tasks : Task_List) is |
| Self_Id : constant Task_Id := STPO.Self; |
| C : Task_Id; |
| P : Task_Id; |
| |
| begin |
| -- If pragma Detect_Blocking is active then Program_Error must be |
| -- raised if this potentially blocking operation is called from a |
| -- protected action. |
| |
| if System.Tasking.Detect_Blocking |
| and then Self_Id.Common.Protected_Action_Nesting > 0 |
| then |
| raise Program_Error with "potentially blocking operation"; |
| end if; |
| |
| Initialization.Defer_Abort_Nestable (Self_Id); |
| |
| -- ????? |
| -- Really should not be nested deferral here. |
| -- Patch for code generation error that defers abort before |
| -- evaluating parameters of an entry call (at least, timed entry |
| -- calls), and so may propagate an exception that causes abort |
| -- to remain undeferred indefinitely. See C97404B. When all |
| -- such bugs are fixed, this patch can be removed. |
| |
| Lock_RTS; |
| |
| for J in Tasks'Range loop |
| C := Tasks (J); |
| Abort_One_Task (Self_Id, C); |
| end loop; |
| |
| C := All_Tasks_List; |
| |
| while C /= null loop |
| if C.Pending_ATC_Level > Level_Completed_Task then |
| P := C.Common.Parent; |
| |
| while P /= null loop |
| if P.Pending_ATC_Level = Level_Completed_Task then |
| Abort_One_Task (Self_Id, C); |
| exit; |
| end if; |
| |
| P := P.Common.Parent; |
| end loop; |
| end if; |
| |
| C := C.Common.All_Tasks_Link; |
| end loop; |
| |
| Unlock_RTS; |
| Initialization.Undefer_Abort_Nestable (Self_Id); |
| end Abort_Tasks; |
| |
| ------------------------------- |
| -- Cancel_Queued_Entry_Calls -- |
| ------------------------------- |
| |
| -- This should only be called by T, unless T is a terminated previously |
| -- unactivated task. |
| |
| procedure Cancel_Queued_Entry_Calls (T : Task_Id) is |
| Next_Entry_Call : Entry_Call_Link; |
| Entry_Call : Entry_Call_Link; |
| Self_Id : constant Task_Id := STPO.Self; |
| |
| Caller : Task_Id; |
| pragma Unreferenced (Caller); |
| -- Should this be removed ??? |
| |
| Level : Integer; |
| pragma Unreferenced (Level); |
| -- Should this be removed ??? |
| |
| begin |
| pragma Assert (T = Self or else T.Common.State = Terminated); |
| |
| for J in 1 .. T.Entry_Num loop |
| Queuing.Dequeue_Head (T.Entry_Queues (J), Entry_Call); |
| |
| while Entry_Call /= null loop |
| |
| -- Leave Entry_Call.Done = False, since this is cancelled |
| |
| Caller := Entry_Call.Self; |
| Entry_Call.Exception_To_Raise := Tasking_Error'Identity; |
| Queuing.Dequeue_Head (T.Entry_Queues (J), Next_Entry_Call); |
| Level := Entry_Call.Level - 1; |
| Unlock (T); |
| Write_Lock (Entry_Call.Self); |
| Initialization.Wakeup_Entry_Caller |
| (Self_Id, Entry_Call, Cancelled); |
| Unlock (Entry_Call.Self); |
| Write_Lock (T); |
| Entry_Call.State := Done; |
| Entry_Call := Next_Entry_Call; |
| end loop; |
| end loop; |
| end Cancel_Queued_Entry_Calls; |
| |
| ------------------------ |
| -- Exit_One_ATC_Level -- |
| ------------------------ |
| |
| -- Call only with abort deferred and holding lock of Self_Id. |
| -- This is a bit of common code for all entry calls. |
| -- The effect is to exit one level of ATC nesting. |
| |
| -- If we have reached the desired ATC nesting level, reset the |
| -- requested level to effective infinity, to allow further calls. |
| -- In any case, reset Self_Id.Aborting, to allow re-raising of |
| -- Abort_Signal. |
| |
| procedure Exit_One_ATC_Level (Self_ID : Task_Id) is |
| begin |
| pragma Assert (Self_ID.ATC_Nesting_Level > Level_No_ATC_Occurring); |
| |
| Self_ID.ATC_Nesting_Level := Self_ID.ATC_Nesting_Level - 1; |
| |
| pragma Debug |
| (Debug.Trace (Self_ID, "EOAL: exited to ATC level: " & |
| ATC_Level'Image (Self_ID.ATC_Nesting_Level), 'A')); |
| |
| if Self_ID.Pending_ATC_Level < Level_No_Pending_Abort then |
| |
| if Self_ID.Pending_ATC_Level = Self_ID.ATC_Nesting_Level then |
| Self_ID.Pending_ATC_Level := Level_No_Pending_Abort; |
| Self_ID.Aborting := False; |
| else |
| -- Force the next Undefer_Abort to re-raise Abort_Signal |
| |
| pragma Assert |
| (Self_ID.Pending_ATC_Level < Self_ID.ATC_Nesting_Level); |
| |
| if Self_ID.Aborting then |
| Self_ID.ATC_Hack := True; |
| Self_ID.Pending_Action := True; |
| end if; |
| end if; |
| end if; |
| end Exit_One_ATC_Level; |
| |
| ---------------------- |
| -- Make_Independent -- |
| ---------------------- |
| |
| function Make_Independent return Boolean is |
| Self_Id : constant Task_Id := STPO.Self; |
| Environment_Task : constant Task_Id := STPO.Environment_Task; |
| Parent : constant Task_Id := Self_Id.Common.Parent; |
| |
| begin |
| if Self_Id.Known_Tasks_Index /= -1 then |
| Known_Tasks (Self_Id.Known_Tasks_Index) := null; |
| end if; |
| |
| Initialization.Defer_Abort (Self_Id); |
| Write_Lock (Environment_Task); |
| Write_Lock (Self_Id); |
| |
| -- The run time assumes that the parent of an independent task is the |
| -- environment task. |
| |
| pragma Assert (Parent = Environment_Task); |
| |
| Self_Id.Master_Of_Task := Independent_Task_Level; |
| |
| -- Update Independent_Task_Count that is needed for the GLADE |
| -- termination rule. See also pending update in |
| -- System.Tasking.Stages.Check_Independent |
| |
| Independent_Task_Count := Independent_Task_Count + 1; |
| |
| -- This should be called before the task reaches its "begin" (see spec), |
| -- which ensures that the environment task cannot race ahead and be |
| -- already waiting for children to complete. |
| |
| Unlock (Self_Id); |
| pragma Assert (Environment_Task.Common.State /= Master_Completion_Sleep); |
| |
| Unlock (Environment_Task); |
| Initialization.Undefer_Abort (Self_Id); |
| |
| -- Return True. Actually the return value is junk, since we expect it |
| -- always to be ignored (see spec), but we have to return something! |
| |
| return True; |
| end Make_Independent; |
| |
| ------------------ |
| -- Make_Passive -- |
| ------------------ |
| |
| procedure Make_Passive (Self_ID : Task_Id; Task_Completed : Boolean) is |
| C : Task_Id := Self_ID; |
| P : Task_Id := C.Common.Parent; |
| |
| Master_Completion_Phase : Integer; |
| |
| begin |
| if P /= null then |
| Write_Lock (P); |
| end if; |
| |
| Write_Lock (C); |
| |
| if Task_Completed then |
| Self_ID.Common.State := Terminated; |
| |
| if Self_ID.Awake_Count = 0 then |
| |
| -- We are completing via a terminate alternative. |
| -- Our parent should wait in Phase 2 of Complete_Master. |
| |
| Master_Completion_Phase := 2; |
| |
| pragma Assert (Task_Completed); |
| pragma Assert (Self_ID.Terminate_Alternative); |
| pragma Assert (Self_ID.Alive_Count = 1); |
| |
| else |
| -- We are NOT on a terminate alternative. |
| -- Our parent should wait in Phase 1 of Complete_Master. |
| |
| Master_Completion_Phase := 1; |
| pragma Assert (Self_ID.Awake_Count >= 1); |
| end if; |
| |
| -- We are accepting with a terminate alternative |
| |
| else |
| if Self_ID.Open_Accepts = null then |
| |
| -- Somebody started a rendezvous while we had our lock open. |
| -- Skip the terminate alternative. |
| |
| Unlock (C); |
| |
| if P /= null then |
| Unlock (P); |
| end if; |
| |
| return; |
| end if; |
| |
| Self_ID.Terminate_Alternative := True; |
| Master_Completion_Phase := 0; |
| |
| pragma Assert (Self_ID.Terminate_Alternative); |
| pragma Assert (Self_ID.Awake_Count >= 1); |
| end if; |
| |
| if Master_Completion_Phase = 2 then |
| |
| -- Since our Awake_Count is zero but our Alive_Count |
| -- is nonzero, we have been accepting with a terminate |
| -- alternative, and we now have been told to terminate |
| -- by a completed master (in some ancestor task) that |
| -- is waiting (with zero Awake_Count) in Phase 2 of |
| -- Complete_Master. |
| |
| pragma Debug (Debug.Trace (Self_ID, "Make_Passive: Phase 2", 'M')); |
| |
| pragma Assert (P /= null); |
| |
| C.Alive_Count := C.Alive_Count - 1; |
| |
| if C.Alive_Count > 0 then |
| Unlock (C); |
| Unlock (P); |
| return; |
| end if; |
| |
| -- C's count just went to zero, indicating that |
| -- all of C's dependents are terminated. |
| -- C has a parent, P. |
| |
| loop |
| -- C's count just went to zero, indicating that all of C's |
| -- dependents are terminated. C has a parent, P. Notify P that |
| -- C and its dependents have all terminated. |
| |
| P.Alive_Count := P.Alive_Count - 1; |
| exit when P.Alive_Count > 0; |
| Unlock (C); |
| Unlock (P); |
| C := P; |
| P := C.Common.Parent; |
| |
| -- Environment task cannot have terminated yet |
| |
| pragma Assert (P /= null); |
| |
| Write_Lock (P); |
| Write_Lock (C); |
| end loop; |
| |
| if P.Common.State = Master_Phase_2_Sleep |
| and then C.Master_Of_Task = P.Master_Within |
| then |
| pragma Assert (P.Common.Wait_Count > 0); |
| P.Common.Wait_Count := P.Common.Wait_Count - 1; |
| |
| if P.Common.Wait_Count = 0 then |
| Wakeup (P, Master_Phase_2_Sleep); |
| end if; |
| end if; |
| |
| Unlock (C); |
| Unlock (P); |
| return; |
| end if; |
| |
| -- We are terminating in Phase 1 or Complete_Master, |
| -- or are accepting on a terminate alternative. |
| |
| C.Awake_Count := C.Awake_Count - 1; |
| |
| if Task_Completed then |
| C.Alive_Count := C.Alive_Count - 1; |
| end if; |
| |
| if C.Awake_Count > 0 or else P = null then |
| Unlock (C); |
| |
| if P /= null then |
| Unlock (P); |
| end if; |
| |
| return; |
| end if; |
| |
| -- C's count just went to zero, indicating that all of C's |
| -- dependents are terminated or accepting with terminate alt. |
| -- C has a parent, P. |
| |
| loop |
| -- Notify P that C has gone passive |
| |
| if P.Awake_Count > 0 then |
| P.Awake_Count := P.Awake_Count - 1; |
| end if; |
| |
| if Task_Completed and then C.Alive_Count = 0 then |
| P.Alive_Count := P.Alive_Count - 1; |
| end if; |
| |
| exit when P.Awake_Count > 0; |
| Unlock (C); |
| Unlock (P); |
| C := P; |
| P := C.Common.Parent; |
| |
| if P = null then |
| return; |
| end if; |
| |
| Write_Lock (P); |
| Write_Lock (C); |
| end loop; |
| |
| -- P has non-passive dependents |
| |
| if P.Common.State = Master_Completion_Sleep |
| and then C.Master_Of_Task = P.Master_Within |
| then |
| pragma Debug |
| (Debug.Trace |
| (Self_ID, "Make_Passive: Phase 1, parent waiting", 'M')); |
| |
| -- If parent is in Master_Completion_Sleep, it cannot be on a |
| -- terminate alternative, hence it cannot have Wait_Count of zero. |
| |
| pragma Assert (P.Common.Wait_Count > 0); |
| P.Common.Wait_Count := P.Common.Wait_Count - 1; |
| |
| if P.Common.Wait_Count = 0 then |
| Wakeup (P, Master_Completion_Sleep); |
| end if; |
| |
| else |
| pragma Debug |
| (Debug.Trace (Self_ID, "Make_Passive: Phase 1, parent awake", 'M')); |
| null; |
| end if; |
| |
| Unlock (C); |
| Unlock (P); |
| end Make_Passive; |
| |
| end System.Tasking.Utilities; |