From 62a0b1d4a2fd1bec6a58abafdf13385508dd7823 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:13:02 +0000 Subject: [PATCH 1/3] Fix. --- .../Handlers/EchoWebSocketHandler.cs | 12 ++++++ .../BrowserWebSockets/BrowserInterop.cs | 8 ++-- .../BrowserWebSockets/BrowserWebSocket.cs | 6 --- .../tests/CloseTest.cs | 38 +++++++++++++++++++ src/mono/wasm/runtime/exports-internal.ts | 3 +- src/mono/wasm/runtime/web-socket.ts | 11 ++++++ 6 files changed, 68 insertions(+), 10 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..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 @@ -144,6 +144,18 @@ await socket.CloseAsync( { await Task.Delay(5000); } + else if (receivedMessage == ".receiveMessageAfterClose") + { + byte[] buffer = new byte[1024]; + string message = $"{receivedMessage} {DateTime.Now.ToString("HH:mm:ss")}"; + buffer = System.Text.Encoding.UTF8.GetBytes(message); + await socket.SendAsync( + new ArraySegment(buffer, 0, message.Length), + WebSocketMessageType.Text, + true, + CancellationToken.None); + 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/BrowserInterop.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs index 5ea45c2a7a2224..e535924a015817 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs @@ -19,11 +19,13 @@ internal static partial class BrowserInterop public static int GetReadyState(JSObject? webSocket) { if (webSocket == null || webSocket.IsDisposed) return -1; - int? readyState = webSocket.GetPropertyAsInt32("readyState"); - if (!readyState.HasValue) return -1; - return readyState.Value; + return BrowserInterop.WebSocketGetState(webSocket); } + [JSImport("INTERNAL.ws_get_state")] + public static partial int WebSocketGetState( + JSObject webSocket); + [JSImport("INTERNAL.ws_wasm_create")] public static partial JSObject WebSocketCreate( string uri, 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..660ee76d7870d0 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -367,6 +368,43 @@ await cws.SendAsync( } } + public static IEnumerable EchoServersSyncState => + EchoServers.SelectMany(server => new List + { + new object[] { server[0], true }, + new object[] { server[0], false } + }); + + [ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServersSyncState))] + public async Task CloseOutputAsync_ServerInitiated_CanReceiveAfterClose(Uri server, bool syncState) + { + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + { + var cts = new CancellationTokenSource(TimeOutMilliseconds); + await cws.SendAsync( + WebSocketData.GetBufferFromText(".receiveMessageAfterClose"), + WebSocketMessageType.Text, + true, + cts.Token); + + await Task.Delay(2000); + + if (syncState) + { + var state = cws.State; + Assert.Equal(WebSocketState.Open, state); + // should be able to receive after this sync + } + + 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); + + Assert.Contains(".receiveMessageAfterClose", message); + } + } + [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/exports-internal.ts b/src/mono/wasm/runtime/exports-internal.ts index 826180282e3bbc..b009da17c6c9bd 100644 --- a/src/mono/wasm/runtime/exports-internal.ts +++ b/src/mono/wasm/runtime/exports-internal.ts @@ -8,7 +8,7 @@ import { http_wasm_supports_streaming_response, http_wasm_create_abort_controler import { exportedRuntimeAPI, Module, runtimeHelpers } from "./globals"; import { get_property, set_property, has_property, get_typeof_property, get_global_this, dynamic_import } from "./invoke-js"; import { mono_wasm_stringify_as_error_with_stack } from "./logging"; -import { ws_wasm_create, ws_wasm_open, ws_wasm_send, ws_wasm_receive, ws_wasm_close, ws_wasm_abort } from "./web-socket"; +import { ws_wasm_create, ws_wasm_open, ws_wasm_send, ws_wasm_receive, ws_wasm_close, ws_wasm_abort, ws_get_state } from "./web-socket"; import { mono_wasm_get_loaded_files } from "./assets"; import { jiterpreter_dump_stats } from "./jiterpreter"; import { getOptions, applyOptions } from "./jiterpreter-support"; @@ -62,6 +62,7 @@ export function export_internal(): any { ws_wasm_receive, ws_wasm_close, ws_wasm_abort, + ws_get_state, // BrowserHttpHandler http_wasm_supports_streaming_response, diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index 5cf400aca536a9..9f82b8bff671a0 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -43,6 +43,17 @@ function verifyEnvironment() { } } +export function ws_get_state(ws: WebSocketExtension) : number +{ + if (ws.readyState != WebSocket.CLOSED) + return ws.readyState ?? -1; + const receive_event_queue = ws[wasm_ws_pending_receive_event_queue]; + const queued_events_count = receive_event_queue.getLength(); + if (queued_events_count == 0) + return ws.readyState ?? -1; + return WebSocket.OPEN; +} + export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension { verifyEnvironment(); mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`); From c399ffa824f639c17902b974737bcdda66d2b03b Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:53:08 +0000 Subject: [PATCH 2/3] Missing change - enqueue promises even when socket is closed. --- src/mono/wasm/runtime/web-socket.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index 9f82b8bff671a0..8e0726ec8da0e7 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -186,15 +186,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 c7e884367f4afdd033a40c09868754158716a0b8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 28 Mar 2024 12:09:19 +0000 Subject: [PATCH 3/3] More tests. --- .../System.Net.WebSockets.Client/tests/CloseTest.cs | 12 ++++++++---- src/mono/wasm/runtime/web-socket.ts | 8 ++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs index 660ee76d7870d0..1e99e356a87c6c 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -265,8 +265,8 @@ public async Task CloseOutputAsync_ClientInitiated_CanReceive_CanClose(Uri serve [ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] - [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] - public async Task CloseOutputAsync_ServerInitiated_CanReceive(Uri server) + [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServersWithSwitch))] + public async Task CloseOutputAsync_ServerInitiated_CanReceive(Uri server, bool delayReceiving) { string message = "Hello WebSockets!"; var expectedCloseStatus = WebSocketCloseStatus.NormalClosure; @@ -282,6 +282,10 @@ await cws.SendAsync( true, cts.Token); + // let server close the output before we request receiving + if (delayReceiving) + await Task.Delay(1000); + // Should be able to receive the message echoed by the server. var recvBuffer = new byte[100]; var segmentRecv = new ArraySegment(recvBuffer); @@ -368,7 +372,7 @@ await cws.SendAsync( } } - public static IEnumerable EchoServersSyncState => + public static IEnumerable EchoServersWithSwitch => EchoServers.SelectMany(server => new List { new object[] { server[0], true }, @@ -376,7 +380,7 @@ await cws.SendAsync( }); [ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] - [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServersSyncState))] + [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServersWithSwitch))] public async Task CloseOutputAsync_ServerInitiated_CanReceiveAfterClose(Uri server, bool syncState) { using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index 8e0726ec8da0e7..0f99b7e2eaf244 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -186,6 +186,14 @@ export function ws_wasm_receive(ws: WebSocketExtension, buffer_ptr: VoidPtr, buf return null; } + if (ws[wasm_ws_close_received]) { + 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;