------------------------------------------------------------------------------ -- -- -- GNAT RUN-TIME LIBRARY (GNARL) COMPONENTS -- -- -- -- S Y S T E M . T A S K I N G . S T A G 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 -- -- . -- -- -- -- GNARL was developed by the GNARL team at Florida State University. -- -- Extensive contributions were provided by Ada Core Technologies, Inc. -- -- -- ------------------------------------------------------------------------------ pragma Partition_Elaboration_Policy (Concurrent); -- This package only implements the concurrent elaboration policy. This pragma -- will enforce it (and detect conflicts with user specified policy). with Ada.Exceptions; with Ada.Unchecked_Deallocation; with Ada.Task_Initialization; with System.Interrupt_Management; with System.Tasking.Debug; with System.Address_Image; with System.Task_Primitives; with System.Task_Primitives.Operations; with System.Tasking.Utilities; with System.Tasking.Queuing; with System.Tasking.Rendezvous; with System.OS_Primitives; with System.Secondary_Stack; with System.Restrictions; with System.Standard_Library; with System.Stack_Usage; with System.Storage_Elements; with System.Soft_Links; -- These are procedure pointers to non-tasking routines that use task -- specific data. In the absence of tasking, these routines refer to global -- data. In the presence of tasking, they must be replaced with pointers to -- task-specific versions. Also used for Create_TSD, Destroy_TSD, Get_Current -- _Excep, Finalize_Library_Objects, Task_Termination, Handler. with System.Tasking.Initialization; pragma Elaborate_All (System.Tasking.Initialization); -- This insures that tasking is initialized if any tasks are created package body System.Tasking.Stages is package STPO renames System.Task_Primitives.Operations; package SSL renames System.Soft_Links; package SSE renames System.Storage_Elements; use Ada.Exceptions; use Secondary_Stack; use Task_Primitives; use Task_Primitives.Operations; ----------------------- -- Local Subprograms -- ----------------------- procedure Free is new Ada.Unchecked_Deallocation (Ada_Task_Control_Block, Task_Id); procedure Trace_Unhandled_Exception_In_Task (Self_Id : Task_Id); -- This procedure outputs the task specific message for exception -- tracing purposes. procedure Task_Wrapper (Self_ID : Task_Id); pragma Convention (C, Task_Wrapper); -- This is the procedure that is called by the GNULL from the new context -- when a task is created. It waits for activation and then calls the task -- body procedure. When the task body procedure completes, it terminates -- the task. -- -- The Task_Wrapper's address will be provided to the underlying threads -- library as the task entry point. Convention C is what makes most sense -- for that purpose (Export C would make the function globally visible, -- and affect the link name on which GDB depends). This will in addition -- trigger an automatic stack alignment suitable for GCC's assumptions if -- need be. -- "Vulnerable_..." in the procedure names below means they must be called -- with abort deferred. procedure Vulnerable_Complete_Task (Self_ID : Task_Id); -- Complete the calling task. This procedure must be called with -- abort deferred. It should only be called by Complete_Task and -- Finalize_Global_Tasks (for the environment task). procedure Vulnerable_Complete_Master (Self_ID : Task_Id); -- Complete the current master of the calling task. This procedure -- must be called with abort deferred. It should only be called by -- Vulnerable_Complete_Task and Complete_Master. procedure Vulnerable_Complete_Activation (Self_ID : Task_Id); -- Signal to Self_ID's activator that Self_ID has completed activation. -- This procedure must be called with abort deferred. procedure Abort_Dependents (Self_ID : Task_Id); -- Abort all the direct dependents of Self at its current master nesting -- level, plus all of their dependents, transitively. RTS_Lock should be -- locked by the caller. procedure Vulnerable_Free_Task (T : Task_Id); -- Recover all runtime system storage associated with the task T. This -- should only be called after T has terminated and will no longer be -- referenced. -- -- For tasks created by an allocator that fails, due to an exception, it is -- called from Expunge_Unactivated_Tasks. -- -- Different code is used at master completion, in Terminate_Dependents, -- due to a need for tighter synchronization with the master. ---------------------- -- Abort_Dependents -- ---------------------- procedure Abort_Dependents (Self_ID : Task_Id) is C : Task_Id; P : Task_Id; -- Each task C will take care of its own dependents, so there is no -- need to worry about them here. In fact, it would be wrong to abort -- indirect dependents here, because we can't distinguish between -- duplicate master ids. For example, suppose we have three nested -- task bodies T1,T2,T3. And suppose T1 also calls P which calls Q (and -- both P and Q are task masters). Q will have the same master id as -- Master_Of_Task of T3. Previous versions of this would abort T3 when -- Q calls Complete_Master, which was completely wrong. begin C := All_Tasks_List; while C /= null loop P := C.Common.Parent; if P = Self_ID then if C.Master_Of_Task = Self_ID.Master_Within then pragma Debug (Debug.Trace (Self_ID, "Aborting", 'X', C)); Utilities.Abort_One_Task (Self_ID, C); C.Dependents_Aborted := True; end if; end if; C := C.Common.All_Tasks_Link; end loop; Self_ID.Dependents_Aborted := True; end Abort_Dependents; ----------------- -- Abort_Tasks -- ----------------- procedure Abort_Tasks (Tasks : Task_List) is begin Utilities.Abort_Tasks (Tasks); end Abort_Tasks; -------------------- -- Activate_Tasks -- -------------------- -- Note that locks of activator and activated task are both locked here. -- This is necessary because C.Common.State and Self.Common.Wait_Count have -- to be synchronized. This is safe from deadlock because the activator is -- always created before the activated task. That satisfies our -- in-order-of-creation ATCB locking policy. -- At one point, we may also lock the parent, if the parent is different -- from the activator. That is also consistent with the lock ordering -- policy, since the activator cannot be created before the parent. -- Since we are holding both the activator's lock, and Task_Wrapper locks -- that before it does anything more than initialize the low-level ATCB -- components, it should be safe to wait to update the counts until we see -- that the thread creation is successful. -- If the thread creation fails, we do need to close the entries of the -- task. The first phase, of dequeuing calls, only requires locking the -- acceptor's ATCB, but the waking up of the callers requires locking the -- caller's ATCB. We cannot safely do this while we are holding other -- locks. Therefore, the queue-clearing operation is done in a separate -- pass over the activation chain. procedure Activate_Tasks (Chain_Access : Activation_Chain_Access) is Self_ID : constant Task_Id := STPO.Self; P : Task_Id; C : Task_Id; Next_C, Last_C : Task_Id; Activate_Prio : System.Any_Priority; Success : Boolean; All_Elaborated : Boolean := True; begin -- If pragma Detect_Blocking is active, then we must check whether 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; pragma Debug (Debug.Trace (Self_ID, "Activate_Tasks", 'C')); Initialization.Defer_Abort_Nestable (Self_ID); pragma Assert (Self_ID.Common.Wait_Count = 0); -- Lock RTS_Lock, to prevent activated tasks from racing ahead before -- we finish activating the chain. Lock_RTS; -- Check that all task bodies have been elaborated C := Chain_Access.T_ID; Last_C := null; while C /= null loop if C.Common.Elaborated /= null and then not C.Common.Elaborated.all then All_Elaborated := False; end if; -- Reverse the activation chain so that tasks are activated in the -- same order they're declared. Next_C := C.Common.Activation_Link; C.Common.Activation_Link := Last_C; Last_C := C; C := Next_C; end loop; Chain_Access.T_ID := Last_C; if not All_Elaborated then Unlock_RTS; Initialization.Undefer_Abort_Nestable (Self_ID); raise Program_Error with "Some tasks have not been elaborated"; end if; -- Activate all the tasks in the chain. Creation of the thread of -- control was deferred until activation. So create it now. C := Chain_Access.T_ID; while C /= null loop if C.Common.State /= Terminated then pragma Assert (C.Common.State = Unactivated); P := C.Common.Parent; Write_Lock (P); Write_Lock (C); Activate_Prio := (if C.Common.Base_Priority < Get_Priority (Self_ID) then Get_Priority (Self_ID) else C.Common.Base_Priority); System.Task_Primitives.Operations.Create_Task (C, Task_Wrapper'Address, Parameters.Size_Type (C.Common.Compiler_Data.Pri_Stack_Info.Size), Activate_Prio, Success); -- There would be a race between the created task and the creator -- to do the following initialization, if we did not have a -- Lock/Unlock_RTS pair in the task wrapper to prevent it from -- racing ahead. if Success then C.Common.State := Activating; C.Awake_Count := 1; C.Alive_Count := 1; P.Awake_Count := P.Awake_Count + 1; P.Alive_Count := P.Alive_Count + 1; if P.Common.State = Master_Completion_Sleep and then C.Master_Of_Task = P.Master_Within then pragma Assert (Self_ID /= P); P.Common.Wait_Count := P.Common.Wait_Count + 1; end if; for J in System.Tasking.Debug.Known_Tasks'Range loop if System.Tasking.Debug.Known_Tasks (J) = null then System.Tasking.Debug.Known_Tasks (J) := C; C.Known_Tasks_Index := J; exit; end if; end loop; if Global_Task_Debug_Event_Set then Debug.Signal_Debug_Event (Debug.Debug_Event_Activating, C); end if; C.Common.State := Runnable; Unlock (C); Unlock (P); else -- No need to set Awake_Count, State, etc. here since the loop -- below will do that for any Unactivated tasks. Unlock (C); Unlock (P); Self_ID.Common.Activation_Failed := True; end if; end if; C := C.Common.Activation_Link; end loop; Unlock_RTS; -- Close the entries of any tasks that failed thread creation, and count -- those that have not finished activation. Write_Lock (Self_ID); Self_ID.Common.State := Activator_Sleep; C := Chain_Access.T_ID; while C /= null loop Write_Lock (C); if C.Common.State = Unactivated then C.Common.Activator := null; C.Common.State := Terminated; C.Callable := False; Utilities.Cancel_Queued_Entry_Calls (C); elsif C.Common.Activator /= null then Self_ID.Common.Wait_Count := Self_ID.Common.Wait_Count + 1; end if; Unlock (C); P := C.Common.Activation_Link; C.Common.Activation_Link := null; C := P; end loop; -- Wait for the activated tasks to complete activation. It is -- unsafe to abort any of these tasks until the count goes to zero. loop exit when Self_ID.Common.Wait_Count = 0; Sleep (Self_ID, Activator_Sleep); end loop; Self_ID.Common.State := Runnable; Unlock (Self_ID); -- Remove the tasks from the chain Chain_Access.T_ID := null; Initialization.Undefer_Abort_Nestable (Self_ID); if Self_ID.Common.Activation_Failed then Self_ID.Common.Activation_Failed := False; raise Tasking_Error with "Failure during activation"; end if; end Activate_Tasks; ------------------------- -- Complete_Activation -- ------------------------- procedure Complete_Activation is Self_ID : constant Task_Id := STPO.Self; begin Initialization.Defer_Abort_Nestable (Self_ID); Vulnerable_Complete_Activation (Self_ID); Initialization.Undefer_Abort_Nestable (Self_ID); -- ??? Why do we need to allow for nested deferral here? end Complete_Activation; --------------------- -- Complete_Master -- --------------------- procedure Complete_Master is Self_ID : constant Task_Id := STPO.Self; begin pragma Assert (Self_ID.Deferral_Level > 0 or else not System.Restrictions.Abort_Allowed); Vulnerable_Complete_Master (Self_ID); end Complete_Master; ------------------- -- Complete_Task -- ------------------- -- See comments on Vulnerable_Complete_Task for details procedure Complete_Task is Self_ID : constant Task_Id := STPO.Self; begin pragma Assert (Self_ID.Deferral_Level > 0 or else not System.Restrictions.Abort_Allowed); Vulnerable_Complete_Task (Self_ID); -- All of our dependents have terminated, never undefer abort again end Complete_Task; ----------------- -- Create_Task -- ----------------- -- Compiler interface only. Do not call from within the RTS. This must be -- called to create a new task. procedure Create_Task (Priority : Integer; Stack_Size : System.Parameters.Size_Type; Secondary_Stack_Size : System.Parameters.Size_Type; Task_Info : System.Task_Info.Task_Info_Type; CPU : Integer; Relative_Deadline : Ada.Real_Time.Time_Span; Domain : Dispatching_Domain_Access; Num_Entries : Task_Entry_Index; Master : Master_Level; State : Task_Procedure_Access; Discriminants : System.Address; Elaborated : Access_Boolean; Chain : in out Activation_Chain; Task_Image : String; Created_Task : out Task_Id) is T, P : Task_Id; Self_ID : constant Task_Id := STPO.Self; Success : Boolean; Base_Priority : System.Any_Priority; Len : Natural; Base_CPU : System.Multiprocessors.CPU_Range; use type System.Multiprocessors.CPU_Range; pragma Unreferenced (Relative_Deadline); -- EDF scheduling is not supported by any of the target platforms so -- this parameter is not passed any further. begin -- If Master is greater than the current master, it means that Master -- has already awaited its dependent tasks. This raises Program_Error, -- by 4.8(10.3/2). See AI-280. Ignore this check for foreign threads. if Self_ID.Master_Of_Task /= Foreign_Task_Level and then Master > Self_ID.Master_Within then raise Program_Error with "create task after awaiting termination"; end if; -- If pragma Detect_Blocking is active must be checked whether 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; pragma Debug (Debug.Trace (Self_ID, "Create_Task", 'C')); Base_Priority := (if Priority = Unspecified_Priority then Self_ID.Common.Base_Priority else System.Any_Priority (Priority)); -- Legal values of CPU are the special Unspecified_CPU value which is -- inserted by the compiler for tasks without CPU aspect, and those in -- the range of CPU_Range but no greater than Number_Of_CPUs. Otherwise -- the task is defined to have failed, and it becomes a completed task -- (RM D.16(14/3)). if CPU /= Unspecified_CPU and then (CPU < Integer (System.Multiprocessors.CPU_Range'First) or else CPU > Integer (System.Multiprocessors.Number_Of_CPUs)) then raise Tasking_Error with "CPU not in range"; -- Normal CPU affinity else -- When the application code says nothing about the task affinity -- (task without CPU aspect) then the compiler inserts the value -- Unspecified_CPU which indicates to the run-time library that -- the task will activate and execute on the same processor as its -- activating task if the activating task is assigned a processor -- (RM D.16(14/3)). Base_CPU := (if CPU = Unspecified_CPU then Self_ID.Common.Base_CPU else System.Multiprocessors.CPU_Range (CPU)); end if; -- Find parent P of new Task, via master level number. Independent -- tasks should have Parent = Environment_Task, and all tasks created -- by independent tasks are also independent. See, for example, -- s-interr.adb, where Interrupt_Manager does "new Server_Task". The -- access type is at library level, so the parent of the Server_Task -- is Environment_Task. P := Self_ID; if P.Master_Of_Task <= Independent_Task_Level then P := Environment_Task; else while P /= null and then P.Master_Of_Task >= Master loop P := P.Common.Parent; end loop; end if; Initialization.Defer_Abort_Nestable (Self_ID); begin T := New_ATCB (Num_Entries); exception when others => Initialization.Undefer_Abort_Nestable (Self_ID); raise Storage_Error with "Cannot allocate task"; end; -- RTS_Lock is used by Abort_Dependents and Abort_Tasks. Up to this -- point, it is possible that we may be part of a family of tasks that -- is being aborted. Lock_RTS; Write_Lock (Self_ID); -- Now, we must check that we have not been aborted. If so, we should -- give up on creating this task, and simply return. if not Self_ID.Callable then pragma Assert (Self_ID.Pending_ATC_Level = Level_Completed_Task); pragma Assert (Self_ID.Pending_Action); pragma Assert (Chain.T_ID = null or else Chain.T_ID.Common.State = Unactivated); Unlock (Self_ID); Unlock_RTS; Initialization.Undefer_Abort_Nestable (Self_ID); -- ??? Should never get here pragma Assert (Standard.False); raise Standard'Abort_Signal; end if; Initialize_ATCB (Self_ID, State, Discriminants, P, Elaborated, Base_Priority, Base_CPU, Domain, Task_Info, Stack_Size, T, Success); if not Success then Free (T); Unlock (Self_ID); Unlock_RTS; Initialization.Undefer_Abort_Nestable (Self_ID); raise Storage_Error with "Failed to initialize task"; end if; if Master = Foreign_Task_Level + 2 then -- This should not happen, except when a foreign task creates non -- library-level Ada tasks. In this case, we pretend the master is -- a regular library level task, otherwise the run-time will get -- confused when waiting for these tasks to terminate. T.Master_Of_Task := Library_Task_Level; else T.Master_Of_Task := Master; end if; T.Master_Within := T.Master_Of_Task + 1; for L in T.Entry_Calls'Range loop T.Entry_Calls (L).Self := T; T.Entry_Calls (L).Level := L; end loop; if Task_Image'Length = 0 then T.Common.Task_Image_Len := 0; else Len := 1; T.Common.Task_Image (1) := Task_Image (Task_Image'First); -- Remove unwanted blank space generated by 'Image for J in Task_Image'First + 1 .. Task_Image'Last loop if Task_Image (J) /= ' ' or else Task_Image (J - 1) /= '(' then Len := Len + 1; T.Common.Task_Image (Len) := Task_Image (J); exit when Len = T.Common.Task_Image'Last; end if; end loop; T.Common.Task_Image_Len := Len; end if; -- Note: we used to have code here to initialize T.Common.Domain, but -- that is not needed, since this is initialized in System.Tasking. Unlock (Self_ID); Unlock_RTS; -- The CPU associated to the task (if any) must belong to the -- dispatching domain. if Base_CPU /= System.Multiprocessors.Not_A_Specific_CPU and then (Base_CPU not in T.Common.Domain'Range or else not T.Common.Domain (Base_CPU)) then Initialization.Undefer_Abort_Nestable (Self_ID); raise Tasking_Error with "CPU not in dispatching domain"; end if; -- To handle the interaction between pragma CPU and dispatching domains -- we need to signal that this task is being allocated to a processor. -- This is needed only for tasks belonging to the system domain (the -- creation of new dispatching domains can only take processors from the -- system domain) and only before the environment task calls the main -- procedure (dispatching domains cannot be created after this). if Base_CPU /= System.Multiprocessors.Not_A_Specific_CPU and then T.Common.Domain = System.Tasking.System_Domain and then not System.Tasking.Dispatching_Domains_Frozen then -- Increase the number of tasks attached to the CPU to which this -- task is being moved. Dispatching_Domain_Tasks (Base_CPU) := Dispatching_Domain_Tasks (Base_CPU) + 1; end if; -- Create the secondary stack for the task as early as possible during -- in the creation of a task, since it may be used by the operation of -- Ada code within the task. begin SSL.Create_TSD (T.Common.Compiler_Data, null, Secondary_Stack_Size); exception when others => Initialization.Undefer_Abort_Nestable (Self_ID); raise Storage_Error with "Secondary stack could not be allocated"; end; T.Common.Activation_Link := Chain.T_ID; Chain.T_ID := T; Created_Task := T; Initialization.Undefer_Abort_Nestable (Self_ID); pragma Debug (Debug.Trace (Self_ID, "Created task in " & T.Master_Of_Task'Img, 'C', T)); end Create_Task; -------------------- -- Current_Master -- -------------------- function Current_Master return Master_Level is begin return STPO.Self.Master_Within; end Current_Master; ------------------ -- Enter_Master -- ------------------ procedure Enter_Master is Self_ID : constant Task_Id := STPO.Self; begin Self_ID.Master_Within := Self_ID.Master_Within + 1; pragma Debug (Debug.Trace (Self_ID, "Enter_Master ->" & Self_ID.Master_Within'Img, 'M')); end Enter_Master; ------------------------------- -- Expunge_Unactivated_Tasks -- ------------------------------- -- See procedure Close_Entries for the general case procedure Expunge_Unactivated_Tasks (Chain : in out Activation_Chain) is Self_ID : constant Task_Id := STPO.Self; C : Task_Id; Call : Entry_Call_Link; Temp : Task_Id; begin pragma Debug (Debug.Trace (Self_ID, "Expunge_Unactivated_Tasks", 'C')); Initialization.Defer_Abort_Nestable (Self_ID); -- ??? -- Experimentation has shown that abort is sometimes (but not always) -- already deferred when this is called. -- That may indicate an error. Find out what is going on C := Chain.T_ID; while C /= null loop pragma Assert (C.Common.State = Unactivated); Temp := C.Common.Activation_Link; if C.Common.State = Unactivated then Lock_RTS; Write_Lock (C); for J in 1 .. C.Entry_Num loop Queuing.Dequeue_Head (C.Entry_Queues (J), Call); pragma Assert (Call = null); end loop; Unlock (C); Initialization.Remove_From_All_Tasks_List (C); Unlock_RTS; Vulnerable_Free_Task (C); C := Temp; end if; end loop; Chain.T_ID := null; Initialization.Undefer_Abort_Nestable (Self_ID); end Expunge_Unactivated_Tasks; --------------------------- -- Finalize_Global_Tasks -- --------------------------- -- ??? -- We have a potential problem here if finalization of global objects does -- anything with signals or the timer server, since by that time those -- servers have terminated. -- It is hard to see how that would occur -- However, a better solution might be to do all this finalization -- using the global finalization chain. procedure Finalize_Global_Tasks is Self_ID : constant Task_Id := STPO.Self; Ignore_1 : Boolean; Ignore_2 : Boolean; function State (Int : System.Interrupt_Management.Interrupt_ID) return Character; pragma Import (C, State, "__gnat_get_interrupt_state"); -- Get interrupt state for interrupt number Int. Defined in init.c Default : constant Character := 's'; -- 's' Interrupt_State pragma set state to System (use "default" -- system handler) begin if Self_ID.Deferral_Level = 0 then -- ??? -- In principle, we should be able to predict whether abort is -- already deferred here (and it should not be deferred yet but in -- practice it seems Finalize_Global_Tasks is being called sometimes, -- from RTS code for exceptions, with abort already deferred. Initialization.Defer_Abort_Nestable (Self_ID); -- Never undefer again end if; -- This code is only executed by the environment task pragma Assert (Self_ID = Environment_Task); -- Set Environment_Task'Callable to false to notify library-level tasks -- that it is waiting for them. Self_ID.Callable := False; -- Exit level 2 master, for normal tasks in library-level packages Complete_Master; -- Force termination of "independent" library-level server tasks Lock_RTS; Abort_Dependents (Self_ID); Unlock_RTS; -- We need to explicitly wait for the task to be terminated here -- because on true concurrent system, we may end this procedure before -- the tasks are really terminated. Write_Lock (Self_ID); -- If the Abort_Task signal is set to system, it means that we may -- not have been able to abort all independent tasks (in particular, -- Server_Task may be blocked, waiting for a signal), in which case, do -- not wait for Independent_Task_Count to go down to 0. We arbitrarily -- limit the number of loop iterations; if an independent task does not -- terminate, we do not want to hang here. In that case, the thread will -- be terminated when the process exits. if State (System.Interrupt_Management.Abort_Task_Interrupt) /= Default then for J in 1 .. 10 loop exit when Utilities.Independent_Task_Count = 0; -- We used to yield here, but this did not take into account low -- priority tasks that would cause dead lock in some cases (true -- FIFO scheduling). Timed_Sleep (Self_ID, 0.01, System.OS_Primitives.Relative, Self_ID.Common.State, Ignore_1, Ignore_2); end loop; end if; -- ??? On multi-processor environments, it seems that the above loop -- isn't sufficient, so we need to add an additional delay. Timed_Sleep (Self_ID, 0.01, System.OS_Primitives.Relative, Self_ID.Common.State, Ignore_1, Ignore_2); Unlock (Self_ID); -- Complete the environment task Vulnerable_Complete_Task (Self_ID); -- Handle normal task termination by the environment task, but only -- for the normal task termination. In the case of Abnormal and -- Unhandled_Exception they must have been handled before, and the -- task termination soft link must have been changed so the task -- termination routine is not executed twice. SSL.Task_Termination_Handler.all (Ada.Exceptions.Null_Occurrence); -- Finalize all library-level controlled objects if not SSL."=" (SSL.Finalize_Library_Objects, null) then SSL.Finalize_Library_Objects.all; end if; -- Reset the soft links to non-tasking SSL.Abort_Defer := SSL.Abort_Defer_NT'Access; SSL.Abort_Undefer := SSL.Abort_Undefer_NT'Access; SSL.Lock_Task := SSL.Task_Lock_NT'Access; SSL.Unlock_Task := SSL.Task_Unlock_NT'Access; SSL.Get_Jmpbuf_Address := SSL.Get_Jmpbuf_Address_NT'Access; SSL.Set_Jmpbuf_Address := SSL.Set_Jmpbuf_Address_NT'Access; SSL.Get_Sec_Stack := SSL.Get_Sec_Stack_NT'Access; SSL.Set_Sec_Stack := SSL.Set_Sec_Stack_NT'Access; SSL.Check_Abort_Status := SSL.Check_Abort_Status_NT'Access; SSL.Get_Stack_Info := SSL.Get_Stack_Info_NT'Access; -- Don't bother trying to finalize Initialization.Global_Task_Lock -- and System.Task_Primitives.RTS_Lock. end Finalize_Global_Tasks; --------------- -- Free_Task -- --------------- procedure Free_Task (T : Task_Id) is Self_Id : constant Task_Id := Self; begin Initialization.Task_Lock (Self_Id); if T.Common.State = Terminated then -- It is not safe to call Abort_Defer or Write_Lock at this stage Lock_RTS; Initialization.Finalize_Attributes (T); Initialization.Remove_From_All_Tasks_List (T); Unlock_RTS; Initialization.Task_Unlock (Self_Id); System.Task_Primitives.Operations.Finalize_TCB (T); else -- If the task is not terminated, then mark the task as to be freed -- upon termination. T.Free_On_Termination := True; Initialization.Task_Unlock (Self_Id); end if; end Free_Task; --------------------------- -- Move_Activation_Chain -- --------------------------- procedure Move_Activation_Chain (From, To : Activation_Chain_Access; New_Master : Master_ID) is Self_ID : constant Task_Id := STPO.Self; C : Task_Id; begin pragma Debug (Debug.Trace (Self_ID, "Move_Activation_Chain", 'C')); -- Nothing to do if From is empty, and we can check that without -- deferring aborts. C := From.all.T_ID; if C = null then return; end if; Initialization.Defer_Abort_Nestable (Self_ID); -- Loop through the From chain, changing their Master_Of_Task fields, -- and to find the end of the chain. loop C.Master_Of_Task := New_Master; exit when C.Common.Activation_Link = null; C := C.Common.Activation_Link; end loop; -- Hook From in at the start of To C.Common.Activation_Link := To.all.T_ID; To.all.T_ID := From.all.T_ID; -- Set From to empty From.all.T_ID := null; Initialization.Undefer_Abort_Nestable (Self_ID); end Move_Activation_Chain; ------------------ -- Task_Wrapper -- ------------------ -- The task wrapper is a procedure that is called first for each task body -- and which in turn calls the compiler-generated task body procedure. -- The wrapper's main job is to do initialization for the task. It also -- has some locally declared objects that serve as per-task local data. -- Task finalization is done by Complete_Task, which is called from an -- at-end handler that the compiler generates. procedure Task_Wrapper (Self_ID : Task_Id) is use System.Standard_Library; use System.Stack_Usage; Bottom_Of_Stack : aliased Integer; Task_Alternate_Stack : aliased SSE.Storage_Array (1 .. Alternate_Stack_Size); -- The alternate signal stack for this task, if any Use_Alternate_Stack : constant Boolean := Alternate_Stack_Size /= 0; -- Whether to use above alternate signal stack for stack overflows SEH_Table : aliased SSE.Storage_Array (1 .. 8); -- Structured Exception Registration table (2 words) procedure Install_SEH_Handler (Addr : System.Address); pragma Import (C, Install_SEH_Handler, "__gnat_install_SEH_handler"); -- Install the SEH (Structured Exception Handling) handler Cause : Cause_Of_Termination := Normal; -- Indicates the reason why this task terminates. Normal corresponds to -- a task terminating due to completing the last statement of its body, -- or as a result of waiting on a terminate alternative. If the task -- terminates because it is being aborted then Cause will be set -- to Abnormal. If the task terminates because of an exception -- raised by the execution of its task body, then Cause is set -- to Unhandled_Exception. EO : Exception_Occurrence; -- If the task terminates because of an exception raised by the -- execution of its task body, then EO will contain the associated -- exception occurrence. Otherwise, it will contain Null_Occurrence. TH : Termination_Handler := null; -- Pointer to the protected procedure to be executed upon task -- termination. procedure Search_Fall_Back_Handler (ID : Task_Id); -- Procedure that searches recursively a fall-back handler through the -- master relationship. If the handler is found, its pointer is stored -- in TH. It stops when the handler is found or when the ID is null. ------------------------------ -- Search_Fall_Back_Handler -- ------------------------------ procedure Search_Fall_Back_Handler (ID : Task_Id) is begin -- A null Task_Id indicates that we have reached the root of the -- task hierarchy and no handler has been found. if ID = null then return; -- If there is a fall back handler, store its pointer for later -- execution. elsif ID.Common.Fall_Back_Handler /= null then TH := ID.Common.Fall_Back_Handler; -- Otherwise look for a fall back handler in the parent else Search_Fall_Back_Handler (ID.Common.Parent); end if; end Search_Fall_Back_Handler; -- Start of processing for Task_Wrapper begin pragma Assert (Self_ID.Deferral_Level = 1); Debug.Master_Hook (Self_ID, Self_ID.Common.Parent, Self_ID.Master_Of_Task); if Use_Alternate_Stack then Self_ID.Common.Task_Alternate_Stack := Task_Alternate_Stack'Address; end if; -- Set the guard page at the bottom of the stack. The call to unprotect -- the page is done in Terminate_Task Stack_Guard (Self_ID, True); -- Initialize low-level TCB components, that cannot be initialized by -- the creator. Enter_Task sets Self_ID.LL.Thread. Enter_Task (Self_ID); -- Initialize dynamic stack usage if System.Stack_Usage.Is_Enabled then declare Guard_Page_Size : constant := 16 * 1024; -- Part of the stack used as a guard page. This is an OS dependent -- value, so we need to use the maximum. This value is only used -- when the stack address is known, that is currently Windows. Small_Overflow_Guard : constant := 12 * 1024; -- Note: this used to be 4K, but was changed to 12K, since -- smaller values resulted in segmentation faults from dynamic -- stack analysis. Big_Overflow_Guard : constant := 64 * 1024 + 8 * 1024; -- These two values are experimental, and seem to work on most -- platforms. They still need to be analyzed further. They also -- need documentation, what are they and why does the logic differ -- depending on whether the stack is large or small??? Pattern_Size : Natural := Natural (Self_ID.Common. Compiler_Data.Pri_Stack_Info.Size); -- Size of the pattern Stack_Base : Address; -- Address of the base of the stack begin Stack_Base := Self_ID.Common.Compiler_Data.Pri_Stack_Info.Base; if Stack_Base = Null_Address then -- On many platforms, we don't know the real stack base -- address. Estimate it using an address in the frame. Stack_Base := Bottom_Of_Stack'Address; -- Adjustments for inner frames Pattern_Size := Pattern_Size - (if Pattern_Size < Big_Overflow_Guard then Small_Overflow_Guard else Big_Overflow_Guard); else -- Reduce by the size of the final guard page Pattern_Size := Pattern_Size - Guard_Page_Size; end if; STPO.Lock_RTS; Initialize_Analyzer (Self_ID.Common.Analyzer, Self_ID.Common.Task_Image (1 .. Self_ID.Common.Task_Image_Len), Natural (Self_ID.Common.Compiler_Data.Pri_Stack_Info.Size), SSE.To_Integer (Stack_Base), Pattern_Size); STPO.Unlock_RTS; Fill_Stack (Self_ID.Common.Analyzer); end; end if; -- We setup the SEH (Structured Exception Handling) handler if supported -- on the target. Install_SEH_Handler (SEH_Table'Address); -- Initialize exception occurrence Save_Occurrence (EO, Ada.Exceptions.Null_Occurrence); -- We lock RTS_Lock to wait for activator to finish activating the rest -- of the chain, so that everyone in the chain comes out in priority -- order. -- This also protects the value of -- Self_ID.Common.Activator.Common.Wait_Count. Lock_RTS; Unlock_RTS; if not System.Restrictions.Abort_Allowed then -- If Abort is not allowed, reset the deferral level since it will -- not get changed by the generated code. Keeping a default value -- of one would prevent some operations (e.g. select or delay) to -- proceed successfully. Self_ID.Deferral_Level := 0; end if; if Global_Task_Debug_Event_Set then Debug.Signal_Debug_Event (Debug.Debug_Event_Run, Self_ID); end if; declare use Ada.Task_Initialization; Global_Initialization_Handler : Initialization_Handler; pragma Atomic (Global_Initialization_Handler); pragma Import (Ada, Global_Initialization_Handler, "__gnat_global_initialization_handler"); begin -- We are separating the following portion of the code in order to -- place the exception handlers in a different block. In this way, -- we do not call Set_Jmpbuf_Address (which needs Self) before we -- set Self in Enter_Task -- Call the initialization hook if any if Global_Initialization_Handler /= null then Global_Initialization_Handler.all; end if; -- Call the task body procedure -- The task body is called with abort still deferred. That -- eliminates a dangerous window, for which we had to patch-up in -- Terminate_Task. -- During the expansion of the task body, we insert an RTS-call -- to Abort_Undefer, at the first point where abort should be -- allowed. Self_ID.Common.Task_Entry_Point (Self_ID.Common.Task_Arg); Initialization.Defer_Abort_Nestable (Self_ID); exception -- We can't call Terminate_Task in the exception handlers below, -- since there may be (e.g. in the case of GCC exception handling) -- clean ups associated with the exception handler that need to -- access task specific data. -- Defer abort so that this task can't be aborted while exiting when Standard'Abort_Signal => Initialization.Defer_Abort_Nestable (Self_ID); -- Update the cause that motivated the task termination so that -- the appropriate information is passed to the task termination -- procedure. Task termination as a result of waiting on a -- terminate alternative is a normal termination, although it is -- implemented using the abort mechanisms. if Self_ID.Terminate_Alternative then Cause := Normal; if Global_Task_Debug_Event_Set then Debug.Signal_Debug_Event (Debug.Debug_Event_Terminated, Self_ID); end if; else Cause := Abnormal; if Global_Task_Debug_Event_Set then Debug.Signal_Debug_Event (Debug.Debug_Event_Abort_Terminated, Self_ID); end if; end if; when others => -- ??? Using an E : others here causes CD2C11A to fail on Tru64 Initialization.Defer_Abort_Nestable (Self_ID); -- Perform the task specific exception tracing duty. We handle -- these outputs here and not in the common notification routine -- because we need access to tasking related data and we don't -- want to drag dependencies against tasking related units in the -- the common notification units. Additionally, no trace is ever -- triggered from the common routine for the Unhandled_Raise case -- in tasks, since an exception never appears unhandled in this -- context because of this handler. if Exception_Trace = Unhandled_Raise then Trace_Unhandled_Exception_In_Task (Self_ID); end if; -- Update the cause that motivated the task termination so that -- the appropriate information is passed to the task termination -- procedure, as well as the associated Exception_Occurrence. Cause := Unhandled_Exception; Save_Occurrence (EO, SSL.Get_Current_Excep.all.all); if Global_Task_Debug_Event_Set then Debug.Signal_Debug_Event (Debug.Debug_Event_Exception_Terminated, Self_ID); end if; end; -- Look for a task termination handler. This code is for all tasks but -- the environment task. The task termination code for the environment -- task is executed by SSL.Task_Termination_Handler. Write_Lock (Self_ID); if Self_ID.Common.Specific_Handler /= null then TH := Self_ID.Common.Specific_Handler; -- Independent tasks should not call the Fall_Back_Handler (of the -- environment task), because they are implementation artifacts that -- should be invisible to Ada programs. elsif Self_ID.Master_Of_Task /= Independent_Task_Level then -- Look for a fall-back handler following the master relationship -- for the task. As specified in ARM C.7.3 par. 9/2, "the fall-back -- handler applies only to the dependent tasks of the task". Hence, -- if the terminating tasks (Self_ID) had a fall-back handler, it -- would not apply to itself, so we start the search with the parent. Search_Fall_Back_Handler (Self_ID.Common.Parent); end if; Unlock (Self_ID); -- Execute the task termination handler if we found it if TH /= null then begin TH.all (Cause, Self_ID, EO); exception -- RM-C.7.3 requires all exceptions raised here to be ignored when others => null; end; end if; if System.Stack_Usage.Is_Enabled then Compute_Result (Self_ID.Common.Analyzer); Report_Result (Self_ID.Common.Analyzer); end if; Terminate_Task (Self_ID); end Task_Wrapper; -------------------- -- Terminate_Task -- -------------------- -- Before we allow the thread to exit, we must clean up. This is a delicate -- job. We must wake up the task's master, who may immediately try to -- deallocate the ATCB from the current task WHILE IT IS STILL EXECUTING. -- To avoid this, the parent task must be blocked up to the latest -- statement executed. The trouble is that we have another step that we -- also want to postpone to the very end, i.e., calling SSL.Destroy_TSD. -- We have to postpone that until the end because compiler-generated code -- is likely to try to access that data at just about any point. -- We can't call Destroy_TSD while we are holding any other locks, because -- it locks Global_Task_Lock, and our deadlock prevention rules require -- that to be the outermost lock. Our first "solution" was to just lock -- Global_Task_Lock in addition to the other locks, and force the parent to -- also lock this lock between its wakeup and its freeing of the ATCB. See -- Complete_Task for the parent-side of the code that has the matching -- calls to Task_Lock and Task_Unlock. That was not really a solution, -- since the operation Task_Unlock continued to access the ATCB after -- unlocking, after which the parent was observed to race ahead, deallocate -- the ATCB, and then reallocate it to another task. The call to -- Undefer_Abort in Task_Unlock by the "terminated" task was overwriting -- the data of the new task that reused the ATCB. To solve this problem, we -- introduced the new operation Final_Task_Unlock. procedure Terminate_Task (Self_ID : Task_Id) is Environment_Task : constant Task_Id := STPO.Environment_Task; Master_Of_Task : Integer; Deallocate : Boolean; begin Debug.Task_Termination_Hook; -- Since GCC cannot allocate stack chunks efficiently without reordering -- some of the allocations, we have to handle this unexpected situation -- here. Normally we never have to call Vulnerable_Complete_Task here. if Self_ID.Common.Activator /= null then Vulnerable_Complete_Task (Self_ID); end if; Initialization.Task_Lock (Self_ID); Master_Of_Task := Self_ID.Master_Of_Task; -- Check if the current task is an independent task If so, decrement -- the Independent_Task_Count value. if Master_Of_Task = Independent_Task_Level then Write_Lock (Environment_Task); Utilities.Independent_Task_Count := Utilities.Independent_Task_Count - 1; Unlock (Environment_Task); end if; -- Unprotect the guard page if needed Stack_Guard (Self_ID, False); Utilities.Make_Passive (Self_ID, Task_Completed => True); Deallocate := Self_ID.Free_On_Termination; pragma Assert (Check_Exit (Self_ID)); SSL.Destroy_TSD (Self_ID.Common.Compiler_Data); Initialization.Final_Task_Unlock (Self_ID); -- WARNING: past this point, this thread must assume that the ATCB has -- been deallocated, and can't access it anymore (which is why we have -- saved the Free_On_Termination flag in a temporary variable). if Deallocate then Free_Task (Self_ID); end if; if Master_Of_Task > 0 then STPO.Exit_Task; end if; end Terminate_Task; ---------------- -- Terminated -- ---------------- function Terminated (T : Task_Id) return Boolean is Self_ID : constant Task_Id := STPO.Self; Result : Boolean; begin Initialization.Defer_Abort_Nestable (Self_ID); Write_Lock (T); Result := T.Common.State = Terminated; Unlock (T); Initialization.Undefer_Abort_Nestable (Self_ID); return Result; end Terminated; ---------------------------------------- -- Trace_Unhandled_Exception_In_Task -- ---------------------------------------- procedure Trace_Unhandled_Exception_In_Task (Self_Id : Task_Id) is procedure To_Stderr (S : String); pragma Import (Ada, To_Stderr, "__gnat_to_stderr"); use System.Soft_Links; function To_Address is new Ada.Unchecked_Conversion (Task_Id, System.Task_Primitives.Task_Address); Excep : constant Exception_Occurrence_Access := SSL.Get_Current_Excep.all; begin -- This procedure is called by the task outermost handler in -- Task_Wrapper below, so only once the task stack has been fully -- unwound. The common notification routine has been called at the -- raise point already. -- Lock to prevent unsynchronized output Initialization.Task_Lock (Self_Id); To_Stderr ("task "); if Self_Id.Common.Task_Image_Len /= 0 then To_Stderr (Self_Id.Common.Task_Image (1 .. Self_Id.Common.Task_Image_Len)); To_Stderr ("_"); end if; To_Stderr (System.Address_Image (To_Address (Self_Id))); To_Stderr (" terminated by unhandled exception"); To_Stderr ([ASCII.LF]); To_Stderr (Exception_Information (Excep.all)); Initialization.Task_Unlock (Self_Id); end Trace_Unhandled_Exception_In_Task; ------------------------------------ -- Vulnerable_Complete_Activation -- ------------------------------------ -- As in several other places, the locks of the activator and activated -- task are both locked here. This follows our deadlock prevention lock -- ordering policy, since the activated task must be created after the -- activator. procedure Vulnerable_Complete_Activation (Self_ID : Task_Id) is Activator : constant Task_Id := Self_ID.Common.Activator; begin pragma Debug (Debug.Trace (Self_ID, "V_Complete_Activation", 'C')); Write_Lock (Activator); Write_Lock (Self_ID); pragma Assert (Self_ID.Common.Activator /= null); -- Remove dangling reference to Activator, since a task may outlive its -- activator. Self_ID.Common.Activator := null; -- Wake up the activator, if it is waiting for a chain of tasks to -- activate, and we are the last in the chain to complete activation. if Activator.Common.State = Activator_Sleep then Activator.Common.Wait_Count := Activator.Common.Wait_Count - 1; if Activator.Common.Wait_Count = 0 then Wakeup (Activator, Activator_Sleep); end if; end if; -- The activator raises a Tasking_Error if any task it is activating -- is completed before the activation is done. However, if the reason -- for the task completion is an abort, we do not raise an exception. -- See RM 9.2(5). if not Self_ID.Callable and then Self_ID.Pending_ATC_Level /= Level_Completed_Task then Activator.Common.Activation_Failed := True; end if; Unlock (Self_ID); Unlock (Activator); -- After the activation, active priority should be the same as base -- priority. We must unlock the Activator first, though, since it -- should not wait if we have lower priority. if Get_Priority (Self_ID) /= Self_ID.Common.Base_Priority then Write_Lock (Self_ID); Set_Priority (Self_ID, Self_ID.Common.Base_Priority); Unlock (Self_ID); end if; end Vulnerable_Complete_Activation; -------------------------------- -- Vulnerable_Complete_Master -- -------------------------------- procedure Vulnerable_Complete_Master (Self_ID : Task_Id) is C : Task_Id; P : Task_Id; CM : constant Master_Level := Self_ID.Master_Within; T : aliased Task_Id; To_Be_Freed : Task_Id; -- This is a list of ATCBs to be freed, after we have released all RTS -- locks. This is necessary because of the locking order rules, since -- the storage manager uses Global_Task_Lock. pragma Warnings (Off); function Check_Unactivated_Tasks return Boolean; pragma Warnings (On); -- Temporary error-checking code below. This is part of the checks -- added in the new run time. Call it only inside a pragma Assert. ----------------------------- -- Check_Unactivated_Tasks -- ----------------------------- function Check_Unactivated_Tasks return Boolean is begin Lock_RTS; Write_Lock (Self_ID); C := All_Tasks_List; while C /= null loop if C.Common.Activator = Self_ID and then C.Master_Of_Task = CM then return False; end if; if C.Common.Parent = Self_ID and then C.Master_Of_Task = CM then Write_Lock (C); if C.Common.State = Unactivated then return False; end if; Unlock (C); end if; C := C.Common.All_Tasks_Link; end loop; Unlock (Self_ID); Unlock_RTS; return True; end Check_Unactivated_Tasks; -- Start of processing for Vulnerable_Complete_Master begin pragma Debug (Debug.Trace (Self_ID, "V_Complete_Master(" & CM'Img & ")", 'C')); pragma Assert (Self_ID.Common.Wait_Count = 0); pragma Assert (Self_ID.Deferral_Level > 0 or else not System.Restrictions.Abort_Allowed); -- Count how many active dependent tasks this master currently has, and -- record this in Wait_Count. -- This count should start at zero, since it is initialized to zero for -- new tasks, and the task should not exit the sleep-loops that use this -- count until the count reaches zero. -- While we're counting, if we run across any unactivated tasks that -- belong to this master, we summarily terminate them as required by -- RM-9.2(6). Lock_RTS; Write_Lock (Self_ID); C := All_Tasks_List; while C /= null loop -- Terminate unactivated (never-to-be activated) tasks if C.Common.Activator = Self_ID and then C.Master_Of_Task = CM then -- Usually, C.Common.Activator = Self_ID implies C.Master_Of_Task -- = CM. The only case where C is pending activation by this -- task, but the master of C is not CM is in Ada 2005, when C is -- part of a return object of a build-in-place function. pragma Assert (C.Common.State = Unactivated); Write_Lock (C); C.Common.Activator := null; C.Common.State := Terminated; C.Callable := False; Utilities.Cancel_Queued_Entry_Calls (C); Unlock (C); end if; -- Count it if directly dependent on this master if C.Common.Parent = Self_ID and then C.Master_Of_Task = CM then Write_Lock (C); if C.Awake_Count /= 0 then Self_ID.Common.Wait_Count := Self_ID.Common.Wait_Count + 1; end if; Unlock (C); end if; C := C.Common.All_Tasks_Link; end loop; Self_ID.Common.State := Master_Completion_Sleep; Unlock (Self_ID); Unlock_RTS; -- Wait until dependent tasks are all terminated or ready to terminate. -- While waiting, the task may be awakened if the task's priority needs -- changing, or this master is aborted. In the latter case, we abort the -- dependents, and resume waiting until Wait_Count goes to zero. Write_Lock (Self_ID); loop exit when Self_ID.Common.Wait_Count = 0; -- Here is a difference as compared to Complete_Master if Self_ID.Pending_ATC_Level < Self_ID.ATC_Nesting_Level and then not Self_ID.Dependents_Aborted then Unlock (Self_ID); Lock_RTS; Abort_Dependents (Self_ID); Unlock_RTS; Write_Lock (Self_ID); else pragma Debug (Debug.Trace (Self_ID, "master_completion_sleep", 'C')); Sleep (Self_ID, Master_Completion_Sleep); end if; end loop; Self_ID.Common.State := Runnable; Unlock (Self_ID); -- Dependents are all terminated or on terminate alternatives. Now, -- force those on terminate alternatives to terminate, by aborting them. pragma Assert (Check_Unactivated_Tasks); if Self_ID.Alive_Count > 1 then -- ??? -- Consider finding a way to skip the following extra steps if there -- are no dependents with terminate alternatives. This could be done -- by adding another count to the ATCB, similar to Awake_Count, but -- keeping track of tasks that are on terminate alternatives. pragma Assert (Self_ID.Common.Wait_Count = 0); -- Force any remaining dependents to terminate by aborting them Lock_RTS; Abort_Dependents (Self_ID); -- Above, when we "abort" the dependents we are simply using this -- operation for convenience. We are not required to support the full -- abort-statement semantics; in particular, we are not required to -- immediately cancel any queued or in-service entry calls. That is -- good, because if we tried to cancel a call we would need to lock -- the caller, in order to wake the caller up. Our anti-deadlock -- rules prevent us from doing that without releasing the locks on C -- and Self_ID. Releasing and retaking those locks would be wasteful -- at best, and should not be considered further without more -- detailed analysis of potential concurrent accesses to the ATCBs -- of C and Self_ID. -- Count how many "alive" dependent tasks this master currently has, -- and record this in Wait_Count. This count should start at zero, -- since it is initialized to zero for new tasks, and the task should -- not exit the sleep-loops that use this count until the count -- reaches zero. pragma Assert (Self_ID.Common.Wait_Count = 0); Write_Lock (Self_ID); C := All_Tasks_List; while C /= null loop if C.Common.Parent = Self_ID and then C.Master_Of_Task = CM then Write_Lock (C); pragma Assert (C.Awake_Count = 0); if C.Alive_Count > 0 then pragma Assert (C.Terminate_Alternative); Self_ID.Common.Wait_Count := Self_ID.Common.Wait_Count + 1; end if; Unlock (C); end if; C := C.Common.All_Tasks_Link; end loop; Self_ID.Common.State := Master_Phase_2_Sleep; Unlock (Self_ID); Unlock_RTS; -- Wait for all counted tasks to finish terminating themselves Write_Lock (Self_ID); loop exit when Self_ID.Common.Wait_Count = 0; Sleep (Self_ID, Master_Phase_2_Sleep); end loop; Self_ID.Common.State := Runnable; Unlock (Self_ID); end if; -- We don't wake up for abort here. We are already terminating just as -- fast as we can, so there is no point. -- Remove terminated tasks from the list of Self_ID's dependents, but -- don't free their ATCBs yet, because of lock order restrictions, which -- don't allow us to call "free" or "malloc" while holding any other -- locks. Instead, we put those ATCBs to be freed onto a temporary list, -- called To_Be_Freed. Lock_RTS; C := All_Tasks_List; P := null; while C /= null loop -- If Free_On_Termination is set, do nothing here, and let the -- task free itself if not already done, otherwise we risk a race -- condition where Vulnerable_Free_Task is called in the loop below, -- while the task calls Free_Task itself, in Terminate_Task. if C.Common.Parent = Self_ID and then C.Master_Of_Task >= CM and then not C.Free_On_Termination then if P /= null then P.Common.All_Tasks_Link := C.Common.All_Tasks_Link; else All_Tasks_List := C.Common.All_Tasks_Link; end if; T := C.Common.All_Tasks_Link; C.Common.All_Tasks_Link := To_Be_Freed; To_Be_Freed := C; C := T; else P := C; C := C.Common.All_Tasks_Link; end if; end loop; Unlock_RTS; -- Free all the ATCBs on the list To_Be_Freed -- The ATCBs in the list are no longer in All_Tasks_List, and after -- any interrupt entries are detached from them they should no longer -- be referenced. -- Global_Task_Lock (Task_Lock/Unlock) is locked in the loop below to -- avoid a race between a terminating task and its parent. The parent -- might try to deallocate the ACTB out from underneath the exiting -- task. Note that Free will also lock Global_Task_Lock, but that is -- OK, since this is the *one* lock for which we have a mechanism to -- support nested locking. See Task_Wrapper and its finalizer for more -- explanation. -- ??? -- The check "T.Common.Parent /= null ..." below is to prevent dangling -- references to terminated library-level tasks, which could otherwise -- occur during finalization of library-level objects. A better solution -- might be to hook task objects into the finalization chain and -- deallocate the ATCB when the task object is deallocated. However, -- this change is not likely to gain anything significant, since all -- this storage should be recovered en-masse when the process exits. while To_Be_Freed /= null loop T := To_Be_Freed; To_Be_Freed := T.Common.All_Tasks_Link; -- ??? On SGI there is currently no Interrupt_Manager, that's why we -- need to check if the Interrupt_Manager_ID is null. if T.Interrupt_Entry and then Interrupt_Manager_ID /= null then declare Detach_Interrupt_Entries_Index : constant Task_Entry_Index := 1; -- Corresponds to the entry index of System.Interrupts. -- Interrupt_Manager.Detach_Interrupt_Entries. Be sure -- to update this value when changing Interrupt_Manager specs. type Param_Type is access all Task_Id; Param : aliased Param_Type := T'Access; begin System.Tasking.Rendezvous.Call_Simple (Interrupt_Manager_ID, Detach_Interrupt_Entries_Index, Param'Address); end; end if; if (T.Common.Parent /= null and then T.Common.Parent.Common.Parent /= null) or else T.Master_Of_Task > Library_Task_Level then Initialization.Task_Lock (Self_ID); -- If Sec_Stack_Ptr is not null, it means that Destroy_TSD -- has not been called yet (case of an unactivated task). if T.Common.Compiler_Data.Sec_Stack_Ptr /= null then SSL.Destroy_TSD (T.Common.Compiler_Data); end if; Vulnerable_Free_Task (T); Initialization.Task_Unlock (Self_ID); end if; end loop; -- It might seem nice to let the terminated task deallocate its own -- ATCB. That would not cover the case of unactivated tasks. It also -- would force us to keep the underlying thread around past termination, -- since references to the ATCB are possible past termination. -- Currently, we get rid of the thread as soon as the task terminates, -- and let the parent recover the ATCB later. -- Some day, if we want to recover the ATCB earlier, at task -- termination, we could consider using "fat task IDs", that include the -- serial number with the ATCB pointer, to catch references to tasks -- that no longer have ATCBs. It is not clear how much this would gain, -- since the user-level task object would still be occupying storage. -- Make next master level up active. We don't need to lock the ATCB, -- since the value is only updated by each task for itself. Self_ID.Master_Within := CM - 1; Debug.Master_Completed_Hook (Self_ID, CM); end Vulnerable_Complete_Master; ------------------------------ -- Vulnerable_Complete_Task -- ------------------------------ -- Complete the calling task -- This procedure must be called with abort deferred. It should only be -- called by Complete_Task and Finalize_Global_Tasks (for the environment -- task). -- The effect is similar to that of Complete_Master. Differences include -- the closing of entries here, and computation of the number of active -- dependent tasks in Complete_Master. -- We don't lock Self_ID before the call to Vulnerable_Complete_Activation, -- because that does its own locking, and because we do not need the lock -- to test Self_ID.Common.Activator. That value should only be read and -- modified by Self. procedure Vulnerable_Complete_Task (Self_ID : Task_Id) is begin pragma Assert (Self_ID.Deferral_Level > 0 or else not System.Restrictions.Abort_Allowed); pragma Assert (Self_ID = Self); pragma Assert (Self_ID.Master_Within in Self_ID.Master_Of_Task .. Self_ID.Master_Of_Task + 3); pragma Assert (Self_ID.Common.Wait_Count = 0); pragma Assert (Self_ID.Open_Accepts = null); pragma Assert (Self_ID.ATC_Nesting_Level = Level_No_ATC_Occurring); pragma Debug (Debug.Trace (Self_ID, "V_Complete_Task", 'C')); Write_Lock (Self_ID); Self_ID.Callable := False; -- In theory, Self should have no pending entry calls left on its -- call-stack. Each async. select statement should clean its own call, -- and blocking entry calls should defer abort until the calls are -- cancelled, then clean up. Utilities.Cancel_Queued_Entry_Calls (Self_ID); Unlock (Self_ID); if Self_ID.Common.Activator /= null then Vulnerable_Complete_Activation (Self_ID); end if; -- If Self_ID.Master_Within = Self_ID.Master_Of_Task + 2 we may have -- dependent tasks for which we need to wait. Otherwise we just exit. if Self_ID.Master_Within = Self_ID.Master_Of_Task + 2 then Vulnerable_Complete_Master (Self_ID); end if; end Vulnerable_Complete_Task; -------------------------- -- Vulnerable_Free_Task -- -------------------------- -- Recover all runtime system storage associated with the task T. This -- should only be called after T has terminated and will no longer be -- referenced. -- For tasks created by an allocator that fails, due to an exception, it -- is called from Expunge_Unactivated_Tasks. -- For tasks created by elaboration of task object declarations it is -- called from the finalization code of the Task_Wrapper procedure. procedure Vulnerable_Free_Task (T : Task_Id) is begin pragma Debug (Debug.Trace (Self, "Vulnerable_Free_Task", 'C', T)); Write_Lock (T); Initialization.Finalize_Attributes (T); Unlock (T); System.Task_Primitives.Operations.Finalize_TCB (T); end Vulnerable_Free_Task; -- Package elaboration code begin -- Establish the Adafinal softlink -- This is not done inside the central RTS initialization routine -- to avoid with'ing this package from System.Tasking.Initialization. SSL.Adafinal := Finalize_Global_Tasks'Access; -- Establish soft links for subprograms that manipulate master_id's. -- This cannot be done when the RTS is initialized, because of various -- elaboration constraints. SSL.Current_Master := Stages.Current_Master'Access; SSL.Enter_Master := Stages.Enter_Master'Access; SSL.Complete_Master := Stages.Complete_Master'Access; end System.Tasking.Stages;