-- -- -- package Copyright (c) Dmitry A. Kazakov -- -- Synchronization.Interprocess. Luebeck -- -- Generic_Process_Call_Service Spring, 2018 -- -- Implementation -- -- Last revision : 14:52 29 Feb 2020 -- -- -- -- This library is free software; you can redistribute it and/or -- -- modify it under the terms of the GNU General Public License as -- -- published by the Free Software Foundation; either version 2 of -- -- the License, or (at your option) any later version. This library -- -- is distributed in the hope that it will be useful, but WITHOUT -- -- ANY WARRANTY; without even the implied warranty of -- -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -- -- General Public License for more details. You should have -- -- received a copy of the GNU General Public License along with -- -- this library; if not, write to the Free Software Foundation, -- -- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -- -- -- -- As a special exception, if other files instantiate generics from -- -- this unit, or you link this unit with other files to produce an -- -- executable, this unit does not by itself cause the resulting -- -- executable to be covered by the GNU General Public License. This -- -- exception does not however invalidate any other reasons why the -- -- executable file might be covered by the GNU Public License. -- --____________________________________________________________________-- with Ada.Calendar; use Ada.Calendar; with Ada.IO_Exceptions; use Ada.IO_Exceptions; with Ada.Tags; use Ada.Tags; with Ada.Task_Identification; use Ada.Task_Identification; with Strings_Edit.Floats; use Strings_Edit.Floats; with Strings_Edit.Integers; use Strings_Edit.Integers; with Ada.Strings.Fixed; with Ada.Unchecked_Conversion; with Ada.Unchecked_Deallocation; with System.Address_To_Access_Conversions; -- with Ada.Text_IO, Synchronization.Mutexes; -- with GNAT.Exception_Actions, GNAT.Traceback.Symbolic; package body Synchronization.Interprocess.Process_Call_Service is Caller_Init : constant String := "The caller's service is not " & "started or alredy stopped"; Callee_Init : constant String := "The callee's service is not " & "started or alredy stopped"; Diff_Service : constant String := "The caller and callee belong " & "to different call services"; Method_Init : constant String := "Method is not initialized or " & "already finalized"; Service_Init : constant String := "Call service is not " & "initialized or already finalized"; Same_Process : constant String := "Caller and callee belong to " & "the same process"; -- -- No -- The unique request sequence number -- No : aliased Sequence_No := 0; -- -- Main -- Environment_Task. Unfortunately the older versions of Ada do -- not have it defined in Ada.Task_Identification. We use -- Current_Task while the package elaboration to get it. -- Main : Task_ID; -- Trace_Lock : aliased Synchronization.Mutexes.Mutex; -- Trace_File : Ada.Text_IO.File_Type; -- Trace_Open : Boolean := False; -- function "&" (Left : String; Right : Duration) return String is -- begin -- return Left & Image (Float (Right), AbsSmall => -3) & "ms"; -- end "&"; -- -- function "&" (Left : String; Right : Storage_Offset) return String is -- begin -- return Left & Image (Integer (Right)); -- end "&"; -- -- procedure Trace_Exception_Action -- ( Occurrence : Exception_Occurrence -- ); -- -- procedure Trace (ID : Call_Service_ID; Text : String) is -- use Ada.Strings.Fixed; -- use Ada.Text_IO; -- use GNAT.Exception_Actions; -- Lock : Synchronization.Mutexes.Holder (Trace_Lock'Access); -- begin -- if not Trace_Open then -- Register_Global_Action (Trace_Exception_Action'Access); -- Create -- ( Trace_File, -- Out_File, -- "d:\temp\test_" & Image (Integer (ID)) & ".log" -- ); -- Trace_Open := True; -- end if; -- declare -- Prefix : constant String := Image (Current_Task); -- begin -- Put_Line -- ( Trace_File, -- ( Prefix -- & "| " -- & ( ( ( Character'Pos (Prefix (Prefix'First)) -- + Character'Pos (Prefix (Prefix'Last)) -- ) -- mod -- 5 -- ) -- * " " -- ) -- & Text -- ) ); -- end; -- Flush (Trace_File); -- end Trace; -- -- procedure Trace_Exception_Action -- ( Occurrence : Exception_Occurrence -- ) is -- use GNAT.Traceback.Symbolic; -- begin -- Trace -- ( 0, -- "++! " & Exception_Information (Occurrence) -- ); -- end Trace_Exception_Action; function "=" (Left, Right : Request_Header) return Boolean is begin return Left.No = Right.No; end "="; procedure Enumerate ( Stream : access Root_Stream_Type'Class; Object : Call_Service ) is begin Enumerate (Stream, Abstract_Shared_Object (Object)); Event'Write (Stream, Object.Requests.Not_Full); Event'Write (Stream, Object.Requests.Not_Empty); Mutex'Write (Stream, Object.Requests.Lock); Universal_FIFO'Write (Stream, Object.Queue); Universal_Stream'Write (Stream, Object.Requests.Stream); Event'Write (Stream, Object.Responses.Not_Full); Event'Write (Stream, Object.Responses.Not_Empty); Mutex'Write (Stream, Object.Responses.Lock); Universal_Stream'Write (Stream, Object.Responses.Stream); Enumerate (Stream, Object.Epilogue); Call_Service'Class (Object.Self.all).Total_Size := ( Get_Offset (Object.Responses.Stream) - Get_Offset (Object) + Get_Size (Object) ); end Enumerate; procedure Finalize (Lock : in out Lock_Holder) is begin if Lock.Owner then -- Trace -- ( Get_ID (Lock.Stream.Service.all), -- ( "releasing results mutex of " -- & Get_Offset (Lock.Stream.Service.Responses.Stream) -- ) ); Lock.Owner := False; Release (Lock.Stream.Service.Responses.Lock); end if; end Finalize; procedure Read_Response_Header ( Service : in out Call_Service'Class; Header : in out Request_Header; Started : Time; Timeout : Duration ) is Empty : Boolean; Current : Request_Header; begin loop -- Trace -- ( Get_ID (Service), -- ( "wait for results " -- & (Timeout - (Clock - Started)) -- & " " -- & Image (Header) -- & " from " -- & Get_Offset (Service.Responses.Stream) -- ) ); select Service.Response.Wait_For_Results (Header, Empty); or delay Timeout - (Clock - Started); Raise_Exception ( Timeout_Error'Identity, "Timed out awaiting for the response" ); end select; exit when not Empty; -- Have header read by another task -- -- Reading the header -- -- Trace -- ( Get_ID (Service), -- ( "wait for response header " -- & (Timeout - (Clock - Started)) -- & " " -- & Image (Header) -- & " from " -- & Get_Offset (Service.Responses.Stream) -- ) ); Wait_For_Not_Empty ( Service.Responses.Stream, Timeout - (Clock - Started), Empty ); if Empty then Raise_Exception ( Timeout_Error'Identity, "No response from the call server" ); end if; begin Request_Header'Read ( Service.Responses.Stream'Access, Current ); Empty := False; exception when Timeout_Error => Raise_Exception ( Timeout_Error'Identity, "Response header reading timeout" ); end; -- Trace -- ( Get_ID (Service), -- ( "response " -- & Image (Current.From) -- & "->" -- & Image (Get_ID (Service)) -- & " header " -- & Image (Current) -- & " from " -- & Get_Offset (Service.Responses.Stream) -- ) ); if Current.Method not in Service.Methods'Range then Raise_Exception ( Data_Error'Identity, "Invalid method in the response header" ); end if; if Header = Current then -- The header is of the response to us Header.Mode := Current.Mode; return; end if; -- The header is for another task -- Trace (Get_ID (Service), "pass response to another task"); Service.Response.Accept_Results (Current); end loop; exception when others => Service.Response.Cancel; Close (Service.Requests.Stream); raise; end Read_Response_Header; procedure Generic_Complete ( Method : in out Abstract_Method'Class; Caller : in out Call_Service'Class; No : Sequence_No ) is Results : Results_Stream (Caller'Access); begin Results.Header.Mode := Success_Response; Results.Header.Method := Get_ID (Method); Results.Header.From := Get_ID (Get_Service (Method).all); Results.Header.No := No; -- Trace -- ( Results.Header.From, -- ( "execute " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Results.Header.From) -- & " from " -- & Get_Offset (Caller.Responses.Stream) -- ) ); Complete ( Method => Method, Results => Results, Caller => Caller, No => No ); exception when Error : others => declare Stream : Universal_Stream renames Caller.Responses.Stream; Lock : Holder (Caller.Responses.Lock'Access); begin Results.Header.Mode := Failure_Response; Request_Header'Write (Stream'Access, Results.Header); Exception_Id'Write ( Stream'Access, Exception_Identity (Error) ); String'Output (Stream'Access, Exception_Message (Error)); exception when others => null; end; end Generic_Complete; function Generic_Function_Call ( Method : Abstract_Method'Class; Callee : Call_Service'Class; Timeout : Duration := Duration'Last ) return Result_Type is begin if Method.Service = null or else Method.Index = 0 then Raise_Exception (Status_Error'Identity, Method_Init); elsif ( Method.Service.Data = null or else Method.Service.Data.Index not in 1..Last_ID ) then Raise_Exception (Status_Error'Identity, Caller_Init); elsif ( Callee.Data = null or else Callee.Data.Index not in 1..Last_ID ) then Raise_Exception (Status_Error'Identity, Callee_Init); elsif Method.Service.Parent /= Callee.Parent then Raise_Exception (Use_Error'Identity, Diff_Service); elsif Method.Service.Data.Index = Callee.Data.Index then Raise_Exception (Use_Error'Identity, Same_Process); end if; declare Caller : Call_Service'Class renames Method.Service.all; Target : Call_Service'Class renames Method.Service.Servers (Callee.Data.Index).all; Started : constant Time := Clock; Header : Request_Header := ( Mode => Synchronous_Request, Method => Method.Index, From => Caller.Data.Index, No => Initiate (Caller'Access) ); begin -- Trace -- ( Get_ID (Caller), -- ( "do function call " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- ) ); Seize (Target.Requests.Lock, Timeout); begin -- Trace -- ( Get_ID (Caller), -- ( "queue header " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " " -- & Image (Header) -- & " to " -- & Get_Offset (Target.Queue) -- ) ); if Is_Closed (Target.Requests.Stream) then Raise_Exception (Status_Error'Identity, Callee_Init); end if; Put (Target.Queue, Header, Timeout - (Clock - Started)); -- Trace -- ( Get_ID (Caller), -- ( "write parameters " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " to " -- & Get_Offset (Target.Requests.Stream) -- ) ); Send_Parameters (Target.Requests.Stream); -- Trace -- ( Get_ID (Caller), -- ( "write parameters done " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " to " -- & Get_Offset (Target.Requests.Stream) -- ) ); Release (Target.Requests.Lock); exception when End_Error => Release (Target.Requests.Lock); Raise_Exception (Status_Error'Identity, Caller_Init); when others => Release (Target.Requests.Lock); raise; end; begin Read_Response_Header (Caller, Header, Started, Timeout); exception when End_Error => Raise_Exception (Status_Error'Identity, Callee_Init); end; begin case Header.Mode is when Failure_Response => -- Error -- Trace -- ( Get_ID (Caller), -- ( "reading exception occurrence " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " from " -- & Get_Offset (Caller.Responses.Stream) -- ) ); declare -- Receive exception ID and message Responses : Universal_Stream renames Caller.Responses.Stream; ID : constant Exception_ID := Exception_ID'Input (Responses'Access); Message : constant String := String'Input (Responses'Access); begin Raise_Exception (ID, Message); end; when Success_Response => -- Success, receive results -- Trace -- ( Get_ID (Caller), -- ( "reading result " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " from " -- & Get_Offset (Caller.Responses.Stream) -- ) ); declare Result : constant Result_Type := Result_Type'Input ( Caller.Responses.Stream'Access ); begin -- Trace -- ( Get_ID (Caller), -- ( "result read " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " from " -- & Get_Offset (Caller.Responses.Stream) -- ) ); Caller.Response.Complete; return Result; end; when others => -- Cancel Raise_Exception ( Status_Error'Identity, "Call canceled by the server" ); end case; exception when End_Error => Caller.Response.Complete; Raise_Exception (Status_Error'Identity, Callee_Init); when others => Caller.Response.Complete; raise; end; end; end Generic_Function_Call; procedure Generic_Post ( Method : Abstract_Method'Class; Callee : Call_Service'Class; Timeout : Duration := Duration'Last ) is begin if Method.Service = null or else Method.Index = 0 then Raise_Exception (Status_Error'Identity, Method_Init); elsif ( Method.Service.Data = null or else Method.Service.Data.Index not in 1..Last_ID ) then Raise_Exception (Status_Error'Identity, Caller_Init); elsif ( Callee.Data = null or else Callee.Data.Index not in 1..Last_ID ) then Raise_Exception (Status_Error'Identity, Callee_Init); elsif Method.Service.Parent /= Callee.Parent then Raise_Exception (Use_Error'Identity, Diff_Service); elsif Method.Service.Data.Index = Callee.Data.Index then Raise_Exception (Use_Error'Identity, Same_Process); end if; declare Caller : Call_Service'Class renames Method.Service.all; Target : Call_Service'Class renames Method.Service.Servers (Callee.Data.Index).all; Started : constant Time := Clock; Header : constant Request_Header := ( Mode => Synchronous_Request, Method => Method.Index, From => Caller.Data.Index, No => Initiate (Caller'Access) ); begin -- Trace -- ( Get_ID (Caller), -- ( "do asynchronous call " -- & Image (Get_ID (Caller)) -- & " ->" -- & Image (Get_ID (Target)) -- ) ); Seize (Target.Requests.Lock, Timeout); begin -- Trace -- ( Get_ID (Caller), -- ( "queue header " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " " -- & Image (Header) -- & " to " -- & Get_Offset (Target.Queue) -- ) ); if Is_Closed (Target.Requests.Stream) then Raise_Exception (Status_Error'Identity, Callee_Init); end if; Put (Target.Queue, Header, Timeout - (Clock - Started)); -- Trace -- ( Get_ID (Caller), -- ( "write parameters " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " to " -- & Get_Offset (Target.Requests.Stream) -- ) ); Send_Parameters (Target.Requests.Stream); -- Trace -- ( Get_ID (Caller), -- ( "write parameters done " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " to " -- & Get_Offset (Target.Requests.Stream) -- ) ); Release (Target.Requests.Lock); exception when End_Error => Release (Target.Requests.Lock); Raise_Exception (Status_Error'Identity, Caller_Init); when others => Release (Target.Requests.Lock); raise; end; end; end Generic_Post; procedure Generic_Procedure_Call ( Method : Abstract_Method'Class; Callee : Call_Service'Class; Timeout : Duration := Duration'Last ) is begin if Method.Service = null or else Method.Index = 0 then Raise_Exception (Status_Error'Identity, Method_Init); elsif ( Method.Service.Data = null or else Method.Service.Data.Index not in 1..Last_ID ) then Raise_Exception (Status_Error'Identity, Caller_Init); elsif ( Callee.Data = null or else Callee.Data.Index not in 1..Last_ID ) then Raise_Exception (Status_Error'Identity, Callee_Init); elsif Method.Service.Parent /= Callee.Parent then Raise_Exception (Use_Error'Identity, Diff_Service); elsif Method.Service.Data.Index = Callee.Data.Index then Raise_Exception (Use_Error'Identity, Same_Process); end if; declare Caller : Call_Service'Class renames Method.Service.all; Target : Call_Service'Class renames Method.Service.Servers (Callee.Data.Index).all; Started : constant Time := Clock; Header : Request_Header := ( Mode => Synchronous_Request, Method => Method.Index, From => Caller.Data.Index, No => Initiate (Caller'Access) ); begin -- Trace -- ( Get_ID (Caller), -- ( "do procedure call " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- ) ); Seize (Target.Requests.Lock, Timeout); begin -- Trace -- ( Get_ID (Caller), -- ( "queue header " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " " -- & Image (Header) -- & " to " -- & Get_Offset (Target.Queue) -- ) ); if Is_Closed (Target.Requests.Stream) then Raise_Exception (Status_Error'Identity, Callee_Init); end if; Put (Target.Queue, Header, Timeout - (Clock - Started)); -- Trace -- ( Get_ID (Caller), -- ( "write parameters " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " to " -- & Get_Offset (Target.Requests.Stream) -- ) ); Send_Parameters (Target.Requests.Stream); -- Trace -- ( Get_ID (Caller), -- ( "write parameters done " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " to " -- & Get_Offset (Target.Requests.Stream) -- ) ); Release (Target.Requests.Lock); exception when End_Error => Release (Target.Requests.Lock); Raise_Exception (Status_Error'Identity, Caller_Init); when others => Release (Target.Requests.Lock); raise; end; begin Read_Response_Header (Caller, Header, Started, Timeout); exception when End_Error => Raise_Exception (Status_Error'Identity, Callee_Init); end; begin if Header.Mode = Failure_Response then -- Error -- Trace -- ( Get_ID (Caller), -- ( "reading exception occurrence " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- ) ); declare -- Receive exception ID and message Responses : Universal_Stream renames Caller.Responses.Stream; ID : constant Exception_ID := Exception_ID'Input (Responses'Access); Message : constant String := String'Input (Responses'Access); begin Raise_Exception (ID, Message); end; else -- Success -- Trace -- ( Get_ID (Caller), -- ( "reading results " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Target)) -- & " from " -- & Get_Offset (Caller.Responses.Stream) -- ) ); Receive_Results (Caller.Responses.Stream); Caller.Response.Complete; return; end if; exception when End_Error => Caller.Response.Complete; Raise_Exception (Status_Error'Identity, Callee_Init); when others => Caller.Response.Complete; raise; end; end; end Generic_Procedure_Call; function Get_ID (Service : Call_Service) return Call_Service_ID is begin if Service.Data = null then Raise_Exception (Status_Error'Identity, Service_Init); else return Service.Data.Index; end if; end Get_ID; function Get_ID (Method : Abstract_Method) return Method_ID is begin if Method.Index = Null_Method_ID then Raise_Exception (Status_Error'Identity, Method_Init); else return Method.Index; end if; end Get_ID; function Get_Process_ID (Service : Call_Service) return Process_ID is begin if Service.Data = null then Raise_Exception (Status_Error'Identity, Service_Init); else return Service.Data.Process; end if; end Get_Process_ID; function Get_Service ( Method : Abstract_Method; ID : Call_Service_ID ) return Call_Service_Ptr is begin if Method.Service = null then Raise_Exception (Status_Error'Identity, Method_Init); elsif Method.Service.Data = null then Raise_Exception (Status_Error'Identity, Service_Init); elsif ID not in Method.Service.Servers'Range then Raise_Exception ( Constraint_Error'Identity, "Invalid call service ID" ); else return Method.Service.Servers (ID); end if; end Get_Service; function Get_Service ( Method : Abstract_Method ) return Call_Service_Ptr is begin if Method.Service = null then Raise_Exception (Status_Error'Identity, Method_Init); elsif Method.Service.Data = null then Raise_Exception (Status_Error'Identity, Service_Init); else return Method.Service; end if; end Get_Service; function Get_Service_ID (Method : Abstract_Method) return Call_Service_ID is begin if Method.Service = null then Raise_Exception (Status_Error'Identity, Method_Init); elsif Method.Service.Data = null then Raise_Exception (Status_Error'Identity, Service_Init); else return Method.Service.Data.Index; end if; end Get_Service_ID; function Get_Size (Object : Call_Service_Stop) return Storage_Count is begin return 0; end Get_Size; function Get_Size (Object : Call_Service) return Storage_Count is begin return Round (Service_Data'Max_Size_In_Storage_Elements); end Get_Size; function Get_Size (Object : Abstract_Method) return Storage_Count is begin return 0; end Get_Size; function Image (ID : Call_Service_ID) return String is Result : constant String := Call_Service_ID'Image (ID); begin return Result (Result'First + 1..Result'Last); end Image; function Image (ID : Method_ID) return String is Result : constant String := Method_ID'Image (ID); begin return Result (Result'First + 1..Result'Last); end Image; function Image ( Request : Request_Header; Show_Mode : Boolean := True ) return String is Text : constant String := Sequence_No'Image (Request.No); begin if Show_Mode then return ( Request_Type'Image (Request.Mode) & "->" & Image (Request.Method) & ":" & Text (Text'First + 1..Text'Last) ); else return ( Image (Request.Method) & ":" & Text (Text'First + 1..Text'Last) ); end if; end Image; function Initiate (Caller : access Call_Service) return Sequence_No is function Increment ( Accumulator : access Sequence_No; Value : Sequence_No ) return Sequence_No; pragma Import ( Intrinsic, Increment, "__sync_add_and_fetch_8" ); begin return Increment (No'Access, 1); end Initiate; function Is_Server (Service : Call_Service) return Boolean is begin return Service.Mode = Server_Mode; end Is_Server; function Is_Synchronous (Method : Abstract_Method) return Boolean is begin return True; end Is_Synchronous; procedure Map ( Object : in out Call_Service_Stop; Shared : in out Abstract_Shared_Environment'Class; Location : System.Address; Size : Storage_Count; Owner : Boolean ) is begin null; end Map; procedure Map ( Object : in out Call_Service; Shared : in out Abstract_Shared_Environment'Class; Location : System.Address; Size : Storage_Count; Owner : Boolean ) is package Mapper is new Generic_Memory_Mapper (Service_Data); begin Object.Data := Mapper.Map (Location, Owner).all'Unchecked_Access; Object.Parent := Shared'Unchecked_Access; case Object.Mode is when Server_Mode => Set_Mode (Object.Queue, Out_End); Set_Mode (Object.Requests.Stream, In_End); Set_Mode (Object.Responses.Stream, In_End); declare -- Enumerate methods and servers This : Abstract_Shared_Object_Ptr := Shared.First; Method_No : Method_ID := 0; Server_No : Call_Service_ID := 0; Server_ID : Call_Service_ID := 0; Done : Boolean := False; -- No more methods Last_Method : Abstract_Method_Ptr; -- Last method Last_Server : Call_Service_Ptr; -- Last server begin while This /= null loop if This = Object'Unchecked_Access then Server_No := Server_No + 1; Server_ID := Server_No; Done := True; Object.Prev := Last_Server; Last_Server := Object'Unchecked_Access; elsif This.all in Call_Service'Class then declare Service : Call_Service'Class renames Call_Service'Class (This.all); begin Server_No := Server_No + 1; Service.Prev := Last_Server; Last_Server := Service'Unchecked_Access; end; elsif ( not Done and then This.all in Abstract_Method'Class ) then declare Method : Abstract_Method'Class renames Abstract_Method'Class (This.all); begin if Method.Service = null then -- A free method Method_No := Method_No + 1; Method.Index := Method_No; Method.Service := Object'Unchecked_Access; Method.Prev := Last_Method; Last_Method := Method'Unchecked_Access; end if; end; end if; This := This.Next; end loop; Object.Methods := new Method_Array (1..Method_No); while Method_No > 0 and then Last_Method /= null loop Object.Methods (Method_No) := Last_Method; Last_Method := Last_Method.Prev; Method_No := Method_No - 1; end loop; Object.Servers := new Call_Service_Array (1..Server_No); while Server_No > 0 and then Last_Server /= null loop Object.Servers (Server_No) := Last_Server; Last_Server := Last_Server.Prev; Server_No := Server_No - 1; end loop; Object.Data.Service := Object'Unchecked_Access; if ( Compare_And_Swap ( Object.Data.Index'Access, 0, Server_ID ) or else ( Compare_And_Swap ( Object.Data.Index'Access, Server_ID, Server_ID ) ) or else ( Compare_And_Swap ( Object.Data.Index'Access, Server_ID + Last_ID - 1, Server_ID ) ) ) then Object.Server_ID := Server_ID; Object.Data.Process := Get_Process_ID; Object.Data.Service := Object'Unchecked_Access; else Raise_Exception ( Mode_Error'Identity, ( "The service (no." & Image (Server_ID) & ") is already assigned to another " & "process (no." & Image (Object.Data.Index) & ")" ) ); end if; end; when Client_Mode => Set_Mode (Object.Queue, Multiplexed_In_End); Set_Mode (Object.Requests.Stream, Multiplexed_Out_End); Set_Mode (Object.Responses.Stream, Multiplexed_Out_End); end case; end Map; procedure Map ( Object : in out Abstract_Method; Shared : in out Abstract_Shared_Environment'Class; Location : System.Address; Size : Storage_Count; Owner : Boolean ) is begin null; end Map; procedure On_Start (Service : in out Call_Service) is begin null; end On_Start; procedure Read ( Stream : access Root_Stream_Type'Class; Request : out Request_Header ) is subtype Four_Elements is Stream_Element_Array (1..4); function To_32 is new Ada.Unchecked_Conversion (Four_Elements, Unsigned_32); subtype Eight_Elements is Stream_Element_Array (1..8); function To_64 is new Ada.Unchecked_Conversion (Eight_Elements, Unsigned_64); Data : Stream_Element_Array (1..17); Last : Stream_Element_Offset; begin Read (Stream.all, Data, Last); if Last /= 17 then raise End_Error; end if; Request.Mode := Request_Type'Val (Data (1)); Request.Method := Method_ID (To_32 (Data (2..5))); Request.From := Call_Service_ID (To_32 (Data (6..9))); Request.No := Sequence_No (To_64 (Data (10..17))); end Read; procedure Read ( Results : in out Null_Stream; Item : out Stream_Element_Array; Last : out Stream_Element_Offset ) is begin Last := Item'First - 1; end Read; procedure Read ( Results : in out Results_Stream; Item : out Stream_Element_Array; Last : out Stream_Element_Offset ) is begin Last := Item'First - 1; end Read; procedure Set_Server (Service : in out Call_Service) is begin if Service.Data /= null then Raise_Exception ( Status_Error'Identity, "Call service is already initialized" ); end if; Service.Mode := Server_Mode; Set_Mode (Service.Queue, Out_End); Set_Mode (Service.Requests.Stream, In_End); Set_Mode (Service.Responses.Stream, In_End); end Set_Server; procedure Start ( Object : in out Call_Service; Shared : in out Abstract_Shared_Environment'Class; Owner : Boolean ) is begin case Object.Mode is when Server_Mode => Object.Server := new Call_Server (Object'Unchecked_Access); when Client_Mode => null; end case; end Start; procedure Unmap ( Object : in out Call_Service_Stop; Shared : in out Abstract_Shared_Environment'Class; Owner : Boolean ) is procedure Free is new Ada.Unchecked_Deallocation ( Call_Server, Call_Server_Ptr ); procedure Free is new Ada.Unchecked_Deallocation ( Method_Array, Method_Array_Ptr ); procedure Free is new Ada.Unchecked_Deallocation ( Call_Service_Array, Call_Service_Array_Ptr ); This : Call_Service'Class renames Object.Parent.all; begin if This.Server /= null then Close (This.Requests.Stream); Close (This.Responses.Stream); Signal (This.Requests.Not_Empty); -- Wake up the readers Signal (This.Responses.Not_Empty); if Current_Task /= Main then This.Server.Quit; end if; while not This.Server'Terminated loop delay 0.001; end loop; if Compare_And_Swap ( This.Data.Index'Access, This.Server_ID, 0 ) then null; end if; This.Data.Process := Null_Process; Free (This.Server); Free (This.Methods); Free (This.Servers); end if; end Unmap; procedure Write ( Stream : access Root_Stream_Type'Class; Request : Request_Header ) is subtype Four_Elements is Stream_Element_Array (1..4); function To_32 is new Ada.Unchecked_Conversion (Unsigned_32, Four_Elements); subtype Eight_Elements is Stream_Element_Array (1..8); function To_64 is new Ada.Unchecked_Conversion (Unsigned_64, Eight_Elements); begin Write ( Stream.all, ( Stream_Element (Request_Type'Pos (Request.Mode)) & To_32 (Unsigned_32 (Request.Method)) & To_32 (Unsigned_32 (Request.From)) & To_64 (Unsigned_64 (Request.No)) ) ); end Write; procedure Write ( Results : in out Null_Stream; Item : Stream_Element_Array ) is begin null; end Write; procedure Write ( Results : in out Results_Stream; Item : Stream_Element_Array ) is Service : Call_Service'Class renames Results.Service.all; Stream : Universal_Stream renames Service.Responses.Stream; begin if not Results.Lock.Owner then -- Trace -- ( Get_ID (Results.Service.all), -- "taking results mutex to " & Get_Offset (Stream) -- ); Seize (Service.Responses.Lock, Service.Timeout); Results.Lock.Owner := True; -- Trace -- ( Get_ID (Results.Service.all), -- ( "writing results header " -- & Image (Results.Header) -- & " to " -- & Get_Offset (Stream) -- ) ); Request_Header'Write (Stream'Access, Results.Header); end if; -- Trace -- ( Get_ID (Results.Service.all), -- "writing results to " & Get_Offset (Stream) -- ); Write (Stream, Item); -- Trace -- ( Get_ID (Results.Service.all), -- "results written to " & Get_Offset (Stream) -- ); end Write; protected body Response_Event is entry Accept_Results (Header : Request_Header) when True is begin if Exiting then return; elsif Cancel'Count > 0 then Raise_Exception ( Data_Error'Identity, "Canceled due to communication error" ); end if; State := not State; Pending := True; Result := Header; requeue Accept_Results_Lounge with abort; end Accept_Results; entry Cancel when ( ( Accept_Results'Count + Incoming_Results (True)'Count + Incoming_Results (False)'Count ) = 0 ) is begin Pending := False; end Cancel; entry Incoming_Results (for Current in Boolean) ( Header : in out Request_Header; No_Header : out Boolean ) when ( Exiting or else Cancel'Count > 0 or else not Pending or else Current = State ) is begin if Exiting then Raise_Exception ( End_Error'Identity, "Process call service is exiting" ); elsif Cancel'Count > 0 then Raise_Exception ( Data_Error'Identity, "Canceled due to communication error" ); elsif Pending then -- Have a header already read if Header = Result then -- The expected header No_Header := False; Header.Mode := Result.Mode; else -- Wait for another header requeue Incoming_Results (not State); end if; else -- Free to read a header No_Header := True; Pending := True; end if; end Incoming_Results; entry Accept_Results_Lounge (Header : Request_Header) when not Pending or else Exiting is begin null; end Accept_Results_Lounge; entry Wait_For_Results ( Header : in out Request_Header; No_Header : out Boolean ) when True is begin if Exiting then Raise_Exception ( End_Error'Identity, "Process call service is exiting" ); elsif Cancel'Count > 0 then Raise_Exception ( Data_Error'Identity, "Canceled due to communication error" ); elsif Pending then -- Have a header already read if Header = Result then -- The expected header No_Header := False; Header.Mode := Result.Mode; else -- Wait for another header requeue Incoming_Results (not State); end if; else -- Free to read a header No_Header := True; Pending := True; end if; end Wait_For_Results; procedure Complete is begin Pending := False; end Complete; procedure Quit is begin Exiting := True; end Quit; end Response_Event; task body Call_Server is Current : Request_Header; Stock : Null_Stream; Empty : Boolean; begin On_Start (Service.all); while Is_Callable (Main) loop select accept Quit; exit; else null; end select; -- Trace -- ( Get_ID (Service.all), -- ( "wait for an incoming request to " -- & Get_Offset (Service.Queue) -- ) ); Wait_For_Not_Empty (Service.Queue, Service.Timeout, Empty); if not Empty then Current := Get (Service.Queue); -- Trace -- ( Get_ID (Service.all), -- ( "request " -- & Image (Current.From) -- & "->" -- & Image (Get_ID (Service.all)) -- & " header " -- & Image (Current) -- & " to " -- & Get_Offset (Service.Queue) -- ) ); declare Method : Abstract_Method'Class renames Service.Methods (Current.Method).all; Caller : Call_Service'Class renames Service.Servers (Current.From).all; begin if Current.Mode = Asynchronous_Request then begin Execute ( Method => Method, Parameters => Service.Requests.Stream, Results => Stock, Caller => Caller, No => Current.No ); exception when others => null; end; else begin if Is_Synchronous (Method) then declare Results : Results_Stream (Caller'Access); begin Results.Header.Mode := Success_Response; Results.Header.Method := Current.Method; Results.Header.From := Get_ID (Service.all); Results.Header.No := Current.No; -- Trace -- ( Get_ID (Service.all), -- ( "execute " -- & Image (Get_ID (Caller)) -- & "->" -- & Image (Get_ID (Service.all)) -- & " from " -- & Get_Offset (Caller.Responses.Stream) -- & " to " -- & Get_Offset (Service.Requests.Stream) -- ) ); Execute ( Method => Method, Parameters => Service.Requests.Stream, Results => Results, Caller => Caller, No => Current.No ); end; else Execute ( Method => Method, Parameters => Service.Requests.Stream, Results => Stock, Caller => Caller, No => Current.No ); end if; exception when Error : others => declare Stream : Universal_Stream renames Caller.Responses.Stream; Lock : Holder ( Caller.Responses.Lock'Access ); begin Current.Mode := Failure_Response; Current.From := Get_ID (Service.all); Request_Header'Write ( Stream'Access, Current ); Exception_Id'Write ( Stream'Access, Exception_Identity (Error) ); String'Output ( Stream'Access, Exception_Message (Error) ); exception when others => null; end; end; end if; end; end if; end loop; -- Trace (Get_ID (Service.all), "initiate exiting call service"); Close (Service.Requests.Stream); Close (Service.Responses.Stream); while not Is_Empty (Service.Queue) loop Current := Get (Service.Queue); -- Trace -- ( Get_ID (Service.all), -- ( "canceling request " -- & Image (Current.From) -- & "->" -- & Image (Get_ID (Service.all)) -- & " header " -- & Image (Current) -- & " to " -- & Get_Offset (Service.Queue) -- ) ); if Current.Mode /= Asynchronous_Request then declare Caller : Call_Service'Class renames Service.Servers (Current.From).all; Stream : Universal_Stream renames Caller.Responses.Stream; Lock : Holder (Caller.Responses.Lock'Access); begin Current.Mode := Cancel_Response; Current.From := Get_ID (Service.all); Request_Header'Write (Stream'Access, Current); exception when others => null; end; end if; end loop; -- Trace (Get_ID (Service.all), "*** exiting call service"); end Call_Server; begin Main := Current_Task; end Synchronization.Interprocess.Process_Call_Service;