diff --git a/packages/fcl-web/src/websocket/fpwebsocket.pp b/packages/fcl-web/src/websocket/fpwebsocket.pp index cbf9db0ced0fee880dd89cbaa31fc50402dfbed8..e656095a00d637941f1ffa6356313ff071b17d55 100644 --- a/packages/fcl-web/src/websocket/fpwebsocket.pp +++ b/packages/fcl-web/src/websocket/fpwebsocket.pp @@ -216,6 +216,7 @@ TWSTransport = class(TObject, IWSTransport) Constructor Create(aStream : TSocketStream); Destructor Destroy; override; Procedure CloseSocket; + Procedure InterruptRead; Property Helper : TWSSocketHelper Read FHelper Implements IWSTransport; Property Socket : TSocketStream Read GetSocket; end; @@ -602,6 +603,21 @@ procedure TWSTransport.CloseSocket; {$IFDEF FPC_DOTTEDUNITS}System.Net.{$ENDIF}sockets.CloseSocket(FStream.Handle); end; +procedure TWSTransport.InterruptRead; +begin + if not Assigned(FStream) then + Exit; + if FStream.Closed then + Exit; + + { Wake a thread blocked below SSL_read/recv without closing the descriptor + or touching the TLS handler. Connection and TLS object destruction must + remain on the owning thread after the reader has been joined. } + {$IFDEF FPC_DOTTEDUNITS}System.Net.{$ENDIF}sockets.fpShutdown( + FStream.Handle, + {$IFDEF FPC_DOTTEDUNITS}System.Net.{$ENDIF}sockets.SHUT_RDWR); +end; + { TWSTransport } constructor TWSSocketHelper.Create(aSocket: TSocketStream); diff --git a/packages/fcl-web/src/websocket/fpwebsocketclient.pp b/packages/fcl-web/src/websocket/fpwebsocketclient.pp index e3b36dc21904e1046c57cd09b1f797dfea79942c..5e9a8031bf9c20179ac17908802b911ce6836dee 100644 --- a/packages/fcl-web/src/websocket/fpwebsocketclient.pp +++ b/packages/fcl-web/src/websocket/fpwebsocketclient.pp @@ -40,12 +40,14 @@ interface TWSMessagePump = Class (TComponent) private FInterval:Integer; + FInterruptList: TThreadList; FList: TThreadList; FReads: TSocketStreamArray; FExceptions : TSocketStreamArray; FOnError: TWSErrorEvent; procedure SetInterval(AValue: Integer); Protected + Procedure InterruptConnections; function WaitForData: Boolean; Function CheckConnections : Boolean; virtual; Procedure ReadConnections; @@ -66,16 +68,17 @@ interface TWSThreadMessagePump = Class(TWSMessagePump) Private FThread : TThread; - Procedure ThreadTerminated(Sender : TObject); Protected Type TMessageDriverThread = Class(TThread) Public FPump : TWSThreadMessagePump; - Constructor Create(aPump : TWSThreadMessagePump; aTerminate : TNotifyEvent); + Constructor Create(aPump : TWSThreadMessagePump; + aTerminate : TNotifyEvent); Procedure Execute;override; End; Public + Destructor Destroy; override; Procedure Execute; override; Procedure Terminate; override; End; @@ -132,6 +135,8 @@ TWebSocketClientConnection = class(TWSClientConnection) procedure SetAutoCheckMessages(const Value: Boolean); procedure SendHeaders(aHeaders: TStrings); procedure ConnectionDisconnected(Sender: TObject); + procedure MessagePumpDisconnected( + AConnection: TWebSocketClientConnection); Protected Procedure CheckInactive; Procedure Loaded; override; @@ -286,6 +291,20 @@ procedure TCustomWebsocketClient.ConnectionDisconnected(Sender : TObject); // We cannot free the connection here, because it still needs to call it's own OnDisconnect. end; +procedure TCustomWebsocketClient.MessagePumpDisconnected( + AConnection: TWebSocketClientConnection); +begin + if FConnection<>AConnection then + Exit; + + { ReadConnections already removed this connection from the pump's locked + list. Keep the connection object alive until the owner reconnects or is + destroyed, matching ConnectionDisconnected's lifetime rule. } + FActive:=False; + if Assigned(OnDisconnect) then + OnDisconnect(AConnection); +end; + procedure TCustomWebsocketClient.Connect; var SSLHandler: TSSLSocketHandler; @@ -577,12 +596,25 @@ procedure TCustomWebsocketClient.SetUseSSL(const Value: Boolean); procedure TWSMessagePump.AddClient(aConnection: TWSClientConnection); begin - List.Add(aConnection); + { Keep interruption registration independent from FList. ReadConnections + holds FList while it reads a complete frame, so termination must not need + that same lock to wake a blocked transport operation. } + FInterruptList.Add(aConnection); + try + List.Add(aConnection); + except + FInterruptList.Remove(aConnection); + raise; + end; end; procedure TWSMessagePump.RemoveClient(aConnection: TWSClientConnection); begin + { Remove from the reader list first. When this returns the reader can no + longer start using the connection. Removal from FInterruptList then waits + for any in-progress termination wake before the caller may free it. } FList.Remove(aConnection); + FInterruptList.Remove(aConnection); end; procedure TWSMessagePump.SetInterval(AValue: Integer); @@ -654,6 +686,8 @@ function TWSMessagePump.CheckConnections: Boolean; constructor TWSMessagePump.Create(aOwner : TComponent); begin + inherited Create(aOwner); + FInterruptList:=TThreadList.Create; FList:=TThreadList.Create; FReads:=[]; FExceptions:=[]; @@ -662,15 +696,39 @@ constructor TWSMessagePump.Create(aOwner : TComponent); destructor TWSMessagePump.Destroy; begin + FreeAndNil(FInterruptList); FreeAndNil(FList); inherited; end; +procedure TWSMessagePump.InterruptConnections; +Var + aList : TList; + aClient: TWSClientConnection; + I : Integer; + +begin + aList:=FInterruptList.LockList; + try + for I:=0 to aList.Count-1 do + begin + aClient:=TWSClientConnection(aList.Items[I]); + if Assigned(aClient) then + if Assigned(aClient.ClientTransport) then + aClient.ClientTransport.InterruptRead; + end; + finally + FInterruptList.UnlockList; + end; +end; + procedure TWSMessagePump.ReadConnections; Var aList : TList; aClient: TWSClientConnection; + aWebSocketClient: TWebSocketClientConnection; + IncomingResult: TIncomingResult; I : Integer; begin @@ -678,11 +736,36 @@ procedure TWSMessagePump.ReadConnections; aList := List.LockList; try FReads:=[]; - for I := 0 to aList.Count - 1 do + { Keep the existing client-processing order. Deleting a closed client at + the current index makes the next client take that same index. } + I:=0; + while I0 then + GraceMs:=QWord(Interval)*2+10 + else + GraceMs:=MinStopGraceMs; + if GraceMsMaxStopGraceMs then + GraceMs:=MaxStopGraceMs; + + StartMs:=TThread.GetTickCount64; + while (not FThread.Finished) and + ((TThread.GetTickCount64-StartMs)