Commit 8e46916
Fix WebSocket polyfill throwing on close()/send() when CLOSING or CLOSED (#160)
[Created by Copilot on behalf of @bghgary]
## Problem
The WebSocket polyfill throws JavaScript errors in two places where the
[WHATWG WebSocket specification](https://websockets.spec.whatwg.org/)
requires different behavior:
### `close()` throws when already closing/closed
`WebSocket::Close` throws `Error: Close has already been called.` when
invoked on a socket whose `readyState` is already `CLOSING` or `CLOSED`.
Per the spec, `close()` in those states must be a silent no-op.
### `send()` throws when closing/closed
`WebSocket::Send` throws `Error: Websocket readyState is not open.` on
any non-OPEN state. Per the spec, `send()` should only throw
`InvalidStateError` when `readyState` is `CONNECTING`. When `CLOSING` or
`CLOSED`, the data is silently discarded (the spec also bumps
`bufferedAmount`, which this polyfill does not track).
Because the throws are synchronous from a JS call, a delayed or racing
call from an async callback surfaces as an **uncaught** exception that
terminates the JS runtime.
## Repro path
Observed on the macOS_Xcode164_Sanitizers leg of #148. Sequence (from
the existing multi-WebSocket test in
`Tests/UnitTests/Scripts/tests.ts`):
1. Test calls `ws.close()` on an open socket.
2. The native side transitions `readyState` -> `Closing` and issues the
close.
3. Before the `onclose` callback marshals to the JS thread, another path
(a pending `onmessage` handler, or a `catch` block on a failed `send`)
calls `ws.close()` again.
4. The second `close()` finds `readyState == Closing` and **throws**,
producing:
```
[Uncaught Error] close@[native code]
@app:///Scripts/tests.js:27799:22
```
The `send()` path is symmetric: a send scheduled between `close()` being
called and the `onclose` callback firing would throw and escape
uncaught.
## Fix
### `close()`
Replace the `throw` with an early `return`. No other state is touched,
so the first `close()` still drives the transition to `CLOSED` via the
normal callback path.
### `send()`
Split the single non-OPEN throw into two cases: throw on `CONNECTING`,
return silently on `CLOSING`/`CLOSED`.
## Scope
- Pure behavioral fix in the WebSocket polyfill. No changes outside
`Polyfills/WebSocket/Source/WebSocket.cpp`.
- No test changes — existing WebSocket tests already exercise these
paths and will stop intermittently failing once this lands.
- `bufferedAmount` tracking during the CLOSING/CLOSED send path is still
not implemented; out of scope for this fix.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 53f8411 commit 8e46916
1 file changed
Lines changed: 13 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
| 54 | + | |
55 | 55 | | |
| 56 | + | |
| 57 | + | |
56 | 58 | | |
57 | 59 | | |
58 | | - | |
| 60 | + | |
59 | 61 | | |
60 | 62 | | |
61 | 63 | | |
62 | 64 | | |
63 | 65 | | |
64 | 66 | | |
65 | 67 | | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
66 | 76 | | |
67 | 77 | | |
68 | | - | |
| 78 | + | |
69 | 79 | | |
70 | 80 | | |
71 | 81 | | |
| |||
0 commit comments