------------------------------------------------------------------------------ -- -- -- GNAT RUN-TIME LIBRARY (GNARL) COMPONENTS -- -- -- -- S Y S T E M . T A S K I N G . R E N D E Z V O U S -- -- -- -- B o d y -- -- -- -- Copyright (C) 1992-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 -- -- . -- -- -- -- GNARL was developed by the GNARL team at Florida State University. -- -- Extensive contributions were provided by Ada Core Technologies, Inc. -- -- -- ------------------------------------------------------------------------------ with System.Task_Primitives.Operations; with System.Tasking.Entry_Calls; with System.Tasking.Initialization; with System.Tasking.Queuing; with System.Tasking.Utilities; with System.Tasking.Protected_Objects.Operations; with System.Tasking.Debug; with System.Restrictions; with System.Parameters; package body System.Tasking.Rendezvous is package STPO renames System.Task_Primitives.Operations; package POO renames Protected_Objects.Operations; package POE renames Protected_Objects.Entries; use Parameters; use Task_Primitives.Operations; type Select_Treatment is ( Accept_Alternative_Selected, -- alternative with non-null body Accept_Alternative_Completed, -- alternative with null body Else_Selected, Terminate_Selected, Accept_Alternative_Open, No_Alternative_Open); ---------------- -- Local Data -- ---------------- Default_Treatment : constant array (Select_Modes) of Select_Treatment := (Simple_Mode => No_Alternative_Open, Else_Mode => Else_Selected, Terminate_Mode => Terminate_Selected, Delay_Mode => No_Alternative_Open); New_State : constant array (Boolean, Entry_Call_State) of Entry_Call_State := (True => (Never_Abortable => Never_Abortable, Not_Yet_Abortable => Now_Abortable, Was_Abortable => Now_Abortable, Now_Abortable => Now_Abortable, Done => Done, Cancelled => Cancelled), False => (Never_Abortable => Never_Abortable, Not_Yet_Abortable => Not_Yet_Abortable, Was_Abortable => Was_Abortable, Now_Abortable => Now_Abortable, Done => Done, Cancelled => Cancelled) ); ----------------------- -- Local Subprograms -- ----------------------- procedure Local_Defer_Abort (Self_Id : Task_Id) renames System.Tasking.Initialization.Defer_Abort_Nestable; procedure Local_Undefer_Abort (Self_Id : Task_Id) renames System.Tasking.Initialization.Undefer_Abort_Nestable; -- Florist defers abort around critical sections that make entry calls -- to the Interrupt_Manager task, which violates the general rule about -- top-level runtime system calls from abort-deferred regions. It is not -- that this is unsafe, but when it occurs in "normal" programs it usually -- means either the user is trying to do a potentially blocking operation -- from within a protected object, or there is a runtime system/compiler -- error that has failed to undefer an earlier abort deferral. Thus, for -- debugging it may be wise to modify the above renamings to the -- non-nestable forms. procedure Local_Complete_Rendezvous (Ex : Ada.Exceptions.Exception_Id); -- Internal version of Complete_Rendezvous, used to implement -- Complete_Rendezvous and Exceptional_Complete_Rendezvous. -- Should be called holding no locks, generally with abort -- not yet deferred. procedure Boost_Priority (Call : Entry_Call_Link; Acceptor : Task_Id); pragma Inline (Boost_Priority); -- Call this only with abort deferred and holding lock of Acceptor procedure Call_Synchronous (Acceptor : Task_Id; E : Task_Entry_Index; Uninterpreted_Data : System.Address; Mode : Call_Modes; Rendezvous_Successful : out Boolean); pragma Inline (Call_Synchronous); -- This call is used to make a simple or conditional entry call. -- Called from Call_Simple and Task_Entry_Call. procedure Setup_For_Rendezvous_With_Body (Entry_Call : Entry_Call_Link; Acceptor : Task_Id); pragma Inline (Setup_For_Rendezvous_With_Body); -- Call this only with abort deferred and holding lock of Acceptor. When -- a rendezvous selected (ready for rendezvous) we need to save previous -- caller and adjust the priority. Also we need to make this call not -- Abortable (Cancellable) since the rendezvous has already been started. procedure Wait_For_Call (Self_Id : Task_Id); pragma Inline (Wait_For_Call); -- Call this only with abort deferred and holding lock of Self_Id. An -- accepting task goes into Sleep by calling this routine waiting for a -- call from the caller or waiting for an abort. Make sure Self_Id is -- locked before calling this routine. ----------------- -- Accept_Call -- ----------------- procedure Accept_Call (E : Task_Entry_Index; Uninterpreted_Data : out System.Address) is Self_Id : constant Task_Id := STPO.Self; Caller : Task_Id := null; Open_Accepts : aliased Accept_List (1 .. 1); Entry_Call : Entry_Call_Link; begin Initialization.Defer_Abort (Self_Id); if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (Self_Id); if not Self_Id.Callable then pragma Assert (Self_Id.Pending_ATC_Level = 0); pragma Assert (Self_Id.Pending_Action); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); -- Should never get here ??? pragma Assert (False); raise Standard'Abort_Signal; end if; Queuing.Dequeue_Head (Self_Id.Entry_Queues (E), Entry_Call); if Entry_Call /= null then Caller := Entry_Call.Self; Setup_For_Rendezvous_With_Body (Entry_Call, Self_Id); Uninterpreted_Data := Entry_Call.Uninterpreted_Data; else -- Wait for a caller Open_Accepts (1).Null_Body := False; Open_Accepts (1).S := E; Self_Id.Open_Accepts := Open_Accepts'Unrestricted_Access; -- Wait for normal call pragma Debug (Debug.Trace (Self_Id, "Accept_Call: wait", 'R')); Wait_For_Call (Self_Id); pragma Assert (Self_Id.Open_Accepts = null); if Self_Id.Common.Call /= null then Caller := Self_Id.Common.Call.Self; Uninterpreted_Data := Caller.Entry_Calls (Caller.ATC_Nesting_Level).Uninterpreted_Data; else -- Case of an aborted task Uninterpreted_Data := System.Null_Address; end if; end if; -- Self_Id.Common.Call should already be updated by the Caller. On -- return, we will start the rendezvous. STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); end Accept_Call; -------------------- -- Accept_Trivial -- -------------------- procedure Accept_Trivial (E : Task_Entry_Index) is Self_Id : constant Task_Id := STPO.Self; Caller : Task_Id := null; Open_Accepts : aliased Accept_List (1 .. 1); Entry_Call : Entry_Call_Link; begin Initialization.Defer_Abort_Nestable (Self_Id); if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (Self_Id); if not Self_Id.Callable then pragma Assert (Self_Id.Pending_ATC_Level = 0); pragma Assert (Self_Id.Pending_Action); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort_Nestable (Self_Id); -- Should never get here ??? pragma Assert (False); raise Standard'Abort_Signal; end if; Queuing.Dequeue_Head (Self_Id.Entry_Queues (E), Entry_Call); if Entry_Call = null then -- Need to wait for entry call Open_Accepts (1).Null_Body := True; Open_Accepts (1).S := E; Self_Id.Open_Accepts := Open_Accepts'Unrestricted_Access; pragma Debug (Debug.Trace (Self_Id, "Accept_Trivial: wait", 'R')); Wait_For_Call (Self_Id); pragma Assert (Self_Id.Open_Accepts = null); -- No need to do anything special here for pending abort. -- Abort_Signal will be raised by Undefer on exit. STPO.Unlock (Self_Id); -- Found caller already waiting else pragma Assert (Entry_Call.State < Done); STPO.Unlock (Self_Id); Caller := Entry_Call.Self; STPO.Write_Lock (Caller); Initialization.Wakeup_Entry_Caller (Self_Id, Entry_Call, Done); STPO.Unlock (Caller); end if; if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort_Nestable (Self_Id); end Accept_Trivial; -------------------- -- Boost_Priority -- -------------------- procedure Boost_Priority (Call : Entry_Call_Link; Acceptor : Task_Id) is Caller : constant Task_Id := Call.Self; Caller_Prio : constant System.Any_Priority := Get_Priority (Caller); Acceptor_Prio : constant System.Any_Priority := Get_Priority (Acceptor); begin if Caller_Prio > Acceptor_Prio then Call.Acceptor_Prev_Priority := Acceptor_Prio; Set_Priority (Acceptor, Caller_Prio); else Call.Acceptor_Prev_Priority := Priority_Not_Boosted; end if; end Boost_Priority; ----------------- -- Call_Simple -- ----------------- procedure Call_Simple (Acceptor : Task_Id; E : Task_Entry_Index; Uninterpreted_Data : System.Address) is Rendezvous_Successful : Boolean; pragma Unreferenced (Rendezvous_Successful); 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 STPO.Self.Common.Protected_Action_Nesting > 0 then raise Program_Error with "potentially blocking operation"; end if; Call_Synchronous (Acceptor, E, Uninterpreted_Data, Simple_Call, Rendezvous_Successful); end Call_Simple; ---------------------- -- Call_Synchronous -- ---------------------- procedure Call_Synchronous (Acceptor : Task_Id; E : Task_Entry_Index; Uninterpreted_Data : System.Address; Mode : Call_Modes; Rendezvous_Successful : out Boolean) is Self_Id : constant Task_Id := STPO.Self; Level : ATC_Level; Entry_Call : Entry_Call_Link; begin pragma Assert (Mode /= Asynchronous_Call); Local_Defer_Abort (Self_Id); Self_Id.ATC_Nesting_Level := Self_Id.ATC_Nesting_Level + 1; pragma Debug (Debug.Trace (Self_Id, "CS: entered ATC level: " & ATC_Level'Image (Self_Id.ATC_Nesting_Level), 'A')); Level := Self_Id.ATC_Nesting_Level; Entry_Call := Self_Id.Entry_Calls (Level)'Access; Entry_Call.Next := null; Entry_Call.Mode := Mode; Entry_Call.Cancellation_Attempted := False; -- If this is a call made inside of an abort deferred region, -- the call should be never abortable. Entry_Call.State := (if Self_Id.Deferral_Level > 1 then Never_Abortable else Now_Abortable); Entry_Call.E := Entry_Index (E); Entry_Call.Prio := Get_Priority (Self_Id); Entry_Call.Uninterpreted_Data := Uninterpreted_Data; Entry_Call.Called_Task := Acceptor; Entry_Call.Exception_To_Raise := Ada.Exceptions.Null_Id; Entry_Call.With_Abort := True; -- Note: the caller will undefer abort on return (see WARNING above) if Single_Lock then Lock_RTS; end if; if not Task_Do_Or_Queue (Self_Id, Entry_Call) then STPO.Write_Lock (Self_Id); Utilities.Exit_One_ATC_Level (Self_Id); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Local_Undefer_Abort (Self_Id); raise Tasking_Error; end if; STPO.Write_Lock (Self_Id); pragma Debug (Debug.Trace (Self_Id, "Call_Synchronous: wait", 'R')); Entry_Calls.Wait_For_Completion (Entry_Call); pragma Debug (Debug.Trace (Self_Id, "Call_Synchronous: done waiting", 'R')); Rendezvous_Successful := Entry_Call.State = Done; STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Local_Undefer_Abort (Self_Id); Entry_Calls.Check_Exception (Self_Id, Entry_Call); end Call_Synchronous; -------------- -- Callable -- -------------- function Callable (T : Task_Id) return Boolean is Result : Boolean; Self_Id : constant Task_Id := STPO.Self; begin Initialization.Defer_Abort_Nestable (Self_Id); if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (T); Result := T.Callable; STPO.Unlock (T); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort_Nestable (Self_Id); return Result; end Callable; ---------------------------- -- Cancel_Task_Entry_Call -- ---------------------------- procedure Cancel_Task_Entry_Call (Cancelled : out Boolean) is begin Entry_Calls.Try_To_Cancel_Entry_Call (Cancelled); end Cancel_Task_Entry_Call; ------------------------- -- Complete_Rendezvous -- ------------------------- procedure Complete_Rendezvous is begin Local_Complete_Rendezvous (Ada.Exceptions.Null_Id); end Complete_Rendezvous; ------------------------------------- -- Exceptional_Complete_Rendezvous -- ------------------------------------- procedure Exceptional_Complete_Rendezvous (Ex : Ada.Exceptions.Exception_Id) is procedure Internal_Reraise; pragma No_Return (Internal_Reraise); pragma Import (C, Internal_Reraise, "__gnat_reraise"); begin Local_Complete_Rendezvous (Ex); Internal_Reraise; -- ??? Do we need to give precedence to Program_Error that might be -- raised due to failure of finalization, over Tasking_Error from -- failure of requeue? end Exceptional_Complete_Rendezvous; ------------------------------- -- Local_Complete_Rendezvous -- ------------------------------- procedure Local_Complete_Rendezvous (Ex : Ada.Exceptions.Exception_Id) is Self_Id : constant Task_Id := STPO.Self; Entry_Call : Entry_Call_Link := Self_Id.Common.Call; Caller : Task_Id; Called_PO : STPE.Protection_Entries_Access; Acceptor_Prev_Priority : Integer; Ceiling_Violation : Boolean; use type Ada.Exceptions.Exception_Id; procedure Transfer_Occurrence (Target : Ada.Exceptions.Exception_Occurrence_Access; Source : Ada.Exceptions.Exception_Occurrence); pragma Import (C, Transfer_Occurrence, "__gnat_transfer_occurrence"); begin -- The deferral level is critical here, since we want to raise an -- exception or allow abort to take place, if there is an exception or -- abort pending. pragma Debug (Debug.Trace (Self_Id, "Local_Complete_Rendezvous", 'R')); if Ex = Ada.Exceptions.Null_Id then -- The call came from normal end-of-rendezvous, so abort is not yet -- deferred. Initialization.Defer_Abort (Self_Id); elsif ZCX_By_Default then -- With ZCX, aborts are not automatically deferred in handlers Initialization.Defer_Abort (Self_Id); end if; -- We need to clean up any accepts which Self may have been serving when -- it was aborted. if Ex = Standard'Abort_Signal'Identity then if Single_Lock then Lock_RTS; end if; while Entry_Call /= null loop Entry_Call.Exception_To_Raise := Tasking_Error'Identity; -- All forms of accept make sure that the acceptor is not -- completed, before accepting further calls, so that we -- can be sure that no further calls are made after the -- current calls are purged. Caller := Entry_Call.Self; -- Take write lock. This follows the lock precedence rule that -- Caller may be locked while holding lock of Acceptor. Complete -- the call abnormally, with exception. STPO.Write_Lock (Caller); Initialization.Wakeup_Entry_Caller (Self_Id, Entry_Call, Done); STPO.Unlock (Caller); Entry_Call := Entry_Call.Acceptor_Prev_Call; end loop; if Single_Lock then Unlock_RTS; end if; else Caller := Entry_Call.Self; if Entry_Call.Needs_Requeue then -- We dare not lock Self_Id at the same time as Caller, for fear -- of deadlock. Entry_Call.Needs_Requeue := False; Self_Id.Common.Call := Entry_Call.Acceptor_Prev_Call; if Entry_Call.Called_Task /= null then -- Requeue to another task entry if Single_Lock then Lock_RTS; end if; if not Task_Do_Or_Queue (Self_Id, Entry_Call) then if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); raise Tasking_Error; end if; if Single_Lock then Unlock_RTS; end if; else -- Requeue to a protected entry Called_PO := POE.To_Protection (Entry_Call.Called_PO); STPE.Lock_Entries_With_Status (Called_PO, Ceiling_Violation); if Ceiling_Violation then pragma Assert (Ex = Ada.Exceptions.Null_Id); Entry_Call.Exception_To_Raise := Program_Error'Identity; if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (Caller); Initialization.Wakeup_Entry_Caller (Self_Id, Entry_Call, Done); STPO.Unlock (Caller); if Single_Lock then Unlock_RTS; end if; else POO.PO_Do_Or_Queue (Self_Id, Called_PO, Entry_Call); POO.PO_Service_Entries (Self_Id, Called_PO); end if; end if; Entry_Calls.Reset_Priority (Self_Id, Entry_Call.Acceptor_Prev_Priority); else -- The call does not need to be requeued Self_Id.Common.Call := Entry_Call.Acceptor_Prev_Call; Entry_Call.Exception_To_Raise := Ex; if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (Caller); -- Done with Caller locked to make sure that Wakeup is not lost if Ex /= Ada.Exceptions.Null_Id then Transfer_Occurrence (Caller.Common.Compiler_Data.Current_Excep'Access, Self_Id.Common.Compiler_Data.Current_Excep); end if; Acceptor_Prev_Priority := Entry_Call.Acceptor_Prev_Priority; Initialization.Wakeup_Entry_Caller (Self_Id, Entry_Call, Done); STPO.Unlock (Caller); if Single_Lock then Unlock_RTS; end if; Entry_Calls.Reset_Priority (Self_Id, Acceptor_Prev_Priority); end if; end if; Initialization.Undefer_Abort (Self_Id); end Local_Complete_Rendezvous; ------------------------------------- -- Requeue_Protected_To_Task_Entry -- ------------------------------------- procedure Requeue_Protected_To_Task_Entry (Object : STPE.Protection_Entries_Access; Acceptor : Task_Id; E : Task_Entry_Index; With_Abort : Boolean) is Entry_Call : constant Entry_Call_Link := Object.Call_In_Progress; begin pragma Assert (STPO.Self.Deferral_Level > 0); Entry_Call.E := Entry_Index (E); Entry_Call.Called_Task := Acceptor; Entry_Call.Called_PO := Null_Address; Entry_Call.With_Abort := With_Abort; Object.Call_In_Progress := null; end Requeue_Protected_To_Task_Entry; ------------------------ -- Requeue_Task_Entry -- ------------------------ procedure Requeue_Task_Entry (Acceptor : Task_Id; E : Task_Entry_Index; With_Abort : Boolean) is Self_Id : constant Task_Id := STPO.Self; Entry_Call : constant Entry_Call_Link := Self_Id.Common.Call; begin Initialization.Defer_Abort (Self_Id); Entry_Call.Needs_Requeue := True; Entry_Call.With_Abort := With_Abort; Entry_Call.E := Entry_Index (E); Entry_Call.Called_Task := Acceptor; Initialization.Undefer_Abort (Self_Id); end Requeue_Task_Entry; -------------------- -- Selective_Wait -- -------------------- procedure Selective_Wait (Open_Accepts : Accept_List_Access; Select_Mode : Select_Modes; Uninterpreted_Data : out System.Address; Index : out Select_Index) is Self_Id : constant Task_Id := STPO.Self; Entry_Call : Entry_Call_Link; Treatment : Select_Treatment; Caller : Task_Id; Selection : Select_Index; Open_Alternative : Boolean; begin Initialization.Defer_Abort (Self_Id); if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (Self_Id); if not Self_Id.Callable then pragma Assert (Self_Id.Pending_ATC_Level = 0); pragma Assert (Self_Id.Pending_Action); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; -- ??? In some cases abort is deferred more than once. Need to -- figure out why this happens. if Self_Id.Deferral_Level > 1 then Self_Id.Deferral_Level := 1; end if; Initialization.Undefer_Abort (Self_Id); -- Should never get here ??? pragma Assert (False); raise Standard'Abort_Signal; end if; pragma Assert (Open_Accepts /= null); Uninterpreted_Data := Null_Address; Queuing.Select_Task_Entry_Call (Self_Id, Open_Accepts, Entry_Call, Selection, Open_Alternative); -- Determine the kind and disposition of the select Treatment := Default_Treatment (Select_Mode); Self_Id.Chosen_Index := No_Rendezvous; if Open_Alternative then if Entry_Call /= null then if Open_Accepts (Selection).Null_Body then Treatment := Accept_Alternative_Completed; else Setup_For_Rendezvous_With_Body (Entry_Call, Self_Id); Treatment := Accept_Alternative_Selected; end if; Self_Id.Chosen_Index := Selection; elsif Treatment = No_Alternative_Open then Treatment := Accept_Alternative_Open; end if; end if; -- Handle the select according to the disposition selected above case Treatment is when Accept_Alternative_Selected => -- Ready to rendezvous Uninterpreted_Data := Self_Id.Common.Call.Uninterpreted_Data; -- In this case the accept body is not Null_Body. Defer abort -- until it gets into the accept body. The compiler has inserted -- a call to Abort_Undefer as part of the entry expansion. pragma Assert (Self_Id.Deferral_Level = 1); Initialization.Defer_Abort_Nestable (Self_Id); STPO.Unlock (Self_Id); when Accept_Alternative_Completed => -- Accept body is null, so rendezvous is over immediately STPO.Unlock (Self_Id); Caller := Entry_Call.Self; STPO.Write_Lock (Caller); Initialization.Wakeup_Entry_Caller (Self_Id, Entry_Call, Done); STPO.Unlock (Caller); when Accept_Alternative_Open => -- Wait for caller Self_Id.Open_Accepts := Open_Accepts; pragma Debug (Debug.Trace (Self_Id, "Selective_Wait: wait", 'R')); Wait_For_Call (Self_Id); pragma Assert (Self_Id.Open_Accepts = null); -- Self_Id.Common.Call should already be updated by the Caller if -- not aborted. It might also be ready to do rendezvous even if -- this wakes up due to an abort. Therefore, if the call is not -- empty we need to do the rendezvous if the accept body is not -- Null_Body. -- Aren't the first two conditions below redundant??? if Self_Id.Chosen_Index /= No_Rendezvous and then Self_Id.Common.Call /= null and then not Open_Accepts (Self_Id.Chosen_Index).Null_Body then Uninterpreted_Data := Self_Id.Common.Call.Uninterpreted_Data; pragma Assert (Self_Id.Deferral_Level = 1 or else (Self_Id.Deferral_Level = 0 and then not Restrictions.Abort_Allowed)); Initialization.Defer_Abort_Nestable (Self_Id); -- Leave abort deferred until the accept body -- The compiler has inserted a call to Abort_Undefer as part of -- the entry expansion. end if; STPO.Unlock (Self_Id); when Else_Selected => pragma Assert (Self_Id.Open_Accepts = null); STPO.Unlock (Self_Id); when Terminate_Selected => -- Terminate alternative is open Self_Id.Open_Accepts := Open_Accepts; Self_Id.Common.State := Acceptor_Sleep; -- Notify ancestors that this task is on a terminate alternative STPO.Unlock (Self_Id); Utilities.Make_Passive (Self_Id, Task_Completed => False); STPO.Write_Lock (Self_Id); -- Wait for normal entry call or termination Wait_For_Call (Self_Id); pragma Assert (Self_Id.Open_Accepts = null); if Self_Id.Terminate_Alternative then -- An entry call should have reset this to False, so we must be -- aborted. We cannot be in an async. select, since that is not -- legal, so the abort must be of the entire task. Therefore, -- we do not need to cancel the terminate alternative. The -- cleanup will be done in Complete_Master. pragma Assert (Self_Id.Pending_ATC_Level = 0); pragma Assert (Self_Id.Awake_Count = 0); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Index := Self_Id.Chosen_Index; Initialization.Undefer_Abort_Nestable (Self_Id); if Self_Id.Pending_Action then Initialization.Do_Pending_Action (Self_Id); end if; return; else -- Self_Id.Common.Call and Self_Id.Chosen_Index -- should already be updated by the Caller. if Self_Id.Chosen_Index /= No_Rendezvous and then not Open_Accepts (Self_Id.Chosen_Index).Null_Body then Uninterpreted_Data := Self_Id.Common.Call.Uninterpreted_Data; pragma Assert (Self_Id.Deferral_Level = 1); -- We need an extra defer here, to keep abort -- deferred until we get into the accept body -- The compiler has inserted a call to Abort_Undefer as part -- of the entry expansion. Initialization.Defer_Abort_Nestable (Self_Id); end if; end if; STPO.Unlock (Self_Id); when No_Alternative_Open => -- In this case, Index will be No_Rendezvous on return, which -- should cause a Program_Error if it is not a Delay_Mode. -- If delay alternative exists (Delay_Mode) we should suspend -- until the delay expires. Self_Id.Open_Accepts := null; if Select_Mode = Delay_Mode then Self_Id.Common.State := Delay_Sleep; loop exit when Self_Id.Pending_ATC_Level < Self_Id.ATC_Nesting_Level; Sleep (Self_Id, Delay_Sleep); end loop; Self_Id.Common.State := Runnable; STPO.Unlock (Self_Id); else STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); raise Program_Error with "entry call not a delay mode"; end if; end case; if Single_Lock then Unlock_RTS; end if; -- Caller has been chosen -- Self_Id.Common.Call should already be updated by the Caller. -- Self_Id.Chosen_Index should either be updated by the Caller -- or by Test_Selective_Wait. -- On return, we sill start rendezvous unless the accept body is -- null. In the latter case, we will have already completed the RV. Index := Self_Id.Chosen_Index; Initialization.Undefer_Abort_Nestable (Self_Id); end Selective_Wait; ------------------------------------ -- Setup_For_Rendezvous_With_Body -- ------------------------------------ procedure Setup_For_Rendezvous_With_Body (Entry_Call : Entry_Call_Link; Acceptor : Task_Id) is begin Entry_Call.Acceptor_Prev_Call := Acceptor.Common.Call; Acceptor.Common.Call := Entry_Call; if Entry_Call.State = Now_Abortable then Entry_Call.State := Was_Abortable; end if; Boost_Priority (Entry_Call, Acceptor); end Setup_For_Rendezvous_With_Body; ---------------- -- Task_Count -- ---------------- function Task_Count (E : Task_Entry_Index) return Natural is Self_Id : constant Task_Id := STPO.Self; Return_Count : Natural; begin Initialization.Defer_Abort (Self_Id); if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (Self_Id); Return_Count := Queuing.Count_Waiting (Self_Id.Entry_Queues (E)); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); return Return_Count; end Task_Count; ---------------------- -- Task_Do_Or_Queue -- ---------------------- function Task_Do_Or_Queue (Self_ID : Task_Id; Entry_Call : Entry_Call_Link) return Boolean is E : constant Task_Entry_Index := Task_Entry_Index (Entry_Call.E); Old_State : constant Entry_Call_State := Entry_Call.State; Acceptor : constant Task_Id := Entry_Call.Called_Task; Parent : constant Task_Id := Acceptor.Common.Parent; Null_Body : Boolean; begin -- Find out whether Entry_Call can be accepted immediately -- If the Acceptor is not callable, return False. -- If the rendezvous can start, initiate it. -- If the accept-body is trivial, also complete the rendezvous. -- If the acceptor is not ready, enqueue the call. -- This should have a special case for Accept_Call and Accept_Trivial, -- so that we don't have the loop setup overhead, below. -- The call state Done is used here and elsewhere to include both the -- case of normal successful completion, and the case of an exception -- being raised. The difference is that if an exception is raised no one -- will pay attention to the fact that State = Done. Instead the -- exception will be raised in Undefer_Abort, and control will skip past -- the place where we normally would resume from an entry call. pragma Assert (not Queuing.Onqueue (Entry_Call)); -- We rely that the call is off-queue for protection, that the caller -- will not exit the Entry_Caller_Sleep, and so will not reuse the call -- record for another call. We rely on the Caller's lock for call State -- mod's. -- If Acceptor.Terminate_Alternative is True, we need to lock Parent and -- Acceptor, in that order; otherwise, we only need a lock on Acceptor. -- However, we can't check Acceptor.Terminate_Alternative until Acceptor -- is locked. Therefore, we need to lock both. Attempts to avoid locking -- Parent tend to result in race conditions. It would work to unlock -- Parent immediately upon finding Acceptor.Terminate_Alternative to be -- False, but that violates the rule of properly nested locking (see -- System.Tasking). STPO.Write_Lock (Parent); STPO.Write_Lock (Acceptor); -- If the acceptor is not callable, abort the call and return False if not Acceptor.Callable then STPO.Unlock (Acceptor); STPO.Unlock (Parent); pragma Assert (Entry_Call.State < Done); -- In case we are not the caller, set up the caller -- to raise Tasking_Error when it wakes up. STPO.Write_Lock (Entry_Call.Self); Entry_Call.Exception_To_Raise := Tasking_Error'Identity; Initialization.Wakeup_Entry_Caller (Self_ID, Entry_Call, Done); STPO.Unlock (Entry_Call.Self); return False; end if; -- Try to serve the call immediately if Acceptor.Open_Accepts /= null then for J in Acceptor.Open_Accepts'Range loop if Entry_Call.E = Entry_Index (Acceptor.Open_Accepts (J).S) then -- Commit acceptor to rendezvous with us Acceptor.Chosen_Index := J; Null_Body := Acceptor.Open_Accepts (J).Null_Body; Acceptor.Open_Accepts := null; -- Prevent abort while call is being served if Entry_Call.State = Now_Abortable then Entry_Call.State := Was_Abortable; end if; if Acceptor.Terminate_Alternative then -- Cancel terminate alternative. See matching code in -- Selective_Wait and Vulnerable_Complete_Master. Acceptor.Terminate_Alternative := False; Acceptor.Awake_Count := Acceptor.Awake_Count + 1; if Acceptor.Awake_Count = 1 then -- Notify parent that acceptor is awake pragma Assert (Parent.Awake_Count > 0); Parent.Awake_Count := Parent.Awake_Count + 1; if Parent.Common.State = Master_Completion_Sleep and then Acceptor.Master_Of_Task = Parent.Master_Within then Parent.Common.Wait_Count := Parent.Common.Wait_Count + 1; end if; end if; end if; if Null_Body then -- Rendezvous is over immediately STPO.Wakeup (Acceptor, Acceptor_Sleep); STPO.Unlock (Acceptor); STPO.Unlock (Parent); STPO.Write_Lock (Entry_Call.Self); Initialization.Wakeup_Entry_Caller (Self_ID, Entry_Call, Done); STPO.Unlock (Entry_Call.Self); else Setup_For_Rendezvous_With_Body (Entry_Call, Acceptor); -- For terminate_alternative, acceptor may not be asleep -- yet, so we skip the wakeup if Acceptor.Common.State /= Runnable then STPO.Wakeup (Acceptor, Acceptor_Sleep); end if; STPO.Unlock (Acceptor); STPO.Unlock (Parent); end if; return True; end if; end loop; -- The acceptor is accepting, but not this entry end if; -- If the acceptor was ready to accept this call, -- we would not have gotten this far, so now we should -- (re)enqueue the call, if the mode permits that. -- If the call is timed, it may have timed out before the requeue, -- in the unusual case where the current accept has taken longer than -- the given delay. In that case the requeue is cancelled, and the -- outer timed call will be aborted. if Entry_Call.Mode = Conditional_Call or else (Entry_Call.Mode = Timed_Call and then Entry_Call.With_Abort and then Entry_Call.Cancellation_Attempted) then STPO.Unlock (Acceptor); STPO.Unlock (Parent); STPO.Write_Lock (Entry_Call.Self); pragma Assert (Entry_Call.State >= Was_Abortable); Initialization.Wakeup_Entry_Caller (Self_ID, Entry_Call, Cancelled); STPO.Unlock (Entry_Call.Self); else -- Timed_Call, Simple_Call, or Asynchronous_Call Queuing.Enqueue (Acceptor.Entry_Queues (E), Entry_Call); -- Update abortability of call pragma Assert (Old_State < Done); Entry_Call.State := New_State (Entry_Call.With_Abort, Entry_Call.State); STPO.Unlock (Acceptor); STPO.Unlock (Parent); if Old_State /= Entry_Call.State and then Entry_Call.State = Now_Abortable and then Entry_Call.Mode /= Simple_Call and then Entry_Call.Self /= Self_ID -- Asynchronous_Call or Conditional_Call then -- Because of ATCB lock ordering rule STPO.Write_Lock (Entry_Call.Self); if Entry_Call.Self.Common.State = Async_Select_Sleep then -- Caller may not yet have reached wait-point STPO.Wakeup (Entry_Call.Self, Async_Select_Sleep); end if; STPO.Unlock (Entry_Call.Self); end if; end if; return True; end Task_Do_Or_Queue; --------------------- -- Task_Entry_Call -- --------------------- procedure Task_Entry_Call (Acceptor : Task_Id; E : Task_Entry_Index; Uninterpreted_Data : System.Address; Mode : Call_Modes; Rendezvous_Successful : out Boolean) is Self_Id : constant Task_Id := STPO.Self; Entry_Call : Entry_Call_Link; 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; if Mode = Simple_Call or else Mode = Conditional_Call then Call_Synchronous (Acceptor, E, Uninterpreted_Data, Mode, Rendezvous_Successful); else -- This is an asynchronous call -- Abort must already be deferred by the compiler-generated code. -- Without this, an abort that occurs between the time that this -- call is made and the time that the abortable part's cleanup -- handler is set up might miss the cleanup handler and leave the -- call pending. Self_Id.ATC_Nesting_Level := Self_Id.ATC_Nesting_Level + 1; pragma Debug (Debug.Trace (Self_Id, "TEC: entered ATC level: " & ATC_Level'Image (Self_Id.ATC_Nesting_Level), 'A')); Entry_Call := Self_Id.Entry_Calls (Self_Id.ATC_Nesting_Level)'Access; Entry_Call.Next := null; Entry_Call.Mode := Mode; Entry_Call.Cancellation_Attempted := False; Entry_Call.State := Not_Yet_Abortable; Entry_Call.E := Entry_Index (E); Entry_Call.Prio := Get_Priority (Self_Id); Entry_Call.Uninterpreted_Data := Uninterpreted_Data; Entry_Call.Called_Task := Acceptor; Entry_Call.Called_PO := Null_Address; Entry_Call.Exception_To_Raise := Ada.Exceptions.Null_Id; Entry_Call.With_Abort := True; if Single_Lock then Lock_RTS; end if; if not Task_Do_Or_Queue (Self_Id, Entry_Call) then STPO.Write_Lock (Self_Id); Utilities.Exit_One_ATC_Level (Self_Id); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); raise Tasking_Error; end if; -- The following is special for async. entry calls. If the call was -- not queued abortably, we need to wait until it is before -- proceeding with the abortable part. -- Wait_Until_Abortable can be called unconditionally here, but it is -- expensive. if Entry_Call.State < Was_Abortable then Entry_Calls.Wait_Until_Abortable (Self_Id, Entry_Call); end if; if Single_Lock then Unlock_RTS; end if; -- Note: following assignment needs to be atomic Rendezvous_Successful := Entry_Call.State = Done; end if; end Task_Entry_Call; ----------------------- -- Task_Entry_Caller -- ----------------------- function Task_Entry_Caller (D : Task_Entry_Nesting_Depth) return Task_Id is Self_Id : constant Task_Id := STPO.Self; Entry_Call : Entry_Call_Link; begin Entry_Call := Self_Id.Common.Call; for Depth in 1 .. D loop Entry_Call := Entry_Call.Acceptor_Prev_Call; pragma Assert (Entry_Call /= null); end loop; return Entry_Call.Self; end Task_Entry_Caller; -------------------------- -- Timed_Selective_Wait -- -------------------------- procedure Timed_Selective_Wait (Open_Accepts : Accept_List_Access; Select_Mode : Select_Modes; Uninterpreted_Data : out System.Address; Timeout : Duration; Mode : Delay_Modes; Index : out Select_Index) is Self_Id : constant Task_Id := STPO.Self; Treatment : Select_Treatment; Entry_Call : Entry_Call_Link; Caller : Task_Id; Selection : Select_Index; Open_Alternative : Boolean; Timedout : Boolean := False; Yielded : Boolean := True; begin pragma Assert (Select_Mode = Delay_Mode); Initialization.Defer_Abort (Self_Id); -- If we are aborted here, the effect will be pending if Single_Lock then Lock_RTS; end if; STPO.Write_Lock (Self_Id); if not Self_Id.Callable then pragma Assert (Self_Id.Pending_ATC_Level = 0); pragma Assert (Self_Id.Pending_Action); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); -- Should never get here ??? pragma Assert (False); raise Standard'Abort_Signal; end if; Uninterpreted_Data := Null_Address; pragma Assert (Open_Accepts /= null); Queuing.Select_Task_Entry_Call (Self_Id, Open_Accepts, Entry_Call, Selection, Open_Alternative); -- Determine the kind and disposition of the select Treatment := Default_Treatment (Select_Mode); Self_Id.Chosen_Index := No_Rendezvous; if Open_Alternative then if Entry_Call /= null then if Open_Accepts (Selection).Null_Body then Treatment := Accept_Alternative_Completed; else Setup_For_Rendezvous_With_Body (Entry_Call, Self_Id); Treatment := Accept_Alternative_Selected; end if; Self_Id.Chosen_Index := Selection; elsif Treatment = No_Alternative_Open then Treatment := Accept_Alternative_Open; end if; end if; -- Handle the select according to the disposition selected above case Treatment is when Accept_Alternative_Selected => -- Ready to rendezvous. In this case the accept body is not -- Null_Body. Defer abort until it gets into the accept body. Uninterpreted_Data := Self_Id.Common.Call.Uninterpreted_Data; Initialization.Defer_Abort_Nestable (Self_Id); STPO.Unlock (Self_Id); when Accept_Alternative_Completed => -- Rendezvous is over STPO.Unlock (Self_Id); Caller := Entry_Call.Self; STPO.Write_Lock (Caller); Initialization.Wakeup_Entry_Caller (Self_Id, Entry_Call, Done); STPO.Unlock (Caller); when Accept_Alternative_Open => -- Wait for caller Self_Id.Open_Accepts := Open_Accepts; -- Wait for a normal call and a pending action until the -- Wakeup_Time is reached. Self_Id.Common.State := Acceptor_Delay_Sleep; -- Try to remove calls to Sleep in the loop below by letting the -- caller a chance of getting ready immediately, using Unlock -- Yield. See similar action in Wait_For_Completion/Wait_For_Call. if Single_Lock then Unlock_RTS; else Unlock (Self_Id); end if; if Self_Id.Open_Accepts /= null then Yield; end if; if Single_Lock then Lock_RTS; else Write_Lock (Self_Id); end if; -- Check if this task has been aborted while the lock was released if Self_Id.Pending_ATC_Level < Self_Id.ATC_Nesting_Level then Self_Id.Open_Accepts := null; end if; loop exit when Self_Id.Open_Accepts = null; if Timedout then Sleep (Self_Id, Acceptor_Delay_Sleep); else STPO.Timed_Sleep (Self_Id, Timeout, Mode, Acceptor_Delay_Sleep, Timedout, Yielded); end if; if Timedout then Self_Id.Open_Accepts := null; end if; end loop; Self_Id.Common.State := Runnable; -- Self_Id.Common.Call should already be updated by the Caller if -- not aborted. It might also be ready to do rendezvous even if -- this wakes up due to an abort. Therefore, if the call is not -- empty we need to do the rendezvous if the accept body is not -- Null_Body. if Self_Id.Chosen_Index /= No_Rendezvous and then Self_Id.Common.Call /= null and then not Open_Accepts (Self_Id.Chosen_Index).Null_Body then Uninterpreted_Data := Self_Id.Common.Call.Uninterpreted_Data; pragma Assert (Self_Id.Deferral_Level = 1); Initialization.Defer_Abort_Nestable (Self_Id); -- Leave abort deferred until the accept body end if; STPO.Unlock (Self_Id); when No_Alternative_Open => -- In this case, Index will be No_Rendezvous on return. We sleep -- for the time we need to. -- Wait for a signal or timeout. A wakeup can be made -- for several reasons: -- 1) Delay is expired -- 2) Pending_Action needs to be checked -- (Abort, Priority change) -- 3) Spurious wakeup Self_Id.Open_Accepts := null; Self_Id.Common.State := Acceptor_Delay_Sleep; STPO.Timed_Sleep (Self_Id, Timeout, Mode, Acceptor_Delay_Sleep, Timedout, Yielded); Self_Id.Common.State := Runnable; STPO.Unlock (Self_Id); when others => -- Should never get here pragma Assert (False); null; end case; if Single_Lock then Unlock_RTS; end if; if not Yielded then Yield; end if; -- Caller has been chosen -- Self_Id.Common.Call should already be updated by the Caller -- Self_Id.Chosen_Index should either be updated by the Caller -- or by Test_Selective_Wait Index := Self_Id.Chosen_Index; Initialization.Undefer_Abort_Nestable (Self_Id); -- Start rendezvous, if not already completed end Timed_Selective_Wait; --------------------------- -- Timed_Task_Entry_Call -- --------------------------- procedure Timed_Task_Entry_Call (Acceptor : Task_Id; E : Task_Entry_Index; Uninterpreted_Data : System.Address; Timeout : Duration; Mode : Delay_Modes; Rendezvous_Successful : out Boolean) is Self_Id : constant Task_Id := STPO.Self; Level : ATC_Level; Entry_Call : Entry_Call_Link; Yielded : Boolean; pragma Unreferenced (Yielded); 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 (Self_Id); Self_Id.ATC_Nesting_Level := Self_Id.ATC_Nesting_Level + 1; pragma Debug (Debug.Trace (Self_Id, "TTEC: entered ATC level: " & ATC_Level'Image (Self_Id.ATC_Nesting_Level), 'A')); Level := Self_Id.ATC_Nesting_Level; Entry_Call := Self_Id.Entry_Calls (Level)'Access; Entry_Call.Next := null; Entry_Call.Mode := Timed_Call; Entry_Call.Cancellation_Attempted := False; -- If this is a call made inside of an abort deferred region, -- the call should be never abortable. Entry_Call.State := (if Self_Id.Deferral_Level > 1 then Never_Abortable else Now_Abortable); Entry_Call.E := Entry_Index (E); Entry_Call.Prio := Get_Priority (Self_Id); Entry_Call.Uninterpreted_Data := Uninterpreted_Data; Entry_Call.Called_Task := Acceptor; Entry_Call.Called_PO := Null_Address; Entry_Call.Exception_To_Raise := Ada.Exceptions.Null_Id; Entry_Call.With_Abort := True; -- Note: the caller will undefer abort on return (see WARNING above) if Single_Lock then Lock_RTS; end if; if not Task_Do_Or_Queue (Self_Id, Entry_Call) then STPO.Write_Lock (Self_Id); Utilities.Exit_One_ATC_Level (Self_Id); STPO.Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; Initialization.Undefer_Abort (Self_Id); raise Tasking_Error; end if; Write_Lock (Self_Id); Entry_Calls.Wait_For_Completion_With_Timeout (Entry_Call, Timeout, Mode, Yielded); Unlock (Self_Id); if Single_Lock then Unlock_RTS; end if; -- ??? Do we need to yield in case Yielded is False Rendezvous_Successful := Entry_Call.State = Done; Initialization.Undefer_Abort (Self_Id); Entry_Calls.Check_Exception (Self_Id, Entry_Call); end Timed_Task_Entry_Call; ------------------- -- Wait_For_Call -- ------------------- procedure Wait_For_Call (Self_Id : Task_Id) is begin Self_Id.Common.State := Acceptor_Sleep; -- Try to remove calls to Sleep in the loop below by letting the caller -- a chance of getting ready immediately, using Unlock & Yield. -- See similar action in Wait_For_Completion & Timed_Selective_Wait. if Single_Lock then Unlock_RTS; else Unlock (Self_Id); end if; if Self_Id.Open_Accepts /= null then Yield; end if; if Single_Lock then Lock_RTS; else Write_Lock (Self_Id); end if; -- Check if this task has been aborted while the lock was released if Self_Id.Pending_ATC_Level < Self_Id.ATC_Nesting_Level then Self_Id.Open_Accepts := null; end if; loop exit when Self_Id.Open_Accepts = null; Sleep (Self_Id, Acceptor_Sleep); end loop; Self_Id.Common.State := Runnable; end Wait_For_Call; end System.Tasking.Rendezvous;