From e921ad2e4c39f359838b63a6a7a1bcc2be157781 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Wed, 5 May 2021 14:45:53 +0200 Subject: [PATCH 01/10] Minor reformatting. --- src/core/aws-net-websocket-registry.adb | 1 + src/core/aws-net-websocket.adb | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 822b2774af..19c78ee03c 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -277,6 +277,7 @@ package body AWS.Net.WebSocket.Registry is DB.Remove (WS); Message_Queue.Add (WS); end if; + K := K + 1; end loop; end; diff --git a/src/core/aws-net-websocket.adb b/src/core/aws-net-websocket.adb index 0d91ac08cc..9cca5c9299 100644 --- a/src/core/aws-net-websocket.adb +++ b/src/core/aws-net-websocket.adb @@ -1,7 +1,7 @@ ------------------------------------------------------------------------------ -- Ada Web Server -- -- -- --- Copyright (C) 2012-2016, AdaCore -- +-- Copyright (C) 2012-2021, AdaCore -- -- -- -- This library is free software; you can redistribute it and/or modify -- -- it under terms of the GNU General Public License as published by the -- @@ -53,7 +53,8 @@ package body AWS.Net.WebSocket is end record; procedure Unchecked_Free is new Ada.Unchecked_Deallocation - (AWS.Client.HTTP_Connection, AWS.Client.HTTP_Connection_Access); + (AWS.Client.HTTP_Connection, AWS.Client.HTTP_Connection_Access); + procedure Unchecked_Free is new Unchecked_Deallocation (Net.WebSocket.Protocol.State'Class, Net.WebSocket.Protocol.State_Class); @@ -91,12 +92,12 @@ package body AWS.Net.WebSocket is (Socket : in out Object'Class; URI : String) is + URL : constant AWS.URL.Object := AWS.URL.Parse (URI); Headers : AWS.Headers.List := AWS.Headers.Empty_List; Resp : AWS.Response.Data; - Protocol : AWS.Net.WebSocket.Protocol.State_Class; - URL : constant AWS.URL.Object := AWS.URL.Parse (URI); + Protocol : Net.WebSocket.Protocol.State_Class; begin - -- Initially, the connection is initiated with standard http GET. + -- Initially, the connection is initiated with standard http GET Socket.Connection := new AWS.Client.HTTP_Connection; Protocol := new Net.WebSocket.Protocol.RFC6455.State; @@ -406,7 +407,7 @@ package body AWS.Net.WebSocket is function Poll (Socket : in out Object'Class; Timeout : Duration) - return Boolean + return Boolean is procedure Do_Receive (Socket : not null access Object'Class; @@ -427,11 +428,11 @@ package body AWS.Net.WebSocket is end Do_Receive; function Read_Message is new AWS.Net.WebSocket.Read_Message - (Receive => Do_Receive); + (Receive => Do_Receive); Obj : Object_Class := Socket'Unrestricted_Access; Event : AWS.Net.Event_Set; - Msg : Ada.Strings.Unbounded.Unbounded_String; + Msg : Unbounded_String; begin Event := Socket.Poll ((AWS.Net.Input => True, others => False), Timeout => Timeout); @@ -469,8 +470,8 @@ package body AWS.Net.WebSocket is ------------------ function Read_Message - (WebSocket : in out Object_Class; - Message : in out Ada.Strings.Unbounded.Unbounded_String) + (WebSocket : in out Object_Class; + Message : in out Unbounded_String) return Boolean is Data : Stream_Element_Array (1 .. 4_096); From 4ebed5987d91d02237c0d84b8c1e9298589aa416 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Thu, 6 May 2021 13:22:18 +0200 Subject: [PATCH 02/10] Make Shutdown_Signal atomic. This variable may be read/writen by different threads. For U506-012. --- src/core/aws-net-websocket-registry.adb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 19c78ee03c..72d1fb9916 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -134,7 +134,7 @@ package body AWS.Net.WebSocket.Registry is Message_Senders : Message_Sender_Set_Ref; - Shutdown_Signal : Boolean := False; + Shutdown_Signal : Boolean := False with Atomic; -- Concurrent access to Set above From c2bc2c2862cb76df4effcca83a9cb45f4678826d Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Tue, 4 May 2021 20:24:45 +0200 Subject: [PATCH 03/10] Fix WebSocket memory free to be thread safe. Also skip WebSocket that have a deferred free status. For U504-028. --- src/core/aws-net-websocket-registry.adb | 73 ++++++++++++++++++------- src/core/aws-net-websocket.ads | 6 +- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 72d1fb9916..8af29f3f30 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -1,7 +1,7 @@ ------------------------------------------------------------------------------ -- Ada Web Server -- -- -- --- Copyright (C) 2012-2019, AdaCore -- +-- Copyright (C) 2012-2021, AdaCore -- -- -- -- This library is free software; you can redistribute it and/or modify -- -- it under terms of the GNU General Public License as published by the -- @@ -160,10 +160,14 @@ package body AWS.Net.WebSocket.Registry is entry Get_Socket (WebSocket : out Object_Class); -- Get a WebSocket having some data to be sent - procedure Release_Socket (WebSocket : Object_Class); + procedure Release_Socket (WebSocket : in out Object_Class); -- Release a socket retrieved with Get_Socket above, this socket will be -- then available again. + procedure Free_Or_Defer (WebSocket : in out Object_Class); + -- Free WebSocket immediately if not taken by another task, otherwise + -- record it to be freed as soon as it is released. + entry Not_Empty; -- Returns if the Set is not empty @@ -313,7 +317,7 @@ package body AWS.Net.WebSocket.Registry is procedure Do_Free (WebSocket : in out Object_Class) is begin - Unchecked_Free (WebSocket); + DB.Free_Or_Defer (WebSocket); end Do_Free; ----------------- @@ -426,7 +430,7 @@ package body AWS.Net.WebSocket.Registry is end; WebSocket.Shutdown; - Unchecked_Free (WebSocket); + DB.Free_Or_Defer (WebSocket); end if; end Close_To; @@ -483,7 +487,9 @@ package body AWS.Net.WebSocket.Registry is Socket.On_Close (Message); Socket.Shutdown; - Unchecked_Free (W); + if W /= null then + DB.Free_Or_Defer (W); + end if; end Close; ---------------- @@ -500,9 +506,11 @@ package body AWS.Net.WebSocket.Registry is -- Add watched sockets for Id of Watched loop - FD_Set.Add - (Result, - Registered (Id).all, Registered (Id), FD_Set.Input); + if not Registered (Id).To_Free then + FD_Set.Add + (Result, + Registered (Id).all, Registered (Id), FD_Set.Input); + end if; end loop; end return; end Create_Set; @@ -539,7 +547,7 @@ package body AWS.Net.WebSocket.Registry is end; WebSocket.Shutdown; - Unchecked_Free (WebSocket); + DB.Free_Or_Defer (WebSocket); end On_Close; begin @@ -552,6 +560,24 @@ package body AWS.Net.WebSocket.Registry is Registered.Clear; end Finalize; + ---------- + -- Free -- + ---------- + + procedure Free_Or_Defer (WebSocket : in out Object_Class) is + begin + -- If WebSocket is in Sending it means that it has been + -- taken by the Get_Socket call. We cannot free it now, we + -- record this socket to be freed as soon as it is released + -- (Release_Socket) call. + + if Sending.Contains (WebSocket.Id) then + WebSocket.To_Free := True; + else + Unchecked_Free (WebSocket); + end if; + end Free_Or_Defer; + ---------------- -- Get_Socket -- ---------------- @@ -722,10 +748,19 @@ package body AWS.Net.WebSocket.Registry is -- Release_Socket -- -------------------- - procedure Release_Socket (WebSocket : Object_Class) is + procedure Release_Socket (WebSocket : in out Object_Class) is begin Sending.Exclude (WebSocket.Id); - New_Pending := True; + + -- The socket has been recorded to be freed. It is not anymore + -- in the registry, we just need to free it now that it has + -- been released. + + if WebSocket.To_Free then + Unchecked_Free (WebSocket); + else + New_Pending := True; + end if; end Release_Socket; ------------ @@ -853,7 +888,7 @@ package body AWS.Net.WebSocket.Registry is begin if Error = null then DB.Unregister (W); - Unchecked_Free (W); + DB.Free_Or_Defer (W); else Error (W.all, A); @@ -861,7 +896,7 @@ package body AWS.Net.WebSocket.Registry is case A is when Close => DB.Unregister (W); - Unchecked_Free (W); + DB.Free_Or_Defer (W); when None => null; end case; @@ -916,9 +951,7 @@ package body AWS.Net.WebSocket.Registry is WS.Close (Exception_Message (E), Going_Away); WS.On_Close (Exception_Message (E)); - -- ??? if we free it now, there might be a reader - -- in parallel that is using this socket... - Unchecked_Free (WS); + DB.Free_Or_Defer (WS); -- No more data to send from this socket Pending := 0; @@ -973,7 +1006,7 @@ package body AWS.Net.WebSocket.Registry is WebSocket.Send (Message); exception when E : others => - Unregister (WebSocket); + DB.Unregister (WebSocket); WebSocket_Exception (WebSocket, Exception_Message (E), Protocol_Error); @@ -981,7 +1014,7 @@ package body AWS.Net.WebSocket.Registry is WebSocket.Close (Exception_Message (E), Going_Away); -- Do not free, it might be used by another - Unchecked_Free (WebSocket); + DB.Free_Or_Defer (WebSocket); end; else @@ -1228,7 +1261,7 @@ package body AWS.Net.WebSocket.Registry is DB.Unregister (WS); WebSocket_Exception (WS, Exception_Message (E), Protocol_Error); - Unchecked_Free (WS); + DB.Free_Or_Defer (WS); -- No more data to send from this socket Pending := 0; end Read_Send; @@ -1507,7 +1540,7 @@ package body AWS.Net.WebSocket.Registry is DB.Watch (WebSocket); exception when others => - Unchecked_Free (WebSocket); + DB.Free_Or_Defer (WebSocket); raise; end Watch; diff --git a/src/core/aws-net-websocket.ads b/src/core/aws-net-websocket.ads index ac7d6c636a..4fbe198a97 100644 --- a/src/core/aws-net-websocket.ads +++ b/src/core/aws-net-websocket.ads @@ -1,7 +1,7 @@ ------------------------------------------------------------------------------ -- Ada Web Server -- -- -- --- Copyright (C) 2012-2020, AdaCore -- +-- Copyright (C) 2012-2021, AdaCore -- -- -- -- This library is free software; you can redistribute it and/or modify -- -- it under terms of the GNU General Public License as published by the -- @@ -289,6 +289,7 @@ private Messages : Message_List.List; Mem_Sock : Net.Socket_Access; In_Mem : Boolean := False; + To_Free : Boolean := False; Connection : AWS.Client.HTTP_Connection_Access; -- Only set when the web socket is initialized as a client. @@ -366,7 +367,8 @@ private Messages => Message_List.Empty_List, Mem_Sock => null, Connection => null, - In_Mem => False); + In_Mem => False, + To_Free => False); -- Error codes corresponding to all errors From 7a06f79812fb8ceb67e271cacc4269f5af8dd432 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Thu, 6 May 2021 13:23:32 +0200 Subject: [PATCH 04/10] Make sure a WebScket is freed only via the registry. This also make sure that the WebSocket is freed only when it is not used anymore. For U504-028. --- src/core/aws-net-websocket-registry.adb | 38 ++++++++++++++++--- src/core/aws-net-websocket-registry.ads | 10 ++++- src/core/aws-net-websocket.adb | 50 +++++++++++++++---------- src/core/aws-net-websocket.ads | 7 ++++ 4 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 8af29f3f30..e1d25bca43 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -164,6 +164,7 @@ package body AWS.Net.WebSocket.Registry is -- Release a socket retrieved with Get_Socket above, this socket will be -- then available again. + procedure Free_Or_Defer (Id : UID); procedure Free_Or_Defer (WebSocket : in out Object_Class); -- Free WebSocket immediately if not taken by another task, otherwise -- record it to be freed as soon as it is released. @@ -471,7 +472,7 @@ package body AWS.Net.WebSocket.Registry is Timeout : Duration := Forever; Error : Error_Type := Normal_Closure) is - W : Object_Class; + W : Object_Class := null; begin -- Look for WebSocket into the registered set, unregisted it if -- present. @@ -506,11 +507,13 @@ package body AWS.Net.WebSocket.Registry is -- Add watched sockets for Id of Watched loop - if not Registered (Id).To_Free then - FD_Set.Add - (Result, - Registered (Id).all, Registered (Id), FD_Set.Input); - end if; + declare + W : constant Object_Class := Registered (Id); + begin + if not W.To_Free then + FD_Set.Add (Result, W.all, W, FD_Set.Input); + end if; + end; end loop; end return; end Create_Set; @@ -573,11 +576,24 @@ package body AWS.Net.WebSocket.Registry is if Sending.Contains (WebSocket.Id) then WebSocket.To_Free := True; + else + if Registered.Contains (WebSocket.Id) then + Unregister (Registered (WebSocket.Id)); + end if; + + Release_Memory (Object (WebSocket.all)); Unchecked_Free (WebSocket); end if; end Free_Or_Defer; + procedure Free_Or_Defer (Id : UID) is + begin + if Registered.Contains (Id) then + Free_Or_Defer (Registered (Id)); + end if; + end Free_Or_Defer; + ---------------- -- Get_Socket -- ---------------- @@ -757,6 +773,7 @@ package body AWS.Net.WebSocket.Registry is -- been released. if WebSocket.To_Free then + Release_Memory (Object (WebSocket.all)); Unchecked_Free (WebSocket); else New_Pending := True; @@ -1211,6 +1228,15 @@ package body AWS.Net.WebSocket.Registry is end return; end Create; + ---------- + -- Free -- + ---------- + + procedure Free (WebSocket : in out Object'Class) is + begin + DB.Free_Or_Defer (WebSocket.Id); + end Free; + ------------------- -- Is_Registered -- ------------------- diff --git a/src/core/aws-net-websocket-registry.ads b/src/core/aws-net-websocket-registry.ads index 69702bbd28..49a902cb2a 100644 --- a/src/core/aws-net-websocket-registry.ads +++ b/src/core/aws-net-websocket-registry.ads @@ -1,7 +1,7 @@ ------------------------------------------------------------------------------ -- Ada Web Server -- -- -- --- Copyright (C) 2012-2019, AdaCore -- +-- Copyright (C) 2012-2021, AdaCore -- -- -- -- This library is free software; you can redistribute it and/or modify -- -- it under terms of the GNU General Public License as published by the -- @@ -176,10 +176,18 @@ package AWS.Net.WebSocket.Registry is Message : String; Timeout : Duration := Forever; Error : Error_Type := Normal_Closure); + -- Send a close message to the WebSocket, unregister it and release all + -- associated memory. function Is_Registered (Id : UID) return Boolean; -- Returns True if the WebSocket Id is registered and False otherwise + procedure Free (WebSocket : in out Object'Class); + -- Free the WebSocket when not used anymore (Either free immediatly or + -- register a deferred free). The difference with Close is that no message + -- is sent to the peer. The WebSocket is simply unregistered and memory is + -- released when possible. + private use GNAT.Regexp; diff --git a/src/core/aws-net-websocket.adb b/src/core/aws-net-websocket.adb index 9cca5c9299..048fab87f5 100644 --- a/src/core/aws-net-websocket.adb +++ b/src/core/aws-net-websocket.adb @@ -38,6 +38,7 @@ with AWS.Headers; with AWS.Messages; with AWS.Net.WebSocket.Protocol.Draft76; with AWS.Net.WebSocket.Protocol.RFC6455; +with AWS.Net.WebSocket.Registry; with AWS.Response; with AWS.Status.Set; with AWS.Translator; @@ -218,27 +219,8 @@ package body AWS.Net.WebSocket is ---------- overriding procedure Free (Socket : in out Object) is - use type AWS.Client.HTTP_Connection_Access; - procedure Unchecked_Free is - new Unchecked_Deallocation (Internal_State, Internal_State_Access); - procedure Unchecked_Free is - new Unchecked_Deallocation (Protocol_State, Protocol_State_Access); begin - Unchecked_Free (Socket.State); - - if Socket.P_State /= null then - Unchecked_Free (Socket.P_State.State); - Unchecked_Free (Socket.P_State); - end if; - - if Socket.Connection /= null then - -- Also closes Socket.Socket, since it is shared - Unchecked_Free (Socket.Connection); - else - Free (Socket.Socket); - end if; - - Free (Socket.Mem_Sock); + WebSocket.Registry.Free (Socket); end Free; -------------- @@ -562,6 +544,34 @@ package body AWS.Net.WebSocket is Socket.State.Last_Activity := Calendar.Clock; end Receive; + -------------------- + -- Release_Memory -- + -------------------- + + procedure Release_Memory (Socket : in out Object) is + use type AWS.Client.HTTP_Connection_Access; + procedure Unchecked_Free is + new Unchecked_Deallocation (Internal_State, Internal_State_Access); + procedure Unchecked_Free is + new Unchecked_Deallocation (Protocol_State, Protocol_State_Access); + begin + Unchecked_Free (Socket.State); + + if Socket.P_State /= null then + Unchecked_Free (Socket.P_State.State); + Unchecked_Free (Socket.P_State); + end if; + + if Socket.Connection /= null then + -- Also closes Socket.Socket, since it is shared + Unchecked_Free (Socket.Connection); + else + Free (Socket.Socket); + end if; + + Free (Socket.Mem_Sock); + end Release_Memory; + ------------- -- Request -- ------------- diff --git a/src/core/aws-net-websocket.ads b/src/core/aws-net-websocket.ads index 4fbe198a97..620627f3a9 100644 --- a/src/core/aws-net-websocket.ads +++ b/src/core/aws-net-websocket.ads @@ -352,6 +352,13 @@ private (Socket : Object; Size : Natural) is null; overriding procedure Free (Socket : in out Object); + -- Call free in the WebSocket registry which is possibly deferred until the + -- object is not used anymore. + + procedure Release_Memory (Socket : in out Object); + -- Release all memory used by the WebSocket object. This is called by + -- the registry only when the WebSocket is not used anymore by a possible + -- deferred free. No_UID : constant UID := 0; From e20c812a53dc223643e17847a4c7a3f9b17b58ea Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Fri, 14 May 2021 10:47:38 +0200 Subject: [PATCH 05/10] Make sure that reading & writing from WebSocket is safe. A WebSocket can be freed/released while reading or sending messages to/from a WebSocket. To handle this cas we record a WebSocket Id on the various containers and ensure that a WebSocket is still properly registered before handling it. This also ensure a deferred free of the WebSocket. Continued work for U504-028. --- src/core/aws-net-websocket-registry.adb | 71 +++++++++++++++++++------ 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index e1d25bca43..3c5565c776 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -76,7 +76,7 @@ package body AWS.Net.WebSocket.Registry is -- A queue for WebSocket with pending messages to be read - package WebSocket_Queue is new Utils.Mailbox_G (Object_Class); + package WebSocket_Queue is new Utils.Mailbox_G (UID); type Queue_Ref is access WebSocket_Queue.Mailbox; -- A list of all WebSockets in the registry, this list is used to send or @@ -98,7 +98,7 @@ package body AWS.Net.WebSocket.Registry is -- The socket set with all sockets to wait for data - package FD_Set is new Net.Generic_Sets (Object_Class); + package FD_Set is new Net.Generic_Sets (UID); use type FD_Set.Socket_Count; task type Watcher with Priority => Config.WebSocket_Priority is @@ -160,6 +160,10 @@ package body AWS.Net.WebSocket.Registry is entry Get_Socket (WebSocket : out Object_Class); -- Get a WebSocket having some data to be sent + procedure Get_Socket (Id : UID; WebSocket : out Object_Class); + -- Get a WebSocket Id or null if the WebSocket is not registerred + -- anymore. + procedure Release_Socket (WebSocket : in out Object_Class); -- Release a socket retrieved with Get_Socket above, this socket will be -- then available again. @@ -273,14 +277,21 @@ package body AWS.Net.WebSocket.Registry is -- signaling socket. declare - -- Skip first entry as it is not a websocket - K : FD_Set.Socket_Count := 2; + -- Skip first entry as it is the signaling socket not a + -- websocket. + K : FD_Set.Socket_Count := 2; + WS_Id : UID; begin while K <= FD_Set.Count (Set) loop if FD_Set.Is_Read_Ready (Set, K) then - WS := FD_Set.Get_Data (Set, K); - DB.Remove (WS); - Message_Queue.Add (WS); + WS_Id := FD_Set.Get_Data (Set, K); + DB.Get_Socket (WS_Id, WS); + + if WS /= null then + DB.Remove (WS); + Message_Queue.Add (WS_Id); + DB.Release_Socket (WS); + end if; end if; K := K + 1; @@ -292,11 +303,16 @@ package body AWS.Net.WebSocket.Registry is -- Send a On_Error message to all registered clients for K in 2 .. FD_Set.Count (Set) loop - WS := FD_Set.Get_Data (Set, K); - WS.State.Errno := Error_Code (Internal_Server_Error); - WS.On_Error - ("WebSocket Watcher server error, " - & Exception_Message (E)); + DB.Get_Socket (FD_Set.Get_Data (Set, K), WS); + + if WS /= null then + WS.State.Errno := Error_Code (Internal_Server_Error); + WS.On_Error + ("WebSocket Watcher server error, " + & Exception_Message (E)); + end if; + + DB.Release_Socket (WS); end loop; end; end loop; @@ -348,16 +364,19 @@ package body AWS.Net.WebSocket.Registry is begin Handle_Message : loop declare + WS_Id : UID; WebSocket : Object_Class; Message : Unbounded_String; begin Message := Null_Unbounded_String; - Message_Queue.Get (WebSocket); + Message_Queue.Get (WS_Id); + + DB.Get_Socket (WS_Id, WebSocket); -- A WebSocket is null when termination is requested - exit Handle_Message when WebSocket = null; + exit Handle_Message when WS_Id = No_UID or else WebSocket = null; -- A message can be sent in multiple chunks and/or multiple -- frames with possibly some control frames in between text or @@ -367,6 +386,8 @@ package body AWS.Net.WebSocket.Registry is exit when Read_Message (WebSocket, Message); end loop; + DB.Release_Socket (WebSocket); + exception when E : others => Do_Unregister (WebSocket); @@ -374,6 +395,7 @@ package body AWS.Net.WebSocket.Registry is (WebSocket, Exception_Message (E), Protocol_Error); WebSocket.On_Close (Exception_Message (E)); WebSocket.Shutdown; + DB.Release_Socket (WebSocket); Do_Free (WebSocket); end; end loop Handle_Message; @@ -502,7 +524,7 @@ package body AWS.Net.WebSocket.Registry is return Result : FD_Set.Socket_Set_Type do -- Add the signaling socket - FD_Set.Add (Result, Sig1, null, FD_Set.Input); + FD_Set.Add (Result, Sig1, No_UID, FD_Set.Input); -- Add watched sockets @@ -511,7 +533,7 @@ package body AWS.Net.WebSocket.Registry is W : constant Object_Class := Registered (Id); begin if not W.To_Free then - FD_Set.Add (Result, W.all, W, FD_Set.Input); + FD_Set.Add (Result, W.all, W.Id, FD_Set.Input); end if; end; end loop; @@ -657,6 +679,21 @@ package body AWS.Net.WebSocket.Registry is end; end Get_Socket; + ---------------- + -- Get_Socket -- + ---------------- + + procedure Get_Socket (Id : UID; WebSocket : out Object_Class) is + begin + if Registered.Contains (Id) then + Sending.Insert (Id); + + WebSocket := Registered (Id); + else + WebSocket := null; + end if; + end Get_Socket; + ---------------- -- Initialize -- ---------------- @@ -1511,7 +1548,7 @@ package body AWS.Net.WebSocket.Registry is -- Now shutdown all the message readers for K in Message_Readers'Range loop - Message_Queue.Add (null); + Message_Queue.Add (No_UID); end loop; for K in Message_Readers'Range loop From 0342c7c341f36e7d57cd357c393eda6f3d5d44dc Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 17 May 2021 16:56:21 +0200 Subject: [PATCH 06/10] Remove duplicate calls to On_Close and Shutdown. Both routines are called as part of WebSocket_Exception. Continued work for U504-028. --- src/core/aws-net-websocket-registry.adb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 3c5565c776..51749abe08 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -393,8 +393,6 @@ package body AWS.Net.WebSocket.Registry is Do_Unregister (WebSocket); WebSocket_Exception (WebSocket, Exception_Message (E), Protocol_Error); - WebSocket.On_Close (Exception_Message (E)); - WebSocket.Shutdown; DB.Release_Socket (WebSocket); Do_Free (WebSocket); end; @@ -1003,7 +1001,6 @@ package body AWS.Net.WebSocket.Registry is (WS, Exception_Message (E), Protocol_Error); WS.Close (Exception_Message (E), Going_Away); - WS.On_Close (Exception_Message (E)); DB.Free_Or_Defer (WS); @@ -1064,7 +1061,6 @@ package body AWS.Net.WebSocket.Registry is WebSocket_Exception (WebSocket, Exception_Message (E), Protocol_Error); - WebSocket.On_Close (Exception_Message (E)); WebSocket.Close (Exception_Message (E), Going_Away); -- Do not free, it might be used by another From d1a593108acd7d33717fc6da27852960603ef1f0 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 17 May 2021 17:00:33 +0200 Subject: [PATCH 07/10] Move sending status to a WebSocket field instead of a containers. Continued work for U504-028. --- src/core/aws-net-websocket-registry.adb | 21 +++++++++------------ src/core/aws-net-websocket.ads | 4 +++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 51749abe08..2b4f1bbf6b 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -236,8 +236,6 @@ package body AWS.Net.WebSocket.Registry is New_Pending : Boolean := False; -- New pending socket Count : Natural := 0; -- Not counting signaling socket Registered : WebSocket_Map.Map; -- Contains all the WebSocket ref - Sending : WebSocket_Set.Set; -- Socket being handed to Sender task - Pending : WebSocket_List.List; -- Pending messages to be sent Watched : WebSocket_Set.Set; @@ -594,7 +592,7 @@ package body AWS.Net.WebSocket.Registry is -- record this socket to be freed as soon as it is released -- (Release_Socket) call. - if Sending.Contains (WebSocket.Id) then + if WebSocket.Sending then WebSocket.To_Free := True; else @@ -641,7 +639,6 @@ package body AWS.Net.WebSocket.Registry is -- Look for a socket not yet being handled declare - use type WebSocket_List.Cursor; Pos : WebSocket_List.Cursor := Pending.First; Id : UID; WS : Object_Class; @@ -649,11 +646,11 @@ package body AWS.Net.WebSocket.Registry is while Pos /= WebSocket_List.No_Element loop Id := Pending (Pos); - -- Check if this socket is not yet being used by a sender task + WS := Registered (Id); - if not Sending.Contains (Id) then - WS := Registered (Id); + -- Check if this socket is not yet being used by a sender task + if WS /= null and then not WS.Sending then -- Check that some messages are to be sent. This is needed -- as some messages could have been dropped if the list was -- too long to avoid congestion. @@ -661,7 +658,7 @@ package body AWS.Net.WebSocket.Registry is if WS.Messages.Length > 0 then Pending.Delete (Pos); WebSocket := WS; - Sending.Insert (Id); + WebSocket.Sending := True; return; end if; end if; @@ -684,9 +681,9 @@ package body AWS.Net.WebSocket.Registry is procedure Get_Socket (Id : UID; WebSocket : out Object_Class) is begin if Registered.Contains (Id) then - Sending.Insert (Id); WebSocket := Registered (Id); + WebSocket.Sending := True; else WebSocket := null; end if; @@ -801,7 +798,7 @@ package body AWS.Net.WebSocket.Registry is procedure Release_Socket (WebSocket : in out Object_Class) is begin - Sending.Exclude (WebSocket.Id); + WebSocket.Sending := False; -- The socket has been recorded to be freed. It is not anymore -- in the registry, we just need to free it now that it has @@ -886,7 +883,7 @@ package body AWS.Net.WebSocket.Registry is -- send task (Asynchronously). if Asynchronous - or else Sending.Contains (WebSocket.Id) + or else WebSocket.Sending then declare M : constant Message_Data := @@ -1146,7 +1143,7 @@ package body AWS.Net.WebSocket.Registry is procedure Unregister (WebSocket : not null access Object'Class) is begin Registered.Exclude (WebSocket.Id); - Sending.Exclude (WebSocket.Id); + WebSocket.Sending := False; Remove (WebSocket); Signal_Socket; diff --git a/src/core/aws-net-websocket.ads b/src/core/aws-net-websocket.ads index 620627f3a9..be7b557269 100644 --- a/src/core/aws-net-websocket.ads +++ b/src/core/aws-net-websocket.ads @@ -290,6 +290,7 @@ private Mem_Sock : Net.Socket_Access; In_Mem : Boolean := False; To_Free : Boolean := False; + Sending : Boolean := False; Connection : AWS.Client.HTTP_Connection_Access; -- Only set when the web socket is initialized as a client. @@ -375,7 +376,8 @@ private Mem_Sock => null, Connection => null, In_Mem => False, - To_Free => False); + To_Free => False, + Sending => False); -- Error codes corresponding to all errors From 18f856a0e00825f97f9028500b700b9c1dea0621 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 17 May 2021 17:03:12 +0200 Subject: [PATCH 08/10] Encapsulate some calls into an exception handler. Some actions must never fail to ensure that the WebSocket gets eventually released. Continued work for U504-028. --- src/core/aws-net-websocket-registry.adb | 130 ++++++++++++++++++------ 1 file changed, 99 insertions(+), 31 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 2b4f1bbf6b..45efec0eee 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -162,7 +162,8 @@ package body AWS.Net.WebSocket.Registry is procedure Get_Socket (Id : UID; WebSocket : out Object_Class); -- Get a WebSocket Id or null if the WebSocket is not registerred - -- anymore. + -- anymore. Marks the socket as in use, so that it will not get + -- freed until Release_Socket is called. procedure Release_Socket (WebSocket : in out Object_Class); -- Release a socket retrieved with Get_Socket above, this socket will be @@ -286,8 +287,16 @@ package body AWS.Net.WebSocket.Registry is DB.Get_Socket (WS_Id, WS); if WS /= null then - DB.Remove (WS); - Message_Queue.Add (WS_Id); + -- We should never fail to ensure the WebSocket is + -- properly released. + begin + DB.Remove (WS); + Message_Queue.Add (WS_Id); + exception + when others => + null; + end; + DB.Release_Socket (WS); end if; end if; @@ -303,12 +312,19 @@ package body AWS.Net.WebSocket.Registry is for K in 2 .. FD_Set.Count (Set) loop DB.Get_Socket (FD_Set.Get_Data (Set, K), WS); - if WS /= null then - WS.State.Errno := Error_Code (Internal_Server_Error); - WS.On_Error - ("WebSocket Watcher server error, " - & Exception_Message (E)); - end if; + -- We should never fail to ensure the WebSocket is + -- properly released. + begin + if WS /= null then + WS.State.Errno := Error_Code (Internal_Server_Error); + WS.On_Error + ("WebSocket Watcher server error, " + & Exception_Message (E)); + end if; + exception + when others => + null; + end; DB.Release_Socket (WS); end loop; @@ -332,7 +348,10 @@ package body AWS.Net.WebSocket.Registry is procedure Do_Free (WebSocket : in out Object_Class) is begin - DB.Free_Or_Defer (WebSocket); + DB.Free_Or_Defer (WebSocket.Id); + exception + when others => + null; end Do_Free; ----------------- @@ -342,6 +361,9 @@ package body AWS.Net.WebSocket.Registry is procedure Do_Register (WebSocket : Object_Class) is begin DB.Watch (WebSocket); + exception + when others => + null; end Do_Register; ------------------- @@ -351,6 +373,9 @@ package body AWS.Net.WebSocket.Registry is procedure Do_Unregister (WebSocket : Object_Class) is begin DB.Unregister (WebSocket); + exception + when others => + null; end Do_Unregister; function Read_Message is new AWS.Net.WebSocket.Read_Message @@ -370,11 +395,15 @@ package body AWS.Net.WebSocket.Registry is Message_Queue.Get (WS_Id); + -- Got a signaling socket + + exit Handle_Message when WS_Id = No_UID; + DB.Get_Socket (WS_Id, WebSocket); -- A WebSocket is null when termination is requested - exit Handle_Message when WS_Id = No_UID or else WebSocket = null; + exit Handle_Message when WebSocket = null; -- A message can be sent in multiple chunks and/or multiple -- frames with possibly some control frames in between text or @@ -388,9 +417,17 @@ package body AWS.Net.WebSocket.Registry is exception when E : others => - Do_Unregister (WebSocket); - WebSocket_Exception - (WebSocket, Exception_Message (E), Protocol_Error); + -- We should never fail to ensure the WebSocket is + -- properly released. + begin + Do_Unregister (WebSocket); + WebSocket_Exception + (WebSocket, Exception_Message (E), Protocol_Error); + exception + when others => + null; + end; + DB.Release_Socket (WebSocket); Do_Free (WebSocket); end; @@ -503,7 +540,16 @@ package body AWS.Net.WebSocket.Registry is Socket.State.Errno := Error_Code (Error); Socket.Set_Timeout (Timeout); Socket.Close (Message, Error); - Socket.On_Close (Message); + + -- Never fail on user's callback + + begin + Socket.On_Close (Message); + exception + when others => + null; + end; + Socket.Shutdown; if W /= null then @@ -581,9 +627,9 @@ package body AWS.Net.WebSocket.Registry is Registered.Clear; end Finalize; - ---------- - -- Free -- - ---------- + ------------------- + -- Free_Or_Defer -- + ------------------- procedure Free_Or_Defer (WebSocket : in out Object_Class) is begin @@ -606,9 +652,10 @@ package body AWS.Net.WebSocket.Registry is end Free_Or_Defer; procedure Free_Or_Defer (Id : UID) is + C : constant WebSocket_Map.Cursor := Registered.Find (Id); begin - if Registered.Contains (Id) then - Free_Or_Defer (Registered (Id)); + if WebSocket_Map.Has_Element (C) then + Free_Or_Defer (Registered (C)); end if; end Free_Or_Defer; @@ -643,7 +690,7 @@ package body AWS.Net.WebSocket.Registry is Id : UID; WS : Object_Class; begin - while Pos /= WebSocket_List.No_Element loop + while WebSocket_List.Has_Element (Pos) loop Id := Pending (Pos); WS := Registered (Id); @@ -679,10 +726,10 @@ package body AWS.Net.WebSocket.Registry is ---------------- procedure Get_Socket (Id : UID; WebSocket : out Object_Class) is + C : constant WebSocket_Map.Cursor := Registered.Find (Id); begin - if Registered.Contains (Id) then - - WebSocket := Registered (Id); + if WebSocket_Map.Has_Element (C) then + WebSocket := Registered (C); WebSocket.Sending := True; else WebSocket := null; @@ -994,10 +1041,16 @@ package body AWS.Net.WebSocket.Registry is exception when E : others => Unregister (WS); - WebSocket_Exception - (WS, Exception_Message (E), Protocol_Error); - WS.Close (Exception_Message (E), Going_Away); + begin + WebSocket_Exception + (WS, Exception_Message (E), Protocol_Error); + + WS.Close (Exception_Message (E), Going_Away); + exception + when others => + null; + end; DB.Free_Or_Defer (WS); @@ -1055,8 +1108,15 @@ package body AWS.Net.WebSocket.Registry is exception when E : others => DB.Unregister (WebSocket); - WebSocket_Exception - (WebSocket, Exception_Message (E), Protocol_Error); + + begin + WebSocket_Exception + (WebSocket, Exception_Message (E), + Protocol_Error); + exception + when others => + null; + end; WebSocket.Close (Exception_Message (E), Going_Away); @@ -1315,8 +1375,15 @@ package body AWS.Net.WebSocket.Registry is exception when E : others => DB.Unregister (WS); - WebSocket_Exception - (WS, Exception_Message (E), Protocol_Error); + + begin + WebSocket_Exception + (WS, Exception_Message (E), Protocol_Error); + exception + when others => + null; + end; + DB.Free_Or_Defer (WS); -- No more data to send from this socket Pending := 0; @@ -1596,6 +1663,7 @@ package body AWS.Net.WebSocket.Registry is DB.Watch (WebSocket); exception when others => + DB.Unregister (WebSocket); DB.Free_Or_Defer (WebSocket); raise; end Watch; From e14061e6b6b21e62654dc4ff069ef336e5fc1d96 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 17 May 2021 22:54:13 +0200 Subject: [PATCH 09/10] Make sure the state To_Free & Sending are shared. Continued work for U504-028. --- src/core/aws-net-websocket-registry.adb | 20 ++++++++++---------- src/core/aws-net-websocket.adb | 4 +++- src/core/aws-net-websocket.ads | 8 +++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 45efec0eee..8754b86af1 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -574,7 +574,7 @@ package body AWS.Net.WebSocket.Registry is declare W : constant Object_Class := Registered (Id); begin - if not W.To_Free then + if not W.State.To_Free then FD_Set.Add (Result, W.all, W.Id, FD_Set.Input); end if; end; @@ -638,8 +638,8 @@ package body AWS.Net.WebSocket.Registry is -- record this socket to be freed as soon as it is released -- (Release_Socket) call. - if WebSocket.Sending then - WebSocket.To_Free := True; + if WebSocket.State.Sending then + WebSocket.State.To_Free := True; else if Registered.Contains (WebSocket.Id) then @@ -697,7 +697,7 @@ package body AWS.Net.WebSocket.Registry is -- Check if this socket is not yet being used by a sender task - if WS /= null and then not WS.Sending then + if WS /= null and then not WS.State.Sending then -- Check that some messages are to be sent. This is needed -- as some messages could have been dropped if the list was -- too long to avoid congestion. @@ -705,7 +705,7 @@ package body AWS.Net.WebSocket.Registry is if WS.Messages.Length > 0 then Pending.Delete (Pos); WebSocket := WS; - WebSocket.Sending := True; + WebSocket.State.Sending := True; return; end if; end if; @@ -730,7 +730,7 @@ package body AWS.Net.WebSocket.Registry is begin if WebSocket_Map.Has_Element (C) then WebSocket := Registered (C); - WebSocket.Sending := True; + WebSocket.State.Sending := True; else WebSocket := null; end if; @@ -845,13 +845,13 @@ package body AWS.Net.WebSocket.Registry is procedure Release_Socket (WebSocket : in out Object_Class) is begin - WebSocket.Sending := False; + WebSocket.State.Sending := False; -- The socket has been recorded to be freed. It is not anymore -- in the registry, we just need to free it now that it has -- been released. - if WebSocket.To_Free then + if WebSocket.State.To_Free then Release_Memory (Object (WebSocket.all)); Unchecked_Free (WebSocket); else @@ -930,7 +930,7 @@ package body AWS.Net.WebSocket.Registry is -- send task (Asynchronously). if Asynchronous - or else WebSocket.Sending + or else WebSocket.State.Sending then declare M : constant Message_Data := @@ -1203,7 +1203,7 @@ package body AWS.Net.WebSocket.Registry is procedure Unregister (WebSocket : not null access Object'Class) is begin Registered.Exclude (WebSocket.Id); - WebSocket.Sending := False; + WebSocket.State.Sending := False; Remove (WebSocket); Signal_Socket; diff --git a/src/core/aws-net-websocket.adb b/src/core/aws-net-websocket.adb index 048fab87f5..410e4ccbf6 100644 --- a/src/core/aws-net-websocket.adb +++ b/src/core/aws-net-websocket.adb @@ -310,7 +310,9 @@ package body AWS.Net.WebSocket is Self.State := new Internal_State' (Kind => Unknown, Errno => Interfaces.Unsigned_16'Last, - Last_Activity => Calendar.Clock); + Last_Activity => Calendar.Clock, + To_Free => False, + Sending => False); Self.P_State := new Protocol_State'(State => Protocol); Self.Mem_Sock := null; Self.In_Mem := False; diff --git a/src/core/aws-net-websocket.ads b/src/core/aws-net-websocket.ads index be7b557269..886521931a 100644 --- a/src/core/aws-net-websocket.ads +++ b/src/core/aws-net-websocket.ads @@ -265,6 +265,8 @@ private Kind : Kind_Type := Unknown; Errno : Interfaces.Unsigned_16 := Interfaces.Unsigned_16'Last; Last_Activity : Calendar.Time; + To_Free : Boolean := False; + Sending : Boolean := False; end record; type Internal_State_Access is access Internal_State; @@ -289,8 +291,6 @@ private Messages : Message_List.List; Mem_Sock : Net.Socket_Access; In_Mem : Boolean := False; - To_Free : Boolean := False; - Sending : Boolean := False; Connection : AWS.Client.HTTP_Connection_Access; -- Only set when the web socket is initialized as a client. @@ -375,9 +375,7 @@ private Messages => Message_List.Empty_List, Mem_Sock => null, Connection => null, - In_Mem => False, - To_Free => False, - Sending => False); + In_Mem => False); -- Error codes corresponding to all errors From 531a93bd2efd7e529f0a007dd1e20d5886bc99e6 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Wed, 19 May 2021 13:51:47 +0200 Subject: [PATCH 10/10] Make Get_Socket blocking. Continued work for U504-028. --- src/core/aws-net-websocket-registry.adb | 44 +++++++++++++++++++------ 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/core/aws-net-websocket-registry.adb b/src/core/aws-net-websocket-registry.adb index 8754b86af1..0e46cf5e8d 100644 --- a/src/core/aws-net-websocket-registry.adb +++ b/src/core/aws-net-websocket-registry.adb @@ -160,7 +160,7 @@ package body AWS.Net.WebSocket.Registry is entry Get_Socket (WebSocket : out Object_Class); -- Get a WebSocket having some data to be sent - procedure Get_Socket (Id : UID; WebSocket : out Object_Class); + entry Get_Socket (Id : UID; WebSocket : out Object_Class); -- Get a WebSocket Id or null if the WebSocket is not registerred -- anymore. Marks the socket as in use, so that it will not get -- freed until Release_Socket is called. @@ -235,6 +235,7 @@ package body AWS.Net.WebSocket.Registry is Signal : Boolean := False; -- Transient signal, release Not_Emtpy S_Signal : Boolean := False; -- Shutdown is in progress New_Pending : Boolean := False; -- New pending socket + New_State : Boolean := False; -- A sokcet has been released Count : Natural := 0; -- Not counting signaling socket Registered : WebSocket_Map.Map; -- Contains all the WebSocket ref Pending : WebSocket_List.List; -- Pending messages to be sent @@ -638,14 +639,14 @@ package body AWS.Net.WebSocket.Registry is -- record this socket to be freed as soon as it is released -- (Release_Socket) call. + if Registered.Contains (WebSocket.Id) then + Unregister (Registered (WebSocket.Id)); + end if; + if WebSocket.State.Sending then WebSocket.State.To_Free := True; else - if Registered.Contains (WebSocket.Id) then - Unregister (Registered (WebSocket.Id)); - end if; - Release_Memory (Object (WebSocket.all)); Unchecked_Free (WebSocket); end if; @@ -725,13 +726,29 @@ package body AWS.Net.WebSocket.Registry is -- Get_Socket -- ---------------- - procedure Get_Socket (Id : UID; WebSocket : out Object_Class) is + entry Get_Socket (Id : UID; WebSocket : out Object_Class) + when New_State or else S_Signal + is C : constant WebSocket_Map.Cursor := Registered.Find (Id); begin + New_State := False; + if WebSocket_Map.Has_Element (C) then - WebSocket := Registered (C); - WebSocket.State.Sending := True; + declare + W : constant Object_Class := Registered (C); + begin + if W.State.Sending then + requeue Get_Socket; + + else + WebSocket := W; + WebSocket.State.Sending := True; + end if; + end; + else + -- The socket is not registerred anymore, just leave now + WebSocket := null; end if; end Get_Socket; @@ -837,6 +854,7 @@ package body AWS.Net.WebSocket.Registry is Registered.Insert (WebSocket.Id, WebSocket); Success := True; + New_State := True; end Register; -------------------- @@ -857,6 +875,8 @@ package body AWS.Net.WebSocket.Registry is else New_Pending := True; end if; + + New_State := True; end Release_Socket; ------------ @@ -1204,6 +1224,7 @@ package body AWS.Net.WebSocket.Registry is begin Registered.Exclude (WebSocket.Id); WebSocket.State.Sending := False; + New_State := True; Remove (WebSocket); Signal_Socket; @@ -1416,9 +1437,12 @@ package body AWS.Net.WebSocket.Registry is begin Send (WS, Message); WS.Messages.Delete_First; - - DB.Release_Socket (WS); + exception + when others => + null; end; + + DB.Release_Socket (WS); end loop; end Message_Sender;