Skip to content

Commit f36dd88

Browse files
authored
[release/8.0][browser] WebSocket works differently depending on if we look up its state or not (#99673)
* Fix. * Missing change - enqueue promises even when socket is closed. * More tests.
1 parent ab416e9 commit f36dd88

File tree

6 files changed

+75
-14
lines changed

6 files changed

+75
-14
lines changed

src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ await socket.CloseAsync(
144144
{
145145
await Task.Delay(5000);
146146
}
147+
else if (receivedMessage == ".receiveMessageAfterClose")
148+
{
149+
byte[] buffer = new byte[1024];
150+
string message = $"{receivedMessage} {DateTime.Now.ToString("HH:mm:ss")}";
151+
buffer = System.Text.Encoding.UTF8.GetBytes(message);
152+
await socket.SendAsync(
153+
new ArraySegment<byte>(buffer, 0, message.Length),
154+
WebSocketMessageType.Text,
155+
true,
156+
CancellationToken.None);
157+
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None);
158+
}
147159
else if (socket.State == WebSocketState.Open)
148160
{
149161
sendMessage = true;

src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ internal static partial class BrowserInterop
1919
public static int GetReadyState(JSObject? webSocket)
2020
{
2121
if (webSocket == null || webSocket.IsDisposed) return -1;
22-
int? readyState = webSocket.GetPropertyAsInt32("readyState");
23-
if (!readyState.HasValue) return -1;
24-
return readyState.Value;
22+
return BrowserInterop.WebSocketGetState(webSocket);
2523
}
2624

25+
[JSImport("INTERNAL.ws_get_state")]
26+
public static partial int WebSocketGetState(
27+
JSObject webSocket);
28+
2729
[JSImport("INTERNAL.ws_wasm_create")]
2830
public static partial JSObject WebSocketCreate(
2931
string uri,

src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,12 +385,6 @@ private void CreateCore(Uri uri, List<string>? requestedSubProtocols)
385385
#endif
386386
_closeStatus = (WebSocketCloseStatus)code;
387387
_closeStatusDescription = reason;
388-
_closeReceived = true;
389-
WebSocketState state = State;
390-
if (state == WebSocketState.Connecting || state == WebSocketState.Open || state == WebSocketState.CloseSent)
391-
{
392-
FastState = WebSocketState.Closed;
393-
}
394388
#if FEATURE_WASM_THREADS
395389
} //lock
396390
#endif

src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Text;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using System.Linq;
1112

1213
using Xunit;
1314
using Xunit.Abstractions;
@@ -264,8 +265,8 @@ public async Task CloseOutputAsync_ClientInitiated_CanReceive_CanClose(Uri serve
264265

265266
[ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
266267
[OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))]
267-
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
268-
public async Task CloseOutputAsync_ServerInitiated_CanReceive(Uri server)
268+
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServersWithSwitch))]
269+
public async Task CloseOutputAsync_ServerInitiated_CanReceive(Uri server, bool delayReceiving)
269270
{
270271
string message = "Hello WebSockets!";
271272
var expectedCloseStatus = WebSocketCloseStatus.NormalClosure;
@@ -281,6 +282,10 @@ await cws.SendAsync(
281282
true,
282283
cts.Token);
283284

285+
// let server close the output before we request receiving
286+
if (delayReceiving)
287+
await Task.Delay(1000);
288+
284289
// Should be able to receive the message echoed by the server.
285290
var recvBuffer = new byte[100];
286291
var segmentRecv = new ArraySegment<byte>(recvBuffer);
@@ -367,6 +372,43 @@ await cws.SendAsync(
367372
}
368373
}
369374

375+
public static IEnumerable<object[]> EchoServersWithSwitch =>
376+
EchoServers.SelectMany(server => new List<object[]>
377+
{
378+
new object[] { server[0], true },
379+
new object[] { server[0], false }
380+
});
381+
382+
[ActiveIssue("https://github.com/dotnet/runtime/issues/28957", typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
383+
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServersWithSwitch))]
384+
public async Task CloseOutputAsync_ServerInitiated_CanReceiveAfterClose(Uri server, bool syncState)
385+
{
386+
using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output))
387+
{
388+
var cts = new CancellationTokenSource(TimeOutMilliseconds);
389+
await cws.SendAsync(
390+
WebSocketData.GetBufferFromText(".receiveMessageAfterClose"),
391+
WebSocketMessageType.Text,
392+
true,
393+
cts.Token);
394+
395+
await Task.Delay(2000);
396+
397+
if (syncState)
398+
{
399+
var state = cws.State;
400+
Assert.Equal(WebSocketState.Open, state);
401+
// should be able to receive after this sync
402+
}
403+
404+
var recvBuffer = new ArraySegment<byte>(new byte[1024]);
405+
WebSocketReceiveResult recvResult = await cws.ReceiveAsync(recvBuffer, cts.Token);
406+
var message = Encoding.UTF8.GetString(recvBuffer.ToArray(), 0, recvResult.Count);
407+
408+
Assert.Contains(".receiveMessageAfterClose", message);
409+
}
410+
}
411+
370412
[OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))]
371413
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
372414
public async Task CloseOutputAsync_CloseDescriptionIsNull_Success(Uri server)

src/mono/wasm/runtime/exports-internal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { http_wasm_supports_streaming_response, http_wasm_create_abort_controler
88
import { exportedRuntimeAPI, Module, runtimeHelpers } from "./globals";
99
import { get_property, set_property, has_property, get_typeof_property, get_global_this, dynamic_import } from "./invoke-js";
1010
import { mono_wasm_stringify_as_error_with_stack } from "./logging";
11-
import { ws_wasm_create, ws_wasm_open, ws_wasm_send, ws_wasm_receive, ws_wasm_close, ws_wasm_abort } from "./web-socket";
11+
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";
1212
import { mono_wasm_get_loaded_files } from "./assets";
1313
import { jiterpreter_dump_stats } from "./jiterpreter";
1414
import { getOptions, applyOptions } from "./jiterpreter-support";
@@ -62,6 +62,7 @@ export function export_internal(): any {
6262
ws_wasm_receive,
6363
ws_wasm_close,
6464
ws_wasm_abort,
65+
ws_get_state,
6566

6667
// BrowserHttpHandler
6768
http_wasm_supports_streaming_response,

src/mono/wasm/runtime/web-socket.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ function verifyEnvironment() {
4343
}
4444
}
4545

46+
export function ws_get_state(ws: WebSocketExtension) : number
47+
{
48+
if (ws.readyState != WebSocket.CLOSED)
49+
return ws.readyState ?? -1;
50+
const receive_event_queue = ws[wasm_ws_pending_receive_event_queue];
51+
const queued_events_count = receive_event_queue.getLength();
52+
if (queued_events_count == 0)
53+
return ws.readyState ?? -1;
54+
return WebSocket.OPEN;
55+
}
56+
4657
export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension {
4758
verifyEnvironment();
4859
mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`);
@@ -175,8 +186,7 @@ export function ws_wasm_receive(ws: WebSocketExtension, buffer_ptr: VoidPtr, buf
175186
return null;
176187
}
177188

178-
const readyState = ws.readyState;
179-
if (readyState == WebSocket.CLOSED) {
189+
if (ws[wasm_ws_close_received]) {
180190
const receive_status_ptr = ws[wasm_ws_receive_status_ptr];
181191
setI32(receive_status_ptr, 0); // count
182192
setI32(<any>receive_status_ptr + 4, 2); // type:close

0 commit comments

Comments
 (0)