From 1bb6a495e085ebedc57bda864085f7e68508ff2e Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 7 Mar 2024 18:37:19 +0100 Subject: [PATCH 1/3] @pavelsavara's version of fix. --- .../Handlers/EchoWebSocketHandler.cs | 16 ++++++++++ .../BrowserWebSockets/BrowserWebSocket.cs | 6 ---- .../tests/CloseTest.cs | 29 +++++++++++++++++++ src/mono/wasm/runtime/web-socket.ts | 9 ------ 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs index 8304f2d1156072..b0fbb93110468b 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs @@ -144,6 +144,22 @@ await socket.CloseAsync( { await Task.Delay(5000); } + else if (receivedMessage == ".receiveMessageAfterClose") + { + byte[] buffer = new byte[1024]; + string message1 = $"{receivedMessage} 1 {DateTime.Now.ToString("HH:mm:ss")}"; + buffer = System.Text.Encoding.UTF8.GetBytes(message1); + var stamp = DateTime.Now.ToString("HH:mm:ss"); + Console.WriteLine($"[{stamp}]: Sending message: {message1}"); + await socket.SendAsync( + new ArraySegment(buffer, 0, message1.Length), + WebSocketMessageType.Text, + true, + CancellationToken.None); + stamp = DateTime.Now.ToString("HH:mm:ss"); + Console.WriteLine($"[{stamp}] Closing the socket"); + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None); + } else if (socket.State == WebSocketState.Open) { sendMessage = true; diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs index 879d45ca0da57c..6c8f7a036ed971 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs @@ -385,12 +385,6 @@ private void CreateCore(Uri uri, List? requestedSubProtocols) #endif _closeStatus = (WebSocketCloseStatus)code; _closeStatusDescription = reason; - _closeReceived = true; - WebSocketState state = State; - if (state == WebSocketState.Connecting || state == WebSocketState.Open || state == WebSocketState.CloseSent) - { - FastState = WebSocketState.Closed; - } #if FEATURE_WASM_THREADS } //lock #endif diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs index 6f1e6faa490192..e8e4d847354e04 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -367,6 +367,35 @@ await cws.SendAsync( } } + [ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] + public async Task CloseOutputAsync_ServerInitiated_CanReceiveAfterClose(Uri server) + { + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + { + var cts = new CancellationTokenSource(TimeOutMilliseconds); + + await cws.SendAsync( + WebSocketData.GetBufferFromText(".receiveMessageAfterClose"), + WebSocketMessageType.Text, + true, + cts.Token); + + var recvBuffer = new ArraySegment(new byte[1024]); + await Task.Delay(2000); // even if we decrease to 100, it still fails with + // System.Net.WebSockets.WebSocketException : The WebSocket is in an invalid state ('Closed') for this operation. Valid states are: 'Open, CloseSent' + // in the manual demo we can wait even 6 sec and the message arrives just fine + var stamp = DateTime.Now.ToString("HH:mm:ss"); + Console.WriteLine($"[{stamp}] Attempting to receive data... cws.State={cws.State}"); + WebSocketReceiveResult recvResult = await cws.ReceiveAsync(recvBuffer, cts.Token); + var message = Encoding.UTF8.GetString(recvBuffer.ToArray(), 0, recvResult.Count); + stamp = DateTime.Now.ToString("HH:mm:ss"); + Console.WriteLine($"[{stamp}] Received message={message}"); + Assert.Contains(".shutdownAfterTwoMessages 1", message); + Console.WriteLine($"cws.State={cws.State}"); + } + } + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseOutputAsync_CloseDescriptionIsNull_Success(Uri server) diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index 5cf400aca536a9..811ab066a72086 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -175,15 +175,6 @@ export function ws_wasm_receive(ws: WebSocketExtension, buffer_ptr: VoidPtr, buf return null; } - const readyState = ws.readyState; - if (readyState == WebSocket.CLOSED) { - const receive_status_ptr = ws[wasm_ws_receive_status_ptr]; - setI32(receive_status_ptr, 0); // count - setI32(receive_status_ptr + 4, 2); // type:close - setI32(receive_status_ptr + 8, 1);// end_of_message: true - return null; - } - const { promise, promise_control } = createPromiseController(); const receive_promise_control = promise_control as ReceivePromiseControl; receive_promise_control.buffer_ptr = buffer_ptr; From e70c0a55e929e1b8521d64c8467240c94f956adb Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 8 Mar 2024 12:09:12 +0100 Subject: [PATCH 2/3] Logging improvements. --- .../Handlers/EchoWebSocketHandler.cs | 6 ++++-- .../tests/CloseTest.cs | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs index b0fbb93110468b..0279c42500177e 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs @@ -150,15 +150,17 @@ await socket.CloseAsync( string message1 = $"{receivedMessage} 1 {DateTime.Now.ToString("HH:mm:ss")}"; buffer = System.Text.Encoding.UTF8.GetBytes(message1); var stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}]: Sending message: {message1}"); + Console.WriteLine($"[{stamp}] Server => Sending message: {message1}"); await socket.SendAsync( new ArraySegment(buffer, 0, message1.Length), WebSocketMessageType.Text, true, CancellationToken.None); stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Closing the socket"); + Console.WriteLine($"[{stamp}] Server => Closing the socket"); await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None); + stamp = DateTime.Now.ToString("HH:mm:ss"); + Console.WriteLine($"[{stamp}] Server => Socket got closed"); } else if (socket.State == WebSocketState.Open) { diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs index e8e4d847354e04..a4cfbd56997cea 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -374,23 +374,27 @@ public async Task CloseOutputAsync_ServerInitiated_CanReceiveAfterClose(Uri serv using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); - await cws.SendAsync( WebSocketData.GetBufferFromText(".receiveMessageAfterClose"), WebSocketMessageType.Text, true, cts.Token); - var recvBuffer = new ArraySegment(new byte[1024]); - await Task.Delay(2000); // even if we decrease to 100, it still fails with + int delay = 2000; + var stamp = DateTime.Now.ToString("HH:mm:ss"); + Console.WriteLine($"[{stamp}] Client -> '.receiveMessageAfterClose' was sent, waiting {delay}ms. cws.State={cws.State}"); + await Task.Delay(delay); // even if we decrease to 100, it still fails with // System.Net.WebSockets.WebSocketException : The WebSocket is in an invalid state ('Closed') for this operation. Valid states are: 'Open, CloseSent' // in the manual demo we can wait even 6 sec and the message arrives just fine - var stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Attempting to receive data... cws.State={cws.State}"); + + stamp = DateTime.Now.ToString("HH:mm:ss"); + Console.WriteLine($"[{stamp}] Client -> Attempting to receive data... cws.State={cws.State}"); + var recvBuffer = new ArraySegment(new byte[1024]); WebSocketReceiveResult recvResult = await cws.ReceiveAsync(recvBuffer, cts.Token); var message = Encoding.UTF8.GetString(recvBuffer.ToArray(), 0, recvResult.Count); + stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Received message={message}"); + Console.WriteLine($"[{stamp}] Client -> Received message={message}"); Assert.Contains(".shutdownAfterTwoMessages 1", message); Console.WriteLine($"cws.State={cws.State}"); } From 42614a0c8ceadb136198283e4d0794550e5f8a35 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 8 Mar 2024 13:49:53 +0100 Subject: [PATCH 3/3] Correct tests. --- .../Handlers/EchoWebSocketHandler.cs | 12 ++--- .../tests/CloseTest.cs | 46 ++++++++++++++----- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs index 0279c42500177e..a290ce63bd4ffb 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs @@ -147,20 +147,14 @@ await socket.CloseAsync( else if (receivedMessage == ".receiveMessageAfterClose") { byte[] buffer = new byte[1024]; - string message1 = $"{receivedMessage} 1 {DateTime.Now.ToString("HH:mm:ss")}"; - buffer = System.Text.Encoding.UTF8.GetBytes(message1); - var stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Server => Sending message: {message1}"); + string message = $"{receivedMessage} {DateTime.Now.ToString("HH:mm:ss")}"; + buffer = System.Text.Encoding.UTF8.GetBytes(message); await socket.SendAsync( - new ArraySegment(buffer, 0, message1.Length), + new ArraySegment(buffer, 0, message.Length), WebSocketMessageType.Text, true, CancellationToken.None); - stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Server => Closing the socket"); await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None); - stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Server => Socket got closed"); } else if (socket.State == WebSocketState.Open) { diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs index a4cfbd56997cea..c8bcc8397e6274 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -370,6 +370,31 @@ await cws.SendAsync( [ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseOutputAsync_ServerInitiated_CanReceiveAfterClose(Uri server) + { + await ReceiveMessageAfterClose(server, false); + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] + public async Task CloseOutputAsync_ServerInitiated_CannotReceiveAfterCloseAndStateSync(Uri server) + { + try + { + await ReceiveMessageAfterClose(server, true); + } + catch (WebSocketException e) + { + Assert.Equal(WebSocketError.InvalidState, e.WebSocketErrorCode); + return; + } + catch (Exception e) + { + Assert.True(false, $"Unexpected exception: {e}"); + } + Assert.True(false, "Expected WebSocketException not thrown."); + } + + private async Task ReceiveMessageAfterClose(Uri server, bool syncState) { using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { @@ -380,23 +405,20 @@ await cws.SendAsync( true, cts.Token); - int delay = 2000; - var stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Client -> '.receiveMessageAfterClose' was sent, waiting {delay}ms. cws.State={cws.State}"); - await Task.Delay(delay); // even if we decrease to 100, it still fails with - // System.Net.WebSockets.WebSocketException : The WebSocket is in an invalid state ('Closed') for this operation. Valid states are: 'Open, CloseSent' - // in the manual demo we can wait even 6 sec and the message arrives just fine + await Task.Delay(2000); + + if (syncState) + { + var state = cws.State; + Assert.Equal(WebSocketState.Closed, state); + // should not be able to receive after this sync + } - stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Client -> Attempting to receive data... cws.State={cws.State}"); var recvBuffer = new ArraySegment(new byte[1024]); WebSocketReceiveResult recvResult = await cws.ReceiveAsync(recvBuffer, cts.Token); var message = Encoding.UTF8.GetString(recvBuffer.ToArray(), 0, recvResult.Count); - stamp = DateTime.Now.ToString("HH:mm:ss"); - Console.WriteLine($"[{stamp}] Client -> Received message={message}"); - Assert.Contains(".shutdownAfterTwoMessages 1", message); - Console.WriteLine($"cws.State={cws.State}"); + Assert.Contains(".receiveMessageAfterClose", message); } }