| ------------------------------------------------------------------------------ |
| -- -- |
| -- 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-2022, Free Software Foundation, Inc. -- |
| -- -- |
| -- GNARL is free software; you can redistribute it and/or modify it under -- |
| -- terms of the GNU General Public License as published by the Free Soft- -- |
| -- ware Foundation; either version 3, or (at your option) any later ver- -- |
| -- sion. GNAT is distributed in the hope that it will be useful, but WITH- -- |
| -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -- |
| -- or FITNESS FOR A PARTICULAR PURPOSE. -- |
| -- -- |
| -- As a special exception under Section 7 of GPL version 3, you are granted -- |
| -- additional permissions described in the GCC Runtime Library Exception, -- |
| -- version 3.1, as published by the Free Software Foundation. -- |
| -- -- |
| -- You should have received a copy of the GNU General Public License and -- |
| -- a copy of the GCC Runtime Library Exception along with this program; -- |
| -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -- |
| -- <http://www.gnu.org/licenses/>. -- |
| -- -- |
| -- GNARL was developed by the GNARL team at Florida State University. -- |
| -- Extensive contributions were provided by Ada Core Technologies, Inc. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| 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; |
| |
| 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 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); |
| STPO.Write_Lock (Self_Id); |
| |
| if not Self_Id.Callable then |
| pragma Assert (Self_Id.Pending_ATC_Level = Level_Completed_Task); |
| |
| pragma Assert (Self_Id.Pending_Action); |
| |
| STPO.Unlock (Self_Id); |
| Initialization.Undefer_Abort (Self_Id); |
| |
| -- Should never get here ??? |
| |
| pragma Assert (Standard.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; |
| |
| pragma Assert (Caller.ATC_Nesting_Level > Level_No_ATC_Occurring); |
| |
| 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); |
| 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); |
| STPO.Write_Lock (Self_Id); |
| |
| if not Self_Id.Callable then |
| pragma Assert (Self_Id.Pending_ATC_Level = Level_Completed_Task); |
| |
| pragma Assert (Self_Id.Pending_Action); |
| |
| STPO.Unlock (Self_Id); |
| Initialization.Undefer_Abort_Nestable (Self_Id); |
| |
| -- Should never get here ??? |
| |
| pragma Assert (Standard.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; |
| |
| 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; |
| |
| 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 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); |
| 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); |
| 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); |
| STPO.Write_Lock (T); |
| Result := T.Callable; |
| STPO.Unlock (T); |
| 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')); |
| |
| Initialization.Defer_Abort (Self_Id); |
| |
| -- We need to clean up any accepts which Self may have been serving when |
| -- it was aborted. |
| |
| if Ex = Standard'Abort_Signal'Identity then |
| 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; |
| 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 not Task_Do_Or_Queue (Self_Id, Entry_Call) then |
| Initialization.Undefer_Abort (Self_Id); |
| raise Tasking_Error; |
| 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; |
| STPO.Write_Lock (Caller); |
| Initialization.Wakeup_Entry_Caller |
| (Self_Id, Entry_Call, Done); |
| STPO.Unlock (Caller); |
| |
| 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; |
| 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); |
| 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); |
| STPO.Write_Lock (Self_Id); |
| |
| if not Self_Id.Callable then |
| pragma Assert (Self_Id.Pending_ATC_Level = Level_Completed_Task); |
| |
| pragma Assert (Self_Id.Pending_Action); |
| |
| STPO.Unlock (Self_Id); |
| |
| -- ??? 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 (Standard.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 = Level_Completed_Task); |
| pragma Assert (Self_Id.Awake_Count = 0); |
| |
| STPO.Unlock (Self_Id); |
| |
| 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); |
| Initialization.Undefer_Abort (Self_Id); |
| raise Program_Error with "entry call not a delay mode"; |
| end if; |
| end case; |
| |
| -- 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); |
| STPO.Write_Lock (Self_Id); |
| Return_Count := Queuing.Count_Waiting (Self_Id.Entry_Queues (E)); |
| STPO.Unlock (Self_Id); |
| 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 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); |
| 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; |
| |
| -- 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 |
| |
| STPO.Write_Lock (Self_Id); |
| |
| if not Self_Id.Callable then |
| pragma Assert (Self_Id.Pending_ATC_Level = Level_Completed_Task); |
| |
| pragma Assert (Self_Id.Pending_Action); |
| |
| STPO.Unlock (Self_Id); |
| Initialization.Undefer_Abort (Self_Id); |
| |
| -- Should never get here ??? |
| |
| pragma Assert (Standard.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. |
| |
| Unlock (Self_Id); |
| |
| if Self_Id.Open_Accepts /= null then |
| Yield; |
| end if; |
| |
| Write_Lock (Self_Id); |
| |
| -- 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 (Standard.False); |
| null; |
| end case; |
| |
| 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; |
| |
| begin |
| -- If pragma Detect_Blocking is active then Program_Error must be |
| -- raised if this potentially blocking operation is called from a |
| -- protected action. |
| |
| if System.Tasking.Detect_Blocking |
| and then Self_Id.Common.Protected_Action_Nesting > 0 |
| then |
| raise Program_Error with |
| "potentially blocking operation"; |
| end if; |
| |
| Initialization.Defer_Abort_Nestable (Self_Id); |
| 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 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); |
| Initialization.Undefer_Abort_Nestable (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); |
| |
| -- ??? Do we need to yield in case Yielded is False |
| |
| Rendezvous_Successful := Entry_Call.State = Done; |
| Initialization.Undefer_Abort_Nestable (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. |
| |
| Unlock (Self_Id); |
| |
| if Self_Id.Open_Accepts /= null then |
| Yield; |
| end if; |
| |
| Write_Lock (Self_Id); |
| |
| -- 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; |